Commit e1d4b59c5b

Andrew Kelley <andrew@ziglang.org>
2020-05-15 21:20:42
self-hosted: update main.zig
After this commit there are no more bit rotted files. The testing program that was in ir.zig has been moved to main.zig Unsupported command line options have been deleted, or error messages added. The compiler repl is available from the build-exe, build-lib, build-obj commands with the --watch option. The main zig build script now builds the self-hosted compiler unconditionally. Linking against LLVM is behind a -Denable-llvm flag that defaults to off.
1 parent ebb81eb
src-self-hosted/ir/text.zig
@@ -20,7 +20,7 @@ pub const Inst = struct {
     name: []const u8,
 
     /// Slice into the source of the part after the = and before the next instruction.
-    contents: []const u8,
+    contents: []const u8 = &[0]u8{},
 
     /// These names are used directly as the instruction names in the text format.
     pub const Tag = enum {
@@ -825,7 +825,6 @@ const Parser = struct {
             .name = inst_name,
             .src = self.i,
             .tag = InstType.base_tag,
-            .contents = undefined,
         };
 
         if (@hasField(InstType, "ty")) {
@@ -960,7 +959,6 @@ const Parser = struct {
                         .name = try self.generateName(),
                         .src = src,
                         .tag = Inst.Str.base_tag,
-                        .contents = undefined,
                     },
                     .positionals = .{ .bytes = ident },
                     .kw_args = .{},
@@ -971,7 +969,6 @@ const Parser = struct {
                         .name = try self.generateName(),
                         .src = src,
                         .tag = Inst.DeclRef.base_tag,
-                        .contents = undefined,
                     },
                     .positionals = .{ .name = &name.base },
                     .kw_args = .{},
src-self-hosted/compilation.zig
@@ -1,1457 +0,0 @@
-const std = @import("std");
-const io = std.io;
-const mem = std.mem;
-const Allocator = mem.Allocator;
-const ArrayListSentineled = std.ArrayListSentineled;
-const llvm = @import("llvm.zig");
-const c = @import("c.zig");
-const builtin = std.builtin;
-const Target = std.Target;
-const warn = std.debug.warn;
-const Token = std.zig.Token;
-const ArrayList = std.ArrayList;
-const errmsg = @import("errmsg.zig");
-const ast = std.zig.ast;
-const event = std.event;
-const assert = std.debug.assert;
-const AtomicRmwOp = builtin.AtomicRmwOp;
-const AtomicOrder = builtin.AtomicOrder;
-const Scope = @import("scope.zig").Scope;
-const Decl = @import("decl.zig").Decl;
-const ir = @import("ir.zig");
-const Value = @import("value.zig").Value;
-const Type = Value.Type;
-const Span = errmsg.Span;
-const Msg = errmsg.Msg;
-const codegen = @import("codegen.zig");
-const Package = @import("package.zig").Package;
-const link = @import("link.zig").link;
-const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
-const CInt = @import("c_int.zig").CInt;
-const fs = std.fs;
-
-pub const Visib = enum {
-    Private,
-    Pub,
-};
-
-const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
-
-/// Data that is local to the event loop.
-pub const ZigCompiler = struct {
-    llvm_handle_pool: std.atomic.Stack(*llvm.Context),
-    lld_lock: event.Lock,
-    allocator: *Allocator,
-
-    /// TODO pool these so that it doesn't have to lock
-    prng: event.Locked(std.rand.DefaultPrng),
-
-    native_libc: event.Future(LibCInstallation),
-
-    var lazy_init_targets = std.once(initializeAllTargets);
-
-    pub fn init(allocator: *Allocator) !ZigCompiler {
-        lazy_init_targets.call();
-
-        var seed_bytes: [@sizeOf(u64)]u8 = undefined;
-        try std.crypto.randomBytes(seed_bytes[0..]);
-        const seed = mem.readIntNative(u64, &seed_bytes);
-
-        return ZigCompiler{
-            .allocator = allocator,
-            .lld_lock = event.Lock.init(),
-            .llvm_handle_pool = std.atomic.Stack(*llvm.Context).init(),
-            .prng = event.Locked(std.rand.DefaultPrng).init(std.rand.DefaultPrng.init(seed)),
-            .native_libc = event.Future(LibCInstallation).init(),
-        };
-    }
-
-    /// Must be called only after EventLoop.run completes.
-    fn deinit(self: *ZigCompiler) void {
-        self.lld_lock.deinit();
-        while (self.llvm_handle_pool.pop()) |node| {
-            llvm.ContextDispose(node.data);
-            self.allocator.destroy(node);
-        }
-    }
-
-    /// Gets an exclusive handle on any LlvmContext.
-    /// Caller must release the handle when done.
-    pub fn getAnyLlvmContext(self: *ZigCompiler) !LlvmHandle {
-        if (self.llvm_handle_pool.pop()) |node| return LlvmHandle{ .node = node };
-
-        const context_ref = llvm.ContextCreate() orelse return error.OutOfMemory;
-        errdefer llvm.ContextDispose(context_ref);
-
-        const node = try self.allocator.create(std.atomic.Stack(*llvm.Context).Node);
-        node.* = std.atomic.Stack(*llvm.Context).Node{
-            .next = undefined,
-            .data = context_ref,
-        };
-        errdefer self.allocator.destroy(node);
-
-        return LlvmHandle{ .node = node };
-    }
-
-    pub fn getNativeLibC(self: *ZigCompiler) !*LibCInstallation {
-        if (self.native_libc.start()) |ptr| return ptr;
-        self.native_libc.data = try LibCInstallation.findNative(.{ .allocator = self.allocator });
-        self.native_libc.resolve();
-        return &self.native_libc.data;
-    }
-
-    /// Must be called only once, ever. Sets global state.
-    pub fn setLlvmArgv(allocator: *Allocator, llvm_argv: []const []const u8) !void {
-        if (llvm_argv.len != 0) {
-            var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(allocator, &[_][]const []const u8{
-                &[_][]const u8{"zig (LLVM option parsing)"},
-                llvm_argv,
-            });
-            defer c_compatible_args.deinit();
-            c.ZigLLVMParseCommandLineOptions(llvm_argv.len + 1, c_compatible_args.ptr);
-        }
-    }
-};
-
-pub const LlvmHandle = struct {
-    node: *std.atomic.Stack(*llvm.Context).Node,
-
-    pub fn release(self: LlvmHandle, zig_compiler: *ZigCompiler) void {
-        zig_compiler.llvm_handle_pool.push(self.node);
-    }
-};
-
-pub const Compilation = struct {
-    pub const FnLinkSet = std.TailQueue(?*Value.Fn);
-
-    zig_compiler: *ZigCompiler,
-    name: ArrayListSentineled(u8, 0),
-    llvm_triple: ArrayListSentineled(u8, 0),
-    root_src_path: ?[]const u8,
-    target: std.Target,
-    llvm_target: *llvm.Target,
-    build_mode: builtin.Mode,
-    zig_lib_dir: []const u8,
-    zig_std_dir: []const u8,
-
-    /// lazily created when we need it
-    tmp_dir: event.Future(BuildError![]u8) = event.Future(BuildError![]u8).init(),
-
-    version: builtin.Version = builtin.Version{ .major = 0, .minor = 0, .patch = 0 },
-
-    linker_script: ?[]const u8 = null,
-    out_h_path: ?[]const u8 = null,
-
-    is_test: bool = false,
-    strip: bool = false,
-    is_static: bool,
-    linker_rdynamic: bool = false,
-
-    clang_argv: []const []const u8 = &[_][]const u8{},
-    assembly_files: []const []const u8 = &[_][]const u8{},
-
-    /// paths that are explicitly provided by the user to link against
-    link_objects: []const []const u8 = &[_][]const u8{},
-
-    /// functions that have their own objects that we need to link
-    /// it uses an optional pointer so that tombstone removals are possible
-    fn_link_set: event.Locked(FnLinkSet) = event.Locked(FnLinkSet).init(FnLinkSet.init()),
-
-    link_libs_list: ArrayList(*LinkLib),
-    libc_link_lib: ?*LinkLib = null,
-
-    err_color: errmsg.Color = .Auto,
-
-    verbose_tokenize: bool = false,
-    verbose_ast_tree: bool = false,
-    verbose_ast_fmt: bool = false,
-    verbose_cimport: bool = false,
-    verbose_ir: bool = false,
-    verbose_llvm_ir: bool = false,
-    verbose_link: bool = false,
-
-    link_eh_frame_hdr: bool = false,
-
-    darwin_version_min: DarwinVersionMin = .None,
-
-    test_filters: []const []const u8 = &[_][]const u8{},
-    test_name_prefix: ?[]const u8 = null,
-
-    emit_bin: bool = true,
-    emit_asm: bool = false,
-    emit_llvm_ir: bool = false,
-    emit_h: bool = false,
-
-    kind: Kind,
-
-    events: *event.Channel(Event),
-
-    exported_symbol_names: event.Locked(Decl.Table),
-
-    /// Before code generation starts, must wait on this group to make sure
-    /// the build is complete.
-    prelink_group: event.Group(BuildError!void),
-
-    compile_errors: event.Locked(CompileErrList),
-
-    meta_type: *Type.MetaType,
-    void_type: *Type.Void,
-    bool_type: *Type.Bool,
-    noreturn_type: *Type.NoReturn,
-    comptime_int_type: *Type.ComptimeInt,
-    u8_type: *Type.Int,
-
-    void_value: *Value.Void,
-    true_value: *Value.Bool,
-    false_value: *Value.Bool,
-    noreturn_value: *Value.NoReturn,
-
-    target_machine: *llvm.TargetMachine,
-    target_data_ref: *llvm.TargetData,
-    target_layout_str: [*:0]u8,
-    target_ptr_bits: u32,
-
-    /// for allocating things which have the same lifetime as this Compilation
-    arena_allocator: std.heap.ArenaAllocator,
-
-    root_package: *Package,
-    std_package: *Package,
-
-    override_libc: ?*LibCInstallation = null,
-
-    /// need to wait on this group before deinitializing
-    deinit_group: event.Group(void),
-
-    destroy_frame: *@Frame(createAsync),
-    main_loop_frame: *@Frame(Compilation.mainLoop),
-    main_loop_future: event.Future(void) = event.Future(void).init(),
-
-    have_err_ret_tracing: bool = false,
-
-    /// not locked because it is read-only
-    primitive_type_table: TypeTable,
-
-    int_type_table: event.Locked(IntTypeTable),
-    array_type_table: event.Locked(ArrayTypeTable),
-    ptr_type_table: event.Locked(PtrTypeTable),
-    fn_type_table: event.Locked(FnTypeTable),
-
-    c_int_types: [CInt.list.len]*Type.Int,
-
-    fs_watch: *fs.Watch(*Scope.Root),
-
-    cancelled: bool = false,
-
-    const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql);
-    const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql);
-    const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql);
-    const FnTypeTable = std.HashMap(*const Type.Fn.Key, *Type.Fn, Type.Fn.Key.hash, Type.Fn.Key.eql);
-    const TypeTable = std.StringHashMap(*Type);
-
-    const CompileErrList = std.ArrayList(*Msg);
-
-    // TODO handle some of these earlier and report them in a way other than error codes
-    pub const BuildError = error{
-        OutOfMemory,
-        EndOfStream,
-        IsDir,
-        Unexpected,
-        SystemResources,
-        SharingViolation,
-        PathAlreadyExists,
-        FileNotFound,
-        AccessDenied,
-        PipeBusy,
-        FileTooBig,
-        SymLinkLoop,
-        ProcessFdQuotaExceeded,
-        NameTooLong,
-        SystemFdQuotaExceeded,
-        NoDevice,
-        NoSpaceLeft,
-        NotDir,
-        FileSystem,
-        OperationAborted,
-        IoPending,
-        BrokenPipe,
-        WouldBlock,
-        FileClosed,
-        DestinationAddressRequired,
-        DiskQuota,
-        InputOutput,
-        NoStdHandles,
-        Overflow,
-        NotSupported,
-        BufferTooSmall,
-        Unimplemented, // TODO remove this one
-        SemanticAnalysisFailed, // TODO remove this one
-        ReadOnlyFileSystem,
-        LinkQuotaExceeded,
-        EnvironmentVariableNotFound,
-        AppDataDirUnavailable,
-        LinkFailed,
-        LibCRequiredButNotProvidedOrFound,
-        LibCMissingDynamicLinker,
-        InvalidDarwinVersionString,
-        UnsupportedLinkArchitecture,
-        UserResourceLimitReached,
-        InvalidUtf8,
-        BadPathName,
-        DeviceBusy,
-        CurrentWorkingDirectoryUnlinked,
-    };
-
-    pub const Event = union(enum) {
-        Ok,
-        Error: BuildError,
-        Fail: []*Msg,
-    };
-
-    pub const DarwinVersionMin = union(enum) {
-        None,
-        MacOS: []const u8,
-        Ios: []const u8,
-    };
-
-    pub const Kind = enum {
-        Exe,
-        Lib,
-        Obj,
-    };
-
-    pub const LinkLib = struct {
-        name: []const u8,
-        path: ?[]const u8,
-
-        /// the list of symbols we depend on from this lib
-        symbols: ArrayList([]u8),
-        provided_explicitly: bool,
-    };
-
-    pub const Emit = enum {
-        Binary,
-        Assembly,
-        LlvmIr,
-    };
-
-    pub fn create(
-        zig_compiler: *ZigCompiler,
-        name: []const u8,
-        root_src_path: ?[]const u8,
-        target: std.zig.CrossTarget,
-        kind: Kind,
-        build_mode: builtin.Mode,
-        is_static: bool,
-        zig_lib_dir: []const u8,
-    ) !*Compilation {
-        var optional_comp: ?*Compilation = null;
-        var frame = try zig_compiler.allocator.create(@Frame(createAsync));
-        errdefer zig_compiler.allocator.destroy(frame);
-        frame.* = async createAsync(
-            &optional_comp,
-            zig_compiler,
-            name,
-            root_src_path,
-            target,
-            kind,
-            build_mode,
-            is_static,
-            zig_lib_dir,
-        );
-        // TODO causes segfault
-        // return optional_comp orelse if (await frame) |_| unreachable else |err| err;
-        if (optional_comp) |comp| {
-            return comp;
-        } else if (await frame) |_| unreachable else |err| return err;
-    }
-    fn createAsync(
-        out_comp: *?*Compilation,
-        zig_compiler: *ZigCompiler,
-        name: []const u8,
-        root_src_path: ?[]const u8,
-        cross_target: std.zig.CrossTarget,
-        kind: Kind,
-        build_mode: builtin.Mode,
-        is_static: bool,
-        zig_lib_dir: []const u8,
-    ) callconv(.Async) !void {
-        const allocator = zig_compiler.allocator;
-
-        // TODO merge this line with stage2.zig crossTargetToTarget
-        const target_info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator, cross_target);
-        const target = target_info.target;
-
-        var comp = Compilation{
-            .arena_allocator = std.heap.ArenaAllocator.init(allocator),
-            .zig_compiler = zig_compiler,
-            .events = undefined,
-            .root_src_path = root_src_path,
-            .target = target,
-            .llvm_target = undefined,
-            .kind = kind,
-            .build_mode = build_mode,
-            .zig_lib_dir = zig_lib_dir,
-            .zig_std_dir = undefined,
-            .destroy_frame = @frame(),
-            .main_loop_frame = undefined,
-
-            .name = undefined,
-            .llvm_triple = undefined,
-            .is_static = is_static,
-            .link_libs_list = undefined,
-            .exported_symbol_names = event.Locked(Decl.Table).init(Decl.Table.init(allocator)),
-            .prelink_group = event.Group(BuildError!void).init(allocator),
-            .deinit_group = event.Group(void).init(allocator),
-            .compile_errors = event.Locked(CompileErrList).init(CompileErrList.init(allocator)),
-            .int_type_table = event.Locked(IntTypeTable).init(IntTypeTable.init(allocator)),
-            .array_type_table = event.Locked(ArrayTypeTable).init(ArrayTypeTable.init(allocator)),
-            .ptr_type_table = event.Locked(PtrTypeTable).init(PtrTypeTable.init(allocator)),
-            .fn_type_table = event.Locked(FnTypeTable).init(FnTypeTable.init(allocator)),
-            .c_int_types = undefined,
-
-            .meta_type = undefined,
-            .void_type = undefined,
-            .void_value = undefined,
-            .bool_type = undefined,
-            .true_value = undefined,
-            .false_value = undefined,
-            .noreturn_type = undefined,
-            .noreturn_value = undefined,
-            .comptime_int_type = undefined,
-            .u8_type = undefined,
-
-            .target_machine = undefined,
-            .target_data_ref = undefined,
-            .target_layout_str = undefined,
-            .target_ptr_bits = target.cpu.arch.ptrBitWidth(),
-
-            .root_package = undefined,
-            .std_package = undefined,
-
-            .primitive_type_table = undefined,
-
-            .fs_watch = undefined,
-        };
-        comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena());
-        comp.primitive_type_table = TypeTable.init(comp.arena());
-
-        defer {
-            comp.int_type_table.private_data.deinit();
-            comp.array_type_table.private_data.deinit();
-            comp.ptr_type_table.private_data.deinit();
-            comp.fn_type_table.private_data.deinit();
-            comp.arena_allocator.deinit();
-        }
-
-        comp.name = try ArrayListSentineled(u8, 0).init(comp.arena(), name);
-        comp.llvm_triple = try getLLVMTriple(comp.arena(), target);
-        comp.llvm_target = try llvmTargetFromTriple(comp.llvm_triple);
-        comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
-
-        const opt_level = switch (build_mode) {
-            .Debug => llvm.CodeGenLevelNone,
-            else => llvm.CodeGenLevelAggressive,
-        };
-
-        const reloc_mode = if (is_static) llvm.RelocStatic else llvm.RelocPIC;
-
-        var target_specific_cpu_args: ?[*:0]u8 = null;
-        var target_specific_cpu_features: ?[*:0]u8 = null;
-        defer llvm.DisposeMessage(target_specific_cpu_args);
-        defer llvm.DisposeMessage(target_specific_cpu_features);
-
-        // TODO detect native CPU & features here
-
-        comp.target_machine = llvm.CreateTargetMachine(
-            comp.llvm_target,
-            comp.llvm_triple.span(),
-            target_specific_cpu_args orelse "",
-            target_specific_cpu_features orelse "",
-            opt_level,
-            reloc_mode,
-            llvm.CodeModelDefault,
-            false, // TODO: add -ffunction-sections option
-        ) orelse return error.OutOfMemory;
-        defer llvm.DisposeTargetMachine(comp.target_machine);
-
-        comp.target_data_ref = llvm.CreateTargetDataLayout(comp.target_machine) orelse return error.OutOfMemory;
-        defer llvm.DisposeTargetData(comp.target_data_ref);
-
-        comp.target_layout_str = llvm.CopyStringRepOfTargetData(comp.target_data_ref) orelse return error.OutOfMemory;
-        defer llvm.DisposeMessage(comp.target_layout_str);
-
-        comp.events = try allocator.create(event.Channel(Event));
-        defer allocator.destroy(comp.events);
-
-        comp.events.init(&[0]Event{});
-        defer comp.events.deinit();
-
-        if (root_src_path) |root_src| {
-            const dirname = fs.path.dirname(root_src) orelse ".";
-            const basename = fs.path.basename(root_src);
-
-            comp.root_package = try Package.create(comp.arena(), dirname, basename);
-            comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");
-            try comp.root_package.add("std", comp.std_package);
-        } else {
-            comp.root_package = try Package.create(comp.arena(), ".", "");
-        }
-
-        comp.fs_watch = try fs.Watch(*Scope.Root).init(allocator, 16);
-        defer comp.fs_watch.deinit();
-
-        try comp.initTypes();
-        defer comp.primitive_type_table.deinit();
-
-        comp.main_loop_frame = try allocator.create(@Frame(mainLoop));
-        defer allocator.destroy(comp.main_loop_frame);
-
-        comp.main_loop_frame.* = async comp.mainLoop();
-        // Set this to indicate that initialization completed successfully.
-        // from here on out we must not return an error.
-        // This must occur before the first suspend/await.
-        out_comp.* = &comp;
-        // This suspend is resumed by destroy()
-        suspend;
-        // From here on is cleanup.
-
-        comp.deinit_group.wait();
-
-        if (comp.tmp_dir.getOrNull()) |tmp_dir_result|
-            if (tmp_dir_result.*) |tmp_dir| {
-                fs.cwd().deleteTree(tmp_dir) catch {};
-            } else |_| {};
-    }
-
-    /// it does ref the result because it could be an arbitrary integer size
-    pub fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type {
-        if (name.len >= 2) {
-            switch (name[0]) {
-                'i', 'u' => blk: {
-                    for (name[1..]) |byte|
-                        switch (byte) {
-                            '0'...'9' => {},
-                            else => break :blk,
-                        };
-                    const is_signed = name[0] == 'i';
-                    const bit_count = std.fmt.parseUnsigned(u32, name[1..], 10) catch |err| switch (err) {
-                        error.Overflow => return error.Overflow,
-                        error.InvalidCharacter => unreachable, // we just checked the characters above
-                    };
-                    const int_type = try Type.Int.get(comp, Type.Int.Key{
-                        .bit_count = bit_count,
-                        .is_signed = is_signed,
-                    });
-                    errdefer int_type.base.base.deref();
-                    return &int_type.base;
-                },
-                else => {},
-            }
-        }
-
-        if (comp.primitive_type_table.get(name)) |entry| {
-            entry.value.base.ref();
-            return entry.value;
-        }
-
-        return null;
-    }
-
-    fn initTypes(comp: *Compilation) !void {
-        comp.meta_type = try comp.arena().create(Type.MetaType);
-        comp.meta_type.* = Type.MetaType{
-            .base = Type{
-                .name = "type",
-                .base = Value{
-                    .id = .Type,
-                    .typ = undefined,
-                    .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice
-                },
-                .id = .Type,
-                .abi_alignment = Type.AbiAlignment.init(),
-            },
-            .value = undefined,
-        };
-        comp.meta_type.value = &comp.meta_type.base;
-        comp.meta_type.base.base.typ = &comp.meta_type.base;
-        assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null);
-
-        comp.void_type = try comp.arena().create(Type.Void);
-        comp.void_type.* = Type.Void{
-            .base = Type{
-                .name = "void",
-                .base = Value{
-                    .id = .Type,
-                    .typ = &Type.MetaType.get(comp).base,
-                    .ref_count = std.atomic.Int(usize).init(1),
-                },
-                .id = .Void,
-                .abi_alignment = Type.AbiAlignment.init(),
-            },
-        };
-        assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null);
-
-        comp.noreturn_type = try comp.arena().create(Type.NoReturn);
-        comp.noreturn_type.* = Type.NoReturn{
-            .base = Type{
-                .name = "noreturn",
-                .base = Value{
-                    .id = .Type,
-                    .typ = &Type.MetaType.get(comp).base,
-                    .ref_count = std.atomic.Int(usize).init(1),
-                },
-                .id = .NoReturn,
-                .abi_alignment = Type.AbiAlignment.init(),
-            },
-        };
-        assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null);
-
-        comp.comptime_int_type = try comp.arena().create(Type.ComptimeInt);
-        comp.comptime_int_type.* = Type.ComptimeInt{
-            .base = Type{
-                .name = "comptime_int",
-                .base = Value{
-                    .id = .Type,
-                    .typ = &Type.MetaType.get(comp).base,
-                    .ref_count = std.atomic.Int(usize).init(1),
-                },
-                .id = .ComptimeInt,
-                .abi_alignment = Type.AbiAlignment.init(),
-            },
-        };
-        assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null);
-
-        comp.bool_type = try comp.arena().create(Type.Bool);
-        comp.bool_type.* = Type.Bool{
-            .base = Type{
-                .name = "bool",
-                .base = Value{
-                    .id = .Type,
-                    .typ = &Type.MetaType.get(comp).base,
-                    .ref_count = std.atomic.Int(usize).init(1),
-                },
-                .id = .Bool,
-                .abi_alignment = Type.AbiAlignment.init(),
-            },
-        };
-        assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null);
-
-        comp.void_value = try comp.arena().create(Value.Void);
-        comp.void_value.* = Value.Void{
-            .base = Value{
-                .id = .Void,
-                .typ = &Type.Void.get(comp).base,
-                .ref_count = std.atomic.Int(usize).init(1),
-            },
-        };
-
-        comp.true_value = try comp.arena().create(Value.Bool);
-        comp.true_value.* = Value.Bool{
-            .base = Value{
-                .id = .Bool,
-                .typ = &Type.Bool.get(comp).base,
-                .ref_count = std.atomic.Int(usize).init(1),
-            },
-            .x = true,
-        };
-
-        comp.false_value = try comp.arena().create(Value.Bool);
-        comp.false_value.* = Value.Bool{
-            .base = Value{
-                .id = .Bool,
-                .typ = &Type.Bool.get(comp).base,
-                .ref_count = std.atomic.Int(usize).init(1),
-            },
-            .x = false,
-        };
-
-        comp.noreturn_value = try comp.arena().create(Value.NoReturn);
-        comp.noreturn_value.* = Value.NoReturn{
-            .base = Value{
-                .id = .NoReturn,
-                .typ = &Type.NoReturn.get(comp).base,
-                .ref_count = std.atomic.Int(usize).init(1),
-            },
-        };
-
-        for (CInt.list) |cint, i| {
-            const c_int_type = try comp.arena().create(Type.Int);
-            c_int_type.* = Type.Int{
-                .base = Type{
-                    .name = cint.zig_name,
-                    .base = Value{
-                        .id = .Type,
-                        .typ = &Type.MetaType.get(comp).base,
-                        .ref_count = std.atomic.Int(usize).init(1),
-                    },
-                    .id = .Int,
-                    .abi_alignment = Type.AbiAlignment.init(),
-                },
-                .key = Type.Int.Key{
-                    .is_signed = cint.is_signed,
-                    .bit_count = cint.sizeInBits(comp.target),
-                },
-                .garbage_node = undefined,
-            };
-            comp.c_int_types[i] = c_int_type;
-            assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null);
-        }
-        comp.u8_type = try comp.arena().create(Type.Int);
-        comp.u8_type.* = Type.Int{
-            .base = Type{
-                .name = "u8",
-                .base = Value{
-                    .id = .Type,
-                    .typ = &Type.MetaType.get(comp).base,
-                    .ref_count = std.atomic.Int(usize).init(1),
-                },
-                .id = .Int,
-                .abi_alignment = Type.AbiAlignment.init(),
-            },
-            .key = Type.Int.Key{
-                .is_signed = false,
-                .bit_count = 8,
-            },
-            .garbage_node = undefined,
-        };
-        assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null);
-    }
-
-    pub fn destroy(self: *Compilation) void {
-        const allocator = self.gpa();
-        self.cancelled = true;
-        await self.main_loop_frame;
-        resume self.destroy_frame;
-        allocator.destroy(self.destroy_frame);
-    }
-
-    fn start(self: *Compilation) void {
-        self.main_loop_future.resolve();
-    }
-    fn mainLoop(self: *Compilation) callconv(.Async) void {
-        // wait until start() is called
-        _ = self.main_loop_future.get();
-
-        var build_result = self.initialCompile();
-
-        while (!self.cancelled) {
-            const link_result = if (build_result) blk: {
-                break :blk self.maybeLink();
-            } else |err| err;
-            // this makes a handy error return trace and stack trace in debug mode
-            if (std.debug.runtime_safety) {
-                link_result catch unreachable;
-            }
-
-            const compile_errors = blk: {
-                const held = self.compile_errors.acquire();
-                defer held.release();
-                break :blk held.value.toOwnedSlice();
-            };
-
-            if (link_result) |_| {
-                if (compile_errors.len == 0) {
-                    self.events.put(Event.Ok);
-                } else {
-                    self.events.put(Event{ .Fail = compile_errors });
-                }
-            } else |err| {
-                // if there's an error then the compile errors have dangling references
-                self.gpa().free(compile_errors);
-
-                self.events.put(Event{ .Error = err });
-            }
-
-            // First, get an item from the watch channel, waiting on the channel.
-            var group = event.Group(BuildError!void).init(self.gpa());
-            {
-                const ev = (self.fs_watch.channel.get()) catch |err| {
-                    build_result = err;
-                    continue;
-                };
-                const root_scope = ev.data;
-                group.call(rebuildFile, .{ self, root_scope }) catch |err| {
-                    build_result = err;
-                    continue;
-                };
-            }
-            // Next, get all the items from the channel that are buffered up.
-            while (self.fs_watch.channel.getOrNull()) |ev_or_err| {
-                if (ev_or_err) |ev| {
-                    const root_scope = ev.data;
-                    group.call(rebuildFile, .{ self, root_scope }) catch |err| {
-                        build_result = err;
-                        continue;
-                    };
-                } else |err| {
-                    build_result = err;
-                    continue;
-                }
-            }
-            build_result = group.wait();
-        }
-    }
-    fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) callconv(.Async) BuildError!void {
-        const tree_scope = blk: {
-            const source_code = fs.cwd().readFileAlloc(
-                self.gpa(),
-                root_scope.realpath,
-                max_src_size,
-            ) catch |err| {
-                try self.addCompileErrorCli(root_scope.realpath, "unable to open: {}", .{@errorName(err)});
-                return;
-            };
-            errdefer self.gpa().free(source_code);
-
-            const tree = try std.zig.parse(self.gpa(), source_code);
-            errdefer {
-                tree.deinit();
-            }
-
-            break :blk try Scope.AstTree.create(self, tree, root_scope);
-        };
-        defer tree_scope.base.deref(self);
-
-        var error_it = tree_scope.tree.errors.iterator(0);
-        while (error_it.next()) |parse_error| {
-            const msg = try Msg.createFromParseErrorAndScope(self, tree_scope, parse_error);
-            errdefer msg.destroy();
-
-            try self.addCompileErrorAsync(msg);
-        }
-        if (tree_scope.tree.errors.len != 0) {
-            return;
-        }
-
-        const locked_table = root_scope.decls.table.acquireWrite();
-        defer locked_table.release();
-
-        var decl_group = event.Group(BuildError!void).init(self.gpa());
-
-        try self.rebuildChangedDecls(
-            &decl_group,
-            locked_table.value,
-            root_scope.decls,
-            &tree_scope.tree.root_node.decls,
-            tree_scope,
-        );
-
-        try decl_group.wait();
-    }
-
-    fn rebuildChangedDecls(
-        self: *Compilation,
-        group: *event.Group(BuildError!void),
-        locked_table: *Decl.Table,
-        decl_scope: *Scope.Decls,
-        ast_decls: *ast.Node.Root.DeclList,
-        tree_scope: *Scope.AstTree,
-    ) !void {
-        var existing_decls = try locked_table.clone();
-        defer existing_decls.deinit();
-
-        var ast_it = ast_decls.iterator(0);
-        while (ast_it.next()) |decl_ptr| {
-            const decl = decl_ptr.*;
-            switch (decl.id) {
-                .Comptime => {
-                    const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl);
-
-                    // TODO connect existing comptime decls to updated source files
-
-                    try self.prelink_group.call(addCompTimeBlock, .{ self, tree_scope, &decl_scope.base, comptime_node });
-                },
-                .VarDecl => @panic("TODO"),
-                .FnProto => {
-                    const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
-
-                    const name = if (fn_proto.name_token) |name_token| tree_scope.tree.tokenSlice(name_token) else {
-                        try self.addCompileError(tree_scope, Span{
-                            .first = fn_proto.fn_token,
-                            .last = fn_proto.fn_token + 1,
-                        }, "missing function name", .{});
-                        continue;
-                    };
-
-                    if (existing_decls.remove(name)) |entry| {
-                        // compare new code to existing
-                        if (entry.value.cast(Decl.Fn)) |existing_fn_decl| {
-                            // Just compare the old bytes to the new bytes of the top level decl.
-                            // Even if the AST is technically the same, we want error messages to display
-                            // from the most recent source.
-                            const old_decl_src = existing_fn_decl.base.tree_scope.tree.getNodeSource(
-                                &existing_fn_decl.fn_proto.base,
-                            );
-                            const new_decl_src = tree_scope.tree.getNodeSource(&fn_proto.base);
-                            if (mem.eql(u8, old_decl_src, new_decl_src)) {
-                                // it's the same, we can skip this decl
-                                continue;
-                            } else {
-                                @panic("TODO decl changed implementation");
-                                // Add the new thing before dereferencing the old thing. This way we don't end
-                                // up pointlessly re-creating things we end up using in the new thing.
-                            }
-                        } else {
-                            @panic("TODO decl changed kind");
-                        }
-                    } else {
-                        // add new decl
-                        const fn_decl = try self.gpa().create(Decl.Fn);
-                        fn_decl.* = Decl.Fn{
-                            .base = Decl{
-                                .id = Decl.Id.Fn,
-                                .name = name,
-                                .visib = parseVisibToken(tree_scope.tree, fn_proto.visib_token),
-                                .resolution = event.Future(BuildError!void).init(),
-                                .parent_scope = &decl_scope.base,
-                                .tree_scope = tree_scope,
-                            },
-                            .value = .Unresolved,
-                            .fn_proto = fn_proto,
-                        };
-                        tree_scope.base.ref();
-                        errdefer self.gpa().destroy(fn_decl);
-
-                        try group.call(addTopLevelDecl, .{ self, &fn_decl.base, locked_table });
-                    }
-                },
-                .TestDecl => @panic("TODO"),
-                else => unreachable,
-            }
-        }
-
-        var existing_decl_it = existing_decls.iterator();
-        while (existing_decl_it.next()) |entry| {
-            // this decl was deleted
-            const existing_decl = entry.value;
-            @panic("TODO handle decl deletion");
-        }
-    }
-
-    fn initialCompile(self: *Compilation) !void {
-        if (self.root_src_path) |root_src_path| {
-            const root_scope = blk: {
-                // TODO async/await fs.realpath
-                const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {
-                    try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});
-                    return;
-                };
-                errdefer self.gpa().free(root_src_real_path);
-
-                break :blk try Scope.Root.create(self, root_src_real_path);
-            };
-            defer root_scope.base.deref(self);
-
-            // assert((try self.fs_watch.addFile(root_scope.realpath, root_scope)) == null);
-            try self.rebuildFile(root_scope);
-        }
-    }
-
-    fn maybeLink(self: *Compilation) !void {
-        (self.prelink_group.wait()) catch |err| switch (err) {
-            error.SemanticAnalysisFailed => {},
-            else => return err,
-        };
-
-        const any_prelink_errors = blk: {
-            const compile_errors = self.compile_errors.acquire();
-            defer compile_errors.release();
-
-            break :blk compile_errors.value.len != 0;
-        };
-
-        if (!any_prelink_errors) {
-            try link(self);
-        }
-    }
-    /// caller takes ownership of resulting Code
-    fn genAndAnalyzeCode(
-        comp: *Compilation,
-        tree_scope: *Scope.AstTree,
-        scope: *Scope,
-        node: *ast.Node,
-        expected_type: ?*Type,
-    ) callconv(.Async) !*ir.Code {
-        const unanalyzed_code = try ir.gen(
-            comp,
-            node,
-            tree_scope,
-            scope,
-        );
-        defer unanalyzed_code.destroy(comp.gpa());
-
-        if (comp.verbose_ir) {
-            std.debug.warn("unanalyzed:\n", .{});
-            unanalyzed_code.dump();
-        }
-
-        const analyzed_code = try ir.analyze(
-            comp,
-            unanalyzed_code,
-            expected_type,
-        );
-        errdefer analyzed_code.destroy(comp.gpa());
-
-        if (comp.verbose_ir) {
-            std.debug.warn("analyzed:\n", .{});
-            analyzed_code.dump();
-        }
-
-        return analyzed_code;
-    }
-    fn addCompTimeBlock(
-        comp: *Compilation,
-        tree_scope: *Scope.AstTree,
-        scope: *Scope,
-        comptime_node: *ast.Node.Comptime,
-    ) callconv(.Async) BuildError!void {
-        const void_type = Type.Void.get(comp);
-        defer void_type.base.base.deref(comp);
-
-        const analyzed_code = genAndAnalyzeCode(
-            comp,
-            tree_scope,
-            scope,
-            comptime_node.expr,
-            &void_type.base,
-        ) catch |err| switch (err) {
-            // This poison value should not cause the errdefers to run. It simply means
-            // that comp.compile_errors is populated.
-            error.SemanticAnalysisFailed => return {},
-            else => return err,
-        };
-        analyzed_code.destroy(comp.gpa());
-    }
-    fn addTopLevelDecl(
-        self: *Compilation,
-        decl: *Decl,
-        locked_table: *Decl.Table,
-    ) callconv(.Async) BuildError!void {
-        const is_export = decl.isExported(decl.tree_scope.tree);
-
-        if (is_export) {
-            try self.prelink_group.call(verifyUniqueSymbol, .{ self, decl });
-            try self.prelink_group.call(resolveDecl, .{ self, decl });
-        }
-
-        const gop = try locked_table.getOrPut(decl.name);
-        if (gop.found_existing) {
-            try self.addCompileError(decl.tree_scope, decl.getSpan(), "redefinition of '{}'", .{decl.name});
-            // TODO note: other definition here
-        } else {
-            gop.kv.value = decl;
-        }
-    }
-
-    fn addCompileError(self: *Compilation, tree_scope: *Scope.AstTree, span: Span, comptime fmt: []const u8, args: var) !void {
-        const text = try std.fmt.allocPrint(self.gpa(), fmt, args);
-        errdefer self.gpa().free(text);
-
-        const msg = try Msg.createFromScope(self, tree_scope, span, text);
-        errdefer msg.destroy();
-
-        try self.prelink_group.call(addCompileErrorAsync, .{ self, msg });
-    }
-
-    fn addCompileErrorCli(self: *Compilation, realpath: []const u8, comptime fmt: []const u8, args: var) !void {
-        const text = try std.fmt.allocPrint(self.gpa(), fmt, args);
-        errdefer self.gpa().free(text);
-
-        const msg = try Msg.createFromCli(self, realpath, text);
-        errdefer msg.destroy();
-
-        try self.prelink_group.call(addCompileErrorAsync, .{ self, msg });
-    }
-    fn addCompileErrorAsync(
-        self: *Compilation,
-        msg: *Msg,
-    ) callconv(.Async) BuildError!void {
-        errdefer msg.destroy();
-
-        const compile_errors = self.compile_errors.acquire();
-        defer compile_errors.release();
-
-        try compile_errors.value.append(msg);
-    }
-    fn verifyUniqueSymbol(self: *Compilation, decl: *Decl) callconv(.Async) BuildError!void {
-        const exported_symbol_names = self.exported_symbol_names.acquire();
-        defer exported_symbol_names.release();
-
-        if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| {
-            try self.addCompileError(decl.tree_scope, decl.getSpan(), "exported symbol collision: '{}'", .{
-                decl.name,
-            });
-            // TODO add error note showing location of other symbol
-        }
-    }
-
-    pub fn haveLibC(self: *Compilation) bool {
-        return self.libc_link_lib != null;
-    }
-
-    pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib {
-        const is_libc = mem.eql(u8, name, "c");
-
-        if (is_libc) {
-            if (self.libc_link_lib) |libc_link_lib| {
-                return libc_link_lib;
-            }
-        }
-
-        for (self.link_libs_list.span()) |existing_lib| {
-            if (mem.eql(u8, name, existing_lib.name)) {
-                return existing_lib;
-            }
-        }
-
-        const link_lib = try self.gpa().create(LinkLib);
-        link_lib.* = LinkLib{
-            .name = name,
-            .path = null,
-            .provided_explicitly = provided_explicitly,
-            .symbols = ArrayList([]u8).init(self.gpa()),
-        };
-        try self.link_libs_list.append(link_lib);
-        if (is_libc) {
-            self.libc_link_lib = link_lib;
-
-            // get a head start on looking for the native libc
-            // TODO this is missing a bunch of logic related to whether the target is native
-            // and whether we can build libc
-            if (self.override_libc == null) {
-                try self.deinit_group.call(startFindingNativeLibC, .{self});
-            }
-        }
-        return link_lib;
-    }
-    fn startFindingNativeLibC(self: *Compilation) callconv(.Async) void {
-        event.Loop.startCpuBoundOperation();
-        // we don't care if it fails, we're just trying to kick off the future resolution
-        _ = self.zig_compiler.getNativeLibC() catch return;
-    }
-
-    /// General Purpose Allocator. Must free when done.
-    fn gpa(self: Compilation) *mem.Allocator {
-        return self.zig_compiler.allocator;
-    }
-
-    /// Arena Allocator. Automatically freed when the Compilation is destroyed.
-    fn arena(self: *Compilation) *mem.Allocator {
-        return &self.arena_allocator.allocator;
-    }
-
-    /// If the temporary directory for this compilation has not been created, it creates it.
-    /// Then it creates a random file name in that dir and returns it.
-    pub fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !ArrayListSentineled(u8, 0) {
-        const tmp_dir = try self.getTmpDir();
-        const file_prefix = self.getRandomFileName();
-
-        const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });
-        defer self.gpa().free(file_name);
-
-        const full_path = try fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });
-        errdefer self.gpa().free(full_path);
-
-        return ArrayListSentineled(u8, 0).fromOwnedSlice(self.gpa(), full_path);
-    }
-
-    /// If the temporary directory for this Compilation has not been created, creates it.
-    /// Then returns it. The directory is unique to this Compilation and cleaned up when
-    /// the Compilation deinitializes.
-    fn getTmpDir(self: *Compilation) ![]const u8 {
-        if (self.tmp_dir.start()) |ptr| return ptr.*;
-        self.tmp_dir.data = self.getTmpDirImpl();
-        self.tmp_dir.resolve();
-        return self.tmp_dir.data;
-    }
-
-    fn getTmpDirImpl(self: *Compilation) ![]u8 {
-        const comp_dir_name = self.getRandomFileName();
-        const zig_dir_path = try getZigDir(self.gpa());
-        defer self.gpa().free(zig_dir_path);
-
-        const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
-        try fs.cwd().makePath(tmp_dir);
-        return tmp_dir;
-    }
-
-    fn getRandomFileName(self: *Compilation) [12]u8 {
-        // here we replace the standard +/ with -_ so that it can be used in a file name
-        const b64_fs_encoder = std.base64.Base64Encoder.init(
-            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
-            std.base64.standard_pad_char,
-        );
-
-        var rand_bytes: [9]u8 = undefined;
-
-        {
-            const held = self.zig_compiler.prng.acquire();
-            defer held.release();
-
-            held.value.random.bytes(rand_bytes[0..]);
-        }
-
-        var result: [12]u8 = undefined;
-        b64_fs_encoder.encode(result[0..], &rand_bytes);
-        return result;
-    }
-
-    fn registerGarbage(comp: *Compilation, comptime T: type, node: *std.atomic.Stack(*T).Node) void {
-        // TODO put the garbage somewhere
-    }
-
-    /// Returns a value which has been ref()'d once
-    fn analyzeConstValue(
-        comp: *Compilation,
-        tree_scope: *Scope.AstTree,
-        scope: *Scope,
-        node: *ast.Node,
-        expected_type: *Type,
-    ) !*Value {
-        var frame = try comp.gpa().create(@Frame(genAndAnalyzeCode));
-        defer comp.gpa().destroy(frame);
-        frame.* = async comp.genAndAnalyzeCode(tree_scope, scope, node, expected_type);
-        const analyzed_code = try await frame;
-        defer analyzed_code.destroy(comp.gpa());
-
-        return analyzed_code.getCompTimeResult(comp);
-    }
-
-    fn analyzeTypeExpr(comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node) !*Type {
-        const meta_type = &Type.MetaType.get(comp).base;
-        defer meta_type.base.deref(comp);
-
-        const result_val = try comp.analyzeConstValue(tree_scope, scope, node, meta_type);
-        errdefer result_val.base.deref(comp);
-
-        return result_val.cast(Type).?;
-    }
-
-    /// This declaration has been blessed as going into the final code generation.
-    pub fn resolveDecl(comp: *Compilation, decl: *Decl) callconv(.Async) BuildError!void {
-        if (decl.resolution.start()) |ptr| return ptr.*;
-
-        decl.resolution.data = try generateDecl(comp, decl);
-        decl.resolution.resolve();
-        return decl.resolution.data;
-    }
-};
-
-fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib {
-    if (optional_token_index) |token_index| {
-        const token = tree.tokens.at(token_index);
-        assert(token.id == Token.Id.Keyword_pub);
-        return Visib.Pub;
-    } else {
-        return Visib.Private;
-    }
-}
-
-/// The function that actually does the generation.
-fn generateDecl(comp: *Compilation, decl: *Decl) !void {
-    switch (decl.id) {
-        .Var => @panic("TODO"),
-        .Fn => {
-            const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl);
-            return generateDeclFn(comp, fn_decl);
-        },
-        .CompTime => @panic("TODO"),
-    }
-}
-
-fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
-    const tree_scope = fn_decl.base.tree_scope;
-
-    const body_node = fn_decl.fn_proto.body_node orelse return generateDeclFnProto(comp, fn_decl);
-
-    const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope);
-    defer fndef_scope.base.deref(comp);
-
-    const fn_type = try analyzeFnType(comp, tree_scope, fn_decl.base.parent_scope, fn_decl.fn_proto);
-    defer fn_type.base.base.deref(comp);
-
-    var symbol_name = try std.ArrayListSentineled(u8, 0).init(comp.gpa(), fn_decl.base.name);
-    var symbol_name_consumed = false;
-    errdefer if (!symbol_name_consumed) symbol_name.deinit();
-
-    // The Decl.Fn owns the initial 1 reference count
-    const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name);
-    fn_decl.value = .{ .Fn = fn_val };
-    symbol_name_consumed = true;
-
-    // Define local parameter variables
-    for (fn_type.key.data.Normal.params) |param, i| {
-        //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i);
-        const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*);
-        const name_token = param_decl.name_token orelse {
-            try comp.addCompileError(tree_scope, Span{
-                .first = param_decl.firstToken(),
-                .last = param_decl.type_node.firstToken(),
-            }, "missing parameter name", .{});
-            return error.SemanticAnalysisFailed;
-        };
-        const param_name = tree_scope.tree.tokenSlice(name_token);
-
-        // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) {
-        //     add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
-        // }
-
-        // TODO check for shadowing
-
-        const var_scope = try Scope.Var.createParam(
-            comp,
-            fn_val.child_scope,
-            param_name,
-            &param_decl.base,
-            i,
-            param.typ,
-        );
-        fn_val.child_scope = &var_scope.base;
-
-        try fn_type.non_key.Normal.variable_list.append(var_scope);
-    }
-
-    var frame = try comp.gpa().create(@Frame(Compilation.genAndAnalyzeCode));
-    defer comp.gpa().destroy(frame);
-    frame.* = async comp.genAndAnalyzeCode(
-        tree_scope,
-        fn_val.child_scope,
-        body_node,
-        fn_type.key.data.Normal.return_type,
-    );
-    const analyzed_code = try await frame;
-    errdefer analyzed_code.destroy(comp.gpa());
-
-    assert(fn_val.block_scope != null);
-
-    // Kick off rendering to LLVM module, but it doesn't block the fn decl
-    // analysis from being complete.
-    try comp.prelink_group.call(codegen.renderToLlvm, .{ comp, fn_val, analyzed_code });
-    try comp.prelink_group.call(addFnToLinkSet, .{ comp, fn_val });
-}
-fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) callconv(.Async) Compilation.BuildError!void {
-    fn_val.base.ref();
-    defer fn_val.base.deref(comp);
-
-    fn_val.link_set_node.data = fn_val;
-
-    const held = comp.fn_link_set.acquire();
-    defer held.release();
-
-    held.value.append(fn_val.link_set_node);
-}
-
-fn getZigDir(allocator: *mem.Allocator) ![]u8 {
-    return fs.getAppDataDir(allocator, "zig");
-}
-
-fn analyzeFnType(
-    comp: *Compilation,
-    tree_scope: *Scope.AstTree,
-    scope: *Scope,
-    fn_proto: *ast.Node.FnProto,
-) !*Type.Fn {
-    const return_type_node = switch (fn_proto.return_type) {
-        .Explicit => |n| n,
-        .InferErrorSet => |n| n,
-    };
-    const return_type = try comp.analyzeTypeExpr(tree_scope, scope, return_type_node);
-    return_type.base.deref(comp);
-
-    var params = ArrayList(Type.Fn.Param).init(comp.gpa());
-    var params_consumed = false;
-    defer if (!params_consumed) {
-        for (params.span()) |param| {
-            param.typ.base.deref(comp);
-        }
-        params.deinit();
-    };
-
-    {
-        var it = fn_proto.params.iterator(0);
-        while (it.next()) |param_node_ptr| {
-            const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?;
-            const param_type = try comp.analyzeTypeExpr(tree_scope, scope, param_node.type_node);
-            errdefer param_type.base.deref(comp);
-            try params.append(Type.Fn.Param{
-                .typ = param_type,
-                .is_noalias = param_node.noalias_token != null,
-            });
-        }
-    }
-
-    const key = Type.Fn.Key{
-        .alignment = null,
-        .data = Type.Fn.Key.Data{
-            .Normal = Type.Fn.Key.Normal{
-                .return_type = return_type,
-                .params = params.toOwnedSlice(),
-                .is_var_args = false, // TODO
-                .cc = .Unspecified, // TODO
-            },
-        },
-    };
-    params_consumed = true;
-    var key_consumed = false;
-    defer if (!key_consumed) {
-        for (key.data.Normal.params) |param| {
-            param.typ.base.deref(comp);
-        }
-        comp.gpa().free(key.data.Normal.params);
-    };
-
-    const fn_type = try Type.Fn.get(comp, key);
-    key_consumed = true;
-    errdefer fn_type.base.base.deref(comp);
-
-    return fn_type;
-}
-
-fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void {
-    const fn_type = try analyzeFnType(
-        comp,
-        fn_decl.base.tree_scope,
-        fn_decl.base.parent_scope,
-        fn_decl.fn_proto,
-    );
-    defer fn_type.base.base.deref(comp);
-
-    var symbol_name = try std.ArrayListSentineled(u8, 0).init(comp.gpa(), fn_decl.base.name);
-    var symbol_name_consumed = false;
-    defer if (!symbol_name_consumed) symbol_name.deinit();
-
-    // The Decl.Fn owns the initial 1 reference count
-    const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name);
-    fn_decl.value = .{ .FnProto = fn_proto_val };
-    symbol_name_consumed = true;
-}
-
-pub fn llvmTargetFromTriple(triple: [:0]const u8) !*llvm.Target {
-    var result: *llvm.Target = undefined;
-    var err_msg: [*:0]u8 = undefined;
-    if (llvm.GetTargetFromTriple(triple, &result, &err_msg) != 0) {
-        std.debug.warn("triple: {s} error: {s}\n", .{ triple, err_msg });
-        return error.UnsupportedTarget;
-    }
-    return result;
-}
-
-pub fn initializeAllTargets() void {
-    llvm.InitializeAllTargets();
-    llvm.InitializeAllTargetInfos();
-    llvm.InitializeAllTargetMCs();
-    llvm.InitializeAllAsmPrinters();
-    llvm.InitializeAllAsmParsers();
-}
-
-pub fn getLLVMTriple(allocator: *std.mem.Allocator, target: std.Target) ![:0]u8 {
-    var result = try std.ArrayListSentineled(u8, 0).initSize(allocator, 0);
-    defer result.deinit();
-
-    try result.outStream().print(
-        "{}-unknown-{}-{}",
-        .{ @tagName(target.cpu.arch), @tagName(target.os.tag), @tagName(target.abi) },
-    );
-
-    return result.toOwnedSlice();
-}
src-self-hosted/errmsg.zig
@@ -1,284 +0,0 @@
-const std = @import("std");
-const mem = std.mem;
-const fs = std.fs;
-const process = std.process;
-const Token = std.zig.Token;
-const ast = std.zig.ast;
-const TokenIndex = std.zig.ast.TokenIndex;
-const Compilation = @import("compilation.zig").Compilation;
-const Scope = @import("scope.zig").Scope;
-
-pub const Color = enum {
-    Auto,
-    Off,
-    On,
-};
-
-pub const Span = struct {
-    first: ast.TokenIndex,
-    last: ast.TokenIndex,
-
-    pub fn token(i: TokenIndex) Span {
-        return Span{
-            .first = i,
-            .last = i,
-        };
-    }
-
-    pub fn node(n: *ast.Node) Span {
-        return Span{
-            .first = n.firstToken(),
-            .last = n.lastToken(),
-        };
-    }
-};
-
-pub const Msg = struct {
-    text: []u8,
-    realpath: []u8,
-    data: Data,
-
-    const Data = union(enum) {
-        Cli: Cli,
-        PathAndTree: PathAndTree,
-        ScopeAndComp: ScopeAndComp,
-    };
-
-    const PathAndTree = struct {
-        span: Span,
-        tree: *ast.Tree,
-        allocator: *mem.Allocator,
-    };
-
-    const ScopeAndComp = struct {
-        span: Span,
-        tree_scope: *Scope.AstTree,
-        compilation: *Compilation,
-    };
-
-    const Cli = struct {
-        allocator: *mem.Allocator,
-    };
-
-    pub fn destroy(self: *Msg) void {
-        switch (self.data) {
-            .Cli => |cli| {
-                cli.allocator.free(self.text);
-                cli.allocator.free(self.realpath);
-                cli.allocator.destroy(self);
-            },
-            .PathAndTree => |path_and_tree| {
-                path_and_tree.allocator.free(self.text);
-                path_and_tree.allocator.free(self.realpath);
-                path_and_tree.allocator.destroy(self);
-            },
-            .ScopeAndComp => |scope_and_comp| {
-                scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation);
-                scope_and_comp.compilation.gpa().free(self.text);
-                scope_and_comp.compilation.gpa().free(self.realpath);
-                scope_and_comp.compilation.gpa().destroy(self);
-            },
-        }
-    }
-
-    fn getAllocator(self: *const Msg) *mem.Allocator {
-        switch (self.data) {
-            .Cli => |cli| return cli.allocator,
-            .PathAndTree => |path_and_tree| {
-                return path_and_tree.allocator;
-            },
-            .ScopeAndComp => |scope_and_comp| {
-                return scope_and_comp.compilation.gpa();
-            },
-        }
-    }
-
-    pub fn getTree(self: *const Msg) *ast.Tree {
-        switch (self.data) {
-            .Cli => unreachable,
-            .PathAndTree => |path_and_tree| {
-                return path_and_tree.tree;
-            },
-            .ScopeAndComp => |scope_and_comp| {
-                return scope_and_comp.tree_scope.tree;
-            },
-        }
-    }
-
-    pub fn getSpan(self: *const Msg) Span {
-        return switch (self.data) {
-            .Cli => unreachable,
-            .PathAndTree => |path_and_tree| path_and_tree.span,
-            .ScopeAndComp => |scope_and_comp| scope_and_comp.span,
-        };
-    }
-
-    /// Takes ownership of text
-    /// References tree_scope, and derefs when the msg is freed
-    pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg {
-        const realpath = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath);
-        errdefer comp.gpa().free(realpath);
-
-        const msg = try comp.gpa().create(Msg);
-        msg.* = Msg{
-            .text = text,
-            .realpath = realpath,
-            .data = Data{
-                .ScopeAndComp = ScopeAndComp{
-                    .tree_scope = tree_scope,
-                    .compilation = comp,
-                    .span = span,
-                },
-            },
-        };
-        tree_scope.base.ref();
-        return msg;
-    }
-
-    /// Caller owns returned Msg and must free with `allocator`
-    /// allocator will additionally be used for printing messages later.
-    pub fn createFromCli(comp: *Compilation, realpath: []const u8, text: []u8) !*Msg {
-        const realpath_copy = try mem.dupe(comp.gpa(), u8, realpath);
-        errdefer comp.gpa().free(realpath_copy);
-
-        const msg = try comp.gpa().create(Msg);
-        msg.* = Msg{
-            .text = text,
-            .realpath = realpath_copy,
-            .data = Data{
-                .Cli = Cli{ .allocator = comp.gpa() },
-            },
-        };
-        return msg;
-    }
-
-    pub fn createFromParseErrorAndScope(
-        comp: *Compilation,
-        tree_scope: *Scope.AstTree,
-        parse_error: *const ast.Error,
-    ) !*Msg {
-        const loc_token = parse_error.loc();
-        var text_buf = std.ArrayList(u8).init(comp.gpa());
-        defer text_buf.deinit();
-
-        const realpath_copy = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath);
-        errdefer comp.gpa().free(realpath_copy);
-
-        try parse_error.render(&tree_scope.tree.tokens, text_buf.outStream());
-
-        const msg = try comp.gpa().create(Msg);
-        msg.* = Msg{
-            .text = undefined,
-            .realpath = realpath_copy,
-            .data = Data{
-                .ScopeAndComp = ScopeAndComp{
-                    .tree_scope = tree_scope,
-                    .compilation = comp,
-                    .span = Span{
-                        .first = loc_token,
-                        .last = loc_token,
-                    },
-                },
-            },
-        };
-        tree_scope.base.ref();
-        msg.text = text_buf.toOwnedSlice();
-        return msg;
-    }
-
-    /// `realpath` must outlive the returned Msg
-    /// `tree` must outlive the returned Msg
-    /// Caller owns returned Msg and must free with `allocator`
-    /// allocator will additionally be used for printing messages later.
-    pub fn createFromParseError(
-        allocator: *mem.Allocator,
-        parse_error: *const ast.Error,
-        tree: *ast.Tree,
-        realpath: []const u8,
-    ) !*Msg {
-        const loc_token = parse_error.loc();
-        var text_buf = std.ArrayList(u8).init(allocator);
-        defer text_buf.deinit();
-
-        const realpath_copy = try mem.dupe(allocator, u8, realpath);
-        errdefer allocator.free(realpath_copy);
-
-        try parse_error.render(&tree.tokens, text_buf.outStream());
-
-        const msg = try allocator.create(Msg);
-        msg.* = Msg{
-            .text = undefined,
-            .realpath = realpath_copy,
-            .data = Data{
-                .PathAndTree = PathAndTree{
-                    .allocator = allocator,
-                    .tree = tree,
-                    .span = Span{
-                        .first = loc_token,
-                        .last = loc_token,
-                    },
-                },
-            },
-        };
-        msg.text = text_buf.toOwnedSlice();
-        errdefer allocator.destroy(msg);
-
-        return msg;
-    }
-
-    pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void {
-        switch (msg.data) {
-            .Cli => {
-                try stream.print("{}:-:-: error: {}\n", .{ msg.realpath, msg.text });
-                return;
-            },
-            else => {},
-        }
-
-        const allocator = msg.getAllocator();
-        const tree = msg.getTree();
-
-        const cwd = try process.getCwdAlloc(allocator);
-        defer allocator.free(cwd);
-
-        const relpath = try fs.path.relative(allocator, cwd, msg.realpath);
-        defer allocator.free(relpath);
-
-        const path = if (relpath.len < msg.realpath.len) relpath else msg.realpath;
-        const span = msg.getSpan();
-
-        const first_token = tree.tokens.at(span.first);
-        const last_token = tree.tokens.at(span.last);
-        const start_loc = tree.tokenLocationPtr(0, first_token);
-        const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
-        if (!color_on) {
-            try stream.print("{}:{}:{}: error: {}\n", .{
-                path,
-                start_loc.line + 1,
-                start_loc.column + 1,
-                msg.text,
-            });
-            return;
-        }
-
-        try stream.print("{}:{}:{}: error: {}\n{}\n", .{
-            path,
-            start_loc.line + 1,
-            start_loc.column + 1,
-            msg.text,
-            tree.source[start_loc.line_start..start_loc.line_end],
-        });
-        try stream.writeByteNTimes(' ', start_loc.column);
-        try stream.writeByteNTimes('~', last_token.end - first_token.start);
-        try stream.writeAll("\n");
-    }
-
-    pub fn printToFile(msg: *const Msg, file: fs.File, color: Color) !void {
-        const color_on = switch (color) {
-            .Auto => file.isTty(),
-            .On => true,
-            .Off => false,
-        };
-        return msg.printToStream(file.outStream(), color_on);
-    }
-};
src-self-hosted/ir.zig
@@ -922,7 +922,6 @@ pub const Module = struct {
         if (self.decl_table.get(hash)) |kv| {
             return kv.value;
         } else {
-            std.debug.warn("creating new decl for {}\n", .{old_inst.name});
             const new_decl = blk: {
                 try self.decl_table.ensureCapacity(self.decl_table.size + 1);
                 const new_decl = try self.allocator.create(Decl);
@@ -2161,101 +2160,3 @@ pub const ErrorMsg = struct {
         self.* = undefined;
     }
 };
-
-pub fn main() anyerror!void {
-    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-    defer arena.deinit();
-    const allocator = if (std.builtin.link_libc) std.heap.c_allocator else &arena.allocator;
-
-    const args = try std.process.argsAlloc(allocator);
-    defer std.process.argsFree(allocator, args);
-
-    const src_path = args[1];
-    const bin_path = args[2];
-    const debug_error_trace = false;
-    const output_zir = false;
-    const object_format: ?std.builtin.ObjectFormat = null;
-
-    const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
-
-    var bin_file = try link.openBinFilePath(allocator, std.fs.cwd(), bin_path, .{
-        .target = native_info.target,
-        .output_mode = .Exe,
-        .link_mode = .Static,
-        .object_format = object_format orelse native_info.target.getObjectFormat(),
-    });
-    defer bin_file.deinit();
-
-    var module = blk: {
-        const root_pkg = try Package.create(allocator, std.fs.cwd(), ".", src_path);
-        errdefer root_pkg.destroy();
-
-        const root_scope = try allocator.create(Module.Scope.ZIRModule);
-        errdefer allocator.destroy(root_scope);
-        root_scope.* = .{
-            .sub_file_path = root_pkg.root_src_path,
-            .source = .{ .unloaded = {} },
-            .contents = .{ .not_available = {} },
-            .status = .never_loaded,
-        };
-
-        break :blk Module{
-            .allocator = allocator,
-            .root_pkg = root_pkg,
-            .root_scope = root_scope,
-            .bin_file = &bin_file,
-            .optimize_mode = .Debug,
-            .decl_table = std.AutoHashMap(Module.Decl.Hash, *Module.Decl).init(allocator),
-            .decl_exports = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator),
-            .export_owners = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator),
-            .failed_decls = std.AutoHashMap(*Module.Decl, *ErrorMsg).init(allocator),
-            .failed_files = std.AutoHashMap(*Module.Scope.ZIRModule, *ErrorMsg).init(allocator),
-            .failed_exports = std.AutoHashMap(*Module.Export, *ErrorMsg).init(allocator),
-            .work_queue = std.fifo.LinearFifo(Module.WorkItem, .Dynamic).init(allocator),
-        };
-    };
-    defer module.deinit();
-
-    const stdin = std.io.getStdIn().inStream();
-    const stderr = std.io.getStdErr().outStream();
-    var repl_buf: [1024]u8 = undefined;
-
-    while (true) {
-        try module.update();
-
-        var errors = try module.getAllErrorsAlloc();
-        defer errors.deinit(allocator);
-
-        if (errors.list.len != 0) {
-            for (errors.list) |full_err_msg| {
-                std.debug.warn("{}:{}:{}: error: {}\n", .{
-                    full_err_msg.src_path,
-                    full_err_msg.line + 1,
-                    full_err_msg.column + 1,
-                    full_err_msg.msg,
-                });
-            }
-            if (debug_error_trace) return error.AnalysisFail;
-        }
-
-        try stderr.print("๐ŸฆŽ ", .{});
-        if (try stdin.readUntilDelimiterOrEof(&repl_buf, '\n')) |line| {
-            if (mem.eql(u8, line, "update")) {
-                continue;
-            } else {
-                try stderr.print("unknown command: {}\n", .{line});
-            }
-        } else {
-            break;
-        }
-    }
-
-    if (output_zir) {
-        var new_zir_module = try text.emit_zir(allocator, module);
-        defer new_zir_module.deinit(allocator);
-
-        var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream());
-        try new_zir_module.writeToStream(allocator, bos.outStream());
-        try bos.flush();
-    }
-}
src-self-hosted/main.zig
@@ -1,29 +1,30 @@
 const std = @import("std");
