From 4d20d6874c418f596c576cea48985de6ef3a3dd2 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 01:50:32 +0300 Subject: [PATCH 01/17] move passing safety tests to stage2 --- src/AstGen.zig | 3 +-- ...rrSetCast error not present in destination.zig | 10 ++++++---- test/cases/safety/@intCast to u0.zig | 10 ++++++---- ... zero to non-optional byte-aligned pointer.zig | 8 +++++--- ...ToPtr address zero to non-optional pointer.zig | 8 +++++--- test/cases/safety/calling panic.zig | 4 ++-- ...nteger to global error and no code matches.zig | 10 ++++++---- .../safety/intToPtr with misaligned address.zig | 2 +- test/cases/safety/integer addition overflow.zig | 2 +- .../safety/integer multiplication overflow.zig | 10 ++++++---- test/cases/safety/integer negation overflow.zig | 10 ++++++---- .../cases/safety/integer subtraction overflow.zig | 10 ++++++---- .../optional unwrap operator on C pointer.zig | 8 +++++--- .../optional unwrap operator on null pointer.zig | 8 +++++--- test/cases/safety/out of bounds slice access.zig | 10 ++++++---- test/cases/safety/signed shift left overflow.zig | 10 ++++++---- test/cases/safety/truncating vector cast.zig | 2 +- test/cases/safety/unreachable.zig | 15 +++++++++++++++ ...in cast to signed integer - same bit count.zig | 10 ++++++---- .../cases/safety/unsigned shift left overflow.zig | 10 ++++++---- test/cases/safety/unsigned-signed vector cast.zig | 2 +- test/cases/safety/unwrap error.zig | 4 ++-- ...value does not fit in shortening cast - u0.zig | 10 ++++++---- .../value does not fit in shortening cast.zig | 10 ++++++---- .../safety/vector integer addition overflow.zig | 10 ++++++---- .../vector integer multiplication overflow.zig | 10 ++++++---- .../safety/vector integer negation overflow.zig | 10 ++++++---- .../vector integer subtraction overflow.zig | 10 ++++++---- 28 files changed, 140 insertions(+), 86 deletions(-) create mode 100644 test/cases/safety/unreachable.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index af0fac04f1..a0ba743c4e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -6504,8 +6504,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref }, .always => { // Value is always an error. Emit both error defers and regular defers. - const result = if (rl == .ptr) try gz.addUnNode(.load, rl.ptr, node) else operand; - const err_code = try gz.addUnNode(.err_union_code, result, node); + const err_code = if (rl == .ptr) try gz.addUnNode(.load, rl.ptr, node) else operand; try genDefers(gz, defer_outer, scope, .{ .both = err_code }); try gz.addRet(rl, operand, node); return Zir.Inst.Ref.unreachable_value; diff --git a/test/cases/safety/@errSetCast error not present in destination.zig b/test/cases/safety/@errSetCast error not present in destination.zig index 5bb92f4f28..bf4dee8421 100644 --- a/test/cases/safety/@errSetCast error not present in destination.zig +++ b/test/cases/safety/@errSetCast error not present in destination.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "invalid error code")) { + std.process.exit(0); + } + std.process.exit(1); } const Set1 = error{A, B}; const Set2 = error{A, C}; @@ -15,5 +17,5 @@ fn foo(set1: Set1) Set2 { return @errSetCast(Set2, set1); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/@intCast to u0.zig b/test/cases/safety/@intCast to u0.zig index 7d51fdec25..0f38d59e50 100644 --- a/test/cases/safety/@intCast to u0.zig +++ b/test/cases/safety/@intCast to u0.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer cast truncated bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -16,5 +18,5 @@ fn bar(one: u1, not_zero: i32) void { _ = x; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/@intToPtr address zero to non-optional byte-aligned pointer.zig b/test/cases/safety/@intToPtr address zero to non-optional byte-aligned pointer.zig index ac781dc53f..a3b7f374ce 100644 --- a/test/cases/safety/@intToPtr address zero to non-optional byte-aligned pointer.zig +++ b/test/cases/safety/@intToPtr address zero to non-optional byte-aligned pointer.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "cast causes pointer to be null")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var zero: usize = 0; @@ -12,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/@intToPtr address zero to non-optional pointer.zig b/test/cases/safety/@intToPtr address zero to non-optional pointer.zig index f8fd53eb97..8724e98131 100644 --- a/test/cases/safety/@intToPtr address zero to non-optional pointer.zig +++ b/test/cases/safety/@intToPtr address zero to non-optional pointer.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "cast causes pointer to be null")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var zero: usize = 0; @@ -12,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/calling panic.zig b/test/cases/safety/calling panic.zig index 5befea3e3c..29f3a6c663 100644 --- a/test/cases/safety/calling panic.zig +++ b/test/cases/safety/calling panic.zig @@ -12,5 +12,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/cast integer to global error and no code matches.zig b/test/cases/safety/cast integer to global error and no code matches.zig index 3a8dc2374f..d423704039 100644 --- a/test/cases/safety/cast integer to global error and no code matches.zig +++ b/test/cases/safety/cast integer to global error and no code matches.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "invalid error code")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { bar(9999) catch {}; @@ -13,5 +15,5 @@ fn bar(x: u16) anyerror { return @intToError(x); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/intToPtr with misaligned address.zig b/test/cases/safety/intToPtr with misaligned address.zig index 5b480eccca..6cb085a64b 100644 --- a/test/cases/safety/intToPtr with misaligned address.zig +++ b/test/cases/safety/intToPtr with misaligned address.zig @@ -14,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/integer addition overflow.zig b/test/cases/safety/integer addition overflow.zig index cd23b66f36..aa2ab60dd4 100644 --- a/test/cases/safety/integer addition overflow.zig +++ b/test/cases/safety/integer addition overflow.zig @@ -19,5 +19,5 @@ fn add(a: u16, b: u16) u16 { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/integer multiplication overflow.zig b/test/cases/safety/integer multiplication overflow.zig index c9894217a7..af7e638381 100644 --- a/test/cases/safety/integer multiplication overflow.zig +++ b/test/cases/safety/integer multiplication overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn mul(a: u16, b: u16) u16 { return a * b; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/integer negation overflow.zig b/test/cases/safety/integer negation overflow.zig index f3eb1feffe..e48ffff761 100644 --- a/test/cases/safety/integer negation overflow.zig +++ b/test/cases/safety/integer negation overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn neg(a: i16) i16 { return -a; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/integer subtraction overflow.zig b/test/cases/safety/integer subtraction overflow.zig index ce1526cf60..6d9380ee26 100644 --- a/test/cases/safety/integer subtraction overflow.zig +++ b/test/cases/safety/integer subtraction overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn sub(a: u16, b: u16) u16 { return a - b; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/optional unwrap operator on C pointer.zig b/test/cases/safety/optional unwrap operator on C pointer.zig index 05614b4ca2..10e605cadd 100644 --- a/test/cases/safety/optional unwrap operator on C pointer.zig +++ b/test/cases/safety/optional unwrap operator on C pointer.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "attempt to use null value")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var ptr: [*c]i32 = null; @@ -12,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/optional unwrap operator on null pointer.zig b/test/cases/safety/optional unwrap operator on null pointer.zig index 1db44ba22a..3d2991d9ec 100644 --- a/test/cases/safety/optional unwrap operator on null pointer.zig +++ b/test/cases/safety/optional unwrap operator on null pointer.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "attempt to use null value")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var ptr: ?*i32 = null; @@ -12,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/out of bounds slice access.zig b/test/cases/safety/out of bounds slice access.zig index 8c95978d4a..a30532aee7 100644 --- a/test/cases/safety/out of bounds slice access.zig +++ b/test/cases/safety/out of bounds slice access.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "attempt to index out of bound: index 4, len 4")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { const a = [_]i32{1, 2, 3, 4}; @@ -15,5 +17,5 @@ fn bar(a: []const i32) i32 { } fn baz(_: i32) void { } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/signed shift left overflow.zig b/test/cases/safety/signed shift left overflow.zig index 88adbe5835..75e9711032 100644 --- a/test/cases/safety/signed shift left overflow.zig +++ b/test/cases/safety/signed shift left overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "left shift overflowed bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn shl(a: i16, b: u4) i16 { return @shlExact(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/truncating vector cast.zig b/test/cases/safety/truncating vector cast.zig index b0a3c4e25b..5985952e02 100644 --- a/test/cases/safety/truncating vector cast.zig +++ b/test/cases/safety/truncating vector cast.zig @@ -16,5 +16,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/unreachable.zig b/test/cases/safety/unreachable.zig new file mode 100644 index 0000000000..f752016aad --- /dev/null +++ b/test/cases/safety/unreachable.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "reached unreachable code")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + unreachable; +} +// run +// backend=llvm +// target=native diff --git a/test/cases/safety/unsigned integer not fitting in cast to signed integer - same bit count.zig b/test/cases/safety/unsigned integer not fitting in cast to signed integer - same bit count.zig index 9bf36b07c9..ca901560f0 100644 --- a/test/cases/safety/unsigned integer not fitting in cast to signed integer - same bit count.zig +++ b/test/cases/safety/unsigned integer not fitting in cast to signed integer - same bit count.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer cast truncated bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var value: u8 = 245; @@ -12,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/unsigned shift left overflow.zig b/test/cases/safety/unsigned shift left overflow.zig index 73c4292cdf..9d6b6a3c48 100644 --- a/test/cases/safety/unsigned shift left overflow.zig +++ b/test/cases/safety/unsigned shift left overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "left shift overflowed bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn shl(a: u16, b: u4) u16 { return @shlExact(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/unsigned-signed vector cast.zig b/test/cases/safety/unsigned-signed vector cast.zig index c78ec2c73f..eb3995269f 100644 --- a/test/cases/safety/unsigned-signed vector cast.zig +++ b/test/cases/safety/unsigned-signed vector cast.zig @@ -16,5 +16,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/unwrap error.zig b/test/cases/safety/unwrap error.zig index ee9a502ebd..498461976d 100644 --- a/test/cases/safety/unwrap error.zig +++ b/test/cases/safety/unwrap error.zig @@ -15,5 +15,5 @@ fn bar() !void { return error.Whatever; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/value does not fit in shortening cast - u0.zig b/test/cases/safety/value does not fit in shortening cast - u0.zig index 072be45731..3ff3aa6e6c 100644 --- a/test/cases/safety/value does not fit in shortening cast - u0.zig +++ b/test/cases/safety/value does not fit in shortening cast - u0.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer cast truncated bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn shorten_cast(x: u8) u0 { return @intCast(u0, x); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/value does not fit in shortening cast.zig b/test/cases/safety/value does not fit in shortening cast.zig index 7188c9a846..510273a57b 100644 --- a/test/cases/safety/value does not fit in shortening cast.zig +++ b/test/cases/safety/value does not fit in shortening cast.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer cast truncated bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn shorten_cast(x: i32) i8 { return @intCast(i8, x); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/vector integer addition overflow.zig b/test/cases/safety/vector integer addition overflow.zig index 5eedc869e7..7e34fa9393 100644 --- a/test/cases/safety/vector integer addition overflow.zig +++ b/test/cases/safety/vector integer addition overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var a: @Vector(4, i32) = [_]i32{ 1, 2, 2147483643, 4 }; @@ -16,5 +18,5 @@ fn add(a: @Vector(4, i32), b: @Vector(4, i32)) @Vector(4, i32) { return a + b; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/vector integer multiplication overflow.zig b/test/cases/safety/vector integer multiplication overflow.zig index 5d247cf545..a2a74072b3 100644 --- a/test/cases/safety/vector integer multiplication overflow.zig +++ b/test/cases/safety/vector integer multiplication overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var a: @Vector(4, u8) = [_]u8{ 1, 2, 200, 4 }; @@ -16,5 +18,5 @@ fn mul(a: @Vector(4, u8), b: @Vector(4, u8)) @Vector(4, u8) { return a * b; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/vector integer negation overflow.zig b/test/cases/safety/vector integer negation overflow.zig index 03d846aab0..7f59982372 100644 --- a/test/cases/safety/vector integer negation overflow.zig +++ b/test/cases/safety/vector integer negation overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var a: @Vector(4, i16) = [_]i16{ 1, -32768, 200, 4 }; @@ -15,5 +17,5 @@ fn neg(a: @Vector(4, i16)) @Vector(4, i16) { return -a; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/vector integer subtraction overflow.zig b/test/cases/safety/vector integer subtraction overflow.zig index 72287ffd07..dbfee3af1e 100644 --- a/test/cases/safety/vector integer subtraction overflow.zig +++ b/test/cases/safety/vector integer subtraction overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var a: @Vector(4, u32) = [_]u32{ 1, 2, 8, 4 }; @@ -16,5 +18,5 @@ fn sub(a: @Vector(4, u32), b: @Vector(4, u32)) @Vector(4, u32) { return a - b; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From 9f10dfcb546556b26b420633c7fa4d7de39f8fd7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 02:15:24 +0300 Subject: [PATCH 02/17] Sema: implement shr_exact runtime safety --- src/Sema.zig | 78 +++++++++++-------- .../safety/signed shift right overflow.zig | 10 ++- .../safety/unsigned shift right overflow.zig | 10 ++- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 8023e60c7d..64a740c6b1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9996,40 +9996,34 @@ fn zirShl( } else rhs; try sema.requireRuntimeBlock(block, src, runtime_src); - if (block.wantSafety()) { - const maybe_op_ov: ?Air.Inst.Tag = switch (air_tag) { - .shl_exact => .shl_with_overflow, - else => null, - }; - if (maybe_op_ov) |op_ov_tag| { - const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty); - const op_ov = try block.addInst(.{ - .tag = op_ov_tag, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(op_ov_tuple_ty), - .payload = try sema.addExtra(Air.Bin{ - .lhs = lhs, - .rhs = rhs, - }), + if (block.wantSafety() and air_tag == .shl_exact) { + const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty); + const op_ov = try block.addInst(.{ + .tag = .shl_with_overflow, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(op_ov_tuple_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = lhs, + .rhs = rhs, + }), + } }, + }); + const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); + const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector) + try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = ov_bit, + .operation = .Or, } }, - }); - const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); - const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector) - try block.addInst(.{ - .tag = .reduce, - .data = .{ .reduce = .{ - .operand = ov_bit, - .operation = .Or, - } }, - }) - else - ov_bit; - const zero_ov = try sema.addConstant(Type.@"u1", Value.zero); - const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov); + }) + else + ov_bit; + const zero_ov = try sema.addConstant(Type.@"u1", Value.zero); + const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov); - try sema.addSafetyCheck(block, no_ov, .shl_overflow); - return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); - } + try sema.addSafetyCheck(block, no_ov, .shl_overflow); + return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); } return block.addBinOp(air_tag, lhs, new_rhs); } @@ -10107,7 +10101,23 @@ fn zirShr( } else rhs_src; try sema.requireRuntimeBlock(block, src, runtime_src); - return block.addBinOp(air_tag, lhs, rhs); + const result = try block.addBinOp(air_tag, lhs, rhs); + if (block.wantSafety() and air_tag == .shr_exact) { + const back = try block.addBinOp(.shl, result, rhs); + + const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { + const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty)); + break :ok try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = eql, + .operation = .And, + } }, + }); + } else try block.addBinOp(.cmp_eq, lhs, back); + try sema.addSafetyCheck(block, ok, .shr_overflow); + } + return result; } fn zirBitwise( @@ -18802,6 +18812,7 @@ pub const PanicId = enum { cast_truncated_data, integer_overflow, shl_overflow, + shr_overflow, }; fn addSafetyCheck( @@ -19019,6 +19030,7 @@ fn safetyPanic( .cast_truncated_data => "integer cast truncated bits", .integer_overflow => "integer overflow", .shl_overflow => "left shift overflowed bits", + .shr_overflow => "right shift overflowed bits", }; const msg_inst = msg_inst: { diff --git a/test/cases/safety/signed shift right overflow.zig b/test/cases/safety/signed shift right overflow.zig index 9d5545ed3a..52c6158a4d 100644 --- a/test/cases/safety/signed shift right overflow.zig +++ b/test/cases/safety/signed shift right overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "right shift overflowed bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn shr(a: i16, b: u4) i16 { return @shrExact(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/unsigned shift right overflow.zig b/test/cases/safety/unsigned shift right overflow.zig index 6a3829f675..500e82866b 100644 --- a/test/cases/safety/unsigned shift right overflow.zig +++ b/test/cases/safety/unsigned shift right overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "right shift overflowed bits")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn shr(a: u16, b: u4) u16 { return @shrExact(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From 0782586b15302654501ebbcab3a2c63755a6fadb Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 02:55:08 +0300 Subject: [PATCH 03/17] Sema: divide by zero safety --- src/Sema.zig | 43 +++++++++++++++++++ .../integer division by zero - vectors.zig | 10 +++-- .../cases/safety/integer division by zero.zig | 10 +++-- .../remainder division by negative number.zig | 20 +++++++++ 4 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 test/cases/safety/remainder division by negative number.zig diff --git a/src/Sema.zig b/src/Sema.zig index 64a740c6b1..9fdb98f3c2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11877,6 +11877,45 @@ fn analyzeArithmetic( return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); } } + switch (rs.air_tag) { + .div_float, .div_exact, .div_trunc, .div_floor => { + 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 = .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(.cmp_neq, casted_rhs, zero); + }; + try sema.addSafetyCheck(block, ok, .divide_by_zero); + }, + .rem, .mod => { + 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, if (scalar_tag == .Int) .gt else .neq, try sema.addType(resolved_type)); + break :ok try block.addInst(.{ + .tag = .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 (scalar_tag == .Int) .cmp_gt else .cmp_neq, casted_rhs, zero); + }; + try sema.addSafetyCheck(block, ok, .remainder_division_zero_negative); + }, + else => {}, + } } return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } @@ -18813,6 +18852,8 @@ pub const PanicId = enum { integer_overflow, shl_overflow, shr_overflow, + divide_by_zero, + remainder_division_zero_negative, }; fn addSafetyCheck( @@ -19031,6 +19072,8 @@ fn safetyPanic( .integer_overflow => "integer overflow", .shl_overflow => "left shift overflowed bits", .shr_overflow => "right shift overflowed bits", + .divide_by_zero => "division by zero", + .remainder_division_zero_negative => "remainder division by zero or negative value", }; const msg_inst = msg_inst: { diff --git a/test/cases/safety/integer division by zero - vectors.zig b/test/cases/safety/integer division by zero - vectors.zig index 136f179935..0789f7f088 100644 --- a/test/cases/safety/integer division by zero - vectors.zig +++ b/test/cases/safety/integer division by zero - vectors.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "division by zero")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var a: @Vector(4, i32) = [4]i32{111, 222, 333, 444}; @@ -16,5 +18,5 @@ fn div0(a: @Vector(4, i32), b: @Vector(4, i32)) @Vector(4, i32) { return @divTrunc(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/integer division by zero.zig b/test/cases/safety/integer division by zero.zig index 8d80a7c848..46f57c1117 100644 --- a/test/cases/safety/integer division by zero.zig +++ b/test/cases/safety/integer division by zero.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "division by zero")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { const x = div0(999, 0); @@ -14,5 +16,5 @@ fn div0(a: i32, b: i32) i32 { return @divTrunc(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/remainder division by negative number.zig b/test/cases/safety/remainder division by negative number.zig new file mode 100644 index 0000000000..2edbf4509c --- /dev/null +++ b/test/cases/safety/remainder division by negative number.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "remainder division by zero or negative value")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + const x = div0(999, -1); + _ = x; + return error.TestFailed; +} +fn div0(a: i32, b: i32) i32 { + return @rem(a, b); +} +// run +// backend=llvm +// target=native From 76d099950aa2e5fee4897c8bc401946f39ed87a4 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 15:56:16 +0300 Subject: [PATCH 04/17] Sema: cast negative to unsigned safety --- src/Sema.zig | 7 +++++-- ...fitting in cast to unsigned integer - widening.zig | 10 ++++++---- ...nteger not fitting in cast to unsigned integer.zig | 11 ++++++----- test/cases/safety/signed-unsigned vector cast.zig | 2 +- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 9fdb98f3c2..e30b7f5cec 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8092,6 +8092,7 @@ fn intCast( const is_in_range = try block.addBinOp(.cmp_lte, diff_unsigned, dest_range); break :ok is_in_range; }; + // TODO negative_to_unsigned? try sema.addSafetyCheck(block, ok, .cast_truncated_data); } else { const ok = if (is_vector) ok: { @@ -8116,7 +8117,7 @@ fn intCast( const ok = if (is_vector) ok: { const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); const zero_inst = try sema.addConstant(operand_ty, zero_val); - const is_in_range = try block.addCmpVector(operand, zero_inst, .lte, try sema.addType(operand_ty)); + const is_in_range = try block.addCmpVector(operand, zero_inst, .gte, try sema.addType(operand_ty)); const all_in_range = try block.addInst(.{ .tag = .reduce, .data = .{ .reduce = .{ @@ -8130,7 +8131,7 @@ fn intCast( const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst); break :ok is_in_range; }; - try sema.addSafetyCheck(block, ok, .cast_truncated_data); + try sema.addSafetyCheck(block, ok, .negative_to_unsigned); } } return block.addTyOp(.intcast, dest_ty, operand); @@ -18849,6 +18850,7 @@ pub const PanicId = enum { incorrect_alignment, invalid_error_code, cast_truncated_data, + negative_to_unsigned, integer_overflow, shl_overflow, shr_overflow, @@ -19069,6 +19071,7 @@ fn safetyPanic( .incorrect_alignment => "incorrect alignment", .invalid_error_code => "invalid error code", .cast_truncated_data => "integer cast truncated bits", + .negative_to_unsigned => "attempt to cast negative value to unsigned integer", .integer_overflow => "integer overflow", .shl_overflow => "left shift overflowed bits", .shr_overflow => "right shift overflowed bits", diff --git a/test/cases/safety/signed integer not fitting in cast to unsigned integer - widening.zig b/test/cases/safety/signed integer not fitting in cast to unsigned integer - widening.zig index 7abd085364..6ee23c9aee 100644 --- a/test/cases/safety/signed integer not fitting in cast to unsigned integer - widening.zig +++ b/test/cases/safety/signed integer not fitting in cast to unsigned integer - widening.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { var value: c_short = -1; @@ -12,5 +14,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/signed integer not fitting in cast to unsigned integer.zig b/test/cases/safety/signed integer not fitting in cast to unsigned integer.zig index 4dea06fc82..5c21e0a098 100644 --- a/test/cases/safety/signed integer not fitting in cast to unsigned integer.zig +++ b/test/cases/safety/signed integer not fitting in cast to unsigned integer.zig @@ -1,11 +1,12 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) { + std.process.exit(0); + } + std.process.exit(1); } - pub fn main() !void { const x = unsigned_cast(-10); if (x == 0) return error.Whatever; @@ -15,5 +16,5 @@ fn unsigned_cast(x: i32) u32 { return @intCast(u32, x); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/signed-unsigned vector cast.zig b/test/cases/safety/signed-unsigned vector cast.zig index 15d120350e..5d8a2ea271 100644 --- a/test/cases/safety/signed-unsigned vector cast.zig +++ b/test/cases/safety/signed-unsigned vector cast.zig @@ -16,5 +16,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native From 55fe34100f8b516480cf530eb58d00ea8b665765 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 16:10:11 +0300 Subject: [PATCH 05/17] Sema: exact division safety --- src/Sema.zig | 43 +++++++++++++++++++ test/behavior/math.zig | 1 + .../exact division failure - vectors.zig | 10 +++-- test/cases/safety/exact division failure.zig | 10 +++-- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e30b7f5cec..f8b9d044d0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11917,6 +11917,47 @@ fn analyzeArithmetic( }, else => {}, } + if (rs.air_tag == .div_exact) { + 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 = .reduce, + .data = .{ .reduce = .{ + .operand = eql, + .operation = .And, + } }, + }); + } else { + const is_in_range = try block.addBinOp(.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(.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); } @@ -18856,6 +18897,7 @@ pub const PanicId = enum { shr_overflow, divide_by_zero, remainder_division_zero_negative, + exact_division_remainder, }; fn addSafetyCheck( @@ -19077,6 +19119,7 @@ fn safetyPanic( .shr_overflow => "right shift overflowed bits", .divide_by_zero => "division by zero", .remainder_division_zero_negative => "remainder division by zero or negative value", + .exact_division_remainder => "exact division produced remainder", }; const msg_inst = msg_inst: { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 7b280bca4e..c8d0becbd6 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -377,6 +377,7 @@ fn testBinaryNot(x: u16) !void { } test "division" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) 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 diff --git a/test/cases/safety/exact division failure - vectors.zig b/test/cases/safety/exact division failure - vectors.zig index a514213f58..9b792b33cf 100644 --- a/test/cases/safety/exact division failure - vectors.zig +++ b/test/cases/safety/exact division failure - vectors.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "exact division produced remainder")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -17,5 +19,5 @@ fn divExact(a: @Vector(4, i32), b: @Vector(4, i32)) @Vector(4, i32) { return @divExact(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/exact division failure.zig b/test/cases/safety/exact division failure.zig index 5e30f14b06..ea4d39ed22 100644 --- a/test/cases/safety/exact division failure.zig +++ b/test/cases/safety/exact division failure.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "exact division produced remainder")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn divExact(a: i32, b: i32) i32 { return @divExact(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From ff7ec4efb5a6da565b92bc7b129d03680a4a72bd Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 16:32:49 +0300 Subject: [PATCH 06/17] Sema: bad union field access safety --- src/AstGen.zig | 3 +- src/Module.zig | 2 +- src/Sema.zig | 131 ++++++++++++------ src/Zir.zig | 5 + src/arch/wasm/abi.zig | 4 +- src/codegen/c.zig | 18 +-- src/codegen/llvm.zig | 4 +- src/print_zir.zig | 1 + src/type.zig | 83 +++++++---- test/behavior/bugs/1381.zig | 2 + test/behavior/struct.zig | 3 + test/behavior/type.zig | 2 +- test/behavior/union.zig | 13 ++ ...ializer_for_union_payload_of_type_type.zig | 3 +- test/cases/safety/bad union field access.zig | 10 +- 15 files changed, 194 insertions(+), 90 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index a0ba743c4e..fa1f72670a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1729,7 +1729,7 @@ fn structInitExprRlPtrInner( for (struct_init.ast.fields) |field_init| { const name_token = tree.firstToken(field_init) - 2; const str_index = try astgen.identAsString(name_token); - const field_ptr = try gz.addPlNode(.field_ptr, field_init, Zir.Inst.Field{ + const field_ptr = try gz.addPlNode(.field_ptr_init, field_init, Zir.Inst.Field{ .lhs = result_ptr, .field_name_start = str_index, }); @@ -2287,6 +2287,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .elem_ptr_imm, .elem_val_node, .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, .field_ptr_named, diff --git a/src/Module.zig b/src/Module.zig index 5f441790b7..da50fd9ae7 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -787,7 +787,7 @@ pub const Decl = struct { const opaque_obj = ty.cast(Type.Payload.Opaque).?.data; return &opaque_obj.namespace; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; return &union_obj.namespace; }, diff --git a/src/Sema.zig b/src/Sema.zig index f8b9d044d0..22b12637f2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -739,7 +739,8 @@ fn analyzeBodyInner( .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false), .error_union_type => try sema.zirErrorUnionType(block, inst), .error_value => try sema.zirErrorValue(block, inst), - .field_ptr => try sema.zirFieldPtr(block, inst), + .field_ptr => try sema.zirFieldPtr(block, inst, false), + .field_ptr_init => try sema.zirFieldPtr(block, inst, true), .field_ptr_named => try sema.zirFieldPtrNamed(block, inst), .field_val => try sema.zirFieldVal(block, inst), .field_val_named => try sema.zirFieldValNamed(block, inst), @@ -1547,11 +1548,11 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) const st_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty)); // st.instruction_addresses = &addrs; - const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src); + const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src, true); try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store); // st.index = 0; - const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src); + const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src, true); const zero = try sema.addConstant(Type.usize, Value.zero); try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store); @@ -2614,7 +2615,14 @@ fn zirUnionDecl( const new_decl_arena_allocator = new_decl_arena.allocator(); const union_obj = try new_decl_arena_allocator.create(Module.Union); - const type_tag: Type.Tag = if (small.has_tag_type or small.auto_enum_tag) .union_tagged else .@"union"; + const type_tag = if (small.has_tag_type or small.auto_enum_tag) + Type.Tag.union_tagged + else if (small.layout != .Auto) + Type.Tag.@"union" + else switch (block.sema.mod.optimizeMode()) { + .Debug, .ReleaseSafe => Type.Tag.union_safety_tagged, + .ReleaseFast, .ReleaseSmall => Type.Tag.@"union", + }; const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union); union_payload.* = .{ .base = .{ .tag = type_tag }, @@ -7923,7 +7931,7 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai return sema.fieldVal(block, src, object, field_name, field_name_src); } -fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index, initializing: bool) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -7933,7 +7941,7 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; const field_name = sema.code.nullTerminatedString(extra.field_name_start); const object_ptr = try sema.resolveInst(extra.lhs); - return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); + return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, initializing); } fn zirFieldCallBind(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7972,7 +7980,7 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; const object_ptr = try sema.resolveInst(extra.lhs); const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime known"); - return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); + return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false); } fn zirFieldCallBindNamed(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { @@ -14536,7 +14544,7 @@ fn zirStructInit( .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); const alloc = try block.addTy(.alloc, alloc_ty); - const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty); + const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty, true); try sema.storePtr(block, src, field_ptr, init_inst); const new_tag = try sema.addConstant(resolved_ty.unionTagTypeHypothetical(), tag_val); _ = try block.addBinOp(.set_union_tag, alloc, new_tag); @@ -15604,13 +15612,21 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I if (decls_val.sliceLen(mod) > 0) { return sema.fail(block, src, "reified unions must have no decls", .{}); } + const layout = layout_val.toEnum(std.builtin.Type.ContainerLayout); var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); errdefer new_decl_arena.deinit(); const new_decl_arena_allocator = new_decl_arena.allocator(); const union_obj = try new_decl_arena_allocator.create(Module.Union); - const type_tag: Type.Tag = if (!tag_type_val.isNull()) .union_tagged else .@"union"; + const type_tag = if (!tag_type_val.isNull()) + Type.Tag.union_tagged + else if (layout != .Auto) + Type.Tag.@"union" + else switch (block.sema.mod.optimizeMode()) { + .Debug, .ReleaseSafe => Type.Tag.union_safety_tagged, + .ReleaseFast, .ReleaseSmall => Type.Tag.@"union", + }; const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union); union_payload.* = .{ .base = .{ .tag = type_tag }, @@ -15631,7 +15647,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .fields = .{}, .node_offset = src.node_offset.x, .zir_index = inst, - .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout), + .layout = layout, .status = .have_field_types, .namespace = .{ .parent = block.namespace, @@ -15641,11 +15657,15 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I }; // Tag type + var enum_field_names: ?*Module.EnumNumbered.NameMap = null; const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); - union_obj.tag_ty = if (tag_type_val.optionalValue()) |payload_val| blk: { + if (tag_type_val.optionalValue()) |payload_val| { var buffer: Value.ToTypeBuffer = undefined; - break :blk try payload_val.toType(&buffer).copy(new_decl_arena_allocator); - } else try sema.generateUnionTagTypeSimple(block, fields_len, null); + union_obj.tag_ty = try payload_val.toType(&buffer).copy(new_decl_arena_allocator); + } else { + union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len, null); + enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; + } // Fields if (fields_len > 0) { @@ -15669,6 +15689,10 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I sema.mod, ); + if (enum_field_names) |set| { + set.putAssumeCapacity(field_name, {}); + } + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); if (gop.found_existing) { // TODO: better source location @@ -18898,6 +18922,8 @@ pub const PanicId = enum { divide_by_zero, remainder_division_zero_negative, exact_division_remainder, + /// TODO make this call `std.builtin.panicInactiveUnionField`. + inactive_union_field, }; fn addSafetyCheck( @@ -19120,6 +19146,7 @@ fn safetyPanic( .divide_by_zero => "division by zero", .remainder_division_zero_negative => "remainder division by zero or negative value", .exact_division_remainder => "exact division produced remainder", + .inactive_union_field => "access of inactive union field", }; const msg_inst = msg_inst: { @@ -19339,7 +19366,7 @@ fn fieldVal( }, .Union => if (is_pointer_to) { // Avoid loading the entire union by fetching a pointer and loading that - const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty); + const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false); return sema.analyzeLoad(block, src, field_ptr, object_src); } else { return sema.unionFieldVal(block, src, object, field_name, field_name_src, inner_ty); @@ -19356,6 +19383,7 @@ fn fieldPtr( object_ptr: Air.Inst.Ref, field_name: []const u8, field_name_src: LazySrcLoc, + initializing: bool, ) CompileError!Air.Inst.Ref { // When editing this function, note that there is corresponding logic to be edited // in `fieldVal`. This function takes a pointer and returns a pointer. @@ -19547,7 +19575,7 @@ fn fieldPtr( try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) else object_ptr; - return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty); + return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing); }, else => {}, } @@ -19995,6 +20023,7 @@ fn unionFieldPtr( field_name: []const u8, field_name_src: LazySrcLoc, unresolved_union_ty: Type, + initializing: bool, ) CompileError!Air.Inst.Ref { const arena = sema.arena; assert(unresolved_union_ty.zigTypeTag() == .Union); @@ -20010,30 +20039,32 @@ fn unionFieldPtr( .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); - if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| { + if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: { switch (union_obj.layout) { - .Auto => { - // TODO emit the access of inactive union field error commented out below. - // In order to do that, we need to first solve the problem that AstGen - // emits field_ptr instructions in order to initialize union values. - // In such case we need to know that the field_ptr instruction (which is - // calling this unionFieldPtr function) is *initializing* the union, - // in which case we would skip this check, and in fact we would actually - // set the union tag here and the payload to undefined. - - //const tag_and_val = union_val.castTag(.@"union").?.data; - //var field_tag_buf: Value.Payload.U32 = .{ - // .base = .{ .tag = .enum_field_index }, - // .data = field_index, - //}; - //const field_tag = Value.initPayload(&field_tag_buf.base); - //const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, mod); - //if (!tag_matches) { - // // TODO enhance this saying which one was active - // // and which one was accessed, and showing where the union was declared. - // return sema.fail(block, src, "access of inactive union field", .{}); - //} - // TODO add runtime safety check for the active tag + .Auto => if (!initializing) { + const union_val = (try sema.pointerDeref(block, src, union_ptr_val, union_ptr_ty)) orelse + break :ct; + if (union_val.isUndef()) { + return sema.failWithUseOfUndef(block, src); + } + const tag_and_val = union_val.castTag(.@"union").?.data; + var field_tag_buf: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = field_index, + }; + const field_tag = Value.initPayload(&field_tag_buf.base); + const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); + if (!tag_matches) { + const msg = msg: { + const active_index = tag_and_val.tag.castTag(.enum_field_index).?.data; + const active_field_name = union_obj.fields.keys()[active_index]; + const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{ field_name, active_field_name }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } }, .Packed, .Extern => {}, } @@ -20048,6 +20079,16 @@ fn unionFieldPtr( } try sema.requireRuntimeBlock(block, src, null); + if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) { + const enum_ty = union_ty.unionTagTypeHypothetical(); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val); + // TODO would it be better if get_union_tag supported pointers to unions? + const union_val = try block.addTyOp(.load, union_ty, union_ptr); + const active_tag = try block.addTyOp(.get_union_tag, enum_ty, union_val); + const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); + try sema.addSafetyCheck(block, ok, .inactive_union_field); + } return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty); } @@ -20106,6 +20147,14 @@ fn unionFieldVal( } try sema.requireRuntimeBlock(block, src, null); + if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) { + const enum_ty = union_ty.unionTagTypeHypothetical(); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val); + const active_tag = try block.addTyOp(.get_union_tag, enum_ty, union_byval); + const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); + try sema.addSafetyCheck(block, ok, .inactive_union_field); + } return block.addStructFieldVal(union_byval, field_index, field.ty); } @@ -25424,7 +25473,7 @@ pub fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) try sema.resolveTypeFieldsStruct(block, src, ty, struct_obj); return ty; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; try sema.resolveTypeFieldsUnion(block, src, ty, union_obj); return ty; @@ -26449,7 +26498,7 @@ pub fn typeHasOnePossibleValue( return null; } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const union_obj = resolved_ty.cast(Type.Payload.Union).?.data; const tag_val = (try sema.typeHasOnePossibleValue(block, src, union_obj.tag_ty)) orelse @@ -27081,7 +27130,7 @@ pub fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; switch (union_obj.requires_comptime) { .no, .wip => return false, diff --git a/src/Zir.zig b/src/Zir.zig index 73ba396e75..d2c62bd7c0 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -410,6 +410,8 @@ pub const Inst = struct { /// to the named field. The field name is stored in string_bytes. Used by a.b syntax. /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field. field_ptr, + /// Same as `field_ptr` but used for struct init. + field_ptr_init, /// Given a struct or object that contains virtual fields, returns the named field. /// The field name is stored in string_bytes. Used by a.b syntax. /// This instruction also accepts a pointer. @@ -1070,6 +1072,7 @@ pub const Inst = struct { .@"export", .export_value, .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, .field_ptr_named, @@ -1370,6 +1373,7 @@ pub const Inst = struct { .elem_ptr_imm, .elem_val_node, .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, .field_ptr_named, @@ -1629,6 +1633,7 @@ pub const Inst = struct { .@"export" = .pl_node, .export_value = .pl_node, .field_ptr = .pl_node, + .field_ptr_init = .pl_node, .field_val = .pl_node, .field_ptr_named = .pl_node, .field_val_named = .pl_node, diff --git a/src/arch/wasm/abi.zig b/src/arch/wasm/abi.zig index 63b319613b..77ba695a90 100644 --- a/src/arch/wasm/abi.zig +++ b/src/arch/wasm/abi.zig @@ -77,7 +77,7 @@ pub fn classifyType(ty: Type, target: Target) [2]Class { .Union => { const layout = ty.unionGetLayout(target); if (layout.payload_size == 0 and layout.tag_size != 0) { - return classifyType(ty.unionTagType().?, target); + return classifyType(ty.unionTagTypeSafety().?, target); } if (ty.unionFields().count() > 1) return memory; return classifyType(ty.unionFields().values()[0].ty, target); @@ -111,7 +111,7 @@ pub fn scalarType(ty: Type, target: std.Target) Type { .Union => { const layout = ty.unionGetLayout(target); if (layout.payload_size == 0 and layout.tag_size != 0) { - return scalarType(ty.unionTagType().?, target); + return scalarType(ty.unionTagTypeSafety().?, target); } std.debug.assert(ty.unionFields().count() == 1); return scalarType(ty.unionFields().values()[0].ty, target); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1e629d7cb0..98f7792a76 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -504,7 +504,7 @@ pub const DeclGen = struct { if (field_ty.hasRuntimeBitsIgnoreComptime()) { try writer.writeAll("&("); try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty); - if (field_ptr.container_ty.tag() == .union_tagged) { + if (field_ptr.container_ty.tag() == .union_tagged or field_ptr.container_ty.tag() == .union_safety_tagged) { try writer.print(")->payload.{ }", .{fmtIdent(field_name)}); } else { try writer.print(")->{ }", .{fmtIdent(field_name)}); @@ -842,7 +842,7 @@ pub const DeclGen = struct { try dg.renderTypecast(writer, ty); try writer.writeAll("){"); - if (ty.unionTagType()) |tag_ty| { + if (ty.unionTagTypeSafety()) |tag_ty| { if (layout.tag_size != 0) { try writer.writeAll(".tag = "); try dg.renderValue(writer, tag_ty, union_obj.tag, location); @@ -858,7 +858,7 @@ pub const DeclGen = struct { try writer.print(".{ } = ", .{fmtIdent(field_name)}); try dg.renderValue(writer, field_ty, union_obj.val, location); } - if (ty.unionTagType()) |_| { + if (ty.unionTagTypeSafety()) |_| { try writer.writeAll("}"); } try writer.writeAll("}"); @@ -1110,7 +1110,7 @@ pub const DeclGen = struct { defer buffer.deinit(); try buffer.appendSlice("typedef "); - if (t.unionTagType()) |tag_ty| { + if (t.unionTagTypeSafety()) |tag_ty| { const name: CValue = .{ .bytes = "tag" }; try buffer.appendSlice("struct {\n "); if (layout.tag_size != 0) { @@ -1134,7 +1134,7 @@ pub const DeclGen = struct { } try buffer.appendSlice("} "); - if (t.unionTagType()) |_| { + if (t.unionTagTypeSafety()) |_| { try buffer.appendSlice("payload;\n} "); } @@ -3368,7 +3368,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const fields = struct_ty.unionFields(); field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; @@ -3383,7 +3383,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc }, else => unreachable, } - const payload = if (struct_ty.tag() == .union_tagged) "payload." else ""; + const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else ""; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); @@ -3415,7 +3415,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { defer buf.deinit(); const field_name = switch (struct_ty.tag()) { .@"struct" => struct_ty.structFields().keys()[extra.field_index], - .@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index], + .@"union", .union_safety_tagged, .union_tagged => struct_ty.unionFields().keys()[extra.field_index], .tuple, .anon_struct => blk: { const tuple = struct_ty.tupleFields(); if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none; @@ -3425,7 +3425,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { }, else => unreachable, }; - const payload = if (struct_ty.tag() == .union_tagged) "payload." else ""; + const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else ""; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6966d8b164..063e2ee0a2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3404,7 +3404,7 @@ pub const DeclGen = struct { if (layout.payload_size == 0) { return lowerValue(dg, .{ - .ty = tv.ty.unionTagType().?, + .ty = tv.ty.unionTagTypeSafety().?, .val = tag_and_val.tag, }); } @@ -3446,7 +3446,7 @@ pub const DeclGen = struct { } } const llvm_tag_value = try lowerValue(dg, .{ - .ty = tv.ty.unionTagType().?, + .ty = tv.ty.unionTagTypeSafety().?, .val = tag_and_val.tag, }); var fields: [3]*const llvm.Value = undefined; diff --git a/src/print_zir.zig b/src/print_zir.zig index 96923c6d86..3a10c8c8cb 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -390,6 +390,7 @@ const Writer = struct { .switch_block => try self.writeSwitchBlock(stream, inst), .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, => try self.writePlNodeField(stream, inst), diff --git a/src/type.zig b/src/type.zig index bdddaab070..d6feedcc31 100644 --- a/src/type.zig +++ b/src/type.zig @@ -149,6 +149,7 @@ pub const Type = extern union { => return .Enum, .@"union", + .union_safety_tagged, .union_tagged, .type_info, => return .Union, @@ -902,7 +903,7 @@ pub const Type = extern union { .reduce_op, => unreachable, // needed to resolve the type before now - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const a_union_obj = a.cast(Payload.Union).?.data; const b_union_obj = (b.cast(Payload.Union) orelse return false).data; return a_union_obj == b_union_obj; @@ -1210,7 +1211,7 @@ pub const Type = extern union { .reduce_op, => unreachable, // needed to resolve the type before now - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj: *const Module.Union = ty.cast(Payload.Union).?.data; std.hash.autoHash(hasher, std.builtin.TypeId.Union); std.hash.autoHash(hasher, union_obj); @@ -1479,7 +1480,7 @@ pub const Type = extern union { .error_set_single => return self.copyPayloadShallow(allocator, Payload.Name), .empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope), .@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct), - .@"union", .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union), + .@"union", .union_safety_tagged, .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union), .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple), .enum_numbered => return self.copyPayloadShallow(allocator, Payload.EnumNumbered), .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull), @@ -1603,7 +1604,7 @@ pub const Type = extern union { @tagName(t), struct_obj.owner_decl, }); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return writer.print("({s} decl={d})", .{ @tagName(t), union_obj.owner_decl, @@ -1989,7 +1990,7 @@ pub const Type = extern union { const decl = mod.declPtr(struct_obj.owner_decl); try decl.renderFullyQualifiedName(mod, writer); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; const decl = mod.declPtr(union_obj.owner_decl); try decl.renderFullyQualifiedName(mod, writer); @@ -2485,8 +2486,8 @@ pub const Type = extern union { return false; } }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { return true; } @@ -2644,7 +2645,7 @@ pub const Type = extern union { .optional => ty.isPtrLikeOptional(), .@"struct" => ty.castTag(.@"struct").?.data.layout != .Auto, - .@"union" => ty.castTag(.@"union").?.data.layout != .Auto, + .@"union", .union_safety_tagged => ty.cast(Payload.Union).?.data.layout != .Auto, .union_tagged => false, }; } @@ -3050,11 +3051,10 @@ pub const Type = extern union { }, .@"union" => { const union_obj = ty.castTag(.@"union").?.data; - // TODO pass `true` for have_tag when unions have a safety tag return abiAlignmentAdvancedUnion(ty, target, strat, union_obj, false); }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; return abiAlignmentAdvancedUnion(ty, target, strat, union_obj, true); }, @@ -3232,11 +3232,10 @@ pub const Type = extern union { }, .@"union" => { const union_obj = ty.castTag(.@"union").?.data; - // TODO pass `true` for have_tag when unions have a safety tag return abiSizeAdvancedUnion(ty, target, strat, union_obj, false); }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; return abiSizeAdvancedUnion(ty, target, strat, union_obj, true); }, @@ -3526,7 +3525,7 @@ pub const Type = extern union { return try bitSizeAdvanced(int_tag_ty, target, sema_kit); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { if (sema_kit) |sk| _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); const union_obj = ty.cast(Payload.Union).?.data; assert(union_obj.haveFieldTypes()); @@ -4194,6 +4193,33 @@ pub const Type = extern union { }; } + /// Same as `unionTagType` but includes safety tag. + /// Codegen should use this version. + pub fn unionTagTypeSafety(ty: Type) ?Type { + return switch (ty.tag()) { + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; + assert(union_obj.haveFieldTypes()); + return union_obj.tag_ty; + }, + + .atomic_order, + .atomic_rmw_op, + .calling_convention, + .address_space, + .float_mode, + .reduce_op, + .call_options, + .prefetch_options, + .export_options, + .extern_options, + .type_info, + => unreachable, // needed to call resolveTypeFields first + + else => null, + }; + } + /// Asserts the type is a union; returns the tag type, even if the tag will /// not be stored at runtime. pub fn unionTagTypeHypothetical(ty: Type) Type { @@ -4225,8 +4251,8 @@ pub const Type = extern union { const union_obj = ty.castTag(.@"union").?.data; return union_obj.getLayout(target, false); }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; return union_obj.getLayout(target, true); }, else => unreachable, @@ -4238,6 +4264,7 @@ pub const Type = extern union { .tuple, .empty_struct_literal, .anon_struct => .Auto, .@"struct" => ty.castTag(.@"struct").?.data.layout, .@"union" => ty.castTag(.@"union").?.data.layout, + .union_safety_tagged => ty.castTag(.union_safety_tagged).?.data.layout, .union_tagged => ty.castTag(.union_tagged).?.data.layout, else => unreachable, }; @@ -4936,7 +4963,7 @@ pub const Type = extern union { return null; } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; const tag_val = union_obj.tag_ty.onePossibleValue() orelse return null; const only_field = union_obj.fields.values()[0]; @@ -5114,7 +5141,7 @@ pub const Type = extern union { } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; switch (union_obj.requires_comptime) { .wip, .unknown => unreachable, // This function asserts types already resolved. @@ -5167,6 +5194,7 @@ pub const Type = extern union { .empty_struct => self.castTag(.empty_struct).?.data, .@"opaque" => &self.castTag(.@"opaque").?.data.namespace, .@"union" => &self.castTag(.@"union").?.data.namespace, + .union_safety_tagged => &self.castTag(.union_safety_tagged).?.data.namespace, .union_tagged => &self.castTag(.union_tagged).?.data.namespace, else => null, @@ -5439,7 +5467,7 @@ pub const Type = extern union { const struct_obj = ty.castTag(.@"struct").?.data; return struct_obj.fields.values()[index].ty; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.fields.values()[index].ty; }, @@ -5456,7 +5484,7 @@ pub const Type = extern union { assert(struct_obj.layout != .Packed); return struct_obj.fields.values()[index].normalAlignment(target); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.fields.values()[index].normalAlignment(target); }, @@ -5619,8 +5647,8 @@ pub const Type = extern union { }, .@"union" => return 0, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; const layout = union_obj.getLayout(target, true); if (layout.tag_align >= layout.payload_align) { // {Tag, Payload} @@ -5660,7 +5688,7 @@ pub const Type = extern union { const error_set = ty.castTag(.error_set).?.data; return error_set.srcLoc(mod); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.srcLoc(mod); }, @@ -5704,7 +5732,7 @@ pub const Type = extern union { const error_set = ty.castTag(.error_set).?.data; return error_set.owner_decl; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.owner_decl; }, @@ -5748,7 +5776,7 @@ pub const Type = extern union { const error_set = ty.castTag(.error_set).?.data; return error_set.node_offset; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.node_offset; }, @@ -5893,6 +5921,7 @@ pub const Type = extern union { @"opaque", @"struct", @"union", + union_safety_tagged, union_tagged, enum_simple, enum_numbered, @@ -6009,7 +6038,7 @@ pub const Type = extern union { .error_set_single => Payload.Name, .@"opaque" => Payload.Opaque, .@"struct" => Payload.Struct, - .@"union", .union_tagged => Payload.Union, + .@"union", .union_safety_tagged, .union_tagged => Payload.Union, .enum_full, .enum_nonexhaustive => Payload.EnumFull, .enum_simple => Payload.EnumSimple, .enum_numbered => Payload.EnumNumbered, diff --git a/test/behavior/bugs/1381.zig b/test/behavior/bugs/1381.zig index ae80132cc1..002d217434 100644 --- a/test/behavior/bugs/1381.zig +++ b/test/behavior/bugs/1381.zig @@ -12,8 +12,10 @@ const A = union(enum) { }; test "union that needs padding bytes inside an array" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; var as = [_]A{ A{ .B = B{ .D = 1 } }, diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 22d09a066d..8fac5697ec 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -998,6 +998,9 @@ test "tuple element initialized with fn call" { } test "struct with union field" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + const Value = struct { ref: u32 = 2, kind: union(enum) { diff --git a/test/behavior/type.zig b/test/behavior/type.zig index f4a32e5df0..16a79a5122 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -412,7 +412,7 @@ test "Type.Union" { const Untagged = @Type(.{ .Union = .{ - .layout = .Auto, + .layout = .Extern, .tag_type = null, .fields = &.{ .{ .name = "int", .field_type = i32, .alignment = @alignOf(f32) }, diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 1787a9f30e..8e4b262565 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -37,6 +37,7 @@ test "init union with runtime value - floats" { test "basic unions" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO var foo = Foo{ .int = 1 }; try expect(foo.int == 1); @@ -430,9 +431,11 @@ const Foo1 = union(enum) { var glbl: Foo1 = undefined; test "global union with single field is correctly initialized" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO glbl = Foo1{ .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, @@ -473,8 +476,11 @@ test "update the tag value for zero-sized unions" { } test "union initializer generates padding only if needed" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const U = union(enum) { A: u24, @@ -747,9 +753,11 @@ fn Setter(attr: Attribute) type { } test "return union init with void payload" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { fn entry() !void { @@ -775,6 +783,7 @@ test "@unionInit stored to a const" { 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 + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const U = union(enum) { @@ -937,6 +946,7 @@ test "cast from anonymous struct to union" { 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 + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const U = union(enum) { @@ -969,6 +979,7 @@ test "cast from pointer to anonymous struct to pointer to union" { 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 + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const U = union(enum) { @@ -1104,6 +1115,8 @@ test "union enum type gets a separate scope" { test "global variable struct contains union initialized to non-most-aligned field" { 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 T = struct { const U = union(enum) { diff --git a/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig b/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig index dd69531b01..03f7ad0a8f 100644 --- a/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig +++ b/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig @@ -13,5 +13,4 @@ export fn entry() void { // backend=stage2 // target=native // -// :9:14: error: expected type 'type', found 'tmp.U' -// :1:11: note: union declared here +// :9:8: error: use of undefined value here causes undefined behavior diff --git a/test/cases/safety/bad union field access.zig b/test/cases/safety/bad union field access.zig index 7adf48abf2..bdde8d9f3a 100644 --- a/test/cases/safety/bad union field access.zig +++ b/test/cases/safety/bad union field access.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "access of inactive union field")) { + std.process.exit(0); + } + std.process.exit(1); } const Foo = union { @@ -21,5 +23,5 @@ fn bar(f: *Foo) void { f.float = 12.34; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From 711b656773fe1d3840c74a4ea1e526ae9bf589fb Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 23:16:18 +0300 Subject: [PATCH 07/17] Sema: `@floatToInt` safety --- src/Sema.zig | 13 ++++++++++++- test/behavior/cast.zig | 3 +++ ...loatToInt cannot fit - negative out of range.zig | 10 ++++++---- ...floatToInt cannot fit - negative to unsigned.zig | 10 ++++++---- ...loatToInt cannot fit - positive out of range.zig | 10 ++++++---- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 22b12637f2..a8b58ebe07 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -15938,7 +15938,16 @@ fn zirFloatToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! } try sema.requireRuntimeBlock(block, inst_data.src(), operand_src); - return block.addTyOp(.float_to_int, dest_ty, operand); + const result = try block.addTyOp(.float_to_int, dest_ty, operand); + if (block.wantSafety()) { + const back = try block.addTyOp(.int_to_float, operand_ty, result); + const diff = try block.addBinOp(.sub, operand, back); + const ok_pos = try block.addBinOp(.cmp_lt, diff, try sema.addConstant(operand_ty, Value.one)); + const ok_neg = try block.addBinOp(.cmp_gt, diff, try sema.addConstant(operand_ty, Value.negative_one)); + const ok = try block.addBinOp(.bool_and, ok_pos, ok_neg); + try sema.addSafetyCheck(block, ok, .integer_part_out_of_bounds); + } + return result; } fn zirIntToFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -18924,6 +18933,7 @@ pub const PanicId = enum { exact_division_remainder, /// TODO make this call `std.builtin.panicInactiveUnionField`. inactive_union_field, + integer_part_out_of_bounds, }; fn addSafetyCheck( @@ -19147,6 +19157,7 @@ fn safetyPanic( .remainder_division_zero_negative => "remainder division by zero or negative value", .exact_division_remainder => "exact division produced remainder", .inactive_union_field => "access of inactive union field", + .integer_part_out_of_bounds => "integer part of floating point value out of bounds", }; const msg_inst = msg_inst: { diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 7064b5abdc..e899def4aa 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -127,6 +127,7 @@ test "@intToFloat(f80)" { } fn testIntToFloat(comptime Int: type, k: Int) !void { + @setRuntimeSafety(false); // TODO const f = @intToFloat(f80, k); const i = @floatToInt(Int, f); try expect(i == k); @@ -151,6 +152,8 @@ test "@intToFloat(f80)" { test "@floatToInt" { 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; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO try testFloatToInts(); comptime try testFloatToInts(); diff --git a/test/cases/safety/@floatToInt cannot fit - negative out of range.zig b/test/cases/safety/@floatToInt cannot fit - negative out of range.zig index f6b2d632f2..937e77a173 100644 --- a/test/cases/safety/@floatToInt cannot fit - negative out of range.zig +++ b/test/cases/safety/@floatToInt cannot fit - negative out of range.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { baz(bar(-129.1)); @@ -14,5 +16,5 @@ fn bar(a: f32) i8 { } fn baz(_: i8) void { } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/@floatToInt cannot fit - negative to unsigned.zig b/test/cases/safety/@floatToInt cannot fit - negative to unsigned.zig index 22e9def050..65da9b2da5 100644 --- a/test/cases/safety/@floatToInt cannot fit - negative to unsigned.zig +++ b/test/cases/safety/@floatToInt cannot fit - negative to unsigned.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { baz(bar(-1.1)); @@ -14,5 +16,5 @@ fn bar(a: f32) u8 { } fn baz(_: u8) void { } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/@floatToInt cannot fit - positive out of range.zig b/test/cases/safety/@floatToInt cannot fit - positive out of range.zig index 67fecde115..e7abf0cf63 100644 --- a/test/cases/safety/@floatToInt cannot fit - positive out of range.zig +++ b/test/cases/safety/@floatToInt cannot fit - positive out of range.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { baz(bar(256.2)); @@ -14,5 +16,5 @@ fn bar(a: f32) u8 { } fn baz(_: u8) void { } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From cf87026e52f7faa090c5fa922d5649f5ec2f1831 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 16 Jul 2022 23:46:24 +0300 Subject: [PATCH 08/17] Sema: `@alignCast` safety --- src/Sema.zig | 32 +++++++++++++++++-- .../obj => }/bad_alignCast_at_comptime.zig | 4 +-- test/cases/safety/@alignCast misaligned.zig | 10 +++--- 3 files changed, 38 insertions(+), 8 deletions(-) rename test/cases/compile_errors/{stage1/obj => }/bad_alignCast_at_comptime.zig (62%) diff --git a/src/Sema.zig b/src/Sema.zig index a8b58ebe07..b456c2ec6a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16278,8 +16278,6 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A // TODO compile error if the result pointer is comptime known and would have an // alignment that disagrees with the Decl's alignment. - // TODO insert safety check that the alignment is correct - const ptr_info = ptr_ty.ptrInfo().data; const dest_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = ptr_info.pointee_type, @@ -16290,6 +16288,36 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A .@"volatile" = ptr_info.@"volatile", .size = ptr_info.size, }); + + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |val| { + if (try val.getUnsignedIntAdvanced(sema.mod.getTarget(), null)) |addr| { + if (addr % dest_align != 0) { + return sema.fail(block, ptr_src, "pointer address 0x{X} is not aligned to {d} bytes", .{ addr, dest_align }); + } + } + return sema.addConstant(dest_ty, val); + } + + try sema.requireRuntimeBlock(block, inst_data.src(), ptr_src); + if (block.wantSafety() and dest_align > 1) { + const val_payload = try sema.arena.create(Value.Payload.U64); + val_payload.* = .{ + .base = .{ .tag = .int_u64 }, + .data = dest_align - 1, + }; + const align_minus_1 = try sema.addConstant( + Type.usize, + Value.initPayload(&val_payload.base), + ); + const actual_ptr = if (ptr_ty.isSlice()) + try sema.analyzeSlicePtr(block, ptr_src, ptr, ptr_ty) + else + ptr; + const ptr_int = try block.addUnOp(.ptrtoint, actual_ptr); + const remainder = try block.addBinOp(.bit_and, ptr_int, align_minus_1); + const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize); + try sema.addSafetyCheck(block, is_aligned, .incorrect_alignment); + } return sema.coerceCompatiblePtrs(block, dest_ty, ptr, ptr_src); } diff --git a/test/cases/compile_errors/stage1/obj/bad_alignCast_at_comptime.zig b/test/cases/compile_errors/bad_alignCast_at_comptime.zig similarity index 62% rename from test/cases/compile_errors/stage1/obj/bad_alignCast_at_comptime.zig rename to test/cases/compile_errors/bad_alignCast_at_comptime.zig index f1ed25e191..0d2d91ece3 100644 --- a/test/cases/compile_errors/stage1/obj/bad_alignCast_at_comptime.zig +++ b/test/cases/compile_errors/bad_alignCast_at_comptime.zig @@ -5,7 +5,7 @@ comptime { } // error -// backend=stage1 +// backend=stage2 // target=native // -// tmp.zig:3:35: error: pointer address 0x1 is not aligned to 4 bytes +// :3:35: error: pointer address 0x1 is not aligned to 4 bytes diff --git a/test/cases/safety/@alignCast misaligned.zig b/test/cases/safety/@alignCast misaligned.zig index 2708d63e6f..e9530e83fe 100644 --- a/test/cases/safety/@alignCast misaligned.zig +++ b/test/cases/safety/@alignCast misaligned.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "incorrect alignment")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -18,5 +20,5 @@ fn foo(bytes: []u8) u32 { return int_slice[0]; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From 9465906775620a0832b19370b42ef2b193b96356 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 19 Jul 2022 14:36:31 +0300 Subject: [PATCH 09/17] Sema: return `.comptime_field_ptr`s for tuples --- src/Sema.zig | 49 ++++++++++++++----- .../invalid_store_to_comptime_field.zig | 9 ++++ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index b456c2ec6a..6d684781c1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3659,7 +3659,10 @@ fn validateStructInit( const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; struct_ptr_zir_ref = field_ptr_extra.lhs; const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); - const field_index = try sema.structFieldIndex(block, struct_ty, field_name, field_src); + const field_index = if (struct_ty.isTuple()) + try sema.tupleFieldIndex(block, struct_ty, field_name, field_src) + else + try sema.structFieldIndex(block, struct_ty, field_name, field_src); if (found_fields[field_index] != 0) { const other_field_ptr = found_fields[field_index]; const other_field_ptr_data = sema.code.instructions.items(.data)[other_field_ptr].pl_node; @@ -3701,7 +3704,7 @@ 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); + const default_field_ptr = 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); @@ -3859,7 +3862,7 @@ 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); + const default_field_ptr = 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); @@ -14494,7 +14497,10 @@ fn zirStructInit( const field_src: LazySrcLoc = .{ .node_offset_initializer = field_type_data.src_node }; const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); - const field_index = try sema.structFieldIndex(block, resolved_ty, field_name, field_src); + const field_index = if (resolved_ty.isTuple()) + try sema.tupleFieldIndex(block, resolved_ty, field_name, field_src) + else + try sema.structFieldIndex(block, resolved_ty, field_name, field_src); if (field_inits[field_index] != .none) { const other_field_type = found_fields[field_index]; const other_field_type_data = zir_datas[other_field_type].pl_node; @@ -14649,7 +14655,7 @@ fn finishStructInit( for (field_inits) |field_init, i_usize| { const i = @intCast(u32, i_usize); const field_src = dest_src; - const field_ptr = try sema.structFieldPtrByIndex(block, dest_src, alloc, i, field_src, struct_ty); + const field_ptr = try sema.structFieldPtrByIndex(block, dest_src, alloc, i, field_src, struct_ty, true); try sema.storePtr(block, dest_src, field_ptr, field_init); } @@ -19398,7 +19404,7 @@ fn fieldVal( }, .Struct => if (is_pointer_to) { // Avoid loading the entire struct by fetching a pointer and loading that - const field_ptr = try sema.structFieldPtr(block, src, object, field_name, field_name_src, inner_ty); + const field_ptr = try sema.structFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false); return sema.analyzeLoad(block, src, field_ptr, object_src); } else { return sema.structFieldVal(block, src, object, field_name, field_name_src, inner_ty); @@ -19607,7 +19613,7 @@ fn fieldPtr( try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) else object_ptr; - return sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty); + return sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing); }, .Union => { const inner_ptr = if (is_pointer_to) @@ -19816,6 +19822,7 @@ fn structFieldPtr( field_name: []const u8, field_name_src: LazySrcLoc, unresolved_struct_ty: Type, + initializing: bool, ) CompileError!Air.Inst.Ref { assert(unresolved_struct_ty.zigTypeTag() == .Struct); @@ -19828,10 +19835,10 @@ fn structFieldPtr( return sema.analyzeRef(block, src, len_inst); } const field_index = try sema.tupleFieldIndex(block, struct_ty, field_name, field_name_src); - return sema.tupleFieldPtr(block, src, struct_ptr, field_name_src, field_index); + return sema.tupleFieldPtr(block, src, struct_ptr, field_name_src, field_index, initializing); } else if (struct_ty.isAnonStruct()) { const field_index = try sema.anonStructFieldIndex(block, struct_ty, field_name, field_name_src); - return sema.tupleFieldPtr(block, src, struct_ptr, field_name_src, field_index); + return sema.tupleFieldPtr(block, src, struct_ptr, field_name_src, field_index, initializing); } const struct_obj = struct_ty.castTag(.@"struct").?.data; @@ -19840,7 +19847,7 @@ fn structFieldPtr( return sema.failWithBadStructFieldAccess(block, struct_obj, field_name_src, field_name); const field_index = @intCast(u32, field_index_big); - return sema.structFieldPtrByIndex(block, src, struct_ptr, field_index, field_name_src, struct_ty); + return sema.structFieldPtrByIndex(block, src, struct_ptr, field_index, field_name_src, struct_ty, initializing); } fn structFieldPtrByIndex( @@ -19851,9 +19858,10 @@ fn structFieldPtrByIndex( field_index: u32, field_src: LazySrcLoc, struct_ty: Type, + initializing: bool, ) CompileError!Air.Inst.Ref { if (struct_ty.isAnonStruct()) { - return sema.tupleFieldPtr(block, src, struct_ptr, field_src, field_index); + return sema.tupleFieldPtr(block, src, struct_ptr, field_src, field_index, initializing); } const struct_obj = struct_ty.castTag(.@"struct").?.data; @@ -20050,6 +20058,10 @@ fn tupleFieldValByIndex( return sema.addConstant(field_ty, field_values[field_index]); } + if (tuple_ty.structFieldValueComptime(field_index)) |default_val| { + return sema.addConstant(field_ty, default_val); + } + try sema.requireRuntimeBlock(block, src, null); return block.addStructFieldVal(tuple_byval, field_index, field_ty); } @@ -20250,7 +20262,7 @@ fn elemPtr( // Tuple field access. const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, "tuple field access index must be comptime known"); const index = @intCast(u32, index_val.toUnsignedInt(target)); - return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index); + return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init); }, else => unreachable, } @@ -20353,6 +20365,7 @@ fn tupleFieldPtr( tuple_ptr: Air.Inst.Ref, field_index_src: LazySrcLoc, field_index: u32, + init: bool, ) CompileError!Air.Inst.Ref { const tuple_ptr_ty = sema.typeOf(tuple_ptr); const tuple_ty = tuple_ptr_ty.childType(); @@ -20386,7 +20399,17 @@ fn tupleFieldPtr( ); } - try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_ptr_src); + if (tuple_ty.structFieldValueComptime(field_index)) |default_val| { + const val = try Value.Tag.comptime_field_ptr.create(sema.arena, .{ + .field_ty = field_ty, + .field_val = default_val, + }); + return sema.addConstant(ptr_field_ty, val); + } + + if (!init) { + try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_ptr_src); + } try sema.requireRuntimeBlock(block, tuple_ptr_src, null); return block.addStructFieldPtr(tuple_ptr, field_index, ptr_field_ty); diff --git a/test/cases/compile_errors/invalid_store_to_comptime_field.zig b/test/cases/compile_errors/invalid_store_to_comptime_field.zig index 48743f3ba1..7ae68692d7 100644 --- a/test/cases/compile_errors/invalid_store_to_comptime_field.zig +++ b/test/cases/compile_errors/invalid_store_to_comptime_field.zig @@ -13,9 +13,18 @@ pub export fn entry1() void { var s: S = .{}; s.a = T{ .a = 2, .b = 2 }; } +pub export fn entry2() void { + var list = .{ 1, 2, 3 }; + var list2 = @TypeOf(list){ .@"0" = 1, .@"1" = 2, .@"2" = 3 }; + var list3 = @TypeOf(list){ 1, 2, 4 }; + _ = list2; + _ = list3; +} + // error // target=native // backend=stage2 // // :6:19: error: value stored in comptime field does not match the default value of the field // :14:19: error: value stored in comptime field does not match the default value of the field +// :19:38: error: value stored in comptime field does not match the default value of the field From 585c160c2022d71197a3ce1399818372371c23a4 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 20 Jul 2022 19:47:54 +0300 Subject: [PATCH 10/17] Sema: handle store to comptime field when `ResultLoc == .none` --- src/Sema.zig | 24 ++++++++++++-- .../invalid_store_to_comptime_field.zig | 31 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 6d684781c1..e675690a1e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14515,6 +14515,16 @@ fn zirStructInit( } found_fields[field_index] = item.data.field_type; field_inits[field_index] = try sema.resolveInst(item.data.init); + if (resolved_ty.structFieldValueComptime(field_index)) |default_value| { + const init_val = (try sema.resolveMaybeUndefVal(block, field_src, field_inits[field_index])) orelse { + return sema.failWithNeededComptime(block, field_src, "value stored in comptime field must be comptime known"); + }; + + if (!init_val.eql(default_value, resolved_ty.structFieldType(field_index), sema.mod)) { + // TODO add note showing where default value is provided + return sema.fail(block, field_src, "value stored in comptime field does not match the default value of the field", .{}); + } + } } return sema.finishStructInit(block, src, src, field_inits, resolved_ty, is_ref); @@ -23688,6 +23698,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", .{}); } @@ -23708,12 +23719,19 @@ fn coerceTupleToStruct( try std.fmt.allocPrint(sema.arena, "{d}", .{i}); const field_index = try sema.structFieldIndex(block, struct_ty, field_name, field_src); const field = fields.values()[field_index]; - if (field.is_comptime) { - return sema.fail(block, dest_ty_src, "TODO: implement coercion from tuples to structs when one of the destination struct fields is comptime", .{}); - } 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 (field.is_comptime) { + 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(field.default_val, field.ty, sema.mod)) { + // TODO add note showing where default value is provided + return sema.fail(block, field_src, "value stored in comptime field does not match the default value of the field", .{}); + } + } if (runtime_src == null) { if (try sema.resolveMaybeUndefVal(block, field_src, coerced)) |field_val| { field_vals[field_index] = field_val; diff --git a/test/cases/compile_errors/invalid_store_to_comptime_field.zig b/test/cases/compile_errors/invalid_store_to_comptime_field.zig index 7ae68692d7..63d83c01d1 100644 --- a/test/cases/compile_errors/invalid_store_to_comptime_field.zig +++ b/test/cases/compile_errors/invalid_store_to_comptime_field.zig @@ -20,6 +20,35 @@ pub export fn entry2() void { _ = list2; _ = list3; } +pub export fn entry3() void { + const U = struct { + comptime foo: u32 = 1, + bar: u32, + fn foo(x: @This()) void { + _ = x; + } + }; + _ = U.foo(U{ .foo = 2, .bar = 2 }); +} +pub export fn entry4() void { + const U = struct { + comptime foo: u32 = 1, + bar: u32, + fn foo(x: @This()) void { + _ = x; + } + }; + _ = U.foo(.{ .foo = 2, .bar = 2 }); +} +// pub export fn entry5() void { +// var x: u32 = 15; +// const T = @TypeOf(.{ @as(i32, -1234), @as(u32, 5678), x }); +// const S = struct { +// fn foo(_: T) void {} +// }; +// _ = S.foo(.{ -1234, 5679, x }); +// } + // error // target=native @@ -28,3 +57,5 @@ pub export fn entry2() void { // :6:19: error: value stored in comptime field does not match the default value of the field // :14:19: error: value stored in comptime field does not match the default value of the field // :19:38: error: value stored in comptime field does not match the default value of the field +// :31:19: error: value stored in comptime field does not match the default value of the field +// :41:14: error: value stored in comptime field does not match the default value of the field From d75fa86d7084bd41f68d1cd03763bd7cf2a87052 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 21 Jul 2022 14:40:00 +0300 Subject: [PATCH 11/17] stage2: implement `@setFloatMode` --- lib/compiler_rt/int_to_float_test.zig | 1 + src/Air.zig | 97 +++++++++++++++--- src/Liveness.zig | 48 ++++++++- src/Sema.zig | 142 +++++++++++++++----------- src/arch/aarch64/CodeGen.zig | 24 +++++ src/arch/arm/CodeGen.zig | 24 +++++ src/arch/riscv64/CodeGen.zig | 24 +++++ src/arch/sparc64/CodeGen.zig | 24 +++++ src/arch/wasm/CodeGen.zig | 24 +++++ src/arch/x86_64/CodeGen.zig | 24 +++++ src/codegen/c.zig | 24 +++++ src/codegen/llvm.zig | 133 +++++++++++++++++------- src/codegen/llvm/bindings.zig | 3 + src/print_air.zig | 24 ++++- 14 files changed, 494 insertions(+), 122 deletions(-) diff --git a/lib/compiler_rt/int_to_float_test.zig b/lib/compiler_rt/int_to_float_test.zig index f6eabbf4ba..7d81115755 100644 --- a/lib/compiler_rt/int_to_float_test.zig +++ b/lib/compiler_rt/int_to_float_test.zig @@ -813,6 +813,7 @@ test "conversion to f32" { test "conversion to f80" { if (builtin.zig_backend == .stage1 and builtin.cpu.arch != .x86_64) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/11408 + if (std.debug.runtime_safety) return error.SkipZigTest; const intToFloat = @import("./int_to_float.zig").intToFloat; diff --git a/src/Air.zig b/src/Air.zig index 2b1c718140..2c0c38a2ef 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -38,11 +38,15 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. add, + /// Same as `add` with optimized float mode. + add_optimized, /// Integer addition. Wrapping is defined to be twos complement wrapping. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. addwrap, + /// Same as `addwrap` with optimized float mode. + addwrap_optimized, /// Saturating integer addition. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -53,11 +57,15 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. sub, + /// Same as `sub` with optimized float mode. + sub_optimized, /// Integer subtraction. Wrapping is defined to be twos complement wrapping. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. subwrap, + /// Same as `sub` with optimized float mode. + subwrap_optimized, /// Saturating integer subtraction. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -68,11 +76,15 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. mul, + /// Same as `mul` with optimized float mode. + mul_optimized, /// Integer multiplication. Wrapping is defined to be twos complement wrapping. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. mulwrap, + /// Same as `mulwrap` with optimized float mode. + mulwrap_optimized, /// Saturating integer multiplication. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -83,32 +95,44 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. div_float, + /// Same as `div_float` with optimized float mode. + div_float_optimized, /// Truncating integer or float division. For integers, wrapping 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. div_trunc, + /// Same as `div_trunc` with optimized float mode. + div_trunc_optimized, /// Flooring integer or float division. For integers, wrapping 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. 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. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. div_exact, + /// Same as `div_exact` with optimized float mode. + div_exact_optimized, /// Integer or float remainder division. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. rem, + /// Same as `rem` with optimized float mode. + rem_optimized, /// Integer or float modulus division. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. mod, + /// Same as `mod` with optimized float mode. + mod_optimized, /// Add an offset to a pointer, returning a new pointer. /// The offset is in element type units, not bytes. /// Wrapping is undefined behavior. @@ -293,29 +317,45 @@ pub const Inst = struct { /// LHS of zero. /// Uses the `un_op` field. neg, + /// Same as `neg` with optimized float mode. + neg_optimized, /// `<`. Result type is always bool. /// Uses the `bin_op` field. cmp_lt, + /// Same as `cmp_lt` with optimized float mode. + cmp_lt_optimized, /// `<=`. Result type is always bool. /// Uses the `bin_op` field. cmp_lte, + /// Same as `cmp_lte` with optimized float mode. + cmp_lte_optimized, /// `==`. Result type is always bool. /// Uses the `bin_op` field. cmp_eq, + /// Same as `cmp_eq` with optimized float mode. + cmp_eq_optimized, /// `>=`. Result type is always bool. /// Uses the `bin_op` field. cmp_gte, + /// Same as `cmp_gte` with optimized float mode. + cmp_gte_optimized, /// `>`. Result type is always bool. /// Uses the `bin_op` field. cmp_gt, + /// Same as `cmp_gt` with optimized float mode. + cmp_gt_optimized, /// `!=`. Result type is always bool. /// Uses the `bin_op` field. cmp_neq, + /// Same as `cmp_neq` with optimized float mode. + cmp_neq_optimized, /// Conditional between two vectors. /// Result type is always a vector of bools. /// Uses the `ty_pl` field, payload is `VectorCmp`. cmp_vector, + /// Same as `cmp_vector` with optimized float mode. + cmp_vector_optimized, /// Conditional branch. /// Result type is always noreturn; no instructions in a block follow this one. @@ -553,6 +593,8 @@ pub const Inst = struct { /// Given a float operand, return the integer with the closest mathematical meaning. /// Uses the `ty_op` field. float_to_int, + /// Same as `float_to_int` with optimized float mode. + float_to_int_optimized, /// Given an integer operand, return the float with the closest mathematical meaning. /// Uses the `ty_op` field. int_to_float, @@ -564,6 +606,8 @@ pub const Inst = struct { /// * min, max, add, mul => integer or float /// Uses the `reduce` field. reduce, + /// Same as `reduce` with optimized float mode. + reduce_optimized, /// Given an integer, bool, float, or pointer operand, return a vector with all elements /// equal to the scalar value. /// Uses the `ty_op` field. @@ -676,25 +720,25 @@ pub const Inst = struct { /// Sets the operand as the current error return trace, set_err_return_trace, - pub fn fromCmpOp(op: std.math.CompareOperator) Tag { - return switch (op) { - .lt => .cmp_lt, - .lte => .cmp_lte, - .eq => .cmp_eq, - .gte => .cmp_gte, - .gt => .cmp_gt, - .neq => .cmp_neq, - }; + pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag { + switch (op) { + .lt => return if (optimized) .cmp_lt_optimized else .cmp_lt, + .lte => return if (optimized) .cmp_lte_optimized else .cmp_lte, + .eq => return if (optimized) .cmp_eq_optimized else .cmp_eq, + .gte => return if (optimized) .cmp_gte_optimized else .cmp_gte, + .gt => return if (optimized) .cmp_gt_optimized else .cmp_gt, + .neq => return if (optimized) .cmp_neq_optimized else .cmp_neq, + } } pub fn toCmpOp(tag: Tag) ?std.math.CompareOperator { return switch (tag) { - .cmp_lt => .lt, - .cmp_lte => .lte, - .cmp_eq => .eq, - .cmp_gte => .gte, - .cmp_gt => .gt, - .cmp_neq => .neq, + .cmp_lt, .cmp_lt_optimized => .lt, + .cmp_lte, .cmp_lte_optimized => .lte, + .cmp_eq, .cmp_eq_optimized => .eq, + .cmp_gte, .cmp_gte_optimized => .gte, + .cmp_gt, .cmp_gt_optimized => .gt, + .cmp_neq, .cmp_neq_optimized => .neq, else => null, }; } @@ -959,6 +1003,18 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .max, .bool_and, .bool_or, + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, => return air.typeOf(datas[inst].bin_op.lhs), .sqrt, @@ -976,6 +1032,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .round, .trunc_float, .neg, + .neg_optimized, => return air.typeOf(datas[inst].un_op), .cmp_lt, @@ -984,6 +1041,12 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .cmp_gte, .cmp_gt, .cmp_neq, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, .cmp_lt_errors_len, .is_null, .is_non_null, @@ -1018,6 +1081,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .union_init, .field_parent_ptr, .cmp_vector, + .cmp_vector_optimized, .add_with_overflow, .sub_with_overflow, .mul_with_overflow, @@ -1054,6 +1118,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .struct_field_ptr_index_3, .array_to_slice, .float_to_int, + .float_to_int_optimized, .int_to_float, .splat, .get_union_tag, @@ -1129,7 +1194,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { return ptr_ty.elemType(); }, - .reduce => return air.typeOf(datas[inst].reduce.operand).childType(), + .reduce, .reduce_optimized => return air.typeOf(datas[inst].reduce.operand).childType(), .mul_add => return air.typeOf(datas[inst].pl_op.operand), .select => { diff --git a/src/Liveness.zig b/src/Liveness.zig index e0a60b50fa..435075a411 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -173,6 +173,25 @@ pub fn categorizeOperand( .shr_exact, .min, .max, + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, => { const o = air_datas[inst].bin_op; if (o.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); @@ -239,6 +258,7 @@ pub fn categorizeOperand( .struct_field_ptr_index_3, .array_to_slice, .float_to_int, + .float_to_int_optimized, .int_to_float, .get_union_tag, .clz, @@ -381,12 +401,12 @@ pub fn categorizeOperand( if (extra.b == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none); return .none; }, - .reduce => { + .reduce, .reduce_optimized => { const reduce = air_datas[inst].reduce; if (reduce.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); return .none; }, - .cmp_vector => { + .cmp_vector, .cmp_vector_optimized => { const extra = air.extraData(Air.VectorCmp, air_datas[inst].ty_pl.payload).data; if (extra.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); if (extra.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none); @@ -701,29 +721,47 @@ fn analyzeInst( switch (inst_tags[inst]) { .add, + .add_optimized, .addwrap, + .addwrap_optimized, .add_sat, .sub, + .sub_optimized, .subwrap, + .subwrap_optimized, .sub_sat, .mul, + .mul_optimized, .mulwrap, + .mulwrap_optimized, .mul_sat, .div_float, + .div_float_optimized, .div_trunc, + .div_trunc_optimized, .div_floor, + .div_floor_optimized, .div_exact, + .div_exact_optimized, .rem, + .rem_optimized, .mod, + .mod_optimized, .bit_and, .bit_or, .xor, .cmp_lt, + .cmp_lt_optimized, .cmp_lte, + .cmp_lte_optimized, .cmp_eq, + .cmp_eq_optimized, .cmp_gte, + .cmp_gte_optimized, .cmp_gt, + .cmp_gt_optimized, .cmp_neq, + .cmp_neq_optimized, .bool_and, .bool_or, .store, @@ -794,6 +832,7 @@ fn analyzeInst( .struct_field_ptr_index_3, .array_to_slice, .float_to_int, + .float_to_int_optimized, .int_to_float, .get_union_tag, .clz, @@ -836,6 +875,7 @@ fn analyzeInst( .round, .trunc_float, .neg, + .neg_optimized, .cmp_lt_errors_len, .set_err_return_trace, => { @@ -903,11 +943,11 @@ fn analyzeInst( const extra = a.air.extraData(Air.Shuffle, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.a, extra.b, .none }); }, - .reduce => { + .reduce, .reduce_optimized => { const reduce = inst_datas[inst].reduce; return trackOperands(a, new_set, inst, main_tomb, .{ reduce.operand, .none, .none }); }, - .cmp_vector => { + .cmp_vector, .cmp_vector_optimized => { const extra = a.air.extraData(Air.VectorCmp, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none }); }, diff --git a/src/Sema.zig b/src/Sema.zig index e675690a1e..7aaff7043f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -144,6 +144,9 @@ pub const Block = struct { /// when null, it is determined by build mode, changed by @setRuntimeSafety want_safety: ?bool = null, + /// What mode to generate float operations in, set by @setFloatMode + float_mode: std.builtin.FloatMode = .Strict, + c_import_buf: ?*std.ArrayList(u8) = null, /// type of `err` in `else => |err|` @@ -206,6 +209,7 @@ pub const Block = struct { .runtime_loop = parent.runtime_loop, .runtime_index = parent.runtime_index, .want_safety = parent.want_safety, + .float_mode = parent.float_mode, .c_import_buf = parent.c_import_buf, .switch_else_err_ty = parent.switch_else_err_ty, }; @@ -414,7 +418,7 @@ pub const Block = struct { fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator, vector_ty: Air.Inst.Ref) !Air.Inst.Ref { return block.addInst(.{ - .tag = .cmp_vector, + .tag = if (block.float_mode == .Optimized) .cmp_vector_optimized else .cmp_vector, .data = .{ .ty_pl = .{ .ty = vector_ty, .payload = try block.sema.addExtra(Air.VectorCmp{ @@ -714,10 +718,10 @@ fn analyzeBodyInner( .closure_get => try sema.zirClosureGet(block, inst), .cmp_lt => try sema.zirCmp(block, inst, .lt), .cmp_lte => try sema.zirCmp(block, inst, .lte), - .cmp_eq => try sema.zirCmpEq(block, inst, .eq, .cmp_eq), + .cmp_eq => try sema.zirCmpEq(block, inst, .eq, Air.Inst.Tag.fromCmpOp(.eq, block.float_mode == .Optimized)), .cmp_gte => try sema.zirCmp(block, inst, .gte), .cmp_gt => try sema.zirCmp(block, inst, .gt), - .cmp_neq => try sema.zirCmpEq(block, inst, .neq, .cmp_neq), + .cmp_neq => try sema.zirCmpEq(block, inst, .neq, Air.Inst.Tag.fromCmpOp(.neq, block.float_mode == .Optimized)), .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst), .decl_ref => try sema.zirDeclRef(block, inst), .decl_val => try sema.zirDeclVal(block, inst), @@ -4705,6 +4709,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, .want_safety = parent_block.want_safety, + .float_mode = parent_block.float_mode, }; defer child_block.instructions.deinit(gpa); @@ -5042,13 +5047,7 @@ fn zirSetCold(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!voi fn zirSetFloatMode(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; const src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; - const float_mode = try sema.resolveBuiltinEnum(block, src, extra.operand, "FloatMode", "operand to @setFloatMode must be comptime known"); - switch (float_mode) { - .Strict => return, - .Optimized => { - // TODO implement optimized float mode - }, - } + block.float_mode = try sema.resolveBuiltinEnum(block, src, extra.operand, "FloatMode", "operand to @setFloatMode must be comptime known"); } fn zirSetRuntimeSafety(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -8092,7 +8091,7 @@ fn intCast( const ok = if (is_vector) ok: { const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte, try sema.addType(operand_ty)); const all_in_range = try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = is_in_range, .operation = .And, @@ -8109,7 +8108,7 @@ fn intCast( const ok = if (is_vector) ok: { const is_in_range = try block.addCmpVector(diff, dest_max, .lte, try sema.addType(operand_ty)); const all_in_range = try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = is_in_range, .operation = .And, @@ -8130,7 +8129,7 @@ fn intCast( const zero_inst = try sema.addConstant(operand_ty, zero_val); const is_in_range = try block.addCmpVector(operand, zero_inst, .gte, try sema.addType(operand_ty)); const all_in_range = try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = is_in_range, .operation = .And, @@ -9391,7 +9390,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } else { for (items) |item_ref| { const item = try sema.resolveInst(item_ref); - const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item); + const cmp_ok = try case_block.addBinOp(if (case_block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, operand, item); if (any_ok != .none) { any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok); } else { @@ -9411,12 +9410,12 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError // operand >= first and operand <= last const range_first_ok = try case_block.addBinOp( - .cmp_gte, + if (case_block.float_mode == .Optimized) .cmp_gte_optimized else .cmp_gte, operand, item_first, ); const range_last_ok = try case_block.addBinOp( - .cmp_lte, + if (case_block.float_mode == .Optimized) .cmp_lte_optimized else .cmp_lte, operand, item_last, ); @@ -10023,7 +10022,7 @@ fn zirShl( const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector) try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = ov_bit, .operation = .Or, @@ -10120,7 +10119,7 @@ fn zirShr( const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty)); break :ok try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = eql, .operation = .And, @@ -10719,7 +10718,7 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. return sema.addConstant(rhs_ty, try rhs_val.floatNeg(rhs_ty, sema.arena, target)); } try sema.requireRuntimeBlock(block, src, null); - return block.addUnOp(.neg, rhs); + return block.addUnOp(if (block.float_mode == .Optimized) .neg_optimized else .neg, rhs); } const lhs = if (rhs_ty.zigTypeTag() == .Vector) @@ -11078,6 +11077,7 @@ fn analyzeArithmetic( return casted_lhs; } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .add_optimized else .add; if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { if (is_int) { @@ -11100,8 +11100,8 @@ fn analyzeArithmetic( try sema.floatAdd(lhs_val, rhs_val, resolved_type), ); } - } else break :rs .{ .src = rhs_src, .air_tag = .add }; - } else break :rs .{ .src = lhs_src, .air_tag = .add }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .addwrap => { // Integers only; floats are checked above. @@ -11112,6 +11112,7 @@ fn analyzeArithmetic( return casted_rhs; } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .addwrap_optimized else .addwrap; if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); @@ -11124,8 +11125,8 @@ fn analyzeArithmetic( resolved_type, try sema.numberAddWrap(block, src, lhs_val, rhs_val, resolved_type), ); - } else break :rs .{ .src = lhs_src, .air_tag = .addwrap }; - } else break :rs .{ .src = rhs_src, .air_tag = .addwrap }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; }, .add_sat => { // Integers only; floats are checked above. @@ -11173,6 +11174,7 @@ fn analyzeArithmetic( return casted_lhs; } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .sub_optimized else .sub; if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { if (is_int) { @@ -11195,8 +11197,8 @@ fn analyzeArithmetic( try sema.floatSub(lhs_val, rhs_val, resolved_type), ); } - } else break :rs .{ .src = rhs_src, .air_tag = .sub }; - } else break :rs .{ .src = lhs_src, .air_tag = .sub }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .subwrap => { // Integers only; floats are checked above. @@ -11210,6 +11212,7 @@ fn analyzeArithmetic( return casted_lhs; } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .subwrap_optimized else .subwrap; if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { return sema.addConstUndef(resolved_type); @@ -11219,8 +11222,8 @@ fn analyzeArithmetic( resolved_type, try sema.numberSubWrap(block, src, lhs_val, rhs_val, resolved_type), ); - } else break :rs .{ .src = rhs_src, .air_tag = .subwrap }; - } else break :rs .{ .src = lhs_src, .air_tag = .subwrap }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .sub_sat => { // Integers only; floats are checked above. @@ -11327,14 +11330,14 @@ fn analyzeArithmetic( if (is_int) { break :rs .{ .src = rhs_src, .air_tag = .div_trunc }; } else { - break :rs .{ .src = rhs_src, .air_tag = .div_float }; + 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 = .div_float }; + break :rs .{ .src = lhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float }; } } }, @@ -11373,6 +11376,7 @@ fn analyzeArithmetic( 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()) { @@ -11398,8 +11402,8 @@ fn analyzeArithmetic( try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target), ); } - } else break :rs .{ .src = rhs_src, .air_tag = .div_trunc }; - } else break :rs .{ .src = lhs_src, .air_tag = .div_trunc }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .div_floor => { // For integers: @@ -11436,6 +11440,7 @@ fn analyzeArithmetic( 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()) { @@ -11461,8 +11466,8 @@ fn analyzeArithmetic( try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target), ); } - } else break :rs .{ .src = rhs_src, .air_tag = .div_floor }; - } else break :rs .{ .src = lhs_src, .air_tag = .div_floor }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .div_exact => { // For integers: @@ -11498,6 +11503,7 @@ fn analyzeArithmetic( 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) { @@ -11513,8 +11519,8 @@ fn analyzeArithmetic( try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), ); } - } else break :rs .{ .src = rhs_src, .air_tag = .div_exact }; - } else break :rs .{ .src = lhs_src, .air_tag = .div_exact }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .mul => { // For integers: @@ -11535,6 +11541,7 @@ fn analyzeArithmetic( } } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .mul_optimized else .mul; if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { if (is_int) { @@ -11570,8 +11577,8 @@ fn analyzeArithmetic( try lhs_val.floatMul(rhs_val, resolved_type, sema.arena, target), ); } - } else break :rs .{ .src = lhs_src, .air_tag = .mul }; - } else break :rs .{ .src = rhs_src, .air_tag = .mul }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; }, .mulwrap => { // Integers only; floats are handled above. @@ -11588,6 +11595,7 @@ fn analyzeArithmetic( } } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .mulwrap_optimized else .mulwrap; if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); @@ -11606,8 +11614,8 @@ fn analyzeArithmetic( resolved_type, try lhs_val.numberMulWrap(rhs_val, resolved_type, sema.arena, target), ); - } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap }; - } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; }, .mul_sat => { // Integers only; floats are checked above. @@ -11777,6 +11785,7 @@ fn analyzeArithmetic( return sema.failWithDivideByZero(block, rhs_src); } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .rem_optimized else .rem; if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { return sema.addConstUndef(resolved_type); @@ -11786,8 +11795,8 @@ fn analyzeArithmetic( resolved_type, try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), ); - } else break :rs .{ .src = rhs_src, .air_tag = .rem }; - } else break :rs .{ .src = lhs_src, .air_tag = .rem }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, .mod => { // For integers: @@ -11834,6 +11843,7 @@ fn analyzeArithmetic( return sema.failWithDivideByZero(block, rhs_src); } } + const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .mod_optimized else .mod; if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { return sema.addConstUndef(resolved_type); @@ -11843,8 +11853,8 @@ fn analyzeArithmetic( resolved_type, try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, target), ); - } else break :rs .{ .src = rhs_src, .air_tag = .mod }; - } else break :rs .{ .src = lhs_src, .air_tag = .mod }; + } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; + } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; }, else => unreachable, } @@ -11874,7 +11884,7 @@ fn analyzeArithmetic( const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); const any_ov_bit = if (resolved_type.zigTypeTag() == .Vector) try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = ov_bit, .operation = .Or, @@ -11890,13 +11900,17 @@ fn analyzeArithmetic( } } switch (rs.air_tag) { - .div_float, .div_exact, .div_trunc, .div_floor => { + // 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 = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = ok, .operation = .And, @@ -11904,17 +11918,17 @@ fn analyzeArithmetic( }); } else ok: { const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); - break :ok try block.addBinOp(.cmp_neq, casted_rhs, 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, .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); const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val); const ok = try block.addCmpVector(casted_rhs, zero, if (scalar_tag == .Int) .gt else .neq, try sema.addType(resolved_type)); break :ok try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = ok, .operation = .And, @@ -11922,13 +11936,19 @@ fn analyzeArithmetic( }); } else ok: { const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); - break :ok try block.addBinOp(if (scalar_tag == .Int) .cmp_gt else .cmp_neq, casted_rhs, zero); + const air_tag = if (scalar_tag == .Int) + Air.Inst.Tag.cmp_gt + else if (block.float_mode == .Optimized) + Air.Inst.Tag.cmp_neq_optimized + else + Air.Inst.Tag.cmp_neq; + break :ok try block.addBinOp(air_tag, casted_rhs, zero); }; try sema.addSafetyCheck(block, ok, .remainder_division_zero_negative); }, else => {}, } - if (rs.air_tag == .div_exact) { + 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); @@ -11936,14 +11956,14 @@ fn analyzeArithmetic( if (resolved_type.zigTypeTag() == .Vector) { const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type)); break :ok try block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = eql, .operation = .And, } }, }); } else { - const is_in_range = try block.addBinOp(.cmp_eq, result, floored); + 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: { @@ -11962,7 +11982,7 @@ fn analyzeArithmetic( }); } else { const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); - const is_in_range = try block.addBinOp(.cmp_eq, remainder, 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; } }; @@ -12476,7 +12496,7 @@ fn cmpSelf( const result_ty_ref = try sema.addType(result_ty); return block.addCmpVector(casted_lhs, casted_rhs, op, result_ty_ref); } - const tag = Air.Inst.Tag.fromCmpOp(op); + const tag = Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized); return block.addBinOp(tag, casted_lhs, casted_rhs); } @@ -15954,12 +15974,12 @@ fn zirFloatToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! } try sema.requireRuntimeBlock(block, inst_data.src(), operand_src); - const result = try block.addTyOp(.float_to_int, dest_ty, operand); + const result = try block.addTyOp(if (block.float_mode == .Optimized) .float_to_int_optimized else .float_to_int, dest_ty, operand); if (block.wantSafety()) { const back = try block.addTyOp(.int_to_float, operand_ty, result); const diff = try block.addBinOp(.sub, operand, back); - const ok_pos = try block.addBinOp(.cmp_lt, diff, try sema.addConstant(operand_ty, Value.one)); - const ok_neg = try block.addBinOp(.cmp_gt, diff, try sema.addConstant(operand_ty, Value.negative_one)); + const ok_pos = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_lt_optimized else .cmp_lt, diff, try sema.addConstant(operand_ty, Value.one)); + const ok_neg = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_gt_optimized else .cmp_gt, diff, try sema.addConstant(operand_ty, Value.negative_one)); const ok = try block.addBinOp(.bool_and, ok_pos, ok_neg); try sema.addSafetyCheck(block, ok, .integer_part_out_of_bounds); } @@ -17194,7 +17214,7 @@ fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. try sema.requireRuntimeBlock(block, inst_data.src(), operand_src); return block.addInst(.{ - .tag = .reduce, + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ .operand = operand, .operation = operation, @@ -24489,7 +24509,7 @@ fn cmpNumeric( }; const casted_lhs = try sema.coerce(block, dest_ty, lhs, lhs_src); const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src); - return block.addBinOp(Air.Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs); + return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized), casted_lhs, casted_rhs); } // For mixed unsigned integer sizes, implicit cast both operands to the larger integer. // For mixed signed and unsigned integers, implicit cast both operands to a signed @@ -24610,7 +24630,7 @@ fn cmpNumeric( const casted_lhs = try sema.coerce(block, dest_ty, lhs, lhs_src); const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src); - return block.addBinOp(Air.Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs); + return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized), casted_lhs, casted_rhs); } /// Asserts that lhs and rhs types are both vectors. diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index ba7c56e2bd..a8bafee4f8 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -729,6 +729,30 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_err => try self.airWrapErrUnionErr(inst), + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => return self.fail("TODO implement optimized float mode", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 9f914a82fa..8bd589e7ba 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -744,6 +744,30 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_err => try self.airWrapErrUnionErr(inst), + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => return self.fail("TODO implement optimized float mode", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index e52dd4ec08..220fb18699 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -669,6 +669,30 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_err => try self.airWrapErrUnionErr(inst), + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => return self.fail("TODO implement optimized float mode", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 75260156f8..2c6a322fca 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -681,6 +681,30 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"), .wrap_errunion_err => try self.airWrapErrUnionErr(inst), + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => @panic("TODO implement optimized float mode"), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index d2d21925b2..ab73e23783 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1622,6 +1622,30 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .err_return_trace, .set_err_return_trace, => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), + + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => return self.fail("TODO implement optimized float mode", .{}), }; } diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index b35db3e97a..09721c661f 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -751,6 +751,30 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_err => try self.airWrapErrUnionErr(inst), + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => return self.fail("TODO implement optimized float mode", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 98f7792a76..280b7604bf 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1928,6 +1928,30 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .wasm_memory_size => try airWasmMemorySize(f, inst), .wasm_memory_grow => try airWasmMemoryGrow(f, inst), + + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .float_to_int_optimized, + => return f.fail("TODO implement optimized float mode", .{}), // zig fmt: on }; switch (result_value) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 063e2ee0a2..de132132b0 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3984,21 +3984,21 @@ pub const FuncGen = struct { for (body) |inst, i| { const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { // zig fmt: off - .add => try self.airAdd(inst), - .addwrap => try self.airAddWrap(inst), + .add => try self.airAdd(inst, false), + .addwrap => try self.airAddWrap(inst, false), .add_sat => try self.airAddSat(inst), - .sub => try self.airSub(inst), - .subwrap => try self.airSubWrap(inst), + .sub => try self.airSub(inst, false), + .subwrap => try self.airSubWrap(inst, false), .sub_sat => try self.airSubSat(inst), - .mul => try self.airMul(inst), - .mulwrap => try self.airMulWrap(inst), + .mul => try self.airMul(inst, false), + .mulwrap => try self.airMulWrap(inst, false), .mul_sat => try self.airMulSat(inst), - .div_float => try self.airDivFloat(inst), - .div_trunc => try self.airDivTrunc(inst), - .div_floor => try self.airDivFloor(inst), - .div_exact => try self.airDivExact(inst), - .rem => try self.airRem(inst), - .mod => try self.airMod(inst), + .div_float => try self.airDivFloat(inst, false), + .div_trunc => try self.airDivTrunc(inst, false), + .div_floor => try self.airDivFloor(inst, false), + .div_exact => try self.airDivExact(inst, false), + .rem => try self.airRem(inst, false), + .mod => try self.airMod(inst, false), .ptr_add => try self.airPtrAdd(inst), .ptr_sub => try self.airPtrSub(inst), .shl => try self.airShl(inst), @@ -4009,6 +4009,19 @@ pub const FuncGen = struct { .slice => try self.airSlice(inst), .mul_add => try self.airMulAdd(inst), + .add_optimized => try self.airAdd(inst, true), + .addwrap_optimized => try self.airAddWrap(inst, true), + .sub_optimized => try self.airSub(inst, true), + .subwrap_optimized => try self.airSubWrap(inst, true), + .mul_optimized => try self.airMul(inst, true), + .mulwrap_optimized => try self.airMulWrap(inst, true), + .div_float_optimized => try self.airDivFloat(inst, true), + .div_trunc_optimized => try self.airDivTrunc(inst, true), + .div_floor_optimized => try self.airDivFloor(inst, true), + .div_exact_optimized => try self.airDivExact(inst, true), + .rem_optimized => try self.airRem(inst, true), + .mod_optimized => try self.airMod(inst, true), + .add_with_overflow => try self.airOverflow(inst, "llvm.sadd.with.overflow", "llvm.uadd.with.overflow"), .sub_with_overflow => try self.airOverflow(inst, "llvm.ssub.with.overflow", "llvm.usub.with.overflow"), .mul_with_overflow => try self.airOverflow(inst, "llvm.smul.with.overflow", "llvm.umul.with.overflow"), @@ -4034,17 +4047,27 @@ pub const FuncGen = struct { .ceil => try self.airUnaryOp(inst, .ceil), .round => try self.airUnaryOp(inst, .round), .trunc_float => try self.airUnaryOp(inst, .trunc), - .neg => try self.airUnaryOp(inst, .neg), - .cmp_eq => try self.airCmp(inst, .eq), - .cmp_gt => try self.airCmp(inst, .gt), - .cmp_gte => try self.airCmp(inst, .gte), - .cmp_lt => try self.airCmp(inst, .lt), - .cmp_lte => try self.airCmp(inst, .lte), - .cmp_neq => try self.airCmp(inst, .neq), + .neg => try self.airNeg(inst, false), + .neg_optimized => try self.airNeg(inst, true), - .cmp_vector => try self.airCmpVector(inst), - .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), + .cmp_eq => try self.airCmp(inst, .eq, false), + .cmp_gt => try self.airCmp(inst, .gt, false), + .cmp_gte => try self.airCmp(inst, .gte, false), + .cmp_lt => try self.airCmp(inst, .lt, false), + .cmp_lte => try self.airCmp(inst, .lte, false), + .cmp_neq => try self.airCmp(inst, .neq, false), + + .cmp_eq_optimized => try self.airCmp(inst, .eq, true), + .cmp_gt_optimized => try self.airCmp(inst, .gt, true), + .cmp_gte_optimized => try self.airCmp(inst, .gte, true), + .cmp_lt_optimized => try self.airCmp(inst, .lt, true), + .cmp_lte_optimized => try self.airCmp(inst, .lte, true), + .cmp_neq_optimized => try self.airCmp(inst, .neq, true), + + .cmp_vector => try self.airCmpVector(inst, false), + .cmp_vector_optimized => try self.airCmpVector(inst, true), + .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .is_non_null => try self.airIsNonNull(inst, false, .NE), .is_non_null_ptr => try self.airIsNonNull(inst, true , .NE), @@ -4093,8 +4116,10 @@ pub const FuncGen = struct { .ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0), .ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1), + .float_to_int => try self.airFloatToInt(inst, false), + .float_to_int_optimized => try self.airFloatToInt(inst, true), + .array_to_slice => try self.airArrayToSlice(inst), - .float_to_int => try self.airFloatToInt(inst), .int_to_float => try self.airIntToFloat(inst), .cmpxchg_weak => try self.airCmpxchg(inst, true), .cmpxchg_strong => try self.airCmpxchg(inst, false), @@ -4115,11 +4140,13 @@ pub const FuncGen = struct { .splat => try self.airSplat(inst), .select => try self.airSelect(inst), .shuffle => try self.airShuffle(inst), - .reduce => try self.airReduce(inst), .aggregate_init => try self.airAggregateInit(inst), .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), + .reduce => try self.airReduce(inst, false), + .reduce_optimized => try self.airReduce(inst, true), + .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), .atomic_store_release => try self.airAtomicStore(inst, .Release), @@ -4485,8 +4512,9 @@ pub const FuncGen = struct { return null; } - fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value { + fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -4496,8 +4524,9 @@ pub const FuncGen = struct { return self.cmp(lhs, rhs, operand_ty, op); } - fn airCmpVector(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airCmpVector(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.VectorCmp, ty_pl.payload).data; @@ -4943,10 +4972,12 @@ pub const FuncGen = struct { return self.builder.buildCall(libc_fn, ¶ms, params.len, .C, .Auto, ""); } - fn airFloatToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airFloatToInt(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); + const target = self.dg.module.getTarget(); const ty_op = self.air.instructions.items(.data)[inst].ty_op; @@ -6095,8 +6126,9 @@ pub const FuncGen = struct { return self.builder.buildInsertValue(partial, len, 1, ""); } - fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airAdd(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6109,8 +6141,9 @@ pub const FuncGen = struct { return self.builder.buildNUWAdd(lhs, rhs, ""); } - fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6134,8 +6167,9 @@ pub const FuncGen = struct { return self.builder.buildUAddSat(lhs, rhs, ""); } - fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airSub(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6148,8 +6182,9 @@ pub const FuncGen = struct { return self.builder.buildNUWSub(lhs, rhs, ""); } - fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6172,8 +6207,9 @@ pub const FuncGen = struct { return self.builder.buildUSubSat(lhs, rhs, ""); } - fn airMul(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airMul(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6186,8 +6222,9 @@ pub const FuncGen = struct { return self.builder.buildNUWMul(lhs, rhs, ""); } - fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6210,8 +6247,9 @@ pub const FuncGen = struct { return self.builder.buildUMulFixSat(lhs, rhs, ""); } - fn airDivFloat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airDivFloat(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6221,8 +6259,9 @@ pub const FuncGen = struct { return self.buildFloatOp(.div, inst_ty, 2, .{ lhs, rhs }); } - fn airDivTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airDivTrunc(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6238,8 +6277,9 @@ pub const FuncGen = struct { return self.builder.buildUDiv(lhs, rhs, ""); } - fn airDivFloor(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airDivFloor(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6270,8 +6310,9 @@ pub const FuncGen = struct { return self.builder.buildUDiv(lhs, rhs, ""); } - fn airDivExact(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airDivExact(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6284,8 +6325,9 @@ pub const FuncGen = struct { return self.builder.buildExactUDiv(lhs, rhs, ""); } - fn airRem(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airRem(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -6298,8 +6340,9 @@ pub const FuncGen = struct { return self.builder.buildURem(lhs, rhs, ""); } - fn airMod(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airMod(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -7613,6 +7656,17 @@ pub const FuncGen = struct { return self.buildFloatOp(op, operand_ty, 1, .{operand}); } + fn airNeg(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const operand_ty = self.air.typeOf(un_op); + + return self.buildFloatOp(.neg, operand_ty, 1, .{operand}); + } + fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, llvm_fn_name: []const u8) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -7927,8 +7981,9 @@ pub const FuncGen = struct { return self.builder.buildShuffleVector(a, b, llvm_mask_value, ""); } - fn airReduce(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airReduce(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + self.builder.setFastMath(want_fast_math); const reduce = self.air.instructions.items(.data)[inst].reduce; const operand = try self.resolveInst(reduce.operand); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 6cd5e41b10..07408f12b9 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -941,6 +941,9 @@ pub const Builder = opaque { pub const buildFPMulReduce = ZigLLVMBuildFPMulReduce; extern fn ZigLLVMBuildFPMulReduce(B: *const Builder, Acc: *const Value, Val: *const Value) *const Value; + + pub const setFastMath = ZigLLVMSetFastMath; + extern fn ZigLLVMSetFastMath(B: *const Builder, on_state: bool) void; }; pub const MDString = opaque { diff --git a/src/print_air.zig b/src/print_air.zig index a58b27fe2f..ec4a94b420 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -138,6 +138,24 @@ const Writer = struct { .set_union_tag, .min, .max, + .add_optimized, + .addwrap_optimized, + .sub_optimized, + .subwrap_optimized, + .mul_optimized, + .mulwrap_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, => try w.writeBinOp(s, inst), .is_null, @@ -169,6 +187,7 @@ const Writer = struct { .round, .trunc_float, .neg, + .neg_optimized, .cmp_lt_errors_len, .set_err_return_trace, => try w.writeUnOp(s, inst), @@ -216,6 +235,7 @@ const Writer = struct { .int_to_float, .splat, .float_to_int, + .float_to_int_optimized, .get_union_tag, .clz, .ctz, @@ -280,8 +300,8 @@ const Writer = struct { .mul_add => try w.writeMulAdd(s, inst), .select => try w.writeSelect(s, inst), .shuffle => try w.writeShuffle(s, inst), - .reduce => try w.writeReduce(s, inst), - .cmp_vector => try w.writeCmpVector(s, inst), + .reduce, .reduce_optimized => try w.writeReduce(s, inst), + .cmp_vector, .cmp_vector_optimized => try w.writeCmpVector(s, inst), .dbg_block_begin, .dbg_block_end => {}, } From 0ef4cc738b9c023c1128e3767919b446c959955a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 21 Jul 2022 15:57:04 +0300 Subject: [PATCH 12/17] Sema: check for zero length slices in `@alignCast` safety --- src/Sema.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Sema.zig b/src/Sema.zig index 7aaff7043f..fd630e88bf 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16352,7 +16352,12 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const ptr_int = try block.addUnOp(.ptrtoint, actual_ptr); const remainder = try block.addBinOp(.bit_and, ptr_int, align_minus_1); const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize); - try sema.addSafetyCheck(block, is_aligned, .incorrect_alignment); + const ok = if (ptr_ty.isSlice()) ok: { + const len = try sema.analyzeSliceLen(block, ptr_src, ptr); + const len_zero = try block.addBinOp(.cmp_eq, len, try sema.addConstant(Type.usize, Value.zero)); + break :ok try block.addBinOp(.bit_or, len_zero, is_aligned); + } else is_aligned; + try sema.addSafetyCheck(block, ok, .incorrect_alignment); } return sema.coerceCompatiblePtrs(block, dest_ty, ptr, ptr_src); } From 15dddfd84d9007689ef1fa6f4abedb88c570973a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 22 Jul 2022 13:20:18 +0300 Subject: [PATCH 13/17] AstGen: make comptime fields in packed and extern structs compile errors --- src/AstGen.zig | 6 ++++++ src/Sema.zig | 5 +++-- .../invalid_comptime_fields.zig | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 test/cases/compile_errors/invalid_comptime_fields.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index fa1f72670a..6df27d6983 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -4214,6 +4214,12 @@ fn structDeclInner( const have_value = member.ast.value_expr != 0; const is_comptime = member.comptime_token != null; + if (is_comptime and layout == .Packed) { + return astgen.failTok(member.comptime_token.?, "packed struct fields cannot be marked comptime", .{}); + } else if (is_comptime and layout == .Extern) { + return astgen.failTok(member.comptime_token.?, "extern struct fields cannot be marked comptime", .{}); + } + if (!is_comptime) { known_non_opv = known_non_opv or nodeImpliesMoreThanOnePossibleValue(tree, member.ast.type_expr); diff --git a/src/Sema.zig b/src/Sema.zig index fd630e88bf..ecb387ed34 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14509,6 +14509,7 @@ fn zirStructInit( var field_i: u32 = 0; var extra_index = extra.end; + const is_packed = resolved_ty.containerLayout() == .Packed; while (field_i < extra.data.fields_len) : (field_i += 1) { const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index); extra_index = item.end; @@ -14535,7 +14536,7 @@ fn zirStructInit( } found_fields[field_index] = item.data.field_type; field_inits[field_index] = try sema.resolveInst(item.data.init); - if (resolved_ty.structFieldValueComptime(field_index)) |default_value| { + if (!is_packed) if (resolved_ty.structFieldValueComptime(field_index)) |default_value| { const init_val = (try sema.resolveMaybeUndefVal(block, field_src, field_inits[field_index])) orelse { return sema.failWithNeededComptime(block, field_src, "value stored in comptime field must be comptime known"); }; @@ -14544,7 +14545,7 @@ fn zirStructInit( // TODO add note showing where default value is provided return sema.fail(block, field_src, "value stored in comptime field does not match the default value of the field", .{}); } - } + }; } return sema.finishStructInit(block, src, src, field_inits, resolved_ty, is_ref); diff --git a/test/cases/compile_errors/invalid_comptime_fields.zig b/test/cases/compile_errors/invalid_comptime_fields.zig new file mode 100644 index 0000000000..3149a46810 --- /dev/null +++ b/test/cases/compile_errors/invalid_comptime_fields.zig @@ -0,0 +1,21 @@ +const U = union { + comptime a: u32 = 1, +}; +const E = enum { + comptime a = 1, +}; +const P = packed struct { + comptime a: u32 = 1, +}; +const X = extern struct { + comptime a: u32 = 1, +}; + +// error +// backend=stage2 +// target=native +// +// :2:5: error: union fields cannot be marked comptime +// :5:5: error: enum fields cannot be marked comptime +// :8:5: error: packed struct fields cannot be marked comptime +// :11:5: error: extern struct fields cannot be marked comptime From 881c0cb20b8cbde252ab38dff2c76886c4b72f1d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 22 Jul 2022 17:15:15 +0300 Subject: [PATCH 14/17] Sema: add default value here note to invalid comptime field store error --- src/Module.zig | 16 ++++++++++++++++ src/Sema.zig | 26 +++++++++++++++++++++----- src/type.zig | 6 +++++- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index da50fd9ae7..9c4ae69fe6 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2704,6 +2704,18 @@ pub const SrcLoc = struct { else => unreachable, } }, + .node_offset_field_default => |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); + + const full: Ast.full.ContainerField = switch (node_tags[parent_node]) { + .container_field => tree.containerField(parent_node), + .container_field_init => tree.containerFieldInit(parent_node), + else => unreachable, + }; + return nodeToSpan(tree, full.ast.value_expr); + }, } } @@ -3021,6 +3033,9 @@ pub const LazySrcLoc = union(enum) { /// The source location points to the tag type of an union or an enum. /// The Decl is determined contextually. node_offset_container_tag: i32, + /// The source location points to the default value of a field. + /// The Decl is determined contextually. + node_offset_field_default: i32, pub const nodeOffset = if (TracedOffset.want_tracing) nodeOffsetDebug else nodeOffsetRelease; @@ -3098,6 +3113,7 @@ pub const LazySrcLoc = union(enum) { .node_offset_ptr_bitoffset, .node_offset_ptr_hostsize, .node_offset_container_tag, + .node_offset_field_default, => .{ .file_scope = decl.getFileScope(), .parent_decl_node = decl.src_node, diff --git a/src/Sema.zig b/src/Sema.zig index ecb387ed34..1ac4658658 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1789,6 +1789,24 @@ fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: }); } +fn failWithInvalidComptimeFieldStore(sema: *Sema, block: *Block, init_src: LazySrcLoc, container_ty: Type, field_index: usize) CompileError { + const msg = msg: { + const msg = try sema.errMsg(block, init_src, "value stored in comptime field does not match the default value of the field", .{}); + errdefer msg.destroy(sema.gpa); + + const decl_index = container_ty.getOwnerDeclOrNull() orelse break :msg msg; + + const tree = try sema.getAstTree(block); + const decl = sema.mod.declPtr(decl_index); + const field_src = enumFieldSrcLoc(decl, tree.*, container_ty.getNodeOffset(), field_index); + const default_value_src: LazySrcLoc = .{ .node_offset_field_default = field_src.node_offset.x }; + + try sema.errNote(block, default_value_src, msg, "default value set here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); +} + /// We don't return a pointer to the new error note because the pointer /// becomes invalid when you add another one. fn errNote( @@ -14542,8 +14560,7 @@ fn zirStructInit( }; if (!init_val.eql(default_value, resolved_ty.structFieldType(field_index), sema.mod)) { - // TODO add note showing where default value is provided - return sema.fail(block, field_src, "value stored in comptime field does not match the default value of the field", .{}); + return sema.failWithInvalidComptimeFieldStore(block, field_src, resolved_ty, field_index); } }; } @@ -22379,7 +22396,7 @@ fn storePtrVal( .direct => |val_ptr| { if (mut_kit.decl_ref_mut.runtime_index == .comptime_field_ptr) { if (!operand_val.eql(val_ptr.*, operand_ty, sema.mod)) { - // TODO add note showing where default value is provided + // TODO use failWithInvalidComptimeFieldStore return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{}); } return; @@ -23754,8 +23771,7 @@ fn coerceTupleToStruct( }; if (!init_val.eql(field.default_val, field.ty, sema.mod)) { - // TODO add note showing where default value is provided - return sema.fail(block, field_src, "value stored in comptime field does not match the default value of the field", .{}); + return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, i); } } if (runtime_src == null) { diff --git a/src/type.zig b/src/type.zig index d6feedcc31..6750ec724b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -5714,6 +5714,10 @@ pub const Type = extern union { } pub fn getOwnerDecl(ty: Type) Module.Decl.Index { + return ty.getOwnerDeclOrNull() orelse unreachable; + } + + pub fn getOwnerDeclOrNull(ty: Type) ?Module.Decl.Index { switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { const enum_full = ty.cast(Payload.EnumFull).?.data; @@ -5753,7 +5757,7 @@ pub const Type = extern union { .type_info, => unreachable, // These need to be resolved earlier. - else => unreachable, + else => return null, } } From 5b29275240a57448514fe5c5d9e8edae3b2362cc Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 21 Jul 2022 21:22:09 +0300 Subject: [PATCH 15/17] Sema: add some more 'declared here' notes --- src/Sema.zig | 39 +++++++------------ .../hello_world_with_updates.0.zig | 1 + .../compile_errors/bogus_compile_var.zig | 1 + .../bogus_method_call_on_slice.zig | 2 +- ...um_literal_to_enum_but_it_doesnt_match.zig | 2 +- ...hod_call_with_first_arg_type_primitive.zig | 3 +- ...ll_with_first_arg_type_wrong_container.zig | 3 +- .../stage2/union_extra_field.zig | 2 +- .../hello_world_with_updates.0.zig | 1 + .../hello_world_with_updates.0.zig | 1 + test/stage2/cbe.zig | 2 +- 11 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 1ac4658658..b2ba53c3e1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6814,12 +6814,7 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A .{ dest_ty.fmt(sema.mod), int_val.fmtValue(sema.typeOf(operand), sema.mod) }, ); errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy( - dest_ty.declSrcLoc(sema.mod), - msg, - "enum declared here", - .{}, - ); + try sema.addDeclaredHereNote(msg, dest_ty); break :msg msg; }; return sema.failWithOwnedErrorMsg(block, msg); @@ -19432,16 +19427,7 @@ fn fieldVal( return inst; } } - // TODO add note: declared here - const kw_name = switch (child_type.zigTypeTag()) { - .Struct => "struct", - .Opaque => "opaque", - .Union => "union", - else => unreachable, - }; - return sema.fail(block, src, "{s} '{}' has no member named '{s}'", .{ - kw_name, child_type.fmt(sema.mod), field_name, - }); + return sema.failWithBadMemberAccess(block, child_type, src, field_name); }, else => { const msg = msg: { @@ -19783,7 +19769,13 @@ fn fieldCallBind( else => {}, } - return sema.fail(block, src, "type '{}' has no field or member function named '{s}'", .{ concrete_ty.fmt(sema.mod), field_name }); + const msg = msg: { + const msg = try sema.errMsg(block, src, "no field or member function named '{s}' in '{}'", .{ field_name, concrete_ty.fmt(sema.mod) }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, concrete_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); } fn finishFieldCallBind( @@ -21176,16 +21168,11 @@ fn coerceExtra( const msg = try sema.errMsg( block, inst_src, - "enum '{}' has no field named '{s}'", - .{ dest_ty.fmt(sema.mod), bytes }, + "no field named '{s}' in enum '{}'", + .{ bytes, dest_ty.fmt(sema.mod) }, ); errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy( - dest_ty.declSrcLoc(sema.mod), - msg, - "enum declared here", - .{}, - ); + try sema.addDeclaredHereNote(msg, dest_ty); break :msg msg; }; return sema.failWithOwnedErrorMsg(block, msg); @@ -26230,7 +26217,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { const msg = msg: { const tree = try sema.getAstTree(&block_scope); const field_src = enumFieldSrcLoc(decl, tree.*, union_obj.node_offset, field_i); - const msg = try sema.errMsg(&block_scope, field_src, "enum '{}' has no field named '{s}'", .{ union_obj.tag_ty.fmt(sema.mod), field_name }); + const msg = try sema.errMsg(&block_scope, field_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; diff --git a/test/cases/aarch64-macos/hello_world_with_updates.0.zig b/test/cases/aarch64-macos/hello_world_with_updates.0.zig index 300baac1a4..0de742bdec 100644 --- a/test/cases/aarch64-macos/hello_world_with_updates.0.zig +++ b/test/cases/aarch64-macos/hello_world_with_updates.0.zig @@ -3,3 +3,4 @@ // target=aarch64-macos // // :107:9: error: struct 'tmp.tmp' has no member named 'main' +// :7:1: note: struct declared here diff --git a/test/cases/compile_errors/bogus_compile_var.zig b/test/cases/compile_errors/bogus_compile_var.zig index 28d8b1dba5..b675fd941c 100644 --- a/test/cases/compile_errors/bogus_compile_var.zig +++ b/test/cases/compile_errors/bogus_compile_var.zig @@ -6,3 +6,4 @@ export fn entry() usize { return @sizeOf(@TypeOf(x)); } // target=native // // :1:29: error: struct 'builtin.builtin' has no member named 'bogus' +// :1:1: note: struct declared here diff --git a/test/cases/compile_errors/bogus_method_call_on_slice.zig b/test/cases/compile_errors/bogus_method_call_on_slice.zig index 5471871a23..b5cb5e472a 100644 --- a/test/cases/compile_errors/bogus_method_call_on_slice.zig +++ b/test/cases/compile_errors/bogus_method_call_on_slice.zig @@ -8,4 +8,4 @@ export fn entry() usize { return @sizeOf(@TypeOf(&f)); } // backend=stage2 // target=native // -// :3:6: error: type '[]const u8' has no field or member function named 'copy' +// :3:6: error: no field or member function named 'copy' in '[]const u8' diff --git a/test/cases/compile_errors/cast_enum_literal_to_enum_but_it_doesnt_match.zig b/test/cases/compile_errors/cast_enum_literal_to_enum_but_it_doesnt_match.zig index 136bf612bf..bd89dc402a 100644 --- a/test/cases/compile_errors/cast_enum_literal_to_enum_but_it_doesnt_match.zig +++ b/test/cases/compile_errors/cast_enum_literal_to_enum_but_it_doesnt_match.zig @@ -11,5 +11,5 @@ export fn entry() void { // backend=stage2 // target=native // -// :6:21: error: enum 'tmp.Foo' has no field named 'c' +// :6:21: error: no field named 'c' in enum 'tmp.Foo' // :1:13: note: enum declared here diff --git a/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig b/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig index e93d1e717d..1cecac6fac 100644 --- a/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig +++ b/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig @@ -18,4 +18,5 @@ export fn f() void { // backend=stage2 // target=native // -// :14:9: error: type 'tmp.Foo' has no field or member function named 'init' +// :14:9: error: no field or member function named 'init' in 'tmp.Foo' +// :1:13: note: struct declared here 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 0820151ff1..9e05a370f9 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 @@ -27,4 +27,5 @@ export fn foo() void { // backend=llvm // target=native // -// :23:6: error: type 'tmp.List' has no field or member function named 'init' +// :23:6: error: no field or member function named 'init' in 'tmp.List' +// :1:14: note: struct declared here diff --git a/test/cases/compile_errors/stage2/union_extra_field.zig b/test/cases/compile_errors/stage2/union_extra_field.zig index 0488ce183c..6d1c644bc6 100644 --- a/test/cases/compile_errors/stage2/union_extra_field.zig +++ b/test/cases/compile_errors/stage2/union_extra_field.zig @@ -16,5 +16,5 @@ export fn entry() usize { // error // target=native // -// :10:5: error: enum 'tmp.E' has no field named 'd' +// :10:5: error: no field named 'd' in enum 'tmp.E' // :1:11: note: enum declared here diff --git a/test/cases/x86_64-linux/hello_world_with_updates.0.zig b/test/cases/x86_64-linux/hello_world_with_updates.0.zig index 47b5e9db7a..4816ec1b26 100644 --- a/test/cases/x86_64-linux/hello_world_with_updates.0.zig +++ b/test/cases/x86_64-linux/hello_world_with_updates.0.zig @@ -3,3 +3,4 @@ // target=x86_64-linux // // :107:9: error: struct 'tmp.tmp' has no member named 'main' +// :7:1: note: struct declared here diff --git a/test/cases/x86_64-macos/hello_world_with_updates.0.zig b/test/cases/x86_64-macos/hello_world_with_updates.0.zig index dd43748e6e..998b2f13eb 100644 --- a/test/cases/x86_64-macos/hello_world_with_updates.0.zig +++ b/test/cases/x86_64-macos/hello_world_with_updates.0.zig @@ -3,3 +3,4 @@ // target=x86_64-macos // // :107:9: error: struct 'tmp.tmp' has no member named 'main' +// :7:1: note: struct declared here diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index cf0f307af3..644cba74c1 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -839,7 +839,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = x; \\} , &.{ - ":3:17: error: enum 'tmp.E' has no field named 'd'", + ":3:17: error: no field named 'd' in enum 'tmp.E'", ":1:11: note: enum declared here", }); } From 2436dd2c1b976f5902be6d20a93c164631ce3df5 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 22 Jul 2022 18:21:03 +0300 Subject: [PATCH 16/17] Sema: validate duplicate fields in anon structs --- src/AstGen.zig | 7 ++- src/Module.zig | 52 +++++++++++++++++++ src/Sema.zig | 50 ++++++++++++++---- ...edding_opaque_type_in_struct_and_union.zig | 2 +- ...cate_field_in_anonymous_struct_literal.zig | 7 ++- .../invalid_store_to_comptime_field.zig | 3 +- 6 files changed, 100 insertions(+), 21 deletions(-) rename test/cases/compile_errors/{stage1/test => }/duplicate_field_in_anonymous_struct_literal.zig (66%) diff --git a/src/AstGen.zig b/src/AstGen.zig index 6df27d6983..df1fb91567 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1589,13 +1589,12 @@ fn structInitExpr( switch (rl) { .discard => { - // TODO if a type expr is given the fields should be validated for that type if (struct_init.ast.type_expr != 0) { const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); _ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node); - } - for (struct_init.ast.fields) |field_init| { - _ = try expr(gz, scope, .discard, field_init); + _ = try structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); + } else { + _ = try structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); } return Zir.Inst.Ref.void_value; }, diff --git a/src/Module.zig b/src/Module.zig index 9c4ae69fe6..8f7c42b542 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5952,6 +5952,58 @@ pub fn argSrc( return LazySrcLoc.nodeOffset(decl.nodeIndexToRelative(full.ast.params[arg_i])); } +pub fn initSrc( + init_node_offset: i32, + gpa: Allocator, + decl: *Decl, + init_index: usize, +) LazySrcLoc { + @setCold(true); + const tree = decl.getFileScope().getTree(gpa) catch |err| { + // In this case we emit a warning + a less precise source location. + log.warn("unable to load {s}: {s}", .{ + decl.getFileScope().sub_file_path, @errorName(err), + }); + return LazySrcLoc.nodeOffset(0); + }; + const node_tags = tree.nodes.items(.tag); + const node = decl.relativeToNodeIndex(init_node_offset); + var buf: [2]Ast.Node.Index = undefined; + const full = switch (node_tags[node]) { + .array_init_one, .array_init_one_comma => tree.arrayInitOne(buf[0..1], node).ast.elements, + .array_init_dot_two, .array_init_dot_two_comma => tree.arrayInitDotTwo(&buf, node).ast.elements, + .array_init_dot, .array_init_dot_comma => tree.arrayInitDot(node).ast.elements, + .array_init, .array_init_comma => tree.arrayInit(node).ast.elements, + + .struct_init_one, .struct_init_one_comma => tree.structInitOne(buf[0..1], node).ast.fields, + .struct_init_dot_two, .struct_init_dot_two_comma => tree.structInitDotTwo(&buf, node).ast.fields, + .struct_init_dot, .struct_init_dot_comma => tree.structInitDot(node).ast.fields, + .struct_init, .struct_init_comma => tree.structInit(node).ast.fields, + else => unreachable, + }; + switch (node_tags[node]) { + .array_init_one, + .array_init_one_comma, + .array_init_dot_two, + .array_init_dot_two_comma, + .array_init_dot, + .array_init_dot_comma, + .array_init, + .array_init_comma, + => return LazySrcLoc.nodeOffset(decl.nodeIndexToRelative(full[init_index])), + .struct_init_one, + .struct_init_one_comma, + .struct_init_dot_two, + .struct_init_dot_two_comma, + .struct_init_dot, + .struct_init_dot_comma, + .struct_init, + .struct_init_comma, + => return LazySrcLoc{ .node_offset_initializer = decl.nodeIndexToRelative(full[init_index]) }, + else => unreachable, + } +} + /// Called from `performAllTheWork`, after all AstGen workers have finished, /// and before the main semantic analysis loop begins. pub fn processOutdatedAndDeletedDecls(mod: *Module) !void { diff --git a/src/Sema.zig b/src/Sema.zig index b2ba53c3e1..d23487a5fd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14721,22 +14721,41 @@ fn zirStructInitAnon( const extra = sema.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index); const types = try sema.arena.alloc(Type, extra.data.fields_len); const values = try sema.arena.alloc(Value, types.len); - const names = try sema.arena.alloc([]const u8, types.len); + var fields = std.StringArrayHashMapUnmanaged(u32){}; + defer fields.deinit(sema.gpa); + try fields.ensureUnusedCapacity(sema.gpa, types.len); - const opt_runtime_src = rs: { - var runtime_src: ?LazySrcLoc = null; + const opt_runtime_index = rs: { + var runtime_index: ?usize = null; var extra_index = extra.end; for (types) |*field_ty, i| { - const init_src = src; // TODO better source location const item = sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index); extra_index = item.end; - names[i] = sema.code.nullTerminatedString(item.data.field_name); + const name = sema.code.nullTerminatedString(item.data.field_name); + const gop = fields.getOrPutAssumeCapacity(name); + if (gop.found_existing) { + const msg = msg: { + const decl = sema.mod.declPtr(block.src_decl); + const field_src = Module.initSrc(src.node_offset.x, sema.gpa, decl, i); + const msg = try sema.errMsg(block, field_src, "duplicate field", .{}); + errdefer msg.destroy(sema.gpa); + + const prev_source = Module.initSrc(src.node_offset.x, sema.gpa, decl, gop.value_ptr.*); + try sema.errNote(block, prev_source, msg, "other field here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + gop.value_ptr.* = @intCast(u32, i); + const init = try sema.resolveInst(item.data.init); field_ty.* = sema.typeOf(init); if (types[i].zigTypeTag() == .Opaque) { const msg = msg: { - const msg = try sema.errMsg(block, init_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{}); + const decl = sema.mod.declPtr(block.src_decl); + const field_src = Module.initSrc(src.node_offset.x, sema.gpa, decl, i); + const msg = try sema.errMsg(block, field_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{}); errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, types[i]); @@ -14744,28 +14763,37 @@ fn zirStructInitAnon( }; return sema.failWithOwnedErrorMsg(block, msg); } + const init_src = src; // TODO better source location if (try sema.resolveMaybeUndefVal(block, init_src, init)) |init_val| { values[i] = init_val; } else { values[i] = Value.initTag(.unreachable_value); - runtime_src = init_src; + runtime_index = i; } } - break :rs runtime_src; + break :rs runtime_index; }; const tuple_ty = try Type.Tag.anon_struct.create(sema.arena, .{ - .names = names, + .names = try sema.arena.dupe([]const u8, fields.keys()), .types = types, .values = values, }); - const runtime_src = opt_runtime_src orelse { + const runtime_index = opt_runtime_index orelse { const tuple_val = try Value.Tag.aggregate.create(sema.arena, values); return sema.addConstantMaybeRef(block, src, tuple_ty, tuple_val, is_ref); }; - try sema.requireRuntimeBlock(block, src, runtime_src); + sema.requireRuntimeBlock(block, src, .unneeded) catch |err| switch (err) { + error.NeededSourceLocation => { + const decl = sema.mod.declPtr(block.src_decl); + const field_src = Module.initSrc(src.node_offset.x, sema.gpa, decl, runtime_index); + try sema.requireRuntimeBlock(block, src, field_src); + return error.AnalysisFail; + }, + else => |e| return e, + }; if (is_ref) { const target = sema.mod.getTarget(); diff --git a/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig b/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig index 5cfce8e61e..3be57ac491 100644 --- a/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig +++ b/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig @@ -34,5 +34,5 @@ export fn d() void { // :7:5: error: opaque types have unknown size and therefore cannot be directly embedded in unions // :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs // :18:22: note: opaque declared here -// :24:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs +// :24:23: error: opaque types have unknown size and therefore cannot be directly embedded in structs // :23:22: note: opaque declared here diff --git a/test/cases/compile_errors/stage1/test/duplicate_field_in_anonymous_struct_literal.zig b/test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig similarity index 66% rename from test/cases/compile_errors/stage1/test/duplicate_field_in_anonymous_struct_literal.zig rename to test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig index 7b6821d669..ed153c1542 100644 --- a/test/cases/compile_errors/stage1/test/duplicate_field_in_anonymous_struct_literal.zig +++ b/test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig @@ -11,9 +11,8 @@ export fn entry() void { } // error -// backend=stage1 +// backend=stage2 // target=native -// is_test=1 // -// tmp.zig:7:13: error: duplicate field -// tmp.zig:4:13: note: other field here +// :7:16: error: duplicate field +// :4:16: note: other field here diff --git a/test/cases/compile_errors/invalid_store_to_comptime_field.zig b/test/cases/compile_errors/invalid_store_to_comptime_field.zig index 63d83c01d1..7fdb08facb 100644 --- a/test/cases/compile_errors/invalid_store_to_comptime_field.zig +++ b/test/cases/compile_errors/invalid_store_to_comptime_field.zig @@ -58,4 +58,5 @@ pub export fn entry4() void { // :14:19: error: value stored in comptime field does not match the default value of the field // :19:38: error: value stored in comptime field does not match the default value of the field // :31:19: error: value stored in comptime field does not match the default value of the field -// :41:14: error: value stored in comptime field does not match the default value of the field +// :25:29: note: default value set here +// :41:16: error: value stored in comptime field does not match the default value of the field From baf516218e227a55b59c9ae9e6c52b0f9ebd0980 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 23 Jul 2022 14:26:01 +0300 Subject: [PATCH 17/17] Sema: don't add union field access safety check for single field unions --- src/Sema.zig | 8 ++++++-- test/behavior/align.zig | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index d23487a5fd..db3cfa6d40 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -20203,7 +20203,9 @@ fn unionFieldPtr( } try sema.requireRuntimeBlock(block, src, null); - if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) { + if (!initializing and union_obj.layout == .Auto and block.wantSafety() and + union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) + { const enum_ty = union_ty.unionTagTypeHypothetical(); const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val); @@ -20271,7 +20273,9 @@ fn unionFieldVal( } try sema.requireRuntimeBlock(block, src, null); - if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) { + if (union_obj.layout == .Auto and block.wantSafety() and + union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) + { const enum_ty = union_ty.unionTagTypeHypothetical(); const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val); diff --git a/test/behavior/align.zig b/test/behavior/align.zig index d09e97c05b..26e3d91373 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -222,6 +222,7 @@ fn testBytesAlign(b: u8) !void { test "@alignCast slices" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; var array align(4) = [_]u32{ 1, 1 }; const slice = array[0..];