zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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:
Msrc/codegen/spirv/CodeGen.zig | 71+++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mtest/behavior/bool.zig | 1-
Mtest/behavior/cast_int.zig | 2--
Mtest/behavior/duplicated_test_names.zig | 1-
Mtest/behavior/hasdecl.zig | 2--
Mtest/behavior/import.zig | 3---
Mtest/behavior/ir_block_deps.zig | 2+-
Mtest/behavior/namespace_depends_on_compile_var.zig | 1-
Mtest/behavior/pub_enum.zig | 2--
Mtest/behavior/wrapping_arithmetic.zig | 22++++++++++------------
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 {