-const builtin = @import("builtin");
-
-const event = std.event;
-const os = std.os;
 const io = std.io;
 const fs = std.fs;
 const mem = std.mem;
 const process = std.process;
 const Allocator = mem.Allocator;
 const ArrayList = std.ArrayList;
+const ast = std.zig.ast;
+const ir = @import("ir.zig");
+const link = @import("link.zig");
+const Package = @import("Package.zig");
 
-const c = @import("c.zig");
-const introspect = @import("introspect.zig");
-const ZigCompiler = @import("compilation.zig").ZigCompiler;
-const Compilation = @import("compilation.zig").Compilation;
-const Target = std.Target;
-const errmsg = @import("errmsg.zig");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
-pub const io_mode = .evented;
+// TODO Improve async I/O enough that we feel comfortable doing this.
+//pub const io_mode = .evented;
 
 pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
 
+pub const Color = enum {
+    Auto,
+    Off,
+    On,
+};
+
 const usage =
-    \\usage: zig [command] [options]
+    \\Usage: zig [command] [options]
     \\
     \\Commands:
     \\
@@ -39,175 +40,154 @@ const usage =
     \\
 ;
 
-const Command = struct {
-    name: []const u8,
-    exec: async fn (*Allocator, []const []const u8) anyerror!void,
-};
-
 pub fn main() !void {
-    const allocator = std.heap.c_allocator;
-
-    const stderr = io.getStdErr().outStream();
+    // TODO general purpose allocator in the zig std lib
+    const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator;
+    var arena_instance = std.heap.ArenaAllocator.init(gpa);
+    defer arena_instance.deinit();
+    const arena = &arena_instance.allocator;
 
-    const args = try process.argsAlloc(allocator);
-    defer process.argsFree(allocator, args);
+    const args = try process.argsAlloc(arena);
 
     if (args.len <= 1) {
-        try stderr.writeAll("expected command argument\n\n");
-        try stderr.writeAll(usage);
+        std.debug.warn("expected command argument\n\n{}", .{usage});
         process.exit(1);
     }
 
     const cmd = args[1];
     const cmd_args = args[2..];
     if (mem.eql(u8, cmd, "build-exe")) {
-        return buildOutputType(allocator, cmd_args, .Exe);
+        return buildOutputType(gpa, arena, cmd_args, .Exe);
     } else if (mem.eql(u8, cmd, "build-lib")) {
-        return buildOutputType(allocator, cmd_args, .Lib);
+        return buildOutputType(gpa, arena, cmd_args, .Lib);
     } else if (mem.eql(u8, cmd, "build-obj")) {
-        return buildOutputType(allocator, cmd_args, .Obj);
+        return buildOutputType(gpa, arena, cmd_args, .Obj);
     } else if (mem.eql(u8, cmd, "fmt")) {
-        return cmdFmt(allocator, cmd_args);
+        return cmdFmt(gpa, cmd_args);
     } else if (mem.eql(u8, cmd, "libc")) {
-        return cmdLibC(allocator, cmd_args);
+        return cmdLibC(gpa, cmd_args);
     } else if (mem.eql(u8, cmd, "targets")) {
-        const info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
+        const info = try std.zig.system.NativeTargetInfo.detect(arena, .{});
         const stdout = io.getStdOut().outStream();
-        return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, info.target);
+        return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
     } else if (mem.eql(u8, cmd, "version")) {
-        return cmdVersion(allocator, cmd_args);
+        // Need to set up the build script to give the version as a comptime value.
+        std.debug.warn("TODO version command not implemented yet\n", .{});
+        return error.Unimplemented;
     } else if (mem.eql(u8, cmd, "zen")) {
-        return cmdZen(allocator, cmd_args);
+        try io.getStdOut().writeAll(info_zen);
     } else if (mem.eql(u8, cmd, "help")) {
-        return cmdHelp(allocator, cmd_args);
-    } else if (mem.eql(u8, cmd, "internal")) {
-        return cmdInternal(allocator, cmd_args);
+        try io.getStdOut().writeAll(usage);
     } else {
-        try stderr.print("unknown command: {}\n\n", .{args[1]});
-        try stderr.writeAll(usage);
+        std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage });
         process.exit(1);
     }
 }
 
 const usage_build_generic =
