Skip to content

Commit 5e3c0b7

Browse files
authored
Allow more operators on bool vectors (ziglang#24131)
* Sema: allow binary operations and boolean not on vectors of bool * langref: Clarify use of operators on vectors (`and` and `or` not allowed) closes ziglang#24093
1 parent 4a02e08 commit 5e3c0b7

File tree

5 files changed

+79
-50
lines changed

5 files changed

+79
-50
lines changed

doc/langref.html.in

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,8 +1926,10 @@ or
19261926
Vector types are created with the builtin function {#link|@Vector#}.
19271927
</p>
19281928
<p>
1929-
Vectors support the same builtin operators as their underlying base types.
1930-
These operations are performed element-wise, and return a vector of the same length
1929+
Vectors generally support the same builtin operators as their underlying base types.
1930+
The only exception to this is the keywords `and` and `or` on vectors of bools, since
1931+
these operators affect control flow, which is not allowed for vectors.
1932+
All other operations are performed element-wise, and return a vector of the same length
19311933
as the input vectors. This includes:
19321934
</p>
19331935
<ul>
@@ -1937,6 +1939,7 @@ or
19371939
<li>Bitwise operators ({#syntax#}>>{#endsyntax#}, {#syntax#}<<{#endsyntax#}, {#syntax#}&{#endsyntax#},
19381940
{#syntax#}|{#endsyntax#}, {#syntax#}~{#endsyntax#}, etc.)</li>
19391941
<li>Comparison operators ({#syntax#}<{#endsyntax#}, {#syntax#}>{#endsyntax#}, {#syntax#}=={#endsyntax#}, etc.)</li>
1942+
<li>Boolean not ({#syntax#}!{#endsyntax#})</li>
19401943
</ul>
19411944
<p>
19421945
It is prohibited to use a math operator on a mixture of scalars (individual numbers)

lib/std/zig/AstGen.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
806806
.bool_and => return boolBinOp(gz, scope, ri, node, .bool_br_and),
807807
.bool_or => return boolBinOp(gz, scope, ri, node, .bool_br_or),
808808

809-
.bool_not => return simpleUnOp(gz, scope, ri, node, coerced_bool_ri, tree.nodeData(node).node, .bool_not),
809+
.bool_not => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, tree.nodeData(node).node, .bool_not),
810810
.bit_not => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, tree.nodeData(node).node, .bit_not),
811811

812812
.negation => return negation(gz, scope, ri, node),

src/Sema.zig

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,11 +1171,11 @@ fn analyzeBodyInner(
11711171
.as_node => try sema.zirAsNode(block, inst),
11721172
.as_shift_operand => try sema.zirAsShiftOperand(block, inst),
11731173
.bit_and => try sema.zirBitwise(block, inst, .bit_and),
1174-
.bit_not => try sema.zirBitNot(block, inst),
1174+
.bit_not => try sema.zirBitNot(block, inst, false),
11751175
.bit_or => try sema.zirBitwise(block, inst, .bit_or),
11761176
.bitcast => try sema.zirBitcast(block, inst),
11771177
.suspend_block => try sema.zirSuspendBlock(block, inst),
1178-
.bool_not => try sema.zirBoolNot(block, inst),
1178+
.bool_not => try sema.zirBitNot(block, inst, true),
11791179
.bool_br_and => try sema.zirBoolBr(block, inst, false),
11801180
.bool_br_or => try sema.zirBoolBr(block, inst, true),
11811181
.c_import => try sema.zirCImport(block, inst),
@@ -14412,9 +14412,9 @@ fn zirBitwise(
1441214412
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
1441314413
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
1441414414

14415-
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
14415+
const is_int_or_bool = scalar_tag == .int or scalar_tag == .comptime_int or scalar_tag == .bool;
1441614416

14417-
if (!is_int) {
14417+
if (!is_int_or_bool) {
1441814418
return sema.fail(block, src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag(zcu)), @tagName(rhs_ty.zigTypeTag(zcu)) });
1441914419
}
1442014420

@@ -14442,7 +14442,12 @@ fn zirBitwise(
1444214442
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
1444314443
}
1444414444

14445-
fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
14445+
fn zirBitNot(
14446+
sema: *Sema,
14447+
block: *Block,
14448+
inst: Zir.Inst.Index,
14449+
is_bool_not: bool,
14450+
) CompileError!Air.Inst.Ref {
1444614451
const tracy = trace(@src());
1444714452
defer tracy.end();
1444814453

@@ -14455,10 +14460,14 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
1445514460
const operand = try sema.resolveInst(inst_data.operand);
1445614461
const operand_type = sema.typeOf(operand);
1445714462
const scalar_type = operand_type.scalarType(zcu);
14463+
const scalar_tag = scalar_type.zigTypeTag(zcu);
1445814464

14459-
if (scalar_type.zigTypeTag(zcu) != .int) {
14460-
return sema.fail(block, src, "unable to perform binary not operation on type '{}'", .{
14461-
operand_type.fmt(pt),
14465+
const is_finite_int_or_bool = scalar_tag == .int or scalar_tag == .bool;
14466+
const is_allowed_type = if (is_bool_not) scalar_tag == .bool else is_finite_int_or_bool;
14467+
14468+
if (!is_allowed_type) {
14469+
return sema.fail(block, src, "unable to perform {s} not operation on type '{}'", .{
14470+
if (is_bool_not) "boolean" else "binary", operand_type.fmt(pt),
1446214471
});
1446314472
}
1446414473

@@ -18336,25 +18345,6 @@ fn zirTypeofPeer(
1833618345
return Air.internedToRef(result_type.toIntern());
1833718346
}
1833818347

18339-
fn zirBoolNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
18340-
const tracy = trace(@src());
18341-
defer tracy.end();
18342-
18343-
const pt = sema.pt;
18344-
const zcu = pt.zcu;
18345-
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
18346-
const src = block.nodeOffset(inst_data.src_node);
18347-
const operand_src = block.src(.{ .node_offset_un_op = inst_data.src_node });
18348-
const uncasted_operand = try sema.resolveInst(inst_data.operand);
18349-
18350-
const operand = try sema.coerce(block, .bool, uncasted_operand, operand_src);
18351-
if (try sema.resolveValue(operand)) |val| {
18352-
return if (val.isUndef(zcu)) .undef_bool else if (val.toBool()) .bool_false else .bool_true;
18353-
}
18354-
try sema.requireRuntimeBlock(block, src, null);
18355-
return block.addTyOp(.not, .bool, operand);
18356-
}
18357-
1835818348
fn zirBoolBr(
1835918349
sema: *Sema,
1836018350
parent_block: *Block,

src/Value.zig

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,7 +1627,7 @@ pub fn numberMin(lhs: Value, rhs: Value, zcu: *Zcu) Value {
16271627
};
16281628
}
16291629

1630-
/// operands must be (vectors of) integers; handles undefined scalars.
1630+
/// operands must be (vectors of) integers or bools; handles undefined scalars.
16311631
pub fn bitwiseNot(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
16321632
const zcu = pt.zcu;
16331633
if (ty.zigTypeTag(zcu) == .vector) {
@@ -1645,7 +1645,7 @@ pub fn bitwiseNot(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Va
16451645
return bitwiseNotScalar(val, ty, arena, pt);
16461646
}
16471647

1648-
/// operands must be integers; handles undefined.
1648+
/// operands must be integers or bools; handles undefined.
16491649
pub fn bitwiseNotScalar(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
16501650
const zcu = pt.zcu;
16511651
if (val.isUndef(zcu)) return Value.fromInterned(try pt.intern(.{ .undef = ty.toIntern() }));
@@ -1671,7 +1671,7 @@ pub fn bitwiseNotScalar(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThrea
16711671
return pt.intValue_big(ty, result_bigint.toConst());
16721672
}
16731673

1674-
/// operands must be (vectors of) integers; handles undefined scalars.
1674+
/// operands must be (vectors of) integers or bools; handles undefined scalars.
16751675
pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu.PerThread) !Value {
16761676
const zcu = pt.zcu;
16771677
if (ty.zigTypeTag(zcu) == .vector) {
@@ -1690,7 +1690,7 @@ pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zc
16901690
return bitwiseAndScalar(lhs, rhs, ty, allocator, pt);
16911691
}
16921692

1693-
/// operands must be integers; handles undefined.
1693+
/// operands must be integers or bools; handles undefined.
16941694
pub fn bitwiseAndScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
16951695
const zcu = pt.zcu;
16961696
// If one operand is defined, we turn the other into `0xAA` so the bitwise AND can
@@ -1744,7 +1744,7 @@ fn intValueAa(ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
17441744
return pt.intValue_big(ty, result_bigint.toConst());
17451745
}
17461746

1747-
/// operands must be (vectors of) integers; handles undefined scalars.
1747+
/// operands must be (vectors of) integers or bools; handles undefined scalars.
17481748
pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
17491749
const zcu = pt.zcu;
17501750
if (ty.zigTypeTag(zcu) == .vector) {
@@ -1763,7 +1763,7 @@ pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.P
17631763
return bitwiseNandScalar(lhs, rhs, ty, arena, pt);
17641764
}
17651765

1766-
/// operands must be integers; handles undefined.
1766+
/// operands must be integers or bools; handles undefined.
17671767
pub fn bitwiseNandScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
17681768
const zcu = pt.zcu;
17691769
if (lhs.isUndef(zcu) or rhs.isUndef(zcu)) return Value.fromInterned(try pt.intern(.{ .undef = ty.toIntern() }));
@@ -1774,7 +1774,7 @@ pub fn bitwiseNandScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt:
17741774
return bitwiseXor(anded, all_ones, ty, arena, pt);
17751775
}
17761776

1777-
/// operands must be (vectors of) integers; handles undefined scalars.
1777+
/// operands must be (vectors of) integers or bools; handles undefined scalars.
17781778
pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu.PerThread) !Value {
17791779
const zcu = pt.zcu;
17801780
if (ty.zigTypeTag(zcu) == .vector) {
@@ -1793,7 +1793,7 @@ pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu
17931793
return bitwiseOrScalar(lhs, rhs, ty, allocator, pt);
17941794
}
17951795

1796-
/// operands must be integers; handles undefined.
1796+
/// operands must be integers or bools; handles undefined.
17971797
pub fn bitwiseOrScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
17981798
// If one operand is defined, we turn the other into `0xAA` so the bitwise AND can
17991799
// still zero out some bits.
@@ -1827,7 +1827,7 @@ pub fn bitwiseOrScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Alloca
18271827
return pt.intValue_big(ty, result_bigint.toConst());
18281828
}
18291829

1830-
/// operands must be (vectors of) integers; handles undefined scalars.
1830+
/// operands must be (vectors of) integers or bools; handles undefined scalars.
18311831
pub fn bitwiseXor(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu.PerThread) !Value {
18321832
const zcu = pt.zcu;
18331833
if (ty.zigTypeTag(zcu) == .vector) {
@@ -1846,7 +1846,7 @@ pub fn bitwiseXor(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zc
18461846
return bitwiseXorScalar(lhs, rhs, ty, allocator, pt);
18471847
}
18481848

1849-
/// operands must be integers; handles undefined.
1849+
/// operands must be integers or bools; handles undefined.
18501850
pub fn bitwiseXorScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value {
18511851
const zcu = pt.zcu;
18521852
if (lhs.isUndef(zcu) or rhs.isUndef(zcu)) return Value.fromInterned(try pt.intern(.{ .undef = ty.toIntern() }));

test/behavior/vector.zig

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,22 @@ test "vector bit operators" {
152152

153153
const S = struct {
154154
fn doTheTest() !void {
155-
var v: @Vector(4, u8) = [4]u8{ 0b10101010, 0b10101010, 0b10101010, 0b10101010 };
156-
var x: @Vector(4, u8) = [4]u8{ 0b11110000, 0b00001111, 0b10101010, 0b01010101 };
157-
_ = .{ &v, &x };
158-
try expect(mem.eql(u8, &@as([4]u8, v ^ x), &[4]u8{ 0b01011010, 0b10100101, 0b00000000, 0b11111111 }));
159-
try expect(mem.eql(u8, &@as([4]u8, v | x), &[4]u8{ 0b11111010, 0b10101111, 0b10101010, 0b11111111 }));
160-
try expect(mem.eql(u8, &@as([4]u8, v & x), &[4]u8{ 0b10100000, 0b00001010, 0b10101010, 0b00000000 }));
155+
{
156+
var v: @Vector(4, bool) = [4]bool{ false, false, true, true };
157+
var x: @Vector(4, bool) = [4]bool{ true, false, true, false };
158+
_ = .{ &v, &x };
159+
try expect(mem.eql(bool, &@as([4]bool, v ^ x), &[4]bool{ true, false, false, true }));
160+
try expect(mem.eql(bool, &@as([4]bool, v | x), &[4]bool{ true, false, true, true }));
161+
try expect(mem.eql(bool, &@as([4]bool, v & x), &[4]bool{ false, false, true, false }));
162+
}
163+
{
164+
var v: @Vector(4, u8) = [4]u8{ 0b10101010, 0b10101010, 0b10101010, 0b10101010 };
165+
var x: @Vector(4, u8) = [4]u8{ 0b11110000, 0b00001111, 0b10101010, 0b01010101 };
166+
_ = .{ &v, &x };
167+
try expect(mem.eql(u8, &@as([4]u8, v ^ x), &[4]u8{ 0b01011010, 0b10100101, 0b00000000, 0b11111111 }));
168+
try expect(mem.eql(u8, &@as([4]u8, v | x), &[4]u8{ 0b11111010, 0b10101111, 0b10101010, 0b11111111 }));
169+
try expect(mem.eql(u8, &@as([4]u8, v & x), &[4]u8{ 0b10100000, 0b00001010, 0b10101010, 0b00000000 }));
170+
}
161171
}
162172
};
163173
try S.doTheTest();
@@ -659,15 +669,41 @@ test "vector bitwise not operator" {
659669
}
660670
}
661671
fn doTheTest() !void {
662-
try doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 });
663-
try doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 });
664-
try doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 });
665-
try doTheTestNot(u64, [_]u64{ 0, 2, 4, 255 });
672+
try doTheTestNot(bool, [_]bool{ true, false, true, false });
666673

667674
try doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 });
668675
try doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 });
669676
try doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 });
670677
try doTheTestNot(u64, [_]u64{ 0, 2, 4, 255 });
678+
679+
try doTheTestNot(i8, [_]i8{ 0, 2, 4, 127 });
680+
try doTheTestNot(i16, [_]i16{ 0, 2, 4, 127 });
681+
try doTheTestNot(i32, [_]i32{ 0, 2, 4, 127 });
682+
try doTheTestNot(i64, [_]i64{ 0, 2, 4, 127 });
683+
}
684+
};
685+
686+
try S.doTheTest();
687+
try comptime S.doTheTest();
688+
}
689+
690+
test "vector boolean not operator" {
691+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
692+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
693+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
694+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
695+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
696+
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
697+
698+
const S = struct {
699+
fn doTheTestNot(comptime T: type, x: @Vector(4, T)) !void {
700+
const y = !x;
701+
for (@as([4]T, y), 0..) |v, i| {
702+
try expect(!x[i] == v);
703+
}
704+
}
705+
fn doTheTest() !void {
706+
try doTheTestNot(bool, [_]bool{ true, false, true, false });
671707
}
672708
};
673709

0 commit comments

Comments
 (0)