Commit 9e234d4208

Andrew Kelley <superjoe30@gmail.com>
2017-10-31 09:47:55
breaking change to std.io API
* Merge io.InStream and io.OutStream into io.File * Introduce io.OutStream and io.InStream interfaces - io.File implements both of these * Move mem.IncrementingAllocator to heap.IncrementingAllocator Instead of: ``` %return std.io.stderr.printf("hello\n"); ``` now do: ``` std.debug.warn("hello\n"); ``` To print to stdout, see `io.getStdOut()`. * Rename std.ArrayList.resizeDown to std.ArrayList.shrink.
1 parent 7a96aca
example/cat/main.zig
@@ -2,47 +2,52 @@ const std = @import("std");
 const io = std.io;
 const mem = std.mem;
 const os = std.os;
+const warn = std.debug.warn;
 
 pub fn main() -> %void {
     const allocator = &std.debug.global_allocator;
     var args_it = os.args();
     const exe = %return unwrapArg(??args_it.next(allocator));
     var catted_anything = false;
+    var stdout_file = %return io.getStdOut();
+    const stdout = &stdout_file.out_stream;
+
     while (args_it.next(allocator)) |arg_or_err| {
         const arg = %return unwrapArg(arg_or_err);
         if (mem.eql(u8, arg, "-")) {
             catted_anything = true;
-            %return cat_stream(&io.stdin);
+            var stdin_file = %return io.getStdIn();
+            %return cat_stream(stdout, &stdin_file.in_stream);
         } else if (arg[0] == '-') {
             return usage(exe);
         } else {
-            var is = io.InStream.open(arg, null) %% |err| {
-                %%io.stderr.printf("Unable to open file: {}\n", @errorName(err));
+            var file = io.File.openRead(arg, null) %% |err| {
+                warn("Unable to open file: {}\n", @errorName(err));
                 return err;
             };
-            defer is.close();
+            defer file.close();
 
             catted_anything = true;
-            %return cat_stream(&is);
+            %return cat_stream(stdout, &file.in_stream);
         }
     }
     if (!catted_anything) {
-        %return cat_stream(&io.stdin);
+        var stdin_file = %return io.getStdIn();
+        %return cat_stream(stdout, &stdin_file.in_stream);
     }
-    %return io.stdout.flush();
 }
 
 fn usage(exe: []const u8) -> %void {
-    %%io.stderr.printf("Usage: {} [FILE]...\n", exe);
+    warn("Usage: {} [FILE]...\n", exe);
     return error.Invalid;
 }
 
-fn cat_stream(is: &io.InStream) -> %void {
+fn cat_stream(stdout: &io.OutStream, is: &io.InStream) -> %void {
     var buf: [1024 * 4]u8 = undefined;
 
     while (true) {
         const bytes_read = is.read(buf[0..]) %% |err| {
-            %%io.stderr.printf("Unable to read from stream: {}\n", @errorName(err));
+            warn("Unable to read from stream: {}\n", @errorName(err));
             return err;
         };
 
@@ -50,8 +55,8 @@ fn cat_stream(is: &io.InStream) -> %void {
             break;
         }
 
-        io.stdout.write(buf[0..bytes_read]) %% |err| {
-            %%io.stderr.printf("Unable to write to stdout: {}\n", @errorName(err));
+        stdout.write(buf[0..bytes_read]) %% |err| {
+            warn("Unable to write to stdout: {}\n", @errorName(err));
             return err;
         };
     }
@@ -59,7 +64,7 @@ fn cat_stream(is: &io.InStream) -> %void {
 
 fn unwrapArg(arg: %[]u8) -> %[]u8 {
     return arg %% |err| {
-        %%io.stderr.printf("Unable to parse command line: {}\n", err);
+        warn("Unable to parse command line: {}\n", err);
         return err;
     };
 }
example/guess_number/main.zig
@@ -5,7 +5,13 @@ const Rand = std.rand.Rand;
 const os = std.os;
 
 pub fn main() -> %void {
-    %%io.stdout.printf("Welcome to the Guess Number Game in Zig.\n");
+    var stdout_file = %return io.getStdOut();
+    const stdout = &stdout_file.out_stream;
+
+    var stdin_file = %return io.getStdIn();
+    const stdin = &stdin_file.in_stream;
+
+    %return stdout.print("Welcome to the Guess Number Game in Zig.\n");
 
     var seed_bytes: [@sizeOf(usize)]u8 = undefined;
     %%os.getRandomBytes(seed_bytes[0..]);
@@ -15,24 +21,24 @@ pub fn main() -> %void {
     const answer = rand.range(u8, 0, 100) + 1;
 
     while (true) {
-        %%io.stdout.printf("\nGuess a number between 1 and 100: ");
+        %return stdout.print("\nGuess a number between 1 and 100: ");
         var line_buf : [20]u8 = undefined;
 
-        const line_len = io.stdin.read(line_buf[0..]) %% |err| {
-            %%io.stdout.printf("Unable to read from stdin: {}\n", @errorName(err));
+        const line_len = stdin.read(line_buf[0..]) %% |err| {
+            %return stdout.print("Unable to read from stdin: {}\n", @errorName(err));
             return err;
         };
 
         const guess = fmt.parseUnsigned(u8, line_buf[0..line_len - 1], 10) %% {
-            %%io.stdout.printf("Invalid number.\n");
+            %return stdout.print("Invalid number.\n");
             continue;
         };
         if (guess > answer) {
-            %%io.stdout.printf("Guess lower.\n");
+            %return stdout.print("Guess lower.\n");
         } else if (guess < answer) {
-            %%io.stdout.printf("Guess higher.\n");
+            %return stdout.print("Guess higher.\n");
         } else {
-            %%io.stdout.printf("You win!\n");
+            %return stdout.print("You win!\n");
             return;
         }
     }
example/hello_world/hello.zig
@@ -1,5 +1,10 @@
-const io = @import("std").io;
+const std = @import("std");
 
 pub fn main() -> %void {
-    %return io.stdout.printf("Hello, world!\n");
+    // If this program is run without stdout attached, exit with an error.
+    var stdout_file = %return std.io.getStdOut();
+    const stdout = &stdout_file.out_stream;
+    // If this program encounters pipe failure when printing to stdout, exit
+    // with an error.
+    %return stdout.print("Hello, world!\n");
 }
std/fmt/index.zig
@@ -19,10 +19,10 @@ const State = enum { // TODO put inside format function and make sure the name a
 };
 
 /// Renders fmt string with args, calling output with slices of bytes.
-/// Return false from output function and output will not be called again.
-/// Returns false if output ever returned false, true otherwise.
-pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
-    comptime fmt: []const u8, args: ...) -> bool
+/// If `output` returns an error, the error is returned from `format` and
+/// `output` is not called again.
+pub fn format(context: var, output: fn(@typeOf(context), []const u8)->%void,
+    comptime fmt: []const u8, args: ...) -> %void
 {
     comptime var start_index = 0;
     comptime var state = State.Start;
@@ -38,15 +38,13 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
                 '{' => {
                     // TODO if you make this an if statement with `and` then it breaks
                     if (start_index < i) {
-                        if (!output(context, fmt[start_index..i]))
-                            return false;
+                        %return output(context, fmt[start_index..i]);
                     }
                     state = State.OpenBrace;
                 },
                 '}' => {
                     if (start_index < i) {
-                        if (!output(context, fmt[start_index..i]))
-                            return false;
+                        %return output(context, fmt[start_index..i]);
                     }
                     state = State.CloseBrace;
                 },
@@ -58,8 +56,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
                     start_index = i;
                 },
                 '}' => {
-                    if (!formatValue(args[next_arg], context, output))
-                        return false;
+                    %return formatValue(args[next_arg], context, output);
                     next_arg += 1;
                     state = State.Start;
                     start_index = i + 1;
@@ -109,8 +106,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
             },
             State.Integer => switch (c) {
                 '}' => {
-                    if (!formatInt(args[next_arg], radix, uppercase, width, context, output))
-                        return false;
+                    %return formatInt(args[next_arg], radix, uppercase, width, context, output);
                     next_arg += 1;
                     state = State.Start;
                     start_index = i + 1;
@@ -124,8 +120,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
             State.IntegerWidth => switch (c) {
                 '}' => {
                     width = comptime %%parseUnsigned(usize, fmt[width_start..i], 10);
-                    if (!formatInt(args[next_arg], radix, uppercase, width, context, output))
-                        return false;
+                    %return formatInt(args[next_arg], radix, uppercase, width, context, output);
                     next_arg += 1;
                     state = State.Start;
                     start_index = i + 1;
@@ -136,8 +131,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
             State.BufWidth => switch (c) {
                 '}' => {
                     width = comptime %%parseUnsigned(usize, fmt[width_start..i], 10);
-                    if (!formatBuf(args[next_arg], width, context, output))
-                        return false;
+                    %return formatBuf(args[next_arg], width, context, output);
                     next_arg += 1;
                     state = State.Start;
                     start_index = i + 1;
@@ -147,8 +141,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
             },
             State.Character => switch (c) {
                 '}' => {
-                    if (!formatAsciiChar(args[next_arg], context, output))
-                        return false;
+                    %return formatAsciiChar(args[next_arg], context, output);
                     next_arg += 1;
                     state = State.Start;
                     start_index = i + 1;
@@ -166,14 +159,11 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
         }
     }
     if (start_index < fmt.len) {
-        if (!output(context, fmt[start_index..]))
-            return false;
+        %return output(context, fmt[start_index..]);
     }
-
-    return true;
 }
 
-pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool {
+pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void {
     const T = @typeOf(value);
     switch (@typeId(T)) {
         builtin.TypeId.Int => {
@@ -203,8 +193,7 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons
             }
         },
         builtin.TypeId.Error => {
-            if (!output(context, "error."))
-                return false;
+            %return output(context, "error.");
             return output(context, @errorName(value));
         },
         builtin.TypeId.Pointer => {
@@ -223,27 +212,23 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons
     }
 }
 
-pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool {
+pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void {
     return output(context, (&c)[0..1]);
 }
 
 pub fn formatBuf(buf: []const u8, width: usize,
-    context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
+    context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void
 {
-    if (!output(context, buf))
-        return false;
+    %return output(context, buf);
 
-    var leftover_padding = if (width > buf.len) (width - buf.len) else return true;
+    var leftover_padding = if (width > buf.len) (width - buf.len) else return;
     const pad_byte: u8 = ' ';
     while (leftover_padding > 0) : (leftover_padding -= 1) {
-        if (!output(context, (&pad_byte)[0..1]))
-            return false;
+        %return output(context, (&pad_byte)[0..1]);
     }
-
-    return true;
 }
 
-pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool {
+pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void {
     var x = f64(value);
 
     // Errol doesn't handle these special cases.
@@ -251,8 +236,7 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons
         return output(context, "NaN");
     }
     if (math.signbit(x)) {
-        if (!output(context, "-"))
-            return false;
+        %return output(context, "-");
         x = -x;
     }
     if (math.isPositiveInf(x)) {
@@ -264,34 +248,27 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons
 
     var buffer: [32]u8 = undefined;
     const float_decimal = errol3(x, buffer[0..]);
-    if (!output(context, float_decimal.digits[0..1]))
-        return false;
-    if (!output(context, "."))
-        return false;
+    %return output(context, float_decimal.digits[0..1]);
+    %return output(context, ".");
     if (float_decimal.digits.len > 1) {
         const num_digits = if (@typeOf(value) == f32) {
             math.min(usize(9), float_decimal.digits.len)
         } else {
             float_decimal.digits.len
         };
-        if (!output(context, float_decimal.digits[1 .. num_digits]))
-            return false;
+        %return output(context, float_decimal.digits[1 .. num_digits]);
     } else {
-        if (!output(context, "0"))
-            return false;
+        %return output(context, "0");
     }
 
     if (float_decimal.exp != 1) {
-        if (!output(context, "e"))
-            return false;
-        if (!formatInt(float_decimal.exp - 1, 10, false, 0, context, output))
-            return false;
+        %return output(context, "e");
+        %return formatInt(float_decimal.exp - 1, 10, false, 0, context, output);
     }
-    return true;
 }
 
 pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize,
-    context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
+    context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void
 {
     if (@typeOf(value).is_signed) {
         return formatIntSigned(value, base, uppercase, width, context, output);
@@ -301,13 +278,12 @@ pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize,
 }
 
 fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize,
-    context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
+    context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void
 {
     const uint = @IntType(false, @typeOf(value).bit_count);
     if (value < 0) {
         const minus_sign: u8 = '-';
-        if (!output(context, (&minus_sign)[0..1]))
-            return false;
+        %return output(context, (&minus_sign)[0..1]);
         const new_value = uint(-(value + 1)) + 1;
         const new_width = if (width == 0) 0 else (width - 1);
         return formatIntUnsigned(new_value, base, uppercase, new_width, context, output);
@@ -315,8 +291,7 @@ fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize,
         return formatIntUnsigned(uint(value), base, uppercase, width, context, output);
     } else {
         const plus_sign: u8 = '+';
-        if (!output(context, (&plus_sign)[0..1]))
-            return false;
+        %return output(context, (&plus_sign)[0..1]);
         const new_value = uint(value);
         const new_width = if (width == 0) 0 else (width - 1);
         return formatIntUnsigned(new_value, base, uppercase, new_width, context, output);
@@ -324,7 +299,7 @@ fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize,
 }
 
 fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize,
-    context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
+    context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void
 {
     // max_int_digits accounts for the minus sign. when printing an unsigned
     // number we don't need to do that.
@@ -348,8 +323,7 @@ fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize,
         const zero_byte: u8 = '0';
         var leftover_padding = padding - index;
         while (true) {
-            if (!output(context, (&zero_byte)[0..1]))
-                return false;
+            %return output(context, (&zero_byte)[0..1]);
             leftover_padding -= 1;
             if (leftover_padding == 0)
                 break;
@@ -368,17 +342,16 @@ pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, width:
         .out_buf = out_buf,
         .index = 0,
     };
-    _ = formatInt(value, base, uppercase, width, &context, formatIntCallback);
+    %%formatInt(value, base, uppercase, width, &context, formatIntCallback);
     return context.index;
 }
 const FormatIntBuf = struct {
     out_buf: []u8,
     index: usize,
 };
-fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) -> bool {
+fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) -> %void {
     mem.copy(u8, context.out_buf[context.index..], bytes);
     context.index += bytes.len;
-    return true;
 }
 
 pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) -> %T {
@@ -440,28 +413,27 @@ const BufPrintContext = struct {
     remaining: []u8,
 };
 
-fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> bool {
+fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> %void {
     mem.copy(u8, context.remaining, bytes);
     context.remaining = context.remaining[bytes.len..];
-    return true;
 }
 
 pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) -> []u8 {
     var context = BufPrintContext { .remaining = buf, };
-    _ = format(&context, bufPrintWrite, fmt, args);
+    %%format(&context, bufPrintWrite, fmt, args);
     return buf[0..buf.len - context.remaining.len];
 }
 
 pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) -> %[]u8 {
     var size: usize = 0;
-    _ = format(&size, countSize, fmt, args);
+    // Cannot fail because `countSize` cannot fail.
+    %%format(&size, countSize, fmt, args);
     const buf = %return allocator.alloc(u8, size);
     return bufPrint(buf, fmt, args);
 }
 
-fn countSize(size: &usize, bytes: []const u8) -> bool {
+fn countSize(size: &usize, bytes: []const u8) -> %void {
     *size += bytes.len;
-    return true;
 }
 
 test "buf print int" {
std/os/child_process.zig
@@ -28,9 +28,9 @@ pub const ChildProcess = struct {
 
     pub allocator: &mem.Allocator,
 
-    pub stdin: ?&io.OutStream,
-    pub stdout: ?&io.InStream,
-    pub stderr: ?&io.InStream,
+    pub stdin: ?io.File,
+    pub stdout: ?io.File,
+    pub stderr: ?io.File,
 
     pub term: ?%Term,
 
@@ -250,17 +250,17 @@ pub const ChildProcess = struct {
     }
 
     fn cleanupStreams(self: &ChildProcess) {
-        if (self.stdin) |stdin| { stdin.close(); self.allocator.destroy(stdin); self.stdin = null; }
-        if (self.stdout) |stdout| { stdout.close(); self.allocator.destroy(stdout); self.stdout = null; }
-        if (self.stderr) |stderr| { stderr.close(); self.allocator.destroy(stderr); self.stderr = null; }
+        if (self.stdin) |*stdin| { stdin.close(); self.stdin = null; }
+        if (self.stdout) |*stdout| { stdout.close(); self.stdout = null; }
+        if (self.stderr) |*stderr| { stderr.close(); self.stderr = null; }
     }
 
     fn cleanupAfterWait(self: &ChildProcess, status: i32) -> %Term {
         children_nodes.remove(&self.llnode);
 
         defer {
-            os.posixClose(self.err_pipe[0]);
-            os.posixClose(self.err_pipe[1]);
+            os.close(self.err_pipe[0]);
+            os.close(self.err_pipe[1]);
         };
 
         // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after
@@ -310,7 +310,7 @@ pub const ChildProcess = struct {
         } else {
             undefined
         };
-        defer { if (any_ignore) os.posixClose(dev_null_fd); };
+        defer { if (any_ignore) os.close(dev_null_fd); };
 
         var env_map_owned: BufMap = undefined;
         var we_own_env_map: bool = undefined;
@@ -329,27 +329,6 @@ pub const ChildProcess = struct {
         const err_pipe = %return makePipe();
         %defer destroyPipe(err_pipe);
 
-        const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) {
-            %return self.allocator.create(io.OutStream)
-        } else {
-            null
-        };
-        %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
-
-        const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
-            %return self.allocator.create(io.InStream)
-        } else {
-            null
-        };
-        %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
-
-        const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
-            %return self.allocator.create(io.InStream)
-        } else {
-            null
-        };
-        %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
-
         block_SIGCHLD();
         const pid_result = posix.fork();
         const pid_err = posix.getErrno(pid_result);
@@ -390,46 +369,35 @@ pub const ChildProcess = struct {
 
         // we are the parent
         const pid = i32(pid_result);
-        if (stdin_ptr) |outstream| {
-            *outstream = io.OutStream {
-                .fd = stdin_pipe[1],
-                .handle = {},
-                .handle_id = {},
-                .buffer = undefined,
-                .index = 0,
-            };
+        if (self.stdin_behavior == StdIo.Pipe) {
+            self.stdin = io.File.openHandle(stdin_pipe[1]);
+        } else {
+            self.stdin = null;
         }
-        if (stdout_ptr) |instream| {
-            *instream = io.InStream {
-                .fd = stdout_pipe[0],
-                .handle = {},
-                .handle_id = {},
-            };
+        if (self.stdout_behavior == StdIo.Pipe) {
+            self.stdout = io.File.openHandle(stdout_pipe[0]);
+        } else {
+            self.stdout = null;
         }
-        if (stderr_ptr) |instream| {
-            *instream = io.InStream {
-                .fd = stderr_pipe[0],
-                .handle = {},
-                .handle_id = {},
-            };
+        if (self.stderr_behavior == StdIo.Pipe) {
+            self.stderr = io.File.openHandle(stderr_pipe[0]);
+        } else {
+            self.stderr = null;
         }
 
         self.pid = pid;
         self.err_pipe = err_pipe;
         self.llnode = LinkedList(&ChildProcess).Node.init(self);
         self.term = null;
-        self.stdin = stdin_ptr;
-        self.stdout = stdout_ptr;
-        self.stderr = stderr_ptr;
 
         // TODO make this atomic so it works even with threads
         children_nodes.prepend(&self.llnode);
 
         restore_SIGCHLD();
 
-        if (self.stdin_behavior == StdIo.Pipe) { os.posixClose(stdin_pipe[0]); }
-        if (self.stdout_behavior == StdIo.Pipe) { os.posixClose(stdout_pipe[1]); }
-        if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }
+        if (self.stdin_behavior == StdIo.Pipe) { os.close(stdin_pipe[0]); }
+        if (self.stdout_behavior == StdIo.Pipe) { os.close(stdout_pipe[1]); }
+        if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); }
     }
 
     fn spawnWindows(self: &ChildProcess) -> %void {
@@ -509,27 +477,6 @@ pub const ChildProcess = struct {
         }
         %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); };
 
-        const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) {
-            %return self.allocator.create(io.OutStream)
-        } else {
-            null
-        };
-        %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
-
-        const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
-            %return self.allocator.create(io.InStream)
-        } else {
-            null
-        };
-        %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
-
-        const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
-            %return self.allocator.create(io.InStream)
-        } else {
-            null
-        };
-        %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
-
         const cmd_line = %return windowsCreateCommandLine(self.allocator, self.argv);
         defer self.allocator.free(cmd_line);
 
@@ -609,36 +556,25 @@ pub const ChildProcess = struct {
             }
         };
 
-        if (stdin_ptr) |outstream| {
-            *outstream = io.OutStream {
-                .fd = {},
-                .handle = g_hChildStd_IN_Wr,
-                .handle_id = undefined,
-                .buffer = undefined,
-                .index = 0,
-            };
+        if (self.stdin_behavior == StdIo.Pipe) {
+            self.stdin = io.File.openHandle(g_hChildStd_IN_Wr);
+        } else {
+            self.stdin = null;
         }
-        if (stdout_ptr) |instream| {
-            *instream = io.InStream {
-                .fd = {},
-                .handle = g_hChildStd_OUT_Rd,
-                .handle_id = undefined,
-            };
+        if (self.stdout_behavior == StdIo.Pipe) {
+            self.stdout = io.File.openHandle(g_hChildStd_OUT_Rd);
+        } else {
+            self.stdout = null;
         }
-        if (stderr_ptr) |instream| {
-            *instream = io.InStream {
-                .fd = {},
-                .handle = g_hChildStd_ERR_Rd,
-                .handle_id = undefined,
-            };
+        if (self.stderr_behavior == StdIo.Pipe) {
+            self.stderr = io.File.openHandle(g_hChildStd_ERR_Rd);
+        } else {
+            self.stderr = null;
         }
 
         self.handle = piProcInfo.hProcess;
         self.thread_handle = piProcInfo.hThread;
         self.term = null;
-        self.stdin = stdin_ptr;
-        self.stdout = stdout_ptr;
-        self.stderr = stderr_ptr;
 
         if (self.stdin_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_IN_Rd); }
         if (self.stderr_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_ERR_Wr); }
@@ -648,7 +584,7 @@ pub const ChildProcess = struct {
     fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
         switch (stdio) {
             StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno),
-            StdIo.Close => os.posixClose(std_fileno),
+            StdIo.Close => os.close(std_fileno),
             StdIo.Inherit => {},
             StdIo.Ignore => %return os.posixDup2(dev_null_fd, std_fileno),
         }
@@ -771,8 +707,8 @@ fn makePipe() -> %[2]i32 {
 }
 
 fn destroyPipe(pipe: &const [2]i32) {
-    os.posixClose((*pipe)[0]);
-    os.posixClose((*pipe)[1]);
+    os.close((*pipe)[0]);
+    os.close((*pipe)[1]);
 }
 
 // Child of fork calls this to report an error to the fork parent.
std/os/index.zig
@@ -1,6 +1,7 @@
 const builtin = @import("builtin");
 const Os = builtin.Os;
 const is_windows = builtin.os == Os.windows;
+const os = this;
 
 pub const windows = @import("windows/index.zig");
 pub const darwin = @import("darwin.zig");
@@ -26,14 +27,14 @@ pub const UserInfo = @import("get_user_id.zig").UserInfo;
 pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
 
 const windows_util = @import("windows/util.zig");
-pub const windowsClose = windows_util.windowsClose;
 pub const windowsWaitSingle = windows_util.windowsWaitSingle;
 pub const windowsWrite = windows_util.windowsWrite;
-pub const windowsIsTty = windows_util.windowsIsTty;
 pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty;
 pub const windowsOpen = windows_util.windowsOpen;
 pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock;
 
+pub const FileHandle = if (is_windows) windows.HANDLE else i32;
+
 const debug = @import("../debug.zig");
 const assert = debug.assert;
 
@@ -88,7 +89,7 @@ pub fn getRandomBytes(buf: []u8) -> %void {
         Os.darwin, Os.macosx, Os.ios => {
             const fd = %return posixOpen("/dev/urandom", posix.O_RDONLY|posix.O_CLOEXEC,
                 0, null);
-            defer posixClose(fd);
+            defer close(fd);
 
             %return posixRead(fd, buf);
         },
@@ -165,14 +166,18 @@ pub coldcc fn exit(status: i32) -> noreturn {
     }
 }
 
-/// Calls POSIX close, and keeps trying if it gets interrupted.
-pub fn posixClose(fd: i32) {
-    while (true) {
-        const err = posix.getErrno(posix.close(fd));
-        if (err == posix.EINTR) {
-            continue;
-        } else {
-            return;
+/// Closes the file handle. Keeps trying if it gets interrupted by a signal.
+pub fn close(handle: FileHandle) {
+    if (is_windows) {
+        windows_util.windowsClose(handle);
+    } else {
+        while (true) {
+            const err = posix.getErrno(posix.close(handle));
+            if (err == posix.EINTR) {
+                continue;
+            } else {
+                return;
+            }
         }
     }
 }
@@ -716,19 +721,18 @@ pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: [
     %return getRandomBytes(rand_buf[0..]);
     _ = base64.encodeWithAlphabet(tmp_path[dest_path.len..], rand_buf, b64_fs_alphabet);
 
-    var out_stream = %return io.OutStream.openMode(tmp_path, mode, allocator);
-    defer out_stream.close();
+    var out_file = %return io.File.openWriteMode(tmp_path, mode, allocator);
+    defer out_file.close();
     %defer _ = deleteFile(allocator, tmp_path);
 
-    var in_stream = %return io.InStream.open(source_path, allocator);
-    defer in_stream.close();
+    var in_file = %return io.File.openRead(source_path, allocator);
+    defer in_file.close();
 
-    const buf = out_stream.buffer[0..];
+    var buf: [page_size]u8 = undefined;
     while (true) {
-        const amt = %return in_stream.read(buf);
-        out_stream.index = amt;
-        %return out_stream.flush();
-        if (amt != out_stream.buffer.len)
+        const amt = %return in_file.in_stream.read(buf[0..]);
+        %return out_file.out_stream.write(buf[0..amt]);
+        if (amt != buf.len)
             return rename(allocator, tmp_path, dest_path);
     }
 }
@@ -973,7 +977,7 @@ pub const Dir = struct {
 
     pub fn close(self: &Dir) {
         self.allocator.free(self.buf);
-        posixClose(self.fd);
+        close(self.fd);
     }
 
     /// Memory such as file names referenced in this returned entry becomes invalid
@@ -1135,7 +1139,6 @@ test "os.sleep" {
     sleep(0, 1);
 }
 
-
 error ResourceLimitReached;
 error InvalidUserId;
 error PermissionDenied;
@@ -1184,6 +1187,21 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
     };
 }
 
+error NoStdHandles;
+pub fn windowsGetStdHandle(handle_id: windows.DWORD) -> %windows.HANDLE {
+    if (windows.GetStdHandle(handle_id)) |handle| {
+        if (handle == windows.INVALID_HANDLE_VALUE) {
+            const err = windows.GetLastError();
+            return switch (err) {
+                else => os.unexpectedErrorWindows(err),
+            };
+        }
+        return handle;
+    } else {
+        return error.NoStdHandles;
+    }
+}
+
 pub const ArgIteratorPosix = struct {
     index: usize,
     count: usize,
@@ -1458,3 +1476,28 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) -> error {
     }
     return error.Unexpected;
 }
+
+pub fn openSelfExe() -> %io.File {
+    switch (builtin.os) {
+        Os.linux => {
+            return io.File.openRead("/proc/self/exe", null);
+        },
+        Os.darwin => {
+            @panic("TODO: openSelfExe on Darwin");
+        },
+        else => @compileError("Unsupported OS"),
+    }
+}
+
+pub fn isTty(handle: FileHandle) -> bool {
+    if (is_windows) {
+        return windows_util.windowsIsTty(handle);
+    } else {
+        if (builtin.link_libc) {
+            return c.isatty(handle) != 0;
+        } else {
+            return posix.isatty(handle);
+        }
+    }
+}
+
std/os/path.zig
@@ -940,7 +940,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
                     else => os.unexpectedErrorWindows(err),
                 };
             }
-            defer os.windowsClose(h_file);
+            defer os.close(h_file);
             var buf = %return allocator.alloc(u8, 256);
             %defer allocator.free(buf);
             while (true) {
@@ -1009,7 +1009,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
         },
         Os.linux => {
             const fd = %return os.posixOpen(pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0, allocator);
-            defer os.posixClose(fd);
+            defer os.close(fd);
 
             var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
             const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd);
std/special/build_runner.zig
@@ -6,6 +6,7 @@ const os = std.os;
 const Builder = std.build.Builder;
 const mem = std.mem;
 const ArrayList = std.ArrayList;
+const warn = std.debug.warn;
 
 error InvalidArgs;
 
@@ -13,7 +14,7 @@ pub fn main() -> %void {
     var arg_it = os.args();
 
     // TODO use a more general purpose allocator here
-    var inc_allocator = %%mem.IncrementingAllocator.init(20 * 1024 * 1024);
+    var inc_allocator = %%std.heap.IncrementingAllocator.init(20 * 1024 * 1024);
     defer inc_allocator.deinit();
 
     const allocator = &inc_allocator.allocator;
@@ -23,15 +24,15 @@ pub fn main() -> %void {
     _ = arg_it.skip();
 
     const zig_exe = %return unwrapArg(arg_it.next(allocator) ?? {
-        %%io.stderr.printf("Expected first argument to be path to zig compiler\n");
+        warn("Expected first argument to be path to zig compiler\n");
         return error.InvalidArgs;
     });
     const build_root = %return unwrapArg(arg_it.next(allocator) ?? {
-        %%io.stderr.printf("Expected second argument to be build root directory path\n");
+        warn("Expected second argument to be build root directory path\n");
         return error.InvalidArgs;
     });
     const cache_root = %return unwrapArg(arg_it.next(allocator) ?? {
-        %%io.stderr.printf("Expected third argument to be cache root directory path\n");
+        warn("Expected third argument to be cache root directory path\n");
         return error.InvalidArgs;
     });
 
@@ -42,32 +43,37 @@ pub fn main() -> %void {
 
     var prefix: ?[]const u8 = null;
 
+    var stderr_file = io.getStdErr();
+    var stderr_stream: %&io.OutStream = if (stderr_file) |*f| &f.out_stream else |err| err;
+    var stdout_file = io.getStdOut();
+    var stdout_stream: %&io.OutStream = if (stdout_file) |*f| &f.out_stream else |err| err;
+
     while (arg_it.next(allocator)) |err_or_arg| {
         const arg = %return unwrapArg(err_or_arg);
         if (mem.startsWith(u8, arg, "-D")) {
             const option_contents = arg[2..];
             if (option_contents.len == 0) {
-                %%io.stderr.printf("Expected option name after '-D'\n\n");
-                return usage(&builder, false, &io.stderr);
+                warn("Expected option name after '-D'\n\n");
+                return usageAndErr(&builder, false, %return stderr_stream);
             }
             if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
                 const option_name = option_contents[0..name_end];
                 const option_value = option_contents[name_end + 1..];
                 if (builder.addUserInputOption(option_name, option_value))
-                    return usage(&builder, false, &io.stderr);
+                    return usageAndErr(&builder, false, %return stderr_stream);
             } else {
                 if (builder.addUserInputFlag(option_contents))
-                    return usage(&builder, false, &io.stderr);
+                    return usageAndErr(&builder, false, %return stderr_stream);
             }
         } else if (mem.startsWith(u8, arg, "-")) {
             if (mem.eql(u8, arg, "--verbose")) {
                 builder.verbose = true;
             } else if (mem.eql(u8, arg, "--help")) {
-                return usage(&builder, false, &io.stdout);
+                return usage(&builder, false, %return stdout_stream);
             } else if (mem.eql(u8, arg, "--prefix")) {
                 prefix = %return unwrapArg(arg_it.next(allocator) ?? {
-                    %%io.stderr.printf("Expected argument after --prefix\n\n");
-                    return usage(&builder, false, &io.stderr);
+                    warn("Expected argument after --prefix\n\n");
+                    return usageAndErr(&builder, false, %return stderr_stream);
                 });
             } else if (mem.eql(u8, arg, "--verbose-tokenize")) {
                 builder.verbose_tokenize = true;
@@ -82,8 +88,8 @@ pub fn main() -> %void {
             } else if (mem.eql(u8, arg, "--verbose-cimport")) {
                 builder.verbose_cimport = true;
             } else {
-                %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
-                return usage(&builder, false, &io.stderr);
+                warn("Unrecognized argument: {}\n\n", arg);
+                return usageAndErr(&builder, false, %return stderr_stream);
             }
         } else {
             %%targets.append(arg);
@@ -94,11 +100,11 @@ pub fn main() -> %void {
     root.build(&builder);
 
     if (builder.validateUserInputDidItFail())
-        return usage(&builder, true, &io.stderr);
+        return usageAndErr(&builder, true, %return stderr_stream);
 
     builder.make(targets.toSliceConst()) %% |err| {
         if (err == error.InvalidStepName) {
-            return usage(&builder, true, &io.stderr);
+            return usageAndErr(&builder, true, %return stderr_stream);
         }
         return err;
     };
@@ -112,7 +118,7 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
     }
 
     // This usage text has to be synchronized with src/main.cpp
-    %%out_stream.printf(
+    %return out_stream.print(
         \\Usage: {} build [steps] [options]
         \\
         \\Steps:
@@ -121,10 +127,10 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
 
     const allocator = builder.allocator;
     for (builder.top_level_steps.toSliceConst()) |top_level_step| {
-        %%out_stream.printf("  {s22} {}\n", top_level_step.step.name, top_level_step.description);
+        %return out_stream.print("  {s22} {}\n", top_level_step.step.name, top_level_step.description);
     }
 
-    %%out_stream.write(
+    %return out_stream.write(
         \\
         \\General Options:
         \\  --help                 Print this help and exit
@@ -136,17 +142,17 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
     );
 
     if (builder.available_options_list.len == 0) {
-        %%out_stream.print("  (none)\n");
+        %return out_stream.print("  (none)\n");
     } else {
         for (builder.available_options_list.toSliceConst()) |option| {
-            const name = %%fmt.allocPrint(allocator,
+            const name = %return fmt.allocPrint(allocator,
                 "  -D{}=${}", option.name, Builder.typeIdName(option.type_id));
             defer allocator.free(name);
-            %%out_stream.print("{s24} {}\n", name, option.description);
+            %return out_stream.print("{s24} {}\n", name, option.description);
         }
     }
 
-    %%out_stream.write(
+    %return out_stream.write(
         \\
         \\Advanced Options:
         \\  --build-file $file     Override path to build.zig
@@ -159,16 +165,16 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
         \\  --verbose-cimport      Enable compiler debug output for C imports
         \\
     );
+}
 
-    %%out_stream.flush();
-
-    if (out_stream == &io.stderr)
-        return error.InvalidArgs;
+fn usageAndErr(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) -> error {
+    usage(builder, already_ran_build, out_stream) %% {};
+    return error.InvalidArgs;
 }
 
 fn unwrapArg(arg: %[]u8) -> %[]u8 {
     return arg %% |err| {
-        %%io.stderr.printf("Unable to parse command line: {}\n", err);
+        warn("Unable to parse command line: {}\n", err);
         return err;
     };
 }
std/special/test_runner.zig
@@ -1,13 +1,15 @@
-const io = @import("std").io;
+const std = @import("std");
+const io = std.io;
 const builtin = @import("builtin");
 const test_fn_list = builtin.__zig_test_fn_slice;
+const warn = std.debug.warn;
 
 pub fn main() -> %void {
     for (test_fn_list) |test_fn, i| {
-        %%io.stderr.printf("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name);
+        warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name);
 
         test_fn.func();
 
-        %%io.stderr.printf("OK\n");
+        warn("OK\n");
     }
 }
std/array_list.zig
@@ -70,7 +70,7 @@ pub fn ArrayList(comptime T: type) -> type{
             l.len = new_len;
         }
 
-        pub fn resizeDown(l: &Self, new_len: usize) {
+        pub fn shrink(l: &Self, new_len: usize) {
             assert(new_len <= l.len);
             l.len = new_len;
         }
std/buffer.zig
@@ -71,6 +71,12 @@ pub const Buffer = struct {
         return self.list.toSliceConst()[0..self.len()];
     }
 
+    pub fn shrink(self: &Buffer, new_len: usize) {
+        assert(new_len <= self.len());
+        self.list.shrink(new_len + 1);
+        self.list.items[self.len()] = 0;
+    }
+
     pub fn resize(self: &Buffer, new_len: usize) -> %void {
         %return self.list.resize(new_len + 1);
         self.list.items[self.len()] = 0;
std/build.zig
@@ -1,17 +1,19 @@
+const std = @import("index.zig");
 const builtin = @import("builtin");
-const io = @import("io.zig");
-const mem = @import("mem.zig");
-const debug = @import("debug.zig");
+const io = std.io;
+const mem = std.mem;
+const debug = std.debug;
 const assert = debug.assert;
-const ArrayList = @import("array_list.zig").ArrayList;
-const HashMap = @import("hash_map.zig").HashMap;
-const Allocator = @import("mem.zig").Allocator;
-const os = @import("os/index.zig");
+const warn = std.debug.warn;
+const ArrayList = std.ArrayList;
+const HashMap = std.HashMap;
+const Allocator = mem.Allocator;
+const os = std.os;
 const StdIo = os.ChildProcess.StdIo;
 const Term = os.ChildProcess.Term;
-const BufSet = @import("buf_set.zig").BufSet;
-const BufMap = @import("buf_map.zig").BufMap;
-const fmt_lib = @import("fmt/index.zig");
+const BufSet = std.BufSet;
+const BufMap = std.BufMap;
+const fmt_lib = std.fmt;
 
 error ExtraArg;
 error UncleanExit;
@@ -280,7 +282,7 @@ pub const Builder = struct {
 
         for (self.installed_files.toSliceConst()) |installed_file| {
             if (self.verbose) {
-                %%io.stderr.printf("rm {}\n", installed_file);
+                warn("rm {}\n", installed_file);
             }
             _ = os.deleteFile(self.allocator, installed_file);
         }
@@ -290,7 +292,7 @@ pub const Builder = struct {
 
     fn makeOneStep(self: &Builder, s: &Step) -> %void {
         if (s.loop_flag) {
-            %%io.stderr.printf("Dependency loop detected:\n  {}\n", s.name);
+            warn("Dependency loop detected:\n  {}\n", s.name);
             return error.DependencyLoopDetected;
         }
         s.loop_flag = true;
@@ -298,7 +300,7 @@ pub const Builder = struct {
         for (s.dependencies.toSlice()) |dep| {
             self.makeOneStep(dep) %% |err| {
                 if (err == error.DependencyLoopDetected) {
-                    %%io.stderr.printf("  {}\n", s.name);
+                    warn("  {}\n", s.name);
                 }
                 return err;
             };
@@ -315,7 +317,7 @@ pub const Builder = struct {
                 return &top_level_step.step;
             }
         }
-        %%io.stderr.printf("Cannot run step '{}' because it does not exist\n", name);
+        warn("Cannot run step '{}' because it does not exist\n", name);
         return error.InvalidStepName;
     }
 
@@ -326,12 +328,12 @@ pub const Builder = struct {
                 const word = it.next() ?? break;
                 if (mem.eql(u8, word, "-isystem")) {
                     const include_path = it.next() ?? {
-                        %%io.stderr.printf("Expected argument after -isystem in NIX_CFLAGS_COMPILE\n");
+                        warn("Expected argument after -isystem in NIX_CFLAGS_COMPILE\n");
                         break;
                     };
                     self.addCIncludePath(include_path);
                 } else {
-                    %%io.stderr.printf("Unrecognized C flag from NIX_CFLAGS_COMPILE: {}\n", word);
+                    warn("Unrecognized C flag from NIX_CFLAGS_COMPILE: {}\n", word);
                     break;
                 }
             }
@@ -344,7 +346,7 @@ pub const Builder = struct {
                 const word = it.next() ?? break;
                 if (mem.eql(u8, word, "-rpath")) {
                     const rpath = it.next() ?? {
-                        %%io.stderr.printf("Expected argument after -rpath in NIX_LDFLAGS\n");
+                        warn("Expected argument after -rpath in NIX_LDFLAGS\n");
                         break;
                     };
                     self.addRPath(rpath);
@@ -352,7 +354,7 @@ pub const Builder = struct {
                     const lib_path = word[2..];
                     self.addLibPath(lib_path);
                 } else {
-                    %%io.stderr.printf("Unrecognized C flag from NIX_LDFLAGS: {}\n", word);
+                    warn("Unrecognized C flag from NIX_LDFLAGS: {}\n", word);
                     break;
                 }
             }
@@ -384,13 +386,13 @@ pub const Builder = struct {
                     } else if (mem.eql(u8, s, "false")) {
                         return false;
                     } else {
-                        %%io.stderr.printf("Expected -D{} to be a boolean, but received '{}'\n", name, s);
+                        warn("Expected -D{} to be a boolean, but received '{}'\n", name, s);
                         self.markInvalidUserInput();
                         return null;
                     }
                 },
                 UserValue.List => {
-                    %%io.stderr.printf("Expected -D{} to be a boolean, but received a list.\n", name);
+                    warn("Expected -D{} to be a boolean, but received a list.\n", name);
                     self.markInvalidUserInput();
                     return null;
                 },
@@ -399,12 +401,12 @@ pub const Builder = struct {
             TypeId.Float => debug.panic("TODO float options to build script"),
             TypeId.String => switch (entry.value.value) {
                 UserValue.Flag => {
-                    %%io.stderr.printf("Expected -D{} to be a string, but received a boolean.\n", name);
+                    warn("Expected -D{} to be a string, but received a boolean.\n", name);
                     self.markInvalidUserInput();
                     return null;
                 },
                 UserValue.List => {
-                    %%io.stderr.printf("Expected -D{} to be a string, but received a list.\n", name);
+                    warn("Expected -D{} to be a string, but received a list.\n", name);
                     self.markInvalidUserInput();
                     return null;
                 },
@@ -437,7 +439,7 @@ pub const Builder = struct {
         } else if (!release_fast and !release_safe) {
             builtin.Mode.Debug
         } else {
-            %%io.stderr.printf("Both -Drelease-safe and -Drelease-fast specified");
+            warn("Both -Drelease-safe and -Drelease-fast specified");
             self.markInvalidUserInput();
             builtin.Mode.Debug
         };
@@ -474,7 +476,7 @@ pub const Builder = struct {
                     });
                 },
                 UserValue.Flag => {
-                    %%io.stderr.printf("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name);
+                    warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name);
                     return true;
                 },
             }
@@ -490,11 +492,11 @@ pub const Builder = struct {
         })) |*prev_value| {
             switch (prev_value.value) {
                 UserValue.Scalar => |s| {
-                    %%io.stderr.printf("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s);
+                    warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s);
                     return true;
                 },
                 UserValue.List => {
-                    %%io.stderr.printf("Flag '-D{}' conflicts with multiple options of the same name.\n", name);
+                    warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name);
                     return true;
                 },
                 UserValue.Flag => {},
@@ -536,7 +538,7 @@ pub const Builder = struct {
         while (true) {
             const entry = it.next() ?? break;
             if (!entry.value.used) {
-                %%io.stderr.printf("Invalid option: -D{}\n\n", entry.key);
+                warn("Invalid option: -D{}\n\n", entry.key);
                 self.markInvalidUserInput();
             }
         }
@@ -549,11 +551,11 @@ pub const Builder = struct {
     }
 
     fn printCmd(cwd: ?[]const u8, argv: []const []const u8) {
-        if (cwd) |yes_cwd| %%io.stderr.print("cd {} && ", yes_cwd);
+        if (cwd) |yes_cwd| warn("cd {} && ", yes_cwd);
         for (argv) |arg| {
-            %%io.stderr.print("{} ", arg);
+            warn("{} ", arg);
         }
-        %%io.stderr.printf("\n");
+        warn("\n");
     }
 
     fn spawnChildEnvMap(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
@@ -570,20 +572,20 @@ pub const Builder = struct {
         child.env_map = env_map;
 
         const term = child.spawnAndWait() %% |err| {
-            %%io.stderr.printf("Unable to spawn {}: {}\n", argv[0], @errorName(err));
+            warn("Unable to spawn {}: {}\n", argv[0], @errorName(err));
             return err;
         };
 
         switch (term) {
             Term.Exited => |code| {
                 if (code != 0) {
-                    %%io.stderr.printf("The following command exited with error code {}:\n", code);
+                    warn("The following command exited with error code {}:\n", code);
                     printCmd(cwd, argv);
                     return error.UncleanExit;
                 }
             },
             else => {
-                %%io.stderr.printf("The following command terminated unexpectedly:\n");
+                warn("The following command terminated unexpectedly:\n");
                 printCmd(cwd, argv);
 
                 return error.UncleanExit;
@@ -594,7 +596,7 @@ pub const Builder = struct {
 
     pub fn makePath(self: &Builder, path: []const u8) -> %void {
         os.makePath(self.allocator, self.pathFromRoot(path)) %% |err| {
-            %%io.stderr.printf("Unable to create path {}: {}\n", path, @errorName(err));
+            warn("Unable to create path {}: {}\n", path, @errorName(err));
             return err;
         };
     }
@@ -633,17 +635,17 @@ pub const Builder = struct {
 
     fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: usize) -> %void {
         if (self.verbose) {
-            %%io.stderr.printf("cp {} {}\n", source_path, dest_path);
+            warn("cp {} {}\n", source_path, dest_path);
         }
 
         const dirname = os.path.dirname(dest_path);
         const abs_source_path = self.pathFromRoot(source_path);
         os.makePath(self.allocator, dirname) %% |err| {
-            %%io.stderr.printf("Unable to create path {}: {}\n", dirname, @errorName(err));
+            warn("Unable to create path {}: {}\n", dirname, @errorName(err));
             return err;
         };
         os.copyFileMode(self.allocator, abs_source_path, dest_path, mode) %% |err| {
-            %%io.stderr.printf("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err));
+            warn("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err));
             return err;
         };
     }
@@ -1103,7 +1105,7 @@ pub const LibExeObjStep = struct {
         assert(self.is_zig);
 
         if (self.root_src == null and self.object_files.len == 0 and self.assembly_files.len == 0) {
-            %%io.stderr.printf("{}: linker needs 1 or more objects to link\n", self.step.name);
+            warn("{}: linker needs 1 or more objects to link\n", self.step.name);
             return error.NeedAnObject;
         }
 
@@ -1799,11 +1801,11 @@ pub const WriteFileStep = struct {
         const full_path = self.builder.pathFromRoot(self.file_path);
         const full_path_dir = os.path.dirname(full_path);
         os.makePath(self.builder.allocator, full_path_dir) %% |err| {
-            %%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err));
+            warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));
             return err;
         };
         io.writeFile(full_path, self.data, self.builder.allocator) %% |err| {
-            %%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err));
+            warn("unable to write {}: {}\n", full_path, @errorName(err));
             return err;
         };
     }
@@ -1824,8 +1826,7 @@ pub const LogStep = struct {
 
     fn make(step: &Step) -> %void {
         const self = @fieldParentPtr(LogStep, "step", step);
-        %%io.stderr.write(self.data);
-        %%io.stderr.flush();
+        warn("{}", self.data);
     }
 };
 
@@ -1847,7 +1848,7 @@ pub const RemoveDirStep = struct {
 
         const full_path = self.builder.pathFromRoot(self.dir_path);
         os.deleteTree(self.builder.allocator, full_path) %% |err| {
-            %%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err));
+            warn("Unable to remove {}: {}\n", full_path, @errorName(err));
             return err;
         };
     }
@@ -1896,13 +1897,13 @@ fn doAtomicSymLinks(allocator: &Allocator, output_path: []const u8, filename_maj
     // sym link for libfoo.so.1 to libfoo.so.1.2.3
     const major_only_path = %%os.path.join(allocator, out_dir, filename_major_only);
     os.atomicSymLink(allocator, out_basename, major_only_path) %% |err| {
-        %%io.stderr.printf("Unable to symlink {} -> {}\n", major_only_path, out_basename);
+        warn("Unable to symlink {} -> {}\n", major_only_path, out_basename);
         return err;
     };
     // sym link for libfoo.so to libfoo.so.1
     const name_only_path = %%os.path.join(allocator, out_dir, filename_name_only);
     os.atomicSymLink(allocator, filename_major_only, name_only_path) %% |err| {
-        %%io.stderr.printf("Unable to symlink {} -> {}\n", name_only_path, filename_major_only);
+        warn("Unable to symlink {} -> {}\n", name_only_path, filename_major_only);
         return err;
     };
 }
std/debug.zig
@@ -1,10 +1,11 @@
-const math = @import("math/index.zig");
-const mem = @import("mem.zig");
-const io = @import("io.zig");
-const os = @import("os/index.zig");
+const std = @import("index.zig");
+const math = std.math;
+const mem = std.mem;
+const io = std.io;
+const os = std.os;
 const elf = @import("elf.zig");
 const DW = @import("dwarf.zig");
-const ArrayList = @import("array_list.zig").ArrayList;
+const ArrayList = std.ArrayList;
 const builtin = @import("builtin");
 
 error MissingDebugInfo;
@@ -12,10 +13,42 @@ error InvalidDebugInfo;
 error UnsupportedDebugInfo;
 
 
+/// Tries to write to stderr, unbuffered, and ignores any error returned.
+/// Does not append a newline.
+/// TODO atomic/multithread support
+var stderr_file: io.File = undefined;
+var stderr_stream: ?&io.OutStream = null;
+pub fn warn(comptime fmt: []const u8, args: ...) {
+    const stderr = getStderrStream() %% return;
+    stderr.print(fmt, args) %% return;
+}
+fn getStderrStream() -> %&io.OutStream {
+    if (stderr_stream) |st| {
+        return st;
+    } else {
+        stderr_file = %return io.getStdErr();
+        const st = &stderr_file.out_stream;
+        stderr_stream = st;
+        return st;
+    };
+}
+
+/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned.
+pub fn dumpStackTrace() {
+    const stderr = getStderrStream() %% return;
+    writeStackTrace(stderr, &global_allocator, stderr_file.isTty(), 1) %% return;
+}
+
+/// This function invokes undefined behavior when `ok` is `false`.
+/// In Debug and ReleaseSafe modes, calls to this function are always
+/// generated, and the `unreachable` statement triggers a panic.
+/// In ReleaseFast and ReleaseSmall modes, calls to this function can be
+/// optimized away.
 pub fn assert(ok: bool) {
     if (!ok) {
         // In ReleaseFast test mode, we still want assert(false) to crash, so
         // we insert an explicit call to @panic instead of unreachable.
+        // TODO we should use `assertOrPanic` in tests and remove this logic.
         if (builtin.is_test) {
             @panic("assertion failure")
         } else {
@@ -24,6 +57,14 @@ pub fn assert(ok: bool) {
     }
 }
 
+/// Call this function when you want to panic if the condition is not true.
+/// If `ok` is `false`, this function will panic in every release mode.
+pub fn assertOrPanic(ok: bool) {
+    if (!ok) {
+        @panic("assertion failure");
+    }
+}
+
 var panicking = false;
 /// This is the default panic implementation.
 pub fn panic(comptime format: []const u8, args: ...) -> noreturn {
@@ -41,18 +82,13 @@ pub fn panic(comptime format: []const u8, args: ...) -> noreturn {
         panicking = true;
     }
 
-    %%io.stderr.printf(format ++ "\n", args);
-    %%writeStackTrace(&io.stderr, &global_allocator, io.stderr.isTty() %% false, 1);
-    %%io.stderr.flush();
+    const stderr = getStderrStream() %% os.abort();
+    stderr.print(format ++ "\n", args) %% os.abort();
+    writeStackTrace(stderr, &global_allocator, stderr_file.isTty(), 1) %% os.abort();
 
     os.abort();
 }
 
-pub fn printStackTrace() -> %void {
-    %return writeStackTrace(&io.stderr, &global_allocator, io.stderr.isTty() %% false, 1);
-    %return io.stderr.flush();
-}
-
 const GREEN = "\x1b[32;1m";
 const WHITE = "\x1b[37;1m";
 const DIM = "\x1b[2m";
@@ -69,7 +105,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty
     switch (builtin.object_format) {
         builtin.ObjectFormat.elf => {
             var stack_trace = ElfStackTrace {
-                .self_exe_stream = undefined,
+                .self_exe_file = undefined,
                 .elf = undefined,
                 .debug_info = undefined,
                 .debug_abbrev = undefined,
@@ -79,10 +115,10 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty
                 .compile_unit_list = ArrayList(CompileUnit).init(allocator),
             };
             const st = &stack_trace;
-            st.self_exe_stream = %return io.openSelfExe();
-            defer st.self_exe_stream.close();
+            st.self_exe_file = %return os.openSelfExe();
+            defer st.self_exe_file.close();
 
-            %return st.elf.openStream(allocator, &st.self_exe_stream);
+            %return st.elf.openFile(allocator, &st.self_exe_file);
             defer st.elf.close();
 
             st.debug_info = (%return st.elf.findSection(".debug_info")) ?? return error.MissingDebugInfo;
@@ -109,7 +145,6 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty
                 const compile_unit = findCompileUnit(st, return_address) ?? {
                     %return out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n    ???\n\n",
                         return_address);
-                    %return out_stream.flush();
                     continue;
                 };
                 const compile_unit_name = %return compile_unit.die.getAttrString(st, DW.AT_name);
@@ -139,7 +174,6 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty
                     },
                     else => return err,
                 };
-                %return out_stream.flush();
             }
         },
         builtin.ObjectFormat.coff => {
@@ -158,7 +192,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty
 }
 
 fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_info: &const LineInfo) -> %void {
-    var f = %return io.InStream.open(line_info.file_name, allocator);
+    var f = %return io.File.openRead(line_info.file_name, allocator);
     defer f.close();
     // TODO fstat and make sure that the file has the correct size
 
@@ -167,7 +201,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_
     var column: usize = 1;
     var abs_index: usize = 0;
     while (true) {
-        const amt_read = %return f.read(buf[0..]);
+        const amt_read = %return f.in_stream.read(buf[0..]);
         const slice = buf[0..amt_read];
 
         for (slice) |byte| {
@@ -191,7 +225,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_
 }
 
 const ElfStackTrace = struct {
-    self_exe_stream: io.InStream,
+    self_exe_file: io.File,
     elf: elf.Elf,
     debug_info: &elf.SectionHeader,
     debug_abbrev: &elf.SectionHeader,
@@ -205,7 +239,7 @@ const ElfStackTrace = struct {
     }
 
     pub fn readString(self: &ElfStackTrace) -> %[]u8 {
-        return readStringRaw(self.allocator(), &self.self_exe_stream);
+        return readStringRaw(self.allocator(), &self.self_exe_file.in_stream);
     }
 };
 
@@ -424,7 +458,7 @@ fn readStringRaw(allocator: &mem.Allocator, in_stream: &io.InStream) -> %[]u8 {
 
 fn getString(st: &ElfStackTrace, offset: u64) -> %[]u8 {
     const pos = st.debug_str.offset + offset;
-    %return st.self_exe_stream.seekTo(pos);
+    %return st.self_exe_file.seekTo(pos);
     return st.readString();
 }
 
@@ -533,7 +567,7 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u
 }
 
 fn parseAbbrevTable(st: &ElfStackTrace) -> %AbbrevTable {
-    const in_stream = &st.self_exe_stream;
+    const in_stream = &st.self_exe_file.in_stream;
     var result = AbbrevTable.init(st.allocator());
     while (true) {
         const abbrev_code = %return readULeb128(in_stream);
@@ -568,7 +602,7 @@ fn getAbbrevTable(st: &ElfStackTrace, abbrev_offset: u64) -> %&const AbbrevTable
             return &header.table;
         }
     }
-    %return st.self_exe_stream.seekTo(st.debug_abbrev.offset + abbrev_offset);
+    %return st.self_exe_file.seekTo(st.debug_abbrev.offset + abbrev_offset);
     %return st.abbrev_table_list.append(AbbrevTableHeader {
         .offset = abbrev_offset,
         .table = %return parseAbbrevTable(st),
@@ -585,8 +619,8 @@ fn getAbbrevTableEntry(abbrev_table: &const AbbrevTable, abbrev_code: u64) -> ?&
 }
 
 fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) -> %Die {
-    const in_stream = &st.self_exe_stream;
-    const abbrev_code = %return readULeb128(in_stream);
+    const in_file = &st.self_exe_file;
+    const abbrev_code = %return readULeb128(&in_file.in_stream);
     const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) ?? return error.InvalidDebugInfo;
 
     var result = Die {
@@ -598,7 +632,7 @@ fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) -
     for (table_entry.attrs.toSliceConst()) |attr, i| {
         result.attrs.items[i] = Die.Attr {
             .id = attr.attr_id,
-            .value = %return parseFormValue(st.allocator(), &st.self_exe_stream, attr.form_id, is_64),
+            .value = %return parseFormValue(st.allocator(), &st.self_exe_file.in_stream, attr.form_id, is_64),
         };
     }
     return result;
@@ -607,16 +641,16 @@ fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) -
 fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, target_address: usize) -> %LineInfo {
     const compile_unit_cwd = %return compile_unit.die.getAttrString(st, DW.AT_comp_dir);
 
-    const in_stream = &st.self_exe_stream;
+    const in_file = &st.self_exe_file;
     const debug_line_end = st.debug_line.offset + st.debug_line.size;
     var this_offset = st.debug_line.offset;
     var this_index: usize = 0;
 
     while (this_offset < debug_line_end) : (this_index += 1) {
-        %return in_stream.seekTo(this_offset);
+        %return in_file.seekTo(this_offset);
 
         var is_64: bool = undefined;
-        const unit_length = %return readInitialLength(in_stream, &is_64);
+        const unit_length = %return readInitialLength(&in_file.in_stream, &is_64);
         if (unit_length == 0)
             return error.MissingDebugInfo;
         const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
@@ -626,28 +660,28 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
             continue;
         }
 
-        const version = %return in_stream.readInt(st.elf.is_big_endian, u16);
+        const version = %return in_file.in_stream.readInt(st.elf.is_big_endian, u16);
         if (version != 2) return error.InvalidDebugInfo;
 
-        const prologue_length = %return in_stream.readInt(st.elf.is_big_endian, u32);
-        const prog_start_offset = (%return in_stream.getPos()) + prologue_length;
+        const prologue_length = %return in_file.in_stream.readInt(st.elf.is_big_endian, u32);
+        const prog_start_offset = (%return in_file.getPos()) + prologue_length;
 
-        const minimum_instruction_length = %return in_stream.readByte();
+        const minimum_instruction_length = %return in_file.in_stream.readByte();
         if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
 
-        const default_is_stmt = (%return in_stream.readByte()) != 0;
-        const line_base = %return in_stream.readByteSigned();
+        const default_is_stmt = (%return in_file.in_stream.readByte()) != 0;
+        const line_base = %return in_file.in_stream.readByteSigned();
 
-        const line_range = %return in_stream.readByte();
+        const line_range = %return in_file.in_stream.readByte();
         if (line_range == 0)
             return error.InvalidDebugInfo;
 
-        const opcode_base = %return in_stream.readByte();
+        const opcode_base = %return in_file.in_stream.readByte();
 
         const standard_opcode_lengths = %return st.allocator().alloc(u8, opcode_base - 1);
 
         {var i: usize = 0; while (i < opcode_base - 1) : (i += 1) {
-            standard_opcode_lengths[i] = %return in_stream.readByte();
+            standard_opcode_lengths[i] = %return in_file.in_stream.readByte();
         }}
 
         var include_directories = ArrayList([]u8).init(st.allocator());
@@ -667,9 +701,9 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
             const file_name = %return st.readString();
             if (file_name.len == 0)
                 break;
-            const dir_index = %return readULeb128(in_stream);
-            const mtime = %return readULeb128(in_stream);
-            const len_bytes = %return readULeb128(in_stream);
+            const dir_index = %return readULeb128(&in_file.in_stream);
+            const mtime = %return readULeb128(&in_file.in_stream);
+            const len_bytes = %return readULeb128(&in_file.in_stream);
             %return file_entries.append(FileEntry {
                 .file_name = file_name,
                 .dir_index = dir_index,
@@ -678,42 +712,32 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
             });
         }
 
-        %return in_stream.seekTo(prog_start_offset);
+        %return in_file.seekTo(prog_start_offset);
 
         while (true) {
-            //const pos = (%return in_stream.getPos()) - this_offset;
-            //if (pos == 0x1a3) @breakpoint();
-            //%%io.stderr.printf("\n{x8}\n", pos);
-
-            const opcode = %return in_stream.readByte();
+            const opcode = %return in_file.in_stream.readByte();
 
             var sub_op: u8 = undefined; // TODO move this to the correct scope and fix the compiler crash
             if (opcode == DW.LNS_extended_op) {
-                const op_size = %return readULeb128(in_stream);
+                const op_size = %return readULeb128(&in_file.in_stream);
                 if (op_size < 1)
                     return error.InvalidDebugInfo;
-                sub_op = %return in_stream.readByte();
+                sub_op = %return in_file.in_stream.readByte();
                 switch (sub_op) {
                     DW.LNE_end_sequence => {
-                        //%%io.stdout.printf("  [0x{x8}]  End Sequence\n", pos);
                         prog.end_sequence = true;
                         if (%return prog.checkLineMatch()) |info| return info;
                         return error.MissingDebugInfo;
                     },
                     DW.LNE_set_address => {
-                        const addr = %return in_stream.readInt(st.elf.is_big_endian, usize);
+                        const addr = %return in_file.in_stream.readInt(st.elf.is_big_endian, usize);
                         prog.address = addr;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Extended opcode {}: set Address to 0x{x}\n",
-                        //    pos, sub_op, addr);
                     },
                     DW.LNE_define_file => {
-                        //%%io.stdout.printf("  [0x{x8}]  Define File\n", pos);
-
                         const file_name = %return st.readString();
-                        const dir_index = %return readULeb128(in_stream);
-                        const mtime = %return readULeb128(in_stream);
-                        const len_bytes = %return readULeb128(in_stream);
+                        const dir_index = %return readULeb128(&in_file.in_stream);
+                        const mtime = %return readULeb128(&in_file.in_stream);
+                        const len_bytes = %return readULeb128(&in_file.in_stream);
                         %return file_entries.append(FileEntry {
                             .file_name = file_name,
                             .dir_index = dir_index,
@@ -723,7 +747,7 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
                     },
                     else => {
                         const fwd_amt = math.cast(isize, op_size - 1) %% return error.InvalidDebugInfo;
-                        %return in_stream.seekForward(fwd_amt);
+                        %return in_file.seekForward(fwd_amt);
                     },
                 }
             } else if (opcode >= opcode_base) {
@@ -733,48 +757,32 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
                 const inc_line = i32(line_base) + i32(adjusted_opcode % line_range);
                 prog.line += inc_line;
                 prog.address += inc_addr;
-                //%%io.stdout.printf(
-                //    "  [0x{x8}]  Special opcode {}: advance Address by {} to 0x{x} and Line by {} to {}\n",
-                //    pos, adjusted_opcode, inc_addr, prog.address, inc_line, prog.line);
                 if (%return prog.checkLineMatch()) |info| return info;
                 prog.basic_block = false;
             } else {
                 switch (opcode) {
                     DW.LNS_copy => {
-                        //%%io.stdout.printf("  [0x{x8}]  Copy\n", pos);
-
                         if (%return prog.checkLineMatch()) |info| return info;
                         prog.basic_block = false;
                     },
                     DW.LNS_advance_pc => {
-                        const arg = %return readULeb128(in_stream);
+                        const arg = %return readULeb128(&in_file.in_stream);
                         prog.address += arg * minimum_instruction_length;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Advance PC by {} to 0x{x}\n", pos, arg, prog.address);
                     },
                     DW.LNS_advance_line => {
-                        const arg = %return readILeb128(in_stream);
+                        const arg = %return readILeb128(&in_file.in_stream);
                         prog.line += arg;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Advance Line by {} to {}\n", pos, arg, prog.line);
                     },
                     DW.LNS_set_file => {
-                        const arg = %return readULeb128(in_stream);
+                        const arg = %return readULeb128(&in_file.in_stream);
                         prog.file = arg;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Set File Name to entry {} in the File Name Table\n",
-                        //    pos, arg);
                     },
                     DW.LNS_set_column => {
-                        const arg = %return readULeb128(in_stream);
+                        const arg = %return readULeb128(&in_file.in_stream);
                         prog.column = arg;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Set column to {}\n", pos, arg);
                     },
                     DW.LNS_negate_stmt => {
                         prog.is_stmt = !prog.is_stmt;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Set is_stmt to {}\n", pos, if (prog.is_stmt) u8(1) else u8(0));
                     },
                     DW.LNS_set_basic_block => {
                         prog.basic_block = true;
@@ -782,23 +790,18 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
                     DW.LNS_const_add_pc => {
                         const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range);
                         prog.address += inc_addr;
-
-                        //%%io.stdout.printf("  [0x{x8}]  Advance PC by constant {} to 0x{x}\n",
-                        //    pos, inc_addr, prog.address);
                     },
                     DW.LNS_fixed_advance_pc => {
-                        const arg = %return in_stream.readInt(st.elf.is_big_endian, u16);
+                        const arg = %return in_file.in_stream.readInt(st.elf.is_big_endian, u16);
                         prog.address += arg;
                     },
                     DW.LNS_set_prologue_end => {
-                        //%%io.stdout.printf("  [0x{x8}]  Set prologue_end to true\n", pos);
                     },
                     else => {
                         if (opcode - 1 >= standard_opcode_lengths.len)
                             return error.InvalidDebugInfo;
-                        //%%io.stdout.printf("  [0x{x8}]  unknown op code {}\n", pos, opcode);
                         const len_bytes = standard_opcode_lengths[opcode - 1];
-                        %return in_stream.seekForward(len_bytes);
+                        %return in_file.seekForward(len_bytes);
                     },
                 }
             }
@@ -815,30 +818,30 @@ fn scanAllCompileUnits(st: &ElfStackTrace) -> %void {
     var this_unit_offset = st.debug_info.offset;
     var cu_index: usize = 0;
     while (this_unit_offset < debug_info_end) {
-        %return st.self_exe_stream.seekTo(this_unit_offset);
+        %return st.self_exe_file.seekTo(this_unit_offset);
 
         var is_64: bool = undefined;
-        const unit_length = %return readInitialLength(&st.self_exe_stream, &is_64);
+        const unit_length = %return readInitialLength(&st.self_exe_file.in_stream, &is_64);
         if (unit_length == 0)
             return;
         const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
 
-        const version = %return st.self_exe_stream.readInt(st.elf.is_big_endian, u16);
+        const version = %return st.self_exe_file.in_stream.readInt(st.elf.is_big_endian, u16);
         if (version < 2 or version > 5) return error.InvalidDebugInfo;
 
         const debug_abbrev_offset = if (is_64) {
-            %return st.self_exe_stream.readInt(st.elf.is_big_endian, u64)
+            %return st.self_exe_file.in_stream.readInt(st.elf.is_big_endian, u64)
         } else {
-            %return st.self_exe_stream.readInt(st.elf.is_big_endian, u32)
+            %return st.self_exe_file.in_stream.readInt(st.elf.is_big_endian, u32)
         };
 
-        const address_size = %return st.self_exe_stream.readByte();
+        const address_size = %return st.self_exe_file.in_stream.readByte();
         if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
 
-        const compile_unit_pos = %return st.self_exe_stream.getPos();
+        const compile_unit_pos = %return st.self_exe_file.getPos();
         const abbrev_table = %return getAbbrevTable(st, debug_abbrev_offset);
 
-        %return st.self_exe_stream.seekTo(compile_unit_pos);
+        %return st.self_exe_file.seekTo(compile_unit_pos);
 
         const compile_unit_die = %return st.allocator().create(Die);
         *compile_unit_die = %return parseDie(st, abbrev_table, is_64);
std/elf.zig
@@ -1,7 +1,9 @@
-const io = @import("io.zig");
-const math = @import("math/index.zig");
-const mem = @import("mem.zig");
-const debug = @import("debug.zig");
+const std = @import("index.zig");
+const io = std.io;
+const math = std.math;
+const mem = std.mem;
+const debug = std.debug;
+const InStream = std.stream.InStream;
 
 error InvalidFormat;
 
@@ -62,7 +64,7 @@ pub const SectionHeader = struct {
 };
 
 pub const Elf = struct {
-    in_stream: &io.InStream,
+    in_file: &io.File,
     auto_close_stream: bool,
     is_64: bool,
     is_big_endian: bool,
@@ -75,44 +77,44 @@ pub const Elf = struct {
     string_section: &SectionHeader,
     section_headers: []SectionHeader,
     allocator: &mem.Allocator,
-    prealloc_stream: io.InStream,
+    prealloc_file: io.File,
 
     /// Call close when done.
-    pub fn openFile(elf: &Elf, allocator: &mem.Allocator, path: []const u8) -> %void {
-        %return elf.prealloc_stream.open(path);
-        %return elf.openStream(allocator, &elf.prealloc_stream);
+    pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) -> %void {
+        %return elf.prealloc_file.open(path);
+        %return elf.openFile(allocator, &elf.prealloc_file);
         elf.auto_close_stream = true;
     }
 
     /// Call close when done.
-    pub fn openStream(elf: &Elf, allocator: &mem.Allocator, stream: &io.InStream) -> %void {
+    pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &io.File) -> %void {
         elf.allocator = allocator;
-        elf.in_stream = stream;
+        elf.in_file = file;
         elf.auto_close_stream = false;
 
         var magic: [4]u8 = undefined;
-        %return elf.in_stream.readNoEof(magic[0..]);
+        %return elf.in_file.in_stream.readNoEof(magic[0..]);
         if (!mem.eql(u8, magic, "\x7fELF")) return error.InvalidFormat;
 
-        elf.is_64 = switch (%return elf.in_stream.readByte()) {
+        elf.is_64 = switch (%return elf.in_file.in_stream.readByte()) {
             1 => false,
             2 => true,
             else => return error.InvalidFormat,
         };
 
-        elf.is_big_endian = switch (%return elf.in_stream.readByte()) {
+        elf.is_big_endian = switch (%return elf.in_file.in_stream.readByte()) {
             1 => false,
             2 => true,
             else => return error.InvalidFormat,
         };
 
-        const version_byte = %return elf.in_stream.readByte();
+        const version_byte = %return elf.in_file.in_stream.readByte();
         if (version_byte != 1) return error.InvalidFormat;
 
         // skip over padding
-        %return elf.in_stream.seekForward(9);
+        %return elf.in_file.seekForward(9);
 
-        elf.file_type = switch (%return elf.in_stream.readInt(elf.is_big_endian, u16)) {
+        elf.file_type = switch (%return elf.in_file.in_stream.readInt(elf.is_big_endian, u16)) {
             1 => FileType.Relocatable,
             2 => FileType.Executable,
             3 => FileType.Shared,
@@ -120,7 +122,7 @@ pub const Elf = struct {
             else => return error.InvalidFormat,
         };
 
-        elf.arch = switch (%return elf.in_stream.readInt(elf.is_big_endian, u16)) {
+        elf.arch = switch (%return elf.in_file.in_stream.readInt(elf.is_big_endian, u16)) {
             0x02 => Arch.Sparc,
             0x03 => Arch.x86,
             0x08 => Arch.Mips,
@@ -133,34 +135,34 @@ pub const Elf = struct {
             else => return error.InvalidFormat,
         };
 
-        const elf_version = %return elf.in_stream.readInt(elf.is_big_endian, u32);
+        const elf_version = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
         if (elf_version != 1) return error.InvalidFormat;
 
         if (elf.is_64) {
-            elf.entry_addr = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-            elf.program_header_offset = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-            elf.section_header_offset = %return elf.in_stream.readInt(elf.is_big_endian, u64);
+            elf.entry_addr = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+            elf.program_header_offset = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+            elf.section_header_offset = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
         } else {
-            elf.entry_addr = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-            elf.program_header_offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-            elf.section_header_offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
+            elf.entry_addr = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+            elf.program_header_offset = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+            elf.section_header_offset = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
         }
 
         // skip over flags
-        %return elf.in_stream.seekForward(4);
+        %return elf.in_file.seekForward(4);
 
-        const header_size = %return elf.in_stream.readInt(elf.is_big_endian, u16);
+        const header_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16);
         if ((elf.is_64 and header_size != 64) or
             (!elf.is_64 and header_size != 52))
         {
             return error.InvalidFormat;
         }
 
-        const ph_entry_size = %return elf.in_stream.readInt(elf.is_big_endian, u16);
-        const ph_entry_count = %return elf.in_stream.readInt(elf.is_big_endian, u16);
-        const sh_entry_size = %return elf.in_stream.readInt(elf.is_big_endian, u16);
-        const sh_entry_count = %return elf.in_stream.readInt(elf.is_big_endian, u16);
-        elf.string_section_index = u64(%return elf.in_stream.readInt(elf.is_big_endian, u16));
+        const ph_entry_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16);
+        const ph_entry_count = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16);
+        const sh_entry_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16);
+        const sh_entry_count = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16);
+        elf.string_section_index = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u16));
 
         if (elf.string_section_index >= sh_entry_count) return error.InvalidFormat;
 
@@ -169,12 +171,12 @@ pub const Elf = struct {
         const ph_byte_count = u64(ph_entry_size) * u64(ph_entry_count);
         const end_ph = %return math.add(u64, elf.program_header_offset, ph_byte_count);
 
-        const stream_end = %return elf.in_stream.getEndPos();
+        const stream_end = %return elf.in_file.getEndPos();
         if (stream_end < end_sh or stream_end < end_ph) {
             return error.InvalidFormat;
         }
 
-        %return elf.in_stream.seekTo(elf.section_header_offset);
+        %return elf.in_file.seekTo(elf.section_header_offset);
 
         elf.section_headers = %return elf.allocator.alloc(SectionHeader, sh_entry_count);
         %defer elf.allocator.free(elf.section_headers);
@@ -183,32 +185,32 @@ pub const Elf = struct {
             if (sh_entry_size != 64) return error.InvalidFormat;
 
             for (elf.section_headers) |*section| {
-                section.name         = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.sh_type      = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.flags        = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-                section.addr         = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-                section.offset       = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-                section.size         = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-                section.link         = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.info         = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.addr_align   = %return elf.in_stream.readInt(elf.is_big_endian, u64);
-                section.ent_size     = %return elf.in_stream.readInt(elf.is_big_endian, u64);
+                section.name         = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.sh_type      = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.flags        = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+                section.addr         = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+                section.offset       = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+                section.size         = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+                section.link         = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.info         = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.addr_align   = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
+                section.ent_size     = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64);
             }
         } else {
             if (sh_entry_size != 40) return error.InvalidFormat;
 
             for (elf.section_headers) |*section| {
                 // TODO (multiple occurences) allow implicit cast from %u32 -> %u64 ?
-                section.name = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.sh_type = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.flags = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-                section.addr = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-                section.offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-                section.size = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-                section.link = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.info = %return elf.in_stream.readInt(elf.is_big_endian, u32);
-                section.addr_align = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
-                section.ent_size = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32));
+                section.name = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.sh_type = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.flags = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+                section.addr = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+                section.offset = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+                section.size = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+                section.link = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.info = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32);
+                section.addr_align = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
+                section.ent_size = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32));
             }
         }
 
@@ -230,7 +232,7 @@ pub const Elf = struct {
         elf.allocator.free(elf.section_headers);
 
         if (elf.auto_close_stream)
-            elf.in_stream.close();
+            elf.in_file.close();
     }
 
     pub fn findSection(elf: &Elf, name: []const u8) -> %?&SectionHeader {
@@ -238,15 +240,15 @@ pub const Elf = struct {
             if (section.sh_type == SHT_NULL) continue;
 
             const name_offset = elf.string_section.offset + section.name;
-            %return elf.in_stream.seekTo(name_offset);
+            %return elf.in_file.seekTo(name_offset);
 
             for (name) |expected_c| {
-                const target_c = %return elf.in_stream.readByte();
+                const target_c = %return elf.in_file.in_stream.readByte();
                 if (target_c == 0 or expected_c != target_c) goto next_section;
             }
 
             {
-                const null_byte = %return elf.in_stream.readByte();
+                const null_byte = %return elf.in_file.in_stream.readByte();
                 if (null_byte == 0) return section;
             }
 
@@ -257,6 +259,6 @@ pub const Elf = struct {
     }
 
     pub fn seekToSection(elf: &Elf, section: &SectionHeader) -> %void {
-        %return elf.in_stream.seekTo(section.offset);
+        %return elf.in_file.seekTo(section.offset);
     }
 };
std/heap.zig
@@ -0,0 +1,158 @@
+const debug = @import("debug.zig");
+const assert = debug.assert;
+const mem = @import("mem.zig");
+const os = @import("os/index.zig");
+const builtin = @import("builtin");
+const Os = builtin.Os;
+const c = @import("c/index.zig");
+
+const Allocator = mem.Allocator;
+
+error OutOfMemory;
+
+pub var c_allocator = Allocator {
+    .allocFn = cAlloc,
+    .reallocFn = cRealloc,
+    .freeFn = cFree,
+};
+
+fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 {
+    if (c.malloc(usize(n))) |mem| {
+        @ptrCast(&u8, mem)[0..n]
+    } else {
+        error.OutOfMemory
+    }
+}
+
+fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 {
+    if (new_size <= old_mem.len) {
+        old_mem[0..new_size]
+    } else {
+        const old_ptr = @ptrCast(&c_void, old_mem.ptr);
+        if (c.realloc(old_ptr, usize(new_size))) |mem| {
+            @ptrCast(&u8, mem)[0..new_size]
+        } else {
+            error.OutOfMemory
+        }
+    }
+}
+
+fn cFree(self: &Allocator, old_mem: []u8) {
+    const old_ptr = @ptrCast(&c_void, old_mem.ptr);
+    c.free(old_ptr);
+}
+
+pub const IncrementingAllocator = struct {
+    allocator: Allocator,
+    bytes: []u8,
+    end_index: usize,
+    heap_handle: if (builtin.os == Os.windows) os.windows.HANDLE else void,
+
+    fn init(capacity: usize) -> %IncrementingAllocator {
+        switch (builtin.os) {
+            Os.linux, Os.darwin, Os.macosx, Os.ios => {
+                const p = os.posix;
+                const addr = p.mmap(null, capacity, p.PROT_READ|p.PROT_WRITE,
+                    p.MAP_PRIVATE|p.MAP_ANONYMOUS|p.MAP_NORESERVE, -1, 0);
+                if (addr == p.MAP_FAILED) {
+                    return error.OutOfMemory;
+                }
+                return IncrementingAllocator {
+                    .allocator = Allocator {
+                        .allocFn = alloc,
+                        .reallocFn = realloc,
+                        .freeFn = free,
+                    },
+                    .bytes = @intToPtr(&u8, addr)[0..capacity],
+                    .end_index = 0,
+                    .heap_handle = {},
+                };
+            },
+            Os.windows => {
+                const heap_handle = os.windows.GetProcessHeap() ?? return error.OutOfMemory;
+                const ptr = os.windows.HeapAlloc(heap_handle, 0, capacity) ?? return error.OutOfMemory;
+                return IncrementingAllocator {
+                    .allocator = Allocator {
+                        .allocFn = alloc,
+                        .reallocFn = realloc,
+                        .freeFn = free,
+                    },
+                    .bytes = @ptrCast(&u8, ptr)[0..capacity],
+                    .end_index = 0,
+                    .heap_handle = heap_handle,
+                };
+            },
+            else => @compileError("Unsupported OS"),
+        }
+    }
+
+    fn deinit(self: &IncrementingAllocator) {
+        switch (builtin.os) {
+            Os.linux, Os.darwin, Os.macosx, Os.ios => {
+                _ = os.posix.munmap(self.bytes.ptr, self.bytes.len);
+            },
+            Os.windows => {
+                _ = os.windows.HeapFree(self.heap_handle, 0, @ptrCast(os.windows.LPVOID, self.bytes.ptr));
+            },
+            else => @compileError("Unsupported OS"),
+        }
+    }
+
+    fn reset(self: &IncrementingAllocator) {
+        self.end_index = 0;
+    }
+
+    fn bytesLeft(self: &const IncrementingAllocator) -> usize {
+        return self.bytes.len - self.end_index;
+    }
+
+    fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 {
+        const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator);
+        const addr = @ptrToInt(&self.bytes[self.end_index]);
+        const rem = @rem(addr, alignment);
+        const march_forward_bytes = if (rem == 0) 0 else (alignment - rem);
+        const adjusted_index = self.end_index + march_forward_bytes;
+        const new_end_index = adjusted_index + n;
+        if (new_end_index > self.bytes.len) {
+            return error.OutOfMemory;
+        }
+        const result = self.bytes[adjusted_index .. new_end_index];
+        self.end_index = new_end_index;
+        return result;
+    }
+
+    fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 {
+        if (new_size <= old_mem.len) {
+            return old_mem[0..new_size];
+        } else {
+            const result = %return alloc(allocator, new_size, alignment);
+            mem.copy(u8, result, old_mem);
+            return result;
+        }
+    }
+
+    fn free(allocator: &Allocator, bytes: []u8) {
+        // Do nothing. That's the point of an incrementing allocator.
+    }
+};
+
+test "IncrementingAllocator" {
+    const total_bytes = 100 * 1024 * 1024;
+    var inc_allocator = %%IncrementingAllocator.init(total_bytes);
+    defer inc_allocator.deinit();
+
+    const allocator = &inc_allocator.allocator;
+    const slice = %%allocator.alloc(&i32, 100);
+
+    for (slice) |*item, i| {
+        *item = %%allocator.create(i32);
+        **item = i32(i);
+    }
+
+    assert(inc_allocator.bytesLeft() == total_bytes - @sizeOf(i32) * 100 - @sizeOf(usize) * 100);
+
+    inc_allocator.reset();
+
+    assert(inc_allocator.bytesLeft() == total_bytes);
+}
+
std/index.zig
@@ -15,6 +15,7 @@ pub const elf = @import("elf.zig");
 pub const empty_import = @import("empty.zig");
 pub const endian = @import("endian.zig");
 pub const fmt = @import("fmt/index.zig");
+pub const heap = @import("heap.zig");
 pub const io = @import("io.zig");
 pub const math = @import("math/index.zig");
 pub const mem = @import("mem.zig");
@@ -45,6 +46,7 @@ test "std" {
     _ = @import("io.zig");
     _ = @import("math/index.zig");
     _ = @import("mem.zig");
+    _ = @import("heap.zig");
     _ = @import("net.zig");
     _ = @import("os/index.zig");
     _ = @import("rand.zig");
std/io.zig
@@ -1,3 +1,4 @@
+const std = @import("index.zig");
 const builtin = @import("builtin");
 const Os = builtin.Os;
 const system = switch(builtin.os) {
@@ -6,41 +7,19 @@ const system = switch(builtin.os) {
     Os.windows => @import("os/windows/index.zig"),
     else => @compileError("Unsupported OS"),
 };
-const c = @import("c/index.zig");
+const c = std.c;
 
-const math = @import("math/index.zig");
-const debug = @import("debug.zig");
+const math = std.math;
+const debug = std.debug;
 const assert = debug.assert;
-const os = @import("os/index.zig");
-const mem = @import("mem.zig");
-const Buffer = @import("buffer.zig").Buffer;
-const fmt = @import("fmt/index.zig");
+const os = std.os;
+const mem = std.mem;
+const Buffer = std.Buffer;
+const fmt = std.fmt;
 
 const is_posix = builtin.os != builtin.Os.windows;
 const is_windows = builtin.os == builtin.Os.windows;
 
-pub var stdin = InStream {
-    .fd = if (is_posix) system.STDIN_FILENO else {},
-    .handle_id = if (is_windows) system.STD_INPUT_HANDLE else {},
-    .handle = if (is_windows) null else {},
-};
-
-pub var stdout = OutStream {
-    .fd = if (is_posix) system.STDOUT_FILENO else {},
-    .handle_id = if (is_windows) system.STD_OUTPUT_HANDLE else {},
-    .handle = if (is_windows) null else {},
-    .buffer = undefined,
-    .index = 0,
-};
-
-pub var stderr = OutStream {
-    .fd = if (is_posix) system.STDERR_FILENO else {},
-    .handle_id = if (is_windows) system.STD_ERROR_HANDLE else {},
-    .handle = if (is_windows) null else {},
-    .buffer = undefined,
-    .index = 0,
-};
-
 /// The function received invalid input at runtime. An Invalid error means a
 /// bug in the program that called the function.
 error Invalid;
@@ -63,306 +42,125 @@ error PathNotFound;
 error OutOfMemory;
 error Unseekable;
 error EndOfFile;
-error NoStdHandles;
-
-pub const OutStream = struct {
-    fd: if (is_posix) i32 else void,
-    handle_id: if (is_windows) system.DWORD else void,
-    handle: if (is_windows) ?system.HANDLE else void,
-    buffer: [os.page_size]u8,
-    index: usize,
-
-    /// Calls ::openMode with 0o666 for the mode.
-    pub fn open(path: []const u8, allocator: ?&mem.Allocator) -> %OutStream {
-        return openMode(path, 0o666, allocator);
 
-    }
-
-    /// `path` may need to be copied in memory to add a null terminating byte. In this case
-    /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed
-    /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned.
-    /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
-    /// Call close to clean up.
-    pub fn openMode(path: []const u8, mode: usize, allocator: ?&mem.Allocator) -> %OutStream {
-        if (is_posix) {
-            const flags = system.O_LARGEFILE|system.O_WRONLY|system.O_CREAT|system.O_CLOEXEC|system.O_TRUNC;
-            const fd = %return os.posixOpen(path, flags, mode, allocator);
-            return OutStream {
-                .fd = fd,
-                .handle = {},
-                .handle_id = {},
-                .index = 0,
-                .buffer = undefined,
-            };
-        } else if (is_windows) {
-            const handle = %return os.windowsOpen(path, system.GENERIC_WRITE,
-                system.FILE_SHARE_WRITE|system.FILE_SHARE_READ|system.FILE_SHARE_DELETE,
-                system.CREATE_ALWAYS, system.FILE_ATTRIBUTE_NORMAL, allocator);
-            return OutStream {
-                .fd = {},
-                .handle = handle,
-                .handle_id = undefined,
-                .index = 0,
-                .buffer = undefined,
-            };
-
-        } else {
-            unreachable;
-        }
-
-    }
-
-    pub fn writeByte(self: &OutStream, b: u8) -> %void {
-        if (self.buffer.len == self.index) %return self.flush();
-        self.buffer[self.index] = b;
-        self.index += 1;
-    }
-
-    pub fn write(self: &OutStream, bytes: []const u8) -> %void {
-        if (bytes.len >= self.buffer.len) {
-            %return self.flush();
-            return self.unbufferedWrite(bytes);
-        }
-
-        var src_index: usize = 0;
-
-        while (src_index < bytes.len) {
-            const dest_space_left = self.buffer.len - self.index;
-            const copy_amt = math.min(dest_space_left, bytes.len - src_index);
-            mem.copy(u8, self.buffer[self.index..], bytes[src_index..src_index + copy_amt]);
-            self.index += copy_amt;
-            assert(self.index <= self.buffer.len);
-            if (self.index == self.buffer.len) {
-                %return self.flush();
-            }
-            src_index += copy_amt;
-        }
-    }
-
-    /// Calls print and then flushes the buffer.
-    pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) -> %void {
-        %return self.print(format, args);
-        %return self.flush();
-    }
-
-    /// Does not flush the buffer.
-    pub fn print(self: &OutStream, comptime format: []const u8, args: ...) -> %void {
-        var context = PrintContext {
-            .self = self,
-            .result = {},
-        };
-        _ = fmt.format(&context, printOutput, format, args);
-        return context.result;
-    }
-    const PrintContext = struct {
-        self: &OutStream,
-        result: %void,
+pub fn getStdErr() -> %File {
+    const handle = if (is_windows) {
+        %return os.windowsGetStdHandle(system.STD_ERROR_HANDLE)
+    } else if (is_posix) {
+        system.STDERR_FILENO
+    } else {
+        unreachable
     };
-    fn printOutput(context: &PrintContext, bytes: []const u8) -> bool {
-        context.self.write(bytes) %% |err| {
-            context.result = err;
-            return false;
-        };
-        return true;
-    }
-
-    pub fn flush(self: &OutStream) -> %void {
-        if (self.index == 0)
-            return;
-
-        %return self.unbufferedWrite(self.buffer[0..self.index]);
-        self.index = 0;
-    }
-
-    pub fn close(self: &OutStream) {
-        assert(self.index == 0); // unflushed buffer
-        if (is_posix) {
-            os.posixClose(self.fd);
-        } else if (is_windows) {
-            os.windowsClose(%%self.getHandle());
-        } else {
-            unreachable;
-        }
-    }
+    return File.openHandle(handle);
+}
 
-    pub fn isTty(self: &OutStream) -> %bool {
-        if (is_posix) {
-            if (builtin.link_libc) {
-                return c.isatty(self.fd) != 0;
-            } else {
-                return system.isatty(self.fd);
-            }
-        } else if (is_windows) {
-            return os.windowsIsTty(%return self.getHandle());
-        } else {
-            unreachable;
-        }
-    }
+pub fn getStdOut() -> %File {
+    const handle = if (is_windows) {
+        %return os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE)
+    } else if (is_posix) {
+        system.STDOUT_FILENO
+    } else {
+        unreachable
+    };
+    return File.openHandle(handle);
+}
 
-    fn getHandle(self: &OutStream) -> %system.HANDLE {
-        if (self.handle) |handle| return handle;
-        if (system.GetStdHandle(self.handle_id)) |handle| {
-            if (handle == system.INVALID_HANDLE_VALUE) {
-                const err = system.GetLastError();
-                return switch (err) {
-                    else => os.unexpectedErrorWindows(err),
-                };
-            }
-            self.handle = handle;
-            return handle;
-        } else {
-            return error.NoStdHandles;
-        }
-    }
+pub fn getStdIn() -> %File {
+    const handle = if (is_windows) {
+        %return os.windowsGetStdHandle(system.STD_INPUT_HANDLE)
+    } else if (is_posix) {
+        system.STDIN_FILENO
+    } else {
+        unreachable
+    };
+    return File.openHandle(handle);
+}
 
-    fn unbufferedWrite(self: &OutStream, bytes: []const u8) -> %void {
-        if (is_posix) {
-            %return os.posixWrite(self.fd, bytes);
-        } else if (is_windows) {
-            const handle = %return self.getHandle();
-            %return os.windowsWrite(handle, bytes);
-        } else {
-            @compileError("Unsupported OS");
-        }
-    }
+pub const File = struct {
+    /// The OS-specific file descriptor or file handle.
+    handle: os.FileHandle,
 
-};
+    /// A file has the `InStream` trait
+    in_stream: InStream,
 
-// TODO created a BufferedInStream struct and move some of this code there
-// BufferedInStream API goes on top of minimal InStream API.
-pub const InStream = struct {
-    fd: if (is_posix) i32 else void,
-    handle_id: if (is_windows) system.DWORD else void,
-    handle: if (is_windows) ?system.HANDLE else void,
+    /// A file has the `OutStream` trait
+    out_stream: OutStream,
 
     /// `path` may need to be copied in memory to add a null terminating byte. In this case
     /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed
     /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned.
     /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
     /// Call close to clean up.
-    pub fn open(path: []const u8, allocator: ?&mem.Allocator) -> %InStream {
+    pub fn openRead(path: []const u8, allocator: ?&mem.Allocator) -> %File {
         if (is_posix) {
             const flags = system.O_LARGEFILE|system.O_RDONLY;
             const fd = %return os.posixOpen(path, flags, 0, allocator);
-            return InStream {
-                .fd = fd,
-                .handle_id = {},
-                .handle = {},
-            };
+            return openHandle(fd);
         } else if (is_windows) {
             const handle = %return os.windowsOpen(path, system.GENERIC_READ, system.FILE_SHARE_READ,
                 system.OPEN_EXISTING, system.FILE_ATTRIBUTE_NORMAL, allocator);
-            return InStream {
-                .fd = {},
-                .handle_id = undefined,
-                .handle = handle,
-            };
+            return openHandle(handle);
         } else {
             unreachable;
         }
     }
 
-    /// Upon success, the stream is in an uninitialized state. To continue using it,
-    /// you must use the open() function.
-    pub fn close(self: &InStream) {
-        if (is_posix) {
-            os.posixClose(self.fd);
-        } else if (is_windows) {
-            os.windowsClose(%%self.getHandle());
-        } else {
-            unreachable;
-        }
+    /// Calls `openWriteMode` with 0o666 for the mode.
+    pub fn openWrite(path: []const u8, allocator: ?&mem.Allocator) -> %File {
+        return openWriteMode(path, 0o666, allocator);
+
     }
 
-    /// Returns the number of bytes read. If the number read is smaller than buf.len, then
-    /// the stream reached End Of File.
-    pub fn read(self: &InStream, buf: []u8) -> %usize {
+    /// `path` may need to be copied in memory to add a null terminating byte. In this case
+    /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed
+    /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned.
+    /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
+    /// Call close to clean up.
+    pub fn openWriteMode(path: []const u8, mode: usize, allocator: ?&mem.Allocator) -> %File {
         if (is_posix) {
-            var index: usize = 0;
-            while (index < buf.len) {
-                const amt_read = system.read(self.fd, &buf[index], buf.len - index);
-                const read_err = system.getErrno(amt_read);
-                if (read_err > 0) {
-                    switch (read_err) {
-                        system.EINTR  => continue,
-                        system.EINVAL => unreachable,
-                        system.EFAULT => unreachable,
-                        system.EBADF  => return error.BadFd,
-                        system.EIO    => return error.Io,
-                        else          => return os.unexpectedErrorPosix(read_err),
-                    }
-                }
-                if (amt_read == 0) return index;
-                index += amt_read;
-            }
-            return index;
+            const flags = system.O_LARGEFILE|system.O_WRONLY|system.O_CREAT|system.O_CLOEXEC|system.O_TRUNC;
+            const fd = %return os.posixOpen(path, flags, mode, allocator);
+            return openHandle(fd);
         } else if (is_windows) {
-            const handle = %return self.getHandle();
-            var index: usize = 0;
-            while (index < buf.len) {
-                const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buf.len - index));
-                var amt_read: system.DWORD = undefined;
-                if (system.ReadFile(handle, @ptrCast(&c_void, &buf[index]), want_read_count, &amt_read, null) == 0) {
-                    const err = system.GetLastError();
-                    return switch (err) {
-                        system.ERROR.OPERATION_ABORTED => continue,
-                        system.ERROR.BROKEN_PIPE => return index,
-                        else => os.unexpectedErrorWindows(err),
-                    };
-                }
-                if (amt_read == 0) return index;
-                index += amt_read;
-            }
-            return index;
+            const handle = %return os.windowsOpen(path, system.GENERIC_WRITE,
+                system.FILE_SHARE_WRITE|system.FILE_SHARE_READ|system.FILE_SHARE_DELETE,
+                system.CREATE_ALWAYS, system.FILE_ATTRIBUTE_NORMAL, allocator);
+            return openHandle(handle);
         } else {
             unreachable;
         }
-    }
 
-    pub fn readNoEof(is: &InStream, buf: []u8) -> %void {
-        const amt_read = %return is.read(buf);
-        if (amt_read < buf.len) return error.EndOfFile;
     }
 
-    pub fn readByte(is: &InStream) -> %u8 {
-        var result: [1]u8 = undefined;
-        %return is.readNoEof(result[0..]);
-        return result[0];
-    }
-
-    pub fn readByteSigned(is: &InStream) -> %i8 {
-        var result: [1]i8 = undefined;
-        %return is.readNoEof(([]u8)(result[0..]));
-        return result[0];
-    }
-
-    pub fn readIntLe(is: &InStream, comptime T: type) -> %T {
-        is.readInt(false, T)
+    pub fn openHandle(handle: os.FileHandle) -> File {
+        return File {
+            .handle = handle,
+            .out_stream = OutStream {
+                .writeFn = writeFn,
+            },
+            .in_stream = InStream {
+                .readFn = readFn,
+            },
+        };
     }
 
-    pub fn readIntBe(is: &InStream, comptime T: type) -> %T {
-        is.readInt(true, T)
-    }
 
-    pub fn readInt(is: &InStream, is_be: bool, comptime T: type) -> %T {
-        var bytes: [@sizeOf(T)]u8 = undefined;
-        %return is.readNoEof(bytes[0..]);
-        return mem.readInt(bytes, T, is_be);
+    /// Upon success, the stream is in an uninitialized state. To continue using it,
+    /// you must use the open() function.
+    pub fn close(self: &File) {
+        os.close(self.handle);
+        self.handle = undefined;
     }
 
-    pub fn readVarInt(is: &InStream, is_be: bool, comptime T: type, size: usize) -> %T {
-        assert(size <= @sizeOf(T));
-        assert(size <= 8);
-        var input_buf: [8]u8 = undefined;
-        const input_slice = input_buf[0..size];
-        %return is.readNoEof(input_slice);
-        return mem.readInt(input_slice, T, is_be);
+    /// Calls `os.isTty` on `self.handle`.
+    pub fn isTty(self: &File) -> bool {
+        return os.isTty(self.handle);
     }
 
-    pub fn seekForward(is: &InStream, amount: isize) -> %void {
+    pub fn seekForward(self: &File, amount: isize) -> %void {
         switch (builtin.os) {
             Os.linux, Os.darwin => {
-                const result = system.lseek(is.fd, amount, system.SEEK_CUR);
+                const result = system.lseek(self.handle, amount, system.SEEK_CUR);
                 const err = system.getErrno(result);
                 if (err > 0) {
                     return switch (err) {
@@ -379,10 +177,10 @@ pub const InStream = struct {
         }
     }
 
-    pub fn seekTo(is: &InStream, pos: usize) -> %void {
+    pub fn seekTo(self: &File, pos: usize) -> %void {
         switch (builtin.os) {
             Os.linux, Os.darwin => {
-                const result = system.lseek(is.fd, @bitCast(isize, pos), system.SEEK_SET);
+                const result = system.lseek(self.handle, @bitCast(isize, pos), system.SEEK_SET);
                 const err = system.getErrno(result);
                 if (err > 0) {
                     return switch (err) {
@@ -399,10 +197,10 @@ pub const InStream = struct {
         }
     }
 
-    pub fn getPos(is: &InStream) -> %usize {
+    pub fn getPos(self: &File) -> %usize {
         switch (builtin.os) {
             Os.linux, Os.darwin => {
-                const result = system.lseek(is.fd, 0, system.SEEK_CUR);
+                const result = system.lseek(self.handle, 0, system.SEEK_CUR);
                 const err = system.getErrno(result);
                 if (err > 0) {
                     return switch (err) {
@@ -420,9 +218,9 @@ pub const InStream = struct {
         }
     }
 
-    pub fn getEndPos(is: &InStream) -> %usize {
+    pub fn getEndPos(self: &File) -> %usize {
         var stat: system.Stat = undefined;
-        const err = system.getErrno(system.fstat(is.fd, &stat));
+        const err = system.getErrno(system.fstat(self.handle, &stat));
         if (err > 0) {
             return switch (err) {
                 system.EBADF => error.BadFd,
@@ -434,89 +232,217 @@ pub const InStream = struct {
         return usize(stat.size);
     }
 
-    pub fn readAll(is: &InStream, buf: &Buffer) -> %void {
-        %return buf.resize(os.page_size);
+    fn readFn(in_stream: &InStream, buffer: []u8) -> %usize {
+        const self = @fieldParentPtr(File, "in_stream", in_stream);
+        if (is_posix) {
+            var index: usize = 0;
+            while (index < buffer.len) {
+                const amt_read = system.read(self.handle, &buffer[index], buffer.len - index);
+                const read_err = system.getErrno(amt_read);
+                if (read_err > 0) {
+                    switch (read_err) {
+                        system.EINTR  => continue,
+                        system.EINVAL => unreachable,
+                        system.EFAULT => unreachable,
+                        system.EBADF  => return error.BadFd,
+                        system.EIO    => return error.Io,
+                        else          => return os.unexpectedErrorPosix(read_err),
+                    }
+                }
+                if (amt_read == 0) return index;
+                index += amt_read;
+            }
+            return index;
+        } else if (is_windows) {
+            var index: usize = 0;
+            while (index < buffer.len) {
+                const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buffer.len - index));
+                var amt_read: system.DWORD = undefined;
+                if (system.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) {
+                    const err = system.GetLastError();
+                    return switch (err) {
+                        system.ERROR.OPERATION_ABORTED => continue,
+                        system.ERROR.BROKEN_PIPE => return index,
+                        else => os.unexpectedErrorWindows(err),
+                    };
+                }
+                if (amt_read == 0) return index;
+                index += amt_read;
+            }
+            return index;
+        } else {
+            unreachable;
+        }
+    }
+
+    fn writeFn(out_stream: &OutStream, bytes: []const u8) -> %void {
+        const self = @fieldParentPtr(File, "out_stream", out_stream);
+        if (is_posix) {
+            %return os.posixWrite(self.handle, bytes);
+        } else if (is_windows) {
+            %return os.windowsWrite(self.handle, bytes);
+        } else {
+            @compileError("Unsupported OS");
+        }
+    }
+
+};
+
+/// `path` may need to be copied in memory to add a null terminating byte. In this case
+/// a fixed size buffer of size `std.os.max_noalloc_path_len` is an attempted solution. If the fixed
+/// size buffer is too small, and the provided allocator is null, `error.NameTooLong` is returned.
+/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
+pub fn writeFile(path: []const u8, data: []const u8, allocator: ?&mem.Allocator) -> %void {
+    var file = %return File.openWrite(path, allocator);
+    defer file.close();
+    %return file.out_stream.write(data);
+}
+
+error StreamTooLong;
+error EndOfStream;
+
+pub const InStream = struct {
+    /// Return the number of bytes read. If the number read is smaller than buf.len, it
+    /// means the stream reached the end. Reaching the end of a stream is not an error
+    /// condition.
+    readFn: fn(self: &InStream, buffer: []u8) -> %usize,
+
+    /// Replaces `buffer` contents by reading from the stream until it is finished.
+    /// If `buffer.len()` woould exceed `max_size`, `error.StreamTooLong` is returned and
+    /// the contents read from the stream are lost.
+    pub fn readAllBuffer(self: &InStream, buffer: &Buffer, max_size: usize) -> %void {
+        %return buffer.resize(0);
 
         var actual_buf_len: usize = 0;
         while (true) {
-            const dest_slice = buf.toSlice()[actual_buf_len..];
-            const bytes_read = %return is.read(dest_slice);
+            const dest_slice = buffer.toSlice()[actual_buf_len..];
+            const bytes_read = %return self.readFn(self, dest_slice);
             actual_buf_len += bytes_read;
 
             if (bytes_read != dest_slice.len) {
-                return buf.resize(actual_buf_len);
+                buffer.shrink(actual_buf_len);
+                return;
             }
 
-            %return buf.resize(actual_buf_len + os.page_size);
+            const new_buf_size = math.min(max_size, actual_buf_len + os.page_size);
+            if (new_buf_size == actual_buf_len)
+                return error.StreamTooLong;
+            %return buffer.resize(new_buf_size);
         }
     }
 
-    pub fn readLine(is: &InStream, buf: &Buffer) -> %void {
+    /// Allocates enough memory to hold all the contents of the stream. If the allocated
+    /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
+    /// Caller owns returned memory.
+    /// If this function returns an error, the contents from the stream read so far are lost.
+    pub fn readAllAlloc(self: &InStream, allocator: &mem.Allocator, max_size: usize) -> %[]u8 {
+        var buf = Buffer.initNull(allocator);
+        defer buf.deinit();
+
+        %return self.readAllBuffer(self, &buf, max_size);
+        return buf.toOwnedSlice();
+    }
+
+    /// Replaces `buffer` contents by reading from the stream until `delimiter` is found.
+    /// Does not include the delimiter in the result.
+    /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and the contents
+    /// read from the stream so far are lost.
+    pub fn readUntilDelimiterBuffer(self: &InStream, buffer: &Buffer, delimiter: u8, max_size: usize) -> %void {
         %return buf.resize(0);
 
         while (true) {
-            var byte: u8 = %return is.readByte();
-            %return buf.appendByte(byte);
+            var byte: u8 = %return self.readByte();
 
-            if (buf.endsWith(os.line_sep)) {
-                break;
+            if (byte == delimiter) {
+                return;
             }
-        }
-    }
 
-    pub fn isTty(self: &InStream) -> %bool {
-        if (is_posix) {
-            if (builtin.link_libc) {
-                return c.isatty(self.fd) != 0;
-            } else {
-                return system.isatty(self.fd);
+            if (buf.len() == max_size) {
+                return error.StreamTooLong;
             }
-        } else if (is_windows) {
-            return os.windowsIsTty(%return self.getHandle());
-        } else {
-            @compileError("Unsupported OS");
+
+            %return buf.appendByte(byte);
         }
     }
 
-    fn getHandle(self: &InStream) -> %system.HANDLE {
-        if (self.handle) |handle| return handle;
-        if (system.GetStdHandle(self.handle_id)) |handle| {
-            if (handle == system.INVALID_HANDLE_VALUE) {
-                const err = system.GetLastError();
-                return switch (err) {
-                    else => os.unexpectedErrorWindows(err),
-                };
-            }
-            self.handle = handle;
-            return handle;
-        } else {
-            return error.NoStdHandles;
-        }
+    /// Allocates enough memory to read until `delimiter`. If the allocated
+    /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
+    /// Caller owns returned memory.
+    /// If this function returns an error, the contents from the stream read so far are lost.
+    pub fn readUntilDelimiterAlloc(self: &InStream, allocator: &mem.Allocator,
+        delimiter: u8, max_size: usize) -> %[]u8
+    {
+        var buf = Buffer.initNull(allocator);
+        defer buf.deinit();
+
+        %return self.readUntilDelimiterBuffer(self, &buf, delimiter, max_size);
+        return buf.toOwnedSlice();
+    }
+
+    /// Returns the number of bytes read. If the number read is smaller than buf.len, it
+    /// means the stream reached the end. Reaching the end of a stream is not an error
+    /// condition.
+    pub fn read(self: &InStream, buffer: []u8) -> %usize {
+        return self.readFn(self, buffer);
+    }
+
+    /// Same as `read` but end of stream returns `error.EndOfStream`.
+    pub fn readNoEof(self: &InStream, buf: []u8) -> %void {
+        const amt_read = %return self.read(buf);
+        if (amt_read < buf.len) return error.EndOfStream;
+    }
+
+    /// Reads 1 byte from the stream or returns `error.EndOfStream`.
+    pub fn readByte(self: &InStream) -> %u8 {
+        var result: [1]u8 = undefined;
+        %return self.readNoEof(result[0..]);
+        return result[0];
     }
+
+    /// Same as `readByte` except the returned byte is signed.
+    pub fn readByteSigned(self: &InStream) -> %i8 {
+        return @bitCast(i8, %return self.readByte());
+    }
+
+    pub fn readIntLe(self: &InStream, comptime T: type) -> %T {
+        return self.readInt(false, T);
+    }
+
+    pub fn readIntBe(self: &InStream, comptime T: type) -> %T {
+        return self.readInt(true, T);
+    }
+
+    pub fn readInt(self: &InStream, is_be: bool, comptime T: type) -> %T {
+        var bytes: [@sizeOf(T)]u8 = undefined;
+        %return self.readNoEof(bytes[0..]);
+        return mem.readInt(bytes, T, is_be);
+    }
+
+    pub fn readVarInt(self: &InStream, is_be: bool, comptime T: type, size: usize) -> %T {
+        assert(size <= @sizeOf(T));
+        assert(size <= 8);
+        var input_buf: [8]u8 = undefined;
+        const input_slice = input_buf[0..size];
+        %return self.readNoEof(input_slice);
+        return mem.readInt(input_slice, T, is_be);
+    }
+
+
 };
 
-pub fn openSelfExe() -> %InStream {
-    switch (builtin.os) {
-        Os.linux => {
-            return InStream.open("/proc/self/exe", null);
-        },
-        Os.darwin => {
-            debug.panic("TODO: openSelfExe on Darwin");
-        },
-        else => @compileError("Unsupported OS"),
+pub const OutStream = struct {
+    writeFn: fn(self: &OutStream, bytes: []const u8) -> %void,
+
+    pub fn print(self: &OutStream, comptime format: []const u8, args: ...) -> %void {
+        return std.fmt.format(self, self.writeFn, format, args);
     }
-}
 
-/// `path` may need to be copied in memory to add a null terminating byte. In this case
-/// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed
-/// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned.
-/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
-pub fn writeFile(path: []const u8, data: []const u8, allocator: ?&mem.Allocator) -> %void {
-    // TODO have an unbuffered File abstraction and use that here.
-    // Then a buffered out stream abstraction can go on top of that for
-    // use cases like stdout and stderr.
-    var out_stream = %return OutStream.open(path, allocator);
-    defer out_stream.close();
-    %return out_stream.write(data);
-    %return out_stream.flush();
-}
+    pub fn write(self: &OutStream, bytes: []const u8) -> %void {
+        return self.writeFn(self, bytes);
+    }
+
+    pub fn writeByte(self: &OutStream, byte: u8) -> %void {
+        const slice = (&byte)[0..1];
+        return self.writeFn(self, slice);
+    }
+};
std/mem.zig
@@ -1,16 +1,9 @@
 const debug = @import("debug.zig");
 const assert = debug.assert;
 const math = @import("math/index.zig");
-const os = @import("os/index.zig");
-const io = @import("io.zig");
-const builtin = @import("builtin");
-const Os = builtin.Os;
-const c = @import("c/index.zig");
 
 pub const Cmp = math.Cmp;
 
-error OutOfMemory;
-
 pub const Allocator = struct {
     /// Allocate byte_count bytes and return them in a slice, with the
     /// slicer's pointer aligned at least to alignment bytes.
@@ -85,151 +78,6 @@ pub const Allocator = struct {
     }
 };
 
-pub var c_allocator = Allocator {
-    .allocFn = cAlloc,
-    .reallocFn = cRealloc,
-    .freeFn = cFree,
-};
-
-fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 {
-    if (c.malloc(usize(n))) |mem| {
-        @ptrCast(&u8, mem)[0..n]
-    } else {
-        error.OutOfMemory
-    }
-}
-
-fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 {
-    if (new_size <= old_mem.len) {
-        old_mem[0..new_size]
-    } else {
-        const old_ptr = @ptrCast(&c_void, old_mem.ptr);
-        if (c.realloc(old_ptr, usize(new_size))) |mem| {
-            @ptrCast(&u8, mem)[0..new_size]
-        } else {
-            error.OutOfMemory
-        }
-    }
-}
-
-fn cFree(self: &Allocator, old_mem: []u8) {
-    const old_ptr = @ptrCast(&c_void, old_mem.ptr);
-    c.free(old_ptr);
-}
-
-pub const IncrementingAllocator = struct {
-    allocator: Allocator,
-    bytes: []u8,
-    end_index: usize,
-    heap_handle: if (builtin.os == Os.windows) os.windows.HANDLE else void,
-
-    fn init(capacity: usize) -> %IncrementingAllocator {
-        switch (builtin.os) {
-            Os.linux, Os.darwin, Os.macosx, Os.ios => {
-                const p = os.posix;
-                const addr = p.mmap(null, capacity, p.PROT_READ|p.PROT_WRITE,
-                    p.MAP_PRIVATE|p.MAP_ANONYMOUS|p.MAP_NORESERVE, -1, 0);
-                if (addr == p.MAP_FAILED) {
-                    return error.OutOfMemory;
-                }
-                return IncrementingAllocator {
-                    .allocator = Allocator {
-                        .allocFn = alloc,
-                        .reallocFn = realloc,
-                        .freeFn = free,
-                    },
-                    .bytes = @intToPtr(&u8, addr)[0..capacity],
-                    .end_index = 0,
-                    .heap_handle = {},
-                };
-            },
-            Os.windows => {
-                const heap_handle = os.windows.GetProcessHeap() ?? return error.OutOfMemory;
-                const ptr = os.windows.HeapAlloc(heap_handle, 0, capacity) ?? return error.OutOfMemory;
-                return IncrementingAllocator {
-                    .allocator = Allocator {
-                        .allocFn = alloc,
-                        .reallocFn = realloc,
-                        .freeFn = free,
-                    },
-                    .bytes = @ptrCast(&u8, ptr)[0..capacity],
-                    .end_index = 0,
-                    .heap_handle = heap_handle,
-                };
-            },
-            else => @compileError("Unsupported OS"),
-        }
-    }
-
-    fn deinit(self: &IncrementingAllocator) {
-        switch (builtin.os) {
-            Os.linux, Os.darwin, Os.macosx, Os.ios => {
-                _ = os.posix.munmap(self.bytes.ptr, self.bytes.len);
-            },
-            Os.windows => {
-                _ = os.windows.HeapFree(self.heap_handle, 0, @ptrCast(os.windows.LPVOID, self.bytes.ptr));
-            },
-            else => @compileError("Unsupported OS"),
-        }
-    }
-
-    fn reset(self: &IncrementingAllocator) {
-        self.end_index = 0;
-    }
-
-    fn bytesLeft(self: &const IncrementingAllocator) -> usize {
-        return self.bytes.len - self.end_index;
-    }
-
-    fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 {
-        const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator);
-        const addr = @ptrToInt(&self.bytes[self.end_index]);
-        const rem = @rem(addr, alignment);
-        const march_forward_bytes = if (rem == 0) 0 else (alignment - rem);
-        const adjusted_index = self.end_index + march_forward_bytes;
-        const new_end_index = adjusted_index + n;
-        if (new_end_index > self.bytes.len) {
-            return error.OutOfMemory;
-        }
-        const result = self.bytes[adjusted_index .. new_end_index];
-        self.end_index = new_end_index;
-        return result;
-    }
-
-    fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 {
-        if (new_size <= old_mem.len) {
-            return old_mem[0..new_size];
-        } else {
-            const result = %return alloc(allocator, new_size, alignment);
-            copy(u8, result, old_mem);
-            return result;
-        }
-    }
-
-    fn free(allocator: &Allocator, bytes: []u8) {
-        // Do nothing. That's the point of an incrementing allocator.
-    }
-};
-
-test "mem.IncrementingAllocator" {
-    const total_bytes = 100 * 1024 * 1024;
-    var inc_allocator = %%IncrementingAllocator.init(total_bytes);
-    defer inc_allocator.deinit();
-
-    const allocator = &inc_allocator.allocator;
-    const slice = %%allocator.alloc(&i32, 100);
-
-    for (slice) |*item, i| {
-        *item = %%allocator.create(i32);
-        **item = i32(i);
-    }
-
-    assert(inc_allocator.bytesLeft() == total_bytes - @sizeOf(i32) * 100 - @sizeOf(usize) * 100);
-
-    inc_allocator.reset();
-
-    assert(inc_allocator.bytesLeft() == total_bytes);
-}
 
 /// Copy all of source into dest at position 0.
 /// dest.len must be >= source.len.
test/compare_output.zig
@@ -17,7 +17,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\
             \\pub fn main() -> %void {
             \\    privateFunction();
-            \\    %%stdout.printf("OK 2\n");
+            \\    const stdout = &(%%getStdOut()).out_stream;
+            \\    %%stdout.print("OK 2\n");
             \\}
             \\
             \\fn privateFunction() {
@@ -31,7 +32,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\// purposefully conflicting function with main.zig
             \\// but it's private so it should be OK
             \\fn privateFunction() {
-            \\    %%stdout.printf("OK 1\n");
+            \\    const stdout = &(%%getStdOut()).out_stream;
+            \\    %%stdout.print("OK 1\n");
             \\}
             \\
             \\pub fn printText() {
@@ -56,7 +58,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         tc.addSourceFile("foo.zig",
             \\use @import("std").io;
             \\pub fn foo_function() {
-            \\    %%stdout.printf("OK\n");
+            \\    const stdout = &(%%getStdOut()).out_stream;
+            \\    %%stdout.print("OK\n");
             \\}
         );
 
@@ -66,7 +69,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\
             \\pub fn bar_function() {
             \\    if (foo_function()) {
-            \\        %%stdout.printf("OK\n");
+            \\        const stdout = &(%%getStdOut()).out_stream;
+            \\        %%stdout.print("OK\n");
             \\    }
             \\}
         );
@@ -97,7 +101,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\pub const a_text = "OK\n";
             \\
             \\pub fn ok() {
-            \\    %%io.stdout.printf(b_text);
+            \\    const stdout = &(%%io.getStdOut()).out_stream;
+            \\    %%stdout.print(b_text);
             \\}
         );
 
@@ -114,7 +119,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\const io = @import("std").io;
         \\
         \\pub fn main() -> %void {
-        \\    %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a'));
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
+        \\    %%stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a'));
         \\}
     , "Hello, world!\n0012 012 a\n");
 
@@ -266,7 +272,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\    var x_local : i32 = print_ok(x);
         \\}
         \\fn print_ok(val: @typeOf(x)) -> @typeOf(foo) {
-        \\    %%io.stdout.printf("OK\n");
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
+        \\    %%stdout.print("OK\n");
         \\    return 0;
         \\}
         \\const foo : i32 = 0;
@@ -347,24 +354,26 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\pub fn main() -> %void {
         \\    const bar = Bar {.field2 = 13,};
         \\    const foo = Foo {.field1 = bar,};
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
         \\    if (!foo.method()) {
-        \\        %%io.stdout.printf("BAD\n");
+        \\        %%stdout.print("BAD\n");
         \\    }
         \\    if (!bar.method()) {
-        \\        %%io.stdout.printf("BAD\n");
+        \\        %%stdout.print("BAD\n");
         \\    }
-        \\    %%io.stdout.printf("OK\n");
+        \\    %%stdout.print("OK\n");
         \\}
     , "OK\n");
 
     cases.add("defer with only fallthrough",
         \\const io = @import("std").io;
         \\pub fn main() -> %void {
-        \\    %%io.stdout.printf("before\n");
-        \\    defer %%io.stdout.printf("defer1\n");
-        \\    defer %%io.stdout.printf("defer2\n");
-        \\    defer %%io.stdout.printf("defer3\n");
-        \\    %%io.stdout.printf("after\n");
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
+        \\    %%stdout.print("before\n");
+        \\    defer %%stdout.print("defer1\n");
+        \\    defer %%stdout.print("defer2\n");
+        \\    defer %%stdout.print("defer3\n");
+        \\    %%stdout.print("after\n");
         \\}
     , "before\nafter\ndefer3\ndefer2\ndefer1\n");
 
@@ -372,13 +381,14 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\const io = @import("std").io;
         \\const os = @import("std").os;
         \\pub fn main() -> %void {
-        \\    %%io.stdout.printf("before\n");
-        \\    defer %%io.stdout.printf("defer1\n");
-        \\    defer %%io.stdout.printf("defer2\n");
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
+        \\    %%stdout.print("before\n");
+        \\    defer %%stdout.print("defer1\n");
+        \\    defer %%stdout.print("defer2\n");
         \\    var args_it = @import("std").os.args();
         \\    if (args_it.skip() and !args_it.skip()) return;
-        \\    defer %%io.stdout.printf("defer3\n");
-        \\    %%io.stdout.printf("after\n");
+        \\    defer %%stdout.print("defer3\n");
+        \\    %%stdout.print("after\n");
         \\}
     , "before\ndefer2\ndefer1\n");
 
@@ -388,12 +398,13 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\    do_test() %% return;
         \\}
         \\fn do_test() -> %void {
-        \\    %%io.stdout.printf("before\n");
-        \\    defer %%io.stdout.printf("defer1\n");
-        \\    %defer %%io.stdout.printf("deferErr\n");
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
+        \\    %%stdout.print("before\n");
+        \\    defer %%stdout.print("defer1\n");
+        \\    %defer %%stdout.print("deferErr\n");
         \\    %return its_gonna_fail();
-        \\    defer %%io.stdout.printf("defer3\n");
-        \\    %%io.stdout.printf("after\n");
+        \\    defer %%stdout.print("defer3\n");
+        \\    %%stdout.print("after\n");
         \\}
         \\error IToldYouItWouldFail;
         \\fn its_gonna_fail() -> %void {
@@ -407,12 +418,13 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\    do_test() %% return;
         \\}
         \\fn do_test() -> %void {
-        \\    %%io.stdout.printf("before\n");
-        \\    defer %%io.stdout.printf("defer1\n");
-        \\    %defer %%io.stdout.printf("deferErr\n");
+        \\    const stdout = &(%%io.getStdOut()).out_stream;
+        \\    %%stdout.print("before\n");
+        \\    defer %%stdout.print("defer1\n");
+        \\    %defer %%stdout.print("deferErr\n");
         \\    %return its_gonna_pass();
-        \\    defer %%io.stdout.printf("defer3\n");
-        \\    %%io.stdout.printf("after\n");
+        \\    defer %%stdout.print("defer3\n");
+        \\    %%stdout.print("after\n");
         \\}
         \\fn its_gonna_pass() -> %void { }
     , "before\nafter\ndefer3\ndefer1\n");
@@ -423,7 +435,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\const io = @import("std").io;
             \\
             \\pub fn main() -> %void {
-            \\    %%io.stdout.printf(foo_txt);
+            \\    const stdout = &(%%io.getStdOut()).out_stream;
+            \\    %%stdout.print(foo_txt);
             \\}
         , "1234\nabcd\n");
 
test/tests.zig
@@ -1,5 +1,6 @@
 const std = @import("std");
 const debug = std.debug;
+const warn = debug.warn;
 const build = std.build;
 const os = std.os;
 const StdIo = os.ChildProcess.StdIo;
@@ -50,6 +51,8 @@ const test_targets = []TestTarget {
 
 error TestFailed;
 
+const max_stdout_size = 1 * 1024 * 1024; // 1 MB
+
 pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     const cases = %%b.allocator.create(CompareOutputContext);
     *cases = CompareOutputContext {
@@ -231,7 +234,7 @@ pub const CompareOutputContext = struct {
 
             const full_exe_path = b.pathFromRoot(self.exe_path);
 
-            %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
+            warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
             const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator);
             defer child.deinit();
@@ -246,8 +249,8 @@ pub const CompareOutputContext = struct {
             var stdout = Buffer.initNull(b.allocator);
             var stderr = Buffer.initNull(b.allocator);
 
-            %%(??child.stdout).readAll(&stdout);
-            %%(??child.stderr).readAll(&stderr);
+            %%(??child.stdout).in_stream.readAllBuffer(&stdout, max_stdout_size);
+            %%(??child.stderr).in_stream.readAllBuffer(&stderr, max_stdout_size);
 
             const term = child.wait() %% |err| {
                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
@@ -255,19 +258,19 @@ pub const CompareOutputContext = struct {
             switch (term) {
                 Term.Exited => |code| {
                     if (code != 0) {
-                        %%io.stderr.printf("Process {} exited with error code {}\n", full_exe_path, code);
+                        warn("Process {} exited with error code {}\n", full_exe_path, code);
                         return error.TestFailed;
                     }
                 },
                 else => {
-                    %%io.stderr.printf("Process {} terminated unexpectedly\n", full_exe_path);
+                    warn("Process {} terminated unexpectedly\n", full_exe_path);
                     return error.TestFailed;
                 },
             };
 
 
             if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
-                %%io.stderr.printf(
+                warn(
                     \\
                     \\========= Expected this output: =========
                     \\{}
@@ -277,7 +280,7 @@ pub const CompareOutputContext = struct {
                 , self.expected_output, stdout.toSliceConst());
                 return error.TestFailed;
             }
-            %%io.stderr.printf("OK\n");
+            warn("OK\n");
         }
     };
 
@@ -310,7 +313,7 @@ pub const CompareOutputContext = struct {
 
             const full_exe_path = b.pathFromRoot(self.exe_path);
 
-            %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
+            warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
             const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator);
             defer child.deinit();
@@ -328,24 +331,24 @@ pub const CompareOutputContext = struct {
             switch (term) {
                 Term.Exited => |code| {
                     if (code != expected_exit_code) {
-                        %%io.stderr.printf("\nProgram expected to exit with code {} " ++
+                        warn("\nProgram expected to exit with code {} " ++
                             "but exited with code {}\n", expected_exit_code, code);
                         return error.TestFailed;
                     }
                 },
                 Term.Signal => |sig| {
-                    %%io.stderr.printf("\nProgram expected to exit with code {} " ++
+                    warn("\nProgram expected to exit with code {} " ++
                         "but instead signaled {}\n", expected_exit_code, sig);
                     return error.TestFailed;
                 },
                 else => {
-                    %%io.stderr.printf("\nProgram expected to exit with code {}" ++
+                    warn("\nProgram expected to exit with code {}" ++
                         " but exited in an unexpected way\n", expected_exit_code);
                     return error.TestFailed;
                 },
             }
 
-            %%io.stderr.printf("OK\n");
+            warn("OK\n");
         }
     };
 
@@ -554,7 +557,7 @@ pub const CompileErrorContext = struct {
                 Mode.ReleaseFast => %%zig_args.append("--release-fast"),
             }
 
-            %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
+            warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
             if (b.verbose) {
                 printInvocation(zig_args.toSliceConst());
@@ -573,8 +576,8 @@ pub const CompileErrorContext = struct {
             var stdout_buf = Buffer.initNull(b.allocator);
             var stderr_buf = Buffer.initNull(b.allocator);
 
-            %%(??child.stdout).readAll(&stdout_buf);
-            %%(??child.stderr).readAll(&stderr_buf);
+            %%(??child.stdout).in_stream.readAllBuffer(&stdout_buf, max_stdout_size);
+            %%(??child.stderr).in_stream.readAllBuffer(&stderr_buf, max_stdout_size);
 
             const term = child.wait() %% |err| {
                 debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err));
@@ -582,12 +585,12 @@ pub const CompileErrorContext = struct {
             switch (term) {
                 Term.Exited => |code| {
                     if (code == 0) {
-                        %%io.stderr.printf("Compilation incorrectly succeeded\n");
+                        warn("Compilation incorrectly succeeded\n");
                         return error.TestFailed;
                     }
                 },
                 else => {
-                    %%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe);
+                    warn("Process {} terminated unexpectedly\n", b.zig_exe);
                     return error.TestFailed;
                 },
             };
@@ -597,7 +600,7 @@ pub const CompileErrorContext = struct {
             const stderr = stderr_buf.toSliceConst();
 
             if (stdout.len != 0) {
-                %%io.stderr.printf(
+                warn(
                     \\
                     \\Expected empty stdout, instead found:
                     \\================================================
@@ -610,7 +613,7 @@ pub const CompileErrorContext = struct {
 
             for (self.case.expected_errors.toSliceConst()) |expected_error| {
                 if (mem.indexOf(u8, stderr, expected_error) == null) {
-                    %%io.stderr.printf(
+                    warn(
                         \\
                         \\========= Expected this compile error: =========
                         \\{}
@@ -621,15 +624,15 @@ pub const CompileErrorContext = struct {
                     return error.TestFailed;
                 }
             }
-            %%io.stderr.printf("OK\n");
+            warn("OK\n");
         }
     };
 
     fn printInvocation(args: []const []const u8) {
         for (args) |arg| {
-            %%io.stderr.printf("{} ", arg);
+            warn("{} ", arg);
         }
-        %%io.stderr.printf("\n");
+        warn("\n");
     }
 
     pub fn create(self: &CompileErrorContext, name: []const u8, source: []const u8,
@@ -822,7 +825,7 @@ pub const ParseCContext = struct {
             %%zig_args.append("parsec");
             %%zig_args.append(b.pathFromRoot(root_src));
 
-            %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
+            warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
             if (b.verbose) {
                 printInvocation(zig_args.toSliceConst());
@@ -841,8 +844,8 @@ pub const ParseCContext = struct {
             var stdout_buf = Buffer.initNull(b.allocator);
             var stderr_buf = Buffer.initNull(b.allocator);
 
-            %%(??child.stdout).readAll(&stdout_buf);
-            %%(??child.stderr).readAll(&stderr_buf);
+            %%(??child.stdout).in_stream.readAllBuffer(&stdout_buf, max_stdout_size);
+            %%(??child.stderr).in_stream.readAllBuffer(&stderr_buf, max_stdout_size);
 
             const term = child.wait() %% |err| {
                 debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err));
@@ -850,16 +853,16 @@ pub const ParseCContext = struct {
             switch (term) {
                 Term.Exited => |code| {
                     if (code != 0) {
-                        %%io.stderr.printf("Compilation failed with exit code {}\n", code);
+                        warn("Compilation failed with exit code {}\n", code);
                         return error.TestFailed;
                     }
                 },
                 Term.Signal => |code| {
-                    %%io.stderr.printf("Compilation failed with signal {}\n", code);
+                    warn("Compilation failed with signal {}\n", code);
                     return error.TestFailed;
                 },
                 else => {
-                    %%io.stderr.printf("Compilation terminated unexpectedly\n");
+                    warn("Compilation terminated unexpectedly\n");
                     return error.TestFailed;
                 },
             };
@@ -868,7 +871,7 @@ pub const ParseCContext = struct {
             const stderr = stderr_buf.toSliceConst();
 
             if (stderr.len != 0 and !self.case.allow_warnings) {
-                %%io.stderr.printf(
+                warn(
                     \\====== parsec emitted warnings: ============
                     \\{}
                     \\============================================
@@ -879,7 +882,7 @@ pub const ParseCContext = struct {
 
             for (self.case.expected_lines.toSliceConst()) |expected_line| {
                 if (mem.indexOf(u8, stdout, expected_line) == null) {
-                    %%io.stderr.printf(
+                    warn(
                         \\
                         \\========= Expected this output: ================
                         \\{}
@@ -890,15 +893,15 @@ pub const ParseCContext = struct {
                     return error.TestFailed;
                 }
             }
-            %%io.stderr.printf("OK\n");
+            warn("OK\n");
         }
     };
 
     fn printInvocation(args: []const []const u8) {
         for (args) |arg| {
-            %%io.stderr.printf("{} ", arg);
+            warn("{} ", arg);
         }
-        %%io.stderr.printf("\n");
+        warn("\n");
     }
 
     pub fn create(self: &ParseCContext, allow_warnings: bool, filename: []const u8, name: []const u8,
CMakeLists.txt
@@ -521,6 +521,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/fmt/errol/index.zig" DESTINATION "${ZIG_S
 install(FILES "${CMAKE_SOURCE_DIR}/std/fmt/errol/lookup.zig" DESTINATION "${ZIG_STD_DEST}/fmt/errol")
 install(FILES "${CMAKE_SOURCE_DIR}/std/fmt/index.zig" DESTINATION "${ZIG_STD_DEST}/fmt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}")
+install(FILES "${CMAKE_SOURCE_DIR}/std/heap.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/index.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/io.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/linked_list.zig" DESTINATION "${ZIG_STD_DEST}")
@@ -605,10 +606,10 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/fixunstfdi.zig" DESTI
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/fixunstfsi.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/fixunstfti.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/index.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
-install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivti3.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivmod.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivmoddi4.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivmodti4.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
+install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivti3.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/umodti3.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/panic.zig" DESTINATION "${ZIG_STD_DEST}/special")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/test_runner.zig" DESTINATION "${ZIG_STD_DEST}/special")