-    \\usage: zig build-exe <options> [file]
-    \\       zig build-lib <options> [file]
-    \\       zig build-obj <options> [file]
+    \\Usage: zig build-exe <options> [files]
+    \\       zig build-lib <options> [files]
+    \\       zig build-obj <options> [files]
+    \\
+    \\Supported file types:
+    \\     (planned)      .zig    Zig source code
+    \\                    .zir    Zig Intermediate Representation code
+    \\     (planned)        .o    ELF object file
+    \\     (planned)        .o    MACH-O (macOS) object file
+    \\     (planned)      .obj    COFF (Windows) object file
+    \\     (planned)      .lib    COFF (Windows) static library
+    \\     (planned)        .a    ELF static library
+    \\     (planned)       .so    ELF shared object (dynamic link)
+    \\     (planned)      .dll    Windows Dynamic Link Library
+    \\     (planned)    .dylib    MACH-O (macOS) dynamic library
+    \\     (planned)        .s    Target-specific assembly source code
+    \\     (planned)        .S    Assembly with C preprocessor (requires LLVM extensions)
+    \\     (planned)        .c    C source code (requires LLVM extensions)
+    \\     (planned)      .cpp    C++ source code (requires LLVM extensions)
+    \\                            Other C++ extensions: .C .cc .cxx
     \\
     \\General Options:
-    \\  --help                       Print this help and exit
-    \\  --color [auto|off|on]        Enable or disable colored error messages
+    \\  -h, --help                Print this help and exit
+    \\  --watch                   Enable compiler REPL
+    \\  --color [auto|off|on]     Enable or disable colored error messages
+    \\  -femit-bin[=path]         (default) output machine code
+    \\  -fno-emit-bin             Do not output machine code
     \\
     \\Compile Options:
-    \\  --libc [file]                Provide a file which specifies libc paths
-    \\  --assembly [source]          Add assembly file to build
-    \\  --emit [filetype]            Emit a specific file format as compilation output
-    \\  --enable-timing-info         Print timing diagnostics
-    \\  --name [name]                Override output name
-    \\  --output [file]              Override destination path
-    \\  --output-h [file]            Override generated header file path
-    \\  --pkg-begin [name] [path]    Make package available to import and push current pkg
-    \\  --pkg-end                    Pop current pkg
-    \\  --mode [mode]                Set the build mode
-    \\    debug                      (default) optimizations off, safety on
-    \\    release-fast               optimizations on, safety off
-    \\    release-safe               optimizations on, safety on
-    \\    release-small              optimize for small binary, safety off
-    \\  --static                     Output will be statically linked
-    \\  --strip                      Exclude debug symbols
-    \\  -target [name]               <arch><sub>-<os>-<abi> see the targets command
-    \\  --eh-frame-hdr               enable C++ exception handling by passing --eh-frame-hdr to linker
-    \\  --verbose-tokenize           Turn on compiler debug output for tokenization
-    \\  --verbose-ast-tree           Turn on compiler debug output for parsing into an AST (tree view)
-    \\  --verbose-ast-fmt            Turn on compiler debug output for parsing into an AST (render source)
-    \\  --verbose-link               Turn on compiler debug output for linking
-    \\  --verbose-ir                 Turn on compiler debug output for Zig IR
-    \\  --verbose-llvm-ir            Turn on compiler debug output for LLVM IR
-    \\  --verbose-cimport            Turn on compiler debug output for C imports
-    \\  -dirafter [dir]              Same as -isystem but do it last
-    \\  -isystem [dir]               Add additional search path for other .h files
-    \\  -mllvm [arg]                 Additional arguments to forward to LLVM's option processing
+    \\  -target [name]            <arch><sub>-<os>-<abi> see the targets command
+    \\  -mcpu [cpu]               Specify target CPU and feature set
+    \\  --name [name]             Override output name
+    \\  --mode [mode]             Set the build mode
+    \\    Debug                   (default) optimizations off, safety on
+    \\    ReleaseFast             optimizations on, safety off
+    \\    ReleaseSafe             optimizations on, safety on
+    \\    ReleaseSmall            optimize for small binary, safety off
+    \\  --dynamic                 Force output to be dynamically linked
+    \\  --strip                   Exclude debug symbols
     \\
     \\Link Options:
-    \\  --ar-path [path]             Set the path to ar
-    \\  --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
-    \\  --library-path [dir]         Add a directory to the library search path
-    \\  --linker-script [path]       Use a custom linker script
-    \\  --object [obj]               Add object file to build
-    \\  -rdynamic                    Add all symbols to the dynamic symbol table
-    \\  -rpath [path]                Add directory to the runtime library search path
-    \\  -framework [name]            (darwin) link against framework
-    \\  -mios-version-min [ver]      (darwin) set iOS deployment target
-    \\  -mmacosx-version-min [ver]   (darwin) set Mac OS X deployment target
-    \\  --ver-major [ver]            Dynamic library semver major version
-    \\  --ver-minor [ver]            Dynamic library semver minor version
-    \\  --ver-patch [ver]            Dynamic library semver patch version
+    \\  -l[lib], --library [lib]  Link against system library
+    \\  --dynamic-linker [path]   Set the dynamic interpreter path (usually ld.so)
+    \\  --version [ver]           Dynamic library semver
     \\
+    \\Debug Options (Zig Compiler Development):
+    \\  -ftime-report             Print timing diagnostics
+    \\  --debug-tokenize          verbose tokenization
+    \\  --debug-ast-tree          verbose parsing into an AST (tree view)
+    \\  --debug-ast-fmt           verbose parsing into an AST (render source)
+    \\  --debug-ir                verbose Zig IR
+    \\  --debug-link              verbose linking
+    \\  --debug-codegen           verbose machine code generation
     \\
 ;
 
-fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Compilation.Kind) !void {
-    const stderr = io.getStdErr().outStream();
+const Emit = union(enum) {
+    no,
+    yes_default_path,
+    yes: []const u8,
+};
 
-    var color: errmsg.Color = .Auto;
+fn buildOutputType(
+    gpa: *Allocator,
+    arena: *Allocator,
+    args: []const []const u8,
+    output_mode: std.builtin.OutputMode,
+) !void {
+    var color: Color = .Auto;
     var build_mode: std.builtin.Mode = .Debug;
-    var emit_bin = true;
-    var emit_asm = false;
-    var emit_llvm_ir = false;
-    var emit_h = false;
     var provided_name: ?[]const u8 = null;
     var is_dynamic = false;
     var root_src_file: ?[]const u8 = null;
-    var libc_arg: ?[]const u8 = null;
     var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 };
-    var linker_script: ?[]const u8 = null;
     var strip = false;
-    var verbose_tokenize = false;
-    var verbose_ast_tree = false;
-    var verbose_ast_fmt = false;
-    var verbose_link = false;
-    var verbose_ir = false;
-    var verbose_llvm_ir = false;
-    var verbose_cimport = false;
-    var linker_rdynamic = false;
-    var link_eh_frame_hdr = false;
-    var macosx_version_min: ?[]const u8 = null;
-    var ios_version_min: ?[]const u8 = null;
-
-    var assembly_files = ArrayList([]const u8).init(allocator);
-    defer assembly_files.deinit();
-
-    var link_objects = ArrayList([]const u8).init(allocator);
-    defer link_objects.deinit();
-
-    var clang_argv_buf = ArrayList([]const u8).init(allocator);
-    defer clang_argv_buf.deinit();
-
-    var mllvm_flags = ArrayList([]const u8).init(allocator);
-    defer mllvm_flags.deinit();
-
-    var cur_pkg = try CliPkg.init(allocator, "", "", null);
-    defer cur_pkg.deinit();
-
-    var system_libs = ArrayList([]const u8).init(allocator);
+    var watch = false;
+    var debug_tokenize = false;
+    var debug_ast_tree = false;
+    var debug_ast_fmt = false;
+    var debug_link = false;
+    var debug_ir = false;
+    var debug_codegen = false;
+    var time_report = false;
+    var emit_bin: Emit = .yes_default_path;
+    var emit_zir: Emit = .no;
+    var target_arch_os_abi: []const u8 = "native";
+    var target_mcpu: ?[]const u8 = null;
+    var target_dynamic_linker: ?[]const u8 = null;
+
+    var system_libs = std.ArrayList([]const u8).init(gpa);
     defer system_libs.deinit();
 
-    var c_src_files = ArrayList([]const u8).init(allocator);
-    defer c_src_files.deinit();
-
     {
         var i: usize = 0;
         while (i < args.len) : (i += 1) {
             const arg = args[i];
             if (mem.startsWith(u8, arg, "-")) {
-                if (mem.eql(u8, arg, "--help")) {
+                if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
                     try io.getStdOut().writeAll(usage_build_generic);
                     process.exit(0);
                 } else if (mem.eql(u8, arg, "--color")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected [auto|on|off] after --color\n");
+                        std.debug.warn("expected [auto|on|off] after --color\n", .{});
                         process.exit(1);
                     }
                     i += 1;
@@ -219,12 +199,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
                     } else if (mem.eql(u8, next_arg, "off")) {
                         color = .Off;
                     } else {
-                        try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
+                        std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
                         process.exit(1);
                     }
                 } else if (mem.eql(u8, arg, "--mode")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n");
+                        std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
                         process.exit(1);
                     }
                     i += 1;
@@ -238,289 +218,317 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
                     } else if (mem.eql(u8, next_arg, "ReleaseSmall")) {
                         build_mode = .ReleaseSmall;
                     } else {
-                        try stderr.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
+                        std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
                         process.exit(1);
                     }
                 } else if (mem.eql(u8, arg, "--name")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after --name\n");
+                        std.debug.warn("expected parameter after --name\n", .{});
                         process.exit(1);
                     }
                     i += 1;
                     provided_name = args[i];
-                } else if (mem.eql(u8, arg, "--ver-major")) {
-                    if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after --ver-major\n");
-                        process.exit(1);
-                    }
-                    i += 1;
-                    version.major = try std.fmt.parseInt(u32, args[i], 10);
-                } else if (mem.eql(u8, arg, "--ver-minor")) {
+                } else if (mem.eql(u8, arg, "--library")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after --ver-minor\n");
+                        std.debug.warn("expected parameter after --library\n", .{});
                         process.exit(1);
                     }
                     i += 1;
-                    version.minor = try std.fmt.parseInt(u32, args[i], 10);
-                } else if (mem.eql(u8, arg, "--ver-patch")) {
+                    try system_libs.append(args[i]);
+                } else if (mem.eql(u8, arg, "--version")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after --ver-patch\n");
+                        std.debug.warn("expected parameter after --version\n", .{});
                         process.exit(1);
                     }
                     i += 1;
-                    version.patch = try std.fmt.parseInt(u32, args[i], 10);
-                } else if (mem.eql(u8, arg, "--linker-script")) {
-                    if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after --linker-script\n");
-                        process.exit(1);
-                    }
-                    i += 1;
-                    linker_script = args[i];
-                } else if (mem.eql(u8, arg, "--libc")) {
-                    if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after --libc\n");
+                    version = std.builtin.Version.parse(args[i]) catch |err| {
+                        std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
                         process.exit(1);
-                    }
-                    i += 1;
-                    libc_arg = args[i];
-                } else if (mem.eql(u8, arg, "-mllvm")) {
+                    };
+                } else if (mem.eql(u8, arg, "-target")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after -mllvm\n");
+                        std.debug.warn("expected parameter after -target\n", .{});
                         process.exit(1);
                     }
                     i += 1;
-                    try clang_argv_buf.append("-mllvm");
-                    try clang_argv_buf.append(args[i]);
-
-                    try mllvm_flags.append(args[i]);
-                } else if (mem.eql(u8, arg, "-mmacosx-version-min")) {
+                    target_arch_os_abi = args[i];
+                } else if (mem.eql(u8, arg, "-mcpu")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after -mmacosx-version-min\n");
+                        std.debug.warn("expected parameter after -mcpu\n", .{});
                         process.exit(1);
                     }
                     i += 1;
-                    macosx_version_min = args[i];
-                } else if (mem.eql(u8, arg, "-mios-version-min")) {
+                    target_mcpu = args[i];
+                } else if (mem.startsWith(u8, arg, "-mcpu=")) {
+                    target_mcpu = arg["-mcpu=".len..];
+                } else if (mem.eql(u8, arg, "--dynamic-linker")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected parameter after -mios-version-min\n");
+                        std.debug.warn("expected parameter after --dynamic-linker\n", .{});
                         process.exit(1);
                     }
                     i += 1;
-                    ios_version_min = args[i];
+                    target_dynamic_linker = args[i];
+                } else if (mem.eql(u8, arg, "--watch")) {
+                    watch = true;
+                } else if (mem.eql(u8, arg, "-ftime-report")) {
+                    time_report = true;
                 } else if (mem.eql(u8, arg, "-femit-bin")) {
-                    emit_bin = true;
+                    emit_bin = .yes_default_path;
+                } else if (mem.startsWith(u8, arg, "-femit-bin=")) {
+                    emit_bin = .{ .yes = arg["-femit-bin=".len..] };
                 } else if (mem.eql(u8, arg, "-fno-emit-bin")) {
-                    emit_bin = false;
-                } else if (mem.eql(u8, arg, "-femit-asm")) {
-                    emit_asm = true;
-                } else if (mem.eql(u8, arg, "-fno-emit-asm")) {
-                    emit_asm = false;
-                } else if (mem.eql(u8, arg, "-femit-llvm-ir")) {
-                    emit_llvm_ir = true;
-                } else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) {
-                    emit_llvm_ir = false;
+                    emit_bin = .no;
+                } else if (mem.eql(u8, arg, "-femit-zir")) {
+                    emit_zir = .yes_default_path;
+                } else if (mem.startsWith(u8, arg, "-femit-zir=")) {
+                    emit_zir = .{ .yes = arg["-femit-zir=".len..] };
+                } else if (mem.eql(u8, arg, "-fno-emit-zir")) {
+                    emit_zir = .no;
                 } else if (mem.eql(u8, arg, "-dynamic")) {
                     is_dynamic = true;
                 } else if (mem.eql(u8, arg, "--strip")) {
                     strip = true;
-                } else if (mem.eql(u8, arg, "--verbose-tokenize")) {
-                    verbose_tokenize = true;
-                } else if (mem.eql(u8, arg, "--verbose-ast-tree")) {
-                    verbose_ast_tree = true;
-                } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) {
-                    verbose_ast_fmt = true;
-                } else if (mem.eql(u8, arg, "--verbose-link")) {
-                    verbose_link = true;
-                } else if (mem.eql(u8, arg, "--verbose-ir")) {
-                    verbose_ir = true;
-                } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
-                    verbose_llvm_ir = true;
-                } else if (mem.eql(u8, arg, "--eh-frame-hdr")) {
-                    link_eh_frame_hdr = true;
-                } else if (mem.eql(u8, arg, "--verbose-cimport")) {
-                    verbose_cimport = true;
-                } else if (mem.eql(u8, arg, "-rdynamic")) {
-                    linker_rdynamic = true;
-                } else if (mem.eql(u8, arg, "--pkg-begin")) {
-                    if (i + 2 >= args.len) {
-                        try stderr.writeAll("expected [name] [path] after --pkg-begin\n");
-                        process.exit(1);
-                    }
-                    i += 1;
-                    const new_pkg_name = args[i];
-                    i += 1;
-                    const new_pkg_path = args[i];
-
-                    var new_cur_pkg = try CliPkg.init(allocator, new_pkg_name, new_pkg_path, cur_pkg);
-                    try cur_pkg.children.append(new_cur_pkg);
-                    cur_pkg = new_cur_pkg;
-                } else if (mem.eql(u8, arg, "--pkg-end")) {
-                    if (cur_pkg.parent) |parent| {
-                        cur_pkg = parent;
-                    } else {
-                        try stderr.writeAll("encountered --pkg-end with no matching --pkg-begin\n");
-                        process.exit(1);
-                    }
+                } else if (mem.eql(u8, arg, "--debug-tokenize")) {
+                    debug_tokenize = true;
+                } else if (mem.eql(u8, arg, "--debug-ast-tree")) {
+                    debug_ast_tree = true;
+                } else if (mem.eql(u8, arg, "--debug-ast-fmt")) {
+                    debug_ast_fmt = true;
+                } else if (mem.eql(u8, arg, "--debug-link")) {
+                    debug_link = true;
+                } else if (mem.eql(u8, arg, "--debug-ir")) {
+                    debug_ir = true;
+                } else if (mem.eql(u8, arg, "--debug-codegen")) {
+                    debug_codegen = true;
                 } else if (mem.startsWith(u8, arg, "-l")) {
                     try system_libs.append(arg[2..]);
                 } else {
-                    try stderr.print("unrecognized parameter: '{}'", .{arg});
+                    std.debug.warn("unrecognized parameter: '{}'", .{arg});
                     process.exit(1);
                 }
-            } else if (mem.endsWith(u8, arg, ".s")) {
-                try assembly_files.append(arg);
+            } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {
+                std.debug.warn("assembly files not supported yet", .{});
+                process.exit(1);
             } else if (mem.endsWith(u8, arg, ".o") or
                 mem.endsWith(u8, arg, ".obj") or
                 mem.endsWith(u8, arg, ".a") or
                 mem.endsWith(u8, arg, ".lib"))
             {
-                try link_objects.append(arg);
+                std.debug.warn("object files and static libraries not supported yet", .{});
+                process.exit(1);
             } else if (mem.endsWith(u8, arg, ".c") or
                 mem.endsWith(u8, arg, ".cpp"))
             {
-                try c_src_files.append(arg);
-            } else if (mem.endsWith(u8, arg, ".zig")) {
+                std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});
+                process.exit(1);
+            } else if (mem.endsWith(u8, arg, ".so") or
+                mem.endsWith(u8, arg, ".dylib") or
+                mem.endsWith(u8, arg, ".dll"))
+            {
+                std.debug.warn("linking against dynamic libraries not yet supported", .{});
+                process.exit(1);
+            } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {
                 if (root_src_file) |other| {
-                    try stderr.print("found another zig file '{}' after root source file '{}'", .{
-                        arg,
-                        other,
-                    });
+                    std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other });
                     process.exit(1);
                 } else {
                     root_src_file = arg;
                 }
             } else {
-                try stderr.print("unrecognized file extension of parameter '{}'", .{arg});
+                std.debug.warn("unrecognized file extension of parameter '{}'", .{arg});
             }
         }
     }
 
-    if (cur_pkg.parent != null) {
-        try stderr.print("unmatched --pkg-begin\n", .{});
-        process.exit(1);
-    }
-
     const root_name = if (provided_name) |n| n else blk: {
         if (root_src_file) |file| {
             const basename = fs.path.basename(file);
             var it = mem.split(basename, ".");
             break :blk it.next() orelse basename;
         } else {
-            try stderr.writeAll("--name [name] not provided and unable to infer\n");
+            std.debug.warn("--name [name] not provided and unable to infer\n", .{});
             process.exit(1);
         }
     };
 
-    if (root_src_file == null and link_objects.len == 0 and assembly_files.len == 0) {
-        try stderr.writeAll("Expected source file argument or at least one --object or --assembly argument\n");
+    if (system_libs.items.len != 0) {
+        std.debug.warn("linking against system libraries not yet supported", .{});
         process.exit(1);
     }
 
-    if (out_type == Compilation.Kind.Obj and link_objects.len != 0) {
-        try stderr.writeAll("When building an object file, --object arguments are invalid\n");
+    var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
+    const cross_target = std.zig.CrossTarget.parse(.{
+        .arch_os_abi = target_arch_os_abi,
+        .cpu_features = target_mcpu,
+        .dynamic_linker = target_dynamic_linker,
+        .diagnostics = &diags,
+    }) catch |err| switch (err) {
+        error.UnknownCpuModel => {
+            std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
+                diags.cpu_name.?,
+                @tagName(diags.arch.?),
+            });
+            for (diags.arch.?.allCpuModels()) |cpu| {
+                std.debug.warn(" {}\n", .{cpu.name});
+            }
+            process.exit(1);
+        },
+        error.UnknownCpuFeature => {
+            std.debug.warn(
+                \\Unknown CPU feature: '{}'
+                \\Available CPU features for architecture '{}':
+                \\
+            , .{
+                diags.unknown_feature_name,
+                @tagName(diags.arch.?),
+            });
+            for (diags.arch.?.allFeaturesList()) |feature| {
+                std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
+            }
+            process.exit(1);
+        },
+        else => |e| return e,
+    };
+
+    const object_format: ?std.builtin.ObjectFormat = null;
+    var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target);
+    if (target_info.cpu_detection_unimplemented) {
+        // TODO We want to just use detected_info.target but implementing
+        // CPU model & feature detection is todo so here we rely on LLVM.
+        std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
         process.exit(1);
     }
 
-    try ZigCompiler.setLlvmArgv(allocator, mllvm_flags.span());
-
-    const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch process.exit(1);
-    defer allocator.free(zig_lib_dir);
-
-    var override_libc: LibCInstallation = undefined;
+    const src_path = root_src_file orelse {
+        std.debug.warn("expected at least one file argument", .{});
+        process.exit(1);
+    };
 
-    var zig_compiler = try ZigCompiler.init(allocator);
-    defer zig_compiler.deinit();
+    const bin_path = switch (emit_bin) {
+        .no => {
+            std.debug.warn("-fno-emit-bin not supported yet", .{});
+            process.exit(1);
+        },
+        .yes_default_path => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.exeFileExt() }),
+        .yes => |p| p,
+    };
 
-    var comp = try Compilation.create(
-        &zig_compiler,
-        root_name,
-        root_src_file,
-        .{},
-        out_type,
-        build_mode,
-        !is_dynamic,
-        zig_lib_dir,
-    );
-    defer comp.destroy();
+    const zir_out_path: ?[]const u8 = switch (emit_zir) {
+        .no => null,
+        .yes_default_path => blk: {
+            if (root_src_file) |rsf| {
+                if (mem.endsWith(u8, rsf, ".zir")) {
+                    break :blk try std.fmt.allocPrint(arena, "{}.out.zir", .{root_name});
+                }
+            }
+            break :blk try std.fmt.allocPrint(arena, "{}.zir", .{root_name});
+        },
+        .yes => |p| p,
+    };
 
