diff --git a/BRANCH_TODO b/BRANCH_TODO index 7292231d1f..0b4c13f8ae 100644 --- a/BRANCH_TODO +++ b/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++ diff --git a/build.zig b/build.zig index 961e143545..17ee040e8f 100644 --- a/build.zig +++ b/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 { diff --git a/src-self-hosted/Compilation.zig b/src-self-hosted/Compilation.zig index d99a85e1d3..92e9d89da0 100644 --- a/src-self-hosted/Compilation.zig +++ b/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; +} diff --git a/src-self-hosted/glibc.zig b/src-self-hosted/glibc.zig index f629400a1a..5bda249080 100644 --- a/src-self-hosted/glibc.zig +++ b/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, diff --git a/src-self-hosted/libunwind.zig b/src-self-hosted/libunwind.zig new file mode 100644 index 0000000000..19f77da2dc --- /dev/null +++ b/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; + } +} diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 8a75796bc3..5c2c73d740 100644 --- a/src-self-hosted/link/Elf.zig +++ b/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 fs = std.fs; +const elf = std.elf; +const log = std.log.scoped(.link); +const DW = std.dwarf; +const leb128 = std.debug.leb; + 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 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. }