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 8bd589e7ba0beae62f88e3863cc7da06e52a2d34..93d98c41d357dce533442884c7d85b898503a885 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 3a49c03b1811ae0e14095afe0d1c6494a515d5b2..4e86bcadeb65cf9b5892fb456536100a8c9a15eb 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) { -- 2.54.0 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 869cd43f0f0cade876c9a4bdfaae7bc84e564391..17ffe356a37a0a242100804a81d73e6d17982d88 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 4576538a35e75d0f535a94173daffde9f7f67d97..397134d9116f51dd363dd0c972feea044c3218fa 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 ad8beff0f352f89ef286d99674a995071b03b20f..ac9e24a9be60e091a98c5c41545e566d550e285a 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)}); -- 2.54.0 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 bd86cd2bb938399ef644e6d496940b333bc23336..f2291a06460421ddbf7892e88110cb2548b64132 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")) { -- 2.54.0 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 ef90ddebce938d165b090c7a1e9d0c7b3aee65e4..40dacc5df8d3881574c33abbb7a4c005945c30f2 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 bd86cd2bb938399ef644e6d496940b333bc23336..87349df38adfbc83e439f236abf65341ebd0d3c6 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 1c0ff7bf097c8caceb8df0678a334b2601f03e09..7b79f8e985a2cdfab16dce5d7a8068a05f12cf11 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 6babb21bfe28af5078eb56bc7618faaff66a86be..3da57d4301cb3c1728d4c5f34e05ce3ce48a7a6f 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 -- 2.54.0 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 3e7d1888cdc6c02c2c64986fe2d191df1ffdfb5e..7660a638e51c7a3b6c7bdf990667d32ced3e2a78 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++"); } -- 2.54.0 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 ffbf12dbc00350d5f7cb053f95e5f6641f2263e0..96d10f1e8354f2bdee137fbcdf4b67ff43c16cd2 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 f026b0da211f53a0db228394c749e9210f93b4d0..d15eae7dc5fc7e58a1d7bc394c193a956dd9f1f5 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 3757b56743aeba969ad51c17688c7c3463b4849f..c43799d280a754f4f2d9ae50d8f599ff74f333ca 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", }, -- 2.54.0 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 528ef930e62da54010692c01dcee4ec8eea7d6f7..0078057eefc2a1574c079971ac6afcad592cd863 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 397134d9116f51dd363dd0c972feea044c3218fa..deff4620b9d6244bd0a86175344e9001e490276e 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 ac9e24a9be60e091a98c5c41545e566d550e285a..5a70679b8d00630c10b6cbd9714748b1eb1bf8c4 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 6e9b133310884eec8927ed4673f2b89933505136..4540032605d750c296fcb50a589d15c80f099e05 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 de51c271c4d5695035c00b49a1df1de606ac7fc3..7723446f1c3d5171ea04b083d6f79d299c916868 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 7eefeb80b4a1f31c3855d85811752a4e970ee104..9e62420f1f3329118ca97597b4f0ee36eb26223b 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 9ace5ddceb6513056031aa183fdcff5070c31245..b2a7312c529301e7f6371d8350f8b9683defecbb 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 554ba3c43e5ab55e5855acaf1a24269d87b85d77..194274a1edf82f3a7a8edea31e242f2a1437a6ba 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 32c71e5c7751725196a0e809f43e369ff24dec41..965454ef03f0bf51b2e5cc8dea46486675aa612b 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 0000000000000000000000000000000000000000..56b65c1ab7c3c7868873b97dbbf513aa9c0cf95e --- /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 d145897b4167ff3892fcd35e0e87100759bbdcc2..391ecbdf1a2bc48d09427ec4ca905180a8c07cc8 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 8e57854728208344098e268d501647199e51106c..54d62e6d371cf1ff3602b6d9e4f3a4e6d61fe28c 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 818455c3540bb7ae27e552703411a5ce023540d2..0eef1c33745ec04619b57f79ecb0f15bd42c70f1 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 ed22cc2cacc6573104293ab240be3e39413303ed..e6753a5911e34415c90f42589bfcc9c19ae355eb 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 -- 2.54.0 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 0078057eefc2a1574c079971ac6afcad592cd863..b6a7450f3a382c6ac032bfd1a044390484cc6812 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 deff4620b9d6244bd0a86175344e9001e490276e..4ac2775515901b7a3977d9c17ea25ff3012ffab8 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 5a70679b8d00630c10b6cbd9714748b1eb1bf8c4..74c8f0b48dbb245c533667426bf3768f01f2f41f 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 4540032605d750c296fcb50a589d15c80f099e05..ccd677df0b19a0619b10ebbec67bd799b3103cc7 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 7723446f1c3d5171ea04b083d6f79d299c916868..6e33154bbd8e6215051d52af043ce3ca32c9a3c6 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 0000000000000000000000000000000000000000..32b52cdc1f0f665064c155961eb72ba006325c34 --- /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 bb8b3c215c7b703d9996c249ca3bdec254745bb2..a38d8d4d8587f26fd4336f71d7ee2d297f2b5460 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 -- 2.54.0 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 b406fed4711e4a6c5f70243062eabcb5b2a080c1..7d0dcd35d08c68b6de1be59639e369ed31eef2a0 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(); } -- 2.54.0 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 74c8f0b48dbb245c533667426bf3768f01f2f41f..a9ad7d0a8a2d8c791a9bad6613011b2976dab2f6 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, -- 2.54.0 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 2c0c38a2efab3a47dce773641b65e51be53bcea5..302822fc9917339cbf65d56832c23eaf756a792a 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 a9ad7d0a8a2d8c791a9bad6613011b2976dab2f6..5b5d51576c36648a6ce35dd9fd6e0dd13a980e1d 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); } -- 2.54.0 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 9e05a370f9fcdaba47458fcedb7f6230d5050237..ad481a61584e4558aafae9273f1b8bc2d9c68576 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 -- 2.54.0 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 23b733c4934ba1dc65ea50ecac37ef447142104a..0000000000000000000000000000000000000000 --- 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 994b67b9d0bc4c8da0c1c344503885c2a8a0dd16..0000000000000000000000000000000000000000 --- 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 -// -- 2.54.0 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 3e9b1ab3edbccd3d61397381d06c78c7c30c933f..917e4c18d1171b6840e5c6793e239c2228f34c58 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| { -- 2.54.0 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 5b5d51576c36648a6ce35dd9fd6e0dd13a980e1d..59b312e0006ab34906a5e60fa2c6fca90d8c17d0 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 6750ec724b829dd41e36ba9e217b80428922cd9d..3b1b0dd59d6c312f69e7684bdbdf425a02e6faf7 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; -- 2.54.0 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 4c3b3842bf2d5c84305a3f503049c845a538c72c..3a54e82c38a518cb42868a11950c6eb5182b8c01 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 -- 2.54.0 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 8ac8ce6de1beb2f6c0ab58a6248a623682c6ebeb..065f951e91f629d01391c23758f116ebf62ccb3c 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); -- 2.54.0 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 3e7d1888cdc6c02c2c64986fe2d191df1ffdfb5e..cbc530e877409f8df46ca50ebe5c2b6c82692029 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, -- 2.54.0 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 46624a822d22120c88c6aed5edff27dc2c2fe4bf..0d0be76542e4679e95342ffa41d37f384c487286 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 2442ae36293aa159c81a1ebd3547e1a525b6e245..4c43ef6be682ba1d8117acc3fdc8746083a5ca36 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}); +} -- 2.54.0 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 59b312e0006ab34906a5e60fa2c6fca90d8c17d0..705fba9b92e735c8bff95c8db782364b2f2abcfb 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 0d0be76542e4679e95342ffa41d37f384c487286..3f3a2df4c31250516b9c11523b663243ba9680ab 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 f2849b5eb486fc09843e554ceaf891dbd47e4f04..cf80c9f4badf4ff05b5ba0150ae1d3a97597e761 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 4d449e9eb9e78b92c65f361b19640c805d3dbdc0..8328ee9b97f6e18a5523705f322551a0e60adbe2 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 98cbc37d41d44d86bd7e571fb7cab8fee07cbca2..f6587dcd7e92447ba77d35c37d974b0fa4910309 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 56d05578be1173d6504127707635c88fc9be0982..e72b783d83e515d41592a240007b9c42d0214810 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 e6454d2ee5a8e71acca500edc3af9278d18db9f6..1c237a17bd0bf78694fda13b654df8da6c5316a5 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 d3ce70c1b0ab286279f79727d6db6cf41a2e929f..44876e938af0eff4aef0836ba910bb5d3082a3c3 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 0c56cb91ea3f20c2a2febf2e06583a097b2456ae..ccd00004942cd995f9ad1eb19a1e9de5f6d6c24d 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 63cf1f178e26db73e4563c161b8ecfdd7655f164..414bf2ce5e6206b65e001fcc1f906a951fd9eb2f 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 578f902697597d364a9fdd242afd66890d004ec5..0b4f395c8182c9977643adb2f7d3761e72ad39bf 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 47be31c71142271c9022c53aeab4a3e1c8e523b8..410bb92658115c592cee8fc7eb39e487525ead48 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 116bd86e0f229e17e8bf4a8a68c66dbeec1b81ba..bd98912a03112143269355c377114df1b1f3b66b 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 1de93ccdf62d041945319c5a8626af4eea39c2f2..e5753fa420989c0c122234def7230f155f00bf17 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 -- 2.54.0 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 705fba9b92e735c8bff95c8db782364b2f2abcfb..31a0e41d95a9dcd6c9e935ad15895667bf963f7c 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 4c43ef6be682ba1d8117acc3fdc8746083a5ca36..14297bd61c68cf2a23bd6eec2ec56966456c4a64 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 5983224676fd805903614b5e78e0252175ff7382..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..4e9bdfa2e56092cd86b9ba758b938d9a7cb0a365 --- /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(.{})' -- 2.54.0 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 f3c219cfc6b6bc1f0996224e28e31deb488a0484..2a7d2623efb28514ab88a69693be88cda5db5c7d 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 38c2960f3143f129f8b82205dcc0adb736179be9..a74d53f21c0d444c862c54785dfa3879b6a366cc 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; -- 2.54.0 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 31a0e41d95a9dcd6c9e935ad15895667bf963f7c..a0829d6eb7eae7ef784521c1eb98a2ec503e0be5 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 598d23a305ba1c9e28f9f1e8fd2379ebbc5a3863..ffa21af10aea16b214fd453d128f464d36caa3fb 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 d6b469aaf164d55a85c886799aff7cb033866391..c5bb2d9643c4620d15891f728513325a5489f32e 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 b204cfc6840d6bc557585665348883f063e63730..b574df88334d07e88e2deb2aad0040fd0b6b83e0 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 82c19126c0bc97729cde29149e6fa79e0c099e44..86bd4ce8bb7a0d37953045529a5d1cc68d1bdb97 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 952b17600a80e7b4503152802cb5a930ddc05634..e1b8a5bc2debeb455342436c92ab7bdda2fa10ef 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 4aa519f41ee031d2f7a103ab5505cfde1a887599..d1b22d86b7f1f08db6257c4d98138e518bd2b389 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 -- 2.54.0 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 e53342990e0311564cbdca09ca7e73c6c0c6f86f..97e47d84f372fb598a49ef0b68ae66195117e56f 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 b88b481ae76ab598bd75928eb25acddccaf111ea..307a7e9ea7b28ec1b58009de054e461fcf02fa3d 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 871ec98fbce54362dddaec15aca6ff6d7ffbcb30..4345625dc1f2d4f89f710ae530db9e6df4547532 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 -- 2.54.0 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 21d8629061972c0d0078c0962ff29794c5346bb8..ba32e55f1e2920fa7abc92c35312482775670492 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 46624a822d22120c88c6aed5edff27dc2c2fe4bf..fd71aeabdc70aa9a8ce6014b76de4e780f8faf6a 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, -- 2.54.0 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 d62a154b842323259c56ea3a62ff1a0acf8e0340..d55abd2dc3c3cdc66b4ea9361f95c4360df44d8a 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}") -- 2.54.0 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 6a2e346113f9ef77dd6bb637ca04af661c6170df..664edb0304c878dd9420838ba1d100d1ce2e50cc 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; } -- 2.54.0 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 5683c5300a959845624ed528fd27cc02a5fc34dc..1192c726298e8919ab6dce959a1c26409ccb8494 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 aa127db8ed49a685680622ae6f991ab7ffa292a3..ae9b441b60ef78abb72aaae579291e9d1c2b98b4 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; -- 2.54.0 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 fd9e13a220fcce326e0e24dc371d2e5fabe101d1..91072d0b4c8b4aac4a45fe2368b52da8cdb5a90b 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 { -- 2.54.0