Commit 574e31f0a0

Andrew Kelley <superjoe30@gmail.com>
2018-07-11 02:18:43
self-hosted: first passing test
* introduce std.atomic.Int * add src-self-hosted/test.zig which is tested by the main test suite - it fully utilizes the multithreaded async/await event loop so the tests should Go Fast * `stage2/bin/zig build-obj test.zig` is able to spit out an error if 2 exported functions collide * ability for `zig test` to accept `--object` and `--assembly` arguments * std.build: TestStep supports addLibPath and addObjectFile
1 parent 8fba0a6
src/main.cpp
@@ -891,15 +891,19 @@ int main(int argc, char **argv) {
 
             add_package(g, cur_pkg, g->root_package);
 
-            if (cmd == CmdBuild || cmd == CmdRun) {
-                codegen_set_emit_file_type(g, emit_file_type);
-
+            if (cmd == CmdBuild || cmd == CmdRun || cmd == CmdTest) {
                 for (size_t i = 0; i < objects.length; i += 1) {
                     codegen_add_object(g, buf_create_from_str(objects.at(i)));
                 }
                 for (size_t i = 0; i < asm_files.length; i += 1) {
                     codegen_add_assembly(g, buf_create_from_str(asm_files.at(i)));
                 }
+            }
+
+
+            if (cmd == CmdBuild || cmd == CmdRun) {
+                codegen_set_emit_file_type(g, emit_file_type);
+
                 codegen_build(g);
                 codegen_link(g, out_file);
                 if (timing_info)
src-self-hosted/errmsg.zig
@@ -11,11 +11,15 @@ pub const Color = enum {
     On,
 };
 
+pub const Span = struct {
+    first: ast.TokenIndex,
+    last: ast.TokenIndex,
+};
+
 pub const Msg = struct {
     path: []const u8,
     text: []u8,
-    first_token: TokenIndex,
-    last_token: TokenIndex,
+    span: Span,
     tree: *ast.Tree,
 };
 
@@ -39,8 +43,10 @@ pub fn createFromParseError(
         .tree = tree,
         .path = path,
         .text = text_buf.toOwnedSlice(),
-        .first_token = loc_token,
-        .last_token = loc_token,
+        .span = Span{
+            .first = loc_token,
+            .last = loc_token,
+        },
     });
     errdefer allocator.destroy(msg);
 
@@ -48,8 +54,8 @@ pub fn createFromParseError(
 }
 
 pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void {
-    const first_token = msg.tree.tokens.at(msg.first_token);
-    const last_token = msg.tree.tokens.at(msg.last_token);
+    const first_token = msg.tree.tokens.at(msg.span.first);
+    const last_token = msg.tree.tokens.at(msg.span.last);
     const start_loc = msg.tree.tokenLocationPtr(0, first_token);
     const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token);
     if (!color_on) {
src-self-hosted/introspect.zig
@@ -53,3 +53,8 @@ pub fn resolveZigLibDir(allocator: *mem.Allocator) ![]u8 {
         return error.ZigLibDirNotFound;
     };
 }
+
+/// Caller must free result
+pub fn resolveZigCacheDir(allocator: *mem.Allocator) ![]u8 {
+    return std.mem.dupe(allocator, u8, "zig-cache");
+}
src-self-hosted/main.zig
@@ -481,29 +481,29 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo
     module.link_out_file = flags.single("out-file");
 
     try module.build();
-    const process_build_events_handle = try async<loop.allocator> processBuildEvents(module, true);
+    const process_build_events_handle = try async<loop.allocator> processBuildEvents(module, color);
     defer cancel process_build_events_handle;
     loop.run();
 }
 
-async fn processBuildEvents(module: *Module, watch: bool) void {
-    while (watch) {
-        // TODO directly awaiting async should guarantee memory allocation elision
-        const build_event = await (async module.events.get() catch unreachable);
+async fn processBuildEvents(module: *Module, color: errmsg.Color) void {
+    // TODO directly awaiting async should guarantee memory allocation elision
+    const build_event = await (async module.events.get() catch unreachable);
 
-        switch (build_event) {
-            Module.Event.Ok => {
-                std.debug.warn("Build succeeded\n");
-                return;
-            },
-            Module.Event.Error => |err| {
-                std.debug.warn("build failed: {}\n", @errorName(err));
-                @panic("TODO error return trace");
-            },
-            Module.Event.Fail => |errs| {
-                @panic("TODO print compile error messages");
-            },
-        }
+    switch (build_event) {
+        Module.Event.Ok => {
+            std.debug.warn("Build succeeded\n");
+            return;
+        },
+        Module.Event.Error => |err| {
+            std.debug.warn("build failed: {}\n", @errorName(err));
+            @panic("TODO error return trace");
+        },
+        Module.Event.Fail => |msgs| {
+            for (msgs) |msg| {
+                errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1);
+            }
+        },
     }
 }
 
src-self-hosted/module.zig
@@ -89,12 +89,9 @@ pub const Module = struct {
     /// the build is complete.
     build_group: event.Group(BuildError!void),
 
-    const BuildErrorsList = std.SegmentedList(BuildErrorDesc, 1);
+    compile_errors: event.Locked(CompileErrList),
 
-    pub const BuildErrorDesc = struct {
-        code: BuildError,
-        text: []const u8,
-    };
+    const CompileErrList = std.ArrayList(*errmsg.Msg);
 
     // TODO handle some of these earlier and report them in a way other than error codes
     pub const BuildError = error{
@@ -131,11 +128,12 @@ pub const Module = struct {
         NoStdHandles,
         Overflow,
         NotSupported,
+        BufferTooSmall,
     };
 
     pub const Event = union(enum) {
         Ok,
-        Fail: []errmsg.Msg,
+        Fail: []*errmsg.Msg,
         Error: BuildError,
     };
 
@@ -249,6 +247,7 @@ pub const Module = struct {
             .link_out_file = null,
             .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)),
             .build_group = event.Group(BuildError!void).init(loop),
+            .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)),
         });
     }
 
