authorgravatar for dakongsgaard@gmail.comDaniel Kongsgaard <dakongsgaard@gmail.com> 2025-06-13 00:16:23+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-06-12 15:16:23-07:00
log5e3c0b7af7cd866f5464c244b9775e488b93ae48
tree3e13c42df3ad3491c5b920c986abd62298b4e450
parent4a02e080d127cc577d40f8ea5d68122ba8ac4243
signaturebadge-check Signed by PGP key B5690EEEBB952194

Allow more operators on bool vectors (#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 #24093

5 files changed, 79 insertions(+), 50 deletions(-)

doc/langref.html.in+5-2
......@@ -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+1-1
......@@ -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+17-27
......@@ -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
14445fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
14445fn 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
18339fn 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
1835818348fn zirBoolBr(
1835918349 sema: *Sema,
1836018350 parent_block: *Block,
src/Value.zig+10-10
......@@ -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.
16311631pub 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.
16491649pub 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.
16751675pub 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.
16941694pub 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.
17481748pub 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.
17671767pub 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.
17781778pub 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.
17971797pub 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.
18311831pub 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.
18501850pub 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+46-10
......@@ -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
690test "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