| ... | @@ -1925,7 +1925,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -1925,7 +1925,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1925 | .breakpoint => cg.airBreakpoint(inst), | 1925 | .breakpoint => cg.airBreakpoint(inst), |
| 1926 | .br => cg.airBr(inst), | 1926 | .br => cg.airBr(inst), |
| 1927 | .repeat => cg.airRepeat(inst), | 1927 | .repeat => cg.airRepeat(inst), |
| 1928 | .switch_dispatch => return cg.fail("TODO implement `switch_dispatch`", .{}), | 1928 | .switch_dispatch => cg.airSwitchDispatch(inst), |
| 1929 | .cond_br => cg.airCondBr(inst), | 1929 | .cond_br => cg.airCondBr(inst), |
| 1930 | .intcast => cg.airIntcast(inst), | 1930 | .intcast => cg.airIntcast(inst), |
| 1931 | .fptrunc => cg.airFptrunc(inst), | 1931 | .fptrunc => cg.airFptrunc(inst), |
| ... | @@ -2005,8 +2005,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2005,8 +2005,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2005 | .struct_field_val => cg.airStructFieldVal(inst), | 2005 | .struct_field_val => cg.airStructFieldVal(inst), |
| 2006 | .field_parent_ptr => cg.airFieldParentPtr(inst), | 2006 | .field_parent_ptr => cg.airFieldParentPtr(inst), |
| 2007 | | 2007 | |
| 2008 | .switch_br => cg.airSwitchBr(inst), | 2008 | .switch_br => cg.airSwitchBr(inst, false), |
| 2009 | .loop_switch_br => return cg.fail("TODO implement `loop_switch_br`", .{}), | 2009 | .loop_switch_br => cg.airSwitchBr(inst, true), |
| 2010 | .trunc => cg.airTrunc(inst), | 2010 | .trunc => cg.airTrunc(inst), |
| 2011 | .unreach => cg.airUnreachable(inst), | 2011 | .unreach => cg.airUnreachable(inst), |
| 2012 | | 2012 | |
| ... | @@ -3356,43 +3356,6 @@ fn emitUndefined(cg: *CodeGen, ty: Type) InnerError!WValue { | ... | @@ -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 | fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { | 3359 | fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3397 | const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; | 3360 | const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 3398 | const extra = cg.air.extraData(Air.Block, ty_pl.payload); | 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,13 +3364,11 @@ fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3401 | | 3364 | |
| 3402 | fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const Air.Inst.Index) InnerError!void { | 3365 | fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const Air.Inst.Index) InnerError!void { |
| 3403 | const zcu = cg.pt.zcu; | 3366 | const zcu = cg.pt.zcu; |
| 3404 | const wasm_block_ty = genBlockType(block_ty, zcu, cg.target); | | |
| 3405 | | | |
| 3406 | // if wasm_block_ty is non-empty, we create a register to store the temporary value | 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: { | 3368 | const block_result: WValue = if (block_ty.hasRuntimeBitsIgnoreComptime(zcu)) |
| 3408 | const ty: Type = if (isByRef(block_ty, zcu, cg.target)) Type.u32 else block_ty; | 3369 | try cg.allocLocal(block_ty) |
| 3409 | break :blk try cg.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten | 3370 | else |
| 3410 | } else .none; | 3371 | .none; |
| 3411 | | 3372 | |
| 3412 | try cg.startBlock(.block, .empty); | 3373 | try cg.startBlock(.block, .empty); |
| 3413 | // Here we set the current block idx, so breaks know the depth to jump | 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,18 +3582,14 @@ fn airCmpLtErrorsLen(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3621 | } | 3582 | } |
| 3622 | | 3583 | |
| 3623 | fn airBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { | 3584 | fn airBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3624 | const zcu = cg.pt.zcu; | | |
| 3625 | const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br; | 3585 | const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br; |
| 3626 | const block = cg.blocks.get(br.block_inst).?; | 3586 | const block = cg.blocks.get(br.block_inst).?; |
| 3627 | | 3587 | |
| 3628 | // if operand has codegen bits we should break with a value | 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 | const operand = try cg.resolveInst(br.operand); | 3590 | const operand = try cg.resolveInst(br.operand); |
| 3631 | try cg.lowerToStack(operand); | 3591 | try cg.lowerToStack(operand); |
| 3632 | | 3592 | try cg.addLocal(.local_set, block.value.local.value); |
| 3633 | if (block.value != .none) { | | |
| 3634 | try cg.addLocal(.local_set, block.value.local.value); | | |
| 3635 | } | | |
| 3636 | } | 3593 | } |
| 3637 | | 3594 | |
| 3638 | // We map every block to its block index. | 3595 | // We map every block to its block index. |
| ... | @@ -3957,189 +3914,192 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3957,189 +3914,192 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3957 | return cg.finishAir(inst, result, &.{struct_field.struct_operand}); | 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 | const pt = cg.pt; | 3918 | const pt = cg.pt; |
| 3962 | const zcu = pt.zcu; | 3919 | const zcu = pt.zcu; |
| 3963 | // result type is always 'noreturn' | 3920 | |
| 3964 | const blocktype: std.wasm.BlockType = .empty; | | |
| 3965 | const switch_br = cg.air.unwrapSwitch(inst); | 3921 | const switch_br = cg.air.unwrapSwitch(inst); |
| 3966 | const target = try cg.resolveInst(switch_br.operand); | | |
| 3967 | const target_ty = cg.typeOf(switch_br.operand); | 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 | const liveness = try cg.liveness.getSwitchBr(cg.gpa, inst, switch_br.cases_len + 1); | 3942 | const liveness = try cg.liveness.getSwitchBr(cg.gpa, inst, switch_br.cases_len + 1); |
| 3969 | defer cg.gpa.free(liveness.deaths); | 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. | 3945 | const has_else_body = switch_br.else_body_len != 0; |
| 3972 | const CaseValue = union(enum) { | 3946 | const branch_count = switch_br.cases_len + 1; // if else branch is missing, we trap when failing all conditions |
| 3973 | singular: struct { integer: i32, value: Value }, | 3947 | try cg.branches.ensureUnusedCapacity(cg.gpa, switch_br.cases_len + @intFromBool(has_else_body)); |
| 3974 | range: struct { min: i32, min_value: Value, max: i32, max_value: Value }, | 3948 | |
| 3975 | }; | 3949 | if (switch_br.cases_len == 0) { |
| 3976 | var case_list = try std.ArrayList(struct { | 3950 | assert(has_else_body); |
| 3977 | values: []const CaseValue, | 3951 | |
| 3978 | body: []const Air.Inst.Index, | 3952 | var it = switch_br.iterateCases(); |
| 3979 | }).initCapacity(cg.gpa, switch_br.cases_len); | 3953 | const else_body = it.elseBody(); |
| 3980 | defer for (case_list.items) |case| { | 3954 | |
| 3981 | cg.gpa.free(case.values); | 3955 | cg.branches.appendAssumeCapacity(.{}); |
| 3982 | } else case_list.deinit(); | 3956 | const else_deaths = liveness.deaths.len - 1; |
| 3983 | | 3957 | try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len); |
| 3984 | var lowest_maybe: ?i32 = null; | 3958 | defer { |
| 3985 | var highest_maybe: ?i32 = null; | 3959 | var else_branch = cg.branches.pop().?; |
| 3986 | var it = switch_br.iterateCases(); | 3960 | else_branch.deinit(cg.gpa); |
| 3987 | while (it.next()) |case| { | 3961 | } |
| 3988 | const values = try cg.gpa.alloc(CaseValue, case.items.len + case.ranges.len); | 3962 | try cg.genBody(else_body); |
| 3989 | errdefer cg.gpa.free(values); | 3963 | |
| 3990 | | 3964 | if (is_dispatch_loop) { |
| 3991 | for (case.items, 0..) |ref, i| { | 3965 | try cg.endBlock(); // dispatch loop end |
| 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 } }; | | |
| 4001 | } | 3966 | } |
| | 3967 | return cg.finishAir(inst, .none, &.{}); |
| | 3968 | } |
| 4002 | | 3969 | |
| 4003 | for (case.ranges, 0..) |range, i| { | 3970 | var min: ?Value = null; |
| 4004 | const min_val = (try cg.air.value(range[0], pt)).?; | 3971 | var max: ?Value = null; |
| 4005 | const int_min_val = cg.valueAsI32(min_val); | 3972 | var branching_size: u32 = 0; // single item +1, range +2 |
| 4006 | | 3973 | |
| 4007 | if (lowest_maybe == null or int_min_val < lowest_maybe.?) { | 3974 | { |
| 4008 | lowest_maybe = int_min_val; | 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)).?; | 3993 | var min_space: Value.BigIntSpace = undefined; |
| 4012 | const int_max_val = cg.valueAsI32(max_val); | 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.?) { | 4003 | const width_maybe: ?u32 = width: { |
| 4015 | highest_maybe = int_max_val; | 4004 | var width_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined }; |
| 4016 | } | 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 = .{ | 4010 | try cg.startBlock(.block, .empty); // whole switch block start |
| 4019 | .min = int_min_val, | | |
| 4020 | .min_value = min_val, | | |
| 4021 | .max = int_max_val, | | |
| 4022 | .max_value = max_val, | | |
| 4023 | } }; | | |
| 4024 | } | | |
| 4025 | | 4011 | |
| 4026 | case_list.appendAssumeCapacity(.{ .values = values, .body = case.body }); | 4012 | for (0..branch_count) |_| { |
| 4027 | try cg.startBlock(.block, blocktype); | 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 | 4016 | // Heuristic on deciding when to use .br_table instead of .br_if jump table |
| 4031 | const lowest = lowest_maybe orelse 0; | 4017 | // 1. Differences between lowest and highest values should fit into u32 |
| 4032 | const highest = highest_maybe orelse 0; | 4018 | // 2. .br_table should be applied for "dense" switch, we test it by checking .br_if jumps will need more instructions |
| 4033 | // When the highest and lowest values are seperated by '50', | 4019 | // 3. Do not use .br_table for tiny switches |
| 4034 | // we define it as sparse and use an if/else-chain, rather than a jump table. | 4020 | const use_br_table = cond: { |
| 4035 | // When the target is an integer size larger than u32, we have no way to use the value | 4021 | const width = width_maybe orelse break :cond false; |
| 4036 | // as an index, therefore we also use an if/else-chain for those cases. | 4022 | if (width > 2 * branching_size) break :cond false; |
| 4037 | // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'. | 4023 | if (width < 2 or branch_count < 2) break :cond false; |
| 4038 | const is_sparse = highest - lowest > 50 or target_ty.bitSize(zcu) > 32; | 4024 | break :cond true; |
| | 4025 | }; |
| 4039 | | 4026 | |
| 4040 | const else_body = it.elseBody(); | 4027 | if (use_br_table) { |
| 4041 | const has_else_body = else_body.len != 0; | 4028 | const width = width_maybe.?; |
| 4042 | if (has_else_body) { | 4029 | |
| 4043 | try cg.startBlock(.block, blocktype); | 4030 | const br_value_original = try cg.binOp(target, try cg.resolveInst(Air.internedToRef(min.?.toIntern())), target_ty, .sub); |
| 4044 | } | 4031 | _ = try cg.intcast(br_value_original, target_ty, Type.u32); |
| 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 | } | | |
| 4063 | | 4032 | |
| 4064 | // Account for default branch so always add '1' | 4033 | const jump_table: Mir.JumpTable = .{ .length = width + 1 }; |
| 4065 | const depth = @as(u32, @intCast(highest - lowest + @intFromBool(has_else_body))) + 1; | | |
| 4066 | const jump_table: Mir.JumpTable = .{ .length = depth }; | | |
| 4067 | const table_extra_index = try cg.addExtra(jump_table); | 4034 | const table_extra_index = try cg.addExtra(jump_table); |
| 4068 | try cg.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } }); | 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)); | 4037 | const branch_list = try cg.mir_extra.addManyAsSlice(cg.gpa, width + 1); |
| 4098 | for (case_list.items, 0..) |case, index| { | 4038 | @memset(branch_list, branch_count - 1); |
| 4099 | // when sparse, we use if/else-chain, so emit conditional checks | 4039 | |
| 4100 | if (is_sparse) { | 4040 | var cases_it = switch_br.iterateCases(); |
| 4101 | // for single value prong we can emit a simple condition | 4041 | while (cases_it.next()) |case| { |
| 4102 | if (case.values.len == 1 and case.values[0] == .singular) { | 4042 | for (case.items) |item| { |
| 4103 | const val = try cg.lowerConstant(case.values[0].singular.value, target_ty); | 4043 | const val = Value.fromInterned(item.toInterned().?); |
| 4104 | // not equal, because we want to jump out of this block if it does not match the condition. | 4044 | var val_space: Value.BigIntSpace = undefined; |
| 4105 | _ = try cg.cmp(target, val, target_ty, .neq); | 4045 | const val_bigint = val.toBigInt(&val_space, zcu); |
| 4106 | try cg.addLabel(.br_if, 0); | 4046 | var index_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined }; |
| 4107 | } else { | 4047 | index_bigint.sub(val_bigint, min_bigint); |
| 4108 | // in multi-value prongs we must check if any prongs match the target value. | 4048 | branch_list[index_bigint.toConst().to(u32) catch unreachable] = case.idx; |
| 4109 | try cg.startBlock(.block, blocktype); | 4049 | } |
| 4110 | for (case.values) |value| { | 4050 | for (case.ranges) |range| { |
| 4111 | switch (value) { | 4051 | var low_space: Value.BigIntSpace = undefined; |
| 4112 | .singular => |single_val| { | 4052 | const low_bigint = Value.fromInterned(range[0].toInterned().?).toBigInt(&low_space, zcu); |
| 4113 | const val = try cg.lowerConstant(single_val.value, target_ty); | 4053 | var high_space: Value.BigIntSpace = undefined; |
| 4114 | _ = try cg.cmp(target, val, target_ty, .eq); | 4054 | const high_bigint = Value.fromInterned(range[1].toInterned().?).toBigInt(&high_space, zcu); |
| 4115 | }, | 4055 | var index_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined }; |
| 4116 | .range => |range| { | 4056 | index_bigint.sub(low_bigint, min_bigint); |
| 4117 | const min_val = try cg.lowerConstant(range.min_value, target_ty); | 4057 | const start = index_bigint.toConst().to(u32) catch unreachable; |
| 4118 | const max_val = try cg.lowerConstant(range.max_value, target_ty); | 4058 | index_bigint.sub(high_bigint, min_bigint); |
| 4119 | | 4059 | const end = (index_bigint.toConst().to(u32) catch unreachable) + 1; |
| 4120 | const gte = try cg.cmp(target, min_val, target_ty, .gte); | 4060 | @memset(branch_list[start..end], case.idx); |
| 4121 | const lte = try cg.cmp(target, max_val, target_ty, .lte); | 4061 | } |
| 4122 | _ = try cg.binOp(gte, lte, Type.bool, .@"and"); | 4062 | } |
| 4123 | }, | 4063 | } else { |
| 4124 | } | 4064 | var cases_it = switch_br.iterateCases(); |
| 4125 | try cg.addLabel(.br_if, 0); | 4065 | while (cases_it.next()) |case| { |
| 4126 | } | 4066 | for (case.items) |ref| { |
| 4127 | // value did not match any of the prong values | 4067 | const val = try cg.resolveInst(ref); |
| 4128 | try cg.addLabel(.br, 1); | 4068 | _ = try cg.cmp(target, val, target_ty, .eq); |
| 4129 | try cg.endBlock(); | 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 | cg.branches.appendAssumeCapacity(.{}); | 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 | defer { | 4090 | defer { |
| 4135 | var case_branch = cg.branches.pop().?; | 4091 | var case_branch = cg.branches.pop().?; |
| 4136 | case_branch.deinit(cg.gpa); | 4092 | case_branch.deinit(cg.gpa); |
| 4137 | } | 4093 | } |
| 4138 | try cg.genBody(case.body); | 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 | if (has_else_body) { | 4100 | if (has_else_body) { |
| | 4101 | const else_body = cases_it.elseBody(); |
| | 4102 | |
| 4143 | cg.branches.appendAssumeCapacity(.{}); | 4103 | cg.branches.appendAssumeCapacity(.{}); |
| 4144 | const else_deaths = liveness.deaths.len - 1; | 4104 | const else_deaths = liveness.deaths.len - 1; |
| 4145 | try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len); | 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,11 +4108,33 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4148 | else_branch.deinit(cg.gpa); | 4108 | else_branch.deinit(cg.gpa); |
| 4149 | } | 4109 | } |
| 4150 | try cg.genBody(else_body); | 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 | return cg.finishAir(inst, .none, &.{}); | 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 | fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void { | 4138 | fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void { |
| 4157 | const zcu = cg.pt.zcu; | 4139 | const zcu = cg.pt.zcu; |
| 4158 | const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; | 4140 | const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; |