authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-14 23:20:56+03:30
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-06-18 13:38:58+02:00
log7140d08334de7d3c0c1342a0c2eb8a8db638b5df
treec031195d03ff3ce11e65a7342eab968a64767d3f
parentf4c4daec863cf7394e141aefc5ad52e9c34f5979

spirv: int/bool fixes

- use logical ops for boolean bitwise instructions - normalize strange-int results in airArithOp, airReduce, and airIntCast Co-authored-by: Quint Daenen <quint@daenen.email>

10 files changed, 62 insertions(+), 45 deletions(-)

src/codegen/spirv/CodeGen.zig+51-20
......@@ -3016,9 +3016,9 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void {
30163016 .ptr_add => try cg.airPtrAdd(inst),
30173017 .ptr_sub => try cg.airPtrSub(inst),
30183018
3019 .bit_and => try cg.airBinOpSimple(inst, .OpBitwiseAnd),
3020 .bit_or => try cg.airBinOpSimple(inst, .OpBitwiseOr),
3021 .xor => try cg.airBinOpSimple(inst, .OpBitwiseXor),
3019 .bit_and => try cg.airBitwiseOp(inst, .bit_and),
3020 .bit_or => try cg.airBitwiseOp(inst, .bit_or),
3021 .xor => try cg.airBitwiseOp(inst, .xor),
30223022
30233023 .shl, .shl_exact => try cg.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical),
30243024 .shr, .shr_exact => try cg.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic),
......@@ -3145,6 +3145,34 @@ fn airBinOpSimple(cg: *CodeGen, inst: Air.Inst.Index, op: Opcode) !?Id {
31453145 return try result.materialize(cg);
31463146}
31473147
3148const BitwiseOp = enum { bit_and, bit_or, xor };
3149
3150fn airBitwiseOp(cg: *CodeGen, inst: Air.Inst.Index, op: BitwiseOp) !?Id {
3151 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3152 const lhs = try cg.temporary(bin_op.lhs);
3153 const rhs = try cg.temporary(bin_op.rhs);
3154 const info = cg.arithmeticTypeInfo(lhs.ty);
3155
3156 // SPIR-V requires logical opcodes for booleans, bitwise opcodes for integers.
3157 const opcode: Opcode = switch (info.class) {
3158 .bool => switch (op) {
3159 .bit_and => .OpLogicalAnd,
3160 .bit_or => .OpLogicalOr,
3161 .xor => .OpLogicalNotEqual,
3162 },
3163 .integer, .strange_integer => switch (op) {
3164 .bit_and => .OpBitwiseAnd,
3165 .bit_or => .OpBitwiseOr,
3166 .xor => .OpBitwiseXor,
3167 },
3168 .float => unreachable,
3169 .composite_integer => unreachable, // TODO
3170 };
3171
3172 const result = try cg.buildBinary(opcode, lhs, rhs);
3173 return try result.materialize(cg);
3174}
3175
31483176fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode) !?Id {
31493177 const zcu = cg.module.zcu;
31503178 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
......@@ -3389,9 +3417,12 @@ fn airArithOp(
33893417 const info = cg.arithmeticTypeInfo(lhs.ty);
33903418 const result = switch (info.class) {
33913419 .composite_integer => unreachable, // TODO
3392 .integer, .strange_integer => switch (info.signedness) {
3393 .signed => try cg.buildBinary(sop, lhs, rhs),
3394 .unsigned => try cg.buildBinary(uop, lhs, rhs),
3420 .integer, .strange_integer => res: {
3421 const raw = switch (info.signedness) {
3422 .signed => try cg.buildBinary(sop, lhs, rhs),
3423 .unsigned => try cg.buildBinary(uop, lhs, rhs),
3424 };
3425 break :res try cg.normalize(raw, info);
33953426 },
33963427 .float => try cg.buildBinary(fop, lhs, rhs),
33973428 .bool => unreachable,
......@@ -3766,7 +3797,6 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
37663797 const operand = try cg.resolve(reduce.operand);
37673798 const operand_ty = cg.typeOf(reduce.operand);
37683799 const scalar_ty = operand_ty.scalarType(zcu);
3769 const scalar_ty_id = try cg.resolveType(scalar_ty, .direct);
37703800 const info = cg.arithmeticTypeInfo(operand_ty);
37713801 const len = operand_ty.vectorLen(zcu);
37723802 const first = try cg.extractVectorComponent(scalar_ty, operand, 0);
......@@ -3792,8 +3822,6 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
37923822 else => {},
37933823 }
37943824
3795 var result_id = first;
3796
37973825 const opcode: Opcode = switch (info.class) {
37983826 .bool => switch (reduce.operation) {
37993827 .And => .OpLogicalAnd,
......@@ -3817,19 +3845,18 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
38173845 .composite_integer => unreachable, // TODO
38183846 };
38193847
3820 for (1..len) |i| {
3821 const lhs = result_id;
3822 const rhs = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i));
3823 result_id = cg.module.allocId();
3848 const needs_normalize = info.class == .strange_integer and
3849 (reduce.operation == .Add or reduce.operation == .Mul);
38243850
3825 try cg.body.emitRaw(cg.module.gpa, opcode, 4);
3826 cg.body.writeOperand(Id, scalar_ty_id);
3827 cg.body.writeOperand(Id, result_id);
3828 cg.body.writeOperand(Id, lhs);
3829 cg.body.writeOperand(Id, rhs);
3851 var result: Temporary = .init(scalar_ty, first);
3852 for (1..len) |i| {
3853 const rhs_id = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i));
3854 const rhs: Temporary = .init(scalar_ty, rhs_id);
3855 const stepped = try cg.buildBinary(opcode, result, rhs);
3856 result = if (needs_normalize) try cg.normalize(stepped, info) else stepped;
38303857 }
38313858
3832 return result_id;
3859 return try result.materialize(cg);
38333860}
38343861
38353862fn airShuffleOne(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
......@@ -4314,7 +4341,11 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
43144341 const dst_info = cg.arithmeticTypeInfo(dst_ty);
43154342
43164343 if (src_info.backing_bits == dst_info.backing_bits) {
4317 return try src.materialize(cg);
4344 const result = if (dst_info.bits < src_info.bits)
4345 try cg.normalize(src.pun(dst_ty), dst_info)
4346 else
4347 src.pun(dst_ty);
4348 return try result.materialize(cg);
43184349 }
43194350
43204351 const converted = try cg.buildConvert(dst_ty, src);
test/behavior/bool.zig-1
......@@ -10,7 +10,6 @@ test "bool literals" {
1010
1111test "cast bool to int" {
1212 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1413
1514 const t = true;
1615 const f = false;
test/behavior/cast_int.zig-2
......@@ -19,7 +19,6 @@ test "@intCast i32 to u7" {
1919}
2020
2121test "coerce i8 to i32 and @intCast back" {
22 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2322 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
2423 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
2524
......@@ -35,7 +34,6 @@ test "coerce i8 to i32 and @intCast back" {
3534}
3635
3736test "coerce non byte-sized integers accross 32bits boundary" {
38 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
3937 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO
4038
4139 {
test/behavior/duplicated_test_names.zig-1
......@@ -15,7 +15,6 @@ comptime {
1515test "thingy" {}
1616
1717test thingy {
18 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1918 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2019
2120 if (thingy(1, 2) != 3) unreachable;
test/behavior/hasdecl.zig-2
......@@ -13,7 +13,6 @@ const Bar = struct {
1313
1414test "@hasDecl" {
1515 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
16 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1716
1817 try expect(@hasDecl(Foo, "public_thing"));
1918 try expect(!@hasDecl(Foo, "private_thing"));
......@@ -26,7 +25,6 @@ test "@hasDecl" {
2625
2726test "@hasDecl using a sliced string literal" {
2827 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
29 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
3028
3129 try expect(@hasDecl(@This(), "std") == true);
3230 try expect(@hasDecl(@This(), "std"[0..0]) == false);
test/behavior/import.zig-3
......@@ -5,21 +5,18 @@ const expectEqual = std.testing.expectEqual;
55const a_namespace = @import("import/a_namespace.zig");
66
77test "call fn via namespace lookup" {
8 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
98 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
109
1110 try expect(@as(i32, 1234) == a_namespace.foo());
1211}
1312
1413test "importing the same thing gives the same import" {
15 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1614 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1715
1816 try expect(@import("std") == @import("std"));
1917}
2018
2119test "import empty file" {
22 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2320 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2421
2522 _ = @import("import/empty.zig");
test/behavior/ir_block_deps.zig+1-1
......@@ -19,8 +19,8 @@ fn getErrInt() anyerror!i32 {
1919
2020test "ir block deps" {
2121 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
22 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
2322 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
23 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
2424
2525 try expect((foo(1) catch unreachable) == 0);
2626 try expect((foo(2) catch unreachable) == 0);
test/behavior/namespace_depends_on_compile_var.zig-1
......@@ -3,7 +3,6 @@ const builtin = @import("builtin");
33const expect = std.testing.expect;
44
55test "namespace depends on compile var" {
6 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
76 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
87
98 if (some_namespace.a_bool) {
test/behavior/pub_enum.zig-2
......@@ -3,7 +3,6 @@ const other = @import("pub_enum/other.zig");
33const expect = @import("std").testing.expect;
44
55test "pub enum" {
6 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
76 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
87
98 try pubEnumTest(other.APubEnum.Two);
......@@ -13,7 +12,6 @@ fn pubEnumTest(foo: other.APubEnum) !void {
1312}
1413
1514test "cast with imported symbol" {
16 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1715 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1816
1917 try expect(@as(other.size_t, 42) == 42);
test/behavior/wrapping_arithmetic.zig+10-12
......@@ -3,9 +3,9 @@ const builtin = @import("builtin");
33const minInt = std.math.minInt;
44const maxInt = std.math.maxInt;
55const expect = std.testing.expect;
6const skip128 = builtin.zig_backend == .stage2_spirv;
67
78test "wrapping add" {
8 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
99 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1010
1111 const S = struct {
......@@ -14,14 +14,14 @@ test "wrapping add" {
1414 try testWrapAdd(i8, -128, -128, 0);
1515 try testWrapAdd(i2, 1, 1, -2);
1616 try testWrapAdd(i64, maxInt(i64), 1, minInt(i64));
17 try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0);
18 try testWrapAdd(i128, minInt(i128), maxInt(i128), -1);
17 if (!skip128) try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0);
18 if (!skip128) try testWrapAdd(i128, minInt(i128), maxInt(i128), -1);
1919 try testWrapAdd(i8, 127, 127, -2);
2020 try testWrapAdd(u8, 3, 10, 13);
2121 try testWrapAdd(u8, 255, 255, 254);
2222 try testWrapAdd(u2, 3, 2, 1);
2323 try testWrapAdd(u3, 7, 1, 0);
24 try testWrapAdd(u128, maxInt(u128), 1, minInt(u128));
24 if (!skip128) try testWrapAdd(u128, maxInt(u128), 1, minInt(u128));
2525 }
2626
2727 fn testWrapAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
......@@ -43,7 +43,6 @@ test "wrapping add" {
4343}
4444
4545test "wrapping subtraction" {
46 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
4746 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
4847
4948 const S = struct {
......@@ -52,12 +51,12 @@ test "wrapping subtraction" {
5251 try testWrapSub(i8, -128, -128, 0);
5352 try testWrapSub(i8, -1, 127, -128);
5453 try testWrapSub(i64, minInt(i64), 1, maxInt(i64));
55 try testWrapSub(i128, maxInt(i128), -1, minInt(i128));
56 try testWrapSub(i128, minInt(i128), -maxInt(i128), -1);
54 if (!skip128) try testWrapSub(i128, maxInt(i128), -1, minInt(i128));
55 if (!skip128) try testWrapSub(i128, minInt(i128), -maxInt(i128), -1);
5756 try testWrapSub(u8, 10, 3, 7);
5857 try testWrapSub(u8, 0, 255, 1);
5958 try testWrapSub(u5, 0, 31, 1);
60 try testWrapSub(u128, 0, maxInt(u128), 1);
59 if (!skip128) try testWrapSub(u128, 0, maxInt(u128), 1);
6160 }
6261
6362 fn testWrapSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
......@@ -79,7 +78,6 @@ test "wrapping subtraction" {
7978}
8079
8180test "wrapping multiplication" {
82 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
8381 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
8482
8583 const S = struct {
......@@ -90,11 +88,11 @@ test "wrapping multiplication" {
9088 try testWrapMul(i8, -128, -128, 0);
9189 try testWrapMul(i8, maxInt(i8), maxInt(i8), 1);
9290 try testWrapMul(i16, maxInt(i16), -1, minInt(i16) + 1);
93 try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1);
94 try testWrapMul(i128, minInt(i128), -1, minInt(i128));
91 if (!skip128) try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1);
92 if (!skip128) try testWrapMul(i128, minInt(i128), -1, minInt(i128));
9593 try testWrapMul(u8, 10, 3, 30);
9694 try testWrapMul(u8, 2, 255, 254);
97 try testWrapMul(u128, maxInt(u128), maxInt(u128), 1);
95 if (!skip128) try testWrapMul(u128, maxInt(u128), maxInt(u128), 1);
9896 }
9997
10098 fn testWrapMul(comptime T: type, lhs: T, rhs: T, expected: T) !void {