Commit aa3b41247f

Andrew Kelley <superjoe30@gmail.com>
2018-07-18 06:34:42
self-hosted: linking against libc
also introduce `zig libc` command to display paths `zig libc file.txt` will parse equivalent text and use that for libc paths.
1 parent 3e4a3fa
src-self-hosted/codegen.zig
@@ -15,7 +15,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)
     defer fn_val.base.deref(comp);
     defer code.destroy(comp.gpa());
 
-    var output_path = try await (async comp.createRandomOutputPath(comp.target.oFileExt()) catch unreachable);
+    var output_path = try await (async comp.createRandomOutputPath(comp.target.objFileExt()) catch unreachable);
     errdefer output_path.deinit();
 
     const llvm_handle = try comp.event_loop_local.getAnyLlvmContext();
src-self-hosted/compilation.zig
@@ -120,7 +120,6 @@ pub const Compilation = struct {
 
     linker_script: ?[]const u8,
     cache_dir: []const u8,
-    dynamic_linker: ?[]const u8,
     out_h_path: ?[]const u8,
 
     is_test: bool,
@@ -201,6 +200,13 @@ pub const Compilation = struct {
     root_package: *Package,
     std_package: *Package,
 
+    override_libc: ?*LibCInstallation,
+
+    /// need to wait on this group before deinitializing
+    deinit_group: event.Group(void),
+
+    destroy_handle: promise,
+
     const CompileErrList = std.ArrayList(*errmsg.Msg);
 
     // TODO handle some of these earlier and report them in a way other than error codes
@@ -246,6 +252,8 @@ pub const Compilation = struct {
         EnvironmentVariableNotFound,
         AppDataDirUnavailable,
         LinkFailed,
+        LibCRequiredButNotProvidedOrFound,
+        LibCMissingDynamicLinker,
     };
 
     pub const Event = union(enum) {
@@ -324,7 +332,6 @@ pub const Compilation = struct {
             .verbose_link = false,
 
             .linker_script = null,
-            .dynamic_linker = null,
             .out_h_path = null,
             .is_test = false,
             .each_lib_rpath = false,
@@ -351,6 +358,7 @@ pub const Compilation = struct {
             .link_out_file = null,
             .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)),
             .prelink_group = event.Group(BuildError!void).init(loop),
+            .deinit_group = event.Group(void).init(loop),
             .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)),
 
             .meta_type = undefined,
@@ -368,6 +376,9 @@ pub const Compilation = struct {
 
             .root_package = undefined,
             .std_package = undefined,
+
+            .override_libc = null,
+            .destroy_handle = undefined,
         });
         errdefer {
             comp.arena_allocator.deinit();
@@ -431,6 +442,9 @@ pub const Compilation = struct {
         }
 
         try comp.initTypes();
+        errdefer comp.derefTypes();
+
+        comp.destroy_handle = try async<loop.allocator> comp.internalDeinit();
 
         return comp;
     }
@@ -526,11 +540,7 @@ pub const Compilation = struct {
         errdefer comp.gpa().destroy(comp.noreturn_value);
     }
 
