Skip to content

Commit 258d07f

Browse files
committed
stage2: improve semantics of atomic operations
ZIR instructions updated: atomic_load, atomic_rmw, atomic_store, cmpxchg These no longer construct a pointer type as the result location. This solves a TODO that was preventing the pointer from possibly being volatile, as well as properly handling allowzero and addrspace. It also allows the pointer to be over-aligned, which may be needed depending on the target. As a consequence, the element type needs to be communicated in the ZIR. This is done by strategically making one of the operands be ResultLoc.ty instead of ResultLoc.coerced_ty if possible, or otherwise explicitly adding elem_type into the ZIR encoding, such as in the case of atomic_load. The pointer type of atomic operations is now checked in Sema by coercing it to an expected pointer type, that maybe over-aligned according to target requirements. Together with the previous commit, Zig now has smaller alignment for large integers, depending on the target, and yet still has type safety for atomic operations that specially require higher alignment.
1 parent 82131c7 commit 258d07f

6 files changed

Lines changed: 183 additions & 132 deletions

File tree

src/AstGen.zig

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7423,63 +7423,33 @@ fn builtinCall(
74237423
},
74247424

74257425
.atomic_load => {
7426-
const int_type = try typeExpr(gz, scope, params[0]);
7427-
// TODO allow this pointer type to be volatile
7428-
const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{
7429-
.ptr_type_simple = .{
7430-
.is_allowzero = false,
7431-
.is_mutable = false,
7432-
.is_volatile = false,
7433-
.size = .One,
7434-
.elem_type = int_type,
7435-
},
7436-
} });
7437-
const result = try gz.addPlNode(.atomic_load, node, Zir.Inst.Bin{
7426+
const result = try gz.addPlNode(.atomic_load, node, Zir.Inst.AtomicLoad{
74387427
// zig fmt: off
7439-
.lhs = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]),
7440-
.rhs = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[2]),
7428+
.elem_type = try typeExpr(gz, scope, params[0]),
7429+
.ptr = try expr (gz, scope, .none, params[1]),
7430+
.ordering = try expr (gz, scope, .{ .coerced_ty = .atomic_order_type }, params[2]),
74417431
// zig fmt: on
74427432
});
74437433
return rvalue(gz, rl, result, node);
74447434
},
74457435
.atomic_rmw => {
74467436
const int_type = try typeExpr(gz, scope, params[0]);
7447-
// TODO allow this pointer type to be volatile
7448-
const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{
7449-
.ptr_type_simple = .{
7450-
.is_allowzero = false,
7451-
.is_mutable = true,
7452-
.is_volatile = false,
7453-
.size = .One,
7454-
.elem_type = int_type,
7455-
},
7456-
} });
74577437
const result = try gz.addPlNode(.atomic_rmw, node, Zir.Inst.AtomicRmw{
74587438
// zig fmt: off
7459-
.ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]),
7439+
.ptr = try expr(gz, scope, .none, params[1]),
74607440
.operation = try expr(gz, scope, .{ .coerced_ty = .atomic_rmw_op_type }, params[2]),
7461-
.operand = try expr(gz, scope, .{ .coerced_ty = int_type }, params[3]),
7441+
.operand = try expr(gz, scope, .{ .ty = int_type }, params[3]),
74627442
.ordering = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[4]),
74637443
// zig fmt: on
74647444
});
74657445
return rvalue(gz, rl, result, node);
74667446
},
74677447
.atomic_store => {
74687448
const int_type = try typeExpr(gz, scope, params[0]);
7469-
// TODO allow this pointer type to be volatile
7470-
const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{
7471-
.ptr_type_simple = .{
7472-
.is_allowzero = false,
7473-
.is_mutable = true,
7474-
.is_volatile = false,
7475-
.size = .One,
7476-
.elem_type = int_type,
7477-
},
7478-
} });
74797449
const result = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{
74807450
// zig fmt: off
7481-
.ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]),
7482-
.operand = try expr(gz, scope, .{ .coerced_ty = int_type }, params[2]),
7451+
.ptr = try expr(gz, scope, .none, params[1]),
7452+
.operand = try expr(gz, scope, .{ .ty = int_type }, params[2]),
74837453
.ordering = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[3]),
74847454
// zig fmt: on
74857455
});
@@ -7684,20 +7654,10 @@ fn cmpxchg(
76847654
tag: Zir.Inst.Tag,
76857655
) InnerError!Zir.Inst.Ref {
76867656
const int_type = try typeExpr(gz, scope, params[0]);
7687-
// TODO: allow this to be volatile
7688-
const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{
7689-
.ptr_type_simple = .{
7690-
.is_allowzero = false,
7691-
.is_mutable = true,
7692-
.is_volatile = false,
7693-
.size = .One,
7694-
.elem_type = int_type,
7695-
},
7696-
} });
76977657
const result = try gz.addPlNode(tag, node, Zir.Inst.Cmpxchg{
76987658
// zig fmt: off
7699-
.ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]),
7700-
.expected_value = try expr(gz, scope, .{ .coerced_ty = int_type }, params[2]),
7659+
.ptr = try expr(gz, scope, .none, params[1]),
7660+
.expected_value = try expr(gz, scope, .{ .ty = int_type }, params[2]),
77017661
.new_value = try expr(gz, scope, .{ .coerced_ty = int_type }, params[3]),
77027662
.success_order = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[4]),
77037663
.failure_order = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[5]),