-    if (libc_arg) |libc_path| {
-        parseLibcPaths(allocator, &override_libc, libc_path);
-        comp.override_libc = &override_libc;
-    }
+    var bin_file = try link.openBinFilePath(gpa, fs.cwd(), bin_path, .{
+        .target = target_info.target,
+        .output_mode = output_mode,
+        .link_mode = if (is_dynamic) .Dynamic else .Static,
+        .object_format = object_format orelse target_info.target.getObjectFormat(),
+    });
+    defer bin_file.deinit();
+
+    var module = blk: {
+        const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path);
+        errdefer root_pkg.destroy();
+
+        const root_scope = try gpa.create(ir.Module.Scope.ZIRModule);
+        errdefer gpa.destroy(root_scope);
+        root_scope.* = .{
+            .sub_file_path = root_pkg.root_src_path,
+            .source = .{ .unloaded = {} },
+            .contents = .{ .not_available = {} },
+            .status = .never_loaded,
+        };
 
-    for (system_libs.span()) |lib| {
-        _ = try comp.addLinkLib(lib, true);
+        break :blk ir.Module{
+            .allocator = gpa,
+            .root_pkg = root_pkg,
+            .root_scope = root_scope,
+            .bin_file = &bin_file,
+            .optimize_mode = .Debug,
+            .decl_table = std.AutoHashMap(ir.Module.Decl.Hash, *ir.Module.Decl).init(gpa),
+            .decl_exports = std.AutoHashMap(*ir.Module.Decl, []*ir.Module.Export).init(gpa),
+            .export_owners = std.AutoHashMap(*ir.Module.Decl, []*ir.Module.Export).init(gpa),
+            .failed_decls = std.AutoHashMap(*ir.Module.Decl, *ir.ErrorMsg).init(gpa),
+            .failed_files = std.AutoHashMap(*ir.Module.Scope.ZIRModule, *ir.ErrorMsg).init(gpa),
+            .failed_exports = std.AutoHashMap(*ir.Module.Export, *ir.ErrorMsg).init(gpa),
+            .work_queue = std.fifo.LinearFifo(ir.Module.WorkItem, .Dynamic).init(gpa),
+        };
+    };
+    defer module.deinit();
+
+    const stdin = std.io.getStdIn().inStream();
+    const stderr = std.io.getStdErr().outStream();
+    var repl_buf: [1024]u8 = undefined;
+
+    try updateModule(gpa, &module, zir_out_path);
+
+    while (watch) {
+        try stderr.print("๐ŸฆŽ ", .{});
+        if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| {
+            try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)});
+            continue;
+        }) |line| {
+            if (mem.eql(u8, line, "update")) {
+                try updateModule(gpa, &module, zir_out_path);
+            } else if (mem.eql(u8, line, "exit")) {
+                break;
+            } else if (mem.eql(u8, line, "help")) {
+                try stderr.writeAll(repl_help);
+            } else {
+                try stderr.print("unknown command: {}\n", .{line});
+            }
+        } else {
+            break;
+        }
     }
+}
 
-    comp.version = version;
-    comp.is_test = false;
-    comp.linker_script = linker_script;
-    comp.clang_argv = clang_argv_buf.span();
-    comp.strip = strip;
-
-    comp.verbose_tokenize = verbose_tokenize;
-    comp.verbose_ast_tree = verbose_ast_tree;
-    comp.verbose_ast_fmt = verbose_ast_fmt;
-    comp.verbose_link = verbose_link;
-    comp.verbose_ir = verbose_ir;
-    comp.verbose_llvm_ir = verbose_llvm_ir;
-    comp.verbose_cimport = verbose_cimport;
-
-    comp.link_eh_frame_hdr = link_eh_frame_hdr;
-
-    comp.err_color = color;
+fn updateModule(gpa: *Allocator, module: *ir.Module, zir_out_path: ?[]const u8) !void {
+    try module.update();
 
-    comp.linker_rdynamic = linker_rdynamic;
+    var errors = try module.getAllErrorsAlloc();
+    defer errors.deinit(module.allocator);
 
-    if (macosx_version_min != null and ios_version_min != null) {
-        try stderr.writeAll("-mmacosx-version-min and -mios-version-min options not allowed together\n");
-        process.exit(1);
+    if (errors.list.len != 0) {
+        for (errors.list) |full_err_msg| {
+            std.debug.warn("{}:{}:{}: error: {}\n", .{
+                full_err_msg.src_path,
+                full_err_msg.line + 1,
+                full_err_msg.column + 1,
+                full_err_msg.msg,
+            });
+        }
     }
 
-    if (macosx_version_min) |ver| {
-        comp.darwin_version_min = Compilation.DarwinVersionMin{ .MacOS = ver };
-    }
-    if (ios_version_min) |ver| {
-        comp.darwin_version_min = Compilation.DarwinVersionMin{ .Ios = ver };
-    }
+    if (zir_out_path) |zop| {
+        var new_zir_module = try ir.text.emit_zir(gpa, module.*);
+        defer new_zir_module.deinit(gpa);
 
-    comp.emit_bin = emit_bin;
-    comp.emit_asm = emit_asm;
-    comp.emit_llvm_ir = emit_llvm_ir;
-    comp.emit_h = emit_h;
-    comp.assembly_files = assembly_files.span();
-    comp.link_objects = link_objects.span();
+        const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{});
+        defer baf.destroy();
 
-    comp.start();
-    processBuildEvents(comp, color);
-}
+        try new_zir_module.writeToStream(gpa, baf.stream());
 
-fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void {
-    const stderr_file = io.getStdErr();
-    const stderr = stderr_file.outStream();
-    var count: usize = 0;
-    while (!comp.cancelled) {
-        const build_event = comp.events.get();
-        count += 1;
-
-        switch (build_event) {
-            .Ok => {
-                stderr.print("Build {} succeeded\n", .{count}) catch process.exit(1);
-            },
-            .Error => |err| {
-                stderr.print("Build {} failed: {}\n", .{ count, @errorName(err) }) catch process.exit(1);
-            },
-            .Fail => |msgs| {
-                stderr.print("Build {} compile errors:\n", .{count}) catch process.exit(1);
-                for (msgs) |msg| {
-                    defer msg.destroy();
-                    msg.printToFile(stderr_file, color) catch process.exit(1);
-                }
-            },
-        }
+        try baf.finish();
     }
 }
 
+const repl_help =
+    \\Commands:
+    \\  update   Detect changes to source files and update output files.
+    \\    help   Print this text
+    \\    exit   Quit this repl
+    \\
+;
+
 pub const usage_fmt =
     \\usage: zig fmt [file]...
     \\
@@ -539,17 +547,17 @@ pub const usage_fmt =
 ;
 
 const Fmt = struct {
-    seen: event.Locked(SeenMap),
+    seen: SeenMap,
     any_error: bool,
-    color: errmsg.Color,
-    allocator: *Allocator,
+    color: Color,
+    gpa: *Allocator,
 
-    const SeenMap = std.StringHashMap(void);
+    const SeenMap = std.BufSet;
 };
 
-fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void {
+fn parseLibcPaths(gpa: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void {
     const stderr = io.getStdErr().outStream();
-    libc.* = LibCInstallation.parse(allocator, libc_paths_file, stderr) catch |err| {
+    libc.* = LibCInstallation.parse(gpa, 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,
@@ -559,13 +567,13 @@ fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_fil
     };
 }
 
-fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void {
+fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void {
     const stderr = io.getStdErr().outStream();
     switch (args.len) {
         0 => {},
         1 => {
             var libc_installation: LibCInstallation = undefined;
-            parseLibcPaths(allocator, &libc_installation, args[0]);
+            parseLibcPaths(gpa, &libc_installation, args[0]);
             return;
         },
         else => {
@@ -574,23 +582,20 @@ fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void {
         },
     }
 
-    var zig_compiler = try ZigCompiler.init(allocator);
-    defer zig_compiler.deinit();
-
-    const libc = zig_compiler.getNativeLibC() catch |err| {
+    const libc = LibCInstallation.findNative(.{ .allocator = gpa }) catch |err| {
         stderr.print("unable to find libc: {}\n", .{@errorName(err)}) catch {};
         process.exit(1);
     };
+
     libc.render(io.getStdOut().outStream()) catch process.exit(1);
 }
 
-fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
+pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
     const stderr_file = io.getStdErr();
-    const stderr = stderr_file.outStream();
-    var color: errmsg.Color = .Auto;
+    var color: Color = .Auto;
     var stdin_flag: bool = false;
     var check_flag: bool = false;
-    var input_files = ArrayList([]const u8).init(allocator);
+    var input_files = ArrayList([]const u8).init(gpa);
 
     {
         var i: usize = 0;
@@ -603,7 +608,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
                     process.exit(0);
                 } else if (mem.eql(u8, arg, "--color")) {
                     if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected [auto|on|off] after --color\n");
+                        std.debug.warn("expected [auto|on|off] after --color\n", .{});
                         process.exit(1);
                     }
                     i += 1;
@@ -615,7 +620,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
                     } else if (mem.eql(u8, next_arg, "off")) {
                         color = .Off;
                     } else {
-                        try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
+                        std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
                         process.exit(1);
                     }
                 } else if (mem.eql(u8, arg, "--stdin")) {
@@ -623,7 +628,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
                 } else if (mem.eql(u8, arg, "--check")) {
                     check_flag = true;
                 } else {
-                    try stderr.print("unrecognized parameter: '{}'", .{arg});
+                    std.debug.warn("unrecognized parameter: '{}'", .{arg});
                     process.exit(1);
                 }
             } else {
@@ -633,60 +638,55 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
     }
 
     if (stdin_flag) {
-        if (input_files.len != 0) {
-            try stderr.writeAll("cannot use --stdin with positional arguments\n");
+        if (input_files.items.len != 0) {
+            std.debug.warn("cannot use --stdin with positional arguments\n", .{});
             process.exit(1);
         }
 
         const stdin = io.getStdIn().inStream();
 
-        const source_code = try stdin.readAllAlloc(allocator, max_src_size);
-        defer allocator.free(source_code);
+        const source_code = try stdin.readAllAlloc(gpa, max_src_size);
+        defer gpa.free(source_code);
 
-        const tree = std.zig.parse(allocator, source_code) catch |err| {
-            try stderr.print("error parsing stdin: {}\n", .{err});
+        const tree = std.zig.parse(gpa, source_code) catch |err| {
+            std.debug.warn("error parsing stdin: {}\n", .{err});
             process.exit(1);
         };
         defer tree.deinit();
 
         var error_it = tree.errors.iterator(0);
         while (error_it.next()) |parse_error| {
-            const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, tree, "<stdin>");
-            defer msg.destroy();
-
-            try msg.printToFile(io.getStdErr(), color);
+            try printErrMsgToFile(gpa, parse_error, tree, "<stdin>", stderr_file, color);
         }
         if (tree.errors.len != 0) {
             process.exit(1);
         }
         if (check_flag) {
-            const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
-            const code: u8 = if (anything_changed) 1 else 0;
+            const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree);
+            const code = if (anything_changed) @as(u8, 1) else @as(u8, 0);
             process.exit(code);
         }
 
         const stdout = io.getStdOut().outStream();
-        _ = try std.zig.render(allocator, stdout, tree);
+        _ = try std.zig.render(gpa, stdout, tree);
         return;
     }
 
-    if (input_files.len == 0) {
-        try stderr.writeAll("expected at least one source file argument\n");
+    if (input_files.items.len == 0) {
+        std.debug.warn("expected at least one source file argument\n", .{});
         process.exit(1);
     }
 
     var fmt = Fmt{
-        .allocator = allocator,
-        .seen = event.Locked(Fmt.SeenMap).init(Fmt.SeenMap.init(allocator)),
+        .gpa = gpa,
+        .seen = Fmt.SeenMap.init(gpa),
         .any_error = false,
         .color = color,
     };
 
-    var group = event.Group(FmtError!void).init(allocator);
     for (input_files.span()) |file_path| {
-        try group.call(fmtPath, .{ &fmt, file_path, check_flag });
+        try fmtPath(&fmt, file_path, check_flag);
     }
-    try group.wait();
     if (fmt.any_error) {
         process.exit(1);
     }
@@ -711,54 +711,45 @@ const FmtError = error{
     ReadOnlyFileSystem,
     LinkQuotaExceeded,
     FileBusy,
-    CurrentWorkingDirectoryUnlinked,
 } || fs.File.OpenError;
 
-async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
-    const stderr_file = io.getStdErr();
-    const stderr = stderr_file.outStream();
-
-    const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
-    defer fmt.allocator.free(file_path);
-
-    {
-        const held = fmt.seen.acquire();
-        defer held.release();
+fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void {
+    // get the real path here to avoid Windows failing on relative file paths with . or .. in them
+    var real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| {
+        std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
+        fmt.any_error = true;
+        return;
+    };
+    defer fmt.gpa.free(real_path);
 
-        if (try held.value.put(file_path, {})) |_| return;
-    }
+    if (fmt.seen.exists(real_path)) return;
+    try fmt.seen.put(real_path);
 
-    const source_code = fs.cwd().readFileAlloc(
-        fmt.allocator,
-        file_path,
-        max_src_size,
-    ) catch |err| switch (err) {
+    const source_code = fs.cwd().readFileAlloc(fmt.gpa, real_path, max_src_size) catch |err| switch (err) {
         error.IsDir, error.AccessDenied => {
             var dir = try fs.cwd().openDir(file_path, .{ .iterate = true });
             defer dir.close();
 
-            var group = event.Group(FmtError!void).init(fmt.allocator);
-            var it = dir.iterate();
-            while (try it.next()) |entry| {
+            var dir_it = dir.iterate();
+
+            while (try dir_it.next()) |entry| {
                 if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) {
-                    const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name });
-                    @panic("TODO https://github.com/ziglang/zig/issues/3777");
-                    // try group.call(fmtPath, .{fmt, full_path, check_mode});
+                    const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
+                    try fmtPath(fmt, full_path, check_mode);
                 }
             }
-            return group.wait();
+            return;
         },
         else => {
-            // TODO lock stderr printing
-            try stderr.print("unable to open '{}': {}\n", .{ file_path, err });
+            std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
             fmt.any_error = true;
             return;
         },
     };
-    defer fmt.allocator.free(source_code);
+    defer fmt.gpa.free(source_code);
 
-    const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
-        try stderr.print("error parsing file '{}': {}\n", .{ file_path, err });
+    const tree = std.zig.parse(fmt.gpa, source_code) catch |err| {
+        std.debug.warn("error parsing file '{}': {}\n", .{ file_path, err });
         fmt.any_error = true;
         return;
     };
@@ -766,10 +757,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
 
     var error_it = tree.errors.iterator(0);
     while (error_it.next()) |parse_error| {
-        const msg = try errmsg.Msg.createFromParseError(fmt.allocator, parse_error, tree, file_path);
-        defer fmt.allocator.destroy(msg);
-
-        try msg.printToFile(stderr_file, fmt.color);
+        try printErrMsgToFile(fmt.gpa, parse_error, tree, file_path, std.io.getStdErr(), fmt.color);
     }
     if (tree.errors.len != 0) {
         fmt.any_error = true;
@@ -777,32 +765,67 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
     }
 
     if (check_mode) {
-        const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
+        const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
         if (anything_changed) {
-            try stderr.print("{}\n", .{file_path});
+            std.debug.warn("{}\n", .{file_path});
             fmt.any_error = true;
         }
     } else {
-        // TODO make this evented
-        const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
+        const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{});
         defer baf.destroy();
 
-        const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
+        const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree);
         if (anything_changed) {
-            try stderr.print("{}\n", .{file_path});
+            std.debug.warn("{}\n", .{file_path});
             try baf.finish();
         }
     }
 }
 
-fn cmdVersion(allocator: *Allocator, args: []const []const u8) !void {
-    const stdout = io.getStdOut().outStream();
-    try stdout.print("{}\n", .{c.ZIG_VERSION_STRING});
-}
-
-fn cmdHelp(allocator: *Allocator, args: []const []const u8) !void {
-    const stdout = io.getStdOut();
-    try stdout.writeAll(usage);
+fn printErrMsgToFile(
+    gpa: *mem.Allocator,
+    parse_error: *const ast.Error,
+    tree: *ast.Tree,
+    path: []const u8,
+    file: fs.File,
+    color: Color,
+) !void {
+    const color_on = switch (color) {
+        .Auto => file.isTty(),
+        .On => true,
+        .Off => false,
+    };
+    const lok_token = parse_error.loc();
+    const span_first = lok_token;
+    const span_last = lok_token;
+
+    const first_token = tree.tokens.at(span_first);
+    const last_token = tree.tokens.at(span_last);
+    const start_loc = tree.tokenLocationPtr(0, first_token);
+    const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
+
+    var text_buf = std.ArrayList(u8).init(gpa);
+    defer text_buf.deinit();
+    const out_stream = text_buf.outStream();
+    try parse_error.render(&tree.tokens, out_stream);
+    const text = text_buf.span();
+
+    const stream = file.outStream();
+    try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text });
+
+    if (!color_on) return;
+
+    // Print \r and \t as one space each so that column counts line up
+    for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| {
+        try stream.writeByte(switch (byte) {
+            '\r', '\t' => ' ',
+            else => byte,
+        });
+    }
+    try stream.writeByte('\n');
+    try stream.writeByteNTimes(' ', start_loc.column);
+    try stream.writeByteNTimes('~', last_token.end - first_token.start);
+    try stream.writeByte('\n');
 }
 
 pub const info_zen =
@@ -817,90 +840,8 @@ pub const info_zen =
     \\ * Avoid local maximums.
     \\ * Reduce the amount one must remember.
     \\ * Minimize energy spent on coding style.
+    \\ * Resource deallocation must succeed.
     \\ * Together we serve end users.
     \\
     \\
 ;
-
-fn cmdZen(allocator: *Allocator, args: []const []const u8) !void {
-    try io.getStdOut().writeAll(info_zen);
-}
-
-const usage_internal =
-    \\usage: zig internal [subcommand]
-    \\
-    \\Sub-Commands:
-    \\  build-info                   Print static compiler build-info
-    \\
-    \\
-;
-
-fn cmdInternal(allocator: *Allocator, args: []const []const u8) !void {
-    const stderr = io.getStdErr().outStream();
-    if (args.len == 0) {
-        try stderr.writeAll(usage_internal);
-        process.exit(1);
-    }
-
-    const sub_commands = [_]Command{Command{
-        .name = "build-info",
-        .exec = cmdInternalBuildInfo,
-    }};
-
-    inline for (sub_commands) |sub_command| {
-        if (mem.eql(u8, sub_command.name, args[0])) {
-            var frame = try allocator.create(@Frame(sub_command.exec));
-            defer allocator.destroy(frame);
-            frame.* = async sub_command.exec(allocator, args[1..]);
-            return await frame;
-        }
-    }
-
-    try stderr.print("unknown sub command: {}\n\n", .{args[0]});
-    try stderr.writeAll(usage_internal);
-}
-
-fn cmdInternalBuildInfo(allocator: *Allocator, args: []const []const u8) !void {
-    const stdout = io.getStdOut().outStream();
-    try stdout.print(
-        \\ZIG_CMAKE_BINARY_DIR {}
-        \\ZIG_CXX_COMPILER     {}
-        \\ZIG_LLD_INCLUDE_PATH {}
-        \\ZIG_LLD_LIBRARIES    {}
-        \\ZIG_LLVM_CONFIG_EXE  {}
-        \\ZIG_DIA_GUIDS_LIB    {}
-        \\
-    , .{
-        c.ZIG_CMAKE_BINARY_DIR,
-        c.ZIG_CXX_COMPILER,
-        c.ZIG_LLD_INCLUDE_PATH,
-        c.ZIG_LLD_LIBRARIES,
-        c.ZIG_LLVM_CONFIG_EXE,
-        c.ZIG_DIA_GUIDS_LIB,
-    });
-}
-
-const CliPkg = struct {
-    name: []const u8,
-    path: []const u8,
-    children: ArrayList(*CliPkg),
-    parent: ?*CliPkg,
-
-    pub fn init(allocator: *mem.Allocator, name: []const u8, path: []const u8, parent: ?*CliPkg) !*CliPkg {
-        var pkg = try allocator.create(CliPkg);
-        pkg.* = CliPkg{
-            .name = name,
-            .path = path,
-            .children = ArrayList(*CliPkg).init(allocator),
-            .parent = parent,
-        };
-        return pkg;
-    }
-
-    pub fn deinit(self: *CliPkg) void {
-        for (self.children.span()) |child| {
-            child.deinit();
-        }
-        self.children.deinit();
-    }
-};
src-self-hosted/stage2.zig
@@ -12,7 +12,6 @@ const ArrayListSentineled = std.ArrayListSentineled;
 const Target = std.Target;
 const CrossTarget = std.zig.CrossTarget;
 const self_hosted_main = @import("main.zig");
-const errmsg = @import("errmsg.zig");
 const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer;
 const assert = std.debug.assert;
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
@@ -168,8 +167,6 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
     return .None;
 }
 
