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 70626f5051..45bc4af1b3 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 595dce77c2..70a1fd5997 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 78d03d4534..f460dbc7ca 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 2403180ec8..562896d14c 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 50ad6bc1a0..ba918ad10d 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 3b62bba410..9a51eba5f6 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); 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 1261981af3..f2d2e2845b 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 45bc4af1b3..1b9f0084c8 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 70a1fd5997..303c930b93 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 e6ca0a2baa..1058628633 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 f460dbc7ca..fb35f5153f 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 81484e93db..92fa32dacf 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 f3a1a72f3d..f45ea8f979 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 562896d14c..85ff2ca603 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 ba918ad10d..0bae2cc6cc 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 315dfb563b..b1eb219cf6 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 02ea5856f4..8675295b2a 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 f478d2ee47..f8803dfcb7 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 9a51eba5f6..db7fc17df0 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 2284e512ec..0108e9fc64 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 c673922335..ccdbf6b155 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 9ab0e269a2..582625c7ff 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 e7c316f385..38d236c560 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 3bdb5500cf..44f3c625c0 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 6d40f4089a..d5d6f9f670 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 a2b3c6b92e..a771491138 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 { 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 0a8da2dd49..2714fa6d6b 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 fb35f5153f..8fb86916e6 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 b1eb219cf6..c95af23026 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 f8803dfcb7..2ed6576033 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 db7fc17df0..d3c7d024a1 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 0108e9fc64..a00e036964 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 ccdbf6b155..8dfc8de6f9 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 582625c7ff..67d787427f 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 38d236c560..2fbab192a7 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 44f3c625c0..1e8eef5937 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 f2412a74ef..d07d4f9e37 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); 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 8fb86916e6..7fc44dfa97 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,