Compare commits

...

12 Commits

Author SHA1 Message Date
Alex Rønne Petersen
782e9a625d
Merge 8fd99f8b73 into f845fa04a0 2024-11-21 07:50:45 +01:00
Alex Rønne Petersen
f845fa04a0 std.debug: Gracefully handle process_vm_readv() EPERM in MemoryAccessor.read().
Closes #21815.
2024-11-20 23:07:46 +01:00
Alex Rønne Petersen
8fd99f8b73
llvm: Fix a bunch of volatile semantics violations.
Also fix some cases where we were being overzealous in applying volatile.
2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
b143202da0
llvm: Don't set nonnull attribute on pointers in non-generic address spaces.
LLVM considers null pointers to be valid for such address spaces.
2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
9948a093ff
llvm: Don't set nonnull attribute on allowzero slices. 2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
8e00f47685
llvm: Set null_pointer_is_valid attribute when accessing allowzero pointers.
This informs optimization passes that they shouldn't assume that a load from a
null pointer invokes undefined behavior.

Closes #15816.
2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
c4f7a7cd27
Air: Fix mustLower() to consider volatile for a handful of instructions.
These can all potentially operate on volatile pointers.
2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
d5dcd2b9d0
Air: Always return true for inline assembly in mustLower().
AstGen requires inline assembly to either have outputs or be marked volatile, so
there doesn't appear to be any point in doing these checks.
2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
f13011c843
Air: Fix mustLower() for atomic_load with inter-thread ordering. 2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
cc507c5024
riscv64: Handle writes to the zero register sensibly in result bookkeeping. 2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
dfd203609d
riscv64: Add missing fence for seq_cst atomic_store. 2024-11-02 09:00:45 +01:00
Alex Rønne Petersen
81f53573d8
riscv64: Get rid of some trailing whitespace. 2024-11-02 09:00:45 +01:00
5 changed files with 256 additions and 105 deletions

View File

