commit 7140d08334de7d3c0c1342a0c2eb8a8db638b5df (tree)
parent f4c4daec863cf7394e141aefc5ad52e9c34f5979
Author: Ali Cheraghi <alichraghi@proton.me>
Date: Sun, 14 Jun 2026 23:20:56 +0330
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>
Diffstat:
10 files changed, 62 insertions(+), 45 deletions(-)
diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig
@@ -3016,9 +3016,9 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void {
.ptr_add => try cg.airPtrAdd(inst),
.ptr_sub => try cg.airPtrSub(inst),
- .bit_and => try cg.airBinOpSimple(inst, .OpBitwiseAnd),
- .bit_or => try cg.airBinOpSimple(inst, .OpBitwiseOr),
- .xor => try cg.airBinOpSimple(inst, .OpBitwiseXor),
+ .bit_and => try cg.airBitwiseOp(inst, .bit_and),
+ .bit_or => try cg.airBitwiseOp(inst, .bit_or),
+ .xor => try cg.airBitwiseOp(inst, .xor),
.shl, .shl_exact => try cg.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical),
.shr, .shr_exact => try cg.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic),
@@ -3145,6 +3145,34 @@ fn airBinOpSimple(cg: *CodeGen, inst: Air.Inst.Index, op: Opcode) !?Id {
return try result.materialize(cg);
}
+const BitwiseOp = enum { bit_and, bit_or, xor };
+
+fn airBitwiseOp(cg: *CodeGen, inst: Air.Inst.Index, op: BitwiseOp) !?Id {
+ const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
+ const lhs = try cg.temporary(bin_op.lhs);
+ const rhs = try cg.temporary(bin_op.rhs);
+ const info = cg.arithmeticTypeInfo(lhs.ty);
+
+ // SPIR-V requires logical opcodes for booleans, bitwise opcodes for integers.
+ const opcode: Opcode = switch (info.class) {
+ .bool => switch (op) {
+ .bit_and => .OpLogicalAnd,
+ .bit_or => .OpLogicalOr,
+ .xor => .OpLogicalNotEqual,
+ },
+ .integer, .strange_integer => switch (op) {
+ .bit_and => .OpBitwiseAnd,
+ .bit_or => .OpBitwiseOr,
+ .xor => .OpBitwiseXor,
+ },
+ .float => unreachable,
+ .composite_integer => unreachable, // TODO
+ };
+
+ const result = try cg.buildBinary(opcode, lhs, rhs);
+ return try result.materialize(cg);
+}
+
fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode) !?Id {
const zcu = cg.module.zcu;
const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
@@ -3389,9 +3417,12 @@ fn airArithOp(
const info = cg.arithmeticTypeInfo(lhs.ty);
const result = switch (info.class) {
.composite_integer => unreachable, // TODO
- .integer, .strange_integer => switch (info.signedness) {
- .signed => try cg.buildBinary(sop, lhs, rhs),
- .unsigned => try cg.buildBinary(uop, lhs, rhs),
+ .integer, .strange_integer => res: {
+ const raw = switch (info.signedness) {
+ .signed => try cg.buildBinary(sop, lhs, rhs),
+ .unsigned => try cg.buildBinary(uop, lhs, rhs),
+ };
+ break :res try cg.normalize(raw, info);
},
.float => try cg.buildBinary(fop, lhs, rhs),
.bool => unreachable,
@@ -3766,7 +3797,6 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
const operand = try cg.resolve(reduce.operand);
const operand_ty = cg.typeOf(reduce.operand);
const scalar_ty = operand_ty.scalarType(zcu);
- const scalar_ty_id = try cg.resolveType(scalar_ty, .direct);
const info = cg.arithmeticTypeInfo(operand_ty);
const len = operand_ty.vectorLen(zcu);
const first = try cg.extractVectorComponent(scalar_ty, operand, 0);
@@ -3792,8 +3822,6 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
else => {},
}
- var result_id = first;
-
const opcode: Opcode = switch (info.class) {
.bool => switch (reduce.operation) {
.And => .OpLogicalAnd,
@@ -3817,19 +3845,18 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
.composite_integer => unreachable, // TODO
};
- for (1..len) |i| {
- const lhs = result_id;
- const rhs = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i));
- result_id = cg.module.allocId();
+ const needs_normalize = info.class == .strange_integer and
+ (reduce.operation == .Add or reduce.operation == .Mul);
- try cg.body.emitRaw(cg.module.gpa, opcode, 4);
- cg.body.writeOperand(Id, scalar_ty_id);
- cg.body.writeOperand(Id, result_id);
- cg.body.writeOperand(Id, lhs);
- cg.body.writeOperand(Id, rhs);
+ var result: Temporary = .init(scalar_ty, first);
+ for (1..len) |i| {
+ const rhs_id = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i));
+ const rhs: Temporary = .init(scalar_ty, rhs_id);
+ const stepped = try cg.buildBinary(opcode, result, rhs);
+ result = if (needs_normalize) try cg.normalize(stepped, info) else stepped;
}
- return result_id;
+ return try result.materialize(cg);
}
fn airShuffleOne(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
@@ -4314,7 +4341,11 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
const dst_info = cg.arithmeticTypeInfo(dst_ty);
if (src_info.backing_bits == dst_info.backing_bits) {
- return try src.materialize(cg);
+ const result = if (dst_info.bits < src_info.bits)
+ try cg.normalize(src.pun(dst_ty), dst_info)
+ else
+ src.pun(dst_ty);
+ return try result.materialize(cg);
}
const converted = try cg.buildConvert(dst_ty, src);
diff --git a/test/behavior/bool.zig b/test/behavior/bool.zig
@@ -10,7 +10,6 @@ test "bool literals" {
test "cast bool to int" {
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const t = true;
const f = false;
diff --git a/test/behavior/cast_int.zig b/test/behavior/cast_int.zig
@@ -19,7 +19,6 @@ test "@intCast i32 to u7" {
}
test "coerce i8 to i32 and @intCast back" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -35,7 +34,6 @@ test "coerce i8 to i32 and @intCast back" {
}
test "coerce non byte-sized integers accross 32bits boundary" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO
{
diff --git a/test/behavior/duplicated_test_names.zig b/test/behavior/duplicated_test_names.zig
@@ -15,7 +15,6 @@ comptime {
test "thingy" {}
test thingy {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (thingy(1, 2) != 3) unreachable;
diff --git a/test/behavior/hasdecl.zig b/test/behavior/hasdecl.zig
@@ -13,7 +13,6 @@ const Bar = struct {
test "@hasDecl" {
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expect(@hasDecl(Foo, "public_thing"));
try expect(!@hasDecl(Foo, "private_thing"));
@@ -26,7 +25,6 @@ test "@hasDecl" {
test "@hasDecl using a sliced string literal" {
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expect(@hasDecl(@This(), "std") == true);
try expect(@hasDecl(@This(), "std"[0..0]) == false);
diff --git a/test/behavior/import.zig b/test/behavior/import.zig
@@ -5,21 +5,18 @@ const expectEqual = std.testing.expectEqual;
const a_namespace = @import("import/a_namespace.zig");
test "call fn via namespace lookup" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
try expect(@as(i32, 1234) == a_namespace.foo());
}
test "importing the same thing gives the same import" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
try expect(@import("std") == @import("std"));
}
test "import empty file" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
_ = @import("import/empty.zig");
diff --git a/test/behavior/ir_block_deps.zig b/test/behavior/ir_block_deps.zig
@@ -19,8 +19,8 @@ fn getErrInt() anyerror!i32 {
test "ir block deps" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
- if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try expect((foo(1) catch unreachable) == 0);
try expect((foo(2) catch unreachable) == 0);
diff --git a/test/behavior/namespace_depends_on_compile_var.zig b/test/behavior/namespace_depends_on_compile_var.zig
@@ -3,7 +3,6 @@ const builtin = @import("builtin");
const expect = std.testing.expect;
test "namespace depends on compile var" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (some_namespace.a_bool) {
diff --git a/test/behavior/pub_enum.zig b/test/behavior/pub_enum.zig
@@ -3,7 +3,6 @@ const other = @import("pub_enum/other.zig");
const expect = @import("std").testing.expect;
test "pub enum" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
try pubEnumTest(other.APubEnum.Two);
@@ -13,7 +12,6 @@ fn pubEnumTest(foo: other.APubEnum) !void {
}
test "cast with imported symbol" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
try expect(@as(other.size_t, 42) == 42);
diff --git a/test/behavior/wrapping_arithmetic.zig b/test/behavior/wrapping_arithmetic.zig
@@ -3,9 +3,9 @@ const builtin = @import("builtin");
const minInt = std.math.minInt;
const maxInt = std.math.maxInt;
const expect = std.testing.expect;
+const skip128 = builtin.zig_backend == .stage2_spirv;
test "wrapping add" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
@@ -14,14 +14,14 @@ test "wrapping add" {
try testWrapAdd(i8, -128, -128, 0);
try testWrapAdd(i2, 1, 1, -2);
try testWrapAdd(i64, maxInt(i64), 1, minInt(i64));
- try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0);
- try testWrapAdd(i128, minInt(i128), maxInt(i128), -1);
+ if (!skip128) try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0);
+ if (!skip128) try testWrapAdd(i128, minInt(i128), maxInt(i128), -1);
try testWrapAdd(i8, 127, 127, -2);
try testWrapAdd(u8, 3, 10, 13);
try testWrapAdd(u8, 255, 255, 254);
try testWrapAdd(u2, 3, 2, 1);
try testWrapAdd(u3, 7, 1, 0);
- try testWrapAdd(u128, maxInt(u128), 1, minInt(u128));
+ if (!skip128) try testWrapAdd(u128, maxInt(u128), 1, minInt(u128));
}
fn testWrapAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
@@ -43,7 +43,6 @@ test "wrapping add" {
}
test "wrapping subtraction" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
@@ -52,12 +51,12 @@ test "wrapping subtraction" {
try testWrapSub(i8, -128, -128, 0);
try testWrapSub(i8, -1, 127, -128);
try testWrapSub(i64, minInt(i64), 1, maxInt(i64));
- try testWrapSub(i128, maxInt(i128), -1, minInt(i128));
- try testWrapSub(i128, minInt(i128), -maxInt(i128), -1);
+ if (!skip128) try testWrapSub(i128, maxInt(i128), -1, minInt(i128));
+ if (!skip128) try testWrapSub(i128, minInt(i128), -maxInt(i128), -1);
try testWrapSub(u8, 10, 3, 7);
try testWrapSub(u8, 0, 255, 1);
try testWrapSub(u5, 0, 31, 1);
- try testWrapSub(u128, 0, maxInt(u128), 1);
+ if (!skip128) try testWrapSub(u128, 0, maxInt(u128), 1);
}
fn testWrapSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
@@ -79,7 +78,6 @@ test "wrapping subtraction" {
}
test "wrapping multiplication" {
- if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
@@ -90,11 +88,11 @@ test "wrapping multiplication" {
try testWrapMul(i8, -128, -128, 0);
try testWrapMul(i8, maxInt(i8), maxInt(i8), 1);
try testWrapMul(i16, maxInt(i16), -1, minInt(i16) + 1);
- try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1);
- try testWrapMul(i128, minInt(i128), -1, minInt(i128));
+ if (!skip128) try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1);
+ if (!skip128) try testWrapMul(i128, minInt(i128), -1, minInt(i128));
try testWrapMul(u8, 10, 3, 30);
try testWrapMul(u8, 2, 255, 254);
- try testWrapMul(u128, maxInt(u128), maxInt(u128), 1);
+ if (!skip128) try testWrapMul(u128, maxInt(u128), maxInt(u128), 1);
}
fn testWrapMul(comptime T: type, lhs: T, rhs: T, expected: T) !void {