From 36295d712fbd561c3de9b3eb46e776d63e646e9a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jul 2021 12:45:32 -0700 Subject: [PATCH 1/4] remove 'pe' object format Portable Executable is an executable format, not an object format. Everywhere in the entire zig codebase, we treated coff and pe as if they were the same. Remove confusion by not including pe in the std.Target.ObjectFormat enum. --- lib/std/target.zig | 1 - lib/std/zig.zig | 2 +- src/Compilation.zig | 2 +- src/link.zig | 6 +++--- src/link/Coff.zig | 7 ++----- src/main.zig | 20 +++++++------------- 6 files changed, 14 insertions(+), 24 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 70626f5051a0b967d9d26e57e9794bfc87c177a7..45bc4af1b3d06962813870829f69033683484838 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -550,7 +550,6 @@ pub const Target = struct { pub const ObjectFormat = enum { coff, - pe, elf, macho, wasm, diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 595dce77c2b072effa1f0cb5261eb63be43dfd57..70a1fd5997e7389ec4a7b5f646285249f190a05b 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -109,7 +109,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro const root_name = options.root_name; const target = options.target; switch (options.object_format orelse target.getObjectFormat()) { - .coff, .pe => switch (options.output_mode) { + .coff => switch (options.output_mode) { .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }), .Lib => { const suffix = switch (options.link_mode orelse .Static) { diff --git a/src/Compilation.zig b/src/Compilation.zig index 78d03d4534b0528e518f74760103137146999cb5..f460dbc7ca11b95e9d57f2b65f7a0a49a21e4ea8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3023,7 +3023,7 @@ pub fn addCCArgs( if (!comp.bin_file.options.strip) { try argv.append("-g"); switch (comp.bin_file.options.object_format) { - .coff, .pe => try argv.append("-gcodeview"), + .coff => try argv.append("-gcodeview"), else => {}, } } diff --git a/src/link.zig b/src/link.zig index 2403180ec8922b6db61e13eff11b8c4902c9dd35..562896d14cd2a7cc41405464619051c5be71e300 100644 --- a/src/link.zig +++ b/src/link.zig @@ -191,7 +191,7 @@ pub const File = struct { const use_stage1 = build_options.is_stage1 and options.use_stage1; if (use_stage1 or options.emit == null) { return switch (options.object_format) { - .coff, .pe => &(try Coff.createEmpty(allocator, options)).base, + .coff => &(try Coff.createEmpty(allocator, options)).base, .elf => &(try Elf.createEmpty(allocator, options)).base, .macho => &(try MachO.createEmpty(allocator, options)).base, .wasm => &(try Wasm.createEmpty(allocator, options)).base, @@ -208,7 +208,7 @@ pub const File = struct { if (options.module == null) { // No point in opening a file, we would not write anything to it. Initialize with empty. return switch (options.object_format) { - .coff, .pe => &(try Coff.createEmpty(allocator, options)).base, + .coff => &(try Coff.createEmpty(allocator, options)).base, .elf => &(try Elf.createEmpty(allocator, options)).base, .macho => &(try MachO.createEmpty(allocator, options)).base, .plan9 => &(try Plan9.createEmpty(allocator, options)).base, @@ -225,7 +225,7 @@ pub const File = struct { errdefer if (use_lld) allocator.free(sub_path); const file: *File = switch (options.object_format) { - .coff, .pe => &(try Coff.openPath(allocator, sub_path, options)).base, + .coff => &(try Coff.openPath(allocator, sub_path, options)).base, .elf => &(try Elf.openPath(allocator, sub_path, options)).base, .macho => &(try MachO.openPath(allocator, sub_path, options)).base, .plan9 => &(try Plan9.openPath(allocator, sub_path, options)).base, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 50ad6bc1a07d134cf838e4654ff3da644e8a8166..ba918ad10d4a2880dbf1565369894f908f5c9ced 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -657,10 +657,7 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { } pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { - if (build_options.skip_non_native and - builtin.object_format != .coff and - builtin.object_format != .pe) - { + if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); } if (build_options.have_llvm) { @@ -697,7 +694,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live } pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { - if (build_options.skip_non_native and builtin.object_format != .coff and builtin.object_format != .pe) { + if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); } if (build_options.have_llvm) { diff --git a/src/main.zig b/src/main.zig index 3b62bba410a78c433fff261bb78660b5c720d32e..9a51eba5f64adfaabbcc9a1165a156b53e4c0fbc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -287,9 +287,9 @@ const usage_build_generic = \\ .s Target-specific assembly source code \\ .S Assembly with C preprocessor (requires LLVM extensions) \\ .c C source code (requires LLVM extensions) - \\ .cpp C++ source code (requires LLVM extensions) - \\ Other C++ extensions: .C .cc .cxx + \\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions) \\ .m Objective-C source code (requires LLVM extensions) + \\ .bc LLVM IR Module (requires LLVM extensions) \\ \\General Options: \\ -h, --help Print this help and exit @@ -361,13 +361,12 @@ const usage_build_generic = \\ elf Executable and Linking Format \\ c Compile to C source code \\ wasm WebAssembly - \\ pe Portable Executable (Windows) \\ coff Common Object File Format (Windows) \\ macho macOS relocatables \\ spirv Standard, Portable Intermediate Representation V (SPIR-V) \\ plan9 Plan 9 from Bell Labs object format - \\ hex (planned) Intel IHEX - \\ raw (planned) Dump machine code directly + \\ hex (planned feature) Intel IHEX + \\ raw (planned feature) Dump machine code directly \\ -dirafter [dir] Add directory to AFTER include search path \\ -isystem [dir] Add directory to SYSTEM include search path \\ -I[dir] Add directory to include search path @@ -1708,8 +1707,6 @@ fn buildOutputType( break :blk .c; } else if (mem.eql(u8, ofmt, "coff")) { break :blk .coff; - } else if (mem.eql(u8, ofmt, "pe")) { - break :blk .pe; } else if (mem.eql(u8, ofmt, "macho")) { break :blk .macho; } else if (mem.eql(u8, ofmt, "wasm")) { @@ -1753,7 +1750,7 @@ fn buildOutputType( }; const a_out_basename = switch (object_format) { - .pe, .coff => "a.exe", + .coff => "a.exe", else => "a.out", }; @@ -2396,11 +2393,8 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi // If a .pdb file is part of the expected output, we must also copy // it into place here. - const coff_or_pe = switch (comp.bin_file.options.object_format) { - .coff, .pe => true, - else => false, - }; - const have_pdb = coff_or_pe and !comp.bin_file.options.strip; + const is_coff = comp.bin_file.options.object_format == .coff; + const have_pdb = is_coff and !comp.bin_file.options.strip; if (have_pdb) { // Replace `.out` or `.exe` with `.pdb` on both the source and destination const src_bin_ext = fs.path.extension(bin_sub_path); -- 2.54.0 From a5fb28070f37c2cad92ac8805bcc704e872fc538 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jul 2021 14:44:06 -0700 Subject: [PATCH 2/4] add -femit-llvm-bc CLI option and implement it * Added doc comments for `std.Target.ObjectFormat` enum * `std.Target.oFileExt` is removed because it is incorrect for Plan-9 targets. Instead, use `std.Target.ObjectFormat.fileExt` and pass a CPU architecture. * Added `Compilation.Directory.joinZ` for when a null byte is desired. * Improvements to `Compilation.create` logic for computing `use_llvm` and reporting errors in contradictory flags. `-femit-llvm-ir` and `-femit-llvm-bc` will now imply `-fLLVM`. * Fix compilation when passing `.bc` files on the command line. * Improvements to the stage2 LLVM backend: - cleaned up error messages and error reporting. Properly bubble up some errors rather than dumping to stderr; others turn into panics. - properly call ZigLLVMCreateTargetMachine and ZigLLVMTargetMachineEmitToFile and implement calculation of the respective parameters (cpu features, code model, abi name, lto, tsan, etc). - LLVM module verification only runs in debug builds of the compiler - use LLVMDumpModule rather than printToString because in the case that we incorrectly pass a null pointer to LLVM it may crash during dumping the module and having it partially printed is helpful in this case. - support -femit-asm, -fno-emit-bin, -femit-llvm-ir, -femit-llvm-bc - Support LLVM backend when used with Mach-O and WASM linkers. --- doc/docgen.zig | 8 +- lib/std/target.zig | 99 ++++++++++------- lib/std/zig.zig | 38 ++----- lib/std/zig/cross_target.zig | 4 - src/Compilation.zig | 43 +++++++- src/codegen/llvm.zig | 196 +++++++++++++++++++++++----------- src/codegen/llvm/bindings.zig | 48 ++++++--- src/link.zig | 10 +- src/link/Coff.zig | 18 ++-- src/link/Elf.zig | 16 +-- src/link/MachO.zig | 9 +- src/link/Wasm.zig | 9 +- src/main.zig | 38 +++++-- src/stage1.zig | 2 + src/stage1/all_types.hpp | 1 + src/stage1/codegen.cpp | 17 +-- src/stage1/stage1.cpp | 1 + src/stage1/stage1.h | 3 + src/zig_llvm.cpp | 19 +++- src/zig_llvm.h | 3 +- 20 files changed, 388 insertions(+), 194 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index 1261981af35b63805225bcdb650ad6355e1eea8a..f2d2e2845b8ed359454697cc897dc5b4e4603e3e 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const builtin = std.builtin; +const builtin = @import("builtin"); const io = std.io; const fs = std.fs; const process = std.process; @@ -13,7 +13,7 @@ const Allocator = std.mem.Allocator; const max_doc_file_size = 10 * 1024 * 1024; const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt(); -const obj_ext = @as(std.zig.CrossTarget, .{}).oFileExt(); +const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch); const tmp_dir_name = "docgen_tmp"; const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext; @@ -281,7 +281,7 @@ const Code = struct { name: []const u8, source_token: Token, is_inline: bool, - mode: builtin.Mode, + mode: std.builtin.Mode, link_objects: []const []const u8, target_str: ?[]const u8, link_libc: bool, @@ -531,7 +531,7 @@ fn genToc(allocator: *Allocator, tokenizer: *Tokenizer) !Toc { return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {s}", .{code_kind_str}); } - var mode: builtin.Mode = .Debug; + var mode: std.builtin.Mode = .Debug; var link_objects = std.ArrayList([]const u8).init(allocator); defer link_objects.deinit(); var target_str: ?[]const u8 = null; diff --git a/lib/std/target.zig b/lib/std/target.zig index 45bc4af1b3d06962813870829f69033683484838..1b9f0084c83a1e3c792a5e75290b395c8365212d 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -549,15 +549,36 @@ pub const Target = struct { }; pub const ObjectFormat = enum { + /// Common Object File Format (Windows) coff, + /// Executable and Linking Format elf, + /// macOS relocatables macho, + /// WebAssembly wasm, + /// C source code c, + /// Standard, Portable Intermediate Representation V spirv, + /// Intel IHEX hex, + /// Machine code with no metadata. raw, + /// Plan 9 from Bell Labs plan9, + + pub fn fileExt(of: ObjectFormat, cpu_arch: Cpu.Arch) [:0]const u8 { + return switch (of) { + .coff => ".obj", + .elf, .macho, .wasm => ".o", + .c => ".c", + .spirv => ".spv", + .hex => ".ihex", + .raw => ".bin", + .plan9 => plan9Ext(cpu_arch), + }; + } }; pub const SubSystem = enum { @@ -1289,30 +1310,16 @@ pub const Target = struct { return linuxTripleSimple(allocator, self.cpu.arch, self.os.tag, self.abi); } - pub fn oFileExt_os_abi(os_tag: Os.Tag, abi: Abi) [:0]const u8 { - if (abi == .msvc) { - return ".obj"; - } - switch (os_tag) { - .windows, .uefi => return ".obj", - else => return ".o", - } - } - - pub fn oFileExt(self: Target) [:0]const u8 { - return oFileExt_os_abi(self.os.tag, self.abi); - } - pub fn exeFileExtSimple(cpu_arch: Cpu.Arch, os_tag: Os.Tag) [:0]const u8 { - switch (os_tag) { - .windows => return ".exe", - .uefi => return ".efi", - else => if (cpu_arch.isWasm()) { - return ".wasm"; - } else { - return ""; + return switch (os_tag) { + .windows => ".exe", + .uefi => ".efi", + .plan9 => plan9Ext(cpu_arch), + else => switch (cpu_arch) { + .wasm32, .wasm64 => ".wasm", + else => "", }, - } + }; } pub fn exeFileExt(self: Target) [:0]const u8 { @@ -1352,20 +1359,16 @@ pub const Target = struct { } pub fn getObjectFormatSimple(os_tag: Os.Tag, cpu_arch: Cpu.Arch) ObjectFormat { - if (os_tag == .windows or os_tag == .uefi) { - return .coff; - } else if (os_tag.isDarwin()) { - return .macho; - } - if (cpu_arch.isWasm()) { - return .wasm; - } - if (cpu_arch.isSPIRV()) { - return .spirv; - } - if (os_tag == .plan9) - return .plan9; - return .elf; + return switch (os_tag) { + .windows, .uefi => .coff, + .ios, .macos, .watchos, .tvos => .macho, + .plan9 => .plan9, + else => return switch (cpu_arch) { + .wasm32, .wasm64 => .wasm, + .spirv32, .spirv64 => .spirv, + else => .elf, + }, + }; } pub fn getObjectFormat(self: Target) ObjectFormat { @@ -1676,6 +1679,30 @@ pub const Target = struct { return false; } + + /// 0c spim little-endian MIPS 3000 family + /// 1c 68000 Motorola MC68000 + /// 2c 68020 Motorola MC68020 + /// 5c arm little-endian ARM + /// 6c amd64 AMD64 and compatibles (e.g., Intel EM64T) + /// 7c arm64 ARM64 (ARMv8) + /// 8c 386 Intel i386, i486, Pentium, etc. + /// kc sparc Sun SPARC + /// qc power Power PC + /// vc mips big-endian MIPS 3000 family + pub fn plan9Ext(cpu_arch: Cpu.Arch) [:0]const u8 { + return switch (cpu_arch) { + .arm => ".5", + .x86_64 => ".6", + .aarch64 => ".7", + .i386 => ".8", + .sparc => ".k", + .powerpc, .powerpcle => ".q", + .mips, .mipsel => ".v", + // ISAs without designated characters get 'X' for lack of a better option. + else => ".X", + }; + } }; test { diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 70a1fd5997e7389ec4a7b5f646285249f190a05b..303c930b93829577311e29aac95be87f55d03034 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -108,7 +108,8 @@ pub const BinNameOptions = struct { pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 { const root_name = options.root_name; const target = options.target; - switch (options.object_format orelse target.getObjectFormat()) { + const ofmt = options.object_format orelse target.getObjectFormat(); + switch (ofmt) { .coff => switch (options.output_mode) { .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }), .Lib => { @@ -118,7 +119,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro }; return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix }); }, - .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }), + .Obj => return std.fmt.allocPrint(allocator, "{s}.obj", .{root_name}), }, .elf => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), @@ -140,7 +141,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro }, } }, - .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }), + .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), }, .macho => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), @@ -163,7 +164,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro } return std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ target.libPrefix(), root_name, suffix }); }, - .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }), + .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), }, .wasm => switch (options.output_mode) { .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }), @@ -175,36 +176,15 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro .Dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}), } }, - .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }), + .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), }, .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}), .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}), .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}), .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), - .plan9 => { - // copied from 2c(1) - // 0c spim little-endian MIPS 3000 family - // 1c 68000 Motorola MC68000 - // 2c 68020 Motorola MC68020 - // 5c arm little-endian ARM - // 6c amd64 AMD64 and compatibles (e.g., Intel EM64T) - // 7c arm64 ARM64 (ARMv8) - // 8c 386 Intel i386, i486, Pentium, etc. - // kc sparc Sun SPARC - // qc power Power PC - // vc mips big-endian MIPS 3000 family - const char: u8 = switch (target.cpu.arch) { - .arm => '5', - .x86_64 => '6', - .aarch64 => '7', - .i386 => '8', - .sparc => 'k', - .powerpc, .powerpcle => 'q', - .mips, .mipsel => 'v', - else => 'X', // this arch does not have a char or maybe was not ported to plan9 so we just use X - }; - return std.fmt.allocPrint(allocator, "{s}.{c}", .{ root_name, char }); - }, + .plan9 => return std.fmt.allocPrint(allocator, "{s}{s}", .{ + root_name, ofmt.fileExt(target.cpu.arch), + }), } } diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index e6ca0a2baacdfd817fdeab1beb73b604de9a0a6b..1058628633737a6c3cd9133a3c723d1ba725e07d 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -473,10 +473,6 @@ pub const CrossTarget = struct { return self.getOsTag() == .windows; } - pub fn oFileExt(self: CrossTarget) [:0]const u8 { - return Target.oFileExt_os_abi(self.getOsTag(), self.getAbi()); - } - pub fn exeFileExt(self: CrossTarget) [:0]const u8 { return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag()); } diff --git a/src/Compilation.zig b/src/Compilation.zig index f460dbc7ca11b95e9d57f2b65f7a0a49a21e4ea8..fb35f5153fcfcf51e1d47ff0e273b4cde31f83b0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -143,6 +143,7 @@ debug_compiler_runtime_libs: bool, emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, +emit_llvm_bc: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, @@ -586,6 +587,17 @@ pub const Directory = struct { return std.fs.path.join(allocator, paths); } } + + pub fn joinZ(self: Directory, allocator: *Allocator, paths: []const []const u8) ![:0]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.joinZ(allocator, &[_][]const u8{ p, part2 }); + } else { + return std.fs.path.joinZ(allocator, paths); + } + } }; pub const EmitLoc = struct { @@ -623,6 +635,8 @@ pub const InitOptions = struct { emit_asm: ?EmitLoc = null, /// `null` means to not emit LLVM IR. emit_llvm_ir: ?EmitLoc = null, + /// `null` means to not emit LLVM module bitcode. + emit_llvm_bc: ?EmitLoc = null, /// `null` means to not emit semantic analysis JSON. emit_analysis: ?EmitLoc = null, /// `null` means to not emit docs. @@ -819,6 +833,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :blk false; } } + // If we have no zig code to compile, no need for stage1 backend. + if (options.root_pkg == null) + break :blk false; + break :blk build_options.is_stage1; }; @@ -835,6 +853,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (ofmt == .c) break :blk false; + // If emitting to LLVM bitcode object format, must use LLVM backend. + if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) + break :blk true; + // The stage1 compiler depends on the stage1 C++ LLVM backend // to compile zig code. if (use_stage1) @@ -853,6 +875,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (options.machine_code_model != .default) { return error.MachineCodeModelNotSupportedWithoutLlvm; } + if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) { + return error.EmittingLlvmModuleRequiresUsingLlvmBackend; + } + if (use_stage1) { + return error.@"stage1 only supports LLVM backend"; + } } const tsan = options.want_tsan orelse false; @@ -1381,6 +1409,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .bin_file = bin_file, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, + .emit_llvm_bc = options.emit_llvm_bc, .emit_analysis = options.emit_analysis, .emit_docs = options.emit_docs, .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa), @@ -2728,7 +2757,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P comp.bin_file.options.root_name else c_source_basename[0 .. c_source_basename.len - std.fs.path.extension(c_source_basename).len]; - const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, comp.getTarget().oFileExt() }); + const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ + o_basename_noext, + comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch), + }); const digest = if (!comp.disable_c_depfile and try man.hit()) man.final() else blk: { var argv = std.ArrayList([]const u8).init(comp.gpa); @@ -3978,6 +4010,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node } man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); + man.hash.addOptionalEmitLoc(comp.emit_llvm_bc); man.hash.addOptionalEmitLoc(comp.emit_analysis); man.hash.addOptionalEmitLoc(comp.emit_docs); man.hash.add(comp.test_evented_io); @@ -4083,13 +4116,14 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node ) orelse return error.OutOfMemory; const emit_bin_path = if (comp.bin_file.options.emit != null) blk: { - const bin_basename = try std.zig.binNameAlloc(arena, .{ + const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = comp.bin_file.options.root_name, .target = target, .output_mode = .Obj, }); - break :blk try directory.join(arena, &[_][]const u8{bin_basename}); + break :blk try directory.join(arena, &[_][]const u8{obj_basename}); } else ""; + if (mod.emit_h != null) { log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); } @@ -4097,6 +4131,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node const emit_h_path = try stage1LocPath(arena, emit_h_loc, directory); const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); + const emit_llvm_bc_path = try stage1LocPath(arena, comp.emit_llvm_bc, directory); const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); const emit_docs_path = try stage1LocPath(arena, comp.emit_docs, directory); const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null); @@ -4117,6 +4152,8 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .emit_asm_len = emit_asm_path.len, .emit_llvm_ir_ptr = emit_llvm_ir_path.ptr, .emit_llvm_ir_len = emit_llvm_ir_path.len, + .emit_bitcode_ptr = emit_llvm_bc_path.ptr, + .emit_bitcode_len = emit_llvm_bc_path.len, .emit_analysis_json_ptr = emit_analysis_path.ptr, .emit_analysis_json_len = emit_analysis_path.len, .emit_docs_ptr = emit_docs_path.ptr, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 81484e93db59fc4099ce86188c47f3c1dd683cce..92fa32dacf420ea4463c33a6cab5102d39896218 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -72,9 +72,9 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { .renderscript32 => "renderscript32", .renderscript64 => "renderscript64", .ve => "ve", - .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, - .spirv32 => return error.LLVMBackendDoesNotSupportSPIRV, - .spirv64 => return error.LLVMBackendDoesNotSupportSPIRV, + .spu_2 => return error.@"LLVM backend does not support SPU Mark II", + .spirv32 => return error.@"LLVM backend does not support SPIR-V", + .spirv64 => return error.@"LLVM backend does not support SPIR-V", }; const llvm_os = switch (target.os.tag) { @@ -114,11 +114,13 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { .wasi => "wasi", .emscripten => "emscripten", .uefi => "windows", - .opencl => return error.LLVMBackendDoesNotSupportOpenCL, - .glsl450 => return error.LLVMBackendDoesNotSupportGLSL450, - .vulkan => return error.LLVMBackendDoesNotSupportVulkan, - .plan9 => return error.LLVMBackendDoesNotSupportPlan9, - .other => "unknown", + + .opencl, + .glsl450, + .vulkan, + .plan9, + .other, + => "unknown", }; const llvm_abi = switch (target.abi) { @@ -152,84 +154,105 @@ pub const Object = struct { llvm_module: *const llvm.Module, context: *const llvm.Context, target_machine: *const llvm.TargetMachine, - object_pathZ: [:0]const u8, - pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Object { - _ = sub_path; - const self = try allocator.create(Object); - errdefer allocator.destroy(self); - - const obj_basename = try std.zig.binNameAlloc(allocator, .{ - .root_name = options.root_name, - .target = options.target, - .output_mode = .Obj, - }); - defer allocator.free(obj_basename); - - const o_directory = options.module.?.zig_cache_artifact_directory; - const object_path = try o_directory.join(allocator, &[_][]const u8{obj_basename}); - defer allocator.free(object_path); - - const object_pathZ = try allocator.dupeZ(u8, object_path); - errdefer allocator.free(object_pathZ); + pub fn create(gpa: *Allocator, options: link.Options) !*Object { + const obj = try gpa.create(Object); + errdefer gpa.destroy(obj); + obj.* = try Object.init(gpa, options); + return obj; + } + pub fn init(gpa: *Allocator, options: link.Options) !Object { const context = llvm.Context.create(); errdefer context.dispose(); initializeLLVMTargets(); - const root_nameZ = try allocator.dupeZ(u8, options.root_name); - defer allocator.free(root_nameZ); + const root_nameZ = try gpa.dupeZ(u8, options.root_name); + defer gpa.free(root_nameZ); const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context); errdefer llvm_module.dispose(); - const llvm_target_triple = try targetTriple(allocator, options.target); - defer allocator.free(llvm_target_triple); + const llvm_target_triple = try targetTriple(gpa, options.target); + defer gpa.free(llvm_target_triple); var error_message: [*:0]const u8 = undefined; var target: *const llvm.Target = undefined; if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message).toBool()) { defer llvm.disposeMessage(error_message); - const stderr = std.io.getStdErr().writer(); - try stderr.print( - \\Zig is expecting LLVM to understand this target: '{s}' - \\However LLVM responded with: "{s}" - \\ - , - .{ llvm_target_triple, error_message }, - ); - return error.InvalidLLVMTriple; + log.err("LLVM failed to parse '{s}': {s}", .{ llvm_target_triple, error_message }); + return error.InvalidLlvmTriple; } - const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else .Aggressive; + const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) + .None + else + .Aggressive; + + const reloc_mode: llvm.RelocMode = if (options.pic) + .PIC + else if (options.link_mode == .Dynamic) + llvm.RelocMode.DynamicNoPIC + else + .Static; + + const code_model: llvm.CodeModel = switch (options.machine_code_model) { + .default => .Default, + .tiny => .Tiny, + .small => .Small, + .kernel => .Kernel, + .medium => .Medium, + .large => .Large, + }; + + // TODO handle float ABI better- it should depend on the ABI portion of std.Target + const float_abi: llvm.ABIType = .Default; + + // TODO a way to override this as part of std.Target ABI? + const abi_name: ?[*:0]const u8 = switch (options.target.cpu.arch) { + .riscv32 => switch (options.target.os.tag) { + .linux => "ilp32d", + else => "ilp32", + }, + .riscv64 => switch (options.target.os.tag) { + .linux => "lp64d", + else => "lp64", + }, + else => null, + }; + const target_machine = llvm.TargetMachine.create( target, llvm_target_triple.ptr, - "", - "", + if (options.target.cpu.model.llvm_name) |s| s.ptr else null, + options.llvm_cpu_features, opt_level, - .Static, - .Default, + reloc_mode, + code_model, + options.function_sections, + float_abi, + abi_name, ); errdefer target_machine.dispose(); - self.* = .{ + return Object{ .llvm_module = llvm_module, .context = context, .target_machine = target_machine, - .object_pathZ = object_pathZ, }; - return self; } - pub fn deinit(self: *Object, allocator: *Allocator) void { + pub fn deinit(self: *Object) void { self.target_machine.dispose(); self.llvm_module.dispose(); self.context.dispose(); + self.* = undefined; + } - allocator.free(self.object_pathZ); - allocator.destroy(self); + pub fn destroy(self: *Object, gpa: *Allocator) void { + self.deinit(); + gpa.destroy(self); } fn initializeLLVMTargets() void { @@ -240,38 +263,81 @@ pub const Object = struct { llvm.initializeAllAsmParsers(); } + fn locPath( + arena: *Allocator, + opt_loc: ?Compilation.EmitLoc, + cache_directory: Compilation.Directory, + ) !?[*:0]u8 { + const loc = opt_loc orelse return null; + const directory = loc.directory orelse cache_directory; + const slice = try directory.joinZ(arena, &[_][]const u8{loc.basename}); + return slice.ptr; + } + pub fn flushModule(self: *Object, comp: *Compilation) !void { if (comp.verbose_llvm_ir) { - const dump = self.llvm_module.printToString(); - defer llvm.disposeMessage(dump); - - const stderr = std.io.getStdErr().writer(); - try stderr.writeAll(std.mem.spanZ(dump)); + self.llvm_module.dump(); } - { + if (std.debug.runtime_safety) { var error_message: [*:0]const u8 = undefined; // verifyModule always allocs the error_message even if there is no error defer llvm.disposeMessage(error_message); if (self.llvm_module.verify(.ReturnStatus, &error_message).toBool()) { - const stderr = std.io.getStdErr().writer(); - try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); - return error.BrokenLLVMModule; + std.debug.print("\n{s}\n", .{error_message}); + @panic("LLVM module verification failed"); } } + var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa); + defer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + const mod = comp.bin_file.options.module.?; + const cache_dir = mod.zig_cache_artifact_directory; + + const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit != null) blk: { + const obj_basename = try std.zig.binNameAlloc(arena, .{ + .root_name = comp.bin_file.options.root_name, + .target = comp.bin_file.options.target, + .output_mode = .Obj, + }); + if (cache_dir.joinZ(arena, &[_][]const u8{obj_basename})) |p| { + break :blk p.ptr; + } else |err| { + return err; + } + } else null; + + const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir); + const emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir); + const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir); + var error_message: [*:0]const u8 = undefined; if (self.target_machine.emitToFile( self.llvm_module, - self.object_pathZ.ptr, - .ObjectFile, &error_message, - ).toBool()) { + comp.bin_file.options.optimize_mode == .Debug, + comp.bin_file.options.optimize_mode == .ReleaseSmall, + comp.time_report, + comp.bin_file.options.tsan, + comp.bin_file.options.lto, + emit_asm_path, + emit_bin_path, + emit_llvm_ir_path, + emit_llvm_bc_path, + )) { defer llvm.disposeMessage(error_message); - const stderr = std.io.getStdErr().writer(); - try stderr.print("LLVM failed to emit file: {s}\n", .{error_message}); + const emit_asm_msg = emit_asm_path orelse "(none)"; + const emit_bin_msg = emit_bin_path orelse "(none)"; + const emit_llvm_ir_msg = emit_llvm_ir_path orelse "(none)"; + const emit_llvm_bc_msg = emit_llvm_bc_path orelse "(none)"; + log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{ + emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg, + error_message, + }); return error.FailedToEmit; } } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index f3a1a72f3d69bfc9d46978c9ec3966f9145ee2fa..f45ea8f979c6bbeb8ddd16daa5353b040e06f081 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -123,6 +123,9 @@ pub const Module = opaque { pub const getNamedGlobal = LLVMGetNamedGlobal; extern fn LLVMGetNamedGlobal(M: *const Module, Name: [*:0]const u8) ?*const Value; + + pub const dump = LLVMDumpModule; + extern fn LLVMDumpModule(M: *const Module) void; }; pub const lookupIntrinsicID = LLVMLookupIntrinsicID; @@ -250,31 +253,41 @@ pub const BasicBlock = opaque { }; pub const TargetMachine = opaque { - pub const create = LLVMCreateTargetMachine; - extern fn LLVMCreateTargetMachine( + pub const create = ZigLLVMCreateTargetMachine; + extern fn ZigLLVMCreateTargetMachine( T: *const Target, Triple: [*:0]const u8, - CPU: [*:0]const u8, - Features: [*:0]const u8, + CPU: ?[*:0]const u8, + Features: ?[*:0]const u8, Level: CodeGenOptLevel, Reloc: RelocMode, - CodeModel: CodeMode, + CodeModel: CodeModel, + function_sections: bool, + float_abi: ABIType, + abi_name: ?[*:0]const u8, ) *const TargetMachine; pub const dispose = LLVMDisposeTargetMachine; extern fn LLVMDisposeTargetMachine(T: *const TargetMachine) void; - pub const emitToFile = LLVMTargetMachineEmitToFile; - extern fn LLVMTargetMachineEmitToFile( - *const TargetMachine, + pub const emitToFile = ZigLLVMTargetMachineEmitToFile; + extern fn ZigLLVMTargetMachineEmitToFile( + T: *const TargetMachine, M: *const Module, - Filename: [*:0]const u8, - codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8, - ) Bool; + is_debug: bool, + is_small: bool, + time_report: bool, + tsan: bool, + lto: bool, + asm_filename: ?[*:0]const u8, + bin_filename: ?[*:0]const u8, + llvm_ir_filename: ?[*:0]const u8, + bitcode_filename: ?[*:0]const u8, + ) bool; }; -pub const CodeMode = enum(c_int) { +pub const CodeModel = enum(c_int) { Default, JITDefault, Tiny, @@ -295,7 +308,7 @@ pub const RelocMode = enum(c_int) { Default, Static, PIC, - DynamicNoPic, + DynamicNoPIC, ROPI, RWPI, ROPI_RWPI, @@ -306,6 +319,15 @@ pub const CodeGenFileType = enum(c_int) { ObjectFile, }; +pub const ABIType = enum(c_int) { + /// Target-specific (either soft or hard depending on triple, etc). + Default, + /// Soft float. + Soft, + // Hard float. + Hard, +}; + pub const Target = opaque { pub const getFromTriple = LLVMGetTargetFromTriple; extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) Bool; diff --git a/src/link.zig b/src/link.zig index 562896d14cd2a7cc41405464619051c5be71e300..85ff2ca603eb95465c0c346a182623362d76b236 100644 --- a/src/link.zig +++ b/src/link.zig @@ -206,7 +206,8 @@ pub const File = struct { const use_lld = build_options.have_llvm and options.use_lld; // comptime known false when !have_llvm const sub_path = if (use_lld) blk: { if (options.module == null) { - // No point in opening a file, we would not write anything to it. Initialize with empty. + // No point in opening a file, we would not write anything to it. + // Initialize with empty. return switch (options.object_format) { .coff => &(try Coff.createEmpty(allocator, options)).base, .elf => &(try Elf.createEmpty(allocator, options)).base, @@ -219,8 +220,11 @@ pub const File = struct { .raw => return error.RawObjectFormatUnimplemented, }; } - // Open a temporary object file, not the final output file because we want to link with LLD. - break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ emit.sub_path, options.target.oFileExt() }); + // Open a temporary object file, not the final output file because we + // want to link with LLD. + break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ + emit.sub_path, options.object_format.fileExt(options.target.cpu.arch), + }); } else emit.sub_path; errdefer if (use_lld) allocator.free(sub_path); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ba918ad10d4a2880dbf1565369894f908f5c9ced..0bae2cc6cca9d4cb85b2e4026dd6259d12f23e6a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -17,9 +17,9 @@ const link = @import("../link.zig"); const build_options = @import("build_options"); const Cache = @import("../Cache.zig"); const mingw = @import("../mingw.zig"); -const llvm_backend = @import("../codegen/llvm.zig"); const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); +const LlvmObject = @import("../codegen/llvm.zig").Object; const allocation_padding = 4 / 3; const minimum_text_block_size = 64 * allocation_padding; @@ -37,7 +37,7 @@ pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_object: ?*llvm_backend.Object = null, +llvm_object: ?*LlvmObject = null, base: link.File, ptr_width: PtrWidth, @@ -132,7 +132,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options); + self.llvm_object = try LlvmObject.create(allocator, options); return self; } @@ -820,8 +820,11 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - if (build_options.have_llvm) - if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp); + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| { + return try llvm_object.flushModule(comp); + } + } if (self.text_section_size_dirty) { // Write the new raw size in the .text header @@ -1395,8 +1398,9 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v } pub fn deinit(self: *Coff) void { - if (build_options.have_llvm) - if (self.llvm_object) |ir_module| ir_module.deinit(self.base.allocator); + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + } self.text_block_free_list.deinit(self.base.allocator); self.offset_table.deinit(self.base.allocator); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 315dfb563bc38462577bf0c27c47f26a78ad0834..b1eb219cf6462ff43cddb48fcfdc17bb10f2618b 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -25,9 +25,9 @@ const target_util = @import("../target.zig"); const glibc = @import("../glibc.zig"); const musl = @import("../musl.zig"); const Cache = @import("../Cache.zig"); -const llvm_backend = @import("../codegen/llvm.zig"); const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); +const LlvmObject = @import("../codegen/llvm.zig").Object; const default_entry_addr = 0x8000000; @@ -38,7 +38,7 @@ base: File, ptr_width: PtrWidth, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_object: ?*llvm_backend.Object = null, +llvm_object: ?*LlvmObject = null, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. @@ -235,7 +235,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options); + self.llvm_object = try LlvmObject.create(allocator, options); return self; } @@ -301,9 +301,9 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf { } pub fn deinit(self: *Elf) void { - if (build_options.have_llvm) - if (self.llvm_object) |ir_module| - ir_module.deinit(self.base.allocator); + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + } self.sections.deinit(self.base.allocator); self.program_headers.deinit(self.base.allocator); @@ -750,8 +750,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { if (build_options.have_llvm) if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp); - // TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the - // Zig source code. + // TODO This linker code currently assumes there is only 1 compilation unit and it + // corresponds to the Zig source code. const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; const target_endian = self.base.options.target.cpu.arch.endian(); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 02ea5856f48ee2f32144384ae096659788302a43..8675295b2a75758fa6980024ceba782051729785 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -30,7 +30,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Trie = @import("MachO/Trie.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); const Zld = @import("MachO/Zld.zig"); -const llvm_backend = @import("../codegen/llvm.zig"); +const LlvmObject = @import("../codegen/llvm.zig").Object; usingnamespace @import("MachO/commands.zig"); @@ -39,7 +39,7 @@ pub const base_tag: File.Tag = File.Tag.macho; base: File, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_object: ?*llvm_backend.Object = null, +llvm_object: ?*LlvmObject = null, /// Debug symbols bundle (or dSym). d_sym: ?DebugSymbols = null, @@ -355,7 +355,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options); + self.llvm_object = try LlvmObject.create(allocator, options); return self; } @@ -989,6 +989,9 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 { } pub fn deinit(self: *MachO) void { + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + } if (self.d_sym) |*ds| { ds.deinit(self.base.allocator); } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index f478d2ee471faf4de224c8b68b999808bbbedd5d..f8803dfcb72cbec52d81694b1f6dba97b077c06e 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -19,7 +19,7 @@ const build_options = @import("build_options"); const wasi_libc = @import("../wasi_libc.zig"); const Cache = @import("../Cache.zig"); const TypedValue = @import("../TypedValue.zig"); -const llvm_backend = @import("../codegen/llvm.zig"); +const LlvmObject = @import("../codegen/llvm.zig").Object; const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); @@ -27,7 +27,7 @@ pub const base_tag = link.File.Tag.wasm; base: link.File, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_object: ?*llvm_backend.Object = null, +llvm_object: ?*LlvmObject = null, /// List of all function Decls to be written to the output file. The index of /// each Decl in this list at the time of writing the binary is used as the /// function index. In the event where ext_funcs' size is not 0, the index of @@ -121,7 +121,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options); + self.llvm_object = try LlvmObject.create(allocator, options); return self; } @@ -153,6 +153,9 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm { } pub fn deinit(self: *Wasm) void { + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + } for (self.symbols.items) |decl| { decl.fn_link.wasm.functype.deinit(self.base.allocator); decl.fn_link.wasm.code.deinit(self.base.allocator); diff --git a/src/main.zig b/src/main.zig index 9a51eba5f64adfaabbcc9a1165a156b53e4c0fbc..db7fc17df0f311210c44c0e95deb6c1eca755b62 100644 --- a/src/main.zig +++ b/src/main.zig @@ -301,6 +301,8 @@ const usage_build_generic = \\ -fno-emit-asm (default) Do not output .s (assembly code) \\ -femit-llvm-ir[=path] Produce a .ll file with LLVM IR (requires LLVM extensions) \\ -fno-emit-llvm-ir (default) Do not produce a .ll file with LLVM IR + \\ -femit-llvm-bc[=path] Produce a LLVM module as a .bc file (requires LLVM extensions) + \\ -fno-emit-llvm-bc (default) Do not produce a LLVM module as a .bc file \\ -femit-h[=path] Generate a C header file (.h) \\ -fno-emit-h (default) Do not generate a C header file (.h) \\ -femit-docs[=path] Create a docs/ dir with html documentation @@ -359,7 +361,7 @@ const usage_build_generic = \\ --single-threaded Code assumes it is only used single-threaded \\ -ofmt=[mode] Override target object format \\ elf Executable and Linking Format - \\ c Compile to C source code + \\ c C source code \\ wasm WebAssembly \\ coff Common Object File Format (Windows) \\ macho macOS relocatables @@ -551,6 +553,7 @@ fn buildOutputType( var emit_bin: EmitBin = .yes_default_path; var emit_asm: Emit = .no; var emit_llvm_ir: Emit = .no; + var emit_llvm_bc: Emit = .no; var emit_docs: Emit = .no; var emit_analysis: Emit = .no; var target_arch_os_abi: []const u8 = "native"; @@ -1010,6 +1013,12 @@ fn buildOutputType( emit_llvm_ir = .{ .yes = arg["-femit-llvm-ir=".len..] }; } else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) { emit_llvm_ir = .no; + } else if (mem.eql(u8, arg, "-femit-llvm-bc")) { + emit_llvm_bc = .yes_default_path; + } else if (mem.startsWith(u8, arg, "-femit-llvm-bc=")) { + emit_llvm_bc = .{ .yes = arg["-femit-llvm-bc=".len..] }; + } else if (mem.eql(u8, arg, "-fno-emit-llvm-bc")) { + emit_llvm_bc = .no; } else if (mem.eql(u8, arg, "-femit-docs")) { emit_docs = .yes_default_path; } else if (mem.startsWith(u8, arg, "-femit-docs=")) { @@ -1815,10 +1824,10 @@ fn buildOutputType( var emit_h_resolved = emit_h.resolve(default_h_basename) catch |err| { switch (emit_h) { .yes => { - fatal("unable to open directory from argument 'femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) }); + fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) }); }, .yes_default_path => { - fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_h_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_h_basename, @errorName(err) }); }, .no => unreachable, } @@ -1829,10 +1838,10 @@ fn buildOutputType( var emit_asm_resolved = emit_asm.resolve(default_asm_basename) catch |err| { switch (emit_asm) { .yes => { - fatal("unable to open directory from argument 'femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) }); + fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) }); }, .yes_default_path => { - fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_asm_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_asm_basename, @errorName(err) }); }, .no => unreachable, } @@ -1843,16 +1852,30 @@ fn buildOutputType( var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename) catch |err| { switch (emit_llvm_ir) { .yes => { - fatal("unable to open directory from argument 'femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) }); + fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) }); }, .yes_default_path => { - fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) }); }, .no => unreachable, } }; defer emit_llvm_ir_resolved.deinit(); + const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name}); + var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename) catch |err| { + switch (emit_llvm_bc) { + .yes => { + fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ emit_llvm_bc.yes, @errorName(err) }); + }, + .yes_default_path => { + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_bc_basename, @errorName(err) }); + }, + .no => unreachable, + } + }; + defer emit_llvm_bc_resolved.deinit(); + const default_analysis_basename = try std.fmt.allocPrint(arena, "{s}-analysis.json", .{root_name}); var emit_analysis_resolved = emit_analysis.resolve(default_analysis_basename) catch |err| { switch (emit_analysis) { @@ -1988,6 +2011,7 @@ fn buildOutputType( .emit_h = emit_h_resolved.data, .emit_asm = emit_asm_resolved.data, .emit_llvm_ir = emit_llvm_ir_resolved.data, + .emit_llvm_bc = emit_llvm_bc_resolved.data, .emit_docs = emit_docs_resolved.data, .emit_analysis = emit_analysis_resolved.data, .link_mode = link_mode, diff --git a/src/stage1.zig b/src/stage1.zig index 2284e512ec292cc1db2c609203949da3d919a9a1..0108e9fc6455ad41333daf467de36ba181805977 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -95,6 +95,8 @@ pub const Module = extern struct { emit_asm_len: usize, emit_llvm_ir_ptr: [*]const u8, emit_llvm_ir_len: usize, + emit_bitcode_ptr: [*]const u8, + emit_bitcode_len: usize, emit_analysis_json_ptr: [*]const u8, emit_analysis_json_len: usize, emit_docs_ptr: [*]const u8, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index c673922335b2561fbf017e452986c078958a1826..ccdbf6b1553233eaacc13ebd5dfd507e9d854c4a 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2090,6 +2090,7 @@ struct CodeGen { Buf h_file_output_path; Buf asm_file_output_path; Buf llvm_ir_file_output_path; + Buf bitcode_file_output_path; Buf analysis_json_output_path; Buf docs_output_path; diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 9ab0e269a277a4ea9b484ed511f978c307b2789c..582625c7ff6d271797538c3aa37d7941fbb212ec 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -8506,19 +8506,22 @@ static void zig_llvm_emit_output(CodeGen *g) { const char *asm_filename = nullptr; const char *bin_filename = nullptr; const char *llvm_ir_filename = nullptr; + const char *bitcode_filename = nullptr; if (buf_len(&g->o_file_output_path) != 0) bin_filename = buf_ptr(&g->o_file_output_path); if (buf_len(&g->asm_file_output_path) != 0) asm_filename = buf_ptr(&g->asm_file_output_path); if (buf_len(&g->llvm_ir_file_output_path) != 0) llvm_ir_filename = buf_ptr(&g->llvm_ir_file_output_path); + if (buf_len(&g->bitcode_file_output_path) != 0) bitcode_filename = buf_ptr(&g->bitcode_file_output_path); - // Unfortunately, LLVM shits the bed when we ask for both binary and assembly. So we call the entire - // pipeline multiple times if this is requested. + // Unfortunately, LLVM shits the bed when we ask for both binary and assembly. + // So we call the entire pipeline multiple times if this is requested. if (asm_filename != nullptr && bin_filename != nullptr) { if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled, - g->have_lto, nullptr, bin_filename, llvm_ir_filename)) + g->have_lto, nullptr, bin_filename, llvm_ir_filename, nullptr)) { - fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg); + fprintf(stderr, "LLVM failed to emit bin=%s, ir=%s: %s\n", + bin_filename, llvm_ir_filename, err_msg); exit(1); } bin_filename = nullptr; @@ -8527,9 +8530,11 @@ static void zig_llvm_emit_output(CodeGen *g) { if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled, - g->have_lto, asm_filename, bin_filename, llvm_ir_filename)) + g->have_lto, asm_filename, bin_filename, llvm_ir_filename, bitcode_filename)) { - fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg); + fprintf(stderr, "LLVM failed to emit asm=%s, bin=%s, ir=%s, bc=%s: %s\n", + asm_filename, bin_filename, llvm_ir_filename, bitcode_filename, + err_msg); exit(1); } diff --git a/src/stage1/stage1.cpp b/src/stage1/stage1.cpp index e7c316f3854c8e1475088e259874f03ad0f37d8a..38d236c560eac6a8cedd76a863957f882457d437 100644 --- a/src/stage1/stage1.cpp +++ b/src/stage1/stage1.cpp @@ -73,6 +73,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) { buf_init_from_mem(&g->h_file_output_path, stage1->emit_h_ptr, stage1->emit_h_len); buf_init_from_mem(&g->asm_file_output_path, stage1->emit_asm_ptr, stage1->emit_asm_len); buf_init_from_mem(&g->llvm_ir_file_output_path, stage1->emit_llvm_ir_ptr, stage1->emit_llvm_ir_len); + buf_init_from_mem(&g->bitcode_file_output_path, stage1->emit_bitcode_ptr, stage1->emit_bitcode_len); buf_init_from_mem(&g->analysis_json_output_path, stage1->emit_analysis_json_ptr, stage1->emit_analysis_json_len); buf_init_from_mem(&g->docs_output_path, stage1->emit_docs_ptr, stage1->emit_docs_len); diff --git a/src/stage1/stage1.h b/src/stage1/stage1.h index 3bdb5500cffcd056a51b2cca6b5267c5c49c94f6..44f3c625c045438f8d1ef6aeeb0d6ed0c546e78f 100644 --- a/src/stage1/stage1.h +++ b/src/stage1/stage1.h @@ -157,6 +157,9 @@ struct ZigStage1 { const char *emit_llvm_ir_ptr; size_t emit_llvm_ir_len; + const char *emit_bitcode_ptr; + size_t emit_bitcode_len; + const char *emit_analysis_json_ptr; size_t emit_analysis_json_len; diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 6d40f4089a9fa6ac5ff97a765c21beaf5f3a76b4..d5d6f9f67015a7199f65dcf9961d790ab3ecaf6c 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -229,12 +229,14 @@ struct TimeTracerRAII { bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, char **error_message, bool is_debug, bool is_small, bool time_report, bool tsan, bool lto, - const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename) + const char *asm_filename, const char *bin_filename, + const char *llvm_ir_filename, const char *bitcode_filename) { TimePassesIsEnabled = time_report; raw_fd_ostream *dest_asm_ptr = nullptr; raw_fd_ostream *dest_bin_ptr = nullptr; + raw_fd_ostream *dest_bitcode_ptr = nullptr; if (asm_filename) { std::error_code EC; @@ -252,9 +254,19 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM return true; } } + if (bitcode_filename) { + std::error_code EC; + dest_bitcode_ptr = new(std::nothrow) raw_fd_ostream(bitcode_filename, EC, sys::fs::F_None); + if (EC) { + *error_message = strdup((const char *)StringRef(EC.message()).bytes_begin()); + return true; + } + } std::unique_ptr dest_asm(dest_asm_ptr), - dest_bin(dest_bin_ptr); + dest_bin(dest_bin_ptr), + dest_bitcode(dest_bitcode_ptr); + auto PID = sys::Process::getProcessId(); std::string ProcName = "zig-"; @@ -389,6 +401,9 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM if (dest_bin && lto) { WriteBitcodeToFile(module, *dest_bin); } + if (dest_bitcode) { + WriteBitcodeToFile(module, *dest_bitcode); + } if (time_report) { TimerGroup::printAll(errs()); diff --git a/src/zig_llvm.h b/src/zig_llvm.h index a2b3c6b92ed33d433e26b08a017ae8907581ea66..a771491138da071f4b6eed627daf67d5a5cab7a9 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -49,7 +49,8 @@ ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void); ZIG_EXTERN_C bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, char **error_message, bool is_debug, bool is_small, bool time_report, bool tsan, bool lto, - const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename); + const char *asm_filename, const char *bin_filename, + const char *llvm_ir_filename, const char *bitcode_filename); enum ZigLLVMABIType { -- 2.54.0 From 7c25390c957273ff43927608a45e257c4ed73549 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jul 2021 16:37:38 -0700 Subject: [PATCH 3/4] support -fcompiler-rt in conjunction with build-obj When using `build-exe` or `build-lib -dynamic`, `-fcompiler-rt` means building compiler-rt into a static library and then linking it into the executable. When using `build-lib`, `-fcompiler-rt` means building compiler-rt into an object file and then adding it into the static archive. Before this commit, when using `build-obj`, zig would build compiler-rt into an object file, and then on ELF, use `lld -r` to merge it into the main object file. Other linker backends of LLD do not support `-r` to merge objects, so this failed with error messages for those targets. Now, `-fcompiler-rt` when used with `build-obj` acts as if the user puts `_ = @import("compiler_rt");` inside their root source file. The symbols of compiler-rt go into the same compilation unit as the root source file. This is hooked up for stage1 only for now. Once stage2 is capable of building compiler-rt, it should be hooked up there as well. --- CMakeLists.txt | 1 + src/Compilation.zig | 32 +++++++++++++------------------- src/link/Elf.zig | 4 ++++ src/link/Wasm.zig | 4 +++- src/main.zig | 4 ++-- src/stage1.zig | 2 +- src/stage1/all_types.hpp | 1 + src/stage1/codegen.cpp | 16 ++++++++++++++++ src/stage1/stage1.cpp | 1 + src/stage1/stage1.h | 1 + src/stage1/zig0.cpp | 5 +++++ 11 files changed, 48 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a8da2dd499a39ca24899e746b79b344c02f520f..2714fa6d6bc5e0f986b3b759cffaf0c0d55840be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -796,6 +796,7 @@ set(BUILD_ZIG1_ARGS --name zig1 --zig-lib-dir "${CMAKE_SOURCE_DIR}/lib" "-femit-bin=${ZIG1_OBJECT}" + -fcompiler-rt "${ZIG1_RELEASE_ARG}" "${ZIG1_SINGLE_THREADED_ARG}" -lc diff --git a/src/Compilation.zig b/src/Compilation.zig index fb35f5153fcfcf51e1d47ff0e273b4cde31f83b0..8fb86916e68ab27e4ba7d8b398f21bd711cf55c5 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -826,6 +826,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const ofmt = options.object_format orelse options.target.getObjectFormat(); const use_stage1 = options.use_stage1 orelse blk: { + // Even though we may have no Zig code to compile (depending on `options.root_pkg`), + // we may need to use stage1 for building compiler-rt and other dependencies. + if (build_options.omit_stage2) break :blk true; if (options.use_llvm) |use_llvm| { @@ -833,9 +836,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :blk false; } } - // If we have no zig code to compile, no need for stage1 backend. - if (options.root_pkg == null) - break :blk false; break :blk build_options.is_stage1; }; @@ -878,9 +878,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) { return error.EmittingLlvmModuleRequiresUsingLlvmBackend; } - if (use_stage1) { - return error.@"stage1 only supports LLVM backend"; - } } const tsan = options.want_tsan orelse false; @@ -1542,24 +1539,19 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { } // The `use_stage1` condition is here only because stage2 cannot yet build compiler-rt. - // Once it is capable this condition should be removed. + // Once it is capable this condition should be removed. When removing this condition, + // also test the use case of `build-obj -fcompiler-rt` with the self-hosted compiler + // and make sure the compiler-rt symbols are emitted. Currently this is hooked up for + // stage1 but not stage2. if (comp.bin_file.options.use_stage1) { if (comp.bin_file.options.include_compiler_rt) { if (is_exe_or_dyn_lib) { try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} }); - } else { + } else if (options.output_mode != .Obj) { + // If build-obj with -fcompiler-rt is requested, that is handled specially + // elsewhere. In this case we are making a static library, so we ask + // for a compiler-rt object to put in it. try comp.work_queue.writeItem(.{ .compiler_rt_obj = {} }); - if (comp.bin_file.options.object_format != .elf and - comp.bin_file.options.output_mode == .Obj) - { - // For ELF we can rely on using -r to link multiple objects together into one, - // but to truly support `build-obj -fcompiler-rt` will require virtually - // injecting `_ = @import("compiler_rt.zig")` into the root source file of - // the compilation. - fatal("Embedding compiler-rt into {s} objects is not yet implemented.", .{ - @tagName(comp.bin_file.options.object_format), - }); - } } } if (needs_c_symbols) { @@ -4002,6 +3994,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(target.os.getVersionRange()); man.hash.add(comp.bin_file.options.dll_export_fns); man.hash.add(comp.bin_file.options.function_sections); + man.hash.add(comp.bin_file.options.include_compiler_rt); man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); man.hash.add(mod.emit_h != null); @@ -4182,6 +4175,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .valgrind_enabled = comp.bin_file.options.valgrind, .tsan_enabled = comp.bin_file.options.tsan, .function_sections = comp.bin_file.options.function_sections, + .include_compiler_rt = comp.bin_file.options.include_compiler_rt, .enable_stack_probing = comp.bin_file.options.stack_check, .red_zone = comp.bin_file.options.red_zone, .enable_time_report = comp.time_report, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b1eb219cf6462ff43cddb48fcfdc17bb10f2618b..c95af23026e52b4e7db6d9e74d2e0965dcf90943 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1289,6 +1289,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // TODO: remove when stage2 can build compiler_rt.zig if (!build_options.is_stage1) break :blk null; + // In the case of build-obj we include the compiler-rt symbols directly alongside + // the symbols of the root source file, in the same compilation unit. + if (is_obj) break :blk null; + if (is_exe_or_dyn_lib) { break :blk comp.compiler_rt_static_lib.?.full_object_path; } else { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index f8803dfcb72cbec52d81694b1f6dba97b077c06e..2ed65760330d5339abc7c845fa09feff181bfb58 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -645,7 +645,9 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { break :blk full_obj_path; } else null; - const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) + const is_obj = self.base.options.output_mode == .Obj; + + const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt and !is_obj) comp.compiler_rt_static_lib.?.full_object_path else null; diff --git a/src/main.zig b/src/main.zig index db7fc17df0f311210c44c0e95deb6c1eca755b62..d3c7d024a1e71d470c5f5c667c569639b654ba6c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -385,8 +385,8 @@ const usage_build_generic = \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) \\ --sysroot [path] Set the system root directory (usually /) \\ --version [ver] Dynamic library semver - \\ -fsoname[=name] (Linux) Override the default SONAME value - \\ -fno-soname (Linux) Disable emitting a SONAME + \\ -fsoname[=name] Override the default SONAME value + \\ -fno-soname Disable emitting a SONAME \\ -fLLD Force using LLD as the linker \\ -fno-LLD Prevent using LLD as the linker \\ -fcompiler-rt Always include compiler-rt symbols in output diff --git a/src/stage1.zig b/src/stage1.zig index 0108e9fc6455ad41333daf467de36ba181805977..a00e036964315f9fb8c9ee459a9ae2837ccc2624 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -21,7 +21,6 @@ comptime { assert(build_options.is_stage1); assert(build_options.have_llvm); if (!builtin.is_test) { - _ = @import("compiler_rt"); @export(main, .{ .name = "main" }); } } @@ -126,6 +125,7 @@ pub const Module = extern struct { valgrind_enabled: bool, tsan_enabled: bool, function_sections: bool, + include_compiler_rt: bool, enable_stack_probing: bool, red_zone: bool, enable_time_report: bool, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index ccdbf6b1553233eaacc13ebd5dfd507e9d854c4a..8dfc8de6f9f3fd86a9951920efae144be96f2797 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2150,6 +2150,7 @@ struct CodeGen { bool have_stack_probing; bool red_zone; bool function_sections; + bool include_compiler_rt; bool test_is_evented; bool valgrind_enabled; bool tsan_enabled; diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 582625c7ff6d271797538c3aa37d7941fbb212ec..67d787427fc2bf0ccf278ef63c5bdd8054c47279 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -9542,6 +9542,22 @@ static void gen_root_source(CodeGen *g) { g->panic_fn = panic_fn_val->data.x_ptr.data.fn.fn_entry; assert(g->panic_fn != nullptr); + if (g->include_compiler_rt) { + Buf *import_target_path; + Buf full_path = BUF_INIT; + ZigType *compiler_rt_import; + if ((err = analyze_import(g, std_import, buf_create_from_str("./special/compiler_rt.zig"), + &compiler_rt_import, &import_target_path, &full_path))) + { + if (err == ErrorFileNotFound) { + fprintf(stderr, "unable to find '%s'", buf_ptr(import_target_path)); + } else { + fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&full_path), err_str(err)); + } + exit(1); + } + } + if (!g->error_during_imports) { semantic_analyze(g); } diff --git a/src/stage1/stage1.cpp b/src/stage1/stage1.cpp index 38d236c560eac6a8cedd76a863957f882457d437..2fbab192a7344a92fa9990f1fdc13d4e819d9fb7 100644 --- a/src/stage1/stage1.cpp +++ b/src/stage1/stage1.cpp @@ -101,6 +101,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) { g->link_libc = stage1->link_libc; g->link_libcpp = stage1->link_libcpp; g->function_sections = stage1->function_sections; + g->include_compiler_rt = stage1->include_compiler_rt; g->subsystem = stage1->subsystem; diff --git a/src/stage1/stage1.h b/src/stage1/stage1.h index 44f3c625c045438f8d1ef6aeeb0d6ed0c546e78f..1e8eef5937ae99efff347e042c87a42d45bafb8d 100644 --- a/src/stage1/stage1.h +++ b/src/stage1/stage1.h @@ -196,6 +196,7 @@ struct ZigStage1 { bool valgrind_enabled; bool tsan_enabled; bool function_sections; + bool include_compiler_rt; bool enable_stack_probing; bool red_zone; bool enable_time_report; diff --git a/src/stage1/zig0.cpp b/src/stage1/zig0.cpp index f2412a74efb4a86e8ec0ac949b382bc2abc732ed..d07d4f9e370f7f574250d0f2e4d28ec39bce7651 100644 --- a/src/stage1/zig0.cpp +++ b/src/stage1/zig0.cpp @@ -39,6 +39,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) { " --color [auto|off|on] enable or disable colored error messages\n" " --name [name] override output name\n" " -femit-bin=[path] Output machine code\n" + " -fcompiler-rt Always include compiler-rt symbols in output\n" " --pkg-begin [name] [path] make pkg available to import and push current pkg\n" " --pkg-end pop current pkg\n" " -ODebug build with optimizations off and safety on\n" @@ -266,6 +267,7 @@ int main(int argc, char **argv) { const char *mcpu = nullptr; bool single_threaded = false; bool is_test_build = false; + bool include_compiler_rt = false; for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; @@ -334,6 +336,8 @@ int main(int argc, char **argv) { mcpu = arg + strlen("-mcpu="); } else if (str_starts_with(arg, "-femit-bin=")) { emit_bin_path = arg + strlen("-femit-bin="); + } else if (strcmp(arg, "-fcompiler-rt") == 0) { + include_compiler_rt = true; } else if (i + 1 >= argc) { fprintf(stderr, "Expected another argument after %s\n", arg); return print_error_usage(arg0); @@ -468,6 +472,7 @@ int main(int argc, char **argv) { stage1->subsystem = subsystem; stage1->pic = true; stage1->is_single_threaded = single_threaded; + stage1->include_compiler_rt = include_compiler_rt; zig_stage1_build_object(stage1); -- 2.54.0 From 80ba9f060d81e8c5674acb4eb07c833d26121462 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jul 2021 18:12:35 -0700 Subject: [PATCH 4/4] fix double linking of compiler-rt symbols on wasm The include_compiler_rt stored in the bin file options means that we need compiler-rt symbols *somehow*. However, in the context of using the stage1 backend we need to tell stage1 to include compiler-rt only if stage1 is the place that needs to provide those symbols. Otherwise the stage2 infrastructure will take care of it in the linker, by putting compiler_rt.o into a static archive, or linking compiler_rt.a against an executable. In other words we only want to set this flag for stage1 if we are using build-obj. --- src/Compilation.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 8fb86916e68ab27e4ba7d8b398f21bd711cf55c5..7fc44dfa972232456b022ecfb32547f27e3ea065 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3973,6 +3973,16 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node const id_symlink_basename = "stage1.id"; const libs_txt_basename = "libs.txt"; + // The include_compiler_rt stored in the bin file options here means that we need + // compiler-rt symbols *somehow*. However, in the context of using the stage1 backend + // we need to tell stage1 to include compiler-rt only if stage1 is the place that + // needs to provide those symbols. Otherwise the stage2 infrastructure will take care + // of it in the linker, by putting compiler_rt.o into a static archive, or linking + // compiler_rt.a against an executable. In other words we only want to set this flag + // for stage1 if we are using build-obj. + const include_compiler_rt = comp.bin_file.options.output_mode == .Obj and + comp.bin_file.options.include_compiler_rt; + // We are about to obtain this lock, so here we give other processes a chance first. comp.releaseStage1Lock(); @@ -3994,7 +4004,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(target.os.getVersionRange()); man.hash.add(comp.bin_file.options.dll_export_fns); man.hash.add(comp.bin_file.options.function_sections); - man.hash.add(comp.bin_file.options.include_compiler_rt); + man.hash.add(include_compiler_rt); man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); man.hash.add(mod.emit_h != null); @@ -4175,7 +4185,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .valgrind_enabled = comp.bin_file.options.valgrind, .tsan_enabled = comp.bin_file.options.tsan, .function_sections = comp.bin_file.options.function_sections, - .include_compiler_rt = comp.bin_file.options.include_compiler_rt, + .include_compiler_rt = include_compiler_rt, .enable_stack_probing = comp.bin_file.options.stack_check, .red_zone = comp.bin_file.options.red_zone, .enable_time_report = comp.time_report, -- 2.54.0