diff --git a/lib/zig.h b/lib/zig.h index 0b9c6e58ca..dc5666d894 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -1981,6 +1981,20 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { return zig_bitCast_i128(zig_bit_reverse_u128(zig_bitCast_u128(val), bits)); } +#if zig_has_int128 +#define zig_switch_int128(operand) switch (operand) +#define zig_switch_prong_begin_int128() +#define zig_switch_case_int128(Type, operand, value) case value: +#define zig_switch_prong_end_int128() +#define zig_switch_default_int128() default: +#else // zig_has_int128 +#define zig_switch_int128(operand) +#define zig_switch_prong_begin_int128() if (0 +#define zig_switch_case_int128(Type, operand, value) || (zig_cmp_##Type(operand, value) == 0) +#define zig_switch_prong_end_int128() ) +#define zig_switch_default_int128() +#endif // zig_has_int128 + /* ========================== Big Integer Support =========================== */ static inline uint16_t zig_int_bytes(uint16_t bits) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 97e624fd50..e126cf535b 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -4453,14 +4453,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void const switch_br = f.air.unwrapSwitch(inst); const init_condition = try f.resolveInst(switch_br.operand); try reap(f, inst, &.{switch_br.operand}); - const condition_ty = f.typeOf(switch_br.operand); + const cond_ty = f.typeOf(switch_br.operand); const w = &f.code.writer; // For dispatches, we will create a local alloc to contain the condition value. // This may not result in optimal codegen for switch loops, but it minimizes the // amount of C code we generate, which is probably more desirable here (and is simpler). - const condition = if (is_dispatch_loop) cond: { - const new_local = try f.allocLocal(inst, condition_ty); + const cond_val = if (is_dispatch_loop) cond: { + const new_local = try f.allocLocal(inst, cond_ty); try f.copyCValue(new_local, init_condition); try w.print("zig_switch_{d}_loop:", .{@intFromEnum(inst)}); try f.newline(); @@ -4472,26 +4472,38 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void assert(f.loop_switch_conds.remove(inst)); }; - try w.writeAll("switch ("); - - const lowered_condition_ty: Type = if (condition_ty.toIntern() == .bool_type) - .u1 - else if (condition_ty.isPtrAtRuntime(zcu)) - .usize - else - condition_ty; - if (condition_ty.toIntern() != lowered_condition_ty.toIntern()) { - try w.writeByte('('); - try f.renderType(w, lowered_condition_ty); - try w.writeByte(')'); - } - try f.writeCValue(w, condition, .other); - try w.writeAll(") {"); - f.indent(); - const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1); defer gpa.free(liveness.deaths); + const lowered_cond_ty: Type = switch (cond_ty.zigTypeTag(zcu)) { + .@"enum", .error_set, .int, .@"struct", .@"union" => cond_ty, + .bool => .u1, + .pointer => .usize, + .void => unreachable, // OPV type, always lowered to block/loop + .comptime_int, .enum_literal, .@"fn", .type => unreachable, // comptime-only + else => unreachable, // not supported by switch statement + }; + const cond_cint = switch (CType.classifyInt(lowered_cond_ty, zcu)) { + .void => unreachable, // OPV type, always lowered to block/loop + .small => |small| small, + .big => { + return lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, false); + }, + }; + + switch (cond_cint) { + .zig_u128, .zig_i128 => try w.writeAll("zig_switch_int128("), + else => try w.writeAll("switch ("), + } + if (cond_ty.toIntern() != lowered_cond_ty.toIntern()) { + try w.writeByte('('); + try f.renderType(w, lowered_cond_ty); + try w.writeByte(')'); + } + try f.writeCValue(w, cond_val, .other); + try w.writeAll(") {"); + f.indent(); + var any_range_cases = false; var it = switch_br.iterateCases(); while (it.next()) |case| { @@ -4499,28 +4511,63 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void any_range_cases = true; continue; } + + switch (cond_cint) { + .zig_u128, .zig_i128 => { + try f.newline(); + try w.writeAll("zig_switch_prong_begin_int128()"); + }, + else => {}, + } + for (case.items) |item| { try f.newline(); - try w.writeAll("case "); + case: { + switch (cond_cint) { + .zig_u128 => try w.writeAll(" zig_switch_case_int128(u128, "), + .zig_i128 => try w.writeAll(" zig_switch_case_int128(i128, "), + else => { + try w.writeAll("case "); + break :case; + }, + } + if (cond_ty.toIntern() != lowered_cond_ty.toIntern()) { + try w.writeByte('('); + try f.renderType(w, lowered_cond_ty); + try w.writeByte(')'); + } + try f.writeCValue(w, cond_val, .other); + try w.writeAll(", "); + } const item_value = try f.air.value(item, pt); // If `item_value` is a pointer with a known integer address, print the address // with no cast to avoid a warning. write_val: { - if (condition_ty.isPtrAtRuntime(zcu)) { + if (cond_ty.zigTypeTag(zcu) == .pointer) { if (item_value.?.getUnsignedInt(zcu)) |item_int| { - try w.print("{f}", .{try f.fmtIntLiteralDec(try pt.intValue(lowered_condition_ty, item_int))}); + try w.print("{f}", .{try f.fmtIntLiteralDec(try pt.intValue(lowered_cond_ty, item_int))}); break :write_val; } - } - if (condition_ty.isPtrAtRuntime(zcu)) { try w.writeByte('('); try f.renderType(w, .usize); try w.writeByte(')'); } try f.dg.renderValue(w, (try f.air.value(item, pt)).?, .other); } - try w.writeByte(':'); + switch (cond_cint) { + .zig_u128, .zig_i128 => try w.writeByte(')'), + else => try w.writeByte(':'), + } } + + switch (cond_cint) { + .zig_u128, .zig_i128 => { + try f.newline(); + try w.writeAll("zig_switch_prong_end_int128()"); + }, + else => {}, + } + try w.writeAll(" {"); f.indent(); try f.newline(); @@ -4537,56 +4584,24 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void // The case body must be noreturn so we don't need to insert a break. } - const else_body = it.elseBody(); try f.newline(); - try w.writeAll("default: "); + switch (cond_cint) { + .zig_u128, .zig_i128 => try w.writeAll("zig_switch_default_int128() "), + else => try w.writeAll("default: "), + } if (any_range_cases) { // We will iterate the cases again to handle those with ranges, and generate // code using conditions rather than switch cases for such cases. - it = switch_br.iterateCases(); - while (it.next()) |case| { - if (case.ranges.len == 0) continue; // handled above - - try w.writeAll("if ("); - for (case.items, 0..) |item, item_i| { - if (item_i != 0) try w.writeAll(" || "); - try f.writeCValue(w, condition, .other); - try w.writeAll(" == "); - try f.dg.renderValue(w, (try f.air.value(item, pt)).?, .other); - } - for (case.ranges, 0..) |range, range_i| { - if (case.items.len != 0 or range_i != 0) try w.writeAll(" || "); - // "(x >= lower && x <= upper)" - try w.writeByte('('); - try f.writeCValue(w, condition, .other); - try w.writeAll(" >= "); - try f.dg.renderValue(w, (try f.air.value(range[0], pt)).?, .other); - try w.writeAll(" && "); - try f.writeCValue(w, condition, .other); - try w.writeAll(" <= "); - try f.dg.renderValue(w, (try f.air.value(range[1], pt)).?, .other); - try w.writeByte(')'); - } - try w.writeAll(") {"); - f.indent(); - try f.newline(); - if (is_dispatch_loop) { - try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx }); - } - try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true); - try f.outdent(); - try w.writeByte('}'); - if (f.dg.expected_block) |_| - return f.fail("runtime code not allowed in naked function", .{}); - } + try lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, true); } if (is_dispatch_loop) { try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); } + const else_body = it.elseBody(); if (else_body.len > 0) { - // Note that this must be the last case, so we do not need to use `genBodyResolveState` since - // the parent block will do it (because the case body is noreturn). + // Note that this must be the last case, so we do not need to use `genBodyResolveState` + // since the parent block will do it (because the case body is noreturn). for (liveness.deaths[liveness.deaths.len - 1]) |death| { try die(f, inst, death.toRef()); } @@ -4598,6 +4613,111 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void try f.outdent(); try w.writeAll("}\n"); } +fn lowerSwitchToConditions( + f: *Function, + inst: Air.Inst.Index, + cond_val: CValue, + cond_ty: Type, + switch_br: Air.UnwrappedSwitch, + liveness: Air.Liveness.SwitchBrTable, + is_dispatch_loop: bool, + only_ranges: bool, +) !void { + const w = &f.code.writer; + + var it = switch_br.iterateCases(); + while (it.next()) |case| { + if (case.ranges.len == 0 and only_ranges) continue; + + try w.writeAll("if ("); + for (case.items, 0..) |item, item_i| { + if (item_i != 0) { + try f.newline(); + try w.writeAll(" || "); + } + try lowerSwitchCmp(f, cond_val, .eq, item, cond_ty); + } + for (case.ranges, 0..) |range, range_i| { + if (case.items.len != 0 or range_i != 0) { + try f.newline(); + try w.writeAll(" || "); + } + // "(x >= lower && x <= upper)" + try w.writeByte('('); + try lowerSwitchCmp(f, cond_val, .gte, range[0], cond_ty); + try w.writeAll(" && "); + try lowerSwitchCmp(f, cond_val, .lte, range[1], cond_ty); + try w.writeByte(')'); + } + try w.writeAll(") {"); + f.indent(); + try f.newline(); + if (is_dispatch_loop) { + try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx }); + } + try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true); + try f.outdent(); + try w.writeByte('}'); + try f.newline(); + if (f.dg.expected_block) |_| + return f.fail("runtime code not allowed in naked function", .{}); + } + + if (!only_ranges) { + if (is_dispatch_loop) { + try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); + } + const else_body = it.elseBody(); + if (else_body.len > 0) { + // Note that this must be the last case, so we do not need to use `genBodyResolveState` + // since the parent block will do it (because the case body is noreturn). + for (liveness.deaths[liveness.deaths.len - 1]) |death| { + try die(f, inst, death.toRef()); + } + try genBody(f, else_body); + if (f.dg.expected_block) |_| + return f.fail("runtime code not allowed in naked function", .{}); + } else try airUnreach(f); + try f.newline(); + } +} +fn lowerSwitchCmp( + f: *Function, + cond_val: CValue, + operator: std.math.CompareOperator, + case_inst: Air.Inst.Ref, + ty: Type, +) !void { + const pt = f.dg.pt; + const zcu = pt.zcu; + const w = &f.code.writer; + + const class = CType.classifyInt(ty, zcu); + const use_builtin = switch (class) { + .void => unreachable, // assertion failure + .small => |small| switch (small) { + .zig_u128, .zig_i128 => true, + else => false, + }, + .big => true, + }; + if (use_builtin) { + try w.writeAll("zig_cmp_"); + try f.dg.renderTypeForBuiltinFnName(w, ty); + try w.writeByte('('); + } + if (class == .big) try w.writeByte('&'); + try f.writeCValue(w, cond_val, .other); + try w.writeAll(if (use_builtin) ", " else compareOperatorC(operator)); + if (class == .big) try w.writeByte('&'); + try f.dg.renderValue(w, (try f.air.value(case_inst, pt)).?, .other); + if (use_builtin) { + try f.dg.renderBuiltinInfo(w, ty, if (class == .big) .bits else .none); + try w.writeByte(')'); + try w.writeAll(compareOperatorC(operator)); + try w.writeByte('0'); + } +} fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool { const dg = f.dg; diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index d95a5733f1..95fd81e58f 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -49,8 +49,6 @@ test "switch arbitrary int size" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // TODO - try expect(testSwitchArbInt(u64, 0) == 0); try expect(testSwitchArbInt(u64, 12) == 1); try expect(testSwitchArbInt(u64, maxInt(u64)) == 2); @@ -1406,8 +1404,6 @@ test "switch on packed union" { } test "switch on nested packed containers" { - if (builtin.object_format == .c) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/31467 - const P = packed struct { iu: u17, is: i31, @@ -1459,3 +1455,35 @@ test "switch on nested packed containers" { .p = .{ .a = 2, .b = 17 }, }); } + +test "switch on large types" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + + const S = struct { + fn doTheTest(a: u128, b: i500) !void { + switch (a) { + 0x0, + 0x3...0xFFFF_FFFF_FFFF_FFFF_FFFF_ABCD, + 0xFFFF_FFFF_FFFF_FFFF_FFFF_EF00, + => return error.TestFailed, + 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000...0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFF0, + => |val| { + try expect(val == 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234); + }, + else => return error.TestFailed, + } + switch (b) { + 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000...0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, + => return error.TestFailed, + 0xFFFF_1234, + 0xFFFF_FFFF_FFFF_FFFF_FFFF_0123...0xFFFF_FFFF_FFFF_FFFF_FFFF_4567, + => |val| { + try expect(val == 0xFFFF_1234); + }, + else => return error.TestFailed, + } + } + }; + try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234); + try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234); +} diff --git a/test/behavior/switch_loop.zig b/test/behavior/switch_loop.zig index a653b6a638..14794d2d12 100644 --- a/test/behavior/switch_loop.zig +++ b/test/behavior/switch_loop.zig @@ -564,3 +564,35 @@ test "switch loop with packed unions with OPV" { try P.doTheTest(.{ .a = 0 }); try comptime P.doTheTest(.{ .a = 0 }); } + +test "switch loop on large types" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + + const S = struct { + fn doTheTest(a: u128, b: i500) !void { + label: switch (a) { + 0x0, + 0x3...0xFFFF_FFFF_FFFF_FFFF_FFFF_ABCD, + 0xFFFF_FFFF_FFFF_FFFF_FFFF_EF00, + => return error.TestFailed, + 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000...0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFF0, + => |val| { + continue :label val + 1; + }, + else => {}, + } + label: switch (b) { + 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000...0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, + => return error.TestFailed, + 0xFFFF_1234, + 0xFFFF_FFFF_FFFF_FFFF_FFFF_0123...0xFFFF_FFFF_FFFF_FFFF_FFFF_4567, + => |val| { + continue :label val + 1; + }, + else => {}, + } + } + }; + try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FF00, 0xFFFF_FFFF_FFFF_FFFF_FFFF_4550); + try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FF00, 0xFFFF_FFFF_FFFF_FFFF_FFFF_4550); +}