| ... | ... | @@ -662,6 +662,8 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct { |
| 662 | 662 | label: u32, |
| 663 | 663 | value: WValue, |
| 664 | 664 | }) = .{}, |
| 665 | /// Maps `loop` instructions to their label. `br` to here repeats the loop. |
| 666 | loops: std.AutoHashMapUnmanaged(Air.Inst.Index, u32) = .{}, |
| 665 | 667 | /// `bytes` contains the wasm bytecode belonging to the 'code' section. |
| 666 | 668 | code: *ArrayList(u8), |
| 667 | 669 | /// The index the next local generated will have |
| ... | ... | @@ -751,6 +753,7 @@ pub fn deinit(func: *CodeGen) void { |
| 751 | 753 | } |
| 752 | 754 | func.branches.deinit(func.gpa); |
| 753 | 755 | func.blocks.deinit(func.gpa); |
| 756 | func.loops.deinit(func.gpa); |
| 754 | 757 | func.locals.deinit(func.gpa); |
| 755 | 758 | func.simd_immediates.deinit(func.gpa); |
| 756 | 759 | func.mir_instructions.deinit(func.gpa); |
| ... | ... | @@ -1903,7 +1906,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1903 | 1906 | .trap => func.airTrap(inst), |
| 1904 | 1907 | .breakpoint => func.airBreakpoint(inst), |
| 1905 | 1908 | .br => func.airBr(inst), |
| 1906 | | .repeat => return func.fail("TODO implement `repeat`", .{}), |
| 1909 | .repeat => func.airRepeat(inst), |
| 1907 | 1910 | .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}), |
| 1908 | 1911 | .int_from_bool => func.airIntFromBool(inst), |
| 1909 | 1912 | .cond_br => func.airCondBr(inst), |
| ... | ... | @@ -3537,10 +3540,11 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3537 | 3540 | // result type of loop is always 'noreturn', meaning we can always |
| 3538 | 3541 | // emit the wasm type 'block_empty'. |
| 3539 | 3542 | try func.startBlock(.loop, wasm.block_empty); |
| 3540 | | try func.genBody(body); |
| 3541 | 3543 | |
| 3542 | | // breaking to the index of a loop block will continue the loop instead |
| 3543 | | try func.addLabel(.br, 0); |
| 3544 | try func.loops.putNoClobber(func.gpa, inst, func.block_depth); |
| 3545 | defer assert(func.loops.remove(inst)); |
| 3546 | |
| 3547 | try func.genBody(body); |
| 3544 | 3548 | try func.endBlock(); |
| 3545 | 3549 | |
| 3546 | 3550 | return func.finishAir(inst, .none, &.{}); |
| ... | ... | @@ -3737,6 +3741,16 @@ fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3737 | 3741 | return func.finishAir(inst, .none, &.{br.operand}); |
| 3738 | 3742 | } |
| 3739 | 3743 | |
| 3744 | fn airRepeat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3745 | const repeat = func.air.instructions.items(.data)[@intFromEnum(inst)].repeat; |
| 3746 | const loop_label = func.loops.get(repeat.loop_inst).?; |
| 3747 | |
| 3748 | const idx: u32 = func.block_depth - loop_label; |
| 3749 | try func.addLabel(.br, idx); |
| 3750 | |
| 3751 | return func.finishAir(inst, .none, &.{}); |
| 3752 | } |
| 3753 | |
| 3740 | 3754 | fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3741 | 3755 | const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 3742 | 3756 | |
| ... | ... | @@ -4053,7 +4067,10 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4053 | 4067 | defer func.gpa.free(liveness.deaths); |
| 4054 | 4068 | |
| 4055 | 4069 | // a list that maps each value with its value and body based on the order inside the list. |
| 4056 | | const CaseValue = struct { integer: i32, value: Value }; |
| 4070 | const CaseValue = union(enum) { |
| 4071 | singular: struct { integer: i32, value: Value }, |
| 4072 | range: struct { min: i32, min_value: Value, max: i32, max_value: Value }, |
| 4073 | }; |
| 4057 | 4074 | var case_list = try std.ArrayList(struct { |
| 4058 | 4075 | values: []const CaseValue, |
| 4059 | 4076 | body: []const Air.Inst.Index, |
| ... | ... | @@ -4064,12 +4081,9 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4064 | 4081 | |
| 4065 | 4082 | var lowest_maybe: ?i32 = null; |
| 4066 | 4083 | var highest_maybe: ?i32 = null; |
| 4067 | | |
| 4068 | 4084 | var it = switch_br.iterateCases(); |
| 4069 | 4085 | while (it.next()) |case| { |
| 4070 | | if (case.ranges.len > 0) return func.fail("TODO: switch with ranges", .{}); |
| 4071 | | |
| 4072 | | const values = try func.gpa.alloc(CaseValue, case.items.len); |
| 4086 | const values = try func.gpa.alloc(CaseValue, case.items.len + case.ranges.len); |
| 4073 | 4087 | errdefer func.gpa.free(values); |
| 4074 | 4088 | |
| 4075 | 4089 | for (case.items, 0..) |ref, i| { |
| ... | ... | @@ -4081,7 +4095,30 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4081 | 4095 | if (highest_maybe == null or int_val > highest_maybe.?) { |
| 4082 | 4096 | highest_maybe = int_val; |
| 4083 | 4097 | } |
| 4084 | | values[i] = .{ .integer = int_val, .value = item_val }; |
| 4098 | values[i] = .{ .singular = .{ .integer = int_val, .value = item_val } }; |
| 4099 | } |
| 4100 | |
| 4101 | for (case.ranges, 0..) |range, i| { |
| 4102 | const min_val = (try func.air.value(range[0], pt)).?; |
| 4103 | const int_min_val = func.valueAsI32(min_val); |
| 4104 | |
| 4105 | if (lowest_maybe == null or int_min_val < lowest_maybe.?) { |
| 4106 | lowest_maybe = int_min_val; |
| 4107 | } |
| 4108 | |
| 4109 | const max_val = (try func.air.value(range[1], pt)).?; |
| 4110 | const int_max_val = func.valueAsI32(max_val); |
| 4111 | |
| 4112 | if (highest_maybe == null or int_max_val > highest_maybe.?) { |
| 4113 | highest_maybe = int_max_val; |
| 4114 | } |
| 4115 | |
| 4116 | values[i + case.items.len] = .{ .range = .{ |
| 4117 | .min = int_min_val, |
| 4118 | .min_value = min_val, |
| 4119 | .max = int_max_val, |
| 4120 | .max_value = max_val, |
| 4121 | } }; |
| 4085 | 4122 | } |
| 4086 | 4123 | |
| 4087 | 4124 | case_list.appendAssumeCapacity(.{ .values = values, .body = case.body }); |
| ... | ... | @@ -4134,7 +4171,12 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4134 | 4171 | const idx = blk: { |
| 4135 | 4172 | for (case_list.items, 0..) |case, idx| { |
| 4136 | 4173 | for (case.values) |case_value| { |
| 4137 | | if (case_value.integer == value) break :blk @as(u32, @intCast(idx)); |
| 4174 | switch (case_value) { |
| 4175 | .singular => |val| if (val.integer == value) break :blk @as(u32, @intCast(idx)), |
| 4176 | .range => |range_val| if (value >= range_val.min and value <= range_val.max) { |
| 4177 | break :blk @as(u32, @intCast(idx)); |
| 4178 | }, |
| 4179 | } |
| 4138 | 4180 | } |
| 4139 | 4181 | } |
| 4140 | 4182 | // error sets are almost always sparse so we use the default case |
| ... | ... | @@ -4150,43 +4192,34 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4150 | 4192 | try func.endBlock(); |
| 4151 | 4193 | } |
| 4152 | 4194 | |
| 4153 | | const signedness: std.builtin.Signedness = blk: { |
| 4154 | | // by default we tell the operand type is unsigned (i.e. bools and enum values) |
| 4155 | | if (target_ty.zigTypeTag(zcu) != .int) break :blk .unsigned; |
| 4156 | | |
| 4157 | | // incase of an actual integer, we emit the correct signedness |
| 4158 | | break :blk target_ty.intInfo(zcu).signedness; |
| 4159 | | }; |
| 4160 | | |
| 4161 | 4195 | try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body)); |
| 4162 | 4196 | for (case_list.items, 0..) |case, index| { |
| 4163 | 4197 | // when sparse, we use if/else-chain, so emit conditional checks |
| 4164 | 4198 | if (is_sparse) { |
| 4165 | | // for single value prong we can emit a simple if |
| 4166 | | if (case.values.len == 1) { |
| 4167 | | try func.emitWValue(target); |
| 4168 | | const val = try func.lowerConstant(case.values[0].value, target_ty); |
| 4169 | | try func.emitWValue(val); |
| 4170 | | const opcode = buildOpcode(.{ |
| 4171 | | .valtype1 = typeToValtype(target_ty, pt, func.target.*), |
| 4172 | | .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition. |
| 4173 | | .signedness = signedness, |
| 4174 | | }); |
| 4175 | | try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 4199 | // for single value prong we can emit a simple condition |
| 4200 | if (case.values.len == 1 and case.values[0] == .singular) { |
| 4201 | const val = try func.lowerConstant(case.values[0].singular.value, target_ty); |
| 4202 | // not equal, because we want to jump out of this block if it does not match the condition. |
| 4203 | _ = try func.cmp(target, val, target_ty, .neq); |
| 4176 | 4204 | try func.addLabel(.br_if, 0); |
| 4177 | 4205 | } else { |
| 4178 | 4206 | // in multi-value prongs we must check if any prongs match the target value. |
| 4179 | 4207 | try func.startBlock(.block, blocktype); |
| 4180 | 4208 | for (case.values) |value| { |
| 4181 | | try func.emitWValue(target); |
| 4182 | | const val = try func.lowerConstant(value.value, target_ty); |
| 4183 | | try func.emitWValue(val); |
| 4184 | | const opcode = buildOpcode(.{ |
| 4185 | | .valtype1 = typeToValtype(target_ty, pt, func.target.*), |
| 4186 | | .op = .eq, |
| 4187 | | .signedness = signedness, |
| 4188 | | }); |
| 4189 | | try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 4209 | switch (value) { |
| 4210 | .singular => |single_val| { |
| 4211 | const val = try func.lowerConstant(single_val.value, target_ty); |
| 4212 | _ = try func.cmp(target, val, target_ty, .eq); |
| 4213 | }, |
| 4214 | .range => |range| { |
| 4215 | const min_val = try func.lowerConstant(range.min_value, target_ty); |
| 4216 | const max_val = try func.lowerConstant(range.max_value, target_ty); |
| 4217 | |
| 4218 | const gte = try func.cmp(target, min_val, target_ty, .gte); |
| 4219 | const lte = try func.cmp(target, max_val, target_ty, .lte); |
| 4220 | _ = try func.binOp(gte, lte, Type.bool, .@"and"); |
| 4221 | }, |
| 4222 | } |
| 4190 | 4223 | try func.addLabel(.br_if, 0); |
| 4191 | 4224 | } |
| 4192 | 4225 | // value did not match any of the prong values |