authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2024-09-18 16:55:50+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-22 18:34:00-05:00
logb25d93e7d95418ea92b388ff8b58a04673c04539
tree81a486b469fdffd0078d6289b1fe5390e1ab614a
parent61b69a418db2f525c4be4e19029487f61fb3234e

stage2-wasm: implement switch_dispatch + handle > 32 bit integers in switches

Updated solution is future proof for arbitary size integer handling for both strategies .br_table lowering if switch case is dense, .br_if base jump table if values are too sparse.

3 files changed, 227 insertions(+), 211 deletions(-)

src/arch/wasm/CodeGen.zig+185-203
......@@ -1925,7 +1925,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19251925 .breakpoint => cg.airBreakpoint(inst),
19261926 .br => cg.airBr(inst),
19271927 .repeat => cg.airRepeat(inst),
1928 .switch_dispatch => return cg.fail("TODO implement `switch_dispatch`", .{}),
1928 .switch_dispatch => cg.airSwitchDispatch(inst),
19291929 .cond_br => cg.airCondBr(inst),
19301930 .intcast => cg.airIntcast(inst),
19311931 .fptrunc => cg.airFptrunc(inst),
......@@ -2005,8 +2005,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
20052005 .struct_field_val => cg.airStructFieldVal(inst),
20062006 .field_parent_ptr => cg.airFieldParentPtr(inst),
20072007
2008 .switch_br => cg.airSwitchBr(inst),
2009 .loop_switch_br => return cg.fail("TODO implement `loop_switch_br`", .{}),
2008 .switch_br => cg.airSwitchBr(inst, false),
2009 .loop_switch_br => cg.airSwitchBr(inst, true),
20102010 .trunc => cg.airTrunc(inst),
20112011 .unreach => cg.airUnreachable(inst),
20122012
......@@ -3356,43 +3356,6 @@ fn emitUndefined(cg: *CodeGen, ty: Type) InnerError!WValue {
33563356 }
33573357}
33583358
3359/// Returns a `Value` as a signed 32 bit value.
3360/// It's illegal to provide a value with a type that cannot be represented
3361/// as an integer value.
3362fn valueAsI32(cg: *const CodeGen, val: Value) i32 {
3363 const zcu = cg.pt.zcu;
3364 const ip = &zcu.intern_pool;
3365
3366 switch (val.toIntern()) {
3367 .bool_true => return 1,
3368 .bool_false => return 0,
3369 else => return switch (ip.indexToKey(val.ip_index)) {
3370 .enum_tag => |enum_tag| intIndexAsI32(ip, enum_tag.int, zcu),
3371 .int => |int| intStorageAsI32(int.storage, zcu),
3372 .ptr => |ptr| {
3373 assert(ptr.base_addr == .int);
3374 return @intCast(ptr.byte_offset);
3375 },
3376 .err => |err| @bitCast(ip.getErrorValueIfExists(err.name).?),
3377 else => unreachable,
3378 },
3379 }
3380}
3381
3382fn intIndexAsI32(ip: *const InternPool, int: InternPool.Index, zcu: *const Zcu) i32 {
3383 return intStorageAsI32(ip.indexToKey(int).int.storage, zcu);
3384}
3385
3386fn intStorageAsI32(storage: InternPool.Key.Int.Storage, zcu: *const Zcu) i32 {
3387 return switch (storage) {
3388 .i64 => |x| @as(i32, @intCast(x)),
3389 .u64 => |x| @as(i32, @bitCast(@as(u32, @intCast(x)))),
3390 .big_int => unreachable,
3391 .lazy_align => |ty| @as(i32, @bitCast(@as(u32, @intCast(Type.fromInterned(ty).abiAlignment(zcu).toByteUnits() orelse 0)))),
3392 .lazy_size => |ty| @as(i32, @bitCast(@as(u32, @intCast(Type.fromInterned(ty).abiSize(zcu))))),
3393 };
3394}
3395
33963359fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
33973360 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
33983361 const extra = cg.air.extraData(Air.Block, ty_pl.payload);
......@@ -3401,13 +3364,11 @@ fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
34013364
34023365fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const Air.Inst.Index) InnerError!void {
34033366 const zcu = cg.pt.zcu;
3404 const wasm_block_ty = genBlockType(block_ty, zcu, cg.target);
3405
34063367 // if wasm_block_ty is non-empty, we create a register to store the temporary value
3407 const block_result: WValue = if (wasm_block_ty != .empty) blk: {
3408 const ty: Type = if (isByRef(block_ty, zcu, cg.target)) Type.u32 else block_ty;
3409 break :blk try cg.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten
3410 } else .none;
3368 const block_result: WValue = if (block_ty.hasRuntimeBitsIgnoreComptime(zcu))
3369 try cg.allocLocal(block_ty)
3370 else
3371 .none;
34113372
34123373 try cg.startBlock(.block, .empty);
34133374 // Here we set the current block idx, so breaks know the depth to jump
......@@ -3621,18 +3582,14 @@ fn airCmpLtErrorsLen(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
36213582}
36223583
36233584fn airBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3624 const zcu = cg.pt.zcu;
36253585 const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br;
36263586 const block = cg.blocks.get(br.block_inst).?;
36273587
36283588 // if operand has codegen bits we should break with a value
3629 if (cg.typeOf(br.operand).hasRuntimeBitsIgnoreComptime(zcu)) {
3589 if (block.value != .none) {
36303590 const operand = try cg.resolveInst(br.operand);
36313591 try cg.lowerToStack(operand);
3632
3633 if (block.value != .none) {
3634 try cg.addLocal(.local_set, block.value.local.value);
3635 }
3592 try cg.addLocal(.local_set, block.value.local.value);
36363593 }
36373594
36383595 // We map every block to its block index.
......@@ -3957,189 +3914,192 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
39573914 return cg.finishAir(inst, result, &.{struct_field.struct_operand});
39583915}
39593916
3960fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3917fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) InnerError!void {
39613918 const pt = cg.pt;
39623919 const zcu = pt.zcu;
3963 // result type is always 'noreturn'
3964 const blocktype: std.wasm.BlockType = .empty;
3920
39653921 const switch_br = cg.air.unwrapSwitch(inst);
3966 const target = try cg.resolveInst(switch_br.operand);
39673922 const target_ty = cg.typeOf(switch_br.operand);
3923
3924 assert(target_ty.hasRuntimeBitsIgnoreComptime(zcu));
3925
3926 // swap target value with placeholder local, for dispatching
3927 const target = if (is_dispatch_loop) target: {
3928 const initial_target = try cg.resolveInst(switch_br.operand);
3929 const target: WValue = try cg.allocLocal(target_ty);
3930 try cg.lowerToStack(initial_target);
3931 try cg.addLocal(.local_set, target.local.value);
3932
3933 try cg.startBlock(.loop, .empty); // dispatch loop start
3934 try cg.blocks.putNoClobber(cg.gpa, inst, .{
3935 .label = cg.block_depth,
3936 .value = target,
3937 });
3938
3939 break :target target;
3940 } else try cg.resolveInst(switch_br.operand);
3941
39683942 const liveness = try cg.liveness.getSwitchBr(cg.gpa, inst, switch_br.cases_len + 1);
39693943 defer cg.gpa.free(liveness.deaths);
39703944
3971 // a list that maps each value with its value and body based on the order inside the list.
3972 const CaseValue = union(enum) {
3973 singular: struct { integer: i32, value: Value },
3974 range: struct { min: i32, min_value: Value, max: i32, max_value: Value },
3975 };
3976 var case_list = try std.ArrayList(struct {
3977 values: []const CaseValue,
3978 body: []const Air.Inst.Index,
3979 }).initCapacity(cg.gpa, switch_br.cases_len);
3980 defer for (case_list.items) |case| {
3981 cg.gpa.free(case.values);
3982 } else case_list.deinit();
3983
3984 var lowest_maybe: ?i32 = null;
3985 var highest_maybe: ?i32 = null;
3986 var it = switch_br.iterateCases();
3987 while (it.next()) |case| {
3988 const values = try cg.gpa.alloc(CaseValue, case.items.len + case.ranges.len);
3989 errdefer cg.gpa.free(values);
3990
3991 for (case.items, 0..) |ref, i| {
3992 const item_val = (try cg.air.value(ref, pt)).?;
3993 const int_val = cg.valueAsI32(item_val);
3994 if (lowest_maybe == null or int_val < lowest_maybe.?) {
3995 lowest_maybe = int_val;
3996 }
3997 if (highest_maybe == null or int_val > highest_maybe.?) {
3998 highest_maybe = int_val;
3999 }
4000 values[i] = .{ .singular = .{ .integer = int_val, .value = item_val } };
3945 const has_else_body = switch_br.else_body_len != 0;
3946 const branch_count = switch_br.cases_len + 1; // if else branch is missing, we trap when failing all conditions
3947 try cg.branches.ensureUnusedCapacity(cg.gpa, switch_br.cases_len + @intFromBool(has_else_body));
3948
3949 if (switch_br.cases_len == 0) {
3950 assert(has_else_body);
3951
3952 var it = switch_br.iterateCases();
3953 const else_body = it.elseBody();
3954
3955 cg.branches.appendAssumeCapacity(.{});
3956 const else_deaths = liveness.deaths.len - 1;
3957 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len);
3958 defer {
3959 var else_branch = cg.branches.pop().?;
3960 else_branch.deinit(cg.gpa);
3961 }
3962 try cg.genBody(else_body);
3963
3964 if (is_dispatch_loop) {
3965 try cg.endBlock(); // dispatch loop end
40013966 }
3967 return cg.finishAir(inst, .none, &.{});
3968 }
40023969
4003 for (case.ranges, 0..) |range, i| {
4004 const min_val = (try cg.air.value(range[0], pt)).?;
4005 const int_min_val = cg.valueAsI32(min_val);
3970 var min: ?Value = null;
3971 var max: ?Value = null;
3972 var branching_size: u32 = 0; // single item +1, range +2
40063973
4007 if (lowest_maybe == null or int_min_val < lowest_maybe.?) {
4008 lowest_maybe = int_min_val;
3974 {
3975 var cases_it = switch_br.iterateCases();
3976 while (cases_it.next()) |case| {
3977 for (case.items) |item| {
3978 const val = Value.fromInterned(item.toInterned().?);
3979 if (min == null or val.compareHetero(.lt, min.?, zcu)) min = val;
3980 if (max == null or val.compareHetero(.gt, max.?, zcu)) max = val;
3981 branching_size += 1;
3982 }
3983 for (case.ranges) |range| {
3984 const low = Value.fromInterned(range[0].toInterned().?);
3985 if (min == null or low.compareHetero(.lt, min.?, zcu)) min = low;
3986 const high = Value.fromInterned(range[1].toInterned().?);
3987 if (max == null or high.compareHetero(.gt, max.?, zcu)) max = high;
3988 branching_size += 2;
40093989 }
3990 }
3991 }
40103992
4011 const max_val = (try cg.air.value(range[1], pt)).?;
4012 const int_max_val = cg.valueAsI32(max_val);
3993 var min_space: Value.BigIntSpace = undefined;
3994 const min_bigint = min.?.toBigInt(&min_space, zcu);
3995 var max_space: Value.BigIntSpace = undefined;
3996 const max_bigint = max.?.toBigInt(&max_space, zcu);
3997 const limbs = try cg.gpa.alloc(
3998 std.math.big.Limb,
3999 @max(min_bigint.limbs.len, max_bigint.limbs.len) + 1,
4000 );
4001 defer cg.gpa.free(limbs);
40134002
4014 if (highest_maybe == null or int_max_val > highest_maybe.?) {
4015 highest_maybe = int_max_val;
4016 }
4003 const width_maybe: ?u32 = width: {
4004 var width_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined };
4005 width_bigint.sub(max_bigint, min_bigint);
4006 width_bigint.addScalar(width_bigint.toConst(), 1);
4007 break :width width_bigint.toConst().to(u32) catch null;
4008 };
40174009
4018 values[i + case.items.len] = .{ .range = .{
4019 .min = int_min_val,
4020 .min_value = min_val,
4021 .max = int_max_val,
4022 .max_value = max_val,
4023 } };
4024 }
4010 try cg.startBlock(.block, .empty); // whole switch block start
40254011
4026 case_list.appendAssumeCapacity(.{ .values = values, .body = case.body });
4027 try cg.startBlock(.block, blocktype);
4012 for (0..branch_count) |_| {
4013 try cg.startBlock(.block, .empty);
40284014 }
40294015
4030 // When highest and lowest are null, we have no cases and can use a jump table
4031 const lowest = lowest_maybe orelse 0;
4032 const highest = highest_maybe orelse 0;
4033 // When the highest and lowest values are seperated by '50',
4034 // we define it as sparse and use an if/else-chain, rather than a jump table.
4035 // When the target is an integer size larger than u32, we have no way to use the value
4036 // as an index, therefore we also use an if/else-chain for those cases.
4037 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
4038 const is_sparse = highest - lowest > 50 or target_ty.bitSize(zcu) > 32;
4016 // Heuristic on deciding when to use .br_table instead of .br_if jump table
4017 // 1. Differences between lowest and highest values should fit into u32
4018 // 2. .br_table should be applied for "dense" switch, we test it by checking .br_if jumps will need more instructions
4019 // 3. Do not use .br_table for tiny switches
4020 const use_br_table = cond: {
4021 const width = width_maybe orelse break :cond false;
4022 if (width > 2 * branching_size) break :cond false;
4023 if (width < 2 or branch_count < 2) break :cond false;
4024 break :cond true;
4025 };
40394026
4040 const else_body = it.elseBody();
4041 const has_else_body = else_body.len != 0;
4042 if (has_else_body) {
4043 try cg.startBlock(.block, blocktype);
4044 }
4045
4046 if (!is_sparse) {
4047 // Generate the jump table 'br_table' when the prongs are not sparse.
4048 // The value 'target' represents the index into the table.
4049 // Each index in the table represents a label to the branch
4050 // to jump to.
4051 try cg.startBlock(.block, blocktype);
4052 try cg.emitWValue(target);
4053 if (lowest < 0) {
4054 // since br_table works using indexes, starting from '0', we must ensure all values
4055 // we put inside, are atleast 0.
4056 try cg.addImm32(@bitCast(lowest * -1));
4057 try cg.addTag(.i32_add);
4058 } else if (lowest > 0) {
4059 // make the index start from 0 by substracting the lowest value
4060 try cg.addImm32(@bitCast(lowest));
4061 try cg.addTag(.i32_sub);
4062 }
4027 if (use_br_table) {
4028 const width = width_maybe.?;
4029
4030 const br_value_original = try cg.binOp(target, try cg.resolveInst(Air.internedToRef(min.?.toIntern())), target_ty, .sub);
4031 _ = try cg.intcast(br_value_original, target_ty, Type.u32);
40634032
4064 // Account for default branch so always add '1'
4065 const depth = @as(u32, @intCast(highest - lowest + @intFromBool(has_else_body))) + 1;
4066 const jump_table: Mir.JumpTable = .{ .length = depth };
4033 const jump_table: Mir.JumpTable = .{ .length = width + 1 };
40674034 const table_extra_index = try cg.addExtra(jump_table);
40684035 try cg.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
4069 try cg.mir_extra.ensureUnusedCapacity(cg.gpa, depth);
4070 var value = lowest;
4071 while (value <= highest) : (value += 1) {
4072 // idx represents the branch we jump to
4073 const idx = blk: {
4074 for (case_list.items, 0..) |case, idx| {
4075 for (case.values) |case_value| {
4076 switch (case_value) {
4077 .singular => |val| if (val.integer == value) break :blk @as(u32, @intCast(idx)),
4078 .range => |range_val| if (value >= range_val.min and value <= range_val.max) {
4079 break :blk @as(u32, @intCast(idx));
4080 },
4081 }
4082 }
4083 }
4084 // error sets are almost always sparse so we use the default case
4085 // for errors that are not present in any branch. This is fine as this default
4086 // case will never be hit for those cases but we do save runtime cost and size
4087 // by using a jump table for this instead of if-else chains.
4088 break :blk if (has_else_body or target_ty.zigTypeTag(zcu) == .error_set) switch_br.cases_len else unreachable;
4089 };
4090 cg.mir_extra.appendAssumeCapacity(idx);
4091 } else if (has_else_body) {
4092 cg.mir_extra.appendAssumeCapacity(switch_br.cases_len); // default branch
4093 }
4094 try cg.endBlock();
4095 }
40964036
4097 try cg.branches.ensureUnusedCapacity(cg.gpa, case_list.items.len + @intFromBool(has_else_body));
4098 for (case_list.items, 0..) |case, index| {
4099 // when sparse, we use if/else-chain, so emit conditional checks
4100 if (is_sparse) {
4101 // for single value prong we can emit a simple condition
4102 if (case.values.len == 1 and case.values[0] == .singular) {
4103 const val = try cg.lowerConstant(case.values[0].singular.value, target_ty);
4104 // not equal, because we want to jump out of this block if it does not match the condition.
4105 _ = try cg.cmp(target, val, target_ty, .neq);
4106 try cg.addLabel(.br_if, 0);
4107 } else {
4108 // in multi-value prongs we must check if any prongs match the target value.
4109 try cg.startBlock(.block, blocktype);
4110 for (case.values) |value| {
4111 switch (value) {
4112 .singular => |single_val| {
4113 const val = try cg.lowerConstant(single_val.value, target_ty);
4114 _ = try cg.cmp(target, val, target_ty, .eq);
4115 },
4116 .range => |range| {
4117 const min_val = try cg.lowerConstant(range.min_value, target_ty);
4118 const max_val = try cg.lowerConstant(range.max_value, target_ty);
4119
4120 const gte = try cg.cmp(target, min_val, target_ty, .gte);
4121 const lte = try cg.cmp(target, max_val, target_ty, .lte);
4122 _ = try cg.binOp(gte, lte, Type.bool, .@"and");
4123 },
4124 }
4125 try cg.addLabel(.br_if, 0);
4126 }
4127 // value did not match any of the prong values
4128 try cg.addLabel(.br, 1);
4129 try cg.endBlock();
4037 const branch_list = try cg.mir_extra.addManyAsSlice(cg.gpa, width + 1);
4038 @memset(branch_list, branch_count - 1);
4039
4040 var cases_it = switch_br.iterateCases();
4041 while (cases_it.next()) |case| {
4042 for (case.items) |item| {
4043 const val = Value.fromInterned(item.toInterned().?);
4044 var val_space: Value.BigIntSpace = undefined;
4045 const val_bigint = val.toBigInt(&val_space, zcu);
4046 var index_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined };
4047 index_bigint.sub(val_bigint, min_bigint);
4048 branch_list[index_bigint.toConst().to(u32) catch unreachable] = case.idx;
4049 }
4050 for (case.ranges) |range| {
4051 var low_space: Value.BigIntSpace = undefined;
4052 const low_bigint = Value.fromInterned(range[0].toInterned().?).toBigInt(&low_space, zcu);
4053 var high_space: Value.BigIntSpace = undefined;
4054 const high_bigint = Value.fromInterned(range[1].toInterned().?).toBigInt(&high_space, zcu);
4055 var index_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined };
4056 index_bigint.sub(low_bigint, min_bigint);
4057 const start = index_bigint.toConst().to(u32) catch unreachable;
4058 index_bigint.sub(high_bigint, min_bigint);
4059 const end = (index_bigint.toConst().to(u32) catch unreachable) + 1;
4060 @memset(branch_list[start..end], case.idx);
4061 }
4062 }
4063 } else {
4064 var cases_it = switch_br.iterateCases();
4065 while (cases_it.next()) |case| {
4066 for (case.items) |ref| {
4067 const val = try cg.resolveInst(ref);
4068 _ = try cg.cmp(target, val, target_ty, .eq);
4069 try cg.addLabel(.br_if, case.idx); // item match found
4070 }
4071 for (case.ranges) |range| {
4072 const low = try cg.resolveInst(range[0]);
4073 const high = try cg.resolveInst(range[1]);
4074
4075 const gte = try cg.cmp(target, low, target_ty, .gte);
4076 const lte = try cg.cmp(target, high, target_ty, .lte);
4077 _ = try cg.binOp(gte, lte, Type.bool, .@"and");
4078 try cg.addLabel(.br_if, case.idx); // range match found
41304079 }
41314080 }
4081 try cg.addLabel(.br, branch_count - 1);
4082 }
4083
4084 var cases_it = switch_br.iterateCases();
4085 while (cases_it.next()) |case| {
4086 try cg.endBlock();
4087
41324088 cg.branches.appendAssumeCapacity(.{});
4133 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[index].len);
4089 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[case.idx].len);
41344090 defer {
41354091 var case_branch = cg.branches.pop().?;
41364092 case_branch.deinit(cg.gpa);
41374093 }
41384094 try cg.genBody(case.body);
4139 try cg.endBlock();
4095
4096 try cg.addLabel(.br, branch_count - case.idx - 1); // matching case found and executed => exit switch
41404097 }
41414098
4099 try cg.endBlock();
41424100 if (has_else_body) {
4101 const else_body = cases_it.elseBody();
4102
41434103 cg.branches.appendAssumeCapacity(.{});
41444104 const else_deaths = liveness.deaths.len - 1;
41454105 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len);
......@@ -4148,11 +4108,33 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
41484108 else_branch.deinit(cg.gpa);
41494109 }
41504110 try cg.genBody(else_body);
4151 try cg.endBlock();
4111 } else {
4112 try cg.addTag(.@"unreachable");
41524113 }
4114
4115 try cg.endBlock(); // whole switch block end
4116
4117 if (is_dispatch_loop) {
4118 try cg.endBlock(); // dispatch loop end
4119 }
4120
41534121 return cg.finishAir(inst, .none, &.{});
41544122}
41554123
4124fn airSwitchDispatch(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4125 const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br;
4126 const switch_loop = cg.blocks.get(br.block_inst).?;
4127
4128 const operand = try cg.resolveInst(br.operand);
4129 try cg.lowerToStack(operand);
4130 try cg.addLocal(.local_set, switch_loop.value.local.value);
4131
4132 const idx: u32 = cg.block_depth - switch_loop.label;
4133 try cg.addLabel(.br, idx);
4134
4135 return cg.finishAir(inst, .none, &.{br.operand});
4136}
4137
41564138fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void {
41574139 const zcu = cg.pt.zcu;
41584140 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
test/behavior/switch.zig+42-1
......@@ -4,6 +4,8 @@ const assert = std.debug.assert;
44const expect = std.testing.expect;
55const expectError = std.testing.expectError;
66const expectEqual = std.testing.expectEqual;
7const minInt = std.math.minInt;
8const maxInt = std.math.maxInt;
79
810test "switch with numbers" {
911 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -41,6 +43,46 @@ fn testSwitchWithAllRanges(x: u32, y: u32) u32 {
4143 };
4244}
4345
46test "switch arbitrary int size" {
47 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
48 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
49 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
50 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
51 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
52 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO
53
54 if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
55
56 try expect(testSwitchArbInt(u64, 0) == 0);
57 try expect(testSwitchArbInt(u64, 12) == 1);
58 try expect(testSwitchArbInt(u64, maxInt(u64)) == 2);
59 try expect(testSwitchArbInt(u64, 5555) == 3);
60
61 try expect(testSwitchArbInt(i64, minInt(i64)) == 0);
62 try expect(testSwitchArbInt(i64, 12) == 1);
63 try expect(testSwitchArbInt(i64, maxInt(i64)) == 2);
64 try expect(testSwitchArbInt(i64, -1000) == 3);
65
66 try expect(testSwitchArbInt(u128, 0) == 0);
67 try expect(testSwitchArbInt(u128, 12) == 1);
68 try expect(testSwitchArbInt(u128, maxInt(u128)) == 2);
69 try expect(testSwitchArbInt(u128, 5555) == 3);
70
71 try expect(testSwitchArbInt(i128, minInt(i128)) == 0);
72 try expect(testSwitchArbInt(i128, 12) == 1);
73 try expect(testSwitchArbInt(i128, maxInt(i128)) == 2);
74 try expect(testSwitchArbInt(i128, -1000) == 3);
75}
76
77fn testSwitchArbInt(comptime T: type, x: T) u32 {
78 return switch (x) {
79 minInt(T) => 0,
80 10...15 => 1,
81 maxInt(T) => 2,
82 else => 3,
83 };
84}
85
4486test "implicit comptime switch" {
4587 const x = 3 + 4;
4688 const result = switch (x) {
......@@ -1003,7 +1045,6 @@ test "labeled switch with break" {
10031045}
10041046
10051047test "unlabeled break ignores switch" {
1006 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
10071048 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
10081049 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
10091050 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
test/behavior/switch_loop.zig-7
......@@ -3,7 +3,6 @@ const std = @import("std");
33const expect = std.testing.expect;
44
55test "simple switch loop" {
6 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
76 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
87 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
98 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -28,7 +27,6 @@ test "simple switch loop" {
2827}
2928
3029test "switch loop with ranges" {
31 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
3230 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
3331 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
3432 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -50,7 +48,6 @@ test "switch loop with ranges" {
5048}
5149
5250test "switch loop on enum" {
53 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
5451 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
5552 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
5653 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -75,7 +72,6 @@ test "switch loop on enum" {
7572}
7673
7774test "switch loop with error set" {
78 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
7975 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
8076 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
8177 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -100,7 +96,6 @@ test "switch loop with error set" {
10096}
10197
10298test "switch loop on tagged union" {
103 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
10499 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
105100 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
106101 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -134,7 +129,6 @@ test "switch loop on tagged union" {
134129}
135130
136131test "switch loop dispatching instructions" {
137 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
138132 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
139133 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
140134 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -185,7 +179,6 @@ test "switch loop dispatching instructions" {
185179}
186180
187181test "switch loop with pointer capture" {
188 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
189182 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
190183 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
191184 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO