Commit 17d40ecb49

Andrew Kelley <andrew@ziglang.org>
2020-09-17 02:17:37
stage2: building libunwind.a
and put glibc shared objects on the elf linker line
1 parent 17f094e
src-self-hosted/link/Elf.zig
@@ -1,26 +1,28 @@
+const Elf = @This();
+
 const std = @import("std");
 const mem = std.mem;
 const assert = std.debug.assert;
 const Allocator = std.mem.Allocator;
-const ir = @import("../ir.zig");
-const Module = @import("../Module.zig");
-const Compilation = @import("../Compilation.zig");
 const fs = std.fs;
 const elf = std.elf;
-const codegen = @import("../codegen.zig");
 const log = std.log.scoped(.link);
 const DW = std.dwarf;
-const trace = @import("../tracy.zig").trace;
 const leb128 = std.debug.leb;
+
+const ir = @import("../ir.zig");
+const Module = @import("../Module.zig");
+const Compilation = @import("../Compilation.zig");
+const codegen = @import("../codegen.zig");
+const trace = @import("../tracy.zig").trace;
 const Package = @import("../Package.zig");
 const Value = @import("../value.zig").Value;
 const Type = @import("../type.zig").Type;
 const link = @import("../link.zig");
 const File = link.File;
-const Elf = @This();
 const build_options = @import("build_options");
 const target_util = @import("../target.zig");
-const fatal = @import("main.zig").fatal;
+const glibc = @import("../glibc.zig");
 
 const default_entry_addr = 0x8000000;
 
@@ -1530,15 +1532,19 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
                     try argv.append("-lpthread");
                 }
             } else if (target.isGnuLibC()) {
-                try argv.append(comp.libunwind_static_lib.?);
-                // TODO here we need to iterate over the glibc libs and add the .so files to the linker line.
-                std.log.warn("TODO port add_glibc_libs to stage2", .{});
+                try argv.append(comp.libunwind_static_lib.?.full_object_path);
+                for (glibc.libs) |lib| {
+                    const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{
+                        comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
+                    });
+                    try argv.append(lib_path);
+                }
                 try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a"));
             } else if (target.isMusl()) {
-                try argv.append(comp.libunwind_static_lib.?);
+                try argv.append(comp.libunwind_static_lib.?.full_object_path);
                 try argv.append(comp.libc_static_lib.?);
             } else if (self.base.options.link_libcpp) {
-                try argv.append(comp.libunwind_static_lib.?);
+                try argv.append(comp.libunwind_static_lib.?.full_object_path);
             } else {
                 unreachable; // Compiler was supposed to emit an error for not being able to provide libc.
             }
src-self-hosted/Compilation.zig
@@ -15,6 +15,7 @@ const liveness = @import("liveness.zig");
 const build_options = @import("build_options");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 const glibc = @import("glibc.zig");
+const libunwind = @import("libunwind.zig");
 const fatal = @import("main.zig").fatal;
 const Module = @import("Module.zig");
 const Cache = @import("Cache.zig");
@@ -63,7 +64,7 @@ libcxx_static_lib: ?[]const u8 = null,
 libcxxabi_static_lib: ?[]const u8 = null,
 /// Populated when we build libunwind.a. A WorkItem to build this is placed in the queue
 /// and resolved before calling linker.flush().
-libunwind_static_lib: ?[]const u8 = null,
+libunwind_static_lib: ?CRTFile = null,
 /// Populated when we build c.a. A WorkItem to build this is placed in the queue
 /// and resolved before calling linker.flush().
 libc_static_lib: ?[]const u8 = null,
@@ -115,6 +116,8 @@ const WorkItem = union(enum) {
     glibc_crt_file: glibc.CRTFile,
     /// all of the glibc shared objects
     glibc_shared_objects,
+
+    libunwind: void,
 };
 
 pub const CObject = struct {
@@ -206,6 +209,17 @@ pub const Directory = struct {
     /// `null` means cwd.
     path: ?[]const u8,
     handle: std.fs.Dir,
+
+    pub fn join(self: Directory, allocator: *Allocator, paths: []const []const u8) ![]u8 {
+        if (self.path) |p| {
+            // TODO clean way to do this with only 1 allocation
+            const part2 = try std.fs.path.join(allocator, paths);
+            defer allocator.free(part2);
+            return std.fs.path.join(allocator, &[_][]const u8{ p, part2 });
+        } else {
+            return std.fs.path.join(allocator, paths);
+        }
+    }
 };
 
 pub const EmitLoc = struct {
@@ -274,9 +288,6 @@ pub const InitOptions = struct {
     version: ?std.builtin.Version = null,
     libc_installation: ?*const LibCInstallation = null,
     machine_code_model: std.builtin.CodeModel = .default,
-    /// TODO Once self-hosted Zig is capable enough, we can remove this special-case
-    /// hack in favor of more general compilation options.
-    stage1_is_dummy_so: bool = false,
 };
 
 pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
@@ -636,6 +647,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
     if (comp.wantBuildGLibCFromSource()) {
         try comp.addBuildingGLibCWorkItems();
     }
+    if (comp.wantBuildLibUnwindFromSource()) {
+        try comp.work_queue.writeItem(.{ .libunwind = {} });
+    }
 
     return comp;
 }
@@ -656,6 +670,10 @@ pub fn destroy(self: *Compilation) void {
         self.crt_files.deinit(gpa);
     }
 
+    if (self.libunwind_static_lib) |*unwind_crt_file| {
+        unwind_crt_file.deinit(gpa);
+    }
+
     for (self.c_object_table.items()) |entry| {
         entry.key.destroy(gpa);
     }
@@ -936,6 +954,12 @@ pub fn performAllTheWork(self: *Compilation) error{OutOfMemory}!void {
                 fatal("unable to build glibc shared objects: {}", .{@errorName(err)});
             };
         },
+        .libunwind => {
+            libunwind.buildStaticLib(self) catch |err| {
+                // TODO Expose this as a normal compile error rather than crashing here.
+                fatal("unable to build libunwind: {}", .{@errorName(err)});
+            };
+        },
     };
 }
 
@@ -1597,3 +1621,13 @@ fn wantBuildGLibCFromSource(comp: *Compilation) bool {
         comp.bin_file.options.libc_installation == null and
         comp.bin_file.options.target.isGnuLibC();
 }
+
+fn wantBuildLibUnwindFromSource(comp: *Compilation) bool {
+    const is_exe_or_dyn_lib = switch (comp.bin_file.options.output_mode) {
+        .Obj => false,
+        .Lib => comp.bin_file.options.link_mode == .Dynamic,
+        .Exe => true,
+    };
+    return comp.bin_file.options.link_libc and is_exe_or_dyn_lib and
+        comp.bin_file.options.libc_installation == null;
+}
src-self-hosted/glibc.zig
@@ -751,6 +751,10 @@ pub fn buildSharedObjects(comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    if (!build_options.have_llvm) {
+        return error.ZigCompilerNotBuiltWithLLVMExtensions;
+    }
+
     var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
     defer arena_allocator.deinit();
     const arena = &arena_allocator.allocator;
@@ -987,7 +991,6 @@ fn buildSharedLib(
         .debug_link = comp.bin_file.options.debug_link,
         .clang_passthrough_mode = comp.clang_passthrough_mode,
         .version = version,
-        .stage1_is_dummy_so = true,
         .version_script = map_file_path,
         .override_soname = override_soname,
         .c_source_files = &c_source_files,
src-self-hosted/libunwind.zig
@@ -0,0 +1,144 @@
+const std = @import("std");
+const path = std.fs.path;
+const assert = std.debug.assert;
+
+const target_util = @import("target.zig");
+const Compilation = @import("Compilation.zig");
+const build_options = @import("build_options");
+const trace = @import("tracy.zig").trace;
+
+pub fn buildStaticLib(comp: *Compilation) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    if (!build_options.have_llvm) {
+        return error.ZigCompilerNotBuiltWithLLVMExtensions;
+    }
+
+    var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
+    defer arena_allocator.deinit();
+    const arena = &arena_allocator.allocator;
+
+    const root_name = "unwind";
+    const output_mode = .Lib;
+    const link_mode = .Static;
+    const target = comp.getTarget();
+    const basename = try std.zig.binNameAlloc(arena, root_name, target, output_mode, link_mode, null);
+
+    const emit_bin = Compilation.EmitLoc{
+        .directory = null, // Put it in the cache directory.
+        .basename = basename,
+    };
+
+    const unwind_src_list = [_][]const u8{
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "libunwind.cpp",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "Unwind-EHABI.cpp",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "Unwind-seh.cpp",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "UnwindLevel1.c",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "UnwindLevel1-gcc-ext.c",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "Unwind-sjlj.c",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "UnwindRegistersRestore.S",
+        "libunwind" ++ path.sep_str ++ "src" ++ path.sep_str ++ "UnwindRegistersSave.S",
+    };
+
+    var c_source_files: [unwind_src_list.len]Compilation.CSourceFile = undefined;
+    for (unwind_src_list) |unwind_src, i| {
+        var cflags = std.ArrayList([]const u8).init(arena);
+
+        switch (Compilation.classifyFileExt(unwind_src)) {
+            .c => {
+                try cflags.append("-std=c99");
+            },
+            .cpp => {
+                try cflags.appendSlice(&[_][]const u8{
+                    "-fno-rtti",
+                    "-I",
+                    try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxx", "include" }),
+                });
+            },
+            .assembly => {},
+            else => unreachable, // You can see the entire list of files just above.
+        }
+        try cflags.append("-I");
+        try cflags.append(try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libunwind", "include" }));
+        if (target_util.supports_fpic(target)) {
+            try cflags.append("-fPIC");
+        }
+        try cflags.append("-D_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS");
+        try cflags.append("-Wa,--noexecstack");
+
+        // This is intentionally always defined because the macro definition means, should it only
+        // build for the target specified by compiler defines. Since we pass -target the compiler
+        // defines will be correct.
+        try cflags.append("-D_LIBUNWIND_IS_NATIVE_ONLY");
+
+        if (comp.bin_file.options.optimize_mode == .Debug) {
+            try cflags.append("-D_DEBUG");
+        }
+        if (comp.bin_file.options.single_threaded) {
+            try cflags.append("-D_LIBUNWIND_HAS_NO_THREADS");
+        }
+        try cflags.append("-Wno-bitwise-conditional-parentheses");
+
+        c_source_files[i] = .{
+            .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{unwind_src}),
+            .extra_flags = cflags.items,
+        };
+    }
+    const sub_compilation = try Compilation.create(comp.gpa, .{
+        // TODO use the global cache directory here
+        .zig_cache_directory = comp.zig_cache_directory,
+        .zig_lib_directory = comp.zig_lib_directory,
+        .target = target,
+        .root_name = root_name,
+        .root_pkg = null,
+        .output_mode = output_mode,
+        .rand = comp.rand,
+        .libc_installation = comp.bin_file.options.libc_installation,
+        .emit_bin = emit_bin,
+        .optimize_mode = comp.bin_file.options.optimize_mode,
+        .link_mode = link_mode,
+        .want_sanitize_c = false,
+        .want_stack_check = false,
+        .want_valgrind = false,
+        .want_pic = comp.bin_file.options.pic,
+        .emit_h = null,
+        .strip = comp.bin_file.options.strip,
+        .is_native_os = comp.bin_file.options.is_native_os,
+        .self_exe_path = comp.self_exe_path,
+        .c_source_files = &c_source_files,
+        .debug_cc = comp.debug_cc,
+        .debug_link = comp.bin_file.options.debug_link,
+        .clang_passthrough_mode = comp.clang_passthrough_mode,
+        .link_libc = true,
+    });
+    defer sub_compilation.destroy();
+
+    try updateSubCompilation(sub_compilation);
+
+    assert(comp.libunwind_static_lib == null);
+    comp.libunwind_static_lib = Compilation.CRTFile{
+        .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{basename}),
+        .lock = sub_compilation.bin_file.toOwnedLock(),
+    };
+}
+
+fn updateSubCompilation(sub_compilation: *Compilation) !void {
+    try sub_compilation.update();
+
+    // Look for compilation errors in this sub_compilation
+    var errors = try sub_compilation.getAllErrorsAlloc();
+    defer errors.deinit(sub_compilation.gpa);
+
+    if (errors.list.len != 0) {
+        for (errors.list) |full_err_msg| {
+            std.log.err("{}:{}:{}: {}\n", .{
+                full_err_msg.src_path,
+                full_err_msg.line + 1,
+                full_err_msg.column + 1,
+                full_err_msg.msg,
+            });
+        }
+        return error.BuildingLibCObjectFailed;
+    }
+}
BRANCH_TODO
@@ -1,7 +1,10 @@
- * libunwind
- * stage1 C++ code integration
+ * make sure zig cc works
+   - using it as a preprocessor (-E)
+   - @breakpoint(); // TODO the first arg is empty string right? skip past that.
+   - try building some software
  * support rpaths in ELF linker code
  * build & link against compiler-rt
+   - stage1 C++ code integration
  * build & link againstn freestanding libc
  * add CLI support for a way to pass extra flags to c source files
  * implement the workaround for using LLVM to detect native CPU features