@@ -288,7 +287,17 @@ pub const Module = struct {
                 await (async self.events.put(Event{ .Error = err }) catch unreachable);
                 return;
             };
-            await (async self.events.put(Event.Ok) catch unreachable);
+            const compile_errors = blk: {
+                const held = await (async self.compile_errors.acquire() catch unreachable);
+                defer held.release();
+                break :blk held.value.toOwnedSlice();
+            };
+
+            if (compile_errors.len == 0) {
+                await (async self.events.put(Event.Ok) catch unreachable);
+            } else {
+                await (async self.events.put(Event{ .Fail = compile_errors }) catch unreachable);
+            }
             // for now we stop after 1
             return;
         }
@@ -310,10 +319,13 @@ pub const Module = struct {
         };
         errdefer self.a().free(source_code);
 
-        var parsed_file = ParsedFile{
-            .tree = try std.zig.parse(self.a(), source_code),
+        const parsed_file = try self.a().create(ParsedFile{
+            .tree = undefined,
             .realpath = root_src_real_path,
-        };
+        });
+        errdefer self.a().destroy(parsed_file);
+
+        parsed_file.tree = try std.zig.parse(self.a(), source_code);
         errdefer parsed_file.tree.deinit();
 
         const tree = &parsed_file.tree;
@@ -337,7 +349,7 @@ pub const Module = struct {
                     const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else {
                         @panic("TODO add compile error");
                         //try self.addCompileError(
-                        //    &parsed_file,
+                        //    parsed_file,
                         //    fn_proto.fn_token,
                         //    fn_proto.fn_token + 1,
                         //    "missing function name",
@@ -357,7 +369,7 @@ pub const Module = struct {
                     });
                     errdefer self.a().destroy(fn_decl);
 
-                    try decl_group.call(addTopLevelDecl, self, tree, &fn_decl.base);
+                    try decl_group.call(addTopLevelDecl, self, parsed_file, &fn_decl.base);
                 },
                 ast.Node.Id.TestDecl => @panic("TODO"),
                 else => unreachable,
@@ -367,20 +379,56 @@ pub const Module = struct {
         try await (async self.build_group.wait() catch unreachable);
     }
 
