From 0fc79d602bf9b3a5c97cfc28b59193b005692cb2 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Wed, 27 Jul 2022 20:24:33 +0200 Subject: [PATCH 01/29] stage2 ARM: more support for switch statements --- src/arch/arm/CodeGen.zig | 59 ++++++++++++++++++++++------------------ test/behavior/switch.zig | 9 ------ 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 8bd589e7ba..93d98c41d3 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -4300,17 +4300,6 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { ); defer self.gpa.free(liveness.deaths); - // If the condition dies here in this switch instruction, process - // that death now instead of later as this has an effect on - // whether it needs to be spilled in the branches - if (self.liveness.operandDies(inst, 0)) { - const op_int = @enumToInt(pl_op.operand); - if (op_int >= Air.Inst.Ref.typed_value_map.len) { - const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); - self.processDeath(op_index); - } - } - var extra_index: usize = switch_br.end; var case_i: u32 = 0; while (case_i < switch_br.data.cases_len) : (case_i += 1) { @@ -4320,21 +4309,43 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; extra_index = case.end + items.len + case_body.len; - var relocs = try self.gpa.alloc(u32, items.len); - defer self.gpa.free(relocs); + // For every item, we compare it to condition and branch into + // the prong if they are equal. After we compared to all + // items, we branch into the next prong (or if no other prongs + // exist out of the switch statement). + // + // cmp condition, item1 + // beq prong + // cmp condition, item2 + // beq prong + // cmp condition, item3 + // beq prong + // b out + // prong: ... + // ... + // out: ... + const branch_into_prong_relocs = try self.gpa.alloc(u32, items.len); + defer self.gpa.free(branch_into_prong_relocs); - if (items.len == 1) { + for (items) |item, idx| { const condition = try self.resolveInst(pl_op.operand); - const item = try self.resolveInst(items[0]); + const item_mcv = try self.resolveInst(item); const operands: BinOpOperands = .{ .mcv = .{ .lhs = condition, - .rhs = item, + .rhs = item_mcv, } }; - const cmp_result = try self.cmp(operands, condition_ty, .eq); - relocs[0] = try self.condBr(cmp_result); - } else { - return self.fail("TODO switch with multiple items", .{}); + const cmp_result = try self.cmp(operands, condition_ty, .neq); + branch_into_prong_relocs[idx] = try self.condBr(cmp_result); + } + + const branch_away_from_prong_reloc = try self.addInst(.{ + .tag = .b, + .data = .{ .inst = undefined }, // populated later through performReloc + }); + + for (branch_into_prong_relocs) |reloc| { + try self.performReloc(reloc); } // Capture the state of register and stack allocation state so that we can revert to it. @@ -4369,9 +4380,7 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { self.next_stack_offset = parent_next_stack_offset; self.register_manager.free_registers = parent_free_registers; - for (relocs) |reloc| { - try self.performReloc(reloc); - } + try self.performReloc(branch_away_from_prong_reloc); } if (switch_br.data.else_body_len > 0) { @@ -4414,9 +4423,7 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { // in airCondBr. } - // We already took care of pl_op.operand earlier, so we're going - // to pass .none here - return self.finishAir(inst, .unreach, .{ .none, .none, .none }); + return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); } fn performReloc(self: *Self, inst: Mir.Inst.Index) !void { diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 3a49c03b18..4e86bcadeb 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -53,7 +53,6 @@ test "implicit comptime switch" { } test "switch on enum" { - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const fruit = Fruit.Orange; @@ -73,7 +72,6 @@ fn nonConstSwitchOnEnum(fruit: Fruit) void { } test "switch statement" { - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try nonConstSwitch(SwitchStatementFoo.C); @@ -91,7 +89,6 @@ const SwitchStatementFoo = enum { A, B, C, D }; test "switch with multiple expressions" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const x = switch (returnsFive()) { @@ -120,7 +117,6 @@ fn trueIfBoolFalseOtherwise(comptime T: type) bool { } test "switching on booleans" { - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try testSwitchOnBools(); @@ -218,7 +214,6 @@ fn poll() void { } test "switch on global mutable var isn't constant-folded" { - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO while (state < 2) { @@ -278,7 +273,6 @@ fn testSwitchEnumPtrCapture() !void { test "switch handles all cases of number" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try testSwitchHandleAllCases(); @@ -370,7 +364,6 @@ test "anon enum literal used in switch on union enum" { } test "switch all prongs unreachable" { - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try testAllProngsUnreachable(); @@ -582,7 +575,6 @@ test "switch on pointer type" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const S = struct { const X = struct { @@ -674,7 +666,6 @@ test "capture of integer forwards the switch condition directly" { } test "enum value without tag name used as switch item" { - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const E = enum(u32) { From 02acde99a142e89c30d0e49cc703d0ad80ea31b7 Mon Sep 17 00:00:00 2001 From: Meghan Date: Tue, 14 Jun 2022 14:36:19 -0700 Subject: [PATCH 02/29] stage2: ensure 'std', 'builtin', and 'root' is always available to `@import` --- src/Compilation.zig | 17 ----------------- src/Module.zig | 9 +++++++++ src/Sema.zig | 4 ---- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 869cd43f0f..17ffe356a3 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1494,31 +1494,14 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { ); errdefer test_pkg.destroy(gpa); - try test_pkg.add(gpa, "builtin", builtin_pkg); - try test_pkg.add(gpa, "root", test_pkg); - try test_pkg.add(gpa, "std", std_pkg); - break :root_pkg test_pkg; } else main_pkg; errdefer if (options.is_test) root_pkg.destroy(gpa); - var other_pkg_iter = main_pkg.table.valueIterator(); - while (other_pkg_iter.next()) |pkg| { - try pkg.*.add(gpa, "builtin", builtin_pkg); - try pkg.*.add(gpa, "std", std_pkg); - } - try main_pkg.addAndAdopt(gpa, "builtin", builtin_pkg); try main_pkg.add(gpa, "root", root_pkg); try main_pkg.addAndAdopt(gpa, "std", std_pkg); - try std_pkg.add(gpa, "builtin", builtin_pkg); - try std_pkg.add(gpa, "root", root_pkg); - try std_pkg.add(gpa, "std", std_pkg); - - try builtin_pkg.add(gpa, "std", std_pkg); - try builtin_pkg.add(gpa, "builtin", builtin_pkg); - const main_pkg_in_std = m: { const std_path = try std.fs.path.resolve(arena, &[_][]const u8{ std_pkg.root_src_directory.path orelse ".", diff --git a/src/Module.zig b/src/Module.zig index 4576538a35..397134d911 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4673,6 +4673,15 @@ pub fn importFile( cur_file: *File, import_string: []const u8, ) !ImportFileResult { + if (std.mem.eql(u8, import_string, "std")) { + return mod.importPkg(mod.main_pkg.table.get("std").?); + } + if (std.mem.eql(u8, import_string, "builtin")) { + return mod.importPkg(mod.main_pkg.table.get("builtin").?); + } + if (std.mem.eql(u8, import_string, "root")) { + return mod.importPkg(mod.root_pkg); + } if (cur_file.pkg.table.get(import_string)) |pkg| { return mod.importPkg(pkg); } diff --git a/src/Sema.zig b/src/Sema.zig index ad8beff0f3..ac9e24a9be 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4674,10 +4674,6 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr error.OutOfMemory => return error.OutOfMemory, else => unreachable, // we pass null for root_src_dir_path }; - const std_pkg = mod.main_pkg.table.get("std").?; - const builtin_pkg = mod.main_pkg.table.get("builtin").?; - try c_import_pkg.add(sema.gpa, "builtin", builtin_pkg); - try c_import_pkg.add(sema.gpa, "std", std_pkg); const result = mod.importPkg(c_import_pkg) catch |err| return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); From ece1d1daf476fdffc69279c686ad1e2101ce6e4d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 20 Jul 2022 16:39:13 +0300 Subject: [PATCH 03/29] CLI: add error for duplicate package --- src/main.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.zig b/src/main.zig index bd86cd2bb9..f2291a0646 100644 --- a/src/main.zig +++ b/src/main.zig @@ -858,6 +858,12 @@ fn buildOutputType( ) catch |err| { fatal("Failed to add package at path {s}: {s}", .{ pkg_path.?, @errorName(err) }); }; + + if (mem.eql(u8, pkg_name.?, "std") or mem.eql(u8, pkg_name.?, "root") or mem.eql(u8, pkg_name.?, "builtin")) { + fatal("unable to add package '{s}' -> '{s}': conflicts with builtin package", .{ pkg_name.?, pkg_path.? }); + } else if (cur_pkg.table.get(pkg_name.?)) |prev| { + fatal("unable to add package '{s}' -> '{s}': already exists as '{s}", .{ pkg_name.?, pkg_path.?, prev.root_src_path }); + } try cur_pkg.addAndAdopt(gpa, pkg_name.?, new_cur_pkg); cur_pkg = new_cur_pkg; } else if (mem.eql(u8, arg, "--pkg-end")) { From b0525344a2b0b158369251c031159bef241048da Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 11 Jul 2022 18:46:24 -0700 Subject: [PATCH 04/29] Add check to verify libc++ is shared by LLVM/Clang This check is needed because if static/dynamic linking is mixed incorrectly, it's possible for Clang and LLVM to end up with duplicate "copies" of libc++. This is not benign: Static variables are not shared, so equality comparisons that depend on pointers to static variables will fail. One such failure is std::generic_category(), which causes POSIX error codes to compare as unequal when passed between LLVM and Clang. I believe this is the cause of https://github.com/ziglang/zig/issues/11168 In order to avoid affecting build times when Zig is repeatedly invoked, we only enable this check for "zig env" and "zig version" --- src/clang.zig | 3 +++ src/main.zig | 19 ++++++++++++++++++- src/zig_clang.cpp | 28 ++++++++++++++++++++++++++++ src/zig_clang.h | 1 + 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/clang.zig b/src/clang.zig index ef90ddebce..40dacc5df8 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -1913,3 +1913,6 @@ extern fn ZigClangLoadFromCommandLine( errors_len: *usize, resources_path: [*:0]const u8, ) ?*ASTUnit; + +pub const isLLVMUsingSeparateLibcxx = ZigClangIsLLVMUsingSeparateLibcxx; +extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool; diff --git a/src/main.zig b/src/main.zig index bd86cd2bb9..87349df38a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -174,6 +174,17 @@ pub fn main() anyerror!void { return mainArgs(gpa, arena, args); } +/// Check that LLVM and Clang have been linked properly so that they are using the same +/// libc++ and can safely share objects with pointers to static variables in libc++ +fn verifyLibcxxCorrectlyLinked() void { + if (build_options.have_llvm and ZigClangIsLLVMUsingSeparateLibcxx()) { + fatal( + \\Zig was built/linked incorrectly: LLVM and Clang have separate copies of libc++ + \\ If you are dynamically linking LLVM, make sure you dynamically link libc++ too + , .{}); + } +} + pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { if (args.len <= 1) { std.log.info("{s}", .{usage}); @@ -261,8 +272,12 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const stdout = io.getStdOut().writer(); return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); } else if (mem.eql(u8, cmd, "version")) { - return std.io.getStdOut().writeAll(build_options.version ++ "\n"); + try std.io.getStdOut().writeAll(build_options.version ++ "\n"); + // Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version" + // to avoid affecting the startup time for build-critical commands (check takes about ~10 μs) + return verifyLibcxxCorrectlyLinked(); } else if (mem.eql(u8, cmd, "env")) { + verifyLibcxxCorrectlyLinked(); return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer()); } else if (mem.eql(u8, cmd, "zen")) { return io.getStdOut().writeAll(info_zen); @@ -4481,6 +4496,8 @@ pub const info_zen = \\ ; +extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool; + extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; extern "c" fn ZigLlvmAr_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 1c0ff7bf09..7b79f8e985 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -3432,3 +3432,31 @@ const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct Zi const llvm::APSInt *result = &casted->getInitVal(); return reinterpret_cast(result); } + +// Get a pointer to a static variable in libc++ from LLVM and make sure that +// it matches our own. +// +// This check is needed because if static/dynamic linking is mixed incorrectly, +// it's possible for Clang and LLVM to end up with duplicate "copies" of libc++. +// +// This is not benign: Static variables are not shared, so equality comparisons +// that depend on pointers to static variables will fail. One such failure is +// std::generic_category(), which causes POSIX error codes to compare as unequal +// when passed between LLVM and Clang. +// +// See also: https://github.com/ziglang/zig/issues/11168 +bool ZigClangIsLLVMUsingSeparateLibcxx() { + + // Temporarily create an InMemoryFileSystem, so that we can perform a file + // lookup that is guaranteed to fail. + auto FS = new llvm::vfs::InMemoryFileSystem(true); + auto StatusOrErr = FS->status("foo.txt"); + delete FS; + + // This should return a POSIX (generic_category) error code, but if LLVM has + // its own copy of libc++ this will actually be a separate category instance. + assert(!StatusOrErr); + auto EC = StatusOrErr.getError(); + return EC.category() != std::generic_category(); +} + diff --git a/src/zig_clang.h b/src/zig_clang.h index 6babb21bfe..3da57d4301 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -1418,4 +1418,5 @@ ZIG_EXTERN_C const struct ZigClangRecordDecl *ZigClangFieldDecl_getParent(const ZIG_EXTERN_C unsigned ZigClangFieldDecl_getFieldIndex(const struct ZigClangFieldDecl *); ZIG_EXTERN_C const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct ZigClangEnumConstantDecl *); +ZIG_EXTERN_C bool ZigClangIsLLVMUsingSeparateLibcxx(); #endif From f1feb1369b128fb515a369b1144574b4c1b53b6e Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 11 Jul 2022 18:47:54 -0700 Subject: [PATCH 05/29] Dynamically link libc++, if integrating with system LLVM This ensures that the zigcpp clang driver and LLVM are using the same copy of libc++. See prior commit for more details. --- build.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/build.zig b/build.zig index 3e7d1888cd..7660a638e5 100644 --- a/build.zig +++ b/build.zig @@ -565,13 +565,17 @@ fn addCmakeCfgOptionsToExe( exe.linkLibCpp(); } else { const need_cpp_includes = true; + const lib_suffix = switch (cfg.llvm_linkage) { + .static => exe.target.staticLibSuffix()[1..], + .dynamic => exe.target.dynamicLibSuffix()[1..], + }; // System -lc++ must be used because in this code path we are attempting to link // against system-provided LLVM, Clang, LLD. if (exe.target.getOsTag() == .linux) { - // First we try to static link against gcc libstdc++. If that doesn't work, - // we fall back to -lc++ and cross our fingers. - addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) { + // First we try to link against gcc libstdc++. If that doesn't work, we fall + // back to -lc++ and cross our fingers. + addCxxKnownPath(b, cfg, exe, b.fmt("libstdc++.{s}", .{lib_suffix}), "", need_cpp_includes) catch |err| switch (err) { error.RequiredLibraryNotFound => { exe.linkSystemLibrary("c++"); }, @@ -579,11 +583,11 @@ fn addCmakeCfgOptionsToExe( }; exe.linkSystemLibrary("unwind"); } else if (exe.target.isFreeBSD()) { - try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes); exe.linkSystemLibrary("pthread"); } else if (exe.target.getOsTag() == .openbsd) { - try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); - try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, b.fmt("libc++abi.{s}", .{lib_suffix}), null, need_cpp_includes); } else if (exe.target.isDarwin()) { exe.linkSystemLibrary("c++"); } From b5861193e072ba6780730a559f2b879378b8587f Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Thu, 28 Jul 2022 13:47:29 -0700 Subject: [PATCH 06/29] std: rename std.Target.systemz to .s390x --- CMakeLists.txt | 2 +- lib/std/target.zig | 10 +++++----- lib/std/target/{systemz.zig => s390x.zig} | 0 tools/update_cpu_features.zig | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename lib/std/target/{systemz.zig => s390x.zig} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ffbf12dbc0..96d10f1e83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -670,7 +670,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/target/powerpc.zig" "${CMAKE_SOURCE_DIR}/lib/std/target/riscv.zig" "${CMAKE_SOURCE_DIR}/lib/std/target/sparc.zig" - "${CMAKE_SOURCE_DIR}/lib/std/target/systemz.zig" + "${CMAKE_SOURCE_DIR}/lib/std/target/s390x.zig" "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig" "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig" "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig" diff --git a/lib/std/target.zig b/lib/std/target.zig index f026b0da21..d15eae7dc5 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -453,7 +453,7 @@ pub const Target = struct { pub const riscv = @import("target/riscv.zig"); pub const sparc = @import("target/sparc.zig"); pub const spirv = @import("target/spirv.zig"); - pub const systemz = @import("target/systemz.zig"); + pub const s390x = @import("target/s390x.zig"); pub const ve = @import("target/ve.zig"); pub const wasm = @import("target/wasm.zig"); pub const x86 = @import("target/x86.zig"); @@ -1178,7 +1178,7 @@ pub const Target = struct { .amdgcn => "amdgpu", .riscv32, .riscv64 => "riscv", .sparc, .sparc64, .sparcel => "sparc", - .s390x => "systemz", + .s390x => "s390x", .i386, .x86_64 => "x86", .nvptx, .nvptx64 => "nvptx", .wasm32, .wasm64 => "wasm", @@ -1202,7 +1202,7 @@ pub const Target = struct { .riscv32, .riscv64 => &riscv.all_features, .sparc, .sparc64, .sparcel => &sparc.all_features, .spirv32, .spirv64 => &spirv.all_features, - .s390x => &systemz.all_features, + .s390x => &s390x.all_features, .i386, .x86_64 => &x86.all_features, .nvptx, .nvptx64 => &nvptx.all_features, .ve => &ve.all_features, @@ -1226,7 +1226,7 @@ pub const Target = struct { .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu), - .s390x => comptime allCpusFromDecls(systemz.cpu), + .s390x => comptime allCpusFromDecls(s390x.cpu), .i386, .x86_64 => comptime allCpusFromDecls(x86.cpu), .nvptx, .nvptx64 => comptime allCpusFromDecls(nvptx.cpu), .ve => comptime allCpusFromDecls(ve.cpu), @@ -1287,7 +1287,7 @@ pub const Target = struct { .riscv64 => &riscv.cpu.generic_rv64, .sparc, .sparcel => &sparc.cpu.generic, .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline - .s390x => &systemz.cpu.generic, + .s390x => &s390x.cpu.generic, .i386 => &x86.cpu.i386, .x86_64 => &x86.cpu.x86_64, .nvptx, .nvptx64 => &nvptx.cpu.sm_20, diff --git a/lib/std/target/systemz.zig b/lib/std/target/s390x.zig similarity index 100% rename from lib/std/target/systemz.zig rename to lib/std/target/s390x.zig diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 3757b56743..c43799d280 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -793,7 +793,7 @@ const llvm_targets = [_]LlvmTarget{ .td_name = "Sparc.td", }, .{ - .zig_name = "systemz", + .zig_name = "s390x", .llvm_name = "SystemZ", .td_name = "SystemZ.td", }, From 9e0a930ce3be01923602adbfee13b50842da08b7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jul 2022 16:20:38 +0300 Subject: [PATCH 07/29] stage2: add error for comptime control flow in runtime block --- src/AstGen.zig | 4 ++++ src/Module.zig | 2 ++ src/Sema.zig | 18 ++++++++++++++++++ src/Zir.zig | 6 ++++++ src/print_zir.zig | 1 + .../comptime_continue_inside_runtime_catch.zig | 6 +++--- ...omptime_continue_inside_runtime_if_bool.zig | 6 +++--- ...mptime_continue_inside_runtime_if_error.zig | 6 +++--- ...ime_continue_inside_runtime_if_optional.zig | 6 +++--- ...comptime_continue_inside_runtime_orelse.zig | 16 ++++++++++++++++ ...comptime_continue_inside_runtime_switch.zig | 6 +++--- ...time_continue_inside_runtime_while_bool.zig | 6 +++--- ...ime_continue_inside_runtime_while_error.zig | 6 +++--- ..._continue_inside_runtime_while_optional.zig | 6 +++--- 14 files changed, 71 insertions(+), 24 deletions(-) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_catch.zig (58%) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_if_bool.zig (59%) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_if_error.zig (60%) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_if_optional.zig (58%) create mode 100644 test/cases/compile_errors/comptime_continue_inside_runtime_orelse.zig rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_switch.zig (64%) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_while_bool.zig (61%) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_while_error.zig (65%) rename test/cases/compile_errors/{stage1/obj => }/comptime_continue_inside_runtime_while_optional.zig (60%) diff --git a/src/AstGen.zig b/src/AstGen.zig index 528ef930e6..0078057eef 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1940,6 +1940,9 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) .break_inline else .@"break"; + if (break_tag == .break_inline) { + _ = try parent_gz.addNode(.check_comptime_control_flow, node); + } _ = try parent_gz.addBreak(break_tag, continue_block, .void_value); return Zir.Inst.Ref.unreachable_value; }, @@ -2473,6 +2476,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .repeat_inline, .panic, .panic_comptime, + .check_comptime_control_flow, => { noreturn_src_node = statement; break :b true; diff --git a/src/Module.zig b/src/Module.zig index 397134d911..deff4620b9 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2283,6 +2283,8 @@ pub const SrcLoc = struct { .@"while" => tree.whileFull(node).ast.cond_expr, .for_simple => tree.forSimple(node).ast.cond_expr, .@"for" => tree.forFull(node).ast.cond_expr, + .@"orelse" => node, + .@"catch" => node, else => unreachable, }; return nodeToSpan(tree, src_node); diff --git a/src/Sema.zig b/src/Sema.zig index ac9e24a9be..5a70679b8d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1146,6 +1146,24 @@ fn analyzeBodyInner( i += 1; continue; }, + .check_comptime_control_flow => { + if (!block.is_comptime) { + if (block.runtime_cond orelse block.runtime_loop) |runtime_src| { + const inst_data = sema.code.instructions.items(.data)[inst].node; + const src = LazySrcLoc.nodeOffset(inst_data); + const msg = msg: { + const msg = try sema.errMsg(block, src, "comptime control flow inside runtime block", .{}); + errdefer msg.destroy(sema.gpa); + + try sema.errNote(block, runtime_src, msg, "runtime control flow here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } + i += 1; + continue; + }, // Special case instructions to handle comptime control flow. .@"break" => { diff --git a/src/Zir.zig b/src/Zir.zig index 6e9b133310..4540032605 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -280,6 +280,9 @@ pub const Inst = struct { /// break instruction in a block, and the target block is the parent. /// Uses the `break` union field. break_inline, + /// Checks that comptime control flow does not happen inside a runtime block. + /// Uses the `node` union field. + check_comptime_control_flow, /// Function call. /// Uses the `pl_node` union field with payload `Call`. /// AST node is the function call. @@ -1266,6 +1269,7 @@ pub const Inst = struct { .repeat_inline, .panic, .panic_comptime, + .check_comptime_control_flow, => true, }; } @@ -1315,6 +1319,7 @@ pub const Inst = struct { .set_runtime_safety, .memcpy, .memset, + .check_comptime_control_flow, => true, .param, @@ -1595,6 +1600,7 @@ pub const Inst = struct { .bool_br_or = .bool_br, .@"break" = .@"break", .break_inline = .@"break", + .check_comptime_control_flow = .node, .call = .pl_node, .cmp_lt = .pl_node, .cmp_lte = .pl_node, diff --git a/src/print_zir.zig b/src/print_zir.zig index de51c271c4..7723446f1c 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -409,6 +409,7 @@ const Writer = struct { .alloc_inferred_comptime_mut, .ret_ptr, .ret_type, + .check_comptime_control_flow, => try self.writeNode(stream, inst), .error_value, diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_catch.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_catch.zig similarity index 58% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_catch.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_catch.zig index 7eefeb80b4..9e62420f1f 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_catch.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_catch.zig @@ -9,8 +9,8 @@ fn bad() !void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:4:21: error: comptime control flow inside runtime block -// tmp.zig:4:15: note: runtime block created here +// :4:21: error: comptime control flow inside runtime block +// :4:15: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_bool.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_if_bool.zig similarity index 59% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_bool.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_if_bool.zig index 9ace5ddceb..b2a7312c52 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_bool.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_if_bool.zig @@ -8,8 +8,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:5:22: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :5:22: error: comptime control flow inside runtime block +// :5:15: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_error.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_if_error.zig similarity index 60% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_error.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_if_error.zig index 554ba3c43e..194274a1ed 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_error.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_if_error.zig @@ -8,8 +8,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:5:20: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :5:20: error: comptime control flow inside runtime block +// :5:13: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_optional.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_if_optional.zig similarity index 58% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_optional.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_if_optional.zig index 32c71e5c77..965454ef03 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_if_optional.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_if_optional.zig @@ -8,8 +8,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:5:20: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :5:20: error: comptime control flow inside runtime block +// :5:13: note: runtime control flow here diff --git a/test/cases/compile_errors/comptime_continue_inside_runtime_orelse.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_orelse.zig new file mode 100644 index 0000000000..56b65c1ab7 --- /dev/null +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_orelse.zig @@ -0,0 +1,16 @@ +export fn entry() void { + const ints = [_]u8{ 1, 2 }; + inline for (ints) |_| { + bad() orelse continue; + } +} +fn bad() ?void { + return null; +} + +// error +// backend=stage2 +// target=native +// +// :4:22: error: comptime control flow inside runtime block +// :4:15: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_switch.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_switch.zig similarity index 64% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_switch.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_switch.zig index d145897b41..391ecbdf1a 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_switch.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_switch.zig @@ -11,8 +11,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:6:19: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :6:19: error: comptime control flow inside runtime block +// :5:17: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_bool.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_while_bool.zig similarity index 61% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_bool.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_while_bool.zig index 8e57854728..54d62e6d37 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_bool.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_while_bool.zig @@ -8,8 +8,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:5:25: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :5:25: error: comptime control flow inside runtime block +// :5:18: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_error.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_while_error.zig similarity index 65% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_error.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_while_error.zig index 818455c354..0eef1c3374 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_error.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_while_error.zig @@ -10,8 +10,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:6:13: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :6:13: error: comptime control flow inside runtime block +// :5:16: note: runtime control flow here diff --git a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_optional.zig b/test/cases/compile_errors/comptime_continue_inside_runtime_while_optional.zig similarity index 60% rename from test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_optional.zig rename to test/cases/compile_errors/comptime_continue_inside_runtime_while_optional.zig index ed22cc2cac..e6753a5911 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_continue_inside_runtime_while_optional.zig +++ b/test/cases/compile_errors/comptime_continue_inside_runtime_while_optional.zig @@ -8,8 +8,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:5:23: error: comptime control flow inside runtime block -// tmp.zig:5:9: note: runtime block created here +// :5:23: error: comptime control flow inside runtime block +// :5:16: note: runtime control flow here From fdaf9c40d6a351477aacb1af27871f3de12d485e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 28 Jul 2022 14:58:20 +0300 Subject: [PATCH 08/29] stage2: handle tuple init edge cases --- src/AstGen.zig | 5 +- src/Module.zig | 19 +++ src/Sema.zig | 126 +++++++++++++++--- src/Zir.zig | 7 +- src/print_zir.zig | 14 +- .../compile_errors/tuple_init_edge_cases.zig | 44 ++++++ .../wrong_size_to_an_array_literal.zig | 2 +- 7 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 test/cases/compile_errors/tuple_init_edge_cases.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 0078057eef..b6a7450f3a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1349,7 +1349,10 @@ fn arrayInitExpr( } } const array_type_inst = try typeExpr(gz, scope, array_init.ast.type_expr); - _ = try gz.addUnNode(.validate_array_init_ty, array_type_inst, array_init.ast.type_expr); + _ = try gz.addPlNode(.validate_array_init_ty, node, Zir.Inst.ArrayInit{ + .ty = array_type_inst, + .init_count = @intCast(u32, array_init.ast.elements.len), + }); break :inst .{ .array = array_type_inst, .elem = .none, diff --git a/src/Module.zig b/src/Module.zig index deff4620b9..4ac2775515 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2728,6 +2728,21 @@ pub const SrcLoc = struct { }; return nodeToSpan(tree, full.ast.value_expr); }, + .node_offset_init_ty => |node_off| { + const tree = try src_loc.file_scope.getTree(gpa); + const node_tags = tree.nodes.items(.tag); + const parent_node = src_loc.declRelativeToNodeIndex(node_off); + + var buf: [2]Ast.Node.Index = undefined; + const full: Ast.full.ArrayInit = switch (node_tags[parent_node]) { + .array_init_one, .array_init_one_comma => tree.arrayInitOne(buf[0..1], parent_node), + .array_init_dot_two, .array_init_dot_two_comma => tree.arrayInitDotTwo(&buf, parent_node), + .array_init_dot, .array_init_dot_comma => tree.arrayInitDot(parent_node), + .array_init, .array_init_comma => tree.arrayInit(parent_node), + else => unreachable, + }; + return nodeToSpan(tree, full.ast.type_expr); + }, } } @@ -3048,6 +3063,9 @@ pub const LazySrcLoc = union(enum) { /// The source location points to the default value of a field. /// The Decl is determined contextually. node_offset_field_default: i32, + /// The source location points to the type of an array or struct initializer. + /// The Decl is determined contextually. + node_offset_init_ty: i32, pub const nodeOffset = if (TracedOffset.want_tracing) nodeOffsetDebug else nodeOffsetRelease; @@ -3126,6 +3144,7 @@ pub const LazySrcLoc = union(enum) { .node_offset_ptr_hostsize, .node_offset_container_tag, .node_offset_field_default, + .node_offset_init_ty, => .{ .file_scope = decl.getFileScope(), .parent_decl_node = decl.src_node, diff --git a/src/Sema.zig b/src/Sema.zig index 5a70679b8d..74c8f0b48d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3493,19 +3493,43 @@ fn validateArrayInitTy( block: *Block, inst: Zir.Inst.Index, ) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - const ty = try sema.resolveType(block, src, inst_data.operand); + const ty_src: LazySrcLoc = .{ .node_offset_init_ty = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data; + const ty = try sema.resolveType(block, ty_src, extra.ty); switch (ty.zigTypeTag()) { - .Array, .Vector => return, + .Array => { + const array_len = ty.arrayLen(); + if (extra.init_count != array_len) { + return sema.fail(block, src, "expected {d} array elements; found {d}", .{ + array_len, extra.init_count, + }); + } + return; + }, + .Vector => { + const array_len = ty.arrayLen(); + if (extra.init_count != array_len) { + return sema.fail(block, src, "expected {d} vector elements; found {d}", .{ + array_len, extra.init_count, + }); + } + return; + }, .Struct => if (ty.isTuple()) { - // TODO validate element count + const array_len = ty.arrayLen(); + if (extra.init_count > array_len) { + return sema.fail(block, src, "expected at most {d} tuple fields; found {d}", .{ + array_len, extra.init_count, + }); + } return; }, else => {}, } - return sema.failWithArrayInitNotSupported(block, src, ty); + return sema.failWithArrayInitNotSupported(block, ty_src, ty); } fn validateStructInitTy( @@ -3741,6 +3765,15 @@ fn validateStructInit( const default_val = struct_ty.structFieldDefaultValue(i); if (default_val.tag() == .unreachable_value) { + if (struct_ty.isTuple()) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + continue; + } const field_name = struct_ty.structFieldName(i); const template = "missing struct field: {s}"; const args = .{field_name}; @@ -3753,7 +3786,10 @@ fn validateStructInit( } const field_src = init_src; // TODO better source location - const default_field_ptr = try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); + const default_field_ptr = if (struct_ty.isTuple()) + try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(u32, i), true) + else + try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); const field_ty = sema.typeOf(default_field_ptr).childType(); const init = try sema.addConstant(field_ty, default_val); try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store); @@ -3868,6 +3904,15 @@ fn validateStructInit( const default_val = struct_ty.structFieldDefaultValue(i); if (default_val.tag() == .unreachable_value) { + if (struct_ty.isTuple()) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + continue; + } const field_name = struct_ty.structFieldName(i); const template = "missing struct field: {s}"; const args = .{field_name}; @@ -3911,7 +3956,10 @@ fn validateStructInit( if (field_ptr != 0) continue; const field_src = init_src; // TODO better source location - const default_field_ptr = try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); + const default_field_ptr = if (struct_ty.isTuple()) + try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(u32, i), true) + else + try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); const field_ty = sema.typeOf(default_field_ptr).childType(); const init = try sema.addConstant(field_ty, field_values[i]); try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store); @@ -3934,15 +3982,24 @@ fn zirValidateArrayInit( const array_ty = sema.typeOf(array_ptr).childType(); const array_len = array_ty.arrayLen(); - if (instrs.len != array_len) { - if (array_ty.zigTypeTag() == .Array) { - return sema.fail(block, init_src, "expected {d} array elements; found {d}", .{ - array_len, instrs.len, - }); - } else { - return sema.fail(block, init_src, "expected {d} vector elements; found {d}", .{ - array_len, instrs.len, - }); + if (instrs.len != array_len and array_ty.isTuple()) { + const struct_obj = array_ty.castTag(.tuple).?.data; + var root_msg: ?*Module.ErrorMsg = null; + for (struct_obj.values) |default_val, i| { + if (i < instrs.len) continue; + + if (default_val.tag() == .unreachable_value) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + } + } + + if (root_msg) |msg| { + return sema.failWithOwnedErrorMsg(block, msg); } } @@ -3995,10 +4052,17 @@ fn zirValidateArrayInit( } first_block_index = @minimum(first_block_index, block_index); - // Array has one possible value, so value is always comptime-known - if (opt_opv) |opv| { - element_vals[i] = opv; - continue; + if (array_ty.isTuple()) { + if (array_ty.structFieldValueComptime(i)) |opv| { + element_vals[i] = opv; + continue; + } + } else { + // Array has one possible value, so value is always comptime-known + if (opt_opv) |opv| { + element_vals[i] = opv; + continue; + } } // If the next instructon is a store with a comptime operand, this element @@ -14710,6 +14774,22 @@ fn finishStructInit( field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val); } } + } else if (struct_ty.isTuple()) { + const struct_obj = struct_ty.castTag(.tuple).?.data; + for (struct_obj.values) |default_val, i| { + if (field_inits[i] != .none) continue; + + if (default_val.tag() == .unreachable_value) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + } else { + field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val); + } + } } else { const struct_obj = struct_ty.castTag(.@"struct").?.data; for (struct_obj.fields.values()) |field, i| { @@ -20255,7 +20335,7 @@ fn tupleFieldVal( return tupleFieldValByIndex(sema, block, src, tuple_byval, field_index, tuple_ty); } -/// Don't forget to check for "len" before calling this. +/// Asserts that `field_name` is not "len". fn tupleFieldIndex( sema: *Sema, block: *Block, @@ -20263,8 +20343,12 @@ fn tupleFieldIndex( field_name: []const u8, field_name_src: LazySrcLoc, ) CompileError!u32 { + assert(!std.mem.eql(u8, field_name, "len")); if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| { if (field_index < tuple_ty.structFieldCount()) return field_index; + return sema.fail(block, field_name_src, "index '{s}' out of bounds of tuple '{}'", .{ + field_name, tuple_ty.fmt(sema.mod), + }); } else |_| {} return sema.fail(block, field_name_src, "no field named '{s}' in tuple '{}'", .{ diff --git a/src/Zir.zig b/src/Zir.zig index 4540032605..ccd677df0b 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1709,7 +1709,7 @@ pub const Inst = struct { .switch_capture_multi_ref = .switch_capture, .array_base_ptr = .un_node, .field_base_ptr = .un_node, - .validate_array_init_ty = .un_node, + .validate_array_init_ty = .pl_node, .validate_struct_init_ty = .un_node, .validate_struct_init = .pl_node, .validate_struct_init_comptime = .pl_node, @@ -3543,6 +3543,11 @@ pub const Inst = struct { line: u32, column: u32, }; + + pub const ArrayInit = struct { + ty: Ref, + init_count: u32, + }; }; pub const SpecialProng = enum { none, @"else", under }; diff --git a/src/print_zir.zig b/src/print_zir.zig index 7723446f1c..6e33154bbd 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -229,7 +229,6 @@ const Writer = struct { .switch_cond_ref, .array_base_ptr, .field_base_ptr, - .validate_array_init_ty, .validate_struct_init_ty, .make_ptr_const, .validate_deref, @@ -246,6 +245,7 @@ const Writer = struct { .bool_br_or, => try self.writeBoolBr(stream, inst), + .validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst), .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), .param_type => try self.writeParamType(stream, inst), .ptr_type => try self.writePtrType(stream, inst), @@ -577,6 +577,18 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeValidateArrayInitTy( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.ty); + try stream.print(", {d}) ", .{extra.init_count}); + try self.writeSrc(stream, inst_data.src()); + } + fn writeArrayTypeSentinel( self: *Writer, stream: anytype, diff --git a/test/cases/compile_errors/tuple_init_edge_cases.zig b/test/cases/compile_errors/tuple_init_edge_cases.zig new file mode 100644 index 0000000000..32b52cdc1f --- /dev/null +++ b/test/cases/compile_errors/tuple_init_edge_cases.zig @@ -0,0 +1,44 @@ +pub export fn entry1() void { + const T = @TypeOf(.{ 123, 3 }); + var b = T{ .@"1" = 3 }; _ = b; + var c = T{ 123, 3 }; _ = c; + var d = T{}; _ = d; +} +pub export fn entry2() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"1" = 3 }; _ = b; + var c = T{ 123, 3 }; _ = c; + var d = T{}; _ = d; +} +pub export fn entry3() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"0" = 123 }; _ = b; +} +comptime { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"0" = 123 }; _ = b; + var c = T{ 123, 2 }; _ = c; + var d = T{}; _ = d; +} +pub export fn entry4() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ 123, 4, 5 }; _ = b; +} +pub export fn entry5() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"0" = 123, .@"2" = 123, .@"1" = 123 }; _ = b; +} + +// error +// backend=stage2 +// target=native +// +// :12:14: error: missing tuple field with index 1 +// :17:14: error: missing tuple field with index 1 +// :29:14: error: expected at most 2 tuple fields; found 3 +// :34:30: error: index '2' out of bounds of tuple 'tuple{comptime comptime_int = 123, u32}' diff --git a/test/cases/compile_errors/wrong_size_to_an_array_literal.zig b/test/cases/compile_errors/wrong_size_to_an_array_literal.zig index bb8b3c215c..a38d8d4d85 100644 --- a/test/cases/compile_errors/wrong_size_to_an_array_literal.zig +++ b/test/cases/compile_errors/wrong_size_to_an_array_literal.zig @@ -7,4 +7,4 @@ comptime { // backend=stage2 // target=native // -// :2:31: error: index 2 outside array of length 2 +// :2:24: error: expected 2 array elements; found 3 From a48251735787f590491caf4e446dad74c66aa13c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 18:40:01 -0700 Subject: [PATCH 09/29] std.debug: default signal handler also handles SIGFPE --- lib/std/debug.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index b406fed471..7d0dcd35d0 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1784,6 +1784,7 @@ pub fn updateSegfaultHandler(act: ?*const os.Sigaction) error{OperationNotSuppor try os.sigaction(os.SIG.SEGV, act, null); try os.sigaction(os.SIG.ILL, act, null); try os.sigaction(os.SIG.BUS, act, null); + try os.sigaction(os.SIG.FPE, act, null); } /// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); @@ -1845,6 +1846,7 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any os.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), + os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), else => unreachable, } catch os.abort(); } From 1fc24e8d807a489254be46c9fcb951617a04f3b1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 18:40:30 -0700 Subject: [PATCH 10/29] Sema: enhance `div` instruction analysis Concrete improvements: * Added safety for integer overflow (-MAX_INT/-1) * Omit division by zero safety check when RHS is comptime known to be non-zero. * Avoid emitting `_optimized` variants of AIR instructions for integers (this suffix is intended to be used for floats only). Subjective changes: I extracted the div logic out from analyzeArithmetic in order to reduce the amount of branches - not for performance reasons but for code clarity. It is more lines of code however, and some logic is duplicated. --- src/Sema.zig | 369 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 278 insertions(+), 91 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 74c8f0b48d..a9ad7d0a8a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -875,7 +875,7 @@ fn analyzeBodyInner( .add => try sema.zirArithmetic(block, inst, .add), .addwrap => try sema.zirArithmetic(block, inst, .addwrap), .add_sat => try sema.zirArithmetic(block, inst, .add_sat), - .div => try sema.zirArithmetic(block, inst, .div), + .div => try sema.zirDiv(block, inst), .div_exact => try sema.zirArithmetic(block, inst, .div_exact), .div_floor => try sema.zirArithmetic(block, inst, .div_floor), .div_trunc => try sema.zirArithmetic(block, inst, .div_trunc), @@ -10920,6 +10920,243 @@ fn zirArithmetic( return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); } +fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const lhs_scalar_ty = lhs_ty.scalarType(); + const rhs_scalar_ty = rhs_ty.scalarType(); + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + // TODO: emit compile error when .div is used on integers and there would be an + // ambiguous result between div_floor and div_trunc. + + // For integers: + // If the lhs is zero, then zero is returned regardless of rhs. + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined: + // * if lhs type is signed: + // * if rhs is comptime-known and not -1, result is undefined + // * if rhs is -1 or runtime-known, compile error because there is a + // possible value (-min_int / -1) for which division would be + // illegal behavior. + // * if lhs type is unsigned, undef is returned regardless of rhs. + // + // For floats: + // If the rhs is zero: + // * comptime_float: compile error for division by zero. + // * other float type: + // * if the lhs is zero: QNaN + // * otherwise: +Inf or -Inf depending on lhs sign + // If the rhs is undefined: + // * comptime_float: compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // * other float type: result is undefined + // If the lhs is undefined, result is undefined. + switch (scalar_tag) { + .Int, .ComptimeInt, .ComptimeFloat => { + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + }, + else => {}, + } + + const runtime_src = rs: { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { + if (maybe_rhs_val) |rhs_val| { + if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + return sema.addConstUndef(resolved_type); + } + } + return sema.failWithUseOfUndef(block, rhs_src); + } + return sema.addConstUndef(resolved_type); + } + + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + resolved_type, + try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), + ); + } else { + return sema.addConstant( + resolved_type, + try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), + ); + } + } else { + break :rs rhs_src; + } + } else { + break :rs lhs_src; + } + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + if (block.wantSafety()) { + int_overflow: { + if (!is_int) break :int_overflow; + + // If the LHS is unsigned, it cannot cause overflow. + if (!lhs_scalar_ty.isSignedInt()) break :int_overflow; + + // If the LHS is widened to a larger integer type, no overflow is possible. + if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) { + break :int_overflow; + } + + const min_int = try resolved_type.minInt(sema.arena, target); + const neg_one = try Value.Tag.int_i64.create(sema.arena, -1); + + // If the LHS is comptime-known to be not equal to the min int, + // no overflow is possible. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) break :int_overflow; + } + + // If the RHS is comptime-known to not be equal to -1, no overflow is possible. + if (maybe_rhs_val) |rhs_val| { + if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) break :int_overflow; + } + + var ok: Air.Inst.Ref = .none; + if (resolved_type.zigTypeTag() == .Vector) { + const vector_ty_ref = try sema.addType(resolved_type); + if (maybe_lhs_val == null) { + const min_int_ref = try sema.addConstant( + resolved_type, + try Value.Tag.repeated.create(sema.arena, min_int), + ); + ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); + } + if (maybe_rhs_val == null) { + const neg_one_ref = try sema.addConstant( + resolved_type, + try Value.Tag.repeated.create(sema.arena, neg_one), + ); + const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); + if (ok == .none) { + ok = rhs_ok; + } else { + ok = try block.addBinOp(.bool_or, ok, rhs_ok); + } + } + assert(ok != .none); + ok = try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = ok, + .operation = .And, + } }, + }); + } else { + if (maybe_lhs_val == null) { + const min_int_ref = try sema.addConstant(resolved_type, min_int); + ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref); + } + if (maybe_rhs_val == null) { + const neg_one_ref = try sema.addConstant(resolved_type, neg_one); + const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref); + if (ok == .none) { + ok = rhs_ok; + } else { + ok = try block.addBinOp(.bool_or, ok, rhs_ok); + } + } + assert(ok != .none); + } + try sema.addSafetyCheck(block, ok, .integer_overflow); + } + + div_by_zero: { + // Strict IEEE floats have well-defined division by zero. + if (!is_int and block.float_mode == .Strict) break :div_by_zero; + + // If rhs was comptime-known to be zero a compile error would have been + // emitted above. + if (maybe_rhs_val != null) break :div_by_zero; + + const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { + const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); + const zero = try sema.addConstant(resolved_type, zero_val); + const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); + break :ok try block.addInst(.{ + .tag = if (is_int) .reduce else .reduce_optimized, + .data = .{ .reduce = .{ + .operand = ok, + .operation = .And, + } }, + }); + } else ok: { + const zero = try sema.addConstant(resolved_type, Value.zero); + break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero); + }; + try sema.addSafetyCheck(block, ok, .divide_by_zero); + } + } + + const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) { + .Optimized => Air.Inst.Tag.div_float_optimized, + .Strict => Air.Inst.Tag.div_float, + }; + return block.addBinOp(air_tag, casted_lhs, casted_rhs); +} + +fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag { + if (is_int) return normal; + return switch (block.float_mode) { + .Strict => normal, + .Optimized => optimized, + }; +} + fn zirOverflowArithmetic( sema: *Sema, block: *Block, @@ -11399,96 +11636,6 @@ fn analyzeArithmetic( } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; }, - .div => { - // TODO: emit compile error when .div is used on integers and there would be an - // ambiguous result between div_floor and div_trunc. - - // For integers: - // If the lhs is zero, then zero is returned regardless of rhs. - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined: - // * if lhs type is signed: - // * if rhs is comptime-known and not -1, result is undefined - // * if rhs is -1 or runtime-known, compile error because there is a - // possible value (-min_int / -1) for which division would be - // illegal behavior. - // * if lhs type is unsigned, undef is returned regardless of rhs. - // TODO: emit runtime safety for division by zero - // - // For floats: - // If the rhs is zero: - // * comptime_float: compile error for division by zero. - // * other float type: - // * if the lhs is zero: QNaN - // * otherwise: +Inf or -Inf depending on lhs sign - // If the rhs is undefined: - // * comptime_float: compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // * other float type: result is undefined - // If the lhs is undefined, result is undefined. - switch (scalar_tag) { - .Int, .ComptimeInt, .ComptimeFloat => { - if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); - } - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - } - }, - else => {}, - } - - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { - if (maybe_rhs_val) |rhs_val| { - if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { - return sema.addConstUndef(resolved_type); - } - } - return sema.failWithUseOfUndef(block, rhs_src); - } - return sema.addConstUndef(resolved_type); - } - - if (maybe_rhs_val) |rhs_val| { - if (is_int) { - return sema.addConstant( - resolved_type, - try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), - ); - } else { - return sema.addConstant( - resolved_type, - try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), - ); - } - } else { - if (is_int) { - break :rs .{ .src = rhs_src, .air_tag = .div_trunc }; - } else { - break :rs .{ .src = rhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float }; - } - } - } else { - if (is_int) { - break :rs .{ .src = lhs_src, .air_tag = .div_trunc }; - } else { - break :rs .{ .src = lhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float }; - } - } - }, .div_trunc => { // For integers: // If the lhs is zero, then zero is returned regardless of rhs. @@ -16804,6 +16951,46 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr } } +fn checkInvalidPtrArithmetic( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ty: Type, + zir_tag: Zir.Inst.Tag, +) CompileError!void { + switch (try ty.zigTypeTagOrPoison()) { + .Pointer => switch (ty.ptrSize()) { + .One, .Slice => return, + .Many, .C => return sema.fail( + block, + src, + "invalid pointer arithmetic operand: '{s}''", + .{@tagName(zir_tag)}, + ), + }, + else => return, + } +} + +fn checkArithmeticOp( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + scalar_tag: std.builtin.TypeId, + lhs_zig_ty_tag: std.builtin.TypeId, + rhs_zig_ty_tag: std.builtin.TypeId, + zir_tag: Zir.Inst.Tag, +) CompileError!void { + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; + + if (!is_int and !(is_float and floatOpAllowed(zir_tag))) { + return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ + @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), + }); + } +} + fn checkPtrOperand( sema: *Sema, block: *Block, From 40f8f0134f5da9baaefd0fdab529d5585fa46199 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 20:46:56 -0700 Subject: [PATCH 11/29] Sema: enhance div_trunc, div_exact, div_floor * No longer emit div_exact AIR instruction that can produce a remainder, invoking undefined behavior. * div_trunc, div_exact, div_floor are extracted from analyzeArithmetic and directly handled similarly to div_trunc, integrating them with integer overflow safety checking. * Also they no longer emit divide-by-zero safety checking when RHS is comptime known to be non-zero. --- src/Air.zig | 5 +- src/Sema.zig | 853 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 503 insertions(+), 355 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 2c0c38a2ef..302822fc99 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -111,8 +111,9 @@ pub const Inst = struct { div_floor, /// Same as `div_floor` with optimized float mode. div_floor_optimized, - /// Integer or float division. Guaranteed no remainder. - /// For integers, wrapping is undefined behavior. + /// Integer or float division. + /// If a remainder would be produced, undefined behavior occurs. + /// For integers, overflow is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. diff --git a/src/Sema.zig b/src/Sema.zig index a9ad7d0a8a..5b5d51576c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -875,10 +875,6 @@ fn analyzeBodyInner( .add => try sema.zirArithmetic(block, inst, .add), .addwrap => try sema.zirArithmetic(block, inst, .addwrap), .add_sat => try sema.zirArithmetic(block, inst, .add_sat), - .div => try sema.zirDiv(block, inst), - .div_exact => try sema.zirArithmetic(block, inst, .div_exact), - .div_floor => try sema.zirArithmetic(block, inst, .div_floor), - .div_trunc => try sema.zirArithmetic(block, inst, .div_trunc), .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), .mod => try sema.zirArithmetic(block, inst, .mod), .rem => try sema.zirArithmetic(block, inst, .rem), @@ -889,6 +885,11 @@ fn analyzeBodyInner( .subwrap => try sema.zirArithmetic(block, inst, .subwrap), .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat), + .div => try sema.zirDiv(block, inst), + .div_exact => try sema.zirDivExact(block, inst), + .div_floor => try sema.zirDivFloor(block, inst), + .div_trunc => try sema.zirDivTrunc(block, inst), + .maximum => try sema.zirMinMax(block, inst, .max), .minimum => try sema.zirMinMax(block, inst, .min), @@ -10999,6 +11000,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } + // TODO: if the RHS is one, return the LHS directly } }, else => {}, @@ -11041,105 +11043,8 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins try sema.requireRuntimeBlock(block, src, runtime_src); if (block.wantSafety()) { - int_overflow: { - if (!is_int) break :int_overflow; - - // If the LHS is unsigned, it cannot cause overflow. - if (!lhs_scalar_ty.isSignedInt()) break :int_overflow; - - // If the LHS is widened to a larger integer type, no overflow is possible. - if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) { - break :int_overflow; - } - - const min_int = try resolved_type.minInt(sema.arena, target); - const neg_one = try Value.Tag.int_i64.create(sema.arena, -1); - - // If the LHS is comptime-known to be not equal to the min int, - // no overflow is possible. - if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) break :int_overflow; - } - - // If the RHS is comptime-known to not be equal to -1, no overflow is possible. - if (maybe_rhs_val) |rhs_val| { - if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) break :int_overflow; - } - - var ok: Air.Inst.Ref = .none; - if (resolved_type.zigTypeTag() == .Vector) { - const vector_ty_ref = try sema.addType(resolved_type); - if (maybe_lhs_val == null) { - const min_int_ref = try sema.addConstant( - resolved_type, - try Value.Tag.repeated.create(sema.arena, min_int), - ); - ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); - } - if (maybe_rhs_val == null) { - const neg_one_ref = try sema.addConstant( - resolved_type, - try Value.Tag.repeated.create(sema.arena, neg_one), - ); - const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); - if (ok == .none) { - ok = rhs_ok; - } else { - ok = try block.addBinOp(.bool_or, ok, rhs_ok); - } - } - assert(ok != .none); - ok = try block.addInst(.{ - .tag = .reduce, - .data = .{ .reduce = .{ - .operand = ok, - .operation = .And, - } }, - }); - } else { - if (maybe_lhs_val == null) { - const min_int_ref = try sema.addConstant(resolved_type, min_int); - ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref); - } - if (maybe_rhs_val == null) { - const neg_one_ref = try sema.addConstant(resolved_type, neg_one); - const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref); - if (ok == .none) { - ok = rhs_ok; - } else { - ok = try block.addBinOp(.bool_or, ok, rhs_ok); - } - } - assert(ok != .none); - } - try sema.addSafetyCheck(block, ok, .integer_overflow); - } - - div_by_zero: { - // Strict IEEE floats have well-defined division by zero. - if (!is_int and block.float_mode == .Strict) break :div_by_zero; - - // If rhs was comptime-known to be zero a compile error would have been - // emitted above. - if (maybe_rhs_val != null) break :div_by_zero; - - const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { - const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); - const zero = try sema.addConstant(resolved_type, zero_val); - const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); - break :ok try block.addInst(.{ - .tag = if (is_int) .reduce else .reduce_optimized, - .data = .{ .reduce = .{ - .operand = ok, - .operation = .And, - } }, - }); - } else ok: { - const zero = try sema.addConstant(resolved_type, Value.zero); - break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero); - }; - try sema.addSafetyCheck(block, ok, .divide_by_zero); - } + try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int); + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); } const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) { @@ -11149,6 +11054,497 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins return block.addBinOp(air_tag, casted_lhs, casted_rhs); } +fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_exact); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const lhs_scalar_ty = lhs_ty.scalarType(); + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_exact); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + const runtime_src = rs: { + // For integers: + // If the lhs is zero, then zero is returned regardless of rhs. + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, compile error because there is a possible + // value for which the division would result in a remainder. + // TODO: emit runtime safety for if there is a remainder + // TODO: emit runtime safety for division by zero + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, compile error because there is a possible + // value for which the division would result in a remainder. + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + // TODO: if the RHS is one, return the LHS directly + } + if (maybe_lhs_val) |lhs_val| { + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + // TODO: emit compile error if there is a remainder + return sema.addConstant( + resolved_type, + try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), + ); + } else { + // TODO: emit compile error if there is a remainder + return sema.addConstant( + resolved_type, + try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), + ); + } + } else break :rs rhs_src; + } else break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + // Depending on whether safety is enabled, we will have a slightly different strategy + // here. The `div_exact` AIR instruction causes undefined behavior if a remainder + // is produced, so in the safety check case, it cannot be used. Instead we do a + // div_trunc and check for remainder. + + if (block.wantSafety()) { + try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int); + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); + + const result = try block.addBinOp(.div_trunc, casted_lhs, casted_rhs); + const ok = if (!is_int) ok: { + const floored = try block.addUnOp(.floor, result); + + if (resolved_type.zigTypeTag() == .Vector) { + const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type)); + break :ok try block.addInst(.{ + .tag = switch (block.float_mode) { + .Strict => .reduce, + .Optimized => .reduce_optimized, + }, + .data = .{ .reduce = .{ + .operand = eql, + .operation = .And, + } }, + }); + } else { + const is_in_range = try block.addBinOp(switch (block.float_mode) { + .Strict => .cmp_eq, + .Optimized => .cmp_eq_optimized, + }, result, floored); + break :ok is_in_range; + } + } else ok: { + const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs); + + if (resolved_type.zigTypeTag() == .Vector) { + const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); + const zero = try sema.addConstant(resolved_type, zero_val); + const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type)); + break :ok try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = eql, + .operation = .And, + } }, + }); + } else { + const zero = try sema.addConstant(resolved_type, Value.zero); + const is_in_range = try block.addBinOp(.cmp_eq, remainder, zero); + break :ok is_in_range; + } + }; + try sema.addSafetyCheck(block, ok, .exact_division_remainder); + return result; + } + + return block.addBinOp(airTag(block, is_int, .div_exact, .div_exact_optimized), casted_lhs, casted_rhs); +} + +fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_floor); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const lhs_scalar_ty = lhs_ty.scalarType(); + const rhs_scalar_ty = rhs_ty.scalarType(); + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_floor); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + const runtime_src = rs: { + // For integers: + // If the lhs is zero, then zero is returned regardless of rhs. + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined: + // * if lhs type is signed: + // * if rhs is comptime-known and not -1, result is undefined + // * if rhs is -1 or runtime-known, compile error because there is a + // possible value (-min_int / -1) for which division would be + // illegal behavior. + // * if lhs type is unsigned, undef is returned regardless of rhs. + // TODO: emit runtime safety for division by zero + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + // TODO: if the RHS is one, return the LHS directly + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { + if (maybe_rhs_val) |rhs_val| { + if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + return sema.addConstUndef(resolved_type); + } + } + return sema.failWithUseOfUndef(block, rhs_src); + } + return sema.addConstUndef(resolved_type); + } + + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + resolved_type, + try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target), + ); + } else { + return sema.addConstant( + resolved_type, + try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target), + ); + } + } else break :rs rhs_src; + } else break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + if (block.wantSafety()) { + try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int); + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); + } + + return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs); +} + +fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_trunc); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const lhs_scalar_ty = lhs_ty.scalarType(); + const rhs_scalar_ty = rhs_ty.scalarType(); + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_trunc); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + const runtime_src = rs: { + // For integers: + // If the lhs is zero, then zero is returned regardless of rhs. + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined: + // * if lhs type is signed: + // * if rhs is comptime-known and not -1, result is undefined + // * if rhs is -1 or runtime-known, compile error because there is a + // possible value (-min_int / -1) for which division would be + // illegal behavior. + // * if lhs type is unsigned, undef is returned regardless of rhs. + // TODO: emit runtime safety for division by zero + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { + if (maybe_rhs_val) |rhs_val| { + if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + return sema.addConstUndef(resolved_type); + } + } + return sema.failWithUseOfUndef(block, rhs_src); + } + return sema.addConstUndef(resolved_type); + } + + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + resolved_type, + try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), + ); + } else { + return sema.addConstant( + resolved_type, + try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target), + ); + } + } else break :rs rhs_src; + } else break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + if (block.wantSafety()) { + try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int); + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); + } + + return block.addBinOp(airTag(block, is_int, .div_trunc, .div_trunc_optimized), casted_lhs, casted_rhs); +} + +fn addDivIntOverflowSafety( + sema: *Sema, + block: *Block, + resolved_type: Type, + lhs_scalar_ty: Type, + maybe_lhs_val: ?Value, + maybe_rhs_val: ?Value, + casted_lhs: Air.Inst.Ref, + casted_rhs: Air.Inst.Ref, + is_int: bool, +) CompileError!void { + if (!is_int) return; + + // If the LHS is unsigned, it cannot cause overflow. + if (!lhs_scalar_ty.isSignedInt()) return; + + const mod = sema.mod; + const target = mod.getTarget(); + + // If the LHS is widened to a larger integer type, no overflow is possible. + if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) { + return; + } + + const min_int = try resolved_type.minInt(sema.arena, target); + const neg_one = try Value.Tag.int_i64.create(sema.arena, -1); + + // If the LHS is comptime-known to be not equal to the min int, + // no overflow is possible. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) return; + } + + // If the RHS is comptime-known to not be equal to -1, no overflow is possible. + if (maybe_rhs_val) |rhs_val| { + if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) return; + } + + var ok: Air.Inst.Ref = .none; + if (resolved_type.zigTypeTag() == .Vector) { + const vector_ty_ref = try sema.addType(resolved_type); + if (maybe_lhs_val == null) { + const min_int_ref = try sema.addConstant( + resolved_type, + try Value.Tag.repeated.create(sema.arena, min_int), + ); + ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); + } + if (maybe_rhs_val == null) { + const neg_one_ref = try sema.addConstant( + resolved_type, + try Value.Tag.repeated.create(sema.arena, neg_one), + ); + const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); + if (ok == .none) { + ok = rhs_ok; + } else { + ok = try block.addBinOp(.bool_or, ok, rhs_ok); + } + } + assert(ok != .none); + ok = try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = ok, + .operation = .And, + } }, + }); + } else { + if (maybe_lhs_val == null) { + const min_int_ref = try sema.addConstant(resolved_type, min_int); + ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref); + } + if (maybe_rhs_val == null) { + const neg_one_ref = try sema.addConstant(resolved_type, neg_one); + const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref); + if (ok == .none) { + ok = rhs_ok; + } else { + ok = try block.addBinOp(.bool_or, ok, rhs_ok); + } + } + assert(ok != .none); + } + try sema.addSafetyCheck(block, ok, .integer_overflow); +} + +fn addDivByZeroSafety( + sema: *Sema, + block: *Block, + resolved_type: Type, + maybe_rhs_val: ?Value, + casted_rhs: Air.Inst.Ref, + is_int: bool, +) CompileError!void { + // Strict IEEE floats have well-defined division by zero. + if (!is_int and block.float_mode == .Strict) return; + + // If rhs was comptime-known to be zero a compile error would have been + // emitted above. + if (maybe_rhs_val != null) return; + + const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { + const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); + const zero = try sema.addConstant(resolved_type, zero_val); + const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); + break :ok try block.addInst(.{ + .tag = if (is_int) .reduce else .reduce_optimized, + .data = .{ .reduce = .{ + .operand = ok, + .operation = .And, + } }, + }); + } else ok: { + const zero = try sema.addConstant(resolved_type, Value.zero); + break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero); + }; + try sema.addSafetyCheck(block, ok, .divide_by_zero); +} + fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag { if (is_int) return normal; return switch (block.float_mode) { @@ -11423,13 +11819,8 @@ fn analyzeArithmetic( const scalar_tag = resolved_type.scalarType().zigTypeTag(); const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; - const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; - if (!is_int and !(is_float and floatOpAllowed(zir_tag))) { - return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ - @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), - }); - } + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag); const mod = sema.mod; const target = mod.getTarget(); @@ -11636,187 +12027,6 @@ fn analyzeArithmetic( } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; }, - .div_trunc => { - // For integers: - // If the lhs is zero, then zero is returned regardless of rhs. - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined: - // * if lhs type is signed: - // * if rhs is comptime-known and not -1, result is undefined - // * if rhs is -1 or runtime-known, compile error because there is a - // possible value (-min_int / -1) for which division would be - // illegal behavior. - // * if lhs type is unsigned, undef is returned regardless of rhs. - // TODO: emit runtime safety for division by zero - // - // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, result is undefined. - if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); - } - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - } - const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_trunc_optimized else .div_trunc; - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { - if (maybe_rhs_val) |rhs_val| { - if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { - return sema.addConstUndef(resolved_type); - } - } - return sema.failWithUseOfUndef(block, rhs_src); - } - return sema.addConstUndef(resolved_type); - } - - if (maybe_rhs_val) |rhs_val| { - if (is_int) { - return sema.addConstant( - resolved_type, - try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), - ); - } else { - return sema.addConstant( - resolved_type, - try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target), - ); - } - } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; - } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; - }, - .div_floor => { - // For integers: - // If the lhs is zero, then zero is returned regardless of rhs. - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined: - // * if lhs type is signed: - // * if rhs is comptime-known and not -1, result is undefined - // * if rhs is -1 or runtime-known, compile error because there is a - // possible value (-min_int / -1) for which division would be - // illegal behavior. - // * if lhs type is unsigned, undef is returned regardless of rhs. - // TODO: emit runtime safety for division by zero - // - // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, result is undefined. - if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); - } - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - } - const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_floor_optimized else .div_floor; - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { - if (maybe_rhs_val) |rhs_val| { - if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { - return sema.addConstUndef(resolved_type); - } - } - return sema.failWithUseOfUndef(block, rhs_src); - } - return sema.addConstUndef(resolved_type); - } - - if (maybe_rhs_val) |rhs_val| { - if (is_int) { - return sema.addConstant( - resolved_type, - try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target), - ); - } else { - return sema.addConstant( - resolved_type, - try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target), - ); - } - } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; - } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; - }, - .div_exact => { - // For integers: - // If the lhs is zero, then zero is returned regardless of rhs. - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, compile error because there is a possible - // value for which the division would result in a remainder. - // TODO: emit runtime safety for if there is a remainder - // TODO: emit runtime safety for division by zero - // - // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, compile error because there is a possible - // value for which the division would result in a remainder. - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } else { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); - } - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - } - const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_exact_optimized else .div_exact; - if (maybe_lhs_val) |lhs_val| { - if (maybe_rhs_val) |rhs_val| { - if (is_int) { - // TODO: emit compile error if there is a remainder - return sema.addConstant( - resolved_type, - try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), - ); - } else { - // TODO: emit compile error if there is a remainder - return sema.addConstant( - resolved_type, - try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), - ); - } - } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; - } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; - }, .mul => { // For integers: // If either of the operands are zero, the result is zero. @@ -12195,28 +12405,6 @@ fn analyzeArithmetic( } } switch (rs.air_tag) { - // zig fmt: off - .div_float, .div_exact, .div_trunc, .div_floor, .div_float_optimized, - .div_exact_optimized, .div_trunc_optimized, .div_floor_optimized - // zig fmt: on - => if (scalar_tag == .Int or block.float_mode == .Optimized) { - const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { - const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); - const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val); - const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); - break :ok try block.addInst(.{ - .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, - .data = .{ .reduce = .{ - .operand = ok, - .operation = .And, - } }, - }); - } else ok: { - const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); - break :ok try block.addBinOp(if (block.float_mode == .Optimized) .cmp_neq_optimized else .cmp_neq, casted_rhs, zero); - }; - try sema.addSafetyCheck(block, ok, .divide_by_zero); - }, .rem, .mod, .rem_optimized, .mod_optimized => { const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); @@ -12243,47 +12431,6 @@ fn analyzeArithmetic( }, else => {}, } - if (rs.air_tag == .div_exact or rs.air_tag == .div_exact_optimized) { - const result = try block.addBinOp(.div_exact, casted_lhs, casted_rhs); - const ok = if (scalar_tag == .Float) ok: { - const floored = try block.addUnOp(.floor, result); - - if (resolved_type.zigTypeTag() == .Vector) { - const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type)); - break :ok try block.addInst(.{ - .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, - .data = .{ .reduce = .{ - .operand = eql, - .operation = .And, - } }, - }); - } else { - const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, result, floored); - break :ok is_in_range; - } - } else ok: { - const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs); - - if (resolved_type.zigTypeTag() == .Vector) { - const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); - const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val); - const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type)); - break :ok try block.addInst(.{ - .tag = .reduce, - .data = .{ .reduce = .{ - .operand = eql, - .operation = .And, - } }, - }); - } else { - const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); - const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, remainder, zero); - break :ok is_in_range; - } - }; - try sema.addSafetyCheck(block, ok, .exact_division_remainder); - return result; - } } return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } From daac39364ad94b5ae374f7391653789da3f578a8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 21:34:32 -0700 Subject: [PATCH 12/29] fix compile error test case note column number --- .../method_call_with_first_arg_type_wrong_container.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig b/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig index 9e05a370f9..ad481a6158 100644 --- a/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig +++ b/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig @@ -3,14 +3,14 @@ pub const List = struct { allocator: *Allocator, pub fn init(allocator: *Allocator) List { - return List { + return List{ .len = 0, .allocator = allocator, }; } }; -pub var global_allocator = Allocator { +pub var global_allocator = Allocator{ .field = 1234, }; @@ -28,4 +28,4 @@ export fn foo() void { // target=native // // :23:6: error: no field or member function named 'init' in 'tmp.List' -// :1:14: note: struct declared here +// :1:18: note: struct declared here From 1b1c70ce381cc3c76517c846eafcd3425a40ce9c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 21:40:05 -0700 Subject: [PATCH 13/29] disable failing incremental compilation test case see #12288 now `zig build test-cases -Denable-llvm` passes locally for me. --- test/cases/llvm/shift_right_plus_left.0.zig | 12 ------------ test/cases/llvm/shift_right_plus_left.1.zig | 10 ---------- 2 files changed, 22 deletions(-) delete mode 100644 test/cases/llvm/shift_right_plus_left.0.zig delete mode 100644 test/cases/llvm/shift_right_plus_left.1.zig diff --git a/test/cases/llvm/shift_right_plus_left.0.zig b/test/cases/llvm/shift_right_plus_left.0.zig deleted file mode 100644 index 23b733c493..0000000000 --- a/test/cases/llvm/shift_right_plus_left.0.zig +++ /dev/null @@ -1,12 +0,0 @@ -pub fn main() void { - var i: u32 = 16; - assert(i >> 1, 8); -} -fn assert(a: u32, b: u32) void { - if (a != b) unreachable; -} - -// run -// backend=llvm -// target=x86_64-linux,x86_64-macos -// diff --git a/test/cases/llvm/shift_right_plus_left.1.zig b/test/cases/llvm/shift_right_plus_left.1.zig deleted file mode 100644 index 994b67b9d0..0000000000 --- a/test/cases/llvm/shift_right_plus_left.1.zig +++ /dev/null @@ -1,10 +0,0 @@ -pub fn main() void { - var i: u32 = 16; - assert(i << 1, 32); -} -fn assert(a: u32, b: u32) void { - if (a != b) unreachable; -} - -// run -// From 58540f968a2ae53b4b1ff5a917fdb404088a222a Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Thu, 28 Jul 2022 22:16:59 -0700 Subject: [PATCH 14/29] ELF: Scan for dylibs-as-objects when adding rpaths Shared libraries can be provided on the command line as if they were objects, as a path to the ".so" file. The "each-lib-rpath" functionality was ignoring these shared libraries accidentally, causing missing rpaths in the output executable. --- src/link/Elf.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 3e9b1ab3ed..917e4c18d1 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1592,6 +1592,15 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v } } } + for (self.base.options.objects) |obj| { + if (Compilation.classifyFileExt(obj.path) == .shared_library) { + const lib_dir_path = std.fs.path.dirname(obj.path).?; + if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) { + try argv.append("-rpath"); + try argv.append(lib_dir_path); + } + } + } } for (self.base.options.lib_dirs) |lib_dir| { From a7a6f38eebfa2bf6dbe4d5f9579f0b2d54593820 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 21:40:57 -0700 Subject: [PATCH 15/29] Sema: fix runtime safety for integer overflow with vectors --- src/Sema.zig | 16 +++++++--------- src/type.zig | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 5b5d51576c..59b312e000 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11450,7 +11450,11 @@ fn addDivIntOverflowSafety( } const min_int = try resolved_type.minInt(sema.arena, target); - const neg_one = try Value.Tag.int_i64.create(sema.arena, -1); + const neg_one_scalar = try Value.Tag.int_i64.create(sema.arena, -1); + const neg_one = if (resolved_type.zigTypeTag() == .Vector) + try Value.Tag.repeated.create(sema.arena, neg_one_scalar) + else + neg_one_scalar; // If the LHS is comptime-known to be not equal to the min int, // no overflow is possible. @@ -11467,17 +11471,11 @@ fn addDivIntOverflowSafety( if (resolved_type.zigTypeTag() == .Vector) { const vector_ty_ref = try sema.addType(resolved_type); if (maybe_lhs_val == null) { - const min_int_ref = try sema.addConstant( - resolved_type, - try Value.Tag.repeated.create(sema.arena, min_int), - ); + const min_int_ref = try sema.addConstant(resolved_type, min_int); ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); } if (maybe_rhs_val == null) { - const neg_one_ref = try sema.addConstant( - resolved_type, - try Value.Tag.repeated.create(sema.arena, neg_one), - ); + const neg_one_ref = try sema.addConstant(resolved_type, neg_one); const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); if (ok == .none) { ok = rhs_ok; diff --git a/src/type.zig b/src/type.zig index 6750ec724b..3b1b0dd59d 100644 --- a/src/type.zig +++ b/src/type.zig @@ -5201,10 +5201,20 @@ pub const Type = extern union { }; } + // Works for vectors and vectors of integers. + pub fn minInt(ty: Type, arena: Allocator, target: Target) !Value { + const scalar = try minIntScalar(ty.scalarType(), arena, target); + if (ty.zigTypeTag() == .Vector) { + return Value.Tag.repeated.create(arena, scalar); + } else { + return scalar; + } + } + /// Asserts that self.zigTypeTag() == .Int. - pub fn minInt(self: Type, arena: Allocator, target: Target) !Value { - assert(self.zigTypeTag() == .Int); - const info = self.intInfo(target); + pub fn minIntScalar(ty: Type, arena: Allocator, target: Target) !Value { + assert(ty.zigTypeTag() == .Int); + const info = ty.intInfo(target); if (info.signedness == .unsigned) { return Value.zero; From 932d1f785ea4ca2b6d0094e0f2217eb3e335b0d7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Jul 2022 21:42:07 -0700 Subject: [PATCH 16/29] CI: -Denable-llvm for test-cases This requires using -Dstatic-llvm and setting the search prefix and the target, just like it is required for building stage2 and stage3. This prevents Zig from trying to integrate with the system, which would trigger an error due to the `cc` command not being installed. closes #12144 --- ci/zinc/linux_test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/zinc/linux_test.sh b/ci/zinc/linux_test.sh index 4c3b3842bf..3a54e82c38 100755 --- a/ci/zinc/linux_test.sh +++ b/ci/zinc/linux_test.sh @@ -63,8 +63,7 @@ stage3/bin/zig build test-translate-c -fqemu -fwasmtime -Denable-llvm stage3/bin/zig build test-run-translated-c -fqemu -fwasmtime -Denable-llvm stage3/bin/zig build test-standalone -fqemu -fwasmtime -Denable-llvm stage3/bin/zig build test-cli -fqemu -fwasmtime -Denable-llvm -# https://github.com/ziglang/zig/issues/12144 -stage3/bin/zig build test-cases -fqemu -fwasmtime +stage3/bin/zig build test-cases -fqemu -fwasmtime -Dstatic-llvm -Dtarget=native-native-musl --search-prefix "$DEPS_LOCAL" stage3/bin/zig build test-link -fqemu -fwasmtime -Denable-llvm $STAGE1_ZIG build test-stack-traces -fqemu -fwasmtime From b3d0694fc53e2fc7c7c412d68e2f82315070ddfd Mon Sep 17 00:00:00 2001 From: Wei Fu Date: Thu, 28 Jul 2022 00:14:59 +0800 Subject: [PATCH 17/29] stage1: remove deadcode ast_print Fixes: 2a990d696 ("stage1: rework tokenizer to match stage2") Fixes: b6354ddd5 ("move AST rendering code to separate file") Signed-off-by: Wei Fu --- src/stage1/parser.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/stage1/parser.hpp b/src/stage1/parser.hpp index 8ac8ce6de1..065f951e91 100644 --- a/src/stage1/parser.hpp +++ b/src/stage1/parser.hpp @@ -14,8 +14,6 @@ AstNode * ast_parse(Buf *buf, ZigType *owner, ErrColor err_color); -void ast_print(AstNode *node, int indent); - void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *context), void *context); Buf *node_identifier_buf(AstNode *node); From 4fc2acdaa4f2b649b17ddf958d2608abc4787a4e Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 15 Jul 2022 17:25:38 -0700 Subject: [PATCH 18/29] build.zig: Emit warning if "config.h" cannot be found We now warn the user if config.h could not be located. This also updates the search to stop early upon encountering a `.git` directory, so that we avoid recursing outside of the zig source if possible. --- build.zig | 78 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/build.zig b/build.zig index 3e7d1888cd..cbc530e877 100644 --- a/build.zig +++ b/build.zig @@ -238,7 +238,15 @@ pub fn build(b: *Builder) !void { exe_options.addOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version)); if (enable_llvm) { - const cmake_cfg = if (static_llvm) null else findAndParseConfigH(b, config_h_path_option); + const cmake_cfg = if (static_llvm) null else blk: { + if (findConfigH(b, config_h_path_option)) |config_h_path| { + const file_contents = fs.cwd().readFileAlloc(b.allocator, config_h_path, max_config_h_bytes) catch unreachable; + break :blk parseConfigH(b, file_contents); + } else { + std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{}); + break :blk null; + } + }; if (is_stage1) { const softfloat = b.addStaticLibrary("softfloat", null); @@ -689,31 +697,53 @@ const CMakeConfig = struct { const max_config_h_bytes = 1 * 1024 * 1024; -fn findAndParseConfigH(b: *Builder, config_h_path_option: ?[]const u8) ?CMakeConfig { - const config_h_text: []const u8 = if (config_h_path_option) |config_h_path| blk: { - break :blk fs.cwd().readFileAlloc(b.allocator, config_h_path, max_config_h_bytes) catch unreachable; - } else blk: { - // TODO this should stop looking for config.h once it detects we hit the - // zig source root directory. - var check_dir = fs.path.dirname(b.zig_exe).?; - while (true) { - var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable; - defer dir.close(); +fn findConfigH(b: *Builder, config_h_path_option: ?[]const u8) ?[]const u8 { + if (config_h_path_option) |path| { + var config_h_or_err = fs.cwd().openFile(path, .{}); + if (config_h_or_err) |*file| { + file.close(); + return path; + } else |_| { + std.log.err("Could not open provided config.h: \"{s}\"", .{path}); + std.os.exit(1); + } + } - break :blk dir.readFileAlloc(b.allocator, "config.h", max_config_h_bytes) catch |err| switch (err) { - error.FileNotFound => { - const new_check_dir = fs.path.dirname(check_dir); - if (new_check_dir == null or mem.eql(u8, new_check_dir.?, check_dir)) { - return null; - } - check_dir = new_check_dir.?; - continue; - }, - else => unreachable, - }; - } else unreachable; // TODO should not need `else unreachable`. - }; + var check_dir = fs.path.dirname(b.zig_exe).?; + while (true) { + var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable; + defer dir.close(); + // Check if config.h is present in dir + var config_h_or_err = dir.openFile("config.h", .{}); + if (config_h_or_err) |*file| { + file.close(); + return fs.path.join( + b.allocator, + &[_][]const u8{ check_dir, "config.h" }, + ) catch unreachable; + } else |e| switch (e) { + error.FileNotFound => {}, + else => unreachable, + } + + // Check if we reached the source root by looking for .git, and bail if so + var git_dir_or_err = dir.openDir(".git", .{}); + if (git_dir_or_err) |*git_dir| { + git_dir.close(); + return null; + } else |_| {} + + // Otherwise, continue search in the parent directory + const new_check_dir = fs.path.dirname(check_dir); + if (new_check_dir == null or mem.eql(u8, new_check_dir.?, check_dir)) { + return null; + } + check_dir = new_check_dir.?; + } else unreachable; // TODO should not need `else unreachable`. +} + +fn parseConfigH(b: *Builder, config_h_text: []const u8) ?CMakeConfig { var ctx: CMakeConfig = .{ .llvm_linkage = undefined, .cmake_binary_dir = undefined, From f43ea43ac920d3fbd629175e4e7fbe4309c6eab5 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 29 Jul 2022 10:30:10 +0300 Subject: [PATCH 19/29] stage2: fix hashing of struct values Closes #12279 --- src/value.zig | 18 +++--------------- test/behavior/tuple.zig | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/value.zig b/src/value.zig index 46624a822d..0d0be76542 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2292,25 +2292,13 @@ pub const Value = extern union { } }, .Struct => { - if (ty.isTupleOrAnonStruct()) { - const fields = ty.tupleFields(); - for (fields.values) |field_val, i| { - field_val.hash(fields.types[i], hasher, mod); - } - return; - } - const fields = ty.structFields().values(); - if (fields.len == 0) return; switch (val.tag()) { - .empty_struct_value => { - for (fields) |field| { - field.default_val.hash(field.ty, hasher, mod); - } - }, + .empty_struct_value => {}, .aggregate => { const field_values = val.castTag(.aggregate).?.data; for (field_values) |field_val, i| { - field_val.hash(fields[i].ty, hasher, mod); + const field_ty = ty.structFieldType(i); + field_val.hash(field_ty, hasher, mod); } }, else => unreachable, diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index 2442ae3629..4c43ef6be6 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -255,3 +255,23 @@ test "initializing anon struct with mixed comptime-runtime fields" { var a: T = .{ .foo = -1234, .bar = x + 1 }; _ = a; } + +test "tuple in tuple passed to generic function" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + const S = struct { + fn pair(x: f32, y: f32) std.meta.Tuple(&.{ f32, f32 }) { + return .{ x, y }; + } + + fn foo(x: anytype) !void { + try expect(x[0][0] == 1.5); + try expect(x[0][1] == 2.5); + } + }; + const x = comptime S.pair(1.5, 2.5); + try S.foo(.{x}); +} From 17622b9db14cb1d8dd600b21f60c8a1041e5b0e1 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 29 Jul 2022 11:55:53 +0300 Subject: [PATCH 20/29] Sema: implement `@Type` for functions Closes #12280 --- src/Sema.zig | 194 +++++++++++++++++- src/value.zig | 13 ++ .../reify_type.Fn_with_is_generic_true.zig | 4 +- ...th_is_var_args_true_and_non-C_callconv.zig | 4 +- .../reify_type.Fn_with_return_type_null.zig | 4 +- ...austive_enum_with_non-integer_tag_type.zig | 4 +- ...xhaustive_enum_with_undefined_tag_type.zig | 4 +- ...e_for_exhaustive_enum_with_zero_fields.zig | 4 +- ...for_tagged_union_with_extra_enum_field.zig | 6 +- ...or_tagged_union_with_extra_union_field.zig | 6 +- .../reify_type_for_union_with_zero_fields.zig | 4 +- .../reify_type_union_payload_is_undefined.zig | 4 +- .../obj => }/reify_type_with_Type.Int.zig | 6 +- .../obj => }/reify_type_with_undefined.zig | 6 +- ...ype.Pointer_with_invalid_address_space.zig | 0 ...eify_type_with_non-constant_expression.zig | 0 16 files changed, 226 insertions(+), 37 deletions(-) rename test/cases/compile_errors/{stage1/obj => }/reify_type.Fn_with_is_generic_true.zig (76%) rename test/cases/compile_errors/{stage1/obj => }/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig (74%) rename test/cases/compile_errors/{stage1/obj => }/reify_type.Fn_with_return_type_null.zig (75%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig (72%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig (73%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_for_exhaustive_enum_with_zero_fields.zig (77%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_for_tagged_union_with_extra_enum_field.zig (83%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_for_tagged_union_with_extra_union_field.zig (87%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_for_union_with_zero_fields.zig (75%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_union_payload_is_undefined.zig (51%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_with_Type.Int.zig (53%) rename test/cases/compile_errors/{stage1/obj => }/reify_type_with_undefined.zig (64%) rename test/cases/compile_errors/stage1/{obj => }/reify_type.Pointer_with_invalid_address_space.zig (100%) rename test/cases/compile_errors/stage1/{obj => }/reify_type_with_non-constant_expression.zig (100%) diff --git a/src/Sema.zig b/src/Sema.zig index 59b312e000..705fba9b92 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -15759,6 +15759,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const tag_ty = type_info_ty.unionTagType().?; const target = mod.getTarget(); const tag_index = tag_ty.enumTagFieldIndex(union_val.tag, mod).?; + if (union_val.val.anyUndef()) return sema.failWithUseOfUndef(block, src); switch (@intToEnum(std.builtin.TypeId, tag_index)) { .Type => return Air.Inst.Ref.type_type, .Void => return Air.Inst.Ref.void_type, @@ -15828,7 +15829,10 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const is_allowzero_val = struct_val[6]; const sentinel_val = struct_val[7]; - const abi_align = @intCast(u29, alignment_val.toUnsignedInt(target)); // TODO: Validate this value. + if (!try sema.intFitsInType(block, src, alignment_val, Type.u32, null)) { + return sema.fail(block, src, "alignment must fit in 'u32'", .{}); + } + const abi_align = @intCast(u29, alignment_val.toUnsignedInt(target)); var buffer: Value.ToTypeBuffer = undefined; const unresolved_elem_ty = child_val.toType(&buffer); @@ -15855,6 +15859,39 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I actual_sentinel = (try sema.pointerDeref(block, src, sentinel_ptr_val, ptr_ty)).?; } + if (elem_ty.zigTypeTag() == .NoReturn) { + return sema.fail(block, src, "pointer to noreturn not allowed", .{}); + } else if (elem_ty.zigTypeTag() == .Fn) { + if (ptr_size != .One) { + return sema.fail(block, src, "function pointers must be single pointers", .{}); + } + const fn_align = elem_ty.fnInfo().alignment; + if (abi_align != 0 and fn_align != 0 and + abi_align != fn_align) + { + return sema.fail(block, src, "function pointer alignment disagrees with function alignment", .{}); + } + } else if (ptr_size == .Many and elem_ty.zigTypeTag() == .Opaque) { + return sema.fail(block, src, "unknown-length pointer to opaque not allowed", .{}); + } else if (ptr_size == .C) { + if (!(try sema.validateExternType(elem_ty, .other))) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "C pointers cannot point to non-C-ABI-compatible type '{}'", .{elem_ty.fmt(sema.mod)}); + errdefer msg.destroy(sema.gpa); + + const src_decl = sema.mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), elem_ty, .other); + + try sema.addDeclaredHereNote(msg, elem_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + if (elem_ty.zigTypeTag() == .Opaque) { + return sema.fail(block, src, "C pointers cannot point to opaque types", .{}); + } + } + const ty = try Type.ptr(sema.arena, mod, .{ .size = ptr_size, .mutable = !is_const_val.toBool(), @@ -15915,6 +15952,10 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const error_set_ty = try error_set_val.toType(&buffer).copy(sema.arena); const payload_ty = try payload_val.toType(&buffer).copy(sema.arena); + if (error_set_ty.zigTypeTag() != .ErrorSet) { + return sema.fail(block, src, "Type.ErrorUnion.error_set must be an error set type", .{}); + } + const ty = try Type.Tag.error_union.create(sema.arena, .{ .error_set = error_set_ty, .payload = payload_ty, @@ -15928,7 +15969,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const decl_index = slice_val.ptr.pointerDecl().?; try sema.ensureDeclAnalyzed(decl_index); const decl = mod.declPtr(decl_index); - const array_val = decl.val.castTag(.aggregate).?.data; + const array_val: []Value = if (decl.val.castTag(.aggregate)) |some| some.data else &.{}; var names: Module.ErrorSet.NameMap = .{}; try names.ensureUnusedCapacity(sema.arena, array_val.len); @@ -15940,7 +15981,10 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const name_str = try name_val.toAllocatedBytes(Type.initTag(.const_slice_u8), sema.arena, sema.mod); const kv = try mod.getErrorValue(name_str); - names.putAssumeCapacityNoClobber(kv.key, {}); + const gop = names.getOrPutAssumeCapacity(kv.key); + if (gop.found_existing) { + return sema.fail(block, src, "duplicate error '{s}'", .{name_str}); + } } // names must be sorted @@ -16022,13 +16066,9 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I new_decl.owns_tv = true; errdefer mod.abortAnonDecl(new_decl_index); - // Enum tag type - var buffer: Value.ToTypeBuffer = undefined; - const int_tag_ty = try tag_type_val.toType(&buffer).copy(new_decl_arena_allocator); - enum_obj.* = .{ .owner_decl = new_decl_index, - .tag_ty = int_tag_ty, + .tag_ty = Type.@"null", .tag_ty_inferred = false, .fields = .{}, .values = .{}, @@ -16040,6 +16080,15 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I }, }; + // Enum tag type + var buffer: Value.ToTypeBuffer = undefined; + const int_tag_ty = try tag_type_val.toType(&buffer).copy(new_decl_arena_allocator); + + if (int_tag_ty.zigTypeTag() != .Int) { + return sema.fail(block, src, "Type.Enum.tag_type must be an integer type", .{}); + } + enum_obj.tag_ty = int_tag_ty; + // Fields const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); if (fields_len > 0) { @@ -16077,6 +16126,8 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .mod = mod, }); } + } else { + return sema.fail(block, src, "enums must have at least one field", .{}); } try new_decl.finalizeNewArena(&new_decl_arena); @@ -16186,11 +16237,17 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I }; // Tag type + var tag_ty_field_names: ?Module.EnumFull.NameMap = null; var enum_field_names: ?*Module.EnumNumbered.NameMap = null; const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); if (tag_type_val.optionalValue()) |payload_val| { var buffer: Value.ToTypeBuffer = undefined; union_obj.tag_ty = try payload_val.toType(&buffer).copy(new_decl_arena_allocator); + + if (union_obj.tag_ty.zigTypeTag() != .Enum) { + return sema.fail(block, src, "Type.Union.tag_type must be an enum type", .{}); + } + tag_ty_field_names = try union_obj.tag_ty.enumFields().clone(sema.arena); } else { union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len, null); enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; @@ -16222,6 +16279,19 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I set.putAssumeCapacity(field_name, {}); } + if (tag_ty_field_names) |*names| { + const enum_has_field = names.orderedRemove(field_name); + if (!enum_has_field) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "no field named '{s}' in enum '{}'", .{ field_name, union_obj.tag_ty.fmt(sema.mod) }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, union_obj.tag_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); if (gop.found_existing) { // TODO: better source location @@ -16234,12 +16304,108 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)), }; } + } else { + return sema.fail(block, src, "unions must have at least one field", .{}); + } + + if (tag_ty_field_names) |names| { + if (names.count() > 0) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "enum field(s) missing in union", .{}); + errdefer msg.destroy(sema.gpa); + + const enum_ty = union_obj.tag_ty; + for (names.keys()) |field_name| { + const field_index = enum_ty.enumFieldIndex(field_name).?; + try sema.addFieldErrNote(block, enum_ty, field_index, msg, "field '{s}' missing, declared here", .{field_name}); + } + try sema.addDeclaredHereNote(msg, union_obj.tag_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } } try new_decl.finalizeNewArena(&new_decl_arena); return sema.analyzeDeclVal(block, src, new_decl_index); }, - .Fn => return sema.fail(block, src, "TODO: Sema.zirReify for Fn", .{}), + .Fn => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // calling_convention: CallingConvention, + const cc = struct_val[0].toEnum(std.builtin.CallingConvention); + // alignment: comptime_int, + const alignment_val = struct_val[1]; + // is_generic: bool, + const is_generic = struct_val[2].toBool(); + // is_var_args: bool, + const is_var_args = struct_val[3].toBool(); + // return_type: ?type, + const return_type_val = struct_val[4]; + // args: []const Param, + const args_val = struct_val[5]; + + if (is_generic) { + return sema.fail(block, src, "Type.Fn.is_generic must be false for @Type", .{}); + } + + if (is_var_args and cc != .C) { + return sema.fail(block, src, "varargs functions must have C calling convention", .{}); + } + + const alignment = @intCast(u29, alignment_val.toUnsignedInt(target)); // TODO: Validate this value. + var buf: Value.ToTypeBuffer = undefined; + + const args: []Value = if (args_val.castTag(.aggregate)) |some| some.data else &.{}; + var param_types = try sema.arena.alloc(Type, args.len); + var comptime_params = try sema.arena.alloc(bool, args.len); + var noalias_bits: u32 = 0; + for (args) |arg, i| { + const arg_val = arg.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // is_generic: bool, + const arg_is_generic = arg_val[0].toBool(); + // is_noalias: bool, + const arg_is_noalias = arg_val[1].toBool(); + // arg_type: ?type, + const param_type_val = arg_val[2]; + + if (arg_is_generic) { + return sema.fail(block, src, "Type.Fn.Param.is_generic must be false for @Type", .{}); + } + + if (arg_is_noalias) { + noalias_bits = @as(u32, 1) << (std.math.cast(u5, i) orelse + return sema.fail(block, src, "this compiler implementation only supports 'noalias' on the first 32 parameters", .{})); + } + + const param_type = param_type_val.optionalValue() orelse + return sema.fail(block, src, "Type.Fn.Param.arg_type must be non-null for @Type", .{}); + + param_types[i] = try param_type.toType(&buf).copy(sema.arena); + } + + const return_type = return_type_val.optionalValue() orelse + return sema.fail(block, src, "Type.Fn.return_type must be non-null for @Type", .{}); + + var fn_info = Type.Payload.Function.Data{ + .param_types = param_types, + .comptime_params = comptime_params.ptr, + .noalias_bits = noalias_bits, + .return_type = try return_type.toType(&buf).copy(sema.arena), + .alignment = alignment, + .cc = cc, + .is_var_args = is_var_args, + .is_generic = false, + .align_is_generic = false, + .cc_is_generic = false, + .section_is_generic = false, + .addrspace_is_generic = false, + }; + + const ty = try Type.Tag.function.create(sema.arena, fn_info); + return sema.addType(ty); + }, .BoundFn => @panic("TODO delete BoundFn from the language"), .Frame => @panic("TODO implement https://github.com/ziglang/zig/issues/10710"), } @@ -16382,6 +16548,11 @@ fn reifyStruct( // alignment: comptime_int, const alignment_val = field_struct_val[4]; + if (!try sema.intFitsInType(block, src, alignment_val, Type.u32, null)) { + return sema.fail(block, src, "alignment must fit in 'u32'", .{}); + } + const abi_align = @intCast(u29, alignment_val.toUnsignedInt(target)); + const field_name = try name_val.toAllocatedBytes( Type.initTag(.const_slice_u8), new_decl_arena_allocator, @@ -16405,7 +16576,7 @@ fn reifyStruct( var buffer: Value.ToTypeBuffer = undefined; gop.value_ptr.* = .{ .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator), - .abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)), + .abi_align = abi_align, .default_val = default_val, .is_comptime = is_comptime_val.toBool(), .offset = undefined, @@ -27314,7 +27485,8 @@ fn enumFieldSrcLoc( .container_decl_arg_trailing, => tree.containerDeclArg(enum_node), - else => unreachable, + // Container was constructed with `@Type`. + else => return LazySrcLoc.nodeOffset(node_offset), }; var it_index: usize = 0; for (container_decl.ast.members) |member_node| { diff --git a/src/value.zig b/src/value.zig index 0d0be76542..3f3a2df4c3 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2766,6 +2766,19 @@ pub const Value = extern union { return self.isUndef(); } + /// Returns true if any value contained in `self` is undefined. + /// TODO: check for cases such as array that is not marked undef but all the element + /// values are marked undef, or struct that is not marked undef but all fields are marked + /// undef, etc. + pub fn anyUndef(self: Value) bool { + if (self.castTag(.aggregate)) |aggregate| { + for (aggregate.data) |val| { + if (val.anyUndef()) return true; + } + } + return self.isUndef(); + } + /// Asserts the value is not undefined and not unreachable. /// Integer value 0 is considered null because of C pointers. pub fn isNull(self: Value) bool { diff --git a/test/cases/compile_errors/stage1/obj/reify_type.Fn_with_is_generic_true.zig b/test/cases/compile_errors/reify_type.Fn_with_is_generic_true.zig similarity index 76% rename from test/cases/compile_errors/stage1/obj/reify_type.Fn_with_is_generic_true.zig rename to test/cases/compile_errors/reify_type.Fn_with_is_generic_true.zig index f2849b5eb4..cf80c9f4ba 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type.Fn_with_is_generic_true.zig +++ b/test/cases/compile_errors/reify_type.Fn_with_is_generic_true.zig @@ -11,7 +11,7 @@ const Foo = @Type(.{ comptime { _ = Foo; } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: Type.Fn.is_generic must be false for @Type +// :1:13: error: Type.Fn.is_generic must be false for @Type diff --git a/test/cases/compile_errors/stage1/obj/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig b/test/cases/compile_errors/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig similarity index 74% rename from test/cases/compile_errors/stage1/obj/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig rename to test/cases/compile_errors/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig index 4d449e9eb9..8328ee9b97 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig +++ b/test/cases/compile_errors/reify_type.Fn_with_is_var_args_true_and_non-C_callconv.zig @@ -11,7 +11,7 @@ const Foo = @Type(.{ comptime { _ = Foo; } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: varargs functions must have C calling convention +// :1:13: error: varargs functions must have C calling convention diff --git a/test/cases/compile_errors/stage1/obj/reify_type.Fn_with_return_type_null.zig b/test/cases/compile_errors/reify_type.Fn_with_return_type_null.zig similarity index 75% rename from test/cases/compile_errors/stage1/obj/reify_type.Fn_with_return_type_null.zig rename to test/cases/compile_errors/reify_type.Fn_with_return_type_null.zig index 98cbc37d41..f6587dcd7e 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type.Fn_with_return_type_null.zig +++ b/test/cases/compile_errors/reify_type.Fn_with_return_type_null.zig @@ -11,7 +11,7 @@ const Foo = @Type(.{ comptime { _ = Foo; } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: Type.Fn.return_type must be non-null for @Type +// :1:13: error: Type.Fn.return_type must be non-null for @Type diff --git a/test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig similarity index 72% rename from test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig rename to test/cases/compile_errors/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig index 56d05578be..e72b783d83 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig +++ b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_non-integer_tag_type.zig @@ -12,7 +12,7 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: Type.Enum.tag_type must be an integer type, not 'bool' +// :1:13: error: Type.Enum.tag_type must be an integer type diff --git a/test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig similarity index 73% rename from test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig rename to test/cases/compile_errors/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig index e6454d2ee5..1c237a17bd 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig +++ b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_undefined_tag_type.zig @@ -12,7 +12,7 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: use of undefined value here causes undefined behavior +// :1:13: error: use of undefined value here causes undefined behavior diff --git a/test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_zero_fields.zig b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig similarity index 77% rename from test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_zero_fields.zig rename to test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig index d3ce70c1b0..44876e938a 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_for_exhaustive_enum_with_zero_fields.zig +++ b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig @@ -12,7 +12,7 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: enums must have 1 or more fields +// :1:13: error: enums must have at least one field diff --git a/test/cases/compile_errors/stage1/obj/reify_type_for_tagged_union_with_extra_enum_field.zig b/test/cases/compile_errors/reify_type_for_tagged_union_with_extra_enum_field.zig similarity index 83% rename from test/cases/compile_errors/stage1/obj/reify_type_for_tagged_union_with_extra_enum_field.zig rename to test/cases/compile_errors/reify_type_for_tagged_union_with_extra_enum_field.zig index 0c56cb91ea..ccd0000494 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_for_tagged_union_with_extra_enum_field.zig +++ b/test/cases/compile_errors/reify_type_for_tagged_union_with_extra_enum_field.zig @@ -28,7 +28,9 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:14:23: error: enum field missing: 'arst' +// :14:16: error: enum field(s) missing in union +// :1:13: note: field 'arst' missing, declared here +// :1:13: note: enum declared here diff --git a/test/cases/compile_errors/stage1/obj/reify_type_for_tagged_union_with_extra_union_field.zig b/test/cases/compile_errors/reify_type_for_tagged_union_with_extra_union_field.zig similarity index 87% rename from test/cases/compile_errors/stage1/obj/reify_type_for_tagged_union_with_extra_union_field.zig rename to test/cases/compile_errors/reify_type_for_tagged_union_with_extra_union_field.zig index 63cf1f178e..414bf2ce5e 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_for_tagged_union_with_extra_union_field.zig +++ b/test/cases/compile_errors/reify_type_for_tagged_union_with_extra_union_field.zig @@ -28,8 +28,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:13:23: error: enum field not found: 'arst' -// tmp.zig:1:20: note: enum declared here +// :13:16: error: no field named 'arst' in enum 'tmp.Tag__enum_264' +// :1:13: note: enum declared here diff --git a/test/cases/compile_errors/stage1/obj/reify_type_for_union_with_zero_fields.zig b/test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig similarity index 75% rename from test/cases/compile_errors/stage1/obj/reify_type_for_union_with_zero_fields.zig rename to test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig index 578f902697..0b4f395c81 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_for_union_with_zero_fields.zig +++ b/test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig @@ -11,7 +11,7 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:25: error: unions must have 1 or more fields +// :1:18: error: unions must have at least one field diff --git a/test/cases/compile_errors/stage1/obj/reify_type_union_payload_is_undefined.zig b/test/cases/compile_errors/reify_type_union_payload_is_undefined.zig similarity index 51% rename from test/cases/compile_errors/stage1/obj/reify_type_union_payload_is_undefined.zig rename to test/cases/compile_errors/reify_type_union_payload_is_undefined.zig index 47be31c711..410bb92658 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_union_payload_is_undefined.zig +++ b/test/cases/compile_errors/reify_type_union_payload_is_undefined.zig @@ -4,7 +4,7 @@ const Foo = @Type(.{ comptime { _ = Foo; } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:1:20: error: use of undefined value here causes undefined behavior +// :1:13: error: use of undefined value here causes undefined behavior diff --git a/test/cases/compile_errors/stage1/obj/reify_type_with_Type.Int.zig b/test/cases/compile_errors/reify_type_with_Type.Int.zig similarity index 53% rename from test/cases/compile_errors/stage1/obj/reify_type_with_Type.Int.zig rename to test/cases/compile_errors/reify_type_with_Type.Int.zig index 116bd86e0f..bd98912a03 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_with_Type.Int.zig +++ b/test/cases/compile_errors/reify_type_with_Type.Int.zig @@ -7,7 +7,9 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:3:31: error: expected type 'std.builtin.Type', found 'std.builtin.Type.Int' +// :3:31: error: expected type 'builtin.Type', found 'builtin.Type.Int' +// :?:?: note: struct declared here +// :?:?: note: union declared here diff --git a/test/cases/compile_errors/stage1/obj/reify_type_with_undefined.zig b/test/cases/compile_errors/reify_type_with_undefined.zig similarity index 64% rename from test/cases/compile_errors/stage1/obj/reify_type_with_undefined.zig rename to test/cases/compile_errors/reify_type_with_undefined.zig index 1de93ccdf6..e5753fa420 100644 --- a/test/cases/compile_errors/stage1/obj/reify_type_with_undefined.zig +++ b/test/cases/compile_errors/reify_type_with_undefined.zig @@ -13,8 +13,8 @@ comptime { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:2:16: error: use of undefined value here causes undefined behavior -// tmp.zig:5:16: error: use of undefined value here causes undefined behavior +// :2:9: error: use of undefined value here causes undefined behavior +// :5:9: error: use of undefined value here causes undefined behavior diff --git a/test/cases/compile_errors/stage1/obj/reify_type.Pointer_with_invalid_address_space.zig b/test/cases/compile_errors/stage1/reify_type.Pointer_with_invalid_address_space.zig similarity index 100% rename from test/cases/compile_errors/stage1/obj/reify_type.Pointer_with_invalid_address_space.zig rename to test/cases/compile_errors/stage1/reify_type.Pointer_with_invalid_address_space.zig diff --git a/test/cases/compile_errors/stage1/obj/reify_type_with_non-constant_expression.zig b/test/cases/compile_errors/stage1/reify_type_with_non-constant_expression.zig similarity index 100% rename from test/cases/compile_errors/stage1/obj/reify_type_with_non-constant_expression.zig rename to test/cases/compile_errors/stage1/reify_type_with_non-constant_expression.zig From 4758752e5d64a3e36086483de569188f62519bac Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 29 Jul 2022 12:30:27 +0300 Subject: [PATCH 21/29] Sema: implement coercion from tuples to tuples Closes #12242 --- src/Sema.zig | 107 +++++++++++++++++- test/behavior/tuple.zig | 15 +++ ...type_mismatch_with_tuple_concatenation.zig | 11 -- ...type_mismatch_with_tuple_concatenation.zig | 10 ++ 4 files changed, 130 insertions(+), 13 deletions(-) delete mode 100644 test/cases/compile_errors/stage1/test/type_mismatch_with_tuple_concatenation.zig create mode 100644 test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig diff --git a/src/Sema.zig b/src/Sema.zig index 705fba9b92..31a0e41d95 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -24528,8 +24528,7 @@ fn coerceTupleToStruct( const struct_ty = try sema.resolveTypeFields(block, dest_ty_src, dest_ty); if (struct_ty.isTupleOrAnonStruct()) { - // NOTE remember to handle comptime fields - return sema.fail(block, dest_ty_src, "TODO: implement coercion from tuples to tuples", .{}); + return sema.coerceTupleToTuple(block, struct_ty, inst, inst_src); } const fields = struct_ty.structFields(); @@ -24612,6 +24611,110 @@ fn coerceTupleToStruct( ); } +fn coerceTupleToTuple( + sema: *Sema, + block: *Block, + tuple_ty: Type, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, +) !Air.Inst.Ref { + const field_count = tuple_ty.structFieldCount(); + const field_vals = try sema.arena.alloc(Value, field_count); + const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len); + mem.set(Air.Inst.Ref, field_refs, .none); + + const inst_ty = sema.typeOf(inst); + const tuple = inst_ty.tupleFields(); + var runtime_src: ?LazySrcLoc = null; + for (tuple.types) |_, i_usize| { + const i = @intCast(u32, i_usize); + const field_src = inst_src; // TODO better source location + const field_name = if (inst_ty.castTag(.anon_struct)) |payload| + payload.data.names[i] + else + try std.fmt.allocPrint(sema.arena, "{d}", .{i}); + + if (mem.eql(u8, field_name, "len")) { + return sema.fail(block, field_src, "cannot assign to 'len' field of tuple", .{}); + } + + const field_index = try sema.tupleFieldIndex(block, tuple_ty, field_name, field_src); + + const field_ty = tuple_ty.structFieldType(i); + const default_val = tuple_ty.structFieldDefaultValue(i); + const elem_ref = try tupleField(sema, block, inst_src, inst, field_src, i); + const coerced = try sema.coerce(block, field_ty, elem_ref, field_src); + field_refs[field_index] = coerced; + if (default_val.tag() != .unreachable_value) { + const init_val = (try sema.resolveMaybeUndefVal(block, field_src, coerced)) orelse { + return sema.failWithNeededComptime(block, field_src, "value stored in comptime field must be comptime known"); + }; + + if (!init_val.eql(default_val, field_ty, sema.mod)) { + return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, i); + } + } + if (runtime_src == null) { + if (try sema.resolveMaybeUndefVal(block, field_src, coerced)) |field_val| { + field_vals[field_index] = field_val; + } else { + runtime_src = field_src; + } + } + } + + // Populate default field values and report errors for missing fields. + var root_msg: ?*Module.ErrorMsg = null; + + for (field_refs) |*field_ref, i| { + if (field_ref.* != .none) continue; + + const default_val = tuple_ty.structFieldDefaultValue(i); + const field_ty = tuple_ty.structFieldType(i); + + const field_src = inst_src; // TODO better source location + if (default_val.tag() == .unreachable_value) { + if (tuple_ty.isTuple()) { + const template = "missing tuple field: {d}"; + if (root_msg) |msg| { + try sema.errNote(block, field_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, field_src, template, .{i}); + } + continue; + } + const template = "missing struct field: {s}"; + const args = .{tuple_ty.structFieldName(i)}; + if (root_msg) |msg| { + try sema.errNote(block, field_src, msg, template, args); + } else { + root_msg = try sema.errMsg(block, field_src, template, args); + } + continue; + } + if (runtime_src == null) { + field_vals[i] = default_val; + } else { + field_ref.* = try sema.addConstant(field_ty, default_val); + } + } + + if (root_msg) |msg| { + try sema.addDeclaredHereNote(msg, tuple_ty); + return sema.failWithOwnedErrorMsg(block, msg); + } + + if (runtime_src) |rs| { + try sema.requireRuntimeBlock(block, inst_src, rs); + return block.addAggregateInit(tuple_ty, field_refs); + } + + return sema.addConstant( + tuple_ty, + try Value.Tag.aggregate.create(sema.arena, field_vals), + ); +} + fn analyzeDeclVal( sema: *Sema, block: *Block, diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index 4c43ef6be6..14297bd61c 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -275,3 +275,18 @@ test "tuple in tuple passed to generic function" { const x = comptime S.pair(1.5, 2.5); try S.foo(.{x}); } + +test "coerce tuple to tuple" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + const T = std.meta.Tuple(&.{u8}); + const S = struct { + fn foo(x: T) !void { + try expect(x[0] == 123); + } + }; + try S.foo(.{123}); +} diff --git a/test/cases/compile_errors/stage1/test/type_mismatch_with_tuple_concatenation.zig b/test/cases/compile_errors/stage1/test/type_mismatch_with_tuple_concatenation.zig deleted file mode 100644 index 5983224676..0000000000 --- a/test/cases/compile_errors/stage1/test/type_mismatch_with_tuple_concatenation.zig +++ /dev/null @@ -1,11 +0,0 @@ -export fn entry() void { - var x = .{}; - x = x ++ .{ 1, 2, 3 }; -} - -// error -// backend=stage1 -// target=native -// is_test=1 -// -// tmp.zig:3:11: error: expected type 'struct:2:14', found 'struct:3:11' diff --git a/test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig b/test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig new file mode 100644 index 0000000000..4e9bdfa2e5 --- /dev/null +++ b/test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig @@ -0,0 +1,10 @@ +export fn entry() void { + var x = .{}; + x = x ++ .{ 1, 2, 3 }; +} + +// error +// backend=stage2 +// target=native +// +// :3:11: error: index '0' out of bounds of tuple '@TypeOf(.{})' From d26d696ee01d3a17d17cde24c8841e7f551ba5f2 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 28 Jul 2022 22:38:48 +0300 Subject: [PATCH 22/29] parser: require expression after colon in slice expr --- lib/std/zig/parse.zig | 2 +- lib/std/zig/parser_test.zig | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index f3c219cfc6..2a7d2623ef 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -3257,7 +3257,7 @@ const Parser = struct { if (p.eatToken(.ellipsis2)) |_| { const end_expr = try p.parseExpr(); if (p.eatToken(.colon)) |_| { - const sentinel = try p.parseExpr(); + const sentinel = try p.expectExpr(); _ = try p.expectToken(.r_bracket); return p.addNode(.{ .tag = .slice_sentinel, diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 38c2960f31..a74d53f21c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -5118,6 +5118,14 @@ test "zig fmt: while continue expr" { }); } +test "zig fmt: error for missing sentinel value in sentinel slice" { + try testError( + \\const foo = foo[0..:]; + , &[_]Error{ + .expected_expr, + }); +} + test "zig fmt: error for invalid bit range" { try testError( \\var x: []align(0:0:0)u8 = bar; From 02dc0732604236a57b43b9612d9b0571f06f905a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 28 Jul 2022 22:21:49 +0300 Subject: [PATCH 23/29] Sema: check comptime slice sentinel --- src/Sema.zig | 96 ++++++++++++++++--- ...atch_memory_at_target_index_terminated.zig | 23 +++-- ...ch_memory_at_target_index_unterminated.zig | 23 +++-- ...entinel_does_not_match_target-sentinel.zig | 23 +++-- ...e-sentinel_is_out_of_bounds_terminated.zig | 16 ++-- ...sentinel_is_out_of_bounds_unterminated.zig | 16 ++-- .../comptime_slice_of_an_undefined_slice.zig | 4 +- 7 files changed, 146 insertions(+), 55 deletions(-) rename test/cases/compile_errors/{stage1/obj => }/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig (68%) rename test/cases/compile_errors/{stage1/obj => }/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig (68%) rename test/cases/compile_errors/{stage1/obj => }/comptime_slice-sentinel_does_not_match_target-sentinel.zig (68%) rename test/cases/compile_errors/{stage1/obj => }/comptime_slice-sentinel_is_out_of_bounds_terminated.zig (72%) rename test/cases/compile_errors/{stage1/obj => }/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig (72%) rename test/cases/compile_errors/{stage1/obj => }/comptime_slice_of_an_undefined_slice.zig (63%) diff --git a/src/Sema.zig b/src/Sema.zig index 31a0e41d95..a0829d6eb7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -25150,7 +25150,10 @@ fn analyzeSlice( if (!end_is_len) { const end = try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| { - if (try sema.resolveDefinedValue(block, src, ptr_or_slice)) |slice_val| { + if (try sema.resolveMaybeUndefVal(block, src, ptr_or_slice)) |slice_val| { + if (slice_val.isUndef()) { + return sema.fail(block, src, "slice of undefined", .{}); + } const has_sentinel = slice_ty.sentinel() != null; var int_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, @@ -25213,8 +25216,8 @@ fn analyzeSlice( }; // requirement: start <= end - if (try sema.resolveDefinedValue(block, src, end)) |end_val| { - if (try sema.resolveDefinedValue(block, src, start)) |start_val| { + if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| { + if (try sema.resolveDefinedValue(block, start_src, start)) |start_val| { if (try sema.compare(block, src, start_val, .gt, end_val, Type.usize)) { return sema.fail( block, @@ -25226,6 +25229,45 @@ fn analyzeSlice( }, ); } + if (try sema.resolveMaybeUndefVal(block, ptr_src, new_ptr)) |ptr_val| sentinel_check: { + const expected_sentinel = sentinel orelse break :sentinel_check; + const start_int = start_val.getUnsignedInt(sema.mod.getTarget()).?; + const end_int = end_val.getUnsignedInt(sema.mod.getTarget()).?; + const sentinel_index = try sema.usizeCast(block, end_src, end_int - start_int); + + const elem_ptr = try ptr_val.elemPtr(sema.typeOf(new_ptr), sema.arena, sentinel_index, sema.mod); + const res = try sema.pointerDerefExtra(block, src, elem_ptr, elem_ty, false); + const actual_sentinel = switch (res) { + .runtime_load => break :sentinel_check, + .val => |v| v, + .needed_well_defined => |ty| return sema.fail( + block, + src, + "comptime dereference requires '{}' to have a well-defined layout, but it does not.", + .{ty.fmt(sema.mod)}, + ), + .out_of_bounds => |ty| return sema.fail( + block, + end_src, + "slice end index {d} exceeds bounds of containing decl of type '{}'", + .{ end_int, ty.fmt(sema.mod) }, + ), + }; + + if (!actual_sentinel.eql(expected_sentinel, elem_ty, sema.mod)) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "value in memory does not match slice sentinel", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "expected '{}', found '{}'", .{ + expected_sentinel.fmtValue(elem_ty, sema.mod), + actual_sentinel.fmtValue(elem_ty, sema.mod), + }); + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } } } @@ -27866,9 +27908,36 @@ pub fn analyzeAddrspace( /// Returns `null` if the pointer contents cannot be loaded at comptime. fn pointerDeref(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, ptr_ty: Type) CompileError!?Value { const load_ty = ptr_ty.childType(); + const res = try sema.pointerDerefExtra(block, src, ptr_val, load_ty, true); + switch (res) { + .runtime_load => return null, + .val => |v| return v, + .needed_well_defined => |ty| return sema.fail( + block, + src, + "comptime dereference requires '{}' to have a well-defined layout, but it does not.", + .{ty.fmt(sema.mod)}, + ), + .out_of_bounds => |ty| return sema.fail( + block, + src, + "dereference of '{}' exceeds bounds of containing decl of type '{}'", + .{ ptr_ty.fmt(sema.mod), ty.fmt(sema.mod) }, + ), + } +} + +const DerefResult = union(enum) { + runtime_load, + val: Value, + needed_well_defined: Type, + out_of_bounds: Type, +}; + +fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, load_ty: Type, want_mutable: bool) CompileError!DerefResult { const target = sema.mod.getTarget(); const deref = sema.beginComptimePtrLoad(block, src, ptr_val, load_ty) catch |err| switch (err) { - error.RuntimeLoad => return null, + error.RuntimeLoad => return DerefResult{ .runtime_load = {} }, else => |e| return e, }; @@ -27879,39 +27948,40 @@ fn pointerDeref(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, ptr if (coerce_in_mem_ok) { // We have a Value that lines up in virtual memory exactly with what we want to load, // and it is in-memory coercible to load_ty. It may be returned without modifications. - if (deref.is_mutable) { + if (deref.is_mutable and want_mutable) { // The decl whose value we are obtaining here may be overwritten with // a different value upon further semantic analysis, which would // invalidate this memory. So we must copy here. - return try tv.val.copy(sema.arena); + return DerefResult{ .val = try tv.val.copy(sema.arena) }; } - return tv.val; + return DerefResult{ .val = tv.val }; } } // The type is not in-memory coercible or the direct dereference failed, so it must // be bitcast according to the pointer type we are performing the load through. - if (!load_ty.hasWellDefinedLayout()) - return sema.fail(block, src, "comptime dereference requires '{}' to have a well-defined layout, but it does not.", .{load_ty.fmt(sema.mod)}); + if (!load_ty.hasWellDefinedLayout()) { + return DerefResult{ .needed_well_defined = load_ty }; + } const load_sz = try sema.typeAbiSize(block, src, load_ty); // Try the smaller bit-cast first, since that's more efficient than using the larger `parent` if (deref.pointee) |tv| if (load_sz <= try sema.typeAbiSize(block, src, tv.ty)) - return try sema.bitCastVal(block, src, tv.val, tv.ty, load_ty, 0); + return DerefResult{ .val = try sema.bitCastVal(block, src, tv.val, tv.ty, load_ty, 0) }; // If that fails, try to bit-cast from the largest parent value with a well-defined layout if (deref.parent) |parent| if (load_sz + parent.byte_offset <= try sema.typeAbiSize(block, src, parent.tv.ty)) - return try sema.bitCastVal(block, src, parent.tv.val, parent.tv.ty, load_ty, parent.byte_offset); + return DerefResult{ .val = try sema.bitCastVal(block, src, parent.tv.val, parent.tv.ty, load_ty, parent.byte_offset) }; if (deref.ty_without_well_defined_layout) |bad_ty| { // We got no parent for bit-casting, or the parent we got was too small. Either way, the problem // is that some type we encountered when de-referencing does not have a well-defined layout. - return sema.fail(block, src, "comptime dereference requires '{}' to have a well-defined layout, but it does not.", .{bad_ty.fmt(sema.mod)}); + return DerefResult{ .needed_well_defined = bad_ty }; } else { // If all encountered types had well-defined layouts, the parent is the root decl and it just // wasn't big enough for the load. - return sema.fail(block, src, "dereference of '{}' exceeds bounds of containing decl of type '{}'", .{ ptr_ty.fmt(sema.mod), deref.parent.?.tv.ty.fmt(sema.mod) }); + return DerefResult{ .out_of_bounds = deref.parent.?.tv.ty }; } } diff --git a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig b/test/cases/compile_errors/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig similarity index 68% rename from test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig rename to test/cases/compile_errors/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig index 598d23a305..ffa21af10a 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig +++ b/test/cases/compile_errors/comptime_slice-sentinel_does_not_match_memory_at_target_index_terminated.zig @@ -55,13 +55,20 @@ export fn foo_slice() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// :4:29: error: slice-sentinel does not match memory at target index -// :12:29: error: slice-sentinel does not match memory at target index -// :20:29: error: slice-sentinel does not match memory at target index -// :28:29: error: slice-sentinel does not match memory at target index -// :36:29: error: slice-sentinel does not match memory at target index -// :44:29: error: slice-sentinel does not match memory at target index -// :52:29: error: slice-sentinel does not match memory at target index +// :4:29: error: value in memory does not match slice sentinel +// :4:29: note: expected '0', found '100' +// :12:29: error: value in memory does not match slice sentinel +// :12:29: note: expected '0', found '100' +// :20:29: error: value in memory does not match slice sentinel +// :20:29: note: expected '0', found '100' +// :28:29: error: value in memory does not match slice sentinel +// :28:29: note: expected '0', found '100' +// :36:29: error: value in memory does not match slice sentinel +// :36:29: note: expected '0', found '100' +// :44:29: error: value in memory does not match slice sentinel +// :44:29: note: expected '0', found '100' +// :52:29: error: value in memory does not match slice sentinel +// :52:29: note: expected '0', found '100' diff --git a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig b/test/cases/compile_errors/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig similarity index 68% rename from test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig rename to test/cases/compile_errors/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig index d6b469aaf1..c5bb2d9643 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig +++ b/test/cases/compile_errors/comptime_slice-sentinel_does_not_match_memory_at_target_index_unterminated.zig @@ -55,13 +55,20 @@ export fn foo_slice() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// :4:29: error: slice-sentinel does not match memory at target index -// :12:29: error: slice-sentinel does not match memory at target index -// :20:29: error: slice-sentinel does not match memory at target index -// :28:29: error: slice-sentinel does not match memory at target index -// :36:29: error: slice-sentinel does not match memory at target index -// :44:29: error: slice-sentinel does not match memory at target index -// :52:29: error: slice-sentinel does not match memory at target index +// :4:29: error: value in memory does not match slice sentinel +// :4:29: note: expected '0', found '100' +// :12:29: error: value in memory does not match slice sentinel +// :12:29: note: expected '0', found '100' +// :20:29: error: value in memory does not match slice sentinel +// :20:29: note: expected '0', found '100' +// :28:29: error: value in memory does not match slice sentinel +// :28:29: note: expected '0', found '100' +// :36:29: error: value in memory does not match slice sentinel +// :36:29: note: expected '0', found '100' +// :44:29: error: value in memory does not match slice sentinel +// :44:29: note: expected '0', found '100' +// :52:29: error: value in memory does not match slice sentinel +// :52:29: note: expected '0', found '100' diff --git a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_target-sentinel.zig b/test/cases/compile_errors/comptime_slice-sentinel_does_not_match_target-sentinel.zig similarity index 68% rename from test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_target-sentinel.zig rename to test/cases/compile_errors/comptime_slice-sentinel_does_not_match_target-sentinel.zig index b204cfc684..b574df8833 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_does_not_match_target-sentinel.zig +++ b/test/cases/compile_errors/comptime_slice-sentinel_does_not_match_target-sentinel.zig @@ -55,13 +55,20 @@ export fn foo_slice() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// :4:29: error: slice-sentinel does not match target-sentinel -// :12:29: error: slice-sentinel does not match target-sentinel -// :20:29: error: slice-sentinel does not match target-sentinel -// :28:29: error: slice-sentinel does not match target-sentinel -// :36:29: error: slice-sentinel does not match target-sentinel -// :44:29: error: slice-sentinel does not match target-sentinel -// :52:29: error: slice-sentinel does not match target-sentinel +// :4:29: error: value in memory does not match slice sentinel +// :4:29: note: expected '255', found '0' +// :12:29: error: value in memory does not match slice sentinel +// :12:29: note: expected '255', found '0' +// :20:29: error: value in memory does not match slice sentinel +// :20:29: note: expected '255', found '0' +// :28:29: error: value in memory does not match slice sentinel +// :28:29: note: expected '255', found '0' +// :36:29: error: value in memory does not match slice sentinel +// :36:29: note: expected '255', found '0' +// :44:29: error: value in memory does not match slice sentinel +// :44:29: note: expected '255', found '0' +// :52:29: error: value in memory does not match slice sentinel +// :52:29: note: expected '255', found '0' diff --git a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_is_out_of_bounds_terminated.zig b/test/cases/compile_errors/comptime_slice-sentinel_is_out_of_bounds_terminated.zig similarity index 72% rename from test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_is_out_of_bounds_terminated.zig rename to test/cases/compile_errors/comptime_slice-sentinel_is_out_of_bounds_terminated.zig index 82c19126c0..86bd4ce8bb 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_is_out_of_bounds_terminated.zig +++ b/test/cases/compile_errors/comptime_slice-sentinel_is_out_of_bounds_terminated.zig @@ -55,13 +55,13 @@ export fn foo_slice() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// :4:29: error: out of bounds slice -// :12:29: error: out of bounds slice -// :20:29: error: out of bounds slice -// :28:29: error: out of bounds slice -// :36:29: error: out of bounds slice -// :44:29: error: out of bounds slice -// :52:29: error: out of bounds slice +// :4:33: error: slice end index 15 exceeds bounds of containing decl of type '[14:0]u8' +// :12:33: error: slice end index 15 exceeds bounds of containing decl of type '[14:0]u8' +// :20:33: error: slice end index 15 exceeds bounds of containing decl of type '[14:0]u8' +// :28:33: error: slice end index 15 exceeds bounds of containing decl of type '[14:0]u8' +// :36:33: error: slice end index 15 exceeds bounds of containing decl of type '[14:0]u8' +// :44:33: error: slice end index 15 exceeds bounds of containing decl of type '[14:0]u8' +// :52:33: error: end index 15 out of bounds for slice of length 14 diff --git a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig b/test/cases/compile_errors/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig similarity index 72% rename from test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig rename to test/cases/compile_errors/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig index 952b17600a..e1b8a5bc2d 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig +++ b/test/cases/compile_errors/comptime_slice-sentinel_is_out_of_bounds_unterminated.zig @@ -55,13 +55,13 @@ export fn foo_slice() void { } // error -// backend=stage1 +// backend=stage2 // target=native // -// :4:29: error: slice-sentinel is out of bounds -// :12:29: error: slice-sentinel is out of bounds -// :20:29: error: slice-sentinel is out of bounds -// :28:29: error: slice-sentinel is out of bounds -// :36:29: error: slice-sentinel is out of bounds -// :44:29: error: slice-sentinel is out of bounds -// :52:29: error: slice-sentinel is out of bounds +// :4:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' +// :12:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' +// :20:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' +// :28:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' +// :36:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' +// :44:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' +// :52:33: error: slice end index 14 exceeds bounds of containing decl of type '[14]u8' diff --git a/test/cases/compile_errors/stage1/obj/comptime_slice_of_an_undefined_slice.zig b/test/cases/compile_errors/comptime_slice_of_an_undefined_slice.zig similarity index 63% rename from test/cases/compile_errors/stage1/obj/comptime_slice_of_an_undefined_slice.zig rename to test/cases/compile_errors/comptime_slice_of_an_undefined_slice.zig index 4aa519f41e..d1b22d86b7 100644 --- a/test/cases/compile_errors/stage1/obj/comptime_slice_of_an_undefined_slice.zig +++ b/test/cases/compile_errors/comptime_slice_of_an_undefined_slice.zig @@ -5,7 +5,7 @@ comptime { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:3:14: error: slice of undefined +// :3:14: error: slice of undefined From 8632e4fc7b6cad33b951f71298ee6ac478e133cb Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 28 Jul 2022 21:04:17 -0700 Subject: [PATCH 24/29] translate-c: use correct number of initializers for vectors Fixes #12264 --- src/translate_c.zig | 30 +++++++++++++++++++------- src/translate_c/ast.zig | 8 +++++++ test/run_translated_c.zig | 44 +++++++++++++++++++-------------------- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index e53342990e..97e47d84f3 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2688,16 +2688,26 @@ fn transInitListExprVector( ) TransError!Node { _ = ty; const qt = getExprQualType(c, @ptrCast(*const clang.Expr, expr)); - const vector_type = try transQualType(c, scope, qt, loc); + const vector_ty = @ptrCast(*const clang.VectorType, qualTypeCanon(qt)); + const init_count = expr.getNumInits(); + const num_elements = vector_ty.getNumElements(); + const element_qt = vector_ty.getElementType(); if (init_count == 0) { - return Tag.container_init.create(c.arena, .{ - .lhs = vector_type, - .inits = try c.arena.alloc(ast.Payload.ContainerInit.Initializer, 0), + const zero_node = try Tag.as.create(c.arena, .{ + .lhs = try transQualType(c, scope, element_qt, loc), + .rhs = Tag.zero_literal.init(), + }); + + return Tag.vector_zero_init.create(c.arena, .{ + .lhs = try transCreateNodeNumber(c, num_elements, .int), + .rhs = zero_node, }); } + const vector_type = try transQualType(c, scope, qt, loc); + var block_scope = try Scope.Block.init(c, scope, true); defer block_scope.deinit(); @@ -2716,11 +2726,15 @@ fn transInitListExprVector( try block_scope.statements.append(tmp_decl_node); } - const init_list = try c.arena.alloc(Node, init_count); + const init_list = try c.arena.alloc(Node, num_elements); for (init_list) |*init, init_index| { - const tmp_decl = block_scope.statements.items[init_index]; - const name = tmp_decl.castTag(.var_simple).?.data.name; - init.* = try Tag.identifier.create(c.arena, name); + if (init_index < init_count) { + const tmp_decl = block_scope.statements.items[init_index]; + const name = tmp_decl.castTag(.var_simple).?.data.name; + init.* = try Tag.identifier.create(c.arena, name); + } else { + init.* = Tag.undefined_literal.init(); + } } const array_init = try Tag.array_init.create(c.arena, .{ diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index b88b481ae7..307a7e9ea7 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -154,6 +154,8 @@ pub const Node = extern union { div_exact, /// @offsetOf(lhs, rhs) offset_of, + /// @splat(lhs, rhs) + vector_zero_init, /// @shuffle(type, a, b, mask) shuffle, @@ -328,6 +330,7 @@ pub const Node = extern union { .div_exact, .offset_of, .helpers_cast, + .vector_zero_init, => Payload.BinOp, .integer_literal, @@ -1829,6 +1832,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { const type_expr = try renderNode(c, payload.cond); return renderArrayInit(c, type_expr, payload.cases); }, + .vector_zero_init => { + const payload = node.castTag(.vector_zero_init).?.data; + return renderBuiltinCall(c, "@splat", &.{ payload.lhs, payload.rhs }); + }, .field_access => { const payload = node.castTag(.field_access).?.data; const lhs = try renderNodeGrouped(c, payload.lhs); @@ -2305,6 +2312,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .@"struct", .@"union", .array_init, + .vector_zero_init, .tuple, .container_init, .container_init_dot, diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 871ec98fbc..4345625dc1 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -1322,28 +1322,28 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\} , ""); - if (@import("builtin").zig_backend == .stage1) { - // https://github.com/ziglang/zig/issues/12264 - cases.add("basic vector expressions", - \\#include - \\#include - \\typedef int16_t __v8hi __attribute__((__vector_size__(16))); - \\int main(int argc, char**argv) { - \\ __v8hi uninitialized; - \\ __v8hi empty_init = {}; - \\ __v8hi partial_init = {0, 1, 2, 3}; - \\ - \\ __v8hi a = {0, 1, 2, 3, 4, 5, 6, 7}; - \\ __v8hi b = (__v8hi) {100, 200, 300, 400, 500, 600, 700, 800}; - \\ - \\ __v8hi sum = a + b; - \\ for (int i = 0; i < 8; i++) { - \\ if (sum[i] != a[i] + b[i]) abort(); - \\ } - \\ return 0; - \\} - , ""); - } + cases.add("basic vector expressions", + \\#include + \\#include + \\typedef int16_t __v8hi __attribute__((__vector_size__(16))); + \\int main(int argc, char**argv) { + \\ __v8hi uninitialized; + \\ __v8hi empty_init = {}; + \\ for (int i = 0; i < 8; i++) { + \\ if (empty_init[i] != 0) abort(); + \\ } + \\ __v8hi partial_init = {0, 1, 2, 3}; + \\ + \\ __v8hi a = {0, 1, 2, 3, 4, 5, 6, 7}; + \\ __v8hi b = (__v8hi) {100, 200, 300, 400, 500, 600, 700, 800}; + \\ + \\ __v8hi sum = a + b; + \\ for (int i = 0; i < 8; i++) { + \\ if (sum[i] != a[i] + b[i]) abort(); + \\ } + \\ return 0; + \\} + , ""); cases.add("__builtin_shufflevector", \\#include From c0a1b4fa46dfd00d0cc4d1b6954bc07ae762e31e Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sat, 30 Jul 2022 06:34:49 -0700 Subject: [PATCH 25/29] stage2: Fix AIR printing Packed structs never have comptime fields, and a slice might actually be backed by a variable, which we need to catch before iterating its elements. --- src/TypedValue.zig | 14 ++++++++++---- src/value.zig | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/TypedValue.zig b/src/TypedValue.zig index 21d8629061..ba32e55f1e 100644 --- a/src/TypedValue.zig +++ b/src/TypedValue.zig @@ -73,6 +73,9 @@ pub fn print( const target = mod.getTarget(); var val = tv.val; var ty = tv.ty; + if (val.isVariable(mod)) + return writer.writeAll("(variable)"); + while (true) switch (val.tag()) { .u1_type => return writer.writeAll("u1"), .u8_type => return writer.writeAll("u8"), @@ -155,9 +158,12 @@ pub fn print( } try print(.{ .ty = ty.structFieldType(i), - .val = ty.structFieldValueComptime(i) orelse b: { - const vals = val.castTag(.aggregate).?.data; - break :b vals[i]; + .val = switch (ty.containerLayout()) { + .Packed => val.castTag(.aggregate).?.data[i], + else => ty.structFieldValueComptime(i) orelse b: { + const vals = val.castTag(.aggregate).?.data; + break :b vals[i]; + }, }, }, writer, level - 1, mod); } @@ -241,7 +247,7 @@ pub fn print( mod.declPtr(val.castTag(.function).?.data.owner_decl).name, }), .extern_fn => return writer.writeAll("(extern function)"), - .variable => return writer.writeAll("(variable)"), + .variable => unreachable, .decl_ref_mut => { const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index; const decl = mod.declPtr(decl_index); diff --git a/src/value.zig b/src/value.zig index 46624a822d..fd71aeabdc 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2664,6 +2664,26 @@ pub const Value = extern union { } } + /// Returns true if a Value is backed by a variable + pub fn isVariable( + val: Value, + mod: *Module, + ) bool { + return switch (val.tag()) { + .slice => val.castTag(.slice).?.data.ptr.isVariable(mod), + .comptime_field_ptr => val.castTag(.comptime_field_ptr).?.data.field_val.isVariable(mod), + .elem_ptr => val.castTag(.elem_ptr).?.data.array_ptr.isVariable(mod), + .field_ptr => val.castTag(.field_ptr).?.data.container_ptr.isVariable(mod), + .eu_payload_ptr => val.castTag(.eu_payload_ptr).?.data.container_ptr.isVariable(mod), + .opt_payload_ptr => val.castTag(.opt_payload_ptr).?.data.container_ptr.isVariable(mod), + .decl_ref => mod.declPtr(val.castTag(.decl_ref).?.data).val.isVariable(mod), + .decl_ref_mut => mod.declPtr(val.castTag(.decl_ref_mut).?.data.decl_index).val.isVariable(mod), + + .variable => true, + else => false, + }; + } + // Asserts that the provided start/end are in-bounds. pub fn sliceArray( val: Value, From b35490c21732d74232680d2de2deb89f97356c0d Mon Sep 17 00:00:00 2001 From: sin-ack Date: Sat, 30 Jul 2022 10:57:44 +0000 Subject: [PATCH 26/29] cmake: Print all LLVM config errors instead of just the last one If you have multiple llvm-config executables in your path, and all of them cause failures, then only the last failure will be printed. This can cause confusion when the multiple llvm-config executables are from different major LLVM versions, i.e. LLVM 13 and 14, which might mask an error that happened on the LLVM 14 llvm-config with an unrelated error. This commit makes it so that all errors are collected into a list and printed all at once; this way, you can see how each llvm-config executable failed to configure properly. Note that the failures still won't be printed if a successful configuration is found. --- cmake/Findllvm.cmake | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmake/Findllvm.cmake b/cmake/Findllvm.cmake index d62a154b84..d55abd2dc3 100644 --- a/cmake/Findllvm.cmake +++ b/cmake/Findllvm.cmake @@ -10,6 +10,7 @@ if(ZIG_USE_LLVM_CONFIG) + set(LLVM_CONFIG_ERROR_MESSAGES "") while(1) unset(LLVM_CONFIG_EXE CACHE) find_program(LLVM_CONFIG_EXE @@ -21,7 +22,8 @@ if(ZIG_USE_LLVM_CONFIG) "C:/Libraries/llvm-14.0.0/bin") if ("${LLVM_CONFIG_EXE}" STREQUAL "LLVM_CONFIG_EXE-NOTFOUND") - if (DEFINED LLVM_CONFIG_ERROR_MESSAGE) + if (NOT LLVM_CONFIG_ERROR_MESSAGES STREQUAL "") + list(JOIN LLVM_CONFIG_ERROR_MESSAGES "\n" LLVM_CONFIG_ERROR_MESSAGE) message(FATAL_ERROR ${LLVM_CONFIG_ERROR_MESSAGE}) else() message(FATAL_ERROR "unable to find llvm-config") @@ -37,7 +39,7 @@ if(ZIG_USE_LLVM_CONFIG) get_filename_component(LLVM_CONFIG_DIR "${LLVM_CONFIG_EXE}" DIRECTORY) if("${LLVM_CONFIG_VERSION}" VERSION_LESS 14 OR "${LLVM_CONFIG_VERSION}" VERSION_EQUAL 15 OR "${LLVM_CONFIG_VERSION}" VERSION_GREATER 15) # Save the error message, in case this is the last llvm-config we find - set(LLVM_CONFIG_ERROR_MESSAGE "expected LLVM 14.x but found ${LLVM_CONFIG_VERSION} using ${LLVM_CONFIG_EXE}") + list(APPEND LLVM_CONFIG_ERROR_MESSAGES "expected LLVM 14.x but found ${LLVM_CONFIG_VERSION} using ${LLVM_CONFIG_EXE}") # Ignore this directory and try the search again list(APPEND CMAKE_IGNORE_PATH "${LLVM_CONFIG_DIR}") @@ -61,9 +63,9 @@ if(ZIG_USE_LLVM_CONFIG) if (LLVM_CONFIG_ERROR) # Save the error message, in case this is the last llvm-config we find if (ZIG_SHARED_LLVM) - set(LLVM_CONFIG_ERROR_MESSAGE "LLVM 14.x found at ${LLVM_CONFIG_EXE} does not support linking as a shared library") + list(APPEND LLVM_CONFIG_ERROR_MESSAGES "LLVM 14.x found at ${LLVM_CONFIG_EXE} does not support linking as a shared library") else() - set(LLVM_CONFIG_ERROR_MESSAGE "LLVM 14.x found at ${LLVM_CONFIG_EXE} does not support linking as a static library") + list(APPEND LLVM_CONFIG_ERROR_MESSAGES "LLVM 14.x found at ${LLVM_CONFIG_EXE} does not support linking as a static library") endif() # Ignore this directory and try the search again @@ -81,7 +83,7 @@ if(ZIG_USE_LLVM_CONFIG) list (FIND LLVM_TARGETS_BUILT "${TARGET_NAME}" _index) if (${_index} EQUAL -1) # Save the error message, in case this is the last llvm-config we find - set(LLVM_CONFIG_ERROR_MESSAGE "LLVM (according to ${LLVM_CONFIG_EXE}) is missing target ${TARGET_NAME}. Zig requires LLVM to be built with all default targets enabled.") + list(APPEND LLVM_CONFIG_ERROR_MESSAGES "LLVM (according to ${LLVM_CONFIG_EXE}) is missing target ${TARGET_NAME}. Zig requires LLVM to be built with all default targets enabled.") # Ignore this directory and try the search again list(APPEND CMAKE_IGNORE_PATH "${LLVM_CONFIG_DIR}") From 075f93fa108030fb0dd12faa6e389ace302cfb4c Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Thu, 28 Jul 2022 17:06:57 -0700 Subject: [PATCH 27/29] stage2 LLVM: Pass inline assembly outputs directly when not targeting memory This change provides a basic implementation of #2349 for stage2. There's still quite a lot of work before this logic is as complete as what's in Clang (https://github.com/llvm/llvm-project/blob/b3645353041818f61e2580635409ddb81ff5a272/clang/lib/CodeGen/CGStmt.cpp#L2304-L2795), particularly considering the diversity of constraints across targets. It's probably not worth doing the complete work until there's a clearer picture for constraints in Zig's future dedicated ASM syntax, but at least this gives us a small improvement for now. As a bonus, this also fixes a bug with how we were handling `_` identifiers. --- src/codegen/llvm.zig | 141 +++++++++++++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6a2e346113..664edb0304 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5491,22 +5491,26 @@ pub const FuncGen = struct { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const return_count: u8 = for (outputs) |output| { - if (output == .none) break 1; - } else 0; - const llvm_params_len = inputs.len + outputs.len - return_count; - const llvm_param_types = try arena.alloc(*const llvm.Type, llvm_params_len); - const llvm_param_values = try arena.alloc(*const llvm.Value, llvm_params_len); - const llvm_param_attrs = try arena.alloc(bool, llvm_params_len); + // The exact number of return / parameter values depends on which output values + // are passed by reference as indirect outputs (determined below). + const max_return_count = outputs.len; + const llvm_ret_types = try arena.alloc(*const llvm.Type, max_return_count); + const llvm_ret_indirect = try arena.alloc(bool, max_return_count); + + const max_param_count = inputs.len + outputs.len; + const llvm_param_types = try arena.alloc(*const llvm.Type, max_param_count); + const llvm_param_values = try arena.alloc(*const llvm.Value, max_param_count); + const llvm_param_attrs = try arena.alloc(bool, max_param_count); const target = self.dg.module.getTarget(); + var llvm_ret_i: usize = 0; var llvm_param_i: usize = 0; - var total_i: usize = 0; + var total_i: u16 = 0; - var name_map: std.StringArrayHashMapUnmanaged(void) = .{}; - try name_map.ensureUnusedCapacity(arena, outputs.len + inputs.len); + var name_map: std.StringArrayHashMapUnmanaged(u16) = .{}; + try name_map.ensureUnusedCapacity(arena, max_param_count); - for (outputs) |output| { + for (outputs) |output, i| { const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); @@ -5519,15 +5523,30 @@ pub const FuncGen = struct { llvm_constraints.appendAssumeCapacity(','); } llvm_constraints.appendAssumeCapacity('='); + + // Pass any non-return outputs indirectly, if the constraint accepts a memory location + llvm_ret_indirect[i] = (output != .none) and constraintAllowsMemory(constraint); if (output != .none) { try llvm_constraints.ensureUnusedCapacity(self.gpa, llvm_constraints.capacity + 1); - llvm_constraints.appendAssumeCapacity('*'); - const output_inst = try self.resolveInst(output); - llvm_param_values[llvm_param_i] = output_inst; - llvm_param_types[llvm_param_i] = output_inst.typeOf(); - llvm_param_attrs[llvm_param_i] = true; - llvm_param_i += 1; + + if (llvm_ret_indirect[i]) { + // Pass the result by reference as an indirect output (e.g. "=*m") + llvm_constraints.appendAssumeCapacity('*'); + + llvm_param_values[llvm_param_i] = output_inst; + llvm_param_types[llvm_param_i] = output_inst.typeOf(); + llvm_param_attrs[llvm_param_i] = true; + llvm_param_i += 1; + } else { + // Pass the result directly (e.g. "=r") + llvm_ret_types[llvm_ret_i] = output_inst.typeOf().getElementType(); + llvm_ret_i += 1; + } + } else { + const ret_ty = self.air.typeOfIndex(inst); + llvm_ret_types[llvm_ret_i] = try self.dg.lowerType(ret_ty); + llvm_ret_i += 1; } // LLVM uses commas internally to separate different constraints, @@ -5536,13 +5555,16 @@ pub const FuncGen = struct { // to GCC's inline assembly. // http://llvm.org/docs/LangRef.html#constraint-codes for (constraint[1..]) |byte| { - llvm_constraints.appendAssumeCapacity(switch (byte) { - ',' => '|', - else => byte, - }); + switch (byte) { + ',' => llvm_constraints.appendAssumeCapacity('|'), + '*' => {}, // Indirect outputs are handled above + else => llvm_constraints.appendAssumeCapacity(byte), + } } - name_map.putAssumeCapacityNoClobber(name, {}); + if (!std.mem.eql(u8, name, "_")) { + name_map.putAssumeCapacityNoClobber(name, total_i); + } total_i += 1; } @@ -5594,7 +5616,7 @@ pub const FuncGen = struct { } if (!std.mem.eql(u8, name, "_")) { - name_map.putAssumeCapacityNoClobber(name, {}); + name_map.putAssumeCapacityNoClobber(name, total_i); } // In the case of indirect inputs, LLVM requires the callsite to have @@ -5625,6 +5647,11 @@ pub const FuncGen = struct { } } + // We have finished scanning through all inputs/outputs, so the number of + // parameters and return values is known. + const param_count = llvm_param_i; + const return_count = llvm_ret_i; + // For some targets, Clang unconditionally adds some clobbers to all inline assembly. // While this is probably not strictly necessary, if we don't follow Clang's lead // here then we may risk tripping LLVM bugs since anything not used by Clang tends @@ -5682,7 +5709,7 @@ pub const FuncGen = struct { const name = asm_source[name_start..i]; state = .start; - const index = name_map.getIndex(name) orelse { + const index = name_map.get(name) orelse { // we should validate the assembly in Sema; by now it is too late return self.todo("unknown input or output name: '{s}'", .{name}); }; @@ -5693,12 +5720,20 @@ pub const FuncGen = struct { } } - const ret_ty = self.air.typeOfIndex(inst); - const ret_llvm_ty = try self.dg.lowerType(ret_ty); + const ret_llvm_ty = switch (return_count) { + 0 => self.context.voidType(), + 1 => llvm_ret_types[0], + else => self.context.structType( + llvm_ret_types.ptr, + @intCast(c_uint, return_count), + .False, + ), + }; + const llvm_fn_ty = llvm.functionType( ret_llvm_ty, llvm_param_types.ptr, - @intCast(c_uint, llvm_param_types.len), + @intCast(c_uint, param_count), .False, ); const asm_fn = llvm.getInlineAsm( @@ -5715,18 +5750,40 @@ pub const FuncGen = struct { const call = self.builder.buildCall( asm_fn, llvm_param_values.ptr, - @intCast(c_uint, llvm_param_values.len), + @intCast(c_uint, param_count), .C, .Auto, "", ); - for (llvm_param_attrs) |need_elem_ty, i| { + for (llvm_param_attrs[0..param_count]) |need_elem_ty, i| { if (need_elem_ty) { const elem_ty = llvm_param_types[i].getElementType(); llvm.setCallElemTypeAttr(call, i, elem_ty); } } - return call; + + var ret_val = call; + llvm_ret_i = 0; + for (outputs) |output, i| { + if (llvm_ret_indirect[i]) continue; + + const output_value = if (return_count > 1) b: { + break :b self.builder.buildExtractValue(call, @intCast(c_uint, llvm_ret_i), ""); + } else call; + + if (output != .none) { + const output_ptr = try self.resolveInst(output); + const output_ptr_ty = self.air.typeOf(output); + + const store_inst = self.builder.buildStore(output_value, output_ptr); + store_inst.setAlignment(output_ptr_ty.ptrAlignment(target)); + } else { + ret_val = output_value; + } + llvm_ret_i += 1; + } + + return ret_val; } fn airIsNonNull( @@ -9709,10 +9766,30 @@ fn errUnionErrorOffset(payload_ty: Type, target: std.Target) u1 { return @boolToInt(Type.anyerror.abiAlignment(target) <= payload_ty.abiAlignment(target)); } +/// Returns true for asm constraint (e.g. "=*m", "=r") if it accepts a memory location +/// +/// See also TargetInfo::validateOutputConstraint, AArch64TargetInfo::validateAsmConstraint, etc. in Clang fn constraintAllowsMemory(constraint: []const u8) bool { - return constraint[0] == 'm'; + // TODO: This implementation is woefully incomplete. + for (constraint) |byte| { + switch (byte) { + '=', '*', ',', '&' => {}, + 'm', 'o', 'X', 'g' => return true, + else => {}, + } + } else return false; } +/// Returns true for asm constraint (e.g. "=*m", "=r") if it accepts a register +/// +/// See also TargetInfo::validateOutputConstraint, AArch64TargetInfo::validateAsmConstraint, etc. in Clang fn constraintAllowsRegister(constraint: []const u8) bool { - return constraint[0] != 'm'; + // TODO: This implementation is woefully incomplete. + for (constraint) |byte| { + switch (byte) { + '=', '*', ',', '&' => {}, + 'm', 'o' => {}, + else => return true, + } + } else return false; } From 1a1b7a3afdd3edac9e8e351ce7e21c91404f8307 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 31 Jul 2022 02:41:32 -0700 Subject: [PATCH 28/29] Linux: Add IN_MASK_CREATE and corresponding error handling in inotify_add_watch From https://man7.org/linux/man-pages/man7/inotify.7.html > **IN_MASK_CREATE** (since Linux 4.18) > > Watch pathname only if it does not already have a watch associated with it; the error EEXIST results if pathname is already being watched. --- lib/std/os.zig | 2 ++ lib/std/os/linux.zig | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/std/os.zig b/lib/std/os.zig index 5683c5300a..1192c72629 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4244,6 +4244,7 @@ pub const INotifyAddWatchError = error{ SystemResources, UserResourceLimitReached, NotDir, + WatchAlreadyExists, } || UnexpectedError; /// add a watch to an initialized inotify instance @@ -4266,6 +4267,7 @@ pub fn inotify_add_watchZ(inotify_fd: i32, pathname: [*:0]const u8, mask: u32) I .NOMEM => return error.SystemResources, .NOSPC => return error.UserResourceLimitReached, .NOTDIR => return error.NotDir, + .EXIST => return error.WatchAlreadyExists, else => |err| return unexpectedErrno(err), } } diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index aa127db8ed..ae9b441b60 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -2976,6 +2976,7 @@ pub const IN = struct { pub const ONLYDIR = 0x01000000; pub const DONT_FOLLOW = 0x02000000; pub const EXCL_UNLINK = 0x04000000; + pub const MASK_CREATE = 0x10000000; pub const MASK_ADD = 0x20000000; pub const ISDIR = 0x40000000; From ff125db53d8c18a63872ebdcdf6dd9653eb3f56b Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sun, 31 Jul 2022 03:43:02 +0900 Subject: [PATCH 29/29] wasm: fix typo in CodeGen.zig occured -> occurred --- src/arch/wasm/CodeGen.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index fd9e13a220..91072d0b4c 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -603,7 +603,7 @@ stack_alignment: u32 = 16, const InnerError = error{ OutOfMemory, - /// An error occured when trying to lower AIR to MIR. + /// An error occurred when trying to lower AIR to MIR. CodegenFail, /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed AnalysisFail, @@ -4410,7 +4410,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue { } // We store the bit if it's overflowed or not in this. As it's zero-initialized - // we only need to update it if an overflow (or underflow) occured. + // we only need to update it if an overflow (or underflow) occurred. const overflow_bit = try self.allocLocal(Type.initTag(.u1)); const int_info = lhs_ty.intInfo(self.target); const wasm_bits = toWasmBits(int_info.bits) orelse {