src/Sema.zig

Lines changed: 87 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -14715,51 +14715,64 @@ fn checkNumericType(
1471514715
}
1471614716
}
1471714717

14718-
fn checkAtomicOperandType(
14718+
/// Returns the casted pointer.
14719+
fn checkAtomicPtrOperand(
1471914720
sema: *Sema,
1472014721
block: *Block,
14721-
ty_src: LazySrcLoc,
14722-
ty: Type,
14723-
) CompileError!void {
14724-
var buffer: Type.Payload.Bits = undefined;
14722+
elem_ty: Type,
14723+
elem_ty_src: LazySrcLoc,
14724+
ptr: Air.Inst.Ref,
14725+
ptr_src: LazySrcLoc,
14726+
ptr_const: bool,
14727+
) CompileError!Air.Inst.Ref {
1472514728
const target = sema.mod.getTarget();
14726-
const max_atomic_bits = target_util.largestAtomicBits(target);
14727-
const int_ty = switch (ty.zigTypeTag()) {
14728-
.Int => ty,
14729-
.Enum => ty.intTagType(&buffer),
14730-
.Float => {
14731-
const bit_count = ty.floatBits(target);
14732-
if (bit_count > max_atomic_bits) {
14733-
return sema.fail(
14734-
block,
14735-
ty_src,
14736-
"expected {d}-bit float type or smaller; found {d}-bit float type",
14737-
.{ max_atomic_bits, bit_count },
14738-
);
14739-
}
14740-
return;
14741-
},
14742-
.Bool => return, // Will be treated as `u8`.
14743-
else => {
14744-
if (ty.isPtrAtRuntime()) return;
14729+
var diag: target_util.AtomicPtrAlignmentDiagnostics = .{};
14730+
const alignment = target_util.atomicPtrAlignment(target, elem_ty, &diag) catch |err| switch (err) {
14731+
error.FloatTooBig => return sema.fail(
14732+
block,
14733+
elem_ty_src,
14734+
"expected {d}-bit float type or smaller; found {d}-bit float type",
14735+
.{ diag.max_bits, diag.bits },
14736+
),
14737+
error.IntTooBig => return sema.fail(
14738+
block,
14739+
elem_ty_src,
14740+
"expected {d}-bit integer type or smaller; found {d}-bit integer type",
14741+
.{ diag.max_bits, diag.bits },
14742+
),
14743+
error.BadType => return sema.fail(
14744+
block,
14745+
elem_ty_src,
14746+
"expected bool, integer, float, enum, or pointer type; found {}",
14747+
.{elem_ty.fmt(sema.mod)},
14748+
),
14749+
};
1474514750

14746-
return sema.fail(
14747-
block,
14748-
ty_src,
14749-
"expected bool, integer, float, enum, or pointer type; found {}",
14750-
.{ty.fmt(sema.mod)},
14751-
);
14751+
var wanted_ptr_data: Type.Payload.Pointer.Data = .{
14752+
.pointee_type = elem_ty,
14753+
.@"align" = alignment,
14754+
.@"addrspace" = .generic,
14755+
.mutable = !ptr_const,
14756+
};
14757+
14758+
const ptr_ty = sema.typeOf(ptr);
14759+
const ptr_data = switch (try ptr_ty.zigTypeTagOrPoison()) {
14760+
.Pointer => ptr_ty.ptrInfo().data,
14761+
else => {
14762+
const wanted_ptr_ty = try Type.ptr(sema.arena, sema.mod, wanted_ptr_data);
14763+
_ = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src);
14764+
unreachable;
1475214765
},
1475314766
};
14754-
const bit_count = int_ty.intInfo(target).bits;
14755-
if (bit_count > max_atomic_bits) {
14756-
return sema.fail(
14757-
block,
14758-
ty_src,
14759-
"expected {d}-bit integer type or smaller; found {d}-bit integer type",
14760-
.{ max_atomic_bits, bit_count },
14761-
);
14762-
}
14767+
14768+
wanted_ptr_data.@"addrspace" = ptr_data.@"addrspace";
14769+
wanted_ptr_data.@"allowzero" = ptr_data.@"allowzero";
14770+
wanted_ptr_data.@"volatile" = ptr_data.@"volatile";
14771+
14772+
const wanted_ptr_ty = try Type.ptr(sema.arena, sema.mod, wanted_ptr_data);
14773+
const casted_ptr = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src);
14774+
14775+
return casted_ptr;
1476314776
}
1476414777

1476514778
fn checkPtrIsNotComptimeMutable(
@@ -15036,10 +15049,8 @@ fn zirCmpxchg(
1503615049
const success_order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg4 = inst_data.src_node };
1503715050
const failure_order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg5 = inst_data.src_node };
1503815051
// zig fmt: on
15039-
const ptr = sema.resolveInst(extra.ptr);
15040-
const ptr_ty = sema.typeOf(ptr);
15041-
const elem_ty = ptr_ty.elemType();
15042-
try sema.checkAtomicOperandType(block, elem_ty_src, elem_ty);
15052+
const expected_value = sema.resolveInst(extra.expected_value);
15053+
const elem_ty = sema.typeOf(expected_value);
1504315054
if (elem_ty.zigTypeTag() == .Float) {
1504415055
return sema.fail(
1504515056
block,
@@ -15048,7 +15059,8 @@ fn zirCmpxchg(
1504815059
.{elem_ty.fmt(sema.mod)},
1504915060
);
1505015061
}
15051-
const expected_value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.expected_value), expected_src);
15062+
const uncasted_ptr = sema.resolveInst(extra.ptr);
15063+
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
1505215064
const new_value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.new_value), new_value_src);
1505315065
const success_order = try sema.resolveAtomicOrder(block, success_order_src, extra.success_order);
1505415066
const failure_order = try sema.resolveAtomicOrder(block, failure_order_src, extra.failure_order);
@@ -15081,6 +15093,7 @@ fn zirCmpxchg(
1508115093
// to become undef as well
1508215094
return sema.addConstUndef(result_ty);
1508315095
}
15096+
const ptr_ty = sema.typeOf(ptr);
1508415097
const stored_val = (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) orelse break :rs ptr_src;
1508515098
const result_val = if (stored_val.eql(expected_val, elem_ty, sema.mod)) blk: {
1508615099
try sema.storePtr(block, src, ptr, new_value);
@@ -15487,17 +15500,16 @@ fn zirSelect(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
1548715500

1548815501
fn zirAtomicLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
1548915502
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
15490-
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
15503+
const extra = sema.code.extraData(Zir.Inst.AtomicLoad, inst_data.payload_index).data;
1549115504
// zig fmt: off
1549215505
const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
1549315506
const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
1549415507
const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
1549515508
// zig fmt: on
15496-
const ptr = sema.resolveInst(extra.lhs);
15497-
const ptr_ty = sema.typeOf(ptr);
15498-
const elem_ty = ptr_ty.elemType();
15499-
try sema.checkAtomicOperandType(block, elem_ty_src, elem_ty);
15500-
const order = try sema.resolveAtomicOrder(block, order_src, extra.rhs);
15509+
const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_type);
15510+
const uncasted_ptr = sema.resolveInst(extra.ptr);
15511+
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, true);
15512+
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering);
1550115513