-    async fn addTopLevelDecl(self: *Module, tree: *ast.Tree, decl: *Decl) !void {
-        const is_export = decl.isExported(tree);
+    async fn addTopLevelDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void {
+        const is_export = decl.isExported(&parsed_file.tree);
 
         if (is_export) {
-            try self.build_group.call(verifyUniqueSymbol, self, decl);
+            try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl);
         }
     }
 
-    async fn verifyUniqueSymbol(self: *Module, decl: *Decl) !void {
+    fn addCompileError(self: *Module, parsed_file: *ParsedFile, span: errmsg.Span, comptime fmt: []const u8, args: ...) !void {
+        const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args);
+        errdefer self.loop.allocator.free(text);
+
+        try self.build_group.call(addCompileErrorAsync, self, parsed_file, span.first, span.last, text);
+    }
+
+    async fn addCompileErrorAsync(
+        self: *Module,
+        parsed_file: *ParsedFile,
+        first_token: ast.TokenIndex,
+        last_token: ast.TokenIndex,
+        text: []u8,
+    ) !void {
+        const msg = try self.loop.allocator.create(errmsg.Msg{
+            .path = parsed_file.realpath,
+            .text = text,
+            .span = errmsg.Span{
+                .first = first_token,
+                .last = last_token,
+            },
+            .tree = &parsed_file.tree,
+        });
+        errdefer self.loop.allocator.destroy(msg);
+
+        const compile_errors = await (async self.compile_errors.acquire() catch unreachable);
+        defer compile_errors.release();
+
+        try compile_errors.value.append(msg);
+    }
+
+    async fn verifyUniqueSymbol(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void {
         const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable);
         defer exported_symbol_names.release();
 
         if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| {
-            @panic("TODO report compile error");
+            try self.addCompileError(
+                parsed_file,
+                decl.getSpan(),
+                "exported symbol collision: '{}'",
+                decl.name,
+            );
         }
     }
 
@@ -503,6 +551,22 @@ pub const Decl = struct {
         }
     }
 
