| author | |
| committer | |
| log | 5e3c0b7af7cd866f5464c244b9775e488b93ae48 |
| tree | 3e13c42df3ad3491c5b920c986abd62298b4e450 |
| parent | 4a02e080d127cc577d40f8ea5d68122ba8ac4243 |
| signature |
* Sema: allow binary operations and boolean not on vectors of bool
* langref: Clarify use of operators on vectors (`and` and `or` not allowed)
closes #240935 files changed, 79 insertions(+), 50 deletions(-)
doc/langref.html.in+5-2| ... | ... | @@ -1926,8 +1926,10 @@ or |
| 1926 | 1926 | Vector types are created with the builtin function {#link|@Vector#}. |
| 1927 | 1927 | </p> |
| 1928 | 1928 | <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 | |
| 1931 | 1933 | as the input vectors. This includes: |
| 1932 | 1934 | </p> |
| 1933 | 1935 | <ul> |
| ... | ... | @@ -1937,6 +1939,7 @@ or |
| 1937 | 1939 | <li>Bitwise operators ({#syntax#}>>{#endsyntax#}, {#syntax#}<<{#endsyntax#}, {#syntax#}&{#endsyntax#}, |
| 1938 | 1940 | {#syntax#}|{#endsyntax#}, {#syntax#}~{#endsyntax#}, etc.)</li> |
| 1939 | 1941 | <li>Comparison operators ({#syntax#}<{#endsyntax#}, {#syntax#}>{#endsyntax#}, {#syntax#}=={#endsyntax#}, etc.)</li> |
| 1942 | <li>Boolean not ({#syntax#}!{#endsyntax#})</li> | |
| 1940 | 1943 | </ul> |
| 1941 | 1944 | <p> |
| 1942 | 1945 | 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 |
| 806 | 806 | .bool_and => return boolBinOp(gz, scope, ri, node, .bool_br_and), |
| 807 | 807 | .bool_or => return boolBinOp(gz, scope, ri, node, .bool_br_or), |
| 808 | 808 | |
| 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), | |
| 810 | 810 | .bit_not => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, tree.nodeData(node).node, .bit_not), |
| 811 | 811 | |
| 812 | 812 | .negation => return negation(gz, scope, ri, node), |
src/Sema.zig+17-27| ... | ... | @@ -1171,11 +1171,11 @@ fn analyzeBodyInner( |
| 1171 | 1171 | .as_node => try sema.zirAsNode(block, inst), |
| 1172 | 1172 | .as_shift_operand => try sema.zirAsShiftOperand(block, inst), |
| 1173 | 1173 | .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), | |
| 1175 | 1175 | .bit_or => try sema.zirBitwise(block, inst, .bit_or), |
| 1176 | 1176 | .bitcast => try sema.zirBitcast(block, inst), |
| 1177 | 1177 | .suspend_block => try sema.zirSuspendBlock(block, inst), |
| 1178 | .bool_not => try sema.zirBoolNot(block, inst), | |
| 1178 | .bool_not => try sema.zirBitNot(block, inst, true), | |
| 1179 | 1179 | .bool_br_and => try sema.zirBoolBr(block, inst, false), |
| 1180 | 1180 | .bool_br_or => try sema.zirBoolBr(block, inst, true), |
| 1181 | 1181 | .c_import => try sema.zirCImport(block, inst), |
| ... | ... | @@ -14412,9 +14412,9 @@ fn zirBitwise( |
| 14412 | 14412 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| 14413 | 14413 | const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); |
| 14414 | 14414 | |
| 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; | |
| 14416 | 14416 | |
| 14417 | if (!is_int) { | |
| 14417 | if (!is_int_or_bool) { | |
| 14418 | 14418 | return sema.fail(block, src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag(zcu)), @tagName(rhs_ty.zigTypeTag(zcu)) }); |
| 14419 | 14419 | } |
| 14420 | 14420 | |
| ... | ... | @@ -14442,7 +14442,12 @@ fn zirBitwise( |
| 14442 | 14442 | return block.addBinOp(air_tag, casted_lhs, casted_rhs); |
| 14443 | 14443 | } |
| 14444 | 14444 | |
| 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 { | |
| 14446 | 14451 | const tracy = trace(@src()); |
| 14447 | 14452 | defer tracy.end(); |
| 14448 | 14453 | |
| ... | ... | @@ -14455,10 +14460,14 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. |
| 14455 | 14460 | const operand = try sema.resolveInst(inst_data.operand); |
| 14456 | 14461 | const operand_type = sema.typeOf(operand); |
| 14457 | 14462 | const scalar_type = operand_type.scalarType(zcu); |
| 14463 | const scalar_tag = scalar_type.zigTypeTag(zcu); | |
| 14458 | 14464 | |
| 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), | |
| 14462 | 14471 | }); |
| 14463 | 14472 | } |
| 14464 | 14473 | |
| ... | ... | @@ -18336,25 +18345,6 @@ fn zirTypeofPeer( |
| 18336 | 18345 | return Air.internedToRef(result_type.toIntern()); |
| 18337 | 18346 | } |
| 18338 | 18347 | |
| 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 | ||
| 18358 | 18348 | fn zirBoolBr( |
| 18359 | 18349 | sema: *Sema, |
| 18360 | 18350 | parent_block: *Block, |
src/Value.zig+10-10| ... | ... | @@ -1627,7 +1627,7 @@ pub fn numberMin(lhs: Value, rhs: Value, zcu: *Zcu) Value { |
| 1627 | 1627 | }; |
| 1628 | 1628 | } |
| 1629 | 1629 | |
| 1630 | /// operands must be (vectors of) integers; handles undefined scalars. | |
| 1630 | /// operands must be (vectors of) integers or bools; handles undefined scalars. | |
| 1631 | 1631 | pub fn bitwiseNot(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1632 | 1632 | const zcu = pt.zcu; |
| 1633 | 1633 | if (ty.zigTypeTag(zcu) == .vector) { |
| ... | ... | @@ -1645,7 +1645,7 @@ pub fn bitwiseNot(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Va |
| 1645 | 1645 | return bitwiseNotScalar(val, ty, arena, pt); |
| 1646 | 1646 | } |
| 1647 | 1647 | |
| 1648 | /// operands must be integers; handles undefined. | |
| 1648 | /// operands must be integers or bools; handles undefined. | |
| 1649 | 1649 | pub fn bitwiseNotScalar(val: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1650 | 1650 | const zcu = pt.zcu; |
| 1651 | 1651 | 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 |
| 1671 | 1671 | return pt.intValue_big(ty, result_bigint.toConst()); |
| 1672 | 1672 | } |
| 1673 | 1673 | |
| 1674 | /// operands must be (vectors of) integers; handles undefined scalars. | |
| 1674 | /// operands must be (vectors of) integers or bools; handles undefined scalars. | |
| 1675 | 1675 | pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu.PerThread) !Value { |
| 1676 | 1676 | const zcu = pt.zcu; |
| 1677 | 1677 | if (ty.zigTypeTag(zcu) == .vector) { |
| ... | ... | @@ -1690,7 +1690,7 @@ pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zc |
| 1690 | 1690 | return bitwiseAndScalar(lhs, rhs, ty, allocator, pt); |
| 1691 | 1691 | } |
| 1692 | 1692 | |
| 1693 | /// operands must be integers; handles undefined. | |
| 1693 | /// operands must be integers or bools; handles undefined. | |
| 1694 | 1694 | pub fn bitwiseAndScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1695 | 1695 | const zcu = pt.zcu; |
| 1696 | 1696 | // 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 { |
| 1744 | 1744 | return pt.intValue_big(ty, result_bigint.toConst()); |
| 1745 | 1745 | } |
| 1746 | 1746 | |
| 1747 | /// operands must be (vectors of) integers; handles undefined scalars. | |
| 1747 | /// operands must be (vectors of) integers or bools; handles undefined scalars. | |
| 1748 | 1748 | pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1749 | 1749 | const zcu = pt.zcu; |
| 1750 | 1750 | if (ty.zigTypeTag(zcu) == .vector) { |
| ... | ... | @@ -1763,7 +1763,7 @@ pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.P |
| 1763 | 1763 | return bitwiseNandScalar(lhs, rhs, ty, arena, pt); |
| 1764 | 1764 | } |
| 1765 | 1765 | |
| 1766 | /// operands must be integers; handles undefined. | |
| 1766 | /// operands must be integers or bools; handles undefined. | |
| 1767 | 1767 | pub fn bitwiseNandScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1768 | 1768 | const zcu = pt.zcu; |
| 1769 | 1769 | 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: |
| 1774 | 1774 | return bitwiseXor(anded, all_ones, ty, arena, pt); |
| 1775 | 1775 | } |
| 1776 | 1776 | |
| 1777 | /// operands must be (vectors of) integers; handles undefined scalars. | |
| 1777 | /// operands must be (vectors of) integers or bools; handles undefined scalars. | |
| 1778 | 1778 | pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu.PerThread) !Value { |
| 1779 | 1779 | const zcu = pt.zcu; |
| 1780 | 1780 | if (ty.zigTypeTag(zcu) == .vector) { |
| ... | ... | @@ -1793,7 +1793,7 @@ pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu |
| 1793 | 1793 | return bitwiseOrScalar(lhs, rhs, ty, allocator, pt); |
| 1794 | 1794 | } |
| 1795 | 1795 | |
| 1796 | /// operands must be integers; handles undefined. | |
| 1796 | /// operands must be integers or bools; handles undefined. | |
| 1797 | 1797 | pub fn bitwiseOrScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1798 | 1798 | // If one operand is defined, we turn the other into `0xAA` so the bitwise AND can |
| 1799 | 1799 | // still zero out some bits. |
| ... | ... | @@ -1827,7 +1827,7 @@ pub fn bitwiseOrScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Alloca |
| 1827 | 1827 | return pt.intValue_big(ty, result_bigint.toConst()); |
| 1828 | 1828 | } |
| 1829 | 1829 | |
| 1830 | /// operands must be (vectors of) integers; handles undefined scalars. | |
| 1830 | /// operands must be (vectors of) integers or bools; handles undefined scalars. | |
| 1831 | 1831 | pub fn bitwiseXor(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zcu.PerThread) !Value { |
| 1832 | 1832 | const zcu = pt.zcu; |
| 1833 | 1833 | if (ty.zigTypeTag(zcu) == .vector) { |
| ... | ... | @@ -1846,7 +1846,7 @@ pub fn bitwiseXor(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, pt: Zc |
| 1846 | 1846 | return bitwiseXorScalar(lhs, rhs, ty, allocator, pt); |
| 1847 | 1847 | } |
| 1848 | 1848 | |
| 1849 | /// operands must be integers; handles undefined. | |
| 1849 | /// operands must be integers or bools; handles undefined. | |
| 1850 | 1850 | pub fn bitwiseXorScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, pt: Zcu.PerThread) !Value { |
| 1851 | 1851 | const zcu = pt.zcu; |
| 1852 | 1852 | 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" { |
| 152 | 152 | |
| 153 | 153 | const S = struct { |
| 154 | 154 | 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 | } | |
| 161 | 171 | } |
| 162 | 172 | }; |
| 163 | 173 | try S.doTheTest(); |
| ... | ... | @@ -659,15 +669,41 @@ test "vector bitwise not operator" { |
| 659 | 669 | } |
| 660 | 670 | } |
| 661 | 671 | 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 }); | |
| 666 | 673 | |
| 667 | 674 | try doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 }); |
| 668 | 675 | try doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 }); |
| 669 | 676 | try doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 }); |
| 670 | 677 | 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 }); | |
| 671 | 707 | } |
| 672 | 708 | }; |
| 673 | 709 |