mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-21 08:32:02 +03:00
Merge pull request 'cbe: fix switch statements on large types' (#31557) from justusk/zig:cbe-switch-128 into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31557
This commit is contained in:
@@ -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) {
|
||||
|
||||
+186
-66
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user