| ... | ... | @@ -1925,7 +1925,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1925 | 1925 | .breakpoint => cg.airBreakpoint(inst), |
| 1926 | 1926 | .br => cg.airBr(inst), |
| 1927 | 1927 | .repeat => cg.airRepeat(inst), |
| 1928 | | .switch_dispatch => return cg.fail("TODO implement `switch_dispatch`", .{}), |
| 1928 | .switch_dispatch => cg.airSwitchDispatch(inst), |
| 1929 | 1929 | .cond_br => cg.airCondBr(inst), |
| 1930 | 1930 | .intcast => cg.airIntcast(inst), |
| 1931 | 1931 | .fptrunc => cg.airFptrunc(inst), |
| ... | ... | @@ -2005,8 +2005,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2005 | 2005 | .struct_field_val => cg.airStructFieldVal(inst), |
| 2006 | 2006 | .field_parent_ptr => cg.airFieldParentPtr(inst), |
| 2007 | 2007 | |
| 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), |
| 2010 | 2010 | .trunc => cg.airTrunc(inst), |
| 2011 | 2011 | .unreach => cg.airUnreachable(inst), |
| 2012 | 2012 | |
| ... | ... | @@ -3356,43 +3356,6 @@ fn emitUndefined(cg: *CodeGen, ty: Type) InnerError!WValue { |
| 3356 | 3356 | } |
| 3357 | 3357 | } |
| 3358 | 3358 | |
| 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. |
| 3362 | | fn 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 | | |
| 3382 | | fn intIndexAsI32(ip: *const InternPool, int: InternPool.Index, zcu: *const Zcu) i32 { |
| 3383 | | return intStorageAsI32(ip.indexToKey(int).int.storage, zcu); |
| 3384 | | } |
| 3385 | | |
| 3386 | | fn 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 | | |
| 3396 | 3359 | fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3397 | 3360 | const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 3398 | 3361 | const extra = cg.air.extraData(Air.Block, ty_pl.payload); |
| ... | ... | @@ -3401,13 +3364,11 @@ fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3401 | 3364 | |
| 3402 | 3365 | fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const Air.Inst.Index) InnerError!void { |
| 3403 | 3366 | const zcu = cg.pt.zcu; |
| 3404 | | const wasm_block_ty = genBlockType(block_ty, zcu, cg.target); |
| 3405 | | |
| 3406 | 3367 | // 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; |
| 3411 | 3372 | |
| 3412 | 3373 | try cg.startBlock(.block, .empty); |
| 3413 | 3374 | // 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 { |
| 3621 | 3582 | } |
| 3622 | 3583 | |
| 3623 | 3584 | fn airBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3624 | | const zcu = cg.pt.zcu; |
| 3625 | 3585 | const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br; |
| 3626 | 3586 | const block = cg.blocks.get(br.block_inst).?; |
| 3627 | 3587 | |
| 3628 | 3588 | // if operand has codegen bits we should break with a value |
| 3629 | | if (cg.typeOf(br.operand).hasRuntimeBitsIgnoreComptime(zcu)) { |
| 3589 | if (block.value != .none) { |
| 3630 | 3590 | const operand = try cg.resolveInst(br.operand); |
| 3631 | 3591 | 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); |
| 3636 | 3593 | } |
| 3637 | 3594 | |
| 3638 | 3595 | // We map every block to its block index. |
| ... | ... | @@ -3957,189 +3914,192 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3957 | 3914 | return cg.finishAir(inst, result, &.{struct_field.struct_operand}); |
| 3958 | 3915 | } |
| 3959 | 3916 | |
| 3960 | | fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3917 | fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) InnerError!void { |
| 3961 | 3918 | const pt = cg.pt; |
| 3962 | 3919 | const zcu = pt.zcu; |
| 3963 | | // result type is always 'noreturn' |
| 3964 | | const blocktype: std.wasm.BlockType = .empty; |
| 3920 | |
| 3965 | 3921 | const switch_br = cg.air.unwrapSwitch(inst); |
| 3966 | | const target = try cg.resolveInst(switch_br.operand); |
| 3967 | 3922 | 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 | |
| 3968 | 3942 | const liveness = try cg.liveness.getSwitchBr(cg.gpa, inst, switch_br.cases_len + 1); |
| 3969 | 3943 | defer cg.gpa.free(liveness.deaths); |
| 3970 | 3944 | |
| 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 |
| 4001 | 3966 | } |
| 3967 | return cg.finishAir(inst, .none, &.{}); |
| 3968 | } |
| 4002 | 3969 | |
| 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 |
| 4006 | 3973 | |
| 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; |
| 4009 | 3989 | } |
| 3990 | } |
| 3991 | } |
| 4010 | 3992 | |
| 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); |
| 4013 | 4002 | |
| 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 | }; |
| 4017 | 4009 | |
| 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 |
| 4025 | 4011 | |
| 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); |
| 4028 | 4014 | } |
| 4029 | 4015 | |
| 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 | }; |
| 4039 | 4026 | |
| 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); |
| 4063 | 4032 | |
| 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 }; |
| 4067 | 4034 | const table_extra_index = try cg.addExtra(jump_table); |
| 4068 | 4035 | 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 | | } |
| 4096 | 4036 | |
| 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 |
| 4130 | 4079 | } |
| 4131 | 4080 | } |
| 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 | |
| 4132 | 4088 | 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); |
| 4134 | 4090 | defer { |
| 4135 | 4091 | var case_branch = cg.branches.pop().?; |
| 4136 | 4092 | case_branch.deinit(cg.gpa); |
| 4137 | 4093 | } |
| 4138 | 4094 | 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 |
| 4140 | 4097 | } |
| 4141 | 4098 | |
| 4099 | try cg.endBlock(); |
| 4142 | 4100 | if (has_else_body) { |
| 4101 | const else_body = cases_it.elseBody(); |
| 4102 | |
| 4143 | 4103 | cg.branches.appendAssumeCapacity(.{}); |
| 4144 | 4104 | const else_deaths = liveness.deaths.len - 1; |
| 4145 | 4105 | 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 { |
| 4148 | 4108 | else_branch.deinit(cg.gpa); |
| 4149 | 4109 | } |
| 4150 | 4110 | try cg.genBody(else_body); |
| 4151 | | try cg.endBlock(); |
| 4111 | } else { |
| 4112 | try cg.addTag(.@"unreachable"); |
| 4152 | 4113 | } |
| 4114 | |
| 4115 | try cg.endBlock(); // whole switch block end |
| 4116 | |
| 4117 | if (is_dispatch_loop) { |
| 4118 | try cg.endBlock(); // dispatch loop end |
| 4119 | } |
| 4120 | |
| 4153 | 4121 | return cg.finishAir(inst, .none, &.{}); |
| 4154 | 4122 | } |
| 4155 | 4123 | |
| 4124 | fn 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 | |
| 4156 | 4138 | fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void { |
| 4157 | 4139 | const zcu = cg.pt.zcu; |
| 4158 | 4140 | const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; |