@@ -12,10 +15,6 @@
  * port the stage1 os.cpp code that raises the open fd limit
  * use global zig-cache dir for crt files
  * `zig translate-c`
- * make sure zig cc works
-   - using it as a preprocessor (-E)
-   - @breakpoint(); // TODO the first arg is empty string right? skip past that.
-   - try building some software
  * MachO LLD linking
  * COFF LLD linking
  * WASM LLD linking
@@ -54,3 +53,5 @@
    - make it possible for Package to not openDir and reference already existing resources.
  * rename src/ to src/stage1/
  * rename src-self-hosted/ to src/
+ * improve Directory.join to only use 1 allocation in a clean way.
+ * tracy builds with lc++
build.zig
@@ -73,6 +73,9 @@ pub fn build(b: *Builder) !void {
     if (only_install_lib_files)
         return;
 
+    const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source");
+    const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse enable_llvm;
+
     var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
     exe.install();
     exe.setBuildMode(mode);
@@ -90,10 +93,8 @@ pub fn build(b: *Builder) !void {
         var ctx = parseConfigH(b, config_h_text);
         ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
 
-        try configureStage2(b, exe, ctx);
+        try configureStage2(b, exe, ctx, tracy != null);
     }
-    const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source");
-    const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse enable_llvm;
     if (link_libc) {
         exe.linkLibC();
         test_stage2.linkLibC();
@@ -151,7 +152,9 @@ pub fn build(b: *Builder) !void {
         ) catch unreachable;
         exe.addIncludeDir(tracy_path);
         exe.addCSourceFile(client_cpp, &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" });
-        exe.linkSystemLibraryName("c++");
+        if (!enable_llvm) {
+            exe.linkSystemLibraryName("c++");
+        }
         exe.linkLibC();
     }
 
@@ -344,7 +347,7 @@ fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep {
     return result;
 }
 
-fn configureStage2(b: *Builder, exe: anytype, ctx: Context) !void {
+fn configureStage2(b: *Builder, exe: anytype, ctx: Context, need_cpp_includes: bool) !void {
     exe.addIncludeDir("src");
     exe.addIncludeDir(ctx.cmake_binary_dir);
     addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp");
@@ -377,7 +380,7 @@ fn configureStage2(b: *Builder, exe: anytype, ctx: Context) !void {
         if (exe.target.getOsTag() == .linux) {
             // First we try to static link against gcc libstdc++. If that doesn't work,
             // we fall back to -lc++ and cross our fingers.
-            addCxxKnownPath(b, ctx, exe, "libstdc++.a", "") catch |err| switch (err) {
+            addCxxKnownPath(b, ctx, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) {
                 error.RequiredLibraryNotFound => {
                     exe.linkSystemLibrary("c++");
                 },
@@ -386,12 +389,12 @@ fn configureStage2(b: *Builder, exe: anytype, ctx: Context) !void {
 
             exe.linkSystemLibrary("pthread");
         } else if (exe.target.isFreeBSD()) {
-            try addCxxKnownPath(b, ctx, exe, "libc++.a", null);
+            try addCxxKnownPath(b, ctx, exe, "libc++.a", null, need_cpp_includes);
             exe.linkSystemLibrary("pthread");
         } else if (exe.target.isDarwin()) {
-            if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "")) {
+            if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "", need_cpp_includes)) {
                 // Compiler is GCC.
-                try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null);
+                try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null, need_cpp_includes);
                 exe.linkSystemLibrary("pthread");
                 // TODO LLD cannot perform this link.
                 // See https://github.com/ziglang/zig/issues/1535
@@ -417,6 +420,7 @@ fn addCxxKnownPath(
     exe: anytype,
     objname: []const u8,
     errtxt: ?[]const u8,
+    need_cpp_includes: bool,
 ) !void {
     const path_padded = try b.exec(&[_][]const u8{
         ctx.cxx_compiler,
@@ -432,6 +436,16 @@ fn addCxxKnownPath(
         return error.RequiredLibraryNotFound;
     }
     exe.addObjectFile(path_unpadded);
+
+    // TODO a way to integrate with system c++ include files here
+    // cc -E -Wp,-v -xc++ /dev/null
+    if (need_cpp_includes) {
+        // I used these temporarily for testing something but we obviously need a
+        // more general purpose solution here.
+        //exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0");
+        //exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0/x86_64-unknown-linux-gnu");
+        //exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0/backward");
+    }
 }
 
 const Context = struct {