From 759d1d9aeffbe1c5eaf7d5675b6d1c165757cdf1 Mon Sep 17 00:00:00 2001 From: Jacob G-W Date: Wed, 14 Jul 2021 18:03:15 -0400 Subject: [PATCH 01/13] astgen: errors for shadowing in loop captures --- src/AstGen.zig | 8 +++++++- test/cases.zig | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index f62a8f18ab..e30f3a272c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -5264,7 +5264,9 @@ fn whileExpr( const ident_token = if (payload_is_ref) payload_token + 1 else payload_token; if (mem.eql(u8, "_", tree.tokenSlice(ident_token))) break :s &then_scope.base; - const ident_name = try astgen.identAsString(payload_token + @boolToInt(payload_is_ref)); + const payload_name_loc = payload_token + @boolToInt(payload_is_ref); + const ident_name = try astgen.identAsString(payload_name_loc); + try astgen.detectLocalShadowing(&then_scope.base, ident_name, payload_name_loc); payload_val_scope = .{ .parent = &then_scope.base, .gen_zir = &then_scope, @@ -5287,6 +5289,7 @@ fn whileExpr( const ident_name = try astgen.identAsString(ident_token); if (mem.eql(u8, "_", tree.tokenSlice(ident_token))) break :s &then_scope.base; + try astgen.detectLocalShadowing(&then_scope.base, ident_name, ident_token); payload_val_scope = .{ .parent = &then_scope.base, .gen_zir = &then_scope, @@ -5344,6 +5347,7 @@ fn whileExpr( const ident_name = try astgen.identAsString(error_token); if (mem.eql(u8, tree.tokenSlice(error_token), "_")) break :s &else_scope.base; + try astgen.detectLocalShadowing(&else_scope.base, ident_name, error_token); payload_val_scope = .{ .parent = &else_scope.base, .gen_zir = &else_scope, @@ -5483,6 +5487,7 @@ fn forExpr( const name_str_index = try astgen.identAsString(ident); const tag: Zir.Inst.Tag = if (is_ptr) .elem_ptr else .elem_val; const payload_inst = try then_scope.addBin(tag, array_ptr, index); + try astgen.detectLocalShadowing(&then_scope.base, name_str_index, ident); payload_val_scope = .{ .parent = &then_scope.base, .gen_zir = &then_scope, @@ -5506,6 +5511,7 @@ fn forExpr( return astgen.failTok(index_token, "discard of index capture; omit it instead", .{}); } const index_name = try astgen.identAsString(index_token); + try astgen.detectLocalShadowing(payload_sub_scope, index_name, index_token); index_scope = .{ .parent = payload_sub_scope, .gen_zir = &then_scope, diff --git a/test/cases.zig b/test/cases.zig index fe1579be44..f5eb566816 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1065,6 +1065,49 @@ pub fn addCases(ctx: *TestContext) !void { ":5:19: error: redeclaration of local constant 'c'", ":4:19: note: previous declaration here", }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ for (n) |_, i| { + \\ } + \\} + , &[_][]const u8{ + ":3:17: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ for (n) |i| { + \\ } + \\} + , &[_][]const u8{ + ":3:14: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ while (n) |i| { + \\ } + \\} + , &[_][]const u8{ + ":3:16: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ while (n) |bruh| { + \\ _ = bruh; + \\ } else |i| { + \\ + \\ } + \\} + , &[_][]const u8{ + ":5:13: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); } { From 1799455e0587644c98cdba67bd10a59a2d45b116 Mon Sep 17 00:00:00 2001 From: Jacob G-W Date: Wed, 14 Jul 2021 18:17:55 -0400 Subject: [PATCH 02/13] astgen: errors for shadowing in if captures --- src/AstGen.zig | 3 +++ test/cases.zig | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/AstGen.zig b/src/AstGen.zig index e30f3a272c..019e73c983 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -5008,6 +5008,7 @@ fn ifExpr( const token_name_str = tree.tokenSlice(token_name_index); if (mem.eql(u8, "_", token_name_str)) break :s &then_scope.base; + try astgen.detectLocalShadowing(&then_scope.base, ident_name, token_name_index); payload_val_scope = .{ .parent = &then_scope.base, .gen_zir = &then_scope, @@ -5030,6 +5031,7 @@ fn ifExpr( break :s &then_scope.base; const payload_inst = try then_scope.addUnNode(tag, cond.inst, node); const ident_name = try astgen.identAsString(ident_token); + try astgen.detectLocalShadowing(&then_scope.base, ident_name, ident_token); payload_val_scope = .{ .parent = &then_scope.base, .gen_zir = &then_scope, @@ -5071,6 +5073,7 @@ fn ifExpr( const error_token_str = tree.tokenSlice(error_token); if (mem.eql(u8, "_", error_token_str)) break :s &else_scope.base; + try astgen.detectLocalShadowing(&else_scope.base, ident_name, error_token); payload_val_scope = .{ .parent = &else_scope.base, .gen_zir = &else_scope, diff --git a/test/cases.zig b/test/cases.zig index f5eb566816..f235992f71 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1108,6 +1108,33 @@ pub fn addCases(ctx: *TestContext) !void { ":5:13: error: redeclaration of local variable 'i'", ":2:9: note: previous declaration here", }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ if (true) |i| {} + \\} + , &[_][]const u8{ + ":3:16: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ if (true) |i| {} else |e| {} + \\} + , &[_][]const u8{ + ":3:16: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); + case.addError( + \\pub fn main() void { + \\ var i = 0; + \\ if (true) |_| {} else |i| {} + \\} + , &[_][]const u8{ + ":3:28: error: redeclaration of local variable 'i'", + ":2:9: note: previous declaration here", + }); } { From 36295d712fbd561c3de9b3eb46e776d63e646e9a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jul 2021 12:45:32 -0700 Subject: [PATCH 03/13] 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 680fa880d63daa6058a084f1c107162e40e18aa8 Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Thu, 22 Jul 2021 01:27:42 +0200 Subject: [PATCH 04/13] std.crypto: handle the top bit in 25519.field.fromBytes64() (#9435) The only known use case for this is the hash-to-curve operation where the top bit is always cleared. But the function is public, so let's make it work as one would expect in the general case. Also fix the comment by the way. --- lib/std/crypto/25519/field.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/crypto/25519/field.zig b/lib/std/crypto/25519/field.zig index 33ee36b816..1d67f0a902 100644 --- a/lib/std/crypto/25519/field.zig +++ b/lib/std/crypto/25519/field.zig @@ -93,7 +93,7 @@ pub const Fe = struct { return s; } - /// Map a 64-bit big endian string into a field element + /// Map a 64 bytes big endian string into a field element pub fn fromBytes64(s: [64]u8) Fe { var fl: [32]u8 = undefined; var gl: [32]u8 = undefined; @@ -106,7 +106,7 @@ pub const Fe = struct { gl[31] &= 0x7f; var fe_f = fromBytes(fl); const fe_g = fromBytes(gl); - fe_f.limbs[0] += (s[32] >> 7) * 19; + fe_f.limbs[0] += (s[32] >> 7) * 19 + @as(u10, s[0] >> 7) * 722; i = 0; while (i < 5) : (i += 1) { fe_f.limbs[i] += 38 * fe_g.limbs[i]; From 093e10b66e5db5fcbccf941b4ffdab6330a83faa Mon Sep 17 00:00:00 2001 From: Biolunar Date: Thu, 22 Jul 2021 01:27:59 +0200 Subject: [PATCH 05/13] linux stdlib: fix definition of RW flags (#9428) --- lib/std/os/bits/linux.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index 69fdbc84fb..81bdfa6608 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -339,19 +339,19 @@ pub const O_RDWR = 0o2; pub const kernel_rwf = u32; /// high priority request, poll if possible -pub const RWF_HIPRI = kernel_rwf(0x00000001); +pub const RWF_HIPRI: kernel_rwf = 0x00000001; /// per-IO O_DSYNC -pub const RWF_DSYNC = kernel_rwf(0x00000002); +pub const RWF_DSYNC: kernel_rwf = 0x00000002; /// per-IO O_SYNC -pub const RWF_SYNC = kernel_rwf(0x00000004); +pub const RWF_SYNC: kernel_rwf = 0x00000004; /// per-IO, return -EAGAIN if operation would block -pub const RWF_NOWAIT = kernel_rwf(0x00000008); +pub const RWF_NOWAIT: kernel_rwf = 0x00000008; /// per-IO O_APPEND -pub const RWF_APPEND = kernel_rwf(0x00000010); +pub const RWF_APPEND: kernel_rwf = 0x00000010; pub const SEEK_SET = 0; pub const SEEK_CUR = 1; From 18b8738069268cc913bbae9580d2d170618a2ae9 Mon Sep 17 00:00:00 2001 From: Michal Ziulek Date: Thu, 22 Jul 2021 01:28:21 +0200 Subject: [PATCH 06/13] Fixed compile error: 'bMenu' needs to casted. (#9426) --- lib/std/os/windows/user32.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/windows/user32.zig b/lib/std/os/windows/user32.zig index ee4ee87d88..d996a0f394 100644 --- a/lib/std/os/windows/user32.zig +++ b/lib/std/os/windows/user32.zig @@ -1336,7 +1336,7 @@ pub extern "user32" fn AdjustWindowRectEx(lpRect: *RECT, dwStyle: DWORD, bMenu: pub fn adjustWindowRectEx(lpRect: *RECT, dwStyle: u32, bMenu: bool, dwExStyle: u32) !void { assert(dwStyle & WS_OVERLAPPED == 0); - if (AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle) == 0) { + if (AdjustWindowRectEx(lpRect, dwStyle, @boolToInt(bMenu), dwExStyle) == 0) { switch (GetLastError()) { .INVALID_PARAMETER => unreachable, else => |err| return windows.unexpectedError(err), From dc4fa83dd767096595ae4e84c3a7dbfd80cbf115 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 17 Jun 2021 17:52:26 -0700 Subject: [PATCH 07/13] translate-c: add framework for special-casing macros Some macros (for example any macro that uses token pasting) cannot be directly translated to Zig, but may nevertheless still admit a Zig implementation. This provides a mechanism for matching macros against templates and mapping them to functions implemented in c_translation.zig. A macro matches a template if it contains the same sequence of tokens, except that the name and parameters may be renamed. No attempt is made to semantically analyze the macro. For example the following two macros are considered equivalent: ```C ``` But the following two are not: ```C ``` --- lib/std/zig/c_translation.zig | 80 ++++++++++ src/translate_c.zig | 269 ++++++++++++++++++++++++++++++++-- src/translate_c/ast.zig | 14 ++ test/translate_c.zig | 6 + 4 files changed, 354 insertions(+), 15 deletions(-) diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig index 7851525bb7..071ce01396 100644 --- a/lib/std/zig/c_translation.zig +++ b/lib/std/zig/c_translation.zig @@ -350,3 +350,83 @@ test "Flexible Array Type" { try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); } + +pub const Macros = struct { + pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { + return promoteIntLiteral(c_uint, n, .decimal); + } + + fn L_SUFFIX_ReturnType(comptime number: anytype) type { + switch (@TypeOf(number)) { + comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), + comptime_float => return c_longdouble, + else => @compileError("Invalid value for L suffix"), + } + } + pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { + switch (@TypeOf(number)) { + comptime_int => return promoteIntLiteral(c_long, number, .decimal), + comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), + else => @compileError("Invalid value for L suffix"), + } + } + + pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { + return promoteIntLiteral(c_ulong, n, .decimal); + } + + pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { + return promoteIntLiteral(c_longlong, n, .decimal); + } + + pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { + return promoteIntLiteral(c_ulonglong, n, .decimal); + } + + pub fn F_SUFFIX(comptime f: comptime_float) f32 { + return @as(f32, f); + } + + pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { + return @fieldParentPtr(@TypeOf(sample.*), member, ptr); + } +}; + +test "Macro suffix functions" { + try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32); + + try testing.expect(@TypeOf(Macros.U_SUFFIX(1)) == c_uint); + if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { + try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); + } + if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { + try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); + } + + try testing.expect(@TypeOf(Macros.L_SUFFIX(1)) == c_long); + if (math.maxInt(c_long) > math.maxInt(c_int)) { + try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); + } + if (math.maxInt(c_longlong) > math.maxInt(c_long)) { + try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); + } + + try testing.expect(@TypeOf(Macros.UL_SUFFIX(1)) == c_ulong); + if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { + try testing.expect(@TypeOf(Macros.UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); + } + + try testing.expect(@TypeOf(Macros.LL_SUFFIX(1)) == c_longlong); + try testing.expect(@TypeOf(Macros.ULL_SUFFIX(1)) == c_ulonglong); +} + +test "WL_CONTAINER_OF" { + const S = struct { + a: u32 = 0, + b: u32 = 0, + }; + var x = S{}; + var y = S{}; + var ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b"); + try testing.expectEqual(&x, ptr); +} diff --git a/src/translate_c.zig b/src/translate_c.zig index 707f8cc485..2ac720a209 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2,6 +2,7 @@ //! and stage2. const std = @import("std"); +const testing = std.testing; const assert = std.debug.assert; const clang = @import("clang.zig"); const ctok = std.c.tokenizer; @@ -18,6 +19,7 @@ const CallingConvention = std.builtin.CallingConvention; pub const ClangErrMsg = clang.Stage2ErrorMsg; pub const Error = std.mem.Allocator.Error; +const MacroProcessingError = Error || error{UnexpectedMacroToken}; const TypeError = Error || error{UnsupportedType}; const TransError = TypeError || error{UnsupportedTranslation}; @@ -27,6 +29,10 @@ const AliasList = std.ArrayList(struct { name: []const u8, }); +// Maps macro parameter names to token position, for determining if different +// identifiers refer to the same positional argument in different macros. +const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize); + const Scope = struct { id: Id, parent: ?*Scope, @@ -322,6 +328,8 @@ pub const Context = struct { /// up front in a pre-processing step. global_names: std.StringArrayHashMapUnmanaged(void) = .{}, + pattern_list: PatternList, + fn getMangle(c: *Context) u32 { c.mangle_count += 1; return c.mangle_count; @@ -375,6 +383,7 @@ pub fn translate( .alias_list = AliasList.init(gpa), .global_scope = try arena.allocator.create(Scope.Root), .clang_context = ast_unit.getASTContext(), + .pattern_list = try PatternList.init(gpa), }; context.global_scope.* = Scope.Root.init(&context); defer { @@ -385,6 +394,7 @@ pub fn translate( context.unnamed_typedefs.deinit(gpa); context.typedefs.deinit(gpa); context.global_scope.deinit(); + context.pattern_list.deinit(gpa); } try context.global_scope.nodes.append(Tag.usingnamespace_builtins.init()); @@ -4829,6 +4839,217 @@ fn isZigPrimitiveType(name: []const u8) bool { return @import("AstGen.zig").simple_types.has(name); } +const PatternList = struct { + patterns: []Pattern, + + /// Templates must be function-like macros + /// first element is macro source, second element is the name of the function + /// in std.lib.zig.c_translation.Macros which implements it + const templates = [_][2][]const u8{ + [2][]const u8{ "f_SUFFIX(X) (X ## f)", "F_SUFFIX" }, + [2][]const u8{ "F_SUFFIX(X) (X ## F)", "F_SUFFIX" }, + + [2][]const u8{ "u_SUFFIX(X) (X ## u)", "U_SUFFIX" }, + [2][]const u8{ "U_SUFFIX(X) (X ## U)", "U_SUFFIX" }, + + [2][]const u8{ "l_SUFFIX(X) (X ## l)", "L_SUFFIX" }, + [2][]const u8{ "L_SUFFIX(X) (X ## L)", "L_SUFFIX" }, + + [2][]const u8{ "ul_SUFFIX(X) (X ## ul)", "UL_SUFFIX" }, + [2][]const u8{ "uL_SUFFIX(X) (X ## uL)", "UL_SUFFIX" }, + [2][]const u8{ "Ul_SUFFIX(X) (X ## Ul)", "UL_SUFFIX" }, + [2][]const u8{ "UL_SUFFIX(X) (X ## UL)", "UL_SUFFIX" }, + + [2][]const u8{ "ll_SUFFIX(X) (X ## ll)", "LL_SUFFIX" }, + [2][]const u8{ "LL_SUFFIX(X) (X ## LL)", "LL_SUFFIX" }, + + [2][]const u8{ "ull_SUFFIX(X) (X ## ull)", "ULL_SUFFIX" }, + [2][]const u8{ "uLL_SUFFIX(X) (X ## uLL)", "ULL_SUFFIX" }, + [2][]const u8{ "Ull_SUFFIX(X) (X ## Ull)", "ULL_SUFFIX" }, + [2][]const u8{ "ULL_SUFFIX(X) (X ## ULL)", "ULL_SUFFIX" }, + + [2][]const u8{ + \\wl_container_of(ptr, sample, member) \ + \\(__typeof__(sample))((char *)(ptr) - \ + \\ offsetof(__typeof__(*sample), member)) + , + "WL_CONTAINER_OF", + }, + }; + + /// Assumes that `ms` represents a tokenized function-like macro. + fn buildArgsHash(allocator: *mem.Allocator, ms: MacroSlicer, hash: *ArgsPositionMap) MacroProcessingError!void { + assert(ms.tokens.len > 2); + assert(ms.tokens[0].id == .Identifier); + assert(ms.tokens[1].id == .LParen); + + var i: usize = 2; + while (true) : (i += 1) { + const token = ms.tokens[i]; + switch (token.id) { + .RParen => break, + .Comma => continue, + .Identifier => { + const identifier = ms.slice(token); + try hash.put(allocator, identifier, i); + }, + else => return error.UnexpectedMacroToken, + } + } + } + + const Pattern = struct { + tokens: []const CToken, + source: []const u8, + impl: []const u8, + args_hash: ArgsPositionMap, + + fn init(self: *Pattern, allocator: *mem.Allocator, template: [2][]const u8) Error!void { + const source = template[0]; + const impl = template[1]; + + var tok_list = std.ArrayList(CToken).init(allocator); + defer tok_list.deinit(); + try tokenizeMacro(source, &tok_list); + const tokens = try allocator.dupe(CToken, tok_list.items); + + self.* = .{ + .tokens = tokens, + .source = source, + .impl = impl, + .args_hash = .{}, + }; + const ms = MacroSlicer{ .source = source, .tokens = tokens }; + buildArgsHash(allocator, ms, &self.args_hash) catch |err| switch (err) { + error.UnexpectedMacroToken => unreachable, + else => |e| return e, + }; + } + + fn deinit(self: *Pattern, allocator: *mem.Allocator) void { + self.args_hash.deinit(allocator); + allocator.free(self.tokens); + } + + /// This function assumes that `ms` has already been validated to contain a function-like + /// macro, and that the parsed template macro in `self` also contains a function-like + /// macro. Please review this logic carefully if changing that assumption. Two + /// function-like macros are considered equivalent if and only if they contain the same + /// list of tokens, modulo parameter names. + fn isEquivalent(self: Pattern, ms: MacroSlicer, args_hash: ArgsPositionMap) bool { + if (self.tokens.len != ms.tokens.len) return false; + if (args_hash.count() != self.args_hash.count()) return false; + + var i: usize = 2; + while (self.tokens[i].id != .RParen) : (i += 1) {} + + const pattern_slicer = MacroSlicer{ .source = self.source, .tokens = self.tokens }; + while (i < self.tokens.len) : (i += 1) { + const pattern_token = self.tokens[i]; + const macro_token = ms.tokens[i]; + if (meta.activeTag(pattern_token.id) != meta.activeTag(macro_token.id)) return false; + + const pattern_bytes = pattern_slicer.slice(pattern_token); + const macro_bytes = ms.slice(macro_token); + switch (pattern_token.id) { + .Identifier => { + const pattern_arg_index = self.args_hash.get(pattern_bytes); + const macro_arg_index = args_hash.get(macro_bytes); + + if (pattern_arg_index == null and macro_arg_index == null) { + if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; + } else if (pattern_arg_index != null and macro_arg_index != null) { + if (pattern_arg_index.? != macro_arg_index.?) return false; + } else { + return false; + } + }, + .MacroString, .StringLiteral, .CharLiteral, .IntegerLiteral, .FloatLiteral => { + if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; + }, + else => { + // other tags correspond to keywords and operators that do not contain a "payload" + // that can vary + }, + } + } + return true; + } + }; + + fn init(allocator: *mem.Allocator) Error!PatternList { + const patterns = try allocator.alloc(Pattern, templates.len); + for (templates) |template, i| { + try patterns[i].init(allocator, template); + } + return PatternList{ .patterns = patterns }; + } + + fn deinit(self: *PatternList, allocator: *mem.Allocator) void { + for (self.patterns) |*pattern| pattern.deinit(allocator); + allocator.free(self.patterns); + } + + fn match(self: PatternList, allocator: *mem.Allocator, ms: MacroSlicer) Error!?Pattern { + var args_hash: ArgsPositionMap = .{}; + defer args_hash.deinit(allocator); + + buildArgsHash(allocator, ms, &args_hash) catch |err| switch (err) { + error.UnexpectedMacroToken => return null, + else => |e| return e, + }; + + for (self.patterns) |pattern| if (pattern.isEquivalent(ms, args_hash)) return pattern; + return null; + } +}; + +const MacroSlicer = struct { + source: []const u8, + tokens: []const CToken, + fn slice(self: MacroSlicer, token: CToken) []const u8 { + return self.source[token.start..token.end]; + } +}; + +// Testing here instead of test/translate_c.zig allows us to also test that the +// mapped function exists in `std.zig.c_translation.Macros` +test "Macro matching" { + const helper = struct { + const MacroFunctions = @import("std").zig.c_translation.Macros; + fn checkMacro(allocator: *mem.Allocator, pattern_list: PatternList, source: []const u8, comptime expected_match: ?[]const u8) !void { + var tok_list = std.ArrayList(CToken).init(allocator); + defer tok_list.deinit(); + try tokenizeMacro(source, &tok_list); + const macro_slicer = MacroSlicer{ .source = source, .tokens = tok_list.items }; + const matched = try pattern_list.match(allocator, macro_slicer); + if (expected_match) |expected| { + try testing.expectEqualStrings(expected, matched.?.impl); + try testing.expect(@hasDecl(MacroFunctions, expected)); + } else { + try testing.expectEqual(@as(@TypeOf(matched), null), matched); + } + } + }; + const allocator = std.testing.allocator; + var pattern_list = try PatternList.init(allocator); + defer pattern_list.deinit(allocator); + + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", "F_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", "U_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", "L_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", "LL_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", "UL_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", "ULL_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, + \\container_of(a, b, c) \ + \\(__typeof__(b))((char *)(a) - \ + \\ offsetof(__typeof__(*b), c)) + , "WL_CONTAINER_OF"); + + try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); +} + const MacroCtx = struct { source: []const u8, list: []const CToken, @@ -4855,8 +5076,30 @@ const MacroCtx = struct { fn fail(self: *MacroCtx, c: *Context, comptime fmt: []const u8, args: anytype) !void { return failDecl(c, self.loc, self.name, fmt, args); } + + fn makeSlicer(self: *const MacroCtx) MacroSlicer { + return MacroSlicer{ .source = self.source, .tokens = self.list }; + } }; +fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!void { + var tokenizer = std.c.Tokenizer{ + .buffer = source, + }; + while (true) { + const tok = tokenizer.next(); + switch (tok.id) { + .Nl, .Eof => { + try tok_list.append(tok); + break; + }, + .LineComment, .MultiLineComment => continue, + else => {}, + } + try tok_list.append(tok); + } +} + fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void { // TODO if we see #undef, delete it from the table var it = unit.getLocalPreprocessingEntities_begin(); @@ -4888,21 +5131,7 @@ fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void { const slice_len = @ptrToInt(end_c) - @ptrToInt(begin_c); const slice = begin_c[0..slice_len]; - var tokenizer = std.c.Tokenizer{ - .buffer = slice, - }; - while (true) { - const tok = tokenizer.next(); - switch (tok.id) { - .Nl, .Eof => { - try tok_list.append(tok); - break; - }, - .LineComment, .MultiLineComment => continue, - else => {}, - } - try tok_list.append(tok); - } + try tokenizeMacro(slice, &tok_list); var macro_ctx = MacroCtx{ .source = slice, @@ -4960,6 +5189,16 @@ fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void { } fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { + const macro_slicer = m.makeSlicer(); + if (try c.pattern_list.match(c.gpa, macro_slicer)) |pattern| { + const decl = try Tag.pub_var_simple.create(c.arena, .{ + .name = m.name, + .init = try Tag.helpers_macro.create(c.arena, pattern.impl), + }); + try c.global_scope.macro_table.put(m.name, decl); + return; + } + var block_scope = try Scope.Block.init(c, &c.global_scope.base, false); defer block_scope.deinit(); const scope = &block_scope.base; diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 6db59afd96..fa6b749589 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -193,6 +193,8 @@ pub const Node = extern union { helpers_flexible_array_type, /// @import("std").zig.c_translation.shuffleVectorIndex(lhs, rhs) helpers_shuffle_vector_index, + /// @import("std").zig.c_translation.Macro. + helpers_macro, /// @import("std").meta.Vector(lhs, rhs) std_meta_vector, /// @import("std").mem.zeroes(operand) @@ -339,6 +341,7 @@ pub const Node = extern union { .identifier, .warning, .type, + .helpers_macro, => Payload.Value, .discard => Payload.Discard, .@"if" => Payload.If, @@ -1112,6 +1115,16 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .data = undefined, }); }, + .helpers_macro => { + const payload = node.castTag(.helpers_macro).?.data; + const chain = [_][]const u8{ + "zig", + "c_translation", + "Macros", + payload, + }; + return renderStdImport(c, &chain); + }, .string_slice => { const payload = node.castTag(.string_slice).?.data; @@ -2310,6 +2323,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .bit_or_assign, .bit_xor_assign, .assign, + .helpers_macro, => { // these should never appear in places where grouping might be needed. unreachable; diff --git a/test/translate_c.zig b/test/translate_c.zig index 2ba42fef07..1ceaf15e5c 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3624,4 +3624,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , \\pub export var @"_": c_int = 42; }); + + cases.add("Macro matching", + \\#define FOO(X) (X ## U) + , &[_][]const u8{ + \\pub const FOO = @import("std").zig.c_translation.Macros.U_SUFFIX; + }); } From b33efa373943f8e13dc432f37862da7ee8bf1b6e Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 22 Jul 2021 09:17:54 -0700 Subject: [PATCH 08/13] translate-c: Handle ambiguous cast or call macro Fixes #9425 --- lib/std/zig/c_translation.zig | 52 +++++++++++++++++++++++++++++++++++ src/translate_c.zig | 3 ++ 2 files changed, 55 insertions(+) diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig index 071ce01396..bcf3b310ea 100644 --- a/lib/std/zig/c_translation.zig +++ b/lib/std/zig/c_translation.zig @@ -390,6 +390,20 @@ pub const Macros = struct { pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { return @fieldParentPtr(@TypeOf(sample.*), member, ptr); } + + /// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) + /// could be either: cast B to A, or call A with the value B. + pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { + .Type => a, + .Fn => |fn_info| fn_info.return_type orelse void, + else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), + } { + switch (@typeInfo(@TypeOf(a))) { + .Type => return cast(a, b), + .Fn => return a(b), + else => unreachable, // return type will be a compile error otherwise + } + } }; test "Macro suffix functions" { @@ -430,3 +444,41 @@ test "WL_CONTAINER_OF" { var ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b"); try testing.expectEqual(&x, ptr); } + +test "CAST_OR_CALL casting" { + var arg = @as(c_int, 1000); + var casted = Macros.CAST_OR_CALL(u8, arg); + try testing.expectEqual(cast(u8, arg), casted); + + const S = struct { + x: u32 = 0, + }; + var s = S{}; + var casted_ptr = Macros.CAST_OR_CALL(*u8, &s); + try testing.expectEqual(cast(*u8, &s), casted_ptr); +} + +test "CAST_OR_CALL calling" { + const Helper = struct { + var last_val: bool = false; + fn returnsVoid(val: bool) void { + last_val = val; + } + fn returnsBool(f: f32) bool { + return f > 0; + } + fn identity(self: c_uint) c_uint { + return self; + } + }; + + Macros.CAST_OR_CALL(Helper.returnsVoid, true); + try testing.expectEqual(true, Helper.last_val); + Macros.CAST_OR_CALL(Helper.returnsVoid, false); + try testing.expectEqual(false, Helper.last_val); + + try testing.expectEqual(Helper.returnsBool(1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); + try testing.expectEqual(Helper.returnsBool(-1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); + + try testing.expectEqual(Helper.identity(@as(c_uint, 100)), Macros.CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); +} diff --git a/src/translate_c.zig b/src/translate_c.zig index 2ac720a209..395a314f7c 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -4868,6 +4868,8 @@ const PatternList = struct { [2][]const u8{ "Ull_SUFFIX(X) (X ## Ull)", "ULL_SUFFIX" }, [2][]const u8{ "ULL_SUFFIX(X) (X ## ULL)", "ULL_SUFFIX" }, + [2][]const u8{ "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL" }, + [2][]const u8{ \\wl_container_of(ptr, sample, member) \ \\(__typeof__(sample))((char *)(ptr) - \ @@ -5048,6 +5050,7 @@ test "Macro matching" { , "WL_CONTAINER_OF"); try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); + try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL"); } const MacroCtx = struct { From a5fb28070f37c2cad92ac8805bcc704e872fc538 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jul 2021 14:44:06 -0700 Subject: [PATCH 09/13] 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 10/13] 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 11/13] 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, From a38a6914875ab3bda02fb1732467228ef876074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 9 Jul 2021 05:15:32 +0200 Subject: [PATCH 12/13] zig: -rdynamic now implies -fdll-export-fns unless the latter is explicitly set. Fixes #9340. --- src/Compilation.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 78d03d4534..6a6a6909c8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -991,7 +991,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :blk lm; } else default_link_mode; - const dll_export_fns = if (options.dll_export_fns) |explicit| explicit else is_dyn_lib; + const dll_export_fns = if (options.dll_export_fns) |explicit| explicit else is_dyn_lib or options.rdynamic; const libc_dirs = try detectLibCIncludeDirs( arena, From e5b476209a1215a03164890d331e2013f20882a6 Mon Sep 17 00:00:00 2001 From: David May Date: Fri, 23 Jul 2021 08:32:20 +0200 Subject: [PATCH 13/13] Docs fix array/pointer/slice type coercion section (#9392) * removed deprecated coercion: [X]T => [] const T * Fixed tests and added desc for first test * Improved heading --- doc/langref.html.in | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index b20f5821a8..0e3e32c52a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5337,16 +5337,15 @@ test "implicit cast to comptime_int" { } {#code_end#} {#header_close#} - {#header_open|Type Coercion: Arrays and Pointers#} - {#code_begin|test|coerce_arrays_and_ptrs#} + {#header_open|Type Coercion: Slices, Arrays and Pointers#} + {#code_begin|test|coerce__slices_arrays_and_ptrs#} const std = @import("std"); const expect = std.testing.expect; -// This cast exists primarily so that string literals can be -// passed to functions that accept const slices. However -// it is probably going to be removed from the language when -// https://github.com/ziglang/zig/issues/265 is implemented. -test "[N]T to []const T" { +// You can assign constant pointers to arrays to a slice with +// const modifier on the element type. Useful in particular for +// String literals. +test "*const [N]T to []const T" { var x1: []const u8 = "hello"; var x2: []const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; try expect(std.mem.eql(u8, x1, x2)); @@ -5356,7 +5355,7 @@ test "[N]T to []const T" { } // Likewise, it works when the destination type is an error union. -test "[N]T to E![]const T" { +test "*const [N]T to E![]const T" { var x1: anyerror![]const u8 = "hello"; var x2: anyerror![]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; try expect(std.mem.eql(u8, try x1, try x2)); @@ -5366,7 +5365,7 @@ test "[N]T to E![]const T" { } // Likewise, it works when the destination type is an optional. -test "[N]T to ?[]const T" { +test "*const [N]T to ?[]const T" { var x1: ?[]const u8 = "hello"; var x2: ?[]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; try expect(std.mem.eql(u8, x1.?, x2.?));