+    pub fn getSpan(base: *const Decl) errmsg.Span {
+        switch (base.id) {
+            Id.Fn => {
+                const fn_decl = @fieldParentPtr(Fn, "base", base);
+                const fn_proto = fn_decl.fn_proto;
+                const start = fn_proto.fn_token;
+                const end = fn_proto.name_token orelse start;
+                return errmsg.Span{
+                    .first = start,
+                    .last = end + 1,
+                };
+            },
+            else => @panic("TODO"),
+        }
+    }
+
     pub const Resolution = enum {
         Unresolved,
         InProgress,
src-self-hosted/test.zig
@@ -0,0 +1,176 @@
+const std = @import("std");
+const mem = std.mem;
+const builtin = @import("builtin");
+const Target = @import("target.zig").Target;
+const Module = @import("module.zig").Module;
+const introspect = @import("introspect.zig");
+const assertOrPanic = std.debug.assertOrPanic;
+const errmsg = @import("errmsg.zig");
+
+test "compile errors" {
+    var ctx: TestContext = undefined;
+    try ctx.init();
+    defer ctx.deinit();
+
+    try ctx.testCompileError(
+        \\export fn entry() void {}
+        \\export fn entry() void {}
+    , file1, 2, 8, "exported symbol collision: 'entry'");
+
+    try ctx.run();
+}
+
+const file1 = "1.zig";
+
+const TestContext = struct {
+    loop: std.event.Loop,
+    zig_lib_dir: []u8,
+    direct_allocator: std.heap.DirectAllocator,
+    arena: std.heap.ArenaAllocator,
+    zig_cache_dir: []u8,
+    file_index: std.atomic.Int(usize),
+    group: std.event.Group(error!void),
+    any_err: error!void,
+
+    const tmp_dir_name = "stage2_test_tmp";
+
+    fn init(self: *TestContext) !void {
+        self.* = TestContext{
+            .any_err = {},
+            .direct_allocator = undefined,
+            .arena = undefined,
+            .loop = undefined,
+            .zig_lib_dir = undefined,
+            .zig_cache_dir = undefined,
+            .group = undefined,
+            .file_index = std.atomic.Int(usize).init(0),
+        };
+
+        self.direct_allocator = std.heap.DirectAllocator.init();
+        errdefer self.direct_allocator.deinit();
+
+        self.arena = std.heap.ArenaAllocator.init(&self.direct_allocator.allocator);
+        errdefer self.arena.deinit();
+
+        // TODO faster allocator for coroutines that is thread-safe/lock-free
+        try self.loop.initMultiThreaded(&self.direct_allocator.allocator);
+        errdefer self.loop.deinit();
+
+        self.group = std.event.Group(error!void).init(&self.loop);
+        errdefer self.group.cancelAll();
+
+        self.zig_lib_dir = try introspect.resolveZigLibDir(&self.arena.allocator);
+        errdefer self.arena.allocator.free(self.zig_lib_dir);
+
+        self.zig_cache_dir = try introspect.resolveZigCacheDir(&self.arena.allocator);
+        errdefer self.arena.allocator.free(self.zig_cache_dir);
+
+        try std.os.makePath(&self.arena.allocator, tmp_dir_name);
+        errdefer std.os.deleteTree(&self.arena.allocator, tmp_dir_name) catch {};
+    }
+
+    fn deinit(self: *TestContext) void {
+        std.os.deleteTree(&self.arena.allocator, tmp_dir_name) catch {};
+        self.arena.allocator.free(self.zig_cache_dir);
+        self.arena.allocator.free(self.zig_lib_dir);
+        self.loop.deinit();
+        self.arena.deinit();
+        self.direct_allocator.deinit();
+    }
+
+    fn run(self: *TestContext) !void {
+        const handle = try self.loop.call(waitForGroup, self);
+        defer cancel handle;
+        self.loop.run();
+        return self.any_err;
+    }
+
+    async fn waitForGroup(self: *TestContext) void {
+        self.any_err = await (async self.group.wait() catch unreachable);
+    }
+
+    fn testCompileError(
+        self: *TestContext,
+        source: []const u8,
+        path: []const u8,
+        line: usize,
+        column: usize,
+        msg: []const u8,
+    ) !void {
+        var file_index_buf: [20]u8 = undefined;
+        const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.next());
+        const file1_path = try std.os.path.join(&self.arena.allocator, tmp_dir_name, file_index, file1);
+
+        if (std.os.path.dirname(file1_path)) |dirname| {
+            try std.os.makePath(&self.arena.allocator, dirname);
+        }
+
+        // TODO async I/O
+        try std.io.writeFile(&self.arena.allocator, file1_path, source);
+
+        var module = try Module.create(
+            &self.loop,
+            "test",
+            file1_path,
+            Target.Native,
+            Module.Kind.Obj,
+            builtin.Mode.Debug,
+            self.zig_lib_dir,
+            self.zig_cache_dir,
+        );
+        errdefer module.destroy();
+
+        try module.build();
+
+        try self.group.call(getModuleEvent, module, source, path, line, column, msg);
+    }
+
+    async fn getModuleEvent(
+        module: *Module,
+        source: []const u8,
+        path: []const u8,
+        line: usize,
+        column: usize,
+        text: []const u8,
+    ) !void {
+        defer module.destroy();
+        const build_event = await (async module.events.get() catch unreachable);
+
+        switch (build_event) {
+            Module.Event.Ok => {
+                @panic("build incorrectly succeeded");
+            },
+            Module.Event.Error => |err| {
+                @panic("build incorrectly failed");
+            },
+            Module.Event.Fail => |msgs| {
+                assertOrPanic(msgs.len != 0);
+                for (msgs) |msg| {
+                    if (mem.endsWith(u8, msg.path, path) and mem.eql(u8, msg.text, text)) {
+                        const first_token = msg.tree.tokens.at(msg.span.first);
+                        const last_token = msg.tree.tokens.at(msg.span.first);
+                        const start_loc = msg.tree.tokenLocationPtr(0, first_token);
+                        if (start_loc.line + 1 == line and start_loc.column + 1 == column) {
+                            return;
+                        }
+                    }
+                }
+                std.debug.warn(
+                    "\n=====source:=======\n{}\n====expected:========\n{}:{}:{}: error: {}\n",
+                    source,
+                    path,
+                    line,
+                    column,
+                    text,
+                );
+                std.debug.warn("\n====found:========\n");
+                var stderr = try std.io.getStdErr();
+                for (msgs) |msg| {
+                    try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto);
+                }
+                std.debug.warn("============\n");
+                return error.TestFailed;
+            },
+        }
+    }
+};
std/atomic/index.zig
@@ -1,9 +1,11 @@
 pub const Stack = @import("stack.zig").Stack;
 pub const QueueMpsc = @import("queue_mpsc.zig").QueueMpsc;
 pub const QueueMpmc = @import("queue_mpmc.zig").QueueMpmc;