-// TODO: just use the actual self-hosted zig fmt. Until https://github.com/ziglang/zig/issues/2377,
-// we use a blocking implementation.
 export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int {
     if (std.debug.runtime_safety) {
         fmtMain(argc, argv) catch unreachable;
@@ -191,258 +188,9 @@ fn fmtMain(argc: c_int, argv: [*]const [*:0]const u8) !void {
         try args_list.append(mem.spanZ(argv[arg_i]));
     }
 
-    stdout = std.io.getStdOut().outStream();
-    stderr_file = std.io.getStdErr();
-    stderr = stderr_file.outStream();
-
     const args = args_list.span()[2..];
 
-    var color: errmsg.Color = .Auto;
-    var stdin_flag: bool = false;
-    var check_flag: bool = false;
-    var input_files = ArrayList([]const u8).init(allocator);
-
-    {
-        var i: usize = 0;
-        while (i < args.len) : (i += 1) {
-            const arg = args[i];
-            if (mem.startsWith(u8, arg, "-")) {
-                if (mem.eql(u8, arg, "--help")) {
-                    try stdout.writeAll(self_hosted_main.usage_fmt);
-                    process.exit(0);
-                } else if (mem.eql(u8, arg, "--color")) {
-                    if (i + 1 >= args.len) {
-                        try stderr.writeAll("expected [auto|on|off] after --color\n");
-                        process.exit(1);
-                    }
-                    i += 1;
-                    const next_arg = args[i];
-                    if (mem.eql(u8, next_arg, "auto")) {
-                        color = .Auto;
-                    } else if (mem.eql(u8, next_arg, "on")) {
-                        color = .On;
-                    } else if (mem.eql(u8, next_arg, "off")) {
-                        color = .Off;
-                    } else {
-                        try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
-                        process.exit(1);
-                    }
-                } else if (mem.eql(u8, arg, "--stdin")) {
-                    stdin_flag = true;
-                } else if (mem.eql(u8, arg, "--check")) {
-                    check_flag = true;
-                } else {
-                    try stderr.print("unrecognized parameter: '{}'", .{arg});
-                    process.exit(1);
-                }
-            } else {
-                try input_files.append(arg);
-            }
-        }
-    }
-
-    if (stdin_flag) {
-        if (input_files.items.len != 0) {
-            try stderr.writeAll("cannot use --stdin with positional arguments\n");
-            process.exit(1);
-        }
-
-        const stdin_file = io.getStdIn();
-        var stdin = stdin_file.inStream();
-
-        const source_code = try stdin.readAllAlloc(allocator, self_hosted_main.max_src_size);
-        defer allocator.free(source_code);
-
-        const tree = std.zig.parse(allocator, source_code) catch |err| {
-            try stderr.print("error parsing stdin: {}\n", .{err});
-            process.exit(1);
-        };
-        defer tree.deinit();
-
-        var error_it = tree.errors.iterator(0);
-        while (error_it.next()) |parse_error| {
-            try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
-        }
-        if (tree.errors.len != 0) {
-            process.exit(1);
-        }
-        if (check_flag) {
-            const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
-            const code = if (anything_changed) @as(u8, 1) else @as(u8, 0);
-            process.exit(code);
-        }
-
-        _ = try std.zig.render(allocator, stdout, tree);
-        return;
-    }
-
-    if (input_files.items.len == 0) {
-        try stderr.writeAll("expected at least one source file argument\n");
-        process.exit(1);
-    }
-
-    var fmt = Fmt{
-        .seen = Fmt.SeenMap.init(allocator),
-        .any_error = false,
-        .color = color,
-        .allocator = allocator,
-    };
-
-    for (input_files.span()) |file_path| {
-        try fmtPath(&fmt, file_path, check_flag);
-    }
-    if (fmt.any_error) {
-        process.exit(1);
-    }
-}
-
-const FmtError = error{
-    SystemResources,
-    OperationAborted,
-    IoPending,
-    BrokenPipe,
-    Unexpected,
-    WouldBlock,
-    FileClosed,
-    DestinationAddressRequired,
-    DiskQuota,
-    FileTooBig,
-    InputOutput,
-    NoSpaceLeft,
-    AccessDenied,
-    OutOfMemory,
-    RenameAcrossMountPoints,
-    ReadOnlyFileSystem,
-    LinkQuotaExceeded,
-    FileBusy,
-} || fs.File.OpenError;
-
-fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void {
-    // get the real path here to avoid Windows failing on relative file paths with . or .. in them
-    var real_path = fs.realpathAlloc(fmt.allocator, file_path) catch |err| {
-        try stderr.print("unable to open '{}': {}\n", .{ file_path, err });
-        fmt.any_error = true;
-        return;
-    };
-    defer fmt.allocator.free(real_path);
-
-    if (fmt.seen.exists(real_path)) return;
-    try fmt.seen.put(real_path);
-
-    const source_code = fs.cwd().readFileAlloc(fmt.allocator, real_path, self_hosted_main.max_src_size) catch |err| switch (err) {
-        error.IsDir, error.AccessDenied => {
-            // TODO make event based (and dir.next())
-            var dir = try fs.cwd().openDir(file_path, .{ .iterate = true });
-            defer dir.close();
-
-            var dir_it = dir.iterate();
-
-            while (try dir_it.next()) |entry| {
-                if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) {
-                    const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name });
-                    try fmtPath(fmt, full_path, check_mode);
-                }
-            }
-            return;
-        },
-        else => {
-            // TODO lock stderr printing
-            try stderr.print("unable to open '{}': {}\n", .{ file_path, err });
-            fmt.any_error = true;
-            return;
-        },
-    };
-    defer fmt.allocator.free(source_code);
-
-    const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
-        try stderr.print("error parsing file '{}': {}\n", .{ file_path, err });
-        fmt.any_error = true;
-        return;
-    };
-    defer tree.deinit();
-
-    var error_it = tree.errors.iterator(0);
-    while (error_it.next()) |parse_error| {
-        try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
-    }
-    if (tree.errors.len != 0) {
-        fmt.any_error = true;
-        return;
-    }
-
-    if (check_mode) {
-        const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
-        if (anything_changed) {
-            try stderr.print("{}\n", .{file_path});
-            fmt.any_error = true;
-        }
-    } else {
-        const baf = try io.BufferedAtomicFile.create(fmt.allocator, fs.cwd(), real_path, .{});
-        defer baf.destroy();
-
-        const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
-        if (anything_changed) {
-            try stderr.print("{}\n", .{file_path});
-            try baf.finish();
-        }
-    }
-}
-
-const Fmt = struct {
-    seen: SeenMap,
-    any_error: bool,
-    color: errmsg.Color,
-    allocator: *mem.Allocator,
-
-    const SeenMap = std.BufSet;
-};
-
-fn printErrMsgToFile(
-    allocator: *mem.Allocator,
-    parse_error: *const ast.Error,
-    tree: *ast.Tree,
-    path: []const u8,
-    file: fs.File,
-    color: errmsg.Color,
-) !void {
-    const color_on = switch (color) {
-        .Auto => file.isTty(),
-        .On => true,
-        .Off => false,
-    };
-    const lok_token = parse_error.loc();
-    const span = errmsg.Span{
-        .first = lok_token,
-        .last = lok_token,
-    };
-
-    const first_token = tree.tokens.at(span.first);
-    const last_token = tree.tokens.at(span.last);
-    const start_loc = tree.tokenLocationPtr(0, first_token);
-    const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
-
-    var text_buf = std.ArrayList(u8).init(allocator);
-    defer text_buf.deinit();
-    const out_stream = text_buf.outStream();
-    try parse_error.render(&tree.tokens, out_stream);
-    const text = text_buf.span();
-
-    const stream = file.outStream();
-    try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text });
-
-    if (!color_on) return;
-
-    // Print \r and \t as one space each so that column counts line up
-    for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| {
-        try stream.writeByte(switch (byte) {
-            '\r', '\t' => ' ',
-            else => byte,
-        });
-    }
-    try stream.writeByte('\n');
-    try stream.writeByteNTimes(' ', start_loc.column);
-    try stream.writeByteNTimes('~', last_token.end - first_token.start);
-    try stream.writeByte('\n');
+    return self_hosted_main.cmdFmt(allocator, args);
 }
 
 export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer {
build.zig
@@ -51,6 +51,9 @@ pub fn build(b: *Builder) !void {
 
     var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
     exe.setBuildMode(mode);
+    test_step.dependOn(&exe.step);
+    b.default_step.dependOn(&exe.step);
+    exe.install();
 
     const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
     const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release;
@@ -58,21 +61,17 @@ pub fn build(b: *Builder) !void {
     const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
     const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
     const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
-    const skip_self_hosted = (b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false) or true; // TODO evented I/O good enough that this passes everywhere
-    if (!skip_self_hosted) {
-        test_step.dependOn(&exe.step);
-    }
 
     const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
-    if (!only_install_lib_files and !skip_self_hosted) {
+    const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false;
+    if (enable_llvm) {
         var ctx = parseConfigH(b, config_h_text);
         ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
 
         try configureStage2(b, exe, ctx);
-
-        b.default_step.dependOn(&exe.step);
-        exe.install();
     }
+    const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false;
+    if (link_libc) exe.linkLibC();
 
     b.installDirectory(InstallDirectoryOptions{
         .source_dir = "lib",