Commit 0489d06c24

Andrew Kelley <andrew@ziglang.org>
2019-09-10 06:25:37
make the std lib support event-based I/O
also add -fstack-report
1 parent a29ce78
src/all_types.hpp
@@ -1972,6 +1972,8 @@ struct CodeGen {
     ZigFn *panic_fn;
     TldFn *panic_tld_fn;
 
+    ZigFn *largest_frame_fn;
+
     WantPIC want_pic;
     WantStackCheck want_stack_check;
     CacheHash cache_hash;
@@ -2004,6 +2006,7 @@ struct CodeGen {
     bool generate_error_name_table;
     bool enable_cache; // mutually exclusive with output_dir
     bool enable_time_report;
+    bool enable_stack_report;
     bool system_linker_hack;
     bool reported_bad_link_libc_error;
     bool is_dynamic; // shared library rather than static library. dynamic musl rather than static musl.
src/analyze.cpp
@@ -5737,11 +5737,19 @@ static void mark_suspension_point(Scope *scope) {
                 return;
             case ScopeIdVarDecl:
             case ScopeIdDefer:
+            case ScopeIdBlock:
                 looking_for_exprs = false;
                 continue;
-            case ScopeIdLoop:
             case ScopeIdRuntime:
                 continue;
+            case ScopeIdLoop: {
+                ScopeLoop *loop_scope = reinterpret_cast<ScopeLoop *>(scope);
+                if (loop_scope->spill_scope != nullptr) {
+                    loop_scope->spill_scope->need_spill = MemoizedBoolTrue;
+                }
+                looking_for_exprs = false;
+                continue;
+            }
             case ScopeIdExpr: {
                 if (!looking_for_exprs) {
                     // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)
@@ -5758,14 +5766,6 @@ static void mark_suspension_point(Scope *scope) {
                 child_expr_scope = parent_expr_scope;
                 continue;
             }
-            case ScopeIdBlock:
-                if (scope->parent->parent->id == ScopeIdLoop) {
-                    ScopeLoop *loop_scope = reinterpret_cast<ScopeLoop *>(scope->parent->parent);
-                    if (loop_scope->spill_scope != nullptr) {
-                        loop_scope->spill_scope->need_spill = MemoizedBoolTrue;
-                    }
-                }
-                return;
         }
     }
 }
@@ -6082,6 +6082,11 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
     frame_type->abi_size = frame_type->data.frame.locals_struct->abi_size;
     frame_type->abi_align = frame_type->data.frame.locals_struct->abi_align;
     frame_type->size_in_bits = frame_type->data.frame.locals_struct->size_in_bits;
+
+    if (g->largest_frame_fn == nullptr || frame_type->abi_size > g->largest_frame_fn->frame_type->abi_size) {
+        g->largest_frame_fn = fn;
+    }
+
     return ErrorNone;
 }
 
src/main.cpp
@@ -16,6 +16,7 @@
 #include "libc_installation.hpp"
 #include "userland.h"
 #include "glibc.hpp"
+#include "stack_report.hpp"
 
 #include <stdio.h>
 
@@ -62,6 +63,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  -fPIC                        enable Position Independent Code\n"
         "  -fno-PIC                     disable Position Independent Code\n"
         "  -ftime-report                print timing diagnostics\n"
+        "  -fstack-report               print stack size diagnostics\n"
         "  --libc [file]                Provide a file which specifies libc paths\n"
         "  --name [name]                override output name\n"
         "  --output-dir [dir]           override output directory (defaults to cwd)\n"
@@ -476,6 +478,7 @@ int main(int argc, char **argv) {
     size_t ver_minor = 0;
     size_t ver_patch = 0;
     bool timing_info = false;
+    bool stack_report = false;
     const char *cache_dir = nullptr;
     CliPkg *cur_pkg = allocate<CliPkg>(1);
     BuildMode build_mode = BuildModeDebug;
@@ -664,6 +667,8 @@ int main(int argc, char **argv) {
                 each_lib_rpath = true;
             } else if (strcmp(arg, "-ftime-report") == 0) {
                 timing_info = true;
+            } else if (strcmp(arg, "-fstack-report") == 0) {
+                stack_report = true;
             } else if (strcmp(arg, "--enable-valgrind") == 0) {
                 valgrind_support = ValgrindSupportEnabled;
             } else if (strcmp(arg, "--disable-valgrind") == 0) {
@@ -1136,6 +1141,7 @@ int main(int argc, char **argv) {
             g->subsystem = subsystem;
 
             g->enable_time_report = timing_info;
+            g->enable_stack_report = stack_report;
             codegen_set_out_name(g, buf_out_name);
             codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
             g->want_single_threaded = want_single_threaded;
@@ -1223,6 +1229,8 @@ int main(int argc, char **argv) {
                 codegen_build_and_link(g);
                 if (timing_info)
                     codegen_print_timing_report(g, stdout);
+                if (stack_report)
+                    zig_print_stack_report(g, stdout);
 
                 if (cmd == CmdRun) {
                     const char *exec_path = buf_ptr(&g->output_file_path);
@@ -1272,6 +1280,10 @@ int main(int argc, char **argv) {
                     codegen_print_timing_report(g, stdout);
                 }
 
+                if (stack_report) {
+                    zig_print_stack_report(g, stdout);
+                }
+
                 Buf *test_exe_path_unresolved = &g->output_file_path;
                 Buf *test_exe_path = buf_alloc();
                 *test_exe_path = os_path_resolve(&test_exe_path_unresolved, 1);
src/stack_report.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#include "stack_report.hpp"
+
+static void tree_print(FILE *f, ZigType *ty, size_t indent);
+
+static void pretty_print_bytes(FILE *f, double n) {
+    if (n > 1024.0 * 1024.0 * 1024.0) {
+        fprintf(f, "%.02f GiB", n / 1024.0 / 1024.0 / 1024.0);
+        return;
+    }
+    if (n > 1024.0 * 1024.0) {
+        fprintf(f, "%.02f MiB", n / 1024.0 / 1024.0);
+        return;
+    }
+    if (n > 1024.0) {
+        fprintf(f, "%.02f KiB", n / 1024.0);
+        return;
+    }
+    fprintf(f, "%.02f bytes", n );
+    return;
+}
+
+static int compare_type_abi_sizes_desc(const void *a, const void *b) {
+    uint64_t size_a = (*(ZigType * const*)(a))->abi_size;
+    uint64_t size_b = (*(ZigType * const*)(b))->abi_size;
+    if (size_a > size_b)
+        return -1;
+    if (size_a < size_b)
+        return 1;
+    return 0;
+}
+
+static void tree_print_struct(FILE *f, ZigType *struct_type, size_t indent) {
+    ZigList<ZigType *> children = {};
+    uint64_t sum_from_fields = 0;
+    for (size_t i = 0; i < struct_type->data.structure.src_field_count; i += 1) {
+        TypeStructField *field = &struct_type->data.structure.fields[i];
+        children.append(field->type_entry);
+        sum_from_fields += field->type_entry->abi_size;
+    }
+    qsort(children.items, children.length, sizeof(ZigType *), compare_type_abi_sizes_desc);
+    fprintf(f, " (padding = %" ZIG_PRI_u64 ")\n", struct_type->abi_size - sum_from_fields);
+    for (size_t i = 0; i < children.length; i += 1) {
+        ZigType *child_type = children.at(i);
+        tree_print(f, child_type, indent + 1);
+    }
+}
+
+static void tree_print(FILE *f, ZigType *ty, size_t indent) {
+    for (size_t i = 0; i < indent; i += 1) {
+        fprintf(f, " ");
+    }
+    fprintf(f, "%s: ", buf_ptr(&ty->name));
+    pretty_print_bytes(f, ty->abi_size);
+    switch (ty->id) {
+        case ZigTypeIdFnFrame:
+            return tree_print_struct(f, ty->data.frame.locals_struct, indent);
+        case ZigTypeIdStruct:
+            return tree_print_struct(f, ty, indent);
+        default:
+            fprintf(f, "\n");
+            return;
+    }
+}
+
+void zig_print_stack_report(CodeGen *g, FILE *f) {
+    if (g->largest_frame_fn == nullptr) {
+        fprintf(f, "No async function frames in entire compilation.\n");
+        return;
+    }
+    tree_print(f, g->largest_frame_fn->frame_type, 0);
+}
src/stack_report.hpp
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_STACK_REPORT_HPP
+#define ZIG_STACK_REPORT_HPP
+
+#include "all_types.hpp"
+#include <stdio.h>
+
+void zig_print_stack_report(CodeGen *g, FILE *f);
+
+#endif
std/event/io.zig
@@ -1,76 +0,0 @@
-const std = @import("../std.zig");
-const builtin = @import("builtin");
-const assert = std.debug.assert;
-const mem = std.mem;
-
-pub fn InStream(comptime ReadError: type) type {
-    return struct {
-        const Self = @This();
-        pub const Error = ReadError;
-
-        /// Return the number of bytes read. It may be less than buffer.len.
-        /// If the number of bytes read is 0, it means end of stream.
-        /// End of stream is not an error condition.
-        readFn: async fn (self: *Self, buffer: []u8) Error!usize,
-
-        /// Return the number of bytes read. It may be less than buffer.len.
-        /// If the number of bytes read is 0, it means end of stream.
-        /// End of stream is not an error condition.
-        pub async fn read(self: *Self, buffer: []u8) !usize {
-            return self.readFn(self, buffer);
-        }
-
-        /// Return the number of bytes read. If it is less than buffer.len
-        /// it means end of stream.
-        pub async fn readFull(self: *Self, buffer: []u8) !usize {
-            var index: usize = 0;
-            while (index != buf.len) {
-                const amt_read = try self.read(buf[index..]);
-                if (amt_read == 0) return index;
-                index += amt_read;
-            }
-            return index;
-        }
-
-        /// Same as `readFull` but end of stream returns `error.EndOfStream`.
-        pub async fn readNoEof(self: *Self, buf: []u8) !void {
-            const amt_read = try self.readFull(buf[index..]);
-            if (amt_read < buf.len) return error.EndOfStream;
-        }
-
-        pub async fn readIntLittle(self: *Self, comptime T: type) !T {
-            var bytes: [@sizeOf(T)]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readIntLittle(T, &bytes);
-        }
-
-        pub async fn readIntBe(self: *Self, comptime T: type) !T {
-            var bytes: [@sizeOf(T)]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readIntBig(T, &bytes);
-        }
-
-        pub async fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T {
-            var bytes: [@sizeOf(T)]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readInt(T, &bytes, endian);
-        }
-
-        pub async fn readStruct(self: *Self, comptime T: type) !T {
-            // Only extern and packed structs have defined in-memory layout.
-            comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto);
-            var res: [1]T = undefined;
-            try self.readNoEof(@sliceToBytes(res[0..]));
-            return res[0];
-        }
-    };
-}
-
-pub fn OutStream(comptime WriteError: type) type {
-    return struct {
-        const Self = @This();
-        pub const Error = WriteError;
-
-        writeFn: async fn (self: *Self, buffer: []u8) Error!void,
-    };
-}
std/event/loop.zig
@@ -86,18 +86,10 @@ pub const Loop = struct {
         };
     };
 
-    pub const IoMode = enum {
-        blocking,
-        evented,
-        mixed,
-    };
-    pub const io_mode: IoMode = if (@hasDecl(root, "io_mode")) root.io_mode else IoMode.blocking;
     var global_instance_state: Loop = undefined;
-    threadlocal var per_thread_instance: ?*Loop = null;
-    const default_instance: ?*Loop = switch (io_mode) {
+    const default_instance: ?*Loop = switch (std.io.mode) {
         .blocking => null,
         .evented => &global_instance_state,
-        .mixed => per_thread_instance,
     };
     pub const instance: ?*Loop = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance;
 
@@ -470,6 +462,10 @@ pub const Loop = struct {
         }
     }
 
+    pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) !void {
+        return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
+    }
+
     pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent {
         var resume_node = ResumeNode.Basic{
             .base = ResumeNode{
std/io/in_stream.zig
@@ -0,0 +1,200 @@
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const root = @import("root");
+const math = std.math;
+const assert = std.debug.assert;
+const mem = std.mem;
+const Buffer = std.Buffer;
+
+pub const default_stack_size = 4 * 1024 * 1024;
+pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_InStream"))
+    root.stack_size_std_io_InStream
+else
+    default_stack_size;
+pub const stack_align = 16;
+
+pub fn InStream(comptime ReadError: type) type {
+    return struct {
+        const Self = @This();
+        pub const Error = ReadError;
+        pub const ReadFn = if (std.io.is_async)
+            async fn (self: *Self, buffer: []u8) Error!usize
+        else
+            fn (self: *Self, buffer: []u8) Error!usize;
+
+        /// Returns the number of bytes read. It may be less than buffer.len.
+        /// If the number of bytes read is 0, it means end of stream.
+        /// End of stream is not an error condition.
+        readFn: ReadFn,
+
+        /// Returns the number of bytes read. It may be less than buffer.len.
+        /// If the number of bytes read is 0, it means end of stream.
+        /// End of stream is not an error condition.
+        pub fn read(self: *Self, buffer: []u8) Error!usize {
+            if (std.io.is_async) {
+                var stack_frame: [stack_size]u8 align(stack_align) = undefined;
+                // TODO https://github.com/ziglang/zig/issues/3068
+                var result: Error!usize = undefined;
+                return await @asyncCall(&stack_frame, &result, self.readFn, self, buffer);
+            } else {
+                return self.readFn(self, buffer);
+            }
+        }
+
+        /// 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 readFull(self: *Self, buffer: []u8) Error!usize {
+            var index: usize = 0;
+            while (index != buffer.len) {
+                const amt = try self.read(buffer[index..]);
+                if (amt == 0) return index;
+                index += amt;
+            }
+            return index;
+        }
+
+        /// Returns the number of bytes read. If the number read would be smaller than buf.len,
+        /// error.EndOfStream is returned instead.
+        pub fn readNoEof(self: *Self, buf: []u8) !void {
+            const amt_read = try self.readFull(buf);
+            if (amt_read < buf.len) return error.EndOfStream;
+        }
+
+        /// Replaces `buffer` contents by reading from the stream until it is finished.
+        /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and
+        /// the contents read from the stream are lost.
+        pub fn readAllBuffer(self: *Self, buffer: *Buffer, max_size: usize) !void {
+            try buffer.resize(0);
+
+            var actual_buf_len: usize = 0;
+            while (true) {
+                const dest_slice = buffer.toSlice()[actual_buf_len..];
+                const bytes_read = try self.readFull(dest_slice);
+                actual_buf_len += bytes_read;
+
+                if (bytes_read != dest_slice.len) {
+                    buffer.shrink(actual_buf_len);
+                    return;
+                }
+
+                const new_buf_size = math.min(max_size, actual_buf_len + mem.page_size);
+                if (new_buf_size == actual_buf_len) return error.StreamTooLong;
+                try buffer.resize(new_buf_size);
+            }
+        }
+
+        /// 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: *Self, allocator: *mem.Allocator, max_size: usize) ![]u8 {
+            var buf = Buffer.initNull(allocator);
+            defer buf.deinit();
+
+            try self.readAllBuffer(&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: *Self, buffer: *Buffer, delimiter: u8, max_size: usize) !void {
+            try buffer.resize(0);
+
+            while (true) {
+                var byte: u8 = try self.readByte();
+
+                if (byte == delimiter) {
+                    return;
+                }
+
+                if (buffer.len() == max_size) {
+                    return error.StreamTooLong;
+                }
+
+                try buffer.appendByte(byte);
+            }
+        }
+
+        /// 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: *Self, allocator: *mem.Allocator, delimiter: u8, max_size: usize) ![]u8 {
+            var buf = Buffer.initNull(allocator);
+            defer buf.deinit();
+
+            try self.readUntilDelimiterBuffer(&buf, delimiter, max_size);
+            return buf.toOwnedSlice();
+        }
+
+        /// Reads 1 byte from the stream or returns `error.EndOfStream`.
+        pub fn readByte(self: *Self) !u8 {
+            var result: [1]u8 = undefined;
+            try self.readNoEof(result[0..]);
+            return result[0];
+        }
+
+        /// Same as `readByte` except the returned byte is signed.
+        pub fn readByteSigned(self: *Self) !i8 {
+            return @bitCast(i8, try self.readByte());
+        }
+
+        /// Reads a native-endian integer
+        pub fn readIntNative(self: *Self, comptime T: type) !T {
+            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+            try self.readNoEof(bytes[0..]);
+            return mem.readIntNative(T, &bytes);
+        }
+
+        /// Reads a foreign-endian integer
+        pub fn readIntForeign(self: *Self, comptime T: type) !T {
+            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+            try self.readNoEof(bytes[0..]);
+            return mem.readIntForeign(T, &bytes);
+        }
+
+        pub fn readIntLittle(self: *Self, comptime T: type) !T {
+            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+            try self.readNoEof(bytes[0..]);
+            return mem.readIntLittle(T, &bytes);
+        }
+
+        pub fn readIntBig(self: *Self, comptime T: type) !T {
+            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+            try self.readNoEof(bytes[0..]);
+            return mem.readIntBig(T, &bytes);
+        }
+
+        pub fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T {
+            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+            try self.readNoEof(bytes[0..]);
+            return mem.readInt(T, &bytes, endian);
+        }
+
+        pub fn readVarInt(self: *Self, comptime ReturnType: type, endian: builtin.Endian, size: usize) !ReturnType {
+            assert(size <= @sizeOf(ReturnType));
+            var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined;
+            const bytes = bytes_buf[0..size];
+            try self.readNoEof(bytes);
+            return mem.readVarInt(ReturnType, bytes, endian);
+        }
+
+        pub fn skipBytes(self: *Self, num_bytes: u64) !void {
+            var i: u64 = 0;
+            while (i < num_bytes) : (i += 1) {
+                _ = try self.readByte();
+            }
+        }
+
+        pub fn readStruct(self: *Self, comptime T: type) !T {
+            // Only extern and packed structs have defined in-memory layout.
+            comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto);
+            var res: [1]T = undefined;
+            try self.readNoEof(@sliceToBytes(res[0..]));
+            return res[0];
+        }
+    };
+}
std/c.zig
@@ -68,6 +68,7 @@ pub extern "c" fn open(path: [*]const u8, oflag: c_uint, ...) c_int;
 pub extern "c" fn openat(fd: c_int, path: [*]const u8, oflag: c_uint, ...) c_int;
 pub extern "c" fn raise(sig: c_int) c_int;
 pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize;
+pub extern "c" fn readv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint) isize;
 pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: u64) isize;
 pub extern "c" fn preadv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint, offset: usize) isize;
 pub extern "c" fn writev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint) isize;
