authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-05-13 10:42:05+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-05-13 10:42:05+02:00
log5b606d435d139487c23d6fd30e8061dbad741ada
tree7c1cb233429d26ea85249b21f7cb7f80e8527476
parenta365971a337116dd23df2b1bc86468d54885b4b2
parent9d8adb38a18169a16707acac2812dd6850de99be
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21882 from alexrp/compiler-fixes

compiler: Fix some real and theoretical miscompilations with `allowzero` and `volatile`

12 files changed, 512 insertions(+), 88 deletions(-)

build.zig+5
...@@ -550,6 +550,11 @@ pub fn build(b: *std.Build) !void {...@@ -550,6 +550,11 @@ pub fn build(b: *std.Build) !void {
550 .skip_non_native = skip_non_native,550 .skip_non_native = skip_non_native,
551 .skip_libc = skip_libc,551 .skip_libc = skip_libc,
552 })) |test_debugger_step| test_step.dependOn(test_debugger_step);552 })) |test_debugger_step| test_step.dependOn(test_debugger_step);
553 if (tests.addLlvmIrTests(b, .{
554 .enable_llvm = enable_llvm,
555 .test_filters = test_filters,
556 .test_target_filters = test_target_filters,
557 })) |test_llvm_ir_step| test_step.dependOn(test_llvm_ir_step);
553558
554 try addWasiUpdateStep(b, version);559 try addWasiUpdateStep(b, version);
555560
lib/std/Build/Module.zig+4
...@@ -33,6 +33,7 @@ omit_frame_pointer: ?bool,...@@ -33,6 +33,7 @@ omit_frame_pointer: ?bool,
33error_tracing: ?bool,33error_tracing: ?bool,
34link_libc: ?bool,34link_libc: ?bool,
35link_libcpp: ?bool,35link_libcpp: ?bool,
36no_builtin: ?bool,
3637
37/// Symbols to be exported when compiling to WebAssembly.38/// Symbols to be exported when compiling to WebAssembly.
38export_symbol_names: []const []const u8 = &.{},39export_symbol_names: []const []const u8 = &.{},
...@@ -268,6 +269,7 @@ pub const CreateOptions = struct {...@@ -268,6 +269,7 @@ pub const CreateOptions = struct {
268 /// more difficult to obtain stack traces. Has target-dependent effects.269 /// more difficult to obtain stack traces. Has target-dependent effects.
269 omit_frame_pointer: ?bool = null,270 omit_frame_pointer: ?bool = null,
270 error_tracing: ?bool = null,271 error_tracing: ?bool = null,
272 no_builtin: ?bool = null,
271};273};
272274
273pub const Import = struct {275pub const Import = struct {
...@@ -314,6 +316,7 @@ pub fn init(...@@ -314,6 +316,7 @@ pub fn init(
314 .omit_frame_pointer = options.omit_frame_pointer,316 .omit_frame_pointer = options.omit_frame_pointer,
315 .error_tracing = options.error_tracing,317 .error_tracing = options.error_tracing,
316 .export_symbol_names = &.{},318 .export_symbol_names = &.{},
319 .no_builtin = options.no_builtin,
317 };320 };
318321
319 m.import_table.ensureUnusedCapacity(allocator, options.imports.len) catch @panic("OOM");322 m.import_table.ensureUnusedCapacity(allocator, options.imports.len) catch @panic("OOM");
...@@ -564,6 +567,7 @@ pub fn appendZigProcessFlags(...@@ -564,6 +567,7 @@ pub fn appendZigProcessFlags(
564 try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");567 try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");
565 try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");568 try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
566 try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");569 try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
570 try addFlag(zig_args, m.no_builtin, "-fno-builtin", "-fbuiltin");
567571
568 if (m.sanitize_c) |sc| switch (sc) {572 if (m.sanitize_c) |sc| switch (sc) {
569 .off => try zig_args.append("-fno-sanitize-c"),573 .off => try zig_args.append("-fno-sanitize-c"),
lib/std/Build/Step/Compile.zig-6
...@@ -229,8 +229,6 @@ is_linking_libc: bool = false,...@@ -229,8 +229,6 @@ is_linking_libc: bool = false,
229/// Computed during make().229/// Computed during make().
230is_linking_libcpp: bool = false,230is_linking_libcpp: bool = false,
231231
232no_builtin: bool = false,
233
234/// Populated during the make phase when there is a long-lived compiler process.232/// Populated during the make phase when there is a long-lived compiler process.
235/// Managed by the build runner, not user build script.233/// Managed by the build runner, not user build script.
236zig_process: ?*Step.ZigProcess,234zig_process: ?*Step.ZigProcess,
...@@ -1646,10 +1644,6 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1646,10 +1644,6 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1646 }1644 }
1647 }1645 }
16481646
1649 if (compile.no_builtin) {
1650 try zig_args.append("-fno-builtin");
1651 }
1652
1653 if (b.sysroot) |sysroot| {1647 if (b.sysroot) |sysroot| {
1654 try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot });1648 try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot });
1655 }1649 }
src/Air.zig+7-15
...@@ -1673,6 +1673,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1673,6 +1673,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1673 const data = air.instructions.items(.data)[@intFromEnum(inst)];1673 const data = air.instructions.items(.data)[@intFromEnum(inst)];
1674 return switch (air.instructions.items(.tag)[@intFromEnum(inst)]) {1674 return switch (air.instructions.items(.tag)[@intFromEnum(inst)]) {
1675 .arg,1675 .arg,
1676 .assembly,
1676 .block,1677 .block,
1677 .loop,1678 .loop,
1678 .repeat,1679 .repeat,
...@@ -1816,12 +1817,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1816,12 +1817,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1816 .cmp_vector_optimized,1817 .cmp_vector_optimized,
1817 .is_null,1818 .is_null,
1818 .is_non_null,1819 .is_non_null,
1819 .is_null_ptr,
1820 .is_non_null_ptr,
1821 .is_err,1820 .is_err,
1822 .is_non_err,1821 .is_non_err,
1823 .is_err_ptr,
1824 .is_non_err_ptr,
1825 .bool_and,1822 .bool_and,
1826 .bool_or,1823 .bool_or,
1827 .fptrunc,1824 .fptrunc,
...@@ -1834,7 +1831,6 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1834,7 +1831,6 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1834 .unwrap_errunion_payload,1831 .unwrap_errunion_payload,
1835 .unwrap_errunion_err,1832 .unwrap_errunion_err,
1836 .unwrap_errunion_payload_ptr,1833 .unwrap_errunion_payload_ptr,
1837 .unwrap_errunion_err_ptr,
1838 .wrap_errunion_payload,1834 .wrap_errunion_payload,
1839 .wrap_errunion_err,1835 .wrap_errunion_err,
1840 .struct_field_ptr,1836 .struct_field_ptr,
...@@ -1879,17 +1875,13 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1879,17 +1875,13 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1879 .work_group_id,1875 .work_group_id,
1880 => false,1876 => false,
18811877
1882 .assembly => {1878 .is_non_null_ptr, .is_null_ptr, .is_non_err_ptr, .is_err_ptr => air.typeOf(data.un_op, ip).isVolatilePtrIp(ip),
1883 const extra = air.extraData(Air.Asm, data.ty_pl.payload);1879 .load, .unwrap_errunion_err_ptr => air.typeOf(data.ty_op.operand, ip).isVolatilePtrIp(ip),
1884 const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
1885 return is_volatile or if (extra.data.outputs_len == 1)
1886 @as(Air.Inst.Ref, @enumFromInt(air.extra[extra.end])) != .none
1887 else
1888 extra.data.outputs_len > 1;
1889 },
1890 .load => air.typeOf(data.ty_op.operand, ip).isVolatilePtrIp(ip),
1891 .slice_elem_val, .ptr_elem_val => air.typeOf(data.bin_op.lhs, ip).isVolatilePtrIp(ip),1880 .slice_elem_val, .ptr_elem_val => air.typeOf(data.bin_op.lhs, ip).isVolatilePtrIp(ip),
1892 .atomic_load => air.typeOf(data.atomic_load.ptr, ip).isVolatilePtrIp(ip),1881 .atomic_load => switch (data.atomic_load.order) {
1882 .unordered, .monotonic => air.typeOf(data.atomic_load.ptr, ip).isVolatilePtrIp(ip),
1883 else => true, // Stronger memory orderings have inter-thread side effects.
1884 },
1893 };1885 };
1894}1886}
18951887
src/arch/riscv64/CodeGen.zig+33-13
...@@ -1460,7 +1460,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1460,7 +1460,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
14601460
1461 .mul,1461 .mul,
1462 .mul_wrap,1462 .mul_wrap,
1463 .div_trunc, 1463 .div_trunc,
1464 .div_exact,1464 .div_exact,
1465 .rem,1465 .rem,
14661466
...@@ -1478,13 +1478,13 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1478,13 +1478,13 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1478 .max,1478 .max,
1479 => try func.airBinOp(inst, tag),1479 => try func.airBinOp(inst, tag),
14801480
1481 1481
1482 .ptr_add,1482 .ptr_add,
1483 .ptr_sub => try func.airPtrArithmetic(inst, tag),1483 .ptr_sub => try func.airPtrArithmetic(inst, tag),
14841484
1485 .mod,1485 .mod,
1486 .div_float, 1486 .div_float,
1487 .div_floor, 1487 .div_floor,
1488 => return func.fail("TODO: {s}", .{@tagName(tag)}),1488 => return func.fail("TODO: {s}", .{@tagName(tag)}),
14891489
1490 .sqrt,1490 .sqrt,
...@@ -1639,7 +1639,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1639,7 +1639,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1639 .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),1639 .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),
16401640
1641 .array_elem_val => try func.airArrayElemVal(inst),1641 .array_elem_val => try func.airArrayElemVal(inst),
1642 1642
1643 .slice_elem_val => try func.airSliceElemVal(inst),1643 .slice_elem_val => try func.airSliceElemVal(inst),
1644 .slice_elem_ptr => try func.airSliceElemPtr(inst),1644 .slice_elem_ptr => try func.airSliceElemPtr(inst),
16451645
...@@ -1769,8 +1769,15 @@ fn finishAirBookkeeping(func: *Func) void {...@@ -1769,8 +1769,15 @@ fn finishAirBookkeeping(func: *Func) void {
1769fn finishAirResult(func: *Func, inst: Air.Inst.Index, result: MCValue) void {1769fn finishAirResult(func: *Func, inst: Air.Inst.Index, result: MCValue) void {
1770 if (func.liveness.isUnused(inst)) switch (result) {1770 if (func.liveness.isUnused(inst)) switch (result) {
1771 .none, .dead, .unreach => {},1771 .none, .dead, .unreach => {},
1772 else => unreachable, // Why didn't the result die?1772 // Why didn't the result die?
1773 .register => |r| if (r != .zero) unreachable,
1774 else => unreachable,
1773 } else {1775 } else {
1776 switch (result) {
1777 .register => |r| if (r == .zero) unreachable, // Why did we discard a used result?
1778 else => {},
1779 }
1780
1774 tracking_log.debug("%{d} => {} (birth)", .{ inst, result });1781 tracking_log.debug("%{d} => {} (birth)", .{ inst, result });
1775 func.inst_tracking.putAssumeCapacityNoClobber(inst, InstTracking.init(result));1782 func.inst_tracking.putAssumeCapacityNoClobber(inst, InstTracking.init(result));
1776 // In some cases, an operand may be reused as the result.1783 // In some cases, an operand may be reused as the result.
...@@ -7729,9 +7736,12 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {...@@ -7729,9 +7736,12 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {
7729 const ptr_mcv = try func.resolveInst(atomic_load.ptr);7736 const ptr_mcv = try func.resolveInst(atomic_load.ptr);
77307737
7731 const bit_size = elem_ty.bitSize(zcu);7738 const bit_size = elem_ty.bitSize(zcu);
7732 if (bit_size > 64) return func.fail("TODO: airAtomicStore > 64 bits", .{});7739 if (bit_size > 64) return func.fail("TODO: airAtomicLoad > 64 bits", .{});
77337740
7734 const result_mcv = try func.allocRegOrMem(elem_ty, inst, true);7741 const result_mcv: MCValue = if (func.liveness.isUnused(inst))
7742 .{ .register = .zero }
7743 else
7744 try func.allocRegOrMem(elem_ty, inst, true);
7735 assert(result_mcv == .register); // should be less than 8 bytes7745 assert(result_mcv == .register); // should be less than 8 bytes
77367746
7737 if (order == .seq_cst) {7747 if (order == .seq_cst) {
...@@ -7747,11 +7757,10 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {...@@ -7747,11 +7757,10 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {
7747 try func.load(result_mcv, ptr_mcv, ptr_ty);7757 try func.load(result_mcv, ptr_mcv, ptr_ty);
77487758
7749 switch (order) {7759 switch (order) {
7750 // Don't guarnetee other memory operations to be ordered after the load.7760 // Don't guarantee other memory operations to be ordered after the load.
7751 .unordered => {},7761 .unordered, .monotonic => {},
7752 .monotonic => {},7762 // Make sure all previous reads happen before any reading or writing occurs.
7753 // Make sure all previous reads happen before any reading or writing accurs.7763 .acquire, .seq_cst => {
7754 .seq_cst, .acquire => {
7755 _ = try func.addInst(.{7764 _ = try func.addInst(.{
7756 .tag = .fence,7765 .tag = .fence,
7757 .data = .{ .fence = .{7766 .data = .{ .fence = .{
...@@ -7793,6 +7802,17 @@ fn airAtomicStore(func: *Func, inst: Air.Inst.Index, order: std.builtin.AtomicOr...@@ -7793,6 +7802,17 @@ fn airAtomicStore(func: *Func, inst: Air.Inst.Index, order: std.builtin.AtomicOr
7793 }7802 }
77947803
7795 try func.store(ptr_mcv, val_mcv, ptr_ty);7804 try func.store(ptr_mcv, val_mcv, ptr_ty);
7805
7806 if (order == .seq_cst) {
7807 _ = try func.addInst(.{
7808 .tag = .fence,
7809 .data = .{ .fence = .{
7810 .pred = .rw,
7811 .succ = .rw,
7812 } },
7813 });
7814 }
7815
7796 return func.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none });7816 return func.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none });
7797}7817}
77987818
src/arch/x86_64/CodeGen.zig+17-11
...@@ -106219,23 +106219,29 @@ fn airAtomicRmw(self: *CodeGen, inst: Air.Inst.Index) !void {...@@ -106219,23 +106219,29 @@ fn airAtomicRmw(self: *CodeGen, inst: Air.Inst.Index) !void {
106219106219
106220fn airAtomicLoad(self: *CodeGen, inst: Air.Inst.Index) !void {106220fn airAtomicLoad(self: *CodeGen, inst: Air.Inst.Index) !void {
106221 const atomic_load = self.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;106221 const atomic_load = self.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
106222 const result: MCValue = result: {
106223 const ptr_ty = self.typeOf(atomic_load.ptr);
106224 const ptr_mcv = try self.resolveInst(atomic_load.ptr);
106225 const ptr_lock = switch (ptr_mcv) {
106226 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
106227 else => null,
106228 };
106229 defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock);
106222106230
106223 const ptr_ty = self.typeOf(atomic_load.ptr);106231 const unused = self.liveness.isUnused(inst);
106224 const ptr_mcv = try self.resolveInst(atomic_load.ptr);
106225 const ptr_lock = switch (ptr_mcv) {
106226 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
106227 else => null,
106228 };
106229 defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock);
106230106232
106231 const dst_mcv =106233 const dst_mcv: MCValue = if (unused)
106232 if (self.reuseOperand(inst, atomic_load.ptr, 0, ptr_mcv))106234 .{ .register = try self.register_manager.allocReg(null, self.regSetForType(ptr_ty.childType(self.pt.zcu))) }
106235 else if (self.reuseOperand(inst, atomic_load.ptr, 0, ptr_mcv))
106233 ptr_mcv106236 ptr_mcv
106234 else106237 else
106235 try self.allocRegOrMem(inst, true);106238 try self.allocRegOrMem(inst, true);
106236106239
106237 try self.load(dst_mcv, ptr_ty, ptr_mcv);106240 try self.load(dst_mcv, ptr_ty, ptr_mcv);
106238 return self.finishAir(inst, dst_mcv, .{ atomic_load.ptr, .none, .none });106241
106242 break :result if (unused) .unreach else dst_mcv;
106243 };
106244 return self.finishAir(inst, result, .{ atomic_load.ptr, .none, .none });
106239}106245}
106240106246
106241fn airAtomicStore(self: *CodeGen, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {106247fn airAtomicStore(self: *CodeGen, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {
src/codegen/llvm.zig+164-38
...@@ -1341,7 +1341,10 @@ pub const Object = struct {...@@ -1341,7 +1341,10 @@ pub const Object = struct {
1341 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);1341 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
1342 }1342 }
1343 }1343 }
1344 if (param_ty.zigTypeTag(zcu) != .optional) {1344 if (param_ty.zigTypeTag(zcu) != .optional and
1345 !ptr_info.flags.is_allowzero and
1346 ptr_info.flags.address_space == .generic)
1347 {
1345 try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);1348 try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
1346 }1349 }
1347 if (ptr_info.flags.is_const) {1350 if (ptr_info.flags.is_const) {
...@@ -1419,8 +1422,6 @@ pub const Object = struct {...@@ -1419,8 +1422,6 @@ pub const Object = struct {
1419 }1422 }
1420 }1423 }
14211424
1422 function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
1423
1424 const file, const subprogram = if (!wip.strip) debug_info: {1425 const file, const subprogram = if (!wip.strip) debug_info: {
1425 const file = try o.getDebugFile(file_scope);1426 const file = try o.getDebugFile(file_scope);
14261427
...@@ -1517,6 +1518,17 @@ pub const Object = struct {...@@ -1517,6 +1518,17 @@ pub const Object = struct {
1517 else => |e| return e,1518 else => |e| return e,
1518 };1519 };
15191520
1521 // If we saw any loads or stores involving `allowzero` pointers, we need to mark the whole
1522 // function as considering null pointers valid so that LLVM's optimizers don't remove these
1523 // operations on the assumption that they're undefined behavior.
1524 if (fg.allowzero_access) {
1525 try attributes.addFnAttr(.null_pointer_is_valid, &o.builder);
1526 } else {
1527 _ = try attributes.removeFnAttr(.null_pointer_is_valid);
1528 }
1529
1530 function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
1531
1520 if (fg.fuzz) |*f| {1532 if (fg.fuzz) |*f| {
1521 {1533 {
1522 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);1534 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
...@@ -4349,7 +4361,10 @@ pub const Object = struct {...@@ -4349,7 +4361,10 @@ pub const Object = struct {
4349 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);4361 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
4350 }4362 }
4351 }4363 }
4352 if (!param_ty.isPtrLikeOptional(zcu) and !ptr_info.flags.is_allowzero) {4364 if (!param_ty.isPtrLikeOptional(zcu) and
4365 !ptr_info.flags.is_allowzero and
4366 ptr_info.flags.address_space == .generic)
4367 {
4353 try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);4368 try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
4354 }4369 }
4355 switch (fn_info.cc) {4370 switch (fn_info.cc) {
...@@ -4667,6 +4682,15 @@ pub const FuncGen = struct {...@@ -4667,6 +4682,15 @@ pub const FuncGen = struct {
46674682
4668 disable_intrinsics: bool,4683 disable_intrinsics: bool,
46694684
4685 /// Have we seen loads or stores involving `allowzero` pointers?
4686 allowzero_access: bool = false,
4687
4688 pub fn maybeMarkAllowZeroAccess(self: *FuncGen, info: InternPool.Key.PtrType) void {
4689 // LLVM already considers null pointers to be valid in non-generic address spaces, so avoid
4690 // pessimizing optimization for functions with accesses to such pointers.
4691 if (info.flags.address_space == .generic and info.flags.is_allowzero) self.allowzero_access = true;
4692 }
4693
4670 const Fuzz = struct {4694 const Fuzz = struct {
4671 counters_variable: Builder.Variable.Index,4695 counters_variable: Builder.Variable.Index,
4672 pcs: std.ArrayListUnmanaged(Builder.Constant),4696 pcs: std.ArrayListUnmanaged(Builder.Constant),
...@@ -5392,7 +5416,10 @@ pub const FuncGen = struct {...@@ -5392,7 +5416,10 @@ pub const FuncGen = struct {
5392 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);5416 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
5393 }5417 }
5394 }5418 }
5395 if (param_ty.zigTypeTag(zcu) != .optional) {5419 if (param_ty.zigTypeTag(zcu) != .optional and
5420 !ptr_info.flags.is_allowzero and
5421 ptr_info.flags.address_space == .generic)
5422 {
5396 try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);5423 try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
5397 }5424 }
5398 if (ptr_info.flags.is_const) {5425 if (ptr_info.flags.is_const) {
...@@ -5519,7 +5546,7 @@ pub const FuncGen = struct {...@@ -5519,7 +5546,7 @@ pub const FuncGen = struct {
5519 ptr_ty.ptrAlignment(zcu).toLlvm(),5546 ptr_ty.ptrAlignment(zcu).toLlvm(),
5520 try o.builder.intValue(.i8, 0xaa),5547 try o.builder.intValue(.i8, 0xaa),
5521 len,5548 len,
5522 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,5549 .normal,
5523 self.disable_intrinsics,5550 self.disable_intrinsics,
5524 );5551 );
5525 const owner_mod = self.ng.ownerModule();5552 const owner_mod = self.ng.ownerModule();
...@@ -5754,8 +5781,8 @@ pub const FuncGen = struct {...@@ -5754,8 +5781,8 @@ pub const FuncGen = struct {
5754 // of optionals that are not pointers.5781 // of optionals that are not pointers.
5755 const is_by_ref = isByRef(scalar_ty, zcu);5782 const is_by_ref = isByRef(scalar_ty, zcu);
5756 const opt_llvm_ty = try o.lowerType(scalar_ty);5783 const opt_llvm_ty = try o.lowerType(scalar_ty);
5757 const lhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, lhs, is_by_ref);5784 const lhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, lhs, is_by_ref, .normal);
5758 const rhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, rhs, is_by_ref);5785 const rhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, rhs, is_by_ref, .normal);
5759 const llvm_i2 = try o.builder.intType(2);5786 const llvm_i2 = try o.builder.intType(2);
5760 const lhs_non_null_i2 = try self.wip.cast(.zext, lhs_non_null, llvm_i2, "");5787 const lhs_non_null_i2 = try self.wip.cast(.zext, lhs_non_null, llvm_i2, "");
5761 const rhs_non_null_i2 = try self.wip.cast(.zext, rhs_non_null, llvm_i2, "");5788 const rhs_non_null_i2 = try self.wip.cast(.zext, rhs_non_null, llvm_i2, "");
...@@ -6206,6 +6233,9 @@ pub const FuncGen = struct {...@@ -6206,6 +6233,9 @@ pub const FuncGen = struct {
6206 const body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]);6233 const body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]);
6207 const err_union_ty = self.typeOf(extra.data.ptr).childType(zcu);6234 const err_union_ty = self.typeOf(extra.data.ptr).childType(zcu);
6208 const is_unused = self.liveness.isUnused(inst);6235 const is_unused = self.liveness.isUnused(inst);
6236
6237 self.maybeMarkAllowZeroAccess(self.typeOf(extra.data.ptr).ptrInfo(zcu));
6238
6209 return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, is_unused, err_cold);6239 return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, is_unused, err_cold);
6210 }6240 }
62116241
...@@ -6229,10 +6259,13 @@ pub const FuncGen = struct {...@@ -6229,10 +6259,13 @@ pub const FuncGen = struct {
62296259
6230 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {6260 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
6231 const loaded = loaded: {6261 const loaded = loaded: {
6262 const access_kind: Builder.MemoryAccessKind =
6263 if (err_union_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
6264
6232 if (!payload_has_bits) {6265 if (!payload_has_bits) {
6233 // TODO add alignment to this load6266 // TODO add alignment to this load
6234 break :loaded if (operand_is_ptr)6267 break :loaded if (operand_is_ptr)
6235 try fg.wip.load(.normal, error_type, err_union, .default, "")6268 try fg.wip.load(access_kind, error_type, err_union, .default, "")
6236 else6269 else
6237 err_union;6270 err_union;
6238 }6271 }
...@@ -6242,7 +6275,7 @@ pub const FuncGen = struct {...@@ -6242,7 +6275,7 @@ pub const FuncGen = struct {
6242 try fg.wip.gepStruct(err_union_llvm_ty, err_union, err_field_index, "");6275 try fg.wip.gepStruct(err_union_llvm_ty, err_union, err_field_index, "");
6243 // TODO add alignment to this load6276 // TODO add alignment to this load
6244 break :loaded try fg.wip.load(6277 break :loaded try fg.wip.load(
6245 .normal,6278 if (operand_is_ptr) access_kind else .normal,
6246 error_type,6279 error_type,
6247 err_field_ptr,6280 err_field_ptr,
6248 .default,6281 .default,
...@@ -6751,10 +6784,14 @@ pub const FuncGen = struct {...@@ -6751,10 +6784,14 @@ pub const FuncGen = struct {
6751 if (self.canElideLoad(body_tail))6784 if (self.canElideLoad(body_tail))
6752 return ptr;6785 return ptr;
67536786
6787 self.maybeMarkAllowZeroAccess(slice_ty.ptrInfo(zcu));
6788
6754 const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();6789 const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();
6755 return self.loadByRef(ptr, elem_ty, elem_alignment, .normal);6790 return self.loadByRef(ptr, elem_ty, elem_alignment, if (slice_ty.isVolatilePtr(zcu)) .@"volatile" else .normal);
6756 }6791 }
67576792
6793 self.maybeMarkAllowZeroAccess(slice_ty.ptrInfo(zcu));
6794
6758 return self.load(ptr, slice_ty);6795 return self.load(ptr, slice_ty);
6759 }6796 }
67606797
...@@ -6824,10 +6861,15 @@ pub const FuncGen = struct {...@@ -6824,10 +6861,15 @@ pub const FuncGen = struct {
6824 &.{rhs}, "");6861 &.{rhs}, "");
6825 if (isByRef(elem_ty, zcu)) {6862 if (isByRef(elem_ty, zcu)) {
6826 if (self.canElideLoad(body_tail)) return ptr;6863 if (self.canElideLoad(body_tail)) return ptr;
6864
6865 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
6866
6827 const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();6867 const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();
6828 return self.loadByRef(ptr, elem_ty, elem_alignment, .normal);6868 return self.loadByRef(ptr, elem_ty, elem_alignment, if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal);
6829 }6869 }
68306870
6871 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
6872
6831 return self.load(ptr, ptr_ty);6873 return self.load(ptr, ptr_ty);
6832 }6874 }
68336875
...@@ -7235,6 +7277,8 @@ pub const FuncGen = struct {...@@ -7235,6 +7277,8 @@ pub const FuncGen = struct {
7235 }),7277 }),
7236 }7278 }
72377279
7280 self.maybeMarkAllowZeroAccess(output_ty.ptrInfo(zcu));
7281
7238 // Pass any non-return outputs indirectly, if the constraint accepts a memory location7282 // Pass any non-return outputs indirectly, if the constraint accepts a memory location
7239 is_indirect.* = constraintAllowsMemory(constraint);7283 is_indirect.* = constraintAllowsMemory(constraint);
7240 if (is_indirect.*) {7284 if (is_indirect.*) {
...@@ -7341,10 +7385,11 @@ pub const FuncGen = struct {...@@ -7341,10 +7385,11 @@ pub const FuncGen = struct {
73417385
7342 // In the case of indirect inputs, LLVM requires the callsite to have7386 // In the case of indirect inputs, LLVM requires the callsite to have
7343 // an elementtype(<ty>) attribute.7387 // an elementtype(<ty>) attribute.
7344 llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*')7388 llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*') blk: {
7345 try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(zcu))7389 if (!is_by_ref) self.maybeMarkAllowZeroAccess(arg_ty.ptrInfo(zcu));
7346 else7390
7347 .none;7391 break :blk try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(zcu));
7392 } else .none;
73487393
7349 llvm_param_i += 1;7394 llvm_param_i += 1;
7350 total_i += 1;7395 total_i += 1;
...@@ -7367,7 +7412,13 @@ pub const FuncGen = struct {...@@ -7367,7 +7412,13 @@ pub const FuncGen = struct {
7367 llvm_param_types[llvm_param_i] = llvm_rw_val.typeOfWip(&self.wip);7412 llvm_param_types[llvm_param_i] = llvm_rw_val.typeOfWip(&self.wip);
7368 } else {7413 } else {
7369 const alignment = rw_ty.abiAlignment(zcu).toLlvm();7414 const alignment = rw_ty.abiAlignment(zcu).toLlvm();
7370 const loaded = try self.wip.load(.normal, llvm_elem_ty, llvm_rw_val, alignment, "");7415 const loaded = try self.wip.load(
7416 if (rw_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
7417 llvm_elem_ty,
7418 llvm_rw_val,
7419 alignment,
7420 "",
7421 );
7371 llvm_param_values[llvm_param_i] = loaded;7422 llvm_param_values[llvm_param_i] = loaded;
7372 llvm_param_types[llvm_param_i] = llvm_elem_ty;7423 llvm_param_types[llvm_param_i] = llvm_elem_ty;
7373 }7424 }
...@@ -7530,9 +7581,13 @@ pub const FuncGen = struct {...@@ -7530,9 +7581,13 @@ pub const FuncGen = struct {
7530 if (output != .none) {7581 if (output != .none) {
7531 const output_ptr = try self.resolveInst(output);7582 const output_ptr = try self.resolveInst(output);
7532 const output_ptr_ty = self.typeOf(output);7583 const output_ptr_ty = self.typeOf(output);
7533
7534 const alignment = output_ptr_ty.ptrAlignment(zcu).toLlvm();7584 const alignment = output_ptr_ty.ptrAlignment(zcu).toLlvm();
7535 _ = try self.wip.store(.normal, output_value, output_ptr, alignment);7585 _ = try self.wip.store(
7586 if (output_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
7587 output_value,
7588 output_ptr,
7589 alignment,
7590 );
7536 } else {7591 } else {
7537 ret_val = output_value;7592 ret_val = output_value;
7538 }7593 }
...@@ -7557,9 +7612,15 @@ pub const FuncGen = struct {...@@ -7557,9 +7612,15 @@ pub const FuncGen = struct {
7557 const optional_ty = if (operand_is_ptr) operand_ty.childType(zcu) else operand_ty;7612 const optional_ty = if (operand_is_ptr) operand_ty.childType(zcu) else operand_ty;
7558 const optional_llvm_ty = try o.lowerType(optional_ty);7613 const optional_llvm_ty = try o.lowerType(optional_ty);
7559 const payload_ty = optional_ty.optionalChild(zcu);7614 const payload_ty = optional_ty.optionalChild(zcu);
7615
7616 const access_kind: Builder.MemoryAccessKind =
7617 if (operand_is_ptr and operand_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
7618
7619 if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7620
7560 if (optional_ty.optionalReprIsPayload(zcu)) {7621 if (optional_ty.optionalReprIsPayload(zcu)) {
7561 const loaded = if (operand_is_ptr)7622 const loaded = if (operand_is_ptr)
7562 try self.wip.load(.normal, optional_llvm_ty, operand, .default, "")7623 try self.wip.load(access_kind, optional_llvm_ty, operand, .default, "")
7563 else7624 else
7564 operand;7625 operand;
7565 if (payload_ty.isSlice(zcu)) {7626 if (payload_ty.isSlice(zcu)) {
...@@ -7577,14 +7638,14 @@ pub const FuncGen = struct {...@@ -7577,14 +7638,14 @@ pub const FuncGen = struct {
75777638
7578 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {7639 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7579 const loaded = if (operand_is_ptr)7640 const loaded = if (operand_is_ptr)
7580 try self.wip.load(.normal, optional_llvm_ty, operand, .default, "")7641 try self.wip.load(access_kind, optional_llvm_ty, operand, .default, "")
7581 else7642 else
7582 operand;7643 operand;
7583 return self.wip.icmp(cond, loaded, try o.builder.intValue(.i8, 0), "");7644 return self.wip.icmp(cond, loaded, try o.builder.intValue(.i8, 0), "");
7584 }7645 }
75857646
7586 const is_by_ref = operand_is_ptr or isByRef(optional_ty, zcu);7647 const is_by_ref = operand_is_ptr or isByRef(optional_ty, zcu);
7587 return self.optCmpNull(cond, optional_llvm_ty, operand, is_by_ref);7648 return self.optCmpNull(cond, optional_llvm_ty, operand, is_by_ref, access_kind);
7588 }7649 }
75897650
7590 fn airIsErr(7651 fn airIsErr(
...@@ -7604,6 +7665,9 @@ pub const FuncGen = struct {...@@ -7604,6 +7665,9 @@ pub const FuncGen = struct {
7604 const error_type = try o.errorIntType();7665 const error_type = try o.errorIntType();
7605 const zero = try o.builder.intValue(error_type, 0);7666 const zero = try o.builder.intValue(error_type, 0);
76067667
7668 const access_kind: Builder.MemoryAccessKind =
7669 if (operand_is_ptr and operand_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
7670
7607 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {7671 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
7608 const val: Builder.Constant = switch (cond) {7672 const val: Builder.Constant = switch (cond) {
7609 .eq => .true, // 0 == 07673 .eq => .true, // 0 == 0
...@@ -7613,9 +7677,11 @@ pub const FuncGen = struct {...@@ -7613,9 +7677,11 @@ pub const FuncGen = struct {
7613 return val.toValue();7677 return val.toValue();
7614 }7678 }
76157679
7680 if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7681
7616 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {7682 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7617 const loaded = if (operand_is_ptr)7683 const loaded = if (operand_is_ptr)
7618 try self.wip.load(.normal, try o.lowerType(err_union_ty), operand, .default, "")7684 try self.wip.load(access_kind, try o.lowerType(err_union_ty), operand, .default, "")
7619 else7685 else
7620 operand;7686 operand;
7621 return self.wip.icmp(cond, loaded, zero, "");7687 return self.wip.icmp(cond, loaded, zero, "");
...@@ -7627,7 +7693,7 @@ pub const FuncGen = struct {...@@ -7627,7 +7693,7 @@ pub const FuncGen = struct {
7627 const err_union_llvm_ty = try o.lowerType(err_union_ty);7693 const err_union_llvm_ty = try o.lowerType(err_union_ty);
7628 const err_field_ptr =7694 const err_field_ptr =
7629 try self.wip.gepStruct(err_union_llvm_ty, operand, err_field_index, "");7695 try self.wip.gepStruct(err_union_llvm_ty, operand, err_field_index, "");
7630 break :loaded try self.wip.load(.normal, error_type, err_field_ptr, .default, "");7696 break :loaded try self.wip.load(access_kind, error_type, err_field_ptr, .default, "");
7631 } else try self.wip.extractValue(operand, &.{err_field_index}, "");7697 } else try self.wip.extractValue(operand, &.{err_field_index}, "");
7632 return self.wip.icmp(cond, loaded, zero, "");7698 return self.wip.icmp(cond, loaded, zero, "");
7633 }7699 }
...@@ -7660,12 +7726,19 @@ pub const FuncGen = struct {...@@ -7660,12 +7726,19 @@ pub const FuncGen = struct {
7660 const zcu = pt.zcu;7726 const zcu = pt.zcu;
7661 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;7727 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
7662 const operand = try self.resolveInst(ty_op.operand);7728 const operand = try self.resolveInst(ty_op.operand);
7663 const optional_ty = self.typeOf(ty_op.operand).childType(zcu);7729 const optional_ptr_ty = self.typeOf(ty_op.operand);
7730 const optional_ty = optional_ptr_ty.childType(zcu);
7664 const payload_ty = optional_ty.optionalChild(zcu);7731 const payload_ty = optional_ty.optionalChild(zcu);
7665 const non_null_bit = try o.builder.intValue(.i8, 1);7732 const non_null_bit = try o.builder.intValue(.i8, 1);
7733
7734 const access_kind: Builder.MemoryAccessKind =
7735 if (optional_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
7736
7666 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {7737 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7738 self.maybeMarkAllowZeroAccess(optional_ptr_ty.ptrInfo(zcu));
7739
7667 // We have a pointer to a i8. We need to set it to 1 and then return the same pointer.7740 // We have a pointer to a i8. We need to set it to 1 and then return the same pointer.
7668 _ = try self.wip.store(.normal, non_null_bit, operand, .default);7741 _ = try self.wip.store(access_kind, non_null_bit, operand, .default);
7669 return operand;7742 return operand;
7670 }7743 }
7671 if (optional_ty.optionalReprIsPayload(zcu)) {7744 if (optional_ty.optionalReprIsPayload(zcu)) {
...@@ -7677,8 +7750,11 @@ pub const FuncGen = struct {...@@ -7677,8 +7750,11 @@ pub const FuncGen = struct {
7677 // First set the non-null bit.7750 // First set the non-null bit.
7678 const optional_llvm_ty = try o.lowerType(optional_ty);7751 const optional_llvm_ty = try o.lowerType(optional_ty);
7679 const non_null_ptr = try self.wip.gepStruct(optional_llvm_ty, operand, 1, "");7752 const non_null_ptr = try self.wip.gepStruct(optional_llvm_ty, operand, 1, "");
7753
7754 self.maybeMarkAllowZeroAccess(optional_ptr_ty.ptrInfo(zcu));
7755
7680 // TODO set alignment on this store7756 // TODO set alignment on this store
7681 _ = try self.wip.store(.normal, non_null_bit, non_null_ptr, .default);7757 _ = try self.wip.store(access_kind, non_null_bit, non_null_ptr, .default);
76827758
7683 // Then return the payload pointer (only if it's used).7759 // Then return the payload pointer (only if it's used).
7684 if (self.liveness.isUnused(inst)) return .none;7760 if (self.liveness.isUnused(inst)) return .none;
...@@ -7764,18 +7840,26 @@ pub const FuncGen = struct {...@@ -7764,18 +7840,26 @@ pub const FuncGen = struct {
7764 }7840 }
7765 }7841 }
77667842
7843 const access_kind: Builder.MemoryAccessKind =
7844 if (operand_is_ptr and operand_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
7845
7767 const payload_ty = err_union_ty.errorUnionPayload(zcu);7846 const payload_ty = err_union_ty.errorUnionPayload(zcu);
7768 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {7847 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7769 if (!operand_is_ptr) return operand;7848 if (!operand_is_ptr) return operand;
7770 return self.wip.load(.normal, error_type, operand, .default, "");7849
7850 self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7851
7852 return self.wip.load(access_kind, error_type, operand, .default, "");
7771 }7853 }
77727854
7773 const offset = try errUnionErrorOffset(payload_ty, pt);7855 const offset = try errUnionErrorOffset(payload_ty, pt);
77747856
7775 if (operand_is_ptr or isByRef(err_union_ty, zcu)) {7857 if (operand_is_ptr or isByRef(err_union_ty, zcu)) {
7858 if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7859
7776 const err_union_llvm_ty = try o.lowerType(err_union_ty);7860 const err_union_llvm_ty = try o.lowerType(err_union_ty);
7777 const err_field_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, offset, "");7861 const err_field_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, offset, "");
7778 return self.wip.load(.normal, error_type, err_field_ptr, .default, "");7862 return self.wip.load(access_kind, error_type, err_field_ptr, .default, "");
7779 }7863 }
77807864
7781 return self.wip.extractValue(operand, &.{offset}, "");7865 return self.wip.extractValue(operand, &.{offset}, "");
...@@ -7787,22 +7871,31 @@ pub const FuncGen = struct {...@@ -7787,22 +7871,31 @@ pub const FuncGen = struct {
7787 const zcu = pt.zcu;7871 const zcu = pt.zcu;
7788 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;7872 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
7789 const operand = try self.resolveInst(ty_op.operand);7873 const operand = try self.resolveInst(ty_op.operand);
7790 const err_union_ty = self.typeOf(ty_op.operand).childType(zcu);7874 const err_union_ptr_ty = self.typeOf(ty_op.operand);
7875 const err_union_ty = err_union_ptr_ty.childType(zcu);
77917876
7792 const payload_ty = err_union_ty.errorUnionPayload(zcu);7877 const payload_ty = err_union_ty.errorUnionPayload(zcu);
7793 const non_error_val = try o.builder.intValue(try o.errorIntType(), 0);7878 const non_error_val = try o.builder.intValue(try o.errorIntType(), 0);
7879
7880 const access_kind: Builder.MemoryAccessKind =
7881 if (err_union_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
7882
7794 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {7883 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7795 _ = try self.wip.store(.normal, non_error_val, operand, .default);7884 self.maybeMarkAllowZeroAccess(err_union_ptr_ty.ptrInfo(zcu));
7885
7886 _ = try self.wip.store(access_kind, non_error_val, operand, .default);
7796 return operand;7887 return operand;
7797 }7888 }
7798 const err_union_llvm_ty = try o.lowerType(err_union_ty);7889 const err_union_llvm_ty = try o.lowerType(err_union_ty);
7799 {7890 {
7891 self.maybeMarkAllowZeroAccess(err_union_ptr_ty.ptrInfo(zcu));
7892
7800 const err_int_ty = try pt.errorIntType();7893 const err_int_ty = try pt.errorIntType();
7801 const error_alignment = err_int_ty.abiAlignment(zcu).toLlvm();7894 const error_alignment = err_int_ty.abiAlignment(zcu).toLlvm();
7802 const error_offset = try errUnionErrorOffset(payload_ty, pt);7895 const error_offset = try errUnionErrorOffset(payload_ty, pt);
7803 // First set the non-error value.7896 // First set the non-error value.
7804 const non_null_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, error_offset, "");7897 const non_null_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, error_offset, "");
7805 _ = try self.wip.store(.normal, non_error_val, non_null_ptr, error_alignment);7898 _ = try self.wip.store(access_kind, non_error_val, non_null_ptr, error_alignment);
7806 }7899 }
7807 // Then return the payload pointer (only if it is used).7900 // Then return the payload pointer (only if it is used).
7808 if (self.liveness.isUnused(inst)) return .none;7901 if (self.liveness.isUnused(inst)) return .none;
...@@ -8017,6 +8110,10 @@ pub const FuncGen = struct {...@@ -8017,6 +8110,10 @@ pub const FuncGen = struct {
8017 const index = try self.resolveInst(extra.lhs);8110 const index = try self.resolveInst(extra.lhs);
8018 const operand = try self.resolveInst(extra.rhs);8111 const operand = try self.resolveInst(extra.rhs);
80198112
8113 self.maybeMarkAllowZeroAccess(vector_ptr_ty.ptrInfo(zcu));
8114
8115 // TODO: Emitting a load here is a violation of volatile semantics. Not fixable in general.
8116 // https://github.com/ziglang/zig/issues/18652#issuecomment-2452844908
8020 const access_kind: Builder.MemoryAccessKind =8117 const access_kind: Builder.MemoryAccessKind =
8021 if (vector_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;8118 if (vector_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
8022 const elem_llvm_ty = try o.lowerType(vector_ptr_ty.childType(zcu));8119 const elem_llvm_ty = try o.lowerType(vector_ptr_ty.childType(zcu));
...@@ -9482,6 +9579,8 @@ pub const FuncGen = struct {...@@ -9482,6 +9579,8 @@ pub const FuncGen = struct {
9482 return .none;9579 return .none;
9483 }9580 }
94849581
9582 self.maybeMarkAllowZeroAccess(ptr_info);
9583
9485 const len = try o.builder.intValue(try o.lowerType(Type.usize), operand_ty.abiSize(zcu));9584 const len = try o.builder.intValue(try o.lowerType(Type.usize), operand_ty.abiSize(zcu));
9486 _ = try self.wip.callMemSet(9585 _ = try self.wip.callMemSet(
9487 dest_ptr,9586 dest_ptr,
...@@ -9497,6 +9596,8 @@ pub const FuncGen = struct {...@@ -9497,6 +9596,8 @@ pub const FuncGen = struct {
9497 return .none;9596 return .none;
9498 }9597 }
94999598
9599 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9600
9500 const src_operand = try self.resolveInst(bin_op.rhs);9601 const src_operand = try self.resolveInst(bin_op.rhs);
9501 try self.store(dest_ptr, ptr_ty, src_operand, .none);9602 try self.store(dest_ptr, ptr_ty, src_operand, .none);
9502 return .none;9603 return .none;
...@@ -9539,6 +9640,9 @@ pub const FuncGen = struct {...@@ -9539,6 +9640,9 @@ pub const FuncGen = struct {
9539 if (!canElideLoad(fg, body_tail)) break :elide;9640 if (!canElideLoad(fg, body_tail)) break :elide;
9540 return ptr;9641 return ptr;
9541 }9642 }
9643
9644 fg.maybeMarkAllowZeroAccess(ptr_info);
9645
9542 return fg.load(ptr, ptr_ty);9646 return fg.load(ptr, ptr_ty);
9543 }9647 }
95449648
...@@ -9598,6 +9702,8 @@ pub const FuncGen = struct {...@@ -9598,6 +9702,8 @@ pub const FuncGen = struct {
9598 new_value = try self.wip.conv(signedness, new_value, llvm_abi_ty, "");9702 new_value = try self.wip.conv(signedness, new_value, llvm_abi_ty, "");
9599 }9703 }
96009704
9705 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9706
9601 const result = try self.wip.cmpxchg(9707 const result = try self.wip.cmpxchg(
9602 kind,9708 kind,
9603 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,9709 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
...@@ -9649,6 +9755,8 @@ pub const FuncGen = struct {...@@ -9649,6 +9755,8 @@ pub const FuncGen = struct {
9649 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;9755 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
9650 const ptr_alignment = ptr_ty.ptrAlignment(zcu).toLlvm();9756 const ptr_alignment = ptr_ty.ptrAlignment(zcu).toLlvm();
96519757
9758 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9759
9652 if (llvm_abi_ty != .none) {9760 if (llvm_abi_ty != .none) {
9653 // operand needs widening and truncating or bitcasting.9761 // operand needs widening and truncating or bitcasting.
9654 return self.wip.cast(if (is_float) .bitcast else .trunc, try self.wip.atomicrmw(9762 return self.wip.cast(if (is_float) .bitcast else .trunc, try self.wip.atomicrmw(
...@@ -9712,6 +9820,8 @@ pub const FuncGen = struct {...@@ -9712,6 +9820,8 @@ pub const FuncGen = struct {
9712 if (info.flags.is_volatile) .@"volatile" else .normal;9820 if (info.flags.is_volatile) .@"volatile" else .normal;
9713 const elem_llvm_ty = try o.lowerType(elem_ty);9821 const elem_llvm_ty = try o.lowerType(elem_ty);
97149822
9823 self.maybeMarkAllowZeroAccess(info);
9824
9715 if (llvm_abi_ty != .none) {9825 if (llvm_abi_ty != .none) {
9716 // operand needs widening and truncating9826 // operand needs widening and truncating
9717 const loaded = try self.wip.loadAtomic(9827 const loaded = try self.wip.loadAtomic(
...@@ -9761,6 +9871,9 @@ pub const FuncGen = struct {...@@ -9761,6 +9871,9 @@ pub const FuncGen = struct {
9761 "",9871 "",
9762 );9872 );
9763 }9873 }
9874
9875 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9876
9764 try self.store(ptr, ptr_ty, element, ordering);9877 try self.store(ptr, ptr_ty, element, ordering);
9765 return .none;9878 return .none;
9766 }9879 }
...@@ -9778,6 +9891,8 @@ pub const FuncGen = struct {...@@ -9778,6 +9891,8 @@ pub const FuncGen = struct {
9778 const access_kind: Builder.MemoryAccessKind =9891 const access_kind: Builder.MemoryAccessKind =
9779 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;9892 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
97809893
9894 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9895
9781 if (try self.air.value(bin_op.rhs, pt)) |elem_val| {9896 if (try self.air.value(bin_op.rhs, pt)) |elem_val| {
9782 if (elem_val.isUndefDeep(zcu)) {9897 if (elem_val.isUndefDeep(zcu)) {
9783 // Even if safety is disabled, we still emit a memset to undefined since it conveys9898 // Even if safety is disabled, we still emit a memset to undefined since it conveys
...@@ -9916,6 +10031,9 @@ pub const FuncGen = struct {...@@ -9916,6 +10031,9 @@ pub const FuncGen = struct {
9916 const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or10031 const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or
9917 dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;10032 dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
991810033
10034 self.maybeMarkAllowZeroAccess(dest_ptr_ty.ptrInfo(zcu));
10035 self.maybeMarkAllowZeroAccess(src_ptr_ty.ptrInfo(zcu));
10036
9919 _ = try self.wip.callMemCpy(10037 _ = try self.wip.callMemCpy(
9920 dest_ptr,10038 dest_ptr,
9921 dest_ptr_ty.ptrAlignment(zcu).toLlvm(),10039 dest_ptr_ty.ptrAlignment(zcu).toLlvm(),
...@@ -9959,20 +10077,27 @@ pub const FuncGen = struct {...@@ -9959,20 +10077,27 @@ pub const FuncGen = struct {
9959 const pt = o.pt;10077 const pt = o.pt;
9960 const zcu = pt.zcu;10078 const zcu = pt.zcu;
9961 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;10079 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
9962 const un_ty = self.typeOf(bin_op.lhs).childType(zcu);10080 const un_ptr_ty = self.typeOf(bin_op.lhs);
10081 const un_ty = un_ptr_ty.childType(zcu);
9963 const layout = un_ty.unionGetLayout(zcu);10082 const layout = un_ty.unionGetLayout(zcu);
9964 if (layout.tag_size == 0) return .none;10083 if (layout.tag_size == 0) return .none;
10084
10085 const access_kind: Builder.MemoryAccessKind =
10086 if (un_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
10087
10088 self.maybeMarkAllowZeroAccess(un_ptr_ty.ptrInfo(zcu));
10089
9965 const union_ptr = try self.resolveInst(bin_op.lhs);10090 const union_ptr = try self.resolveInst(bin_op.lhs);
9966 const new_tag = try self.resolveInst(bin_op.rhs);10091 const new_tag = try self.resolveInst(bin_op.rhs);
9967 if (layout.payload_size == 0) {10092 if (layout.payload_size == 0) {
9968 // TODO alignment on this store10093 // TODO alignment on this store
9969 _ = try self.wip.store(.normal, new_tag, union_ptr, .default);10094 _ = try self.wip.store(access_kind, new_tag, union_ptr, .default);
9970 return .none;10095 return .none;
9971 }10096 }
9972 const tag_index = @intFromBool(layout.tag_align.compare(.lt, layout.payload_align));10097 const tag_index = @intFromBool(layout.tag_align.compare(.lt, layout.payload_align));
9973 const tag_field_ptr = try self.wip.gepStruct(try o.lowerType(un_ty), union_ptr, tag_index, "");10098 const tag_field_ptr = try self.wip.gepStruct(try o.lowerType(un_ty), union_ptr, tag_index, "");
9974 // TODO alignment on this store10099 // TODO alignment on this store
9975 _ = try self.wip.store(.normal, new_tag, tag_field_ptr, .default);10100 _ = try self.wip.store(access_kind, new_tag, tag_field_ptr, .default);
9976 return .none;10101 return .none;
9977 }10102 }
997810103
...@@ -10869,12 +10994,13 @@ pub const FuncGen = struct {...@@ -10869,12 +10994,13 @@ pub const FuncGen = struct {
10869 opt_llvm_ty: Builder.Type,10994 opt_llvm_ty: Builder.Type,
10870 opt_handle: Builder.Value,10995 opt_handle: Builder.Value,
10871 is_by_ref: bool,10996 is_by_ref: bool,
10997 access_kind: Builder.MemoryAccessKind,
10872 ) Allocator.Error!Builder.Value {10998 ) Allocator.Error!Builder.Value {
10873 const o = self.ng.object;10999 const o = self.ng.object;
10874 const field = b: {11000 const field = b: {
10875 if (is_by_ref) {11001 if (is_by_ref) {
10876 const field_ptr = try self.wip.gepStruct(opt_llvm_ty, opt_handle, 1, "");11002 const field_ptr = try self.wip.gepStruct(opt_llvm_ty, opt_handle, 1, "");
10877 break :b try self.wip.load(.normal, .i8, field_ptr, .default, "");11003 break :b try self.wip.load(access_kind, .i8, field_ptr, .default, "");
10878 }11004 }
10879 break :b try self.wip.extractValue(opt_handle, &.{1}, "");11005 break :b try self.wip.extractValue(opt_handle, &.{1}, "");
10880 };11006 };
...@@ -11183,7 +11309,7 @@ pub const FuncGen = struct {...@@ -11183,7 +11309,7 @@ pub const FuncGen = struct {
11183 const vec_elem_ty = try o.lowerType(elem_ty);11309 const vec_elem_ty = try o.lowerType(elem_ty);
11184 const vec_ty = try o.builder.vectorType(.normal, info.packed_offset.host_size, vec_elem_ty);11310 const vec_ty = try o.builder.vectorType(.normal, info.packed_offset.host_size, vec_elem_ty);
1118511311
11186 const loaded_vector = try self.wip.load(access_kind, vec_ty, ptr, ptr_alignment, "");11312 const loaded_vector = try self.wip.load(.normal, vec_ty, ptr, ptr_alignment, "");
1118711313
11188 const modified_vector = try self.wip.insertElement(loaded_vector, elem, index_u32, "");11314 const modified_vector = try self.wip.insertElement(loaded_vector, elem, index_u32, "");
1118911315
...@@ -11196,7 +11322,7 @@ pub const FuncGen = struct {...@@ -11196,7 +11322,7 @@ pub const FuncGen = struct {
11196 const containing_int_ty = try o.builder.intType(@intCast(info.packed_offset.host_size * 8));11322 const containing_int_ty = try o.builder.intType(@intCast(info.packed_offset.host_size * 8));
11197 assert(ordering == .none);11323 assert(ordering == .none);
11198 const containing_int =11324 const containing_int =
11199 try self.wip.load(access_kind, containing_int_ty, ptr, ptr_alignment, "");11325 try self.wip.load(.normal, containing_int_ty, ptr, ptr_alignment, "");
11200 const elem_bits = ptr_ty.childType(zcu).bitSize(zcu);11326 const elem_bits = ptr_ty.childType(zcu).bitSize(zcu);
11201 const shift_amt = try o.builder.intConst(containing_int_ty, info.packed_offset.bit_offset);11327 const shift_amt = try o.builder.intConst(containing_int_ty, info.packed_offset.bit_offset);
11202 // Convert to equally-sized integer type in order to perform the bit11328 // Convert to equally-sized integer type in order to perform the bit
src/main.zig+2-2
...@@ -471,8 +471,6 @@ const usage_build_generic =...@@ -471,8 +471,6 @@ const usage_build_generic =
471 \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports471 \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports
472 \\ -freference-trace[=num] Show num lines of reference trace per compile error472 \\ -freference-trace[=num] Show num lines of reference trace per compile error
473 \\ -fno-reference-trace Disable reference trace473 \\ -fno-reference-trace Disable reference trace
474 \\ -fbuiltin Enable implicit builtin knowledge of functions
475 \\ -fno-builtin Disable implicit builtin knowledge of functions
476 \\ -ffunction-sections Places each function in a separate section474 \\ -ffunction-sections Places each function in a separate section
477 \\ -fno-function-sections All functions go into same section475 \\ -fno-function-sections All functions go into same section
478 \\ -fdata-sections Places each data in a separate section476 \\ -fdata-sections Places each data in a separate section
...@@ -534,6 +532,8 @@ const usage_build_generic =...@@ -534,6 +532,8 @@ const usage_build_generic =
534 \\ -fno-sanitize-thread Disable Thread Sanitizer532 \\ -fno-sanitize-thread Disable Thread Sanitizer
535 \\ -ffuzz Enable fuzz testing instrumentation533 \\ -ffuzz Enable fuzz testing instrumentation
536 \\ -fno-fuzz Disable fuzz testing instrumentation534 \\ -fno-fuzz Disable fuzz testing instrumentation
535 \\ -fbuiltin Enable implicit builtin knowledge of functions
536 \\ -fno-builtin Disable implicit builtin knowledge of functions
537 \\ -funwind-tables Always produce unwind table entries for all functions537 \\ -funwind-tables Always produce unwind table entries for all functions
538 \\ -fasync-unwind-tables Always produce asynchronous unwind table entries for all functions538 \\ -fasync-unwind-tables Always produce asynchronous unwind table entries for all functions
539 \\ -fno-unwind-tables Never produce unwind table entries539 \\ -fno-unwind-tables Never produce unwind table entries
test/llvm_ir.zig created+122
...@@ -0,0 +1,122 @@
1pub fn addCases(cases: *tests.LlvmIrContext) void {
2 cases.addMatches("nonnull ptr load",
3 \\export fn entry(ptr: *i16) i16 {
4 \\ return ptr.*;
5 \\}
6 , &.{
7 "ptr nonnull",
8 "load i16, ptr %0",
9 }, .{});
10
11 cases.addMatches("nonnull ptr store",
12 \\export fn entry(ptr: *i16) void {
13 \\ ptr.* = 42;
14 \\}
15 , &.{
16 "ptr nonnull",
17 "store i16 42, ptr %0",
18 }, .{});
19
20 cases.addMatches("unused acquire atomic ptr load",
21 \\export fn entry(ptr: *i16) void {
22 \\ _ = @atomicLoad(i16, ptr, .acquire);
23 \\}
24 , &.{
25 "load atomic i16, ptr %0 acquire",
26 }, .{});
27
28 cases.addMatches("unused unordered atomic volatile ptr load",
29 \\export fn entry(ptr: *volatile i16) void {
30 \\ _ = @atomicLoad(i16, ptr, .unordered);
31 \\}
32 , &.{
33 "load atomic volatile i16, ptr %0 unordered",
34 }, .{});
35
36 cases.addMatches("unused volatile ptr load",
37 \\export fn entry(ptr: *volatile i16) void {
38 \\ _ = ptr.*;
39 \\}
40 , &.{
41 "load volatile i16, ptr %0",
42 }, .{});
43
44 cases.addMatches("dead volatile ptr store",
45 \\export fn entry(ptr: *volatile i16) void {
46 \\ ptr.* = 123;
47 \\ ptr.* = 321;
48 \\}
49 , &.{
50 "store volatile i16 123, ptr %0",
51 "store volatile i16 321, ptr %0",
52 }, .{});
53
54 cases.addMatches("unused volatile slice load",
55 \\export fn entry(ptr: *volatile i16) void {
56 \\ entry2(ptr[0..1]);
57 \\}
58 \\fn entry2(ptr: []volatile i16) void {
59 \\ _ = ptr[0];
60 \\}
61 , &.{
62 "load volatile i16, ptr",
63 }, .{});
64
65 cases.addMatches("dead volatile slice store",
66 \\export fn entry(ptr: *volatile i16) void {
67 \\ entry2(ptr[0..1]);
68 \\}
69 \\fn entry2(ptr: []volatile i16) void {
70 \\ ptr[0] = 123;
71 \\ ptr[0] = 321;
72 \\}
73 , &.{
74 "store volatile i16 123, ptr",
75 "store volatile i16 321, ptr",
76 }, .{});
77
78 cases.addMatches("allowzero ptr load",
79 \\export fn entry(ptr: *allowzero i16) i16 {
80 \\ return ptr.*;
81 \\}
82 , &.{
83 "null_pointer_is_valid",
84 "load i16, ptr %0",
85 }, .{});
86
87 cases.addMatches("allowzero ptr store",
88 \\export fn entry(ptr: *allowzero i16) void {
89 \\ ptr.* = 42;
90 \\}
91 , &.{
92 "null_pointer_is_valid",
93 "store i16 42, ptr %0",
94 }, .{});
95
96 cases.addMatches("allowzero slice load",
97 \\export fn entry(ptr: *allowzero i16) i16 {
98 \\ return entry2(ptr[0..1]);
99 \\}
100 \\fn entry2(ptr: []allowzero i16) i16 {
101 \\ return ptr[0];
102 \\}
103 , &.{
104 "null_pointer_is_valid",
105 "load i16, ptr",
106 }, .{});
107
108 cases.addMatches("allowzero slice store",
109 \\export fn entry(ptr: *allowzero i16) void {
110 \\ entry2(ptr[0..1]);
111 \\}
112 \\fn entry2(ptr: []allowzero i16) void {
113 \\ ptr[0] = 42;
114 \\}
115 , &.{
116 "null_pointer_is_valid",
117 "store i16 42, ptr",
118 }, .{});
119}
120
121const std = @import("std");
122const tests = @import("tests.zig");
test/src/Debugger.zig+4-2
...@@ -2442,8 +2442,10 @@ fn addTest(...@@ -2442,8 +2442,10 @@ fn addTest(
2442 db_argv2: []const []const u8,2442 db_argv2: []const []const u8,
2443 expected_output: []const []const u8,2443 expected_output: []const []const u8,
2444) void {2444) void {
2445 for (db.options.test_filters) |test_filter| {2445 if (db.options.test_filters.len > 0) {
2446 if (std.mem.indexOf(u8, name, test_filter)) |_| return;2446 for (db.options.test_filters) |test_filter| {
2447 if (std.mem.indexOf(u8, name, test_filter) != null) break;
2448 } else return;
2447 }2449 }
2448 if (db.options.test_target_filters.len > 0) {2450 if (db.options.test_target_filters.len > 0) {
2449 const triple_txt = target.resolved.result.zigTriple(db.b.allocator) catch @panic("OOM");2451 const triple_txt = target.resolved.result.zigTriple(db.b.allocator) catch @panic("OOM");
test/src/LlvmIr.zig created+132
...@@ -0,0 +1,132 @@
1b: *std.Build,
2options: Options,
3root_step: *std.Build.Step,
4
5pub const Options = struct {
6 enable_llvm: bool,
7 test_filters: []const []const u8,
8 test_target_filters: []const []const u8,
9};
10
11const TestCase = struct {
12 name: []const u8,
13 source: []const u8,
14 check: union(enum) {
15 matches: []const []const u8,
16 exact: []const u8,
17 },
18 params: Params,
19
20 pub const Params = struct {
21 code_model: std.builtin.CodeModel = .default,
22 dll_export_fns: ?bool = null,
23 dwarf_format: ?std.dwarf.Format = null,
24 error_tracing: ?bool = null,
25 no_builtin: ?bool = null,
26 omit_frame_pointer: ?bool = null,
27 // For most cases, we want to test the LLVM IR that we output; we don't want to be in the
28 // business of testing LLVM's optimization passes. `Debug` gets us the closest to that as it
29 // disables the vast majority of passes in LLVM.
30 optimize: std.builtin.OptimizeMode = .Debug,
31 pic: ?bool = null,
32 pie: ?bool = null,
33 red_zone: ?bool = null,
34 sanitize_thread: ?bool = null,
35 single_threaded: ?bool = null,
36 stack_check: ?bool = null,
37 stack_protector: ?bool = null,
38 strip: ?bool = null,
39 target: std.Target.Query = .{},
40 unwind_tables: ?std.builtin.UnwindTables = null,
41 valgrind: ?bool = null,
42 };
43};
44
45pub fn addMatches(
46 self: *LlvmIr,
47 name: []const u8,
48 source: []const u8,
49 matches: []const []const u8,
50 params: TestCase.Params,
51) void {
52 self.addCase(.{
53 .name = name,
54 .source = source,
55 .check = .{ .matches = matches },
56 .params = params,
57 });
58}
59
60pub fn addExact(
61 self: *LlvmIr,
62 name: []const u8,
63 source: []const u8,
64 expected: []const []const u8,
65 params: TestCase.Params,
66) void {
67 self.addCase(.{
68 .name = name,
69 .source = source,
70 .check = .{ .exact = expected },
71 .params = params,
72 });
73}
74
75pub fn addCase(self: *LlvmIr, case: TestCase) void {
76 const target = self.b.resolveTargetQuery(case.params.target);
77 if (self.options.test_target_filters.len > 0) {
78 const triple_txt = target.result.zigTriple(self.b.allocator) catch @panic("OOM");
79 for (self.options.test_target_filters) |filter| {
80 if (std.mem.indexOf(u8, triple_txt, filter) != null) break;
81 } else return;
82 }
83
84 const name = std.fmt.allocPrint(self.b.allocator, "check llvm-ir {s}", .{case.name}) catch @panic("OOM");
85 if (self.options.test_filters.len > 0) {
86 for (self.options.test_filters) |filter| {
87 if (std.mem.indexOf(u8, name, filter) != null) break;
88 } else return;
89 }
90
91 const obj = self.b.addObject(.{
92 .name = "test",
93 .root_source_file = self.b.addWriteFiles().add("test.zig", case.source),
94 .use_llvm = true,
95
96 .code_model = case.params.code_model,
97 .error_tracing = case.params.error_tracing,
98 .omit_frame_pointer = case.params.omit_frame_pointer,
99 .optimize = case.params.optimize,
100 .pic = case.params.pic,
101 .sanitize_thread = case.params.sanitize_thread,
102 .single_threaded = case.params.single_threaded,
103 .strip = case.params.strip,
104 .target = target,
105 .unwind_tables = case.params.unwind_tables,
106 });
107
108 obj.dll_export_fns = case.params.dll_export_fns;
109 obj.pie = case.params.pie;
110
111 obj.root_module.dwarf_format = case.params.dwarf_format;
112 obj.root_module.no_builtin = case.params.no_builtin;
113 obj.root_module.red_zone = case.params.red_zone;
114 obj.root_module.stack_check = case.params.stack_check;
115 obj.root_module.stack_protector = case.params.stack_protector;
116 obj.root_module.valgrind = case.params.valgrind;
117
118 // This is not very sophisticated at the moment. Eventually, we should move towards something
119 // like LLVM's `FileCheck` utility (https://llvm.org/docs/CommandGuide/FileCheck.html), though
120 // likely a more simplified version as we probably don't want a full-blown regex engine in the
121 // standard library...
122 const check = self.b.addCheckFile(obj.getEmittedLlvmIr(), switch (case.check) {
123 .matches => |m| .{ .expected_matches = m },
124 .exact => |e| .{ .expected_exact = e },
125 });
126 check.setName(name);
127
128 self.root_step.dependOn(&check.step);
129}
130
131const LlvmIr = @This();
132const std = @import("std");
test/tests.zig+22-1
...@@ -11,6 +11,7 @@ const stack_traces = @import("stack_traces.zig");...@@ -11,6 +11,7 @@ const stack_traces = @import("stack_traces.zig");
11const assemble_and_link = @import("assemble_and_link.zig");11const assemble_and_link = @import("assemble_and_link.zig");
12const translate_c = @import("translate_c.zig");12const translate_c = @import("translate_c.zig");
13const run_translated_c = @import("run_translated_c.zig");13const run_translated_c = @import("run_translated_c.zig");
14const llvm_ir = @import("llvm_ir.zig");
1415
15// Implementations16// Implementations
16pub const TranslateCContext = @import("src/TranslateC.zig");17pub const TranslateCContext = @import("src/TranslateC.zig");
...@@ -18,6 +19,7 @@ pub const RunTranslatedCContext = @import("src/RunTranslatedC.zig");...@@ -18,6 +19,7 @@ pub const RunTranslatedCContext = @import("src/RunTranslatedC.zig");
18pub const CompareOutputContext = @import("src/CompareOutput.zig");19pub const CompareOutputContext = @import("src/CompareOutput.zig");
19pub const StackTracesContext = @import("src/StackTrace.zig");20pub const StackTracesContext = @import("src/StackTrace.zig");
20pub const DebuggerContext = @import("src/Debugger.zig");21pub const DebuggerContext = @import("src/Debugger.zig");
22pub const LlvmIrContext = @import("src/LlvmIr.zig");
2123
22const TestTarget = struct {24const TestTarget = struct {
23 linkage: ?std.builtin.LinkMode = null,25 linkage: ?std.builtin.LinkMode = null,
...@@ -1825,7 +1827,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {...@@ -1825,7 +1827,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
1825 .zig_lib_dir = b.path("lib"),1827 .zig_lib_dir = b.path("lib"),
1826 });1828 });
1827 these_tests.linkage = test_target.linkage;1829 these_tests.linkage = test_target.linkage;
1828 if (options.no_builtin) these_tests.no_builtin = true;1830 if (options.no_builtin) these_tests.root_module.no_builtin = false;
1829 if (options.build_options) |build_options| {1831 if (options.build_options) |build_options| {
1830 these_tests.root_module.addOptions("build_options", build_options);1832 these_tests.root_module.addOptions("build_options", build_options);
1831 }1833 }
...@@ -2125,3 +2127,22 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {...@@ -2125,3 +2127,22 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
2125 test_step.dependOn(&run.step);2127 test_step.dependOn(&run.step);
2126 }2128 }
2127}2129}
2130
2131pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {
2132 const step = b.step("test-llvm-ir", "Run the LLVM IR tests");
2133
2134 if (!options.enable_llvm) {
2135 step.dependOn(&b.addFail("test-llvm-ir requires -Denable-llvm").step);
2136 return null;
2137 }
2138
2139 var context: LlvmIrContext = .{
2140 .b = b,
2141 .options = options,
2142 .root_step = step,
2143 };
2144
2145 llvm_ir.addCases(&context);
2146
2147 return step;
2148}