diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d6db51082..20d19fa167 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -792,6 +792,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/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/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.?)); 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]; 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; 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), diff --git a/lib/std/target.zig b/lib/std/target.zig index 70626f5051..1b9f0084c8 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -549,16 +549,36 @@ pub const Target = struct { }; pub const ObjectFormat = enum { + /// Common Object File Format (Windows) coff, - pe, + /// 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 { @@ -1290,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 { @@ -1353,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 { @@ -1677,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 595dce77c2..303c930b93 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -108,8 +108,9 @@ 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()) { - .coff, .pe => switch (options.output_mode) { + 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 => { const suffix = switch (options.link_mode orelse .Static) { @@ -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/c_translation.zig b/lib/std/zig/c_translation.zig index 7851525bb7..bcf3b310ea 100644 --- a/lib/std/zig/c_translation.zig +++ b/lib/std/zig/c_translation.zig @@ -350,3 +350,135 @@ 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); + } + + /// 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" { + 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); +} + +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/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/AstGen.zig b/src/AstGen.zig index 31e7f040a2..8fb7cde6de 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -5009,6 +5009,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, @@ -5031,6 +5032,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, @@ -5072,6 +5074,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, @@ -5265,7 +5268,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, @@ -5288,6 +5293,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, @@ -5345,6 +5351,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, @@ -5484,6 +5491,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, @@ -5507,6 +5515,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/src/Compilation.zig b/src/Compilation.zig index c7323b91f9..72209b657e 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. @@ -812,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| { @@ -819,6 +836,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { 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,9 @@ 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; + } } const tsan = options.want_tsan orelse false; @@ -996,7 +1021,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, @@ -1386,6 +1411,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), @@ -1518,24 +1544,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) { @@ -2733,7 +2754,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); @@ -3028,7 +3052,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 => {}, } } @@ -3954,6 +3978,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(); @@ -3975,6 +4009,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(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); @@ -3983,6 +4018,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); @@ -4088,13 +4124,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", .{}); } @@ -4102,6 +4139,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); @@ -4122,6 +4160,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, @@ -4150,6 +4190,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 = 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/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 e9f2c932de..1293fab4d2 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, @@ -206,9 +206,10 @@ 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, .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, @@ -219,13 +220,16 @@ 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); 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..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; } @@ -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) { @@ -823,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 @@ -1398,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..c95af23026 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(); @@ -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/MachO.zig b/src/link/MachO.zig index a8749c1dfb..21f4e9c33c 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -29,21 +29,22 @@ const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Dylib = @import("MachO/Dylib.zig"); +const File = link.File; const Object = @import("MachO/Object.zig"); const Liveness = @import("../Liveness.zig"); +const LlvmObject = @import("../codegen/llvm.zig").Object; const LoadCommand = commands.LoadCommand; const Module = @import("../Module.zig"); -const File = link.File; +const SegmentCommand = commands.SegmentCommand; pub const TextBlock = @import("MachO/TextBlock.zig"); const Trie = @import("MachO/Trie.zig"); -const SegmentCommand = commands.SegmentCommand; 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, @@ -333,7 +334,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; } @@ -3305,6 +3306,10 @@ fn writeSymbolTable(self: *MachO) !void { } 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..2ed6576033 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); @@ -642,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 239159c4f5..547744ab94 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 @@ -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,15 +361,14 @@ 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 - \\ 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 @@ -384,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 @@ -552,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"; @@ -1011,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=")) { @@ -1720,8 +1728,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")) { @@ -1765,7 +1771,7 @@ fn buildOutputType( }; const a_out_basename = switch (object_format) { - .pe, .coff => "a.exe", + .coff => "a.exe", else => "a.out", }; @@ -1830,10 +1836,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, } @@ -1844,10 +1850,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, } @@ -1858,16 +1864,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) { @@ -2003,6 +2023,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, @@ -2408,11 +2429,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); diff --git a/src/stage1.zig b/src/stage1.zig index 2284e512ec..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" }); } } @@ -95,6 +94,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, @@ -124,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 c673922335..8dfc8de6f9 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; @@ -2149,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 9ab0e269a2..67d787427f 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); } @@ -9537,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 e7c316f385..2fbab192a7 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); @@ -100,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 3bdb5500cf..1e8eef5937 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; @@ -193,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); diff --git a/src/translate_c.zig b/src/translate_c.zig index 707f8cc485..395a314f7c 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,220 @@ 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{ "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL" }, + + [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); + try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL"); +} + const MacroCtx = struct { source: []const u8, list: []const CToken, @@ -4855,8 +5079,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 +5134,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 +5192,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/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 { diff --git a/test/cases.zig b/test/cases.zig index fe1579be44..f235992f71 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1065,6 +1065,76 @@ 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", + }); + 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", + }); } { 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; + }); }