std/debug.zig
@@ -330,14 +330,16 @@ pub fn writeCurrentStackTraceWindows(
     }
 }
 
+/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,
+/// make this `noasync fn` and remove the individual noasync calls.
 pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void {
     if (windows.is_the_target) {
-        return printSourceAtAddressWindows(debug_info, out_stream, address, tty_color);
+        return noasync printSourceAtAddressWindows(debug_info, out_stream, address, tty_color);
     }
     if (os.darwin.is_the_target) {
-        return printSourceAtAddressMacOs(debug_info, out_stream, address, tty_color);
+        return noasync printSourceAtAddressMacOs(debug_info, out_stream, address, tty_color);
     }
-    return printSourceAtAddressPosix(debug_info, out_stream, address, tty_color);
+    return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_color);
 }
 
 fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_address: usize, tty_color: bool) !void {
@@ -793,7 +795,7 @@ fn printLineInfo(
                 try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
             }
         } else |err| switch (err) {
-            error.EndOfFile, error.FileNotFound  => {},
+            error.EndOfFile, error.FileNotFound => {},
             else => return err,
         }
     } else {
@@ -816,16 +818,18 @@ pub const OpenSelfDebugInfoError = error{
     UnsupportedOperatingSystem,
 };
 
+/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,
+/// make this `noasync fn` and remove the individual noasync calls.
 pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo {
     if (builtin.strip_debug_info)
         return error.MissingDebugInfo;
     if (windows.is_the_target) {
-        return openSelfDebugInfoWindows(allocator);
+        return noasync openSelfDebugInfoWindows(allocator);
     }
     if (os.darwin.is_the_target) {
-        return openSelfDebugInfoMacOs(allocator);
+        return noasync openSelfDebugInfoMacOs(allocator);
     }
-    return openSelfDebugInfoPosix(allocator);
+    return noasync openSelfDebugInfoPosix(allocator);
 }
 
 fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
@@ -1508,15 +1512,25 @@ fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !
 }
 
 fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue {
+    // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here.
+    // `noasync` should be removed from all the function calls once it is fixed.
     return FormValue{
         .Const = Constant{
             .signed = signed,
             .payload = switch (size) {
-                1 => try in_stream.readIntLittle(u8),
-                2 => try in_stream.readIntLittle(u16),
-                4 => try in_stream.readIntLittle(u32),
-                8 => try in_stream.readIntLittle(u64),
-                -1 => if (signed) @bitCast(u64, try leb.readILEB128(i64, in_stream)) else try leb.readULEB128(u64, in_stream),
+                1 => try noasync in_stream.readIntLittle(u8),
+                2 => try noasync in_stream.readIntLittle(u16),
+                4 => try noasync in_stream.readIntLittle(u32),
+                8 => try noasync in_stream.readIntLittle(u64),
+                -1 => blk: {
+                    if (signed) {
+                        const x = try noasync leb.readILEB128(i64, in_stream);
+                        break :blk @bitCast(u64, x);
+                    } else {
+                        const x = try noasync leb.readULEB128(u64, in_stream);
+                        break :blk x;
+                    }
+                },
                 else => @compileError("Invalid size"),
             },
         },
@@ -1584,7 +1598,10 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64
         DW.FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
         DW.FORM_indirect => {
             const child_form_id = try leb.readULEB128(u64, in_stream);
-            return parseFormValue(allocator, in_stream, child_form_id, is_64);
+            const F = @typeOf(async parseFormValue(allocator, in_stream, child_form_id, is_64));
+            var frame = try allocator.create(F);
+            defer allocator.destroy(frame);
+            return await @asyncCall(frame, {}, parseFormValue, allocator, in_stream, child_form_id, is_64);
         },
         else => error.InvalidDebugInfo,
     };
std/event.zig
@@ -6,7 +6,6 @@ pub const Locked = @import("event/locked.zig").Locked;
 pub const RwLock = @import("event/rwlock.zig").RwLock;
 pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
 pub const Loop = @import("event/loop.zig").Loop;
-pub const io = @import("event/io.zig");
 pub const fs = @import("event/fs.zig");
 pub const net = @import("event/net.zig");
 
@@ -15,7 +14,6 @@ test "import event tests" {
     _ = @import("event/fs.zig");
     _ = @import("event/future.zig");
     _ = @import("event/group.zig");
-    _ = @import("event/io.zig");
     _ = @import("event/lock.zig");
     _ = @import("event/locked.zig");
     _ = @import("event/rwlock.zig");
std/io.zig
@@ -1,5 +1,6 @@
 const std = @import("std.zig");
 const builtin = @import("builtin");
+const root = @import("root");
 const c = std.c;
 
 const math = std.math;
@@ -15,6 +16,18 @@ const fmt = std.fmt;
 const File = std.fs.File;
 const testing = std.testing;
 
+pub const Mode = enum {
+    blocking,
+    evented,
+};
+pub const mode: Mode = if (@hasDecl(root, "io_mode"))
+    root.io_mode
+else if (@hasDecl(root, "event_loop"))
+    Mode.evented
+else
+    Mode.blocking;
+pub const is_async = mode != .blocking;
+
 pub const GetStdIoError = os.windows.GetStdHandleError;
 
 pub fn getStdOut() GetStdIoError!File {
@@ -44,180 +57,7 @@ pub fn getStdIn() GetStdIoError!File {
 pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
 pub const SliceSeekableInStream = @import("io/seekable_stream.zig").SliceSeekableInStream;
 pub const COutStream = @import("io/c_out_stream.zig").COutStream;
-
-pub fn InStream(comptime ReadError: type) type {
-    return struct {
-        const Self = @This();
-        pub const Error = ReadError;
-
-        /// 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: *Self, buffer: []u8) Error!usize,
-
-        /// Replaces `buffer` contents by reading from the stream until it is finished.
-        /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and
-        /// the contents read from the stream are lost.
-        pub fn readAllBuffer(self: *Self, buffer: *Buffer, max_size: usize) !void {
-            try buffer.resize(0);
-
-            var actual_buf_len: usize = 0;
-            while (true) {
-                const dest_slice = buffer.toSlice()[actual_buf_len..];
-                const bytes_read = try self.readFull(dest_slice);
-                actual_buf_len += bytes_read;
-
-                if (bytes_read != dest_slice.len) {
-                    buffer.shrink(actual_buf_len);
-                    return;
-                }
-
-                const new_buf_size = math.min(max_size, actual_buf_len + mem.page_size);
-                if (new_buf_size == actual_buf_len) return error.StreamTooLong;
-                try buffer.resize(new_buf_size);
-            }
-        }
-
-        /// 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: *Self, allocator: *mem.Allocator, max_size: usize) ![]u8 {
-            var buf = Buffer.initNull(allocator);
-            defer buf.deinit();
-
-            try self.readAllBuffer(&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: *Self, buffer: *Buffer, delimiter: u8, max_size: usize) !void {
-            try buffer.resize(0);
-
-            while (true) {
-                var byte: u8 = try self.readByte();
-
-                if (byte == delimiter) {
-                    return;
-                }
-
-                if (buffer.len() == max_size) {
-                    return error.StreamTooLong;
-                }
-
-                try buffer.appendByte(byte);
-            }
-        }
-
-        /// 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: *Self, allocator: *mem.Allocator, delimiter: u8, max_size: usize) ![]u8 {
-            var buf = Buffer.initNull(allocator);
-            defer buf.deinit();
-
-            try self.readUntilDelimiterBuffer(&buf, delimiter, max_size);
-            return buf.toOwnedSlice();
-        }
-
-        /// Returns the number of bytes read. It may be less than buffer.len.
-        /// If the number of bytes read is 0, it means end of stream.
-        /// End of stream is not an error condition.
-        pub fn read(self: *Self, buffer: []u8) Error!usize {
-            return self.readFn(self, buffer);
-        }
-
-        /// 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 readFull(self: *Self, buffer: []u8) Error!usize {
-            var index: usize = 0;
-            while (index != buffer.len) {
-                const amt = try self.read(buffer[index..]);
-                if (amt == 0) return index;
-                index += amt;
-            }
-            return index;
-        }
-
-        /// Same as `readFull` but end of stream returns `error.EndOfStream`.
-        pub fn readNoEof(self: *Self, buf: []u8) !void {
-            const amt_read = try self.readFull(buf);
-            if (amt_read < buf.len) return error.EndOfStream;
-        }
-
-        /// Reads 1 byte from the stream or returns `error.EndOfStream`.
-        pub fn readByte(self: *Self) !u8 {
-            var result: [1]u8 = undefined;
-            try self.readNoEof(result[0..]);
-            return result[0];
-        }
-
-        /// Same as `readByte` except the returned byte is signed.
-        pub fn readByteSigned(self: *Self) !i8 {
-            return @bitCast(i8, try self.readByte());
-        }
-
-        /// Reads a native-endian integer
-        pub fn readIntNative(self: *Self, comptime T: type) !T {
-            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readIntNative(T, &bytes);
-        }
-
-        /// Reads a foreign-endian integer
-        pub fn readIntForeign(self: *Self, comptime T: type) !T {
-            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readIntForeign(T, &bytes);
-        }
-
-        pub fn readIntLittle(self: *Self, comptime T: type) !T {
-            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readIntLittle(T, &bytes);
-        }
-
-        pub fn readIntBig(self: *Self, comptime T: type) !T {
-            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readIntBig(T, &bytes);
-        }
-
-        pub fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T {
-            var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
-            try self.readNoEof(bytes[0..]);
-            return mem.readInt(T, &bytes, endian);
-        }
-
-        pub fn readVarInt(self: *Self, comptime ReturnType: type, endian: builtin.Endian, size: usize) !ReturnType {
-            assert(size <= @sizeOf(ReturnType));
-            var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined;
-            const bytes = bytes_buf[0..size];
-            try self.readNoEof(bytes);
-            return mem.readVarInt(ReturnType, bytes, endian);
-        }
-
-        pub fn skipBytes(self: *Self, num_bytes: u64) !void {
-            var i: u64 = 0;
-            while (i < num_bytes) : (i += 1) {
-                _ = try self.readByte();
-            }
-        }
-
-        pub fn readStruct(self: *Self, comptime T: type) !T {
-            // Only extern and packed structs have defined in-memory layout.
-            comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto);
-            var res: [1]T = undefined;
-            try self.readNoEof(@sliceToBytes(res[0..]));
-            return res[0];
-        }
-    };
-}
+pub const InStream = @import("io/in_stream.zig").InStream;
 
 pub fn OutStream(comptime WriteError: type) type {
     return struct {
std/os.zig
@@ -254,13 +254,18 @@ pub const ReadError = error{
     IsDir,
     OperationAborted,
     BrokenPipe,
+
+    /// This error occurs when no global event loop is configured,
+    /// and reading from the file descriptor would block.
+    WouldBlock,
+
     Unexpected,
 };
 
 /// Returns the number of bytes that were read, which can be less than
 /// buf.len. If 0 bytes were read, that means EOF.
-/// This function is for blocking file descriptors only. For non-blocking, see
-/// `readAsync`.
+/// If the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
 pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
     if (windows.is_the_target) {
         return windows.ReadFile(fd, buf);
@@ -279,28 +284,19 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
         }
     }
 
-    // Linux can return EINVAL when read amount is > 0x7ffff000
-    // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274
-    // TODO audit this. Shawn Landden says that this is not actually true.
-    // if this logic should stay, move it to std.os.linux
-    const max_buf_len = 0x7ffff000;
-
-    var index: usize = 0;
-    while (index < buf.len) {
-        const want_to_read = math.min(buf.len - index, usize(max_buf_len));
-        const rc = system.read(fd, buf.ptr + index, want_to_read);
+    while (true) {
+        const rc = system.read(fd, buf.ptr, buf.len);
         switch (errno(rc)) {
-            0 => {
-                const amt_read = @intCast(usize, rc);
-                index += amt_read;
-                if (amt_read == want_to_read) continue;
-                // Read returned less than buf.len.
-                return index;
-            },
+            0 => return @intCast(usize, rc),
             EINTR => continue,
             EINVAL => unreachable,
             EFAULT => unreachable,
-            EAGAIN => unreachable, // This function is for blocking reads.
+            EAGAIN => if (std.event.Loop.instance) |loop| {
+                loop.waitUntilFdReadable(fd) catch return error.WouldBlock;
+                continue;
+            } else {
+                return error.WouldBlock;
+            },
             EBADF => unreachable, // Always a race condition.
             EIO => return error.InputOutput,
             EISDIR => return error.IsDir,
@@ -313,8 +309,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
 }
 
 /// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
-/// This function is for blocking file descriptors only. For non-blocking, see
-/// `preadvAsync`.
+/// This function is for blocking file descriptors only.
 pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
     if (darwin.is_the_target) {
         // Darwin does not have preadv but it does have pread.
@@ -386,8 +381,7 @@ pub const WriteError = error{
 };
 
 /// Write to a file descriptor. Keeps trying if it gets interrupted.
-/// This function is for blocking file descriptors only. For non-blocking, see
-/// `writeAsync`.
+/// This function is for blocking file descriptors only.
 pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
     if (windows.is_the_target) {
         return windows.WriteFile(fd, bytes);
CMakeLists.txt
@@ -449,6 +449,7 @@ set(ZIG_SOURCES
     "${CMAKE_SOURCE_DIR}/src/os.cpp"
     "${CMAKE_SOURCE_DIR}/src/parser.cpp"
     "${CMAKE_SOURCE_DIR}/src/range_set.cpp"
+    "${CMAKE_SOURCE_DIR}/src/stack_report.cpp"
     "${CMAKE_SOURCE_DIR}/src/target.cpp"
     "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp"
     "${CMAKE_SOURCE_DIR}/src/translate_c.cpp"