-    pub fn destroy(self: *Compilation) void {
-        if (self.tmp_dir.getOrNull()) |tmp_dir_result| if (tmp_dir_result.*) |tmp_dir| {
-            os.deleteTree(self.arena(), tmp_dir) catch {};
-        } else |_| {};
-
+    fn derefTypes(self: *Compilation) void {
         self.noreturn_value.base.deref(self);
         self.void_value.base.deref(self);
         self.false_value.base.deref(self);
@@ -538,6 +548,17 @@ pub const Compilation = struct {
         self.noreturn_type.base.base.deref(self);
         self.void_type.base.base.deref(self);
         self.meta_type.base.base.deref(self);
+    }
+
+    async fn internalDeinit(self: *Compilation) void {
+        suspend;
+        await (async self.deinit_group.wait() catch unreachable);
+        if (self.tmp_dir.getOrNull()) |tmp_dir_result| if (tmp_dir_result.*) |tmp_dir| {
+            // TODO evented I/O?
+            os.deleteTree(self.arena(), tmp_dir) catch {};
+        } else |_| {};
+
+        self.derefTypes();
 
         self.events.destroy();
 
@@ -549,6 +570,10 @@ pub const Compilation = struct {
         self.gpa().destroy(self);
     }
 
+    pub fn destroy(self: *Compilation) void {
+        resume self.destroy_handle;
+    }
+
     pub fn build(self: *Compilation) !void {
         if (self.llvm_argv.len != 0) {
             var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.arena(), [][]const []const u8{
@@ -680,7 +705,7 @@ pub const Compilation = struct {
         };
 
         if (!any_prelink_errors) {
-            try link(self);
+            try await (async link(self) catch unreachable);
         }
     }
 
@@ -765,8 +790,8 @@ pub const Compilation = struct {
             self.libc_link_lib = link_lib;
 
             // get a head start on looking for the native libc
-            if (self.target == Target.Native) {
-                try async<self.loop.allocator> self.startFindingNativeLibC();
+            if (self.target == Target.Native and self.override_libc == null) {
+                try self.deinit_group.call(startFindingNativeLibC, self);
             }
         }
         return link_lib;
@@ -774,11 +799,9 @@ pub const Compilation = struct {
 
     /// cancels itself so no need to await or cancel the promise.
     async fn startFindingNativeLibC(self: *Compilation) void {
+        await (async self.loop.yield() catch unreachable);
         // we don't care if it fails, we're just trying to kick off the future resolution
-        _ = (await (async self.loop.call(EventLoopLocal.getNativeLibC, self.event_loop_local) catch unreachable)) catch {};
-        suspend |p| {
-            cancel p;
-        }
+        _ = (await (async self.event_loop_local.getNativeLibC() catch unreachable)) catch return;
     }
 
     /// General Purpose Allocator. Must free when done.
src-self-hosted/libc_installation.zig
@@ -1,31 +1,18 @@
 const std = @import("std");
 const builtin = @import("builtin");
 const event = std.event;
+const Target = @import("target.zig").Target;
 
+/// See the render function implementation for documentation of the fields.
 pub const LibCInstallation = struct {
-    /// The directory that contains `stdlib.h`.
-    /// On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null`
     include_dir: []const u8,
-
-    /// The directory that contains `crt1.o`.
-    /// On Linux, can be found with `cc -print-file-name=crt1.o`.
-    /// Not needed when targeting MacOS.
     lib_dir: ?[]const u8,
-
-    /// The directory that contains `crtbegin.o`.
-    /// On Linux, can be found with `cc -print-file-name=crt1.o`.
-    /// Not needed when targeting MacOS or Windows.
     static_lib_dir: ?[]const u8,
-
-    /// The directory that contains `vcruntime.lib`.
-    /// Only needed when targeting Windows.
     msvc_lib_dir: ?[]const u8,
-
-    /// The directory that contains `kernel32.lib`.
-    /// Only needed when targeting Windows.
     kernel32_lib_dir: ?[]const u8,
+    dynamic_linker_path: ?[]const u8,
 
-    pub const Error = error{
+    pub const FindError = error{
         OutOfMemory,
         FileSystem,
         UnableToSpawnCCompiler,
@@ -36,16 +23,124 @@ pub const LibCInstallation = struct {
         LibCStdLibHeaderNotFound,
     };
 
+    pub fn parse(
+        self: *LibCInstallation,
+        allocator: *std.mem.Allocator,
+        libc_file: []const u8,
+        stderr: *std.io.OutStream(std.io.FileOutStream.Error),
+    ) !void {
+        self.initEmpty();
+
+        const keys = []const []const u8{
+            "include_dir",
+            "lib_dir",
+            "static_lib_dir",
+            "msvc_lib_dir",
+            "kernel32_lib_dir",
+            "dynamic_linker_path",
+        };
+        const FoundKey = struct {
+            found: bool,
+            allocated: ?[]u8,
+        };
+        var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** keys.len;
+        errdefer {
+            self.initEmpty();
+            for (found_keys) |found_key| {
+                if (found_key.allocated) |s| allocator.free(s);
+            }
+        }
+
+        const contents = try std.io.readFileAlloc(allocator, libc_file);
+        defer allocator.free(contents);
+
+        var it = std.mem.split(contents, "\n");
+        while (it.next()) |line| {
+            if (line.len == 0 or line[0] == '#') continue;
+            var line_it = std.mem.split(line, "=");
+            const name = line_it.next() orelse {
+                try stderr.print("missing equal sign after field name\n");
+                return error.ParseError;
+            };
+            const value = line_it.rest();
+            inline for (keys) |key, i| {
+                if (std.mem.eql(u8, name, key)) {
+                    found_keys[i].found = true;
+                    switch (@typeInfo(@typeOf(@field(self, key)))) {
+                        builtin.TypeId.Optional => {
+                            if (value.len == 0) {
+                                @field(self, key) = null;
+                            } else {
+                                found_keys[i].allocated = try std.mem.dupe(allocator, u8, value);
+                                @field(self, key) = found_keys[i].allocated;
+                            }
+                        },
+                        else => {
+                            if (value.len == 0) {
+                                try stderr.print("field cannot be empty: {}\n", key);
+                                return error.ParseError;
+                            }
+                            const dupe = try std.mem.dupe(allocator, u8, value);
+                            found_keys[i].allocated = dupe;
+                            @field(self, key) = dupe;
+                        },
+                    }
+                    break;
+                }
+            }
+        }
+        for (found_keys) |found_key, i| {
+            if (!found_key.found) {
+                try stderr.print("missing field: {}\n", keys[i]);
+                return error.ParseError;
+            }
+        }
+    }
+
+    pub fn render(self: *const LibCInstallation, out: *std.io.OutStream(std.io.FileOutStream.Error)) !void {
+        @setEvalBranchQuota(4000);
+        try out.print(
+            \\# The directory that contains `stdlib.h`.
+            \\# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null`
+            \\include_dir={}
+            \\
+            \\# The directory that contains `crt1.o`.
+            \\# On Linux, can be found with `cc -print-file-name=crt1.o`.
+            \\# Not needed when targeting MacOS.
+            \\lib_dir={}
+            \\
+            \\# The directory that contains `crtbegin.o`.
+            \\# On Linux, can be found with `cc -print-file-name=crt1.o`.
+            \\# Not needed when targeting MacOS or Windows.
+            \\static_lib_dir={}
+            \\
+            \\# The directory that contains `vcruntime.lib`.
+            \\# Only needed when targeting Windows.
+            \\msvc_lib_dir={}
+            \\
+            \\# The directory that contains `kernel32.lib`.
+            \\# Only needed when targeting Windows.
+            \\kernel32_lib_dir={}
+            \\
+            \\# The full path to the dynamic linker.
+            \\# Only needed when targeting Linux.
+            \\dynamic_linker_path={}
+            \\
+        ,
+            self.include_dir,
+            self.lib_dir orelse "",
+            self.static_lib_dir orelse "",
+            self.msvc_lib_dir orelse "",
+            self.kernel32_lib_dir orelse "",
+            self.dynamic_linker_path orelse Target(Target.Native).getDynamicLinkerPath(),
+        );
+    }
+
     /// Finds the default, native libc.
     pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void {
-        self.* = LibCInstallation{
-            .lib_dir = null,
-            .include_dir = ([*]const u8)(undefined)[0..0],
-            .static_lib_dir = null,
-            .msvc_lib_dir = null,
-            .kernel32_lib_dir = null,
-        };
-        var group = event.Group(Error!void).init(loop);
+        self.initEmpty();
+        var group = event.Group(FindError!void).init(loop);
+        errdefer group.cancelAll();
         switch (builtin.os) {
             builtin.Os.windows => {
                 try group.call(findNativeIncludeDirWindows, self, loop);
@@ -57,6 +152,7 @@ pub const LibCInstallation = struct {
                 try group.call(findNativeIncludeDirLinux, self, loop);
                 try group.call(findNativeLibDirLinux, self, loop);
                 try group.call(findNativeStaticLibDir, self, loop);
+                try group.call(findNativeDynamicLinker, self, loop);
             },
             builtin.Os.macosx => {
                 try group.call(findNativeIncludeDirMacOS, self, loop);
@@ -147,7 +243,7 @@ pub const LibCInstallation = struct {
         self.include_dir = try std.mem.dupe(loop.allocator, u8, "/usr/include");
     }
 
-    async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop) Error!void {
+    async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop) FindError!void {
         // TODO
         //ZigWindowsSDK *sdk = get_windows_sdk(g);
 
@@ -180,31 +276,83 @@ pub const LibCInstallation = struct {
         @panic("TODO");
     }
 
-    async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) Error!void {
-        self.lib_dir = try await (async ccPrintFileNameDir(loop, "crt1.o") catch unreachable);
+    async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) FindError!void {
+        self.lib_dir = try await (async ccPrintFileName(loop, "crt1.o", true) catch unreachable);
+    }
+
+    async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void {
+        self.static_lib_dir = try await (async ccPrintFileName(loop, "crtbegin.o", true) catch unreachable);
+    }
+
+    async fn findNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop) FindError!void {
+        var dyn_tests = []DynTest{
+            DynTest{
+                .name = "ld-linux-x86-64.so.2",
+                .result = null,
+            },
+            DynTest{
+                .name = "ld-musl-x86_64.so.1",
+                .result = null,
+            },
+        };
+        var group = event.Group(FindError!void).init(loop);
+        errdefer group.cancelAll();
+        for (dyn_tests) |*dyn_test| {
+            try group.call(testNativeDynamicLinker, self, loop, dyn_test);
+        }
+        try await (async group.wait() catch unreachable);
+        for (dyn_tests) |*dyn_test| {
+            if (dyn_test.result) |result| {
+                self.dynamic_linker_path = result;
+                return;
+            }
+        }
     }
 
-    async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) Error!void {
-        self.static_lib_dir = try await (async ccPrintFileNameDir(loop, "crtbegin.o") catch unreachable);
+    const DynTest = struct {
+        name: []const u8,
+        result: ?[]const u8,
+    };
+
+    async fn testNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop, dyn_test: *DynTest) FindError!void {
+        if (await (async ccPrintFileName(loop, dyn_test.name, false) catch unreachable)) |result| {
+            dyn_test.result = result;
+            return;
+        } else |err| switch (err) {
+            error.CCompilerCannotFindCRuntime => return,
+            else => return err,
+        }
     }
 
-    async fn findNativeMsvcLibDir(self: *LibCInstallation, loop: *event.Loop) Error!void {
+    async fn findNativeMsvcLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void {
         @panic("TODO");
     }
 
-    async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop) Error!void {
+    async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void {
         @panic("TODO");
     }
+
+    fn initEmpty(self: *LibCInstallation) void {
+        self.* = LibCInstallation{
+            .include_dir = ([*]const u8)(undefined)[0..0],
+            .lib_dir = null,
+            .static_lib_dir = null,
+            .msvc_lib_dir = null,
+            .kernel32_lib_dir = null,
+            .dynamic_linker_path = null,
+        };
+    }
 };
 
 /// caller owns returned memory
-async fn ccPrintFileNameDir(loop: *event.Loop, o_file: []const u8) ![]u8 {
+async fn ccPrintFileName(loop: *event.Loop, o_file: []const u8, want_dirname: bool) ![]u8 {
     const cc_exe = std.os.getEnvPosix("CC") orelse "cc";
     const arg1 = try std.fmt.allocPrint(loop.allocator, "-print-file-name={}", o_file);
     defer loop.allocator.free(arg1);
     const argv = []const []const u8{ cc_exe, arg1 };
 
-    // TODO evented I/O
+    // TODO This simulates evented I/O for the child process exec
+    await (async loop.yield() catch unreachable);
     const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024);
     const exec_result = if (std.debug.runtime_safety) blk: {
         break :blk errorable_result catch unreachable;
@@ -230,5 +378,9 @@ async fn ccPrintFileNameDir(loop: *event.Loop, o_file: []const u8) ![]u8 {
     const line = it.next() orelse return error.CCompilerCannotFindCRuntime;
     const dirname = std.os.path.dirname(line) orelse return error.CCompilerCannotFindCRuntime;
 
-    return std.mem.dupe(loop.allocator, u8, dirname);
+    if (want_dirname) {
+        return std.mem.dupe(loop.allocator, u8, dirname);
+    } else {
+        return std.mem.dupe(loop.allocator, u8, line);
+    }
 }
src-self-hosted/link.zig
@@ -3,6 +3,8 @@ const c = @import("c.zig");
 const builtin = @import("builtin");
 const ObjectFormat = builtin.ObjectFormat;
 const Compilation = @import("compilation.zig").Compilation;
+const Target = @import("target.zig").Target;
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 const Context = struct {
     comp: *Compilation,
@@ -12,9 +14,12 @@ const Context = struct {
 
     link_err: error{OutOfMemory}!void,
     link_msg: std.Buffer,
+
+    libc: *LibCInstallation,
+    out_file_path: std.Buffer,
 };
 
-pub fn link(comp: *Compilation) !void {
+pub async fn link(comp: *Compilation) !void {
     var ctx = Context{
         .comp = comp,
         .arena = std.heap.ArenaAllocator.init(comp.gpa()),
@@ -22,15 +27,45 @@ pub fn link(comp: *Compilation) !void {
         .link_in_crt = comp.haveLibC() and comp.kind == Compilation.Kind.Exe,
         .link_err = {},
         .link_msg = undefined,
+        .libc = undefined,
+        .out_file_path = undefined,
     };
     defer ctx.arena.deinit();
     ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator);
     ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator);
 
+    if (comp.link_out_file) |out_file| {
+        ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, out_file);
+    } else {
+        ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, comp.name.toSliceConst());
+        switch (comp.kind) {
+            Compilation.Kind.Exe => {
+                try ctx.out_file_path.append(comp.target.exeFileExt());
+            },
+            Compilation.Kind.Lib => {
+                try ctx.out_file_path.append(comp.target.libFileExt(comp.is_static));
+            },
+            Compilation.Kind.Obj => {
+                try ctx.out_file_path.append(comp.target.objFileExt());
+            },
+        }
+    }
+
     // even though we're calling LLD as a library it thinks the first
     // argument is its own exe name
     try ctx.args.append(c"lld");
 
+    if (comp.haveLibC()) {
+        ctx.libc = ctx.comp.override_libc orelse blk: {
+            switch (comp.target) {
+                Target.Native => {
+                    break :blk (await (async comp.event_loop_local.getNativeLibC() catch unreachable)) catch return error.LibCRequiredButNotProvidedOrFound;
+                },
+                else => return error.LibCRequiredButNotProvidedOrFound,
+            }
+        };
+    }
+
     try constructLinkerArgs(&ctx);
 
     if (comp.verbose_link) {
@@ -43,6 +78,7 @@ pub fn link(comp: *Compilation) !void {
 
     const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat());
     const args_slice = ctx.args.toSlice();
+    // Not evented I/O. LLD does its own multithreading internally.
     if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) {
         if (!ctx.link_msg.isNull()) {
             // TODO capture these messages and pass them through the system, reporting them through the
@@ -95,10 +131,7 @@ fn constructLinkerArgs(ctx: *Context) !void {
 }
 
 fn constructLinkerArgsElf(ctx: *Context) !void {
-    //if (g->libc_link_lib != nullptr) {
-    //    find_libc_lib_path(g);
-    //}
-
+    // TODO commented out code in this function
     //if (g->linker_script) {
     //    lj->args.append("-T");
     //    lj->args.append(g->linker_script);
@@ -107,7 +140,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void {
     //if (g->no_rosegment_workaround) {
     //    lj->args.append("--no-rosegment");
     //}
-    //lj->args.append("--gc-sections");
+    try ctx.args.append(c"--gc-sections");
 
     //lj->args.append("-m");
     //lj->args.append(getLDMOption(&g->zig_target));
@@ -115,14 +148,13 @@ fn constructLinkerArgsElf(ctx: *Context) !void {
     //bool is_lib = g->out_type == OutTypeLib;
     //bool shared = !g->is_static && is_lib;
     //Buf *soname = nullptr;
-    //if (g->is_static) {
-    //    if (g->zig_target.arch.arch == ZigLLVM_arm || g->zig_target.arch.arch == ZigLLVM_armeb ||
-    //        g->zig_target.arch.arch == ZigLLVM_thumb || g->zig_target.arch.arch == ZigLLVM_thumbeb)
-    //    {
-    //        lj->args.append("-Bstatic");
-    //    } else {
-    //        lj->args.append("-static");
-    //    }
+    if (ctx.comp.is_static) {
+        if (ctx.comp.target.isArmOrThumb()) {
+            try ctx.args.append(c"-Bstatic");
+        } else {
+            try ctx.args.append(c"-static");
+        }
+    }
     //} else if (shared) {
     //    lj->args.append("-shared");
 
@@ -133,23 +165,16 @@ fn constructLinkerArgsElf(ctx: *Context) !void {
     //    soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize "", buf_ptr(g->root_out_name), g->version_major);
     //}
 
-    //lj->args.append("-o");
-    //lj->args.append(buf_ptr(&lj->out_file));
+    try ctx.args.append(c"-o");
+    try ctx.args.append(ctx.out_file_path.ptr());
 
-    //if (lj->link_in_crt) {
-    //    const char *crt1o;
-    //    const char *crtbegino;
-    //    if (g->is_static) {
-    //        crt1o = "crt1.o";
-    //        crtbegino = "crtbeginT.o";
-    //    } else {
-    //        crt1o = "Scrt1.o";
-    //        crtbegino = "crtbegin.o";
-    //    }
-    //    lj->args.append(get_libc_file(g, crt1o));
-    //    lj->args.append(get_libc_file(g, "crti.o"));
-    //    lj->args.append(get_libc_static_file(g, crtbegino));
-    //}
+    if (ctx.link_in_crt) {
+        const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o";
+        const crtbegino = if (ctx.comp.is_static) "crtbeginT.o" else "crtbegin.o";
+        try addPathJoin(ctx, ctx.libc.lib_dir.?, crt1o);
+        try addPathJoin(ctx, ctx.libc.lib_dir.?, "crti.o");
+        try addPathJoin(ctx, ctx.libc.static_lib_dir.?, crtbegino);
+    }
 
     //for (size_t i = 0; i < g->rpath_list.length; i += 1) {
     //    Buf *rpath = g->rpath_list.at(i);
@@ -182,25 +207,23 @@ fn constructLinkerArgsElf(ctx: *Context) !void {
     //    lj->args.append(lib_dir);
     //}
 
-    //if (g->libc_link_lib != nullptr) {
-    //    lj->args.append("-L");
-    //    lj->args.append(buf_ptr(g->libc_lib_dir));
-
-    //    lj->args.append("-L");
-    //    lj->args.append(buf_ptr(g->libc_static_lib_dir));
-    //}
-
-    //if (!g->is_static) {
-    //    if (g->dynamic_linker != nullptr) {
-    //        assert(buf_len(g->dynamic_linker) != 0);
-    //        lj->args.append("-dynamic-linker");
-    //        lj->args.append(buf_ptr(g->dynamic_linker));
-    //    } else {
-    //        Buf *resolved_dynamic_linker = get_dynamic_linker_path(g);
-    //        lj->args.append("-dynamic-linker");
-    //        lj->args.append(buf_ptr(resolved_dynamic_linker));
-    //    }
-    //}
+    if (ctx.comp.haveLibC()) {
+        try ctx.args.append(c"-L");
+        try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.lib_dir.?)).ptr);
+
+        try ctx.args.append(c"-L");
+        try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr);
+
+        if (!ctx.comp.is_static) {
+            const dl = blk: {
+                if (ctx.libc.dynamic_linker_path) |dl| break :blk dl;
+                if (ctx.comp.target.getDynamicLinkerPath()) |dl| break :blk dl;
+                return error.LibCMissingDynamicLinker;
+            };
+            try ctx.args.append(c"-dynamic-linker");
+            try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr);
+        }
+    }
 
     //if (shared) {
     //    lj->args.append("-soname");
@@ -241,45 +264,51 @@ fn constructLinkerArgsElf(ctx: *Context) !void {
     //    lj->args.append(buf_ptr(arg));
     //}
 
-    //// libc dep
-    //if (g->libc_link_lib != nullptr) {
-    //    if (g->is_static) {
-    //        lj->args.append("--start-group");
-    //        lj->args.append("-lgcc");
-    //        lj->args.append("-lgcc_eh");
-    //        lj->args.append("-lc");
-    //        lj->args.append("-lm");
-    //        lj->args.append("--end-group");
-    //    } else {
-    //        lj->args.append("-lgcc");
-    //        lj->args.append("--as-needed");
-    //        lj->args.append("-lgcc_s");
-    //        lj->args.append("--no-as-needed");
-    //        lj->args.append("-lc");
-    //        lj->args.append("-lm");
-    //        lj->args.append("-lgcc");
-    //        lj->args.append("--as-needed");
-    //        lj->args.append("-lgcc_s");
-    //        lj->args.append("--no-as-needed");
-    //    }
-    //}
+    // libc dep
+    if (ctx.comp.haveLibC()) {
+        if (ctx.comp.is_static) {
+            try ctx.args.append(c"--start-group");
+            try ctx.args.append(c"-lgcc");
+            try ctx.args.append(c"-lgcc_eh");
+            try ctx.args.append(c"-lc");
+            try ctx.args.append(c"-lm");
+            try ctx.args.append(c"--end-group");
+        } else {
+            try ctx.args.append(c"-lgcc");
+            try ctx.args.append(c"--as-needed");
+            try ctx.args.append(c"-lgcc_s");
+            try ctx.args.append(c"--no-as-needed");
+            try ctx.args.append(c"-lc");
+            try ctx.args.append(c"-lm");
+            try ctx.args.append(c"-lgcc");
+            try ctx.args.append(c"--as-needed");
+            try ctx.args.append(c"-lgcc_s");
+            try ctx.args.append(c"--no-as-needed");
+        }
+    }
 
-    //// crt end
-    //if (lj->link_in_crt) {
-    //    lj->args.append(get_libc_static_file(g, "crtend.o"));
-    //    lj->args.append(get_libc_file(g, "crtn.o"));
-    //}
+    // crt end
+    if (ctx.link_in_crt) {
+        try addPathJoin(ctx, ctx.libc.static_lib_dir.?, "crtend.o");
+        try addPathJoin(ctx, ctx.libc.lib_dir.?, "crtn.o");
+    }
 
-    //if (!g->is_native_target) {
-    //    lj->args.append("--allow-shlib-undefined");
-    //}
+    if (ctx.comp.target != Target.Native) {
+        try ctx.args.append(c"--allow-shlib-undefined");
+    }
 
-    //if (g->zig_target.os == OsZen) {
-    //    lj->args.append("-e");
-    //    lj->args.append("_start");
+    if (ctx.comp.target.getOs() == builtin.Os.zen) {
+        try ctx.args.append(c"-e");
+        try ctx.args.append(c"_start");
 
-    //    lj->args.append("--image-base=0x10000000");
-    //}
+        try ctx.args.append(c"--image-base=0x10000000");
+    }
+}
+
+fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void {
+    const full_path = try std.os.path.join(&ctx.arena.allocator, dirname, basename);
+    const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path);
+    try ctx.args.append(full_path_with_null.ptr);
 }
 
 fn constructLinkerArgsCoff(ctx: *Context) void {
src-self-hosted/main.zig
@@ -18,6 +18,7 @@ const EventLoopLocal = @import("compilation.zig").EventLoopLocal;
 const Compilation = @import("compilation.zig").Compilation;
 const Target = @import("target.zig").Target;
 const errmsg = @import("errmsg.zig");
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 var stderr_file: os.File = undefined;
 var stderr: *io.OutStream(io.FileOutStream.Error) = undefined;
@@ -28,14 +29,14 @@ const usage =
     \\
     \\Commands:
     \\
-    \\  build-exe   [source]         Create executable from source or object files
-    \\  build-lib   [source]         Create library from source or object files
-    \\  build-obj   [source]         Create object from source or assembly
-    \\  find-libc                    Show native libc installation paths
-    \\  fmt         [source]         Parse file and render in canonical zig format
-    \\  targets                      List available compilation targets
-    \\  version                      Print version number and exit
-    \\  zen                          Print zen of zig and exit
+    \\  build-exe  [source]      Create executable from source or object files
+    \\  build-lib  [source]      Create library from source or object files
+    \\  build-obj  [source]      Create object from source or assembly
+    \\  fmt        [source]      Parse file and render in canonical zig format
+    \\  libc       [paths_file]  Display native libc paths file or validate one
+    \\  targets                  List available compilation targets
+    \\  version                  Print version number and exit
+    \\  zen                      Print zen of zig and exit
     \\
     \\
 ;
@@ -82,14 +83,14 @@ pub fn main() !void {
             .name = "build-obj",
             .exec = cmdBuildObj,
         },
-        Command{
-            .name = "find-libc",
-            .exec = cmdFindLibc,
-        },
         Command{
             .name = "fmt",
             .exec = cmdFmt,
         },
+        Command{
+            .name = "libc",
+            .exec = cmdLibC,
+        },
         Command{
             .name = "targets",
             .exec = cmdTargets,
@@ -135,6 +136,7 @@ const usage_build_generic =
     \\  --color [auto|off|on]        Enable or disable colored error messages
     \\
     \\Compile Options:
+    \\  --libc [file]                Provide a file which specifies libc paths
     \\  --assembly [source]          Add assembly file to build
     \\  --cache-dir [path]           Override the cache directory
     \\  --emit [filetype]            Emit a specific file format as compilation output
@@ -167,7 +169,6 @@ const usage_build_generic =
     \\
     \\Link Options:
     \\  --ar-path [path]             Set the path to ar
-    \\  --dynamic-linker [path]      Set the path to ld.so
     \\  --each-lib-rpath             Add rpath for each used dynamic library
     \\  --library [lib]              Link against lib
     \\  --forbid-library [lib]       Make it an error to link against lib
@@ -210,6 +211,7 @@ const args_build_generic = []Flag{
         "llvm-ir",
     }),
     Flag.Bool("--enable-timing-info"),
+    Flag.Arg1("--libc"),
     Flag.Arg1("--name"),
     Flag.Arg1("--output"),
     Flag.Arg1("--output-h"),
@@ -233,7 +235,6 @@ const args_build_generic = []Flag{
     Flag.Arg1("-mllvm"),
 
     Flag.Arg1("--ar-path"),
-    Flag.Arg1("--dynamic-linker"),
     Flag.Bool("--each-lib-rpath"),
     Flag.ArgMergeN("--library", 1),
     Flag.ArgMergeN("--forbid-library", 1),
@@ -382,6 +383,8 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
     const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch os.exit(1);
     defer allocator.free(zig_lib_dir);
 
+    var override_libc: LibCInstallation = undefined;
+
     var loop: event.Loop = undefined;
     try loop.initMultiThreaded(allocator);
     defer loop.deinit();
@@ -402,6 +405,15 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
     );
     defer comp.destroy();
 
+    if (flags.single("libc")) |libc_path| {
+        parseLibcPaths(loop.allocator, &override_libc, libc_path);
+        comp.override_libc = &override_libc;
+    }
+
+    for (flags.many("library")) |lib| {
+        _ = try comp.addLinkLib(lib, true);
+    }
+
     comp.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") orelse "0", 10);
     comp.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") orelse "0", 10);
     comp.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") orelse "0", 10);
@@ -425,10 +437,6 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
 
     comp.strip = flags.present("strip");
 
-    if (flags.single("dynamic-linker")) |dynamic_linker| {
-        comp.dynamic_linker = dynamic_linker;
-    }
-
     comp.verbose_tokenize = flags.present("verbose-tokenize");
     comp.verbose_ast_tree = flags.present("verbose-ast-tree");
     comp.verbose_ast_fmt = flags.present("verbose-ast-fmt");
@@ -479,7 +487,6 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void {
 
     switch (build_event) {
         Compilation.Event.Ok => {
-            std.debug.warn("Build succeeded\n");
             return;
         },
         Compilation.Event.Error => |err| {
@@ -559,7 +566,32 @@ const Fmt = struct {
     }
 };
 
-fn cmdFindLibc(allocator: *Allocator, args: []const []const u8) !void {
+fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void {
+    libc.parse(allocator, libc_paths_file, stderr) catch |err| {
+        stderr.print(
+            "Unable to parse libc path file '{}': {}.\n" ++
+                "Try running `zig libc` to see an example for the native target.\n",
+            libc_paths_file,
+            @errorName(err),
+        ) catch os.exit(1);
+        os.exit(1);
+    };
+}
+
+fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void {
+    switch (args.len) {
+        0 => {},
+        1 => {
+            var libc_installation: LibCInstallation = undefined;
+            parseLibcPaths(allocator, &libc_installation, args[0]);
+            return;
+        },
+        else => {
+            try stderr.print("unexpected extra parameter: {}\n", args[1]);
+            os.exit(1);
+        },
+    }
+
     var loop: event.Loop = undefined;
     try loop.initMultiThreaded(allocator);
     defer loop.deinit();
@@ -578,20 +610,7 @@ async fn findLibCAsync(event_loop_local: *EventLoopLocal) void {
         stderr.print("unable to find libc: {}\n", @errorName(err)) catch os.exit(1);
         os.exit(1);
     };
-    stderr.print(
-        \\include_dir={}
-        \\lib_dir={}
-        \\static_lib_dir={}
-        \\msvc_lib_dir={}
-        \\kernel32_lib_dir={}
-        \\
-    ,
-        libc.include_dir,
-        libc.lib_dir,
-        libc.static_lib_dir orelse "",
-        libc.msvc_lib_dir orelse "",
-        libc.kernel32_lib_dir orelse "",
-    ) catch os.exit(1);
+    libc.render(stdout) catch os.exit(1);
 }
 
 fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
src-self-hosted/target.zig
@@ -2,6 +2,12 @@ const std = @import("std");
 const builtin = @import("builtin");
 const llvm = @import("llvm.zig");
 
+pub const FloatAbi = enum {
+    Hard,
+    Soft,
+    SoftFp,
+};
+
 pub const Target = union(enum) {
     Native,
     Cross: Cross,
@@ -13,7 +19,7 @@ pub const Target = union(enum) {
         object_format: builtin.ObjectFormat,
     };
 
-    pub fn oFileExt(self: Target) []const u8 {
+    pub fn objFileExt(self: Target) []const u8 {
         return switch (self.getObjectFormat()) {
             builtin.ObjectFormat.coff => ".obj",
             else => ".o",
@@ -27,6 +33,13 @@ pub const Target = union(enum) {
         };
     }
 
+    pub fn libFileExt(self: Target, is_static: bool) []const u8 {
+        return switch (self.getOs()) {
+            builtin.Os.windows => if (is_static) ".lib" else ".dll",
+            else => if (is_static) ".a" else ".so",
+        };
+    }
+
     pub fn getOs(self: Target) builtin.Os {
         return switch (self) {
             Target.Native => builtin.os,
@@ -76,6 +89,56 @@ pub const Target = union(enum) {
         };
     }
 
+    /// TODO expose the arch and subarch separately
+    pub fn isArmOrThumb(self: Target) bool {
+        return switch (self.getArch()) {
+            builtin.Arch.armv8_3a,
+            builtin.Arch.armv8_2a,
+            builtin.Arch.armv8_1a,
+            builtin.Arch.armv8,
+            builtin.Arch.armv8r,
+            builtin.Arch.armv8m_baseline,
+            builtin.Arch.armv8m_mainline,
+            builtin.Arch.armv7,
+            builtin.Arch.armv7em,
+            builtin.Arch.armv7m,
+            builtin.Arch.armv7s,
+            builtin.Arch.armv7k,
+            builtin.Arch.armv7ve,
+            builtin.Arch.armv6,
+            builtin.Arch.armv6m,
+            builtin.Arch.armv6k,
+            builtin.Arch.armv6t2,
+            builtin.Arch.armv5,
+            builtin.Arch.armv5te,
+            builtin.Arch.armv4t,
+            builtin.Arch.armebv8_3a,
+            builtin.Arch.armebv8_2a,
+            builtin.Arch.armebv8_1a,
+            builtin.Arch.armebv8,
+            builtin.Arch.armebv8r,
+            builtin.Arch.armebv8m_baseline,
+            builtin.Arch.armebv8m_mainline,
+            builtin.Arch.armebv7,
+            builtin.Arch.armebv7em,
+            builtin.Arch.armebv7m,
+            builtin.Arch.armebv7s,
+            builtin.Arch.armebv7k,
+            builtin.Arch.armebv7ve,
+            builtin.Arch.armebv6,
+            builtin.Arch.armebv6m,
+            builtin.Arch.armebv6k,
+            builtin.Arch.armebv6t2,
+            builtin.Arch.armebv5,
+            builtin.Arch.armebv5te,
+            builtin.Arch.armebv4t,
+            builtin.Arch.thumb,
+            builtin.Arch.thumbeb,
+            => true,
+            else => false,
+        };
+    }
+
     pub fn initializeAll() void {
         llvm.InitializeAllTargets();
         llvm.InitializeAllTargetInfos();
@@ -106,6 +169,257 @@ pub const Target = union(enum) {
         return result;
     }
 
+    pub fn is64bit(self: Target) bool {
+        return self.getArchPtrBitWidth() == 64;
+    }
+
+    pub fn getArchPtrBitWidth(self: Target) u8 {
+        switch (self.getArch()) {
+            builtin.Arch.avr,
+            builtin.Arch.msp430,
+            => return 16,
+
+            builtin.Arch.arc,
+            builtin.Arch.armv8_3a,
+            builtin.Arch.armv8_2a,
+            builtin.Arch.armv8_1a,
+            builtin.Arch.armv8,
+            builtin.Arch.armv8r,
+            builtin.Arch.armv8m_baseline,
+            builtin.Arch.armv8m_mainline,
+            builtin.Arch.armv7,
+            builtin.Arch.armv7em,
+            builtin.Arch.armv7m,
+            builtin.Arch.armv7s,
+            builtin.Arch.armv7k,
+            builtin.Arch.armv7ve,
+            builtin.Arch.armv6,
+            builtin.Arch.armv6m,
+            builtin.Arch.armv6k,
+            builtin.Arch.armv6t2,
+            builtin.Arch.armv5,
+            builtin.Arch.armv5te,
+            builtin.Arch.armv4t,
+            builtin.Arch.armebv8_3a,
+            builtin.Arch.armebv8_2a,
+            builtin.Arch.armebv8_1a,
+            builtin.Arch.armebv8,
+            builtin.Arch.armebv8r,
+            builtin.Arch.armebv8m_baseline,
+            builtin.Arch.armebv8m_mainline,
+            builtin.Arch.armebv7,
+            builtin.Arch.armebv7em,
+            builtin.Arch.armebv7m,
+            builtin.Arch.armebv7s,
+            builtin.Arch.armebv7k,
+            builtin.Arch.armebv7ve,
+            builtin.Arch.armebv6,
+            builtin.Arch.armebv6m,
+            builtin.Arch.armebv6k,
+            builtin.Arch.armebv6t2,
+            builtin.Arch.armebv5,
+            builtin.Arch.armebv5te,
+            builtin.Arch.armebv4t,
+            builtin.Arch.hexagon,
+            builtin.Arch.le32,
+            builtin.Arch.mips,
+            builtin.Arch.mipsel,
+            builtin.Arch.nios2,
+            builtin.Arch.powerpc,
+            builtin.Arch.r600,
+            builtin.Arch.riscv32,
+            builtin.Arch.sparc,
+            builtin.Arch.sparcel,
+            builtin.Arch.tce,
+            builtin.Arch.tcele,
+            builtin.Arch.thumb,
+            builtin.Arch.thumbeb,
+            builtin.Arch.i386,
+            builtin.Arch.xcore,
+            builtin.Arch.nvptx,
+            builtin.Arch.amdil,
+            builtin.Arch.hsail,
+            builtin.Arch.spir,
+            builtin.Arch.kalimbav3,
+            builtin.Arch.kalimbav4,
+            builtin.Arch.kalimbav5,
+            builtin.Arch.shave,
+            builtin.Arch.lanai,
+            builtin.Arch.wasm32,
+            builtin.Arch.renderscript32,
+            => return 32,
+
+            builtin.Arch.aarch64,
+            builtin.Arch.aarch64_be,
+            builtin.Arch.mips64,
+            builtin.Arch.mips64el,
+            builtin.Arch.powerpc64,
+            builtin.Arch.powerpc64le,
+            builtin.Arch.riscv64,
+            builtin.Arch.x86_64,
+            builtin.Arch.nvptx64,
+            builtin.Arch.le64,
+            builtin.Arch.amdil64,
+            builtin.Arch.hsail64,
+            builtin.Arch.spir64,
+            builtin.Arch.wasm64,
+            builtin.Arch.renderscript64,
+            builtin.Arch.amdgcn,
+            builtin.Arch.bpfel,
+            builtin.Arch.bpfeb,
+            builtin.Arch.sparcv9,
+            builtin.Arch.s390x,
+            => return 64,
+        }
+    }
+
+    pub fn getFloatAbi(self: Target) FloatAbi {
+        return switch (self.getEnviron()) {
+            builtin.Environ.gnueabihf,
+            builtin.Environ.eabihf,
+            builtin.Environ.musleabihf,
+            => FloatAbi.Hard,
+            else => FloatAbi.Soft,
+        };
+    }
+
+    pub fn getDynamicLinkerPath(self: Target) ?[]const u8 {
+        const env = self.getEnviron();
+        const arch = self.getArch();
+        switch (env) {
+            builtin.Environ.android => {
+                if (self.is64bit()) {
+                    return "/system/bin/linker64";
+                } else {
+                    return "/system/bin/linker";
+                }
+            },
+            builtin.Environ.gnux32 => {
+                if (arch == builtin.Arch.x86_64) {
+                    return "/libx32/ld-linux-x32.so.2";
+                }
+            },
+            builtin.Environ.musl,
+            builtin.Environ.musleabi,
+            builtin.Environ.musleabihf,
+            => {
+                if (arch == builtin.Arch.x86_64) {
+                    return "/lib/ld-musl-x86_64.so.1";
+                }
+            },
+            else => {},
+        }
+        switch (arch) {
+            builtin.Arch.i386,
+            builtin.Arch.sparc,
+            builtin.Arch.sparcel,
+            => return "/lib/ld-linux.so.2",
+
+            builtin.Arch.aarch64 => return "/lib/ld-linux-aarch64.so.1",
+            builtin.Arch.aarch64_be => return "/lib/ld-linux-aarch64_be.so.1",
+
+            builtin.Arch.armv8_3a,
+            builtin.Arch.armv8_2a,
+            builtin.Arch.armv8_1a,
+            builtin.Arch.armv8,
+            builtin.Arch.armv8r,
+            builtin.Arch.armv8m_baseline,
+            builtin.Arch.armv8m_mainline,
+            builtin.Arch.armv7,
+            builtin.Arch.armv7em,
+            builtin.Arch.armv7m,
+            builtin.Arch.armv7s,
+            builtin.Arch.armv7k,
+            builtin.Arch.armv7ve,
+            builtin.Arch.armv6,
+            builtin.Arch.armv6m,
+            builtin.Arch.armv6k,
+            builtin.Arch.armv6t2,
+            builtin.Arch.armv5,
+            builtin.Arch.armv5te,
+            builtin.Arch.armv4t,
+            builtin.Arch.thumb,
+            => return switch (self.getFloatAbi()) {
+                FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3",
+                else => return "/lib/ld-linux.so.3",
+            },
+
+            builtin.Arch.armebv8_3a,
+            builtin.Arch.armebv8_2a,
+            builtin.Arch.armebv8_1a,
+            builtin.Arch.armebv8,
+            builtin.Arch.armebv8r,
+            builtin.Arch.armebv8m_baseline,
+            builtin.Arch.armebv8m_mainline,
+            builtin.Arch.armebv7,
+            builtin.Arch.armebv7em,
+            builtin.Arch.armebv7m,
+            builtin.Arch.armebv7s,
+            builtin.Arch.armebv7k,
+            builtin.Arch.armebv7ve,
+            builtin.Arch.armebv6,
+            builtin.Arch.armebv6m,
+            builtin.Arch.armebv6k,
+            builtin.Arch.armebv6t2,
+            builtin.Arch.armebv5,
+            builtin.Arch.armebv5te,
+            builtin.Arch.armebv4t,
+            builtin.Arch.thumbeb,
+            => return switch (self.getFloatAbi()) {
+                FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3",
+                else => return "/lib/ld-linux.so.3",
+            },
+
+            builtin.Arch.mips,
+            builtin.Arch.mipsel,
+            builtin.Arch.mips64,
+            builtin.Arch.mips64el,
+            => return null,
+
+            builtin.Arch.powerpc => return "/lib/ld.so.1",
+            builtin.Arch.powerpc64 => return "/lib64/ld64.so.2",
+            builtin.Arch.powerpc64le => return "/lib64/ld64.so.2",
+            builtin.Arch.s390x => return "/lib64/ld64.so.1",
+            builtin.Arch.sparcv9 => return "/lib64/ld-linux.so.2",
+            builtin.Arch.x86_64 => return "/lib64/ld-linux-x86-64.so.2",
+
+            builtin.Arch.arc,
+            builtin.Arch.avr,
+            builtin.Arch.bpfel,
+            builtin.Arch.bpfeb,
+            builtin.Arch.hexagon,
+            builtin.Arch.msp430,
+            builtin.Arch.nios2,
+            builtin.Arch.r600,
+            builtin.Arch.amdgcn,
+            builtin.Arch.riscv32,
+            builtin.Arch.riscv64,
+            builtin.Arch.tce,
+            builtin.Arch.tcele,
+            builtin.Arch.xcore,
+            builtin.Arch.nvptx,
+            builtin.Arch.nvptx64,
+            builtin.Arch.le32,
+            builtin.Arch.le64,
+            builtin.Arch.amdil,
+            builtin.Arch.amdil64,
+            builtin.Arch.hsail,
+            builtin.Arch.hsail64,
+            builtin.Arch.spir,
+            builtin.Arch.spir64,
+            builtin.Arch.kalimbav3,
+            builtin.Arch.kalimbav4,
+            builtin.Arch.kalimbav5,
+            builtin.Arch.shave,
+            builtin.Arch.lanai,
+            builtin.Arch.wasm32,
+            builtin.Arch.wasm64,
+            builtin.Arch.renderscript32,
+            builtin.Arch.renderscript64,
+            => return null,
+        }
+    }
+
     pub fn llvmTargetFromTriple(triple: std.Buffer) !llvm.TargetRef {
         var result: llvm.TargetRef = undefined;
         var err_msg: [*]u8 = undefined;
std/event/group.zig
@@ -6,7 +6,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
 const AtomicOrder = builtin.AtomicOrder;
 const assert = std.debug.assert;
 
-/// ReturnType should be `void` or `E!void`
+/// ReturnType must be `void` or `E!void`
 pub fn Group(comptime ReturnType: type) type {
     return struct {
         coro_stack: Stack,
@@ -39,7 +39,7 @@ pub fn Group(comptime ReturnType: type) type {
         }
 
         /// This is equivalent to an async call, but the async function is added to the group, instead
-        /// of returning a promise. func must be async and have return type void.
+        /// of returning a promise. func must be async and have return type ReturnType.
         /// Thread-safe.
         pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) {
             const S = struct {
std/event/loop.zig
@@ -444,7 +444,7 @@ pub const Loop = struct {
                 .next = undefined,
                 .data = p,
             };
-            loop.onNextTick(&my_tick_node);
+            self.onNextTick(&my_tick_node);
         }
     }