@ -48,7 +48,8 @@ fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool {
switch (linux.E.init(bytes_read)) {
.SUCCESS => return bytes_read == buf.len,
.FAULT => return false,
.INVAL, .PERM, .SRCH => unreachable, // own pid is always valid
.INVAL, .SRCH => unreachable, // own pid is always valid
.PERM => {}, // Known to happen in containers.
.NOMEM => {},
.NOSYS => {}, // QEMU is known not to implement this syscall.
else => unreachable, // unexpected

View File

@ -1610,6 +1610,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
const data = air.instructions.items(.data)[@intFromEnum(inst)];
return switch (air.instructions.items(.tag)[@intFromEnum(inst)]) {
.arg,
.assembly,
.block,
.loop,
.repeat,
@ -1750,12 +1751,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.cmp_vector_optimized,
.is_null,
.is_non_null,
.is_null_ptr,
.is_non_null_ptr,
.is_err,
.is_non_err,
.is_err_ptr,
.is_non_err_ptr,
.bool_and,
.bool_or,
.int_from_ptr,
@ -1770,7 +1767,6 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.unwrap_errunion_payload,
.unwrap_errunion_err,
.unwrap_errunion_payload_ptr,
.unwrap_errunion_err_ptr,
.wrap_errunion_payload,
.wrap_errunion_err,
.struct_field_ptr,
@ -1815,17 +1811,13 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.work_group_id,
=> false,
.assembly => {
const extra = air.extraData(Air.Asm, data.ty_pl.payload);
const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
return is_volatile or if (extra.data.outputs_len == 1)
@as(Air.Inst.Ref, @enumFromInt(air.extra[extra.end])) != .none
else
extra.data.outputs_len > 1;
},
.load => air.typeOf(data.ty_op.operand, ip).isVolatilePtrIp(ip),
.is_non_null_ptr, .is_null_ptr, .is_non_err_ptr, .is_err_ptr => air.typeOf(data.un_op, ip).isVolatilePtrIp(ip),
.load, .unwrap_errunion_err_ptr => air.typeOf(data.ty_op.operand, ip).isVolatilePtrIp(ip),
.slice_elem_val, .ptr_elem_val => air.typeOf(data.bin_op.lhs, ip).isVolatilePtrIp(ip),
.atomic_load => air.typeOf(data.atomic_load.ptr, ip).isVolatilePtrIp(ip),
.atomic_load => switch (data.atomic_load.order) {
.unordered, .monotonic => air.typeOf(data.atomic_load.ptr, ip).isVolatilePtrIp(ip),
else => true, // Stronger memory orderings have inter-thread side effects.
},
};
}

View File

@ -1503,7 +1503,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.mul,
.mul_wrap,
.div_trunc,
.div_trunc,
.div_exact,
.rem,
@ -1521,13 +1521,13 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.max,
=> try func.airBinOp(inst, tag),
.ptr_add,
.ptr_sub => try func.airPtrArithmetic(inst, tag),
.mod,
.div_float,
.div_floor,
.div_float,
.div_floor,
=> return func.fail("TODO: {s}", .{@tagName(tag)}),
.sqrt,
@ -1681,7 +1681,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),
.array_elem_val => try func.airArrayElemVal(inst),
.slice_elem_val => try func.airSliceElemVal(inst),
.slice_elem_ptr => try func.airSliceElemPtr(inst),
@ -1811,8 +1811,15 @@ fn finishAirBookkeeping(func: *Func) void {
fn finishAirResult(func: *Func, inst: Air.Inst.Index, result: MCValue) void {
if (func.liveness.isUnused(inst)) switch (result) {
.none, .dead, .unreach => {},
else => unreachable, // Why didn't the result die?
// Why didn't the result die?
.register => |r| if (r != .zero) unreachable,
else => unreachable,
} else {
switch (result) {
.register => |r| if (r == .zero) unreachable, // Why did we discard a used result?
else => {},
}
tracking_log.debug("%{d} => {} (birth)", .{ inst, result });
func.inst_tracking.putAssumeCapacityNoClobber(inst, InstTracking.init(result));
// In some cases, an operand may be reused as the result.
@ -7785,48 +7792,56 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {
const pt = func.pt;
const zcu = pt.zcu;
const atomic_load = func.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
const order: std.builtin.AtomicOrder = atomic_load.order;
const result: MCValue = result: {
const order: std.builtin.AtomicOrder = atomic_load.order;
const ptr_ty = func.typeOf(atomic_load.ptr);
const elem_ty = ptr_ty.childType(zcu);
const ptr_mcv = try func.resolveInst(atomic_load.ptr);
const ptr_ty = func.typeOf(atomic_load.ptr);
const elem_ty = ptr_ty.childType(zcu);
const ptr_mcv = try func.resolveInst(atomic_load.ptr);
const bit_size = elem_ty.bitSize(zcu);
if (bit_size > 64) return func.fail("TODO: airAtomicStore > 64 bits", .{});
const bit_size = elem_ty.bitSize(zcu);
if (bit_size > 64) return func.fail("TODO: airAtomicLoad > 64 bits", .{});
const result_mcv = try func.allocRegOrMem(elem_ty, inst, true);
assert(result_mcv == .register); // should be less than 8 bytes
const unused = func.liveness.isUnused(inst);
if (order == .seq_cst) {
_ = try func.addInst(.{
.tag = .fence,
.data = .{ .fence = .{
.pred = .rw,
.succ = .rw,
} },
});
}
const result_mcv: MCValue = if (func.liveness.isUnused(inst))
.{ .register = .zero }
else
try func.allocRegOrMem(elem_ty, inst, true);
assert(result_mcv == .register); // should be less than 8 bytes
try func.load(result_mcv, ptr_mcv, ptr_ty);
switch (order) {
// Don't guarnetee other memory operations to be ordered after the load.
.unordered => {},
.monotonic => {},
// Make sure all previous reads happen before any reading or writing accurs.
.seq_cst, .acquire => {
if (order == .seq_cst) {
_ = try func.addInst(.{
.tag = .fence,
.data = .{ .fence = .{
.pred = .r,
.pred = .rw,
.succ = .rw,
} },
});
},
else => unreachable,
}
}
return func.finishAir(inst, result_mcv, .{ atomic_load.ptr, .none, .none });
try func.load(result_mcv, ptr_mcv, ptr_ty);
switch (order) {
// Don't guarantee other memory operations to be ordered after the load.
.unordered, .monotonic => {},
// Make sure all previous reads happen before any reading or writing occurs.
.acquire, .seq_cst => {
_ = try func.addInst(.{
.tag = .fence,
.data = .{ .fence = .{
.pred = .r,
.succ = .rw,
} },
});
},
else => unreachable,
}
break :result if (unused) .unreach else result_mcv;
};
return func.finishAir(inst, result, .{ atomic_load.ptr, .none, .none });
}
fn airAtomicStore(func: *Func, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {
@ -7856,6 +7871,17 @@ fn airAtomicStore(func: *Func, inst: Air.Inst.Index, order: std.builtin.AtomicOr
}
try func.store(ptr_mcv, val_mcv, ptr_ty);
if (order == .seq_cst) {
_ = try func.addInst(.{
.tag = .fence,
.data = .{ .fence = .{
.pred = .rw,
.succ = .rw,
} },
});
}
return func.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none });
}

View File

@ -16596,23 +16596,29 @@ fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void {
const atomic_load = self.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
const result: MCValue = result: {
const ptr_ty = self.typeOf(atomic_load.ptr);
const ptr_mcv = try self.resolveInst(atomic_load.ptr);
const ptr_lock = switch (ptr_mcv) {
.register => |reg| self.register_manager.lockRegAssumeUnused(reg),
else => null,
};
defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock);
const ptr_ty = self.typeOf(atomic_load.ptr);
const ptr_mcv = try self.resolveInst(atomic_load.ptr);
const ptr_lock = switch (ptr_mcv) {
.register => |reg| self.register_manager.lockRegAssumeUnused(reg),
else => null,
const unused = self.liveness.isUnused(inst);
const dst_mcv: MCValue = if (unused)
.{ .register = try self.register_manager.allocReg(null, self.regClassForType(ptr_ty.childType(self.pt.zcu))) }
else if (self.reuseOperand(inst, atomic_load.ptr, 0, ptr_mcv))
ptr_mcv
else
try self.allocRegOrMem(inst, true);
try self.load(dst_mcv, ptr_ty, ptr_mcv);
break :result if (unused) .unreach else dst_mcv;
};
defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock);
const dst_mcv =
if (self.reuseOperand(inst, atomic_load.ptr, 0, ptr_mcv))
ptr_mcv
else
try self.allocRegOrMem(inst, true);
try self.load(dst_mcv, ptr_ty, ptr_mcv);
return self.finishAir(inst, dst_mcv, .{ atomic_load.ptr, .none, .none });
return self.finishAir(inst, result, .{ atomic_load.ptr, .none, .none });
}
fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {

View File

@ -1696,7 +1696,10 @@ pub const Object = struct {
try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
}
}
if (param_ty.zigTypeTag(zcu) != .optional) {
if (param_ty.zigTypeTag(zcu) != .optional and
!ptr_info.flags.is_allowzero and
ptr_info.flags.address_space == .generic)
{
try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
}
if (ptr_info.flags.is_const) {
@ -1774,8 +1777,6 @@ pub const Object = struct {
}
}
function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
const file, const subprogram = if (!wip.strip) debug_info: {
const file = try o.getDebugFile(file_scope);
@ -1866,6 +1867,17 @@ pub const Object = struct {
else => |e| return e,
};
// If we saw any loads or stores involving `allowzero` pointers, we need to mark the whole
// function as considering null pointers valid so that LLVM's optimizers don't remove these
// operations on the assumption that they're undefined behavior.
if (fg.allowzero_access) {
try attributes.addFnAttr(.null_pointer_is_valid, &o.builder);
} else {
_ = try attributes.removeFnAttr(.null_pointer_is_valid);
}
function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
if (fg.fuzz) |*f| {
{
const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
@ -4723,7 +4735,10 @@ pub const Object = struct {
try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
}
}
if (!param_ty.isPtrLikeOptional(zcu) and !ptr_info.flags.is_allowzero) {
if (!param_ty.isPtrLikeOptional(zcu) and
!ptr_info.flags.is_allowzero and
ptr_info.flags.address_space == .generic)
{
try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
}
switch (fn_info.cc) {
@ -5039,6 +5054,15 @@ pub const FuncGen = struct {
sync_scope: Builder.SyncScope,
/// Have we seen loads or stores involving `allowzero` pointers?
allowzero_access: bool = false,
pub fn maybeMarkAllowZeroAccess(self: *FuncGen, info: InternPool.Key.PtrType) void {
// LLVM already considers null pointers to be valid in non-generic address spaces, so avoid
// pessimizing optimization for functions with accesses to such pointers.
if (info.flags.address_space == .generic and info.flags.is_allowzero) self.allowzero_access = true;
}
const Fuzz = struct {
counters_variable: Builder.Variable.Index,
pcs: std.ArrayListUnmanaged(Builder.Constant),
@ -5775,7 +5799,10 @@ pub const FuncGen = struct {
try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
}
}
if (param_ty.zigTypeTag(zcu) != .optional) {
if (param_ty.zigTypeTag(zcu) != .optional and
!ptr_info.flags.is_allowzero and
ptr_info.flags.address_space == .generic)
{
try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
}
if (ptr_info.flags.is_const) {
@ -5919,7 +5946,7 @@ pub const FuncGen = struct {
ptr_ty.ptrAlignment(zcu).toLlvm(),
try o.builder.intValue(.i8, 0xaa),
len,
if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
.normal,
);
const owner_mod = self.ng.ownerModule();
if (owner_mod.valgrind) {
@ -6152,8 +6179,8 @@ pub const FuncGen = struct {
// of optionals that are not pointers.
const is_by_ref = isByRef(scalar_ty, zcu);
const opt_llvm_ty = try o.lowerType(scalar_ty);
const lhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, lhs, is_by_ref);
const rhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, rhs, is_by_ref);
const lhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, lhs, is_by_ref, .normal);
const rhs_non_null = try self.optCmpNull(.ne, opt_llvm_ty, rhs, is_by_ref, .normal);
const llvm_i2 = try o.builder.intType(2);
const lhs_non_null_i2 = try self.wip.cast(.zext, lhs_non_null, llvm_i2, "");
const rhs_non_null_i2 = try self.wip.cast(.zext, rhs_non_null, llvm_i2, "");
@ -6604,6 +6631,9 @@ pub const FuncGen = struct {
const body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]);
const err_union_ty = self.typeOf(extra.data.ptr).childType(zcu);
const is_unused = self.liveness.isUnused(inst);
self.maybeMarkAllowZeroAccess(self.typeOf(extra.data.ptr).ptrInfo(zcu));
return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, is_unused, err_cold);
}
@ -6627,10 +6657,13 @@ pub const FuncGen = struct {
if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
const loaded = loaded: {
const access_kind: Builder.MemoryAccessKind =
if (err_union_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
if (!payload_has_bits) {
// TODO add alignment to this load
break :loaded if (operand_is_ptr)
try fg.wip.load(.normal, error_type, err_union, .default, "")
try fg.wip.load(access_kind, error_type, err_union, .default, "")
else
err_union;
}
@ -6640,7 +6673,7 @@ pub const FuncGen = struct {
try fg.wip.gepStruct(err_union_llvm_ty, err_union, err_field_index, "");
// TODO add alignment to this load
break :loaded try fg.wip.load(
.normal,
if (operand_is_ptr) access_kind else .normal,
error_type,
err_field_ptr,
.default,
@ -7149,10 +7182,14 @@ pub const FuncGen = struct {
if (self.canElideLoad(body_tail))
return ptr;
self.maybeMarkAllowZeroAccess(slice_ty.ptrInfo(zcu));
const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();
return self.loadByRef(ptr, elem_ty, elem_alignment, .normal);
return self.loadByRef(ptr, elem_ty, elem_alignment, if (slice_ty.isVolatilePtr(zcu)) .@"volatile" else .normal);
}
self.maybeMarkAllowZeroAccess(slice_ty.ptrInfo(zcu));
return self.load(ptr, slice_ty);
}
@ -7222,10 +7259,15 @@ pub const FuncGen = struct {
&.{rhs}, "");
if (isByRef(elem_ty, zcu)) {
if (self.canElideLoad(body_tail)) return ptr;
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();
return self.loadByRef(ptr, elem_ty, elem_alignment, .normal);
return self.loadByRef(ptr, elem_ty, elem_alignment, if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal);
}
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
return self.load(ptr, ptr_ty);
}
@ -7627,6 +7669,8 @@ pub const FuncGen = struct {
}),
}
self.maybeMarkAllowZeroAccess(output_ty.ptrInfo(zcu));
// Pass any non-return outputs indirectly, if the constraint accepts a memory location
is_indirect.* = constraintAllowsMemory(constraint);
if (is_indirect.*) {
@ -7733,10 +7777,11 @@ pub const FuncGen = struct {
// In the case of indirect inputs, LLVM requires the callsite to have
// an elementtype(<ty>) attribute.
llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*')
try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(zcu))
else
.none;
llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*') blk: {
if (!is_by_ref) self.maybeMarkAllowZeroAccess(arg_ty.ptrInfo(zcu));
break :blk try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(zcu));
} else .none;
llvm_param_i += 1;
total_i += 1;
@ -7759,7 +7804,13 @@ pub const FuncGen = struct {
llvm_param_types[llvm_param_i] = llvm_rw_val.typeOfWip(&self.wip);
} else {
const alignment = rw_ty.abiAlignment(zcu).toLlvm();
const loaded = try self.wip.load(.normal, llvm_elem_ty, llvm_rw_val, alignment, "");
const loaded = try self.wip.load(
if (rw_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
llvm_elem_ty,
llvm_rw_val,
alignment,
"",
);
llvm_param_values[llvm_param_i] = loaded;
llvm_param_types[llvm_param_i] = llvm_elem_ty;
}
@ -7918,9 +7969,13 @@ pub const FuncGen = struct {
if (output != .none) {
const output_ptr = try self.resolveInst(output);
const output_ptr_ty = self.typeOf(output);
const alignment = output_ptr_ty.ptrAlignment(zcu).toLlvm();
_ = try self.wip.store(.normal, output_value, output_ptr, alignment);
_ = try self.wip.store(
if (output_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
output_value,
output_ptr,
alignment,
);
} else {
ret_val = output_value;
}
@ -7945,9 +8000,15 @@ pub const FuncGen = struct {
const optional_ty = if (operand_is_ptr) operand_ty.childType(zcu) else operand_ty;
const optional_llvm_ty = try o.lowerType(optional_ty);
const payload_ty = optional_ty.optionalChild(zcu);
const access_kind: Builder.MemoryAccessKind =
if (operand_is_ptr and operand_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
if (optional_ty.optionalReprIsPayload(zcu)) {
const loaded = if (operand_is_ptr)
try self.wip.load(.normal, optional_llvm_ty, operand, .default, "")
try self.wip.load(access_kind, optional_llvm_ty, operand, .default, "")
else
operand;
if (payload_ty.isSlice(zcu)) {
@ -7965,14 +8026,14 @@ pub const FuncGen = struct {
if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
const loaded = if (operand_is_ptr)
try self.wip.load(.normal, optional_llvm_ty, operand, .default, "")
try self.wip.load(access_kind, optional_llvm_ty, operand, .default, "")
else
operand;
return self.wip.icmp(cond, loaded, try o.builder.intValue(.i8, 0), "");
}
const is_by_ref = operand_is_ptr or isByRef(optional_ty, zcu);
return self.optCmpNull(cond, optional_llvm_ty, operand, is_by_ref);
return self.optCmpNull(cond, optional_llvm_ty, operand, is_by_ref, access_kind);
}
fn airIsErr(
@ -7992,6 +8053,9 @@ pub const FuncGen = struct {
const error_type = try o.errorIntType();
const zero = try o.builder.intValue(error_type, 0);
const access_kind: Builder.MemoryAccessKind =
if (operand_is_ptr and operand_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
const val: Builder.Constant = switch (cond) {
.eq => .true, // 0 == 0
@ -8001,9 +8065,11 @@ pub const FuncGen = struct {
return val.toValue();
}
if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
const loaded = if (operand_is_ptr)
try self.wip.load(.normal, try o.lowerType(err_union_ty), operand, .default, "")
try self.wip.load(access_kind, try o.lowerType(err_union_ty), operand, .default, "")
else
operand;
return self.wip.icmp(cond, loaded, zero, "");
@ -8015,7 +8081,7 @@ pub const FuncGen = struct {
const err_union_llvm_ty = try o.lowerType(err_union_ty);
const err_field_ptr =
try self.wip.gepStruct(err_union_llvm_ty, operand, err_field_index, "");
break :loaded try self.wip.load(.normal, error_type, err_field_ptr, .default, "");
break :loaded try self.wip.load(access_kind, error_type, err_field_ptr, .default, "");
} else try self.wip.extractValue(operand, &.{err_field_index}, "");
return self.wip.icmp(cond, loaded, zero, "");
}
@ -8048,12 +8114,19 @@ pub const FuncGen = struct {
const zcu = pt.zcu;
const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.typeOf(ty_op.operand).childType(zcu);
const optional_ptr_ty = self.typeOf(ty_op.operand);
const optional_ty = optional_ptr_ty.childType(zcu);
const payload_ty = optional_ty.optionalChild(zcu);
const non_null_bit = try o.builder.intValue(.i8, 1);
const access_kind: Builder.MemoryAccessKind =
if (optional_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
self.maybeMarkAllowZeroAccess(optional_ptr_ty.ptrInfo(zcu));
// We have a pointer to a i8. We need to set it to 1 and then return the same pointer.
_ = try self.wip.store(.normal, non_null_bit, operand, .default);
_ = try self.wip.store(access_kind, non_null_bit, operand, .default);
return operand;
}
if (optional_ty.optionalReprIsPayload(zcu)) {
@ -8065,8 +8138,11 @@ pub const FuncGen = struct {
// First set the non-null bit.
const optional_llvm_ty = try o.lowerType(optional_ty);
const non_null_ptr = try self.wip.gepStruct(optional_llvm_ty, operand, 1, "");
self.maybeMarkAllowZeroAccess(optional_ptr_ty.ptrInfo(zcu));
// TODO set alignment on this store
_ = try self.wip.store(.normal, non_null_bit, non_null_ptr, .default);
_ = try self.wip.store(access_kind, non_null_bit, non_null_ptr, .default);
// Then return the payload pointer (only if it's used).
if (self.liveness.isUnused(inst)) return .none;
@ -8152,18 +8228,26 @@ pub const FuncGen = struct {
}
}
const access_kind: Builder.MemoryAccessKind =
if (operand_is_ptr and operand_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
const payload_ty = err_union_ty.errorUnionPayload(zcu);
if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
if (!operand_is_ptr) return operand;
return self.wip.load(.normal, error_type, operand, .default, "");
self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
return self.wip.load(access_kind, error_type, operand, .default, "");
}
const offset = try errUnionErrorOffset(payload_ty, pt);
if (operand_is_ptr or isByRef(err_union_ty, zcu)) {
if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
const err_union_llvm_ty = try o.lowerType(err_union_ty);
const err_field_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, offset, "");
return self.wip.load(.normal, error_type, err_field_ptr, .default, "");
return self.wip.load(access_kind, error_type, err_field_ptr, .default, "");
}
return self.wip.extractValue(operand, &.{offset}, "");
@ -8175,22 +8259,31 @@ pub const FuncGen = struct {
const zcu = pt.zcu;
const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const err_union_ty = self.typeOf(ty_op.operand).childType(zcu);
const err_union_ptr_ty = self.typeOf(ty_op.operand);
const err_union_ty = err_union_ptr_ty.childType(zcu);
const payload_ty = err_union_ty.errorUnionPayload(zcu);
const non_error_val = try o.builder.intValue(try o.errorIntType(), 0);
const access_kind: Builder.MemoryAccessKind =
if (err_union_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
_ = try self.wip.store(.normal, non_error_val, operand, .default);
self.maybeMarkAllowZeroAccess(err_union_ptr_ty.ptrInfo(zcu));
_ = try self.wip.store(access_kind, non_error_val, operand, .default);
return operand;
}
const err_union_llvm_ty = try o.lowerType(err_union_ty);
{
self.maybeMarkAllowZeroAccess(err_union_ptr_ty.ptrInfo(zcu));
const err_int_ty = try pt.errorIntType();
const error_alignment = err_int_ty.abiAlignment(zcu).toLlvm();
const error_offset = try errUnionErrorOffset(payload_ty, pt);
// First set the non-error value.
const non_null_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, error_offset, "");
_ = try self.wip.store(.normal, non_error_val, non_null_ptr, error_alignment);
_ = try self.wip.store(access_kind, non_error_val, non_null_ptr, error_alignment);
}
// Then return the payload pointer (only if it is used).
if (self.liveness.isUnused(inst)) return .none;
@ -8405,6 +8498,10 @@ pub const FuncGen = struct {
const index = try self.resolveInst(extra.lhs);
const operand = try self.resolveInst(extra.rhs);
self.maybeMarkAllowZeroAccess(vector_ptr_ty.ptrInfo(zcu));
// TODO: Emitting a load here is a violation of volatile semantics. Not fixable in general.
// https://github.com/ziglang/zig/issues/18652#issuecomment-2452844908
const access_kind: Builder.MemoryAccessKind =
if (vector_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
const elem_llvm_ty = try o.lowerType(vector_ptr_ty.childType(zcu));
@ -9757,6 +9854,8 @@ pub const FuncGen = struct {
return .none;
}
self.maybeMarkAllowZeroAccess(ptr_info);
const len = try o.builder.intValue(try o.lowerType(Type.usize), operand_ty.abiSize(zcu));
_ = try self.wip.callMemSet(
dest_ptr,
@ -9771,6 +9870,8 @@ pub const FuncGen = struct {
return .none;
}
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
const src_operand = try self.resolveInst(bin_op.rhs);
try self.store(dest_ptr, ptr_ty, src_operand, .none);
return .none;
@ -9813,6 +9914,9 @@ pub const FuncGen = struct {
if (!canElideLoad(fg, body_tail)) break :elide;
return ptr;
}
fg.maybeMarkAllowZeroAccess(ptr_info);
return fg.load(ptr, ptr_ty);
}
@ -9872,6 +9976,8 @@ pub const FuncGen = struct {
new_value = try self.wip.conv(signedness, new_value, llvm_abi_ty, "");
}
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
const result = try self.wip.cmpxchg(
kind,
if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
@ -9923,6 +10029,8 @@ pub const FuncGen = struct {
if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
const ptr_alignment = ptr_ty.ptrAlignment(zcu).toLlvm();
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
if (llvm_abi_ty != .none) {
// operand needs widening and truncating or bitcasting.
return self.wip.cast(if (is_float) .bitcast else .trunc, try self.wip.atomicrmw(
@ -9986,6 +10094,8 @@ pub const FuncGen = struct {
if (info.flags.is_volatile) .@"volatile" else .normal;
const elem_llvm_ty = try o.lowerType(elem_ty);
self.maybeMarkAllowZeroAccess(info);
if (llvm_abi_ty != .none) {
// operand needs widening and truncating
const loaded = try self.wip.loadAtomic(
@ -10035,6 +10145,9 @@ pub const FuncGen = struct {
"",
);
}
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
try self.store(ptr, ptr_ty, element, ordering);
return .none;
}
@ -10052,6 +10165,8 @@ pub const FuncGen = struct {
const access_kind: Builder.MemoryAccessKind =
if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
// Any WebAssembly runtime will trap when the destination pointer is out-of-bounds, regardless
// of the length. This means we need to emit a check where we skip the memset when the length
// is 0 as we allow for undefined pointers in 0-sized slices.
@ -10208,6 +10323,9 @@ pub const FuncGen = struct {
const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or
dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
self.maybeMarkAllowZeroAccess(dest_ptr_ty.ptrInfo(zcu));
self.maybeMarkAllowZeroAccess(src_ptr_ty.ptrInfo(zcu));
// When bulk-memory is enabled, this will be lowered to WebAssembly's memory.copy instruction.
// This instruction will trap on an invalid address, regardless of the length.
// For this reason we must add a check for 0-sized slices as its pointer field can be undefined.
@ -10252,20 +10370,27 @@ pub const FuncGen = struct {
const pt = o.pt;
const zcu = pt.zcu;
const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
const un_ty = self.typeOf(bin_op.lhs).childType(zcu);
const un_ptr_ty = self.typeOf(bin_op.lhs);
const un_ty = un_ptr_ty.childType(zcu);
const layout = un_ty.unionGetLayout(zcu);
if (layout.tag_size == 0) return .none;
const access_kind: Builder.MemoryAccessKind =
if (un_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
self.maybeMarkAllowZeroAccess(un_ptr_ty.ptrInfo(zcu));
const union_ptr = try self.resolveInst(bin_op.lhs);
const new_tag = try self.resolveInst(bin_op.rhs);
if (layout.payload_size == 0) {
// TODO alignment on this store
_ = try self.wip.store(.normal, new_tag, union_ptr, .default);
_ = try self.wip.store(access_kind, new_tag, union_ptr, .default);
return .none;
}
const tag_index = @intFromBool(layout.tag_align.compare(.lt, layout.payload_align));
const tag_field_ptr = try self.wip.gepStruct(try o.lowerType(un_ty), union_ptr, tag_index, "");
// TODO alignment on this store
_ = try self.wip.store(.normal, new_tag, tag_field_ptr, .default);
_ = try self.wip.store(access_kind, new_tag, tag_field_ptr, .default);
return .none;
}
@ -11162,12 +11287,13 @@ pub const FuncGen = struct {
opt_llvm_ty: Builder.Type,
opt_handle: Builder.Value,
is_by_ref: bool,
access_kind: Builder.MemoryAccessKind,
) Allocator.Error!Builder.Value {
const o = self.ng.object;
const field = b: {
if (is_by_ref) {
const field_ptr = try self.wip.gepStruct(opt_llvm_ty, opt_handle, 1, "");
break :b try self.wip.load(.normal, .i8, field_ptr, .default, "");
break :b try self.wip.load(access_kind, .i8, field_ptr, .default, "");
}
break :b try self.wip.extractValue(opt_handle, &.{1}, "");
};
@ -11475,7 +11601,7 @@ pub const FuncGen = struct {
const vec_elem_ty = try o.lowerType(elem_ty);
const vec_ty = try o.builder.vectorType(.normal, info.packed_offset.host_size, vec_elem_ty);
const loaded_vector = try self.wip.load(access_kind, vec_ty, ptr, ptr_alignment, "");
const loaded_vector = try self.wip.load(.normal, vec_ty, ptr, ptr_alignment, "");
const modified_vector = try self.wip.insertElement(loaded_vector, elem, index_u32, "");
@ -11488,7 +11614,7 @@ pub const FuncGen = struct {
const containing_int_ty = try o.builder.intType(@intCast(info.packed_offset.host_size * 8));
assert(ordering == .none);
const containing_int =
try self.wip.load(access_kind, containing_int_ty, ptr, ptr_alignment, "");
try self.wip.load(.normal, containing_int_ty, ptr, ptr_alignment, "");
const elem_bits = ptr_ty.childType(zcu).bitSize(zcu);
const shift_amt = try o.builder.intConst(containing_int_ty, info.packed_offset.bit_offset);
// Convert to equally-sized integer type in order to perform the bit