+pub const Int = @import("int.zig").Int;
 
 test "std.atomic" {
     _ = @import("stack.zig");
     _ = @import("queue_mpsc.zig");
     _ = @import("queue_mpmc.zig");
+    _ = @import("int.zig");
 }
std/atomic/int.zig
@@ -0,0 +1,19 @@
+const builtin = @import("builtin");
+const AtomicOrder = builtin.AtomicOrder;
+
+/// Thread-safe, lock-free integer
+pub fn Int(comptime T: type) type {
+    return struct {
+        value: T,
+
+        pub const Self = this;
+
+        pub fn init(init_val: T) Self {
+            return Self{ .value = init_val };
+        }
+
+        pub fn next(self: *Self) T {
+            return @atomicRmw(T, &self.value, builtin.AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+        }
+    };
+}
std/build.zig
@@ -1596,6 +1596,8 @@ pub const TestStep = struct {
     target: Target,
     exec_cmd_args: ?[]const ?[]const u8,
     include_dirs: ArrayList([]const u8),
+    lib_paths: ArrayList([]const u8),
+    object_files: ArrayList([]const u8),
 
     pub fn init(builder: *Builder, root_src: []const u8) TestStep {
         const step_name = builder.fmt("test {}", root_src);
@@ -1611,9 +1613,15 @@ pub const TestStep = struct {
             .target = Target{ .Native = {} },
             .exec_cmd_args = null,
             .include_dirs = ArrayList([]const u8).init(builder.allocator),
+            .lib_paths = ArrayList([]const u8).init(builder.allocator),
+            .object_files = ArrayList([]const u8).init(builder.allocator),
         };
     }
 
+    pub fn addLibPath(self: *TestStep, path: []const u8) void {
+        self.lib_paths.append(path) catch unreachable;
+    }
+
     pub fn setVerbose(self: *TestStep, value: bool) void {
         self.verbose = value;
     }
@@ -1638,6 +1646,10 @@ pub const TestStep = struct {
         self.filter = text;
     }
 
+    pub fn addObjectFile(self: *TestStep, path: []const u8) void {
+        self.object_files.append(path) catch unreachable;
+    }
+
     pub fn setTarget(self: *TestStep, target_arch: builtin.Arch, target_os: builtin.Os, target_environ: builtin.Environ) void {
         self.target = Target{
             .Cross = CrossTarget{
@@ -1699,6 +1711,11 @@ pub const TestStep = struct {
             try zig_args.append(self.name_prefix);
         }
 
+        for (self.object_files.toSliceConst()) |object_file| {
+            try zig_args.append("--object");
+            try zig_args.append(builder.pathFromRoot(object_file));
+        }
+
         {
             var it = self.link_libs.iterator();
             while (true) {
@@ -1734,6 +1751,11 @@ pub const TestStep = struct {
             try zig_args.append(rpath);
         }
 
+        for (self.lib_paths.toSliceConst()) |lib_path| {
+            try zig_args.append("--library-path");
+            try zig_args.append(lib_path);
+        }
+
         for (builder.lib_paths.toSliceConst()) |lib_path| {
             try zig_args.append("--library-path");
             try zig_args.append(lib_path);
build.zig
@@ -35,70 +35,27 @@ pub fn build(b: *Builder) !void {
         "BUILD_INFO",
     });
     var index: usize = 0;
-    const cmake_binary_dir = nextValue(&index, build_info);
-    const cxx_compiler = nextValue(&index, build_info);
-    const llvm_config_exe = nextValue(&index, build_info);
-    const lld_include_dir = nextValue(&index, build_info);
-    const lld_libraries = nextValue(&index, build_info);
-    const std_files = nextValue(&index, build_info);
-    const c_header_files = nextValue(&index, build_info);
-    const dia_guids_lib = nextValue(&index, build_info);
+    var ctx = Context{
+        .cmake_binary_dir = nextValue(&index, build_info),
+        .cxx_compiler = nextValue(&index, build_info),
+        .llvm_config_exe = nextValue(&index, build_info),
+        .lld_include_dir = nextValue(&index, build_info),
+        .lld_libraries = nextValue(&index, build_info),
+        .std_files = nextValue(&index, build_info),
+        .c_header_files = nextValue(&index, build_info),
+        .dia_guids_lib = nextValue(&index, build_info),
+        .llvm = undefined,
+    };
+    ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
 
-    const llvm = findLLVM(b, llvm_config_exe) catch unreachable;
+    var test_stage2 = b.addTest("src-self-hosted/test.zig");
+    test_stage2.setBuildMode(builtin.Mode.Debug);
 
     var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
     exe.setBuildMode(mode);
 
-    // This is for finding /lib/libz.a on alpine linux.
-    // TODO turn this into -Dextra-lib-path=/lib option
-    exe.addLibPath("/lib");
-
-    exe.addIncludeDir("src");
-    exe.addIncludeDir(cmake_binary_dir);
-    addCppLib(b, exe, cmake_binary_dir, "zig_cpp");
-    if (lld_include_dir.len != 0) {
-        exe.addIncludeDir(lld_include_dir);
-        var it = mem.split(lld_libraries, ";");
-        while (it.next()) |lib| {
-            exe.addObjectFile(lib);
-        }
-    } else {
-        addCppLib(b, exe, cmake_binary_dir, "embedded_lld_wasm");
-        addCppLib(b, exe, cmake_binary_dir, "embedded_lld_elf");
-        addCppLib(b, exe, cmake_binary_dir, "embedded_lld_coff");
-        addCppLib(b, exe, cmake_binary_dir, "embedded_lld_lib");
-    }
-    dependOnLib(exe, llvm);
-
-    if (exe.target.getOs() == builtin.Os.linux) {
-        const libstdcxx_path_padded = try b.exec([][]const u8{
-            cxx_compiler,
-            "-print-file-name=libstdc++.a",
-        });
-        const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?;
-        if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) {
-            warn(
-                \\Unable to determine path to libstdc++.a
-                \\On Fedora, install libstdc++-static and try again.
-                \\
-            );
-            return error.RequiredLibraryNotFound;
-        }
-        exe.addObjectFile(libstdcxx_path);
-
-        exe.linkSystemLibrary("pthread");
-    } else if (exe.target.isDarwin()) {
-        exe.linkSystemLibrary("c++");
-    }
-
-    if (dia_guids_lib.len != 0) {
-        exe.addObjectFile(dia_guids_lib);
-    }
-
-    if (exe.target.getOs() != builtin.Os.windows) {
-        exe.linkSystemLibrary("xml2");
-    }
-    exe.linkSystemLibrary("c");
+    try configureStage2(b, test_stage2, ctx);
+    try configureStage2(b, exe, ctx);
 
     b.default_step.dependOn(&exe.step);
 
@@ -110,12 +67,16 @@ pub fn build(b: *Builder) !void {
     exe.setVerboseLink(verbose_link_exe);
 
     b.installArtifact(exe);
-    installStdLib(b, std_files);
-    installCHeaders(b, c_header_files);
+    installStdLib(b, ctx.std_files);
+    installCHeaders(b, ctx.c_header_files);
 
     const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
     const with_lldb = b.option(bool, "with-lldb", "Run tests in LLDB to get a backtrace if one fails") orelse false;
 
+    const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
+    test_stage2_step.dependOn(&test_stage2.step);
+    test_step.dependOn(test_stage2_step);
+
     test_step.dependOn(docs_step);
 
     test_step.dependOn(tests.addPkgTests(b, test_filter, "test/behavior.zig", "behavior", "Run the behavior tests", with_lldb));
@@ -133,7 +94,7 @@ pub fn build(b: *Builder) !void {
     test_step.dependOn(tests.addGenHTests(b, test_filter));
 }
 
-fn dependOnLib(lib_exe_obj: *std.build.LibExeObjStep, dep: *const LibraryDep) void {
+fn dependOnLib(lib_exe_obj: var, dep: *const LibraryDep) void {
     for (dep.libdirs.toSliceConst()) |lib_dir| {
         lib_exe_obj.addLibPath(lib_dir);
     }
@@ -148,7 +109,7 @@ fn dependOnLib(lib_exe_obj: *std.build.LibExeObjStep, dep: *const LibraryDep) vo
     }
 }
 
-fn addCppLib(b: *Builder, lib_exe_obj: *std.build.LibExeObjStep, cmake_binary_dir: []const u8, lib_name: []const u8) void {
+fn addCppLib(b: *Builder, lib_exe_obj: var, cmake_binary_dir: []const u8, lib_name: []const u8) void {
     const lib_prefix = if (lib_exe_obj.target.isWindows()) "" else "lib";
     lib_exe_obj.addObjectFile(os.path.join(b.allocator, cmake_binary_dir, "zig_cpp", b.fmt("{}{}{}", lib_prefix, lib_name, lib_exe_obj.target.libFileExt())) catch unreachable);
 }
@@ -254,3 +215,68 @@ fn nextValue(index: *usize, build_info: []const u8) []const u8 {
         }
     }
 }
+
+fn configureStage2(b: *Builder, exe: var, ctx: Context) !void {
+    // This is for finding /lib/libz.a on alpine linux.
+    // TODO turn this into -Dextra-lib-path=/lib option
+    exe.addLibPath("/lib");
+
+    exe.addIncludeDir("src");
+    exe.addIncludeDir(ctx.cmake_binary_dir);
+    addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp");
+    if (ctx.lld_include_dir.len != 0) {
+        exe.addIncludeDir(ctx.lld_include_dir);
+        var it = mem.split(ctx.lld_libraries, ";");
+        while (it.next()) |lib| {
+            exe.addObjectFile(lib);
+        }
+    } else {
+        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_wasm");
+        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_elf");
+        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_coff");
+        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_lib");
+    }
+    dependOnLib(exe, ctx.llvm);
+
+    if (exe.target.getOs() == builtin.Os.linux) {
+        const libstdcxx_path_padded = try b.exec([][]const u8{
+            ctx.cxx_compiler,
+            "-print-file-name=libstdc++.a",
+        });
+        const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?;
+        if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) {
+            warn(
+                \\Unable to determine path to libstdc++.a
+                \\On Fedora, install libstdc++-static and try again.
+                \\
+            );
+            return error.RequiredLibraryNotFound;
+        }
+        exe.addObjectFile(libstdcxx_path);
+
+        exe.linkSystemLibrary("pthread");
+    } else if (exe.target.isDarwin()) {
+        exe.linkSystemLibrary("c++");
+    }
+
+    if (ctx.dia_guids_lib.len != 0) {
+        exe.addObjectFile(ctx.dia_guids_lib);
+    }
+
+    if (exe.target.getOs() != builtin.Os.windows) {
+        exe.linkSystemLibrary("xml2");
+    }
+    exe.linkSystemLibrary("c");
+}
+
+const Context = struct {
+    cmake_binary_dir: []const u8,
+    cxx_compiler: []const u8,
+    llvm_config_exe: []const u8,
+    lld_include_dir: []const u8,
+    lld_libraries: []const u8,
+    std_files: []const u8,
+    c_header_files: []const u8,
+    dia_guids_lib: []const u8,
+    llvm: LibraryDep,
+};
CMakeLists.txt
@@ -431,6 +431,7 @@ set(ZIG_CPP_SOURCES
 set(ZIG_STD_FILES
     "array_list.zig"
     "atomic/index.zig"
+    "atomic/int.zig"
     "atomic/queue_mpmc.zig"
     "atomic/queue_mpsc.zig"
     "atomic/stack.zig"