1550215514
switch (order) {
1550315515
.Release, .AcqRel => {
@@ -15516,7 +15528,7 @@ fn zirAtomicLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
1551615528
}
1551715529

1551815530
if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
15519-
if (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) |elem_val| {
15531+
if (try sema.pointerDeref(block, ptr_src, ptr_val, sema.typeOf(ptr))) |elem_val| {
1552015532
return sema.addConstant(elem_ty, elem_val);
1552115533
}
1552215534
}
@@ -15536,19 +15548,19 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1553615548
const extra = sema.code.extraData(Zir.Inst.AtomicRmw, inst_data.payload_index).data;
1553715549
const src = inst_data.src();
1553815550
// zig fmt: off
15539-
const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
15551+
const elem_ty_src : LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
1554015552
const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
1554115553
const op_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
1554215554
const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node };
1554315555
const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg4 = inst_data.src_node };
1554415556
// zig fmt: on
15545-
const ptr = sema.resolveInst(extra.ptr);
15546-
const ptr_ty = sema.typeOf(ptr);
15547-
const operand_ty = ptr_ty.elemType();
15548-
try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty);
15557+
const operand = sema.resolveInst(extra.operand);
15558+
const elem_ty = sema.typeOf(operand);
15559+
const uncasted_ptr = sema.resolveInst(extra.ptr);
15560+
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
1554915561
const op = try sema.resolveAtomicRmwOp(block, op_src, extra.operation);
1555015562

