authorgravatar for dakongsgaard@gmail.comDaniel Kongsgaard <dakongsgaard@gmail.com> 2026-10-15 22:27:42+02:00
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-10-25 09:50:54+01:00
log2f90fcd8393b493eafa3879b9ca6b2d644721f83
tree5f605e17c1cc40afb2719c5fe65b26dd2b696dc6
parent064c78505596ae2bbfec63236df3b93a85e79aad

spirv: fix negative integers in switch


2 files changed, 89 insertions(+), 26 deletions(-)

src/codegen/spirv/CodeGen.zig+41-26
......@@ -1137,6 +1137,42 @@ fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id {
11371137 }
11381138}
11391139
1140/// Returns the OpSwitch literal int corresponding to ref (case of the switch),
1141/// where ty is the type we switch on and words is the width of the literal
1142/// (words = 1 means up to 32 bits, and 2 means 64 bits).
1143fn literalInt(cg: *CodeGen, ty: Type, words: u32, ref: Air.Inst.Ref) spec.LiteralContextDependentNumber {
1144 const zcu = cg.zcu;
1145 const target = cg.zcu.getTarget();
1146
1147 var int_ty: Type = ty;
1148 var value: Value = .fromInterned(ref.toInterned().?);
1149 const int_val: u64 = blk: switch (int_ty.zigTypeTag(zcu)) {
1150 .bool, .int => if (int_ty.isSignedInt(zcu)) switch (target.os.tag) {
1151 // Kernel only supports unsigned ints, so use the unsigned backing type.
1152 .opencl, .amdhsa => switch (cg.backingIntBits(int_ty.intInfo(zcu).bits).@"0") {
1153 inline 8, 16, 32, 64 => |bits| @as(@Int(.unsigned, bits), @truncate(@as(u64, @bitCast(value.toSignedInt(zcu))))),
1154 else => unreachable,
1155 },
1156 else => @bitCast(value.toSignedInt(zcu)),
1157 } else value.toUnsignedInt(zcu),
1158 .@"enum" => {
1159 // Handle proper backingInt conversion in the .int case.
1160 int_ty = int_ty.backingIntType(zcu);
1161 value = value.backingInt(zcu);
1162 continue :blk .int;
1163 },
1164 .error_set => value.getErrorInt(zcu),
1165 .pointer => value.toUnsignedInt(zcu),
1166 else => unreachable,
1167 };
1168
1169 return switch (words) {
1170 1 => .{ .uint32 = @truncate(int_val) },
1171 2 => .{ .uint64 = int_val },
1172 else => unreachable,
1173 };
1174}
1175
11401176/// Emits an integer constant.
11411177/// This function, unlike cg.constInt, takes care to bitcast
11421178/// the value to an unsigned int first for Kernels.
......@@ -7864,19 +7900,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
78647900 const label = case_labels.at(case.idx);
78657901
78667902 for (case.items) |item| {
7867 const value: Value = .fromInterned(item.toInterned().?);
7868 const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) {
7869 .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu),
7870 .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu),
7871 .error_set => value.getErrorInt(zcu),
7872 .pointer => value.toUnsignedInt(zcu),
7873 else => unreachable,
7874 };
7875 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
7876 1 => .{ .uint32 = @intCast(int_val) },
7877 2 => .{ .uint64 = int_val },
7878 else => unreachable,
7879 };
7903 const int_lit = cg.literalInt(cond_ty, cond_words, item);
78807904 cg.body.writeOperand(spec.LiteralContextDependentNumber, int_lit);
78817905 cg.body.writeOperand(Id, label);
78827906 }
......@@ -8098,25 +8122,16 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
80988122 cg.body.writeOperand(Id, cond_indirect);
80998123 cg.body.writeOperand(Id, switch_default);
81008124
8125 // Emit the non-range cases into the OpSwitch.
8126 // Cases with ranges are handled by the conditional chain below.
81018127 {
81028128 var it = switch_br.iterateCases();
81038129 while (it.next()) |case| {
81048130 if (case.ranges.len > 0) continue;
81058131 const label = case_labels.at(case.idx);
8132
81068133 for (case.items) |item| {
8107 const value: Value = .fromInterned(item.toInterned().?);
8108 const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) {
8109 .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu),
8110 .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu),
8111 .error_set => value.getErrorInt(zcu),
8112 .pointer => value.toUnsignedInt(zcu),
8113 else => unreachable,
8114 };
8115 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
8116 1 => .{ .uint32 = @intCast(int_val) },
8117 2 => .{ .uint64 = int_val },
8118 else => unreachable,
8119 };
8134 const int_lit = cg.literalInt(cond_ty, cond_words, item);
81208135 cg.body.writeOperand(spec.LiteralContextDependentNumber, int_lit);
81218136 cg.body.writeOperand(Id, label);
81228137 }
test/cases/spirv_switch_negative_ints.zig created+48
......@@ -0,0 +1,48 @@
1// This tests that switches allow negative values when compiling for SPIR-V.
2// We use i32 to test the one-word OpSwitch literal path, which used to fail.
3
4const E = enum(i32) { neg = -1, pos = 1 };
5
6export fn intSwitch() void {
7 var x: i32 = -1;
8 _ = &x;
9 var y: i32 = switch (x) {
10 -1 => 1,
11 else => 0,
12 };
13 _ = &y;
14}
15
16export fn intLoopSwitch() void {
17 var x: i32 = -1;
18 _ = &x;
19 blk: switch (x) {
20 -1 => continue :blk 0,
21 else => {},
22 }
23}
24
25export fn enumSwitch() void {
26 var e: E = .neg;
27 _ = &e;
28 var y: i32 = switch (e) {
29 .neg => 1,
30 .pos => 2,
31 };
32 _ = &y;
33}
34
35export fn enumLoopSwitch() void {
36 var e: E = .neg;
37 _ = &e;
38 blk: switch (e) {
39 .neg => continue :blk .pos,
40 .pos => {},
41 }
42}
43
44// compile
45// output_mode=Obj
46// backend=selfhosted
47// target=spirv64-opencl,spirv64-vulkan
48// emit_bin=true