diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 7d6ab02462d784d24338909a91d611ee397b471a..3041553935052683e54ffef987db4c1944a57dda 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -1137,6 +1137,42 @@ fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id { } } +/// Returns the OpSwitch literal int corresponding to ref (case of the switch), +/// where ty is the type we switch on and words is the width of the literal +/// (words = 1 means up to 32 bits, and 2 means 64 bits). +fn literalInt(cg: *CodeGen, ty: Type, words: u32, ref: Air.Inst.Ref) spec.LiteralContextDependentNumber { + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); + + var int_ty: Type = ty; + var value: Value = .fromInterned(ref.toInterned().?); + const int_val: u64 = blk: switch (int_ty.zigTypeTag(zcu)) { + .bool, .int => if (int_ty.isSignedInt(zcu)) switch (target.os.tag) { + // Kernel only supports unsigned ints, so use the unsigned backing type. + .opencl, .amdhsa => switch (cg.backingIntBits(int_ty.intInfo(zcu).bits).@"0") { + inline 8, 16, 32, 64 => |bits| @as(@Int(.unsigned, bits), @truncate(@as(u64, @bitCast(value.toSignedInt(zcu))))), + else => unreachable, + }, + else => @bitCast(value.toSignedInt(zcu)), + } else value.toUnsignedInt(zcu), + .@"enum" => { + // Handle proper backingInt conversion in the .int case. + int_ty = int_ty.backingIntType(zcu); + value = value.backingInt(zcu); + continue :blk .int; + }, + .error_set => value.getErrorInt(zcu), + .pointer => value.toUnsignedInt(zcu), + else => unreachable, + }; + + return switch (words) { + 1 => .{ .uint32 = @truncate(int_val) }, + 2 => .{ .uint64 = int_val }, + else => unreachable, + }; +} + /// Emits an integer constant. /// This function, unlike cg.constInt, takes care to bitcast /// the value to an unsigned int first for Kernels. @@ -7864,19 +7900,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const label = case_labels.at(case.idx); for (case.items) |item| { - const value: Value = .fromInterned(item.toInterned().?); - const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) { - .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu), - .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu), - .error_set => value.getErrorInt(zcu), - .pointer => value.toUnsignedInt(zcu), - else => unreachable, - }; - const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) { - 1 => .{ .uint32 = @intCast(int_val) }, - 2 => .{ .uint64 = int_val }, - else => unreachable, - }; + const int_lit = cg.literalInt(cond_ty, cond_words, item); cg.body.writeOperand(spec.LiteralContextDependentNumber, int_lit); cg.body.writeOperand(Id, label); } @@ -8098,25 +8122,16 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { cg.body.writeOperand(Id, cond_indirect); cg.body.writeOperand(Id, switch_default); + // Emit the non-range cases into the OpSwitch. + // Cases with ranges are handled by the conditional chain below. { var it = switch_br.iterateCases(); while (it.next()) |case| { if (case.ranges.len > 0) continue; const label = case_labels.at(case.idx); + for (case.items) |item| { - const value: Value = .fromInterned(item.toInterned().?); - const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) { - .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu), - .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu), - .error_set => value.getErrorInt(zcu), - .pointer => value.toUnsignedInt(zcu), - else => unreachable, - }; - const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) { - 1 => .{ .uint32 = @intCast(int_val) }, - 2 => .{ .uint64 = int_val }, - else => unreachable, - }; + const int_lit = cg.literalInt(cond_ty, cond_words, item); cg.body.writeOperand(spec.LiteralContextDependentNumber, int_lit); cg.body.writeOperand(Id, label); } diff --git a/test/cases/spirv_switch_negative_ints.zig b/test/cases/spirv_switch_negative_ints.zig new file mode 100644 index 0000000000000000000000000000000000000000..a9e33a98db1d534b00d27abdeb48ae7ea09771b3 --- /dev/null +++ b/test/cases/spirv_switch_negative_ints.zig @@ -0,0 +1,48 @@ +// This tests that switches allow negative values when compiling for SPIR-V. +// We use i32 to test the one-word OpSwitch literal path, which used to fail. + +const E = enum(i32) { neg = -1, pos = 1 }; + +export fn intSwitch() void { + var x: i32 = -1; + _ = &x; + var y: i32 = switch (x) { + -1 => 1, + else => 0, + }; + _ = &y; +} + +export fn intLoopSwitch() void { + var x: i32 = -1; + _ = &x; + blk: switch (x) { + -1 => continue :blk 0, + else => {}, + } +} + +export fn enumSwitch() void { + var e: E = .neg; + _ = &e; + var y: i32 = switch (e) { + .neg => 1, + .pos => 2, + }; + _ = &y; +} + +export fn enumLoopSwitch() void { + var e: E = .neg; + _ = &e; + blk: switch (e) { + .neg => continue :blk .pos, + .pos => {}, + } +} + +// compile +// output_mode=Obj +// backend=selfhosted +// target=spirv64-opencl,spirv64-vulkan +// emit_bin=true