15551-
switch (operand_ty.zigTypeTag()) {
15563+
switch (elem_ty.zigTypeTag()) {
1555215564
.Enum => if (op != .Xchg) {
1555315565
return sema.fail(block, op_src, "@atomicRmw with enum only allowed with .Xchg", .{});
1555415566
},
@@ -15561,16 +15573,15 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1556115573
},
1556215574
else => {},
1556315575
}
15564-
const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src);
1556515576
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering);
1556615577

1556715578
if (order == .Unordered) {
1556815579
return sema.fail(block, order_src, "@atomicRmw atomic ordering must not be Unordered", .{});
1556915580
}
1557015581

1557115582
// special case zero bit types
15572-
if (try sema.typeHasOnePossibleValue(block, operand_ty_src, operand_ty)) |val| {
15573-
return sema.addConstant(operand_ty, val);
15583+
if (try sema.typeHasOnePossibleValue(block, elem_ty_src, elem_ty)) |val| {
15584+
return sema.addConstant(elem_ty, val);
1557415585
}
1557515586

1557615587
const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: {
@@ -15581,22 +15592,23 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1558115592
};
1558215593
if (ptr_val.isComptimeMutablePtr()) {
1558315594
const target = sema.mod.getTarget();
15595+
const ptr_ty = sema.typeOf(ptr);
1558415596
const stored_val = (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) orelse break :rs ptr_src;
1558515597
const new_val = switch (op) {
1558615598
// zig fmt: off
1558715599
.Xchg => operand_val,
15588-
.Add => try stored_val.numberAddWrap(operand_val, operand_ty, sema.arena, target),
15589-
.Sub => try stored_val.numberSubWrap(operand_val, operand_ty, sema.arena, target),
15590-
.And => try stored_val.bitwiseAnd (operand_val, operand_ty, sema.arena, target),
15591-
.Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target),
15592-
.Or => try stored_val.bitwiseOr (operand_val, operand_ty, sema.arena, target),
15593-
.Xor => try stored_val.bitwiseXor (operand_val, operand_ty, sema.arena, target),
15594-
.Max => stored_val.numberMax (operand_val, target),
15595-
.Min => stored_val.numberMin (operand_val, target),
15600+
.Add => try stored_val.numberAddWrap(operand_val, elem_ty, sema.arena, target),
15601+
.Sub => try stored_val.numberSubWrap(operand_val, elem_ty, sema.arena, target),
15602+
.And => try stored_val.bitwiseAnd (operand_val, elem_ty, sema.arena, target),
15603+
.Nand => try stored_val.bitwiseNand (operand_val, elem_ty, sema.arena, target),
15604+
.Or => try stored_val.bitwiseOr (operand_val, elem_ty, sema.arena, target),
15605+
.Xor => try stored_val.bitwiseXor (operand_val, elem_ty, sema.arena, target),
15606+
.Max => stored_val.numberMax (operand_val, target),
15607+
.Min => stored_val.numberMin (operand_val, target),
1559615608
// zig fmt: on
1559715609
};
15598-
try sema.storePtrVal(block, src, ptr_val, new_val, operand_ty);
15599-
return sema.addConstant(operand_ty, stored_val);
15610+
try sema.storePtrVal(block, src, ptr_val, new_val, elem_ty);
15611+
return sema.addConstant(elem_ty, stored_val);
1560015612
} else break :rs ptr_src;
1560115613
} else ptr_src;
1560215614

@@ -15620,15 +15632,15 @@ fn zirAtomicStore(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
1562015632
const extra = sema.code.extraData(Zir.Inst.AtomicStore, inst_data.payload_index).data;
1562115633
const src = inst_data.src();
1562215634
// zig fmt: off
15623-
const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
15635+
const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
1562415636
const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
1562515637
const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
1562615638
const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node };
1562715639
// zig fmt: on
15628-
const ptr = sema.resolveInst(extra.ptr);
15629-
const operand_ty = sema.typeOf(ptr).elemType();
15630-
try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty);
15631-
const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src);
15640+
const operand = sema.resolveInst(extra.operand);
15641+
const elem_ty = sema.typeOf(operand);
15642+
const uncasted_ptr = sema.resolveInst(extra.ptr);
15643+
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
1563215644
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering);
1563315645

1563415646
const air_tag: Air.Inst.Tag = switch (order) {

src/Zir.zig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ pub const Inst = struct {
903903
/// Uses the `pl_node` union field with payload `Select`.
904904
select,
905905
/// Implements the `@atomicLoad` builtin.
906-
/// Uses the `pl_node` union field with payload `Bin`.
906+
/// Uses the `pl_node` union field with payload `AtomicLoad`.
907907
atomic_load,
908908
/// Implements the `@atomicRmw` builtin.
909909
/// Uses the `pl_node` union field with payload `AtomicRmw`.
@@ -3293,6 +3293,12 @@ pub const Inst = struct {
32933293
ordering: Ref,
32943294
};
32953295

3296+
pub const AtomicLoad = struct {
3297+
elem_type: Ref,
3298+
ptr: Ref,
3299+
ordering: Ref,
3300+
};
3301+
32963302
pub const MulAdd = struct {
32973303
mulend1: Ref,
32983304
mulend2: Ref,

0 commit comments

Comments
 (0)