authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-04-29 23:41:08+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-01 18:30:31+01:00
logcb68c0917ab6ef858a7a9a3ed9e85672304f7ab2
tree331d83110d74e2a99ba3195b745c21be13e34ad7
parent3b52e5a2217baca92f0328c0f9134e982bf15698
signaturelock-open Commit is signed but in an unrecognized format.

wasm: un-regress `loop` and `switch_br`

`.loop` is also a block, so the block_depth must be stored *after* block creation, ensuring a correct block_depth to jump back to when receiving `.repeat`. This also un-regresses `switch_br` which now correctly handles ranges within cases. It supports it for both jump tables as well as regular conditional branches.

1 files changed, 72 insertions(+), 39 deletions(-)

src/arch/wasm/CodeGen.zig+72-39
......@@ -662,6 +662,8 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
662662 label: u32,
663663 value: WValue,
664664}) = .{},
665/// Maps `loop` instructions to their label. `br` to here repeats the loop.
666loops: std.AutoHashMapUnmanaged(Air.Inst.Index, u32) = .{},
665667/// `bytes` contains the wasm bytecode belonging to the 'code' section.
666668code: *ArrayList(u8),
667669/// The index the next local generated will have
......@@ -751,6 +753,7 @@ pub fn deinit(func: *CodeGen) void {
751753 }
752754 func.branches.deinit(func.gpa);
753755 func.blocks.deinit(func.gpa);
756 func.loops.deinit(func.gpa);
754757 func.locals.deinit(func.gpa);
755758 func.simd_immediates.deinit(func.gpa);
756759 func.mir_instructions.deinit(func.gpa);
......@@ -1903,7 +1906,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19031906 .trap => func.airTrap(inst),
19041907 .breakpoint => func.airBreakpoint(inst),
19051908 .br => func.airBr(inst),
1906 .repeat => return func.fail("TODO implement `repeat`", .{}),
1909 .repeat => func.airRepeat(inst),
19071910 .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}),
19081911 .int_from_bool => func.airIntFromBool(inst),
19091912 .cond_br => func.airCondBr(inst),
......@@ -3537,10 +3540,11 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
35373540 // result type of loop is always 'noreturn', meaning we can always
35383541 // emit the wasm type 'block_empty'.
35393542 try func.startBlock(.loop, wasm.block_empty);
3540 try func.genBody(body);
35413543
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);
35443548 try func.endBlock();
35453549
35463550 return func.finishAir(inst, .none, &.{});
......@@ -3737,6 +3741,16 @@ fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
37373741 return func.finishAir(inst, .none, &.{br.operand});
37383742}
37393743
3744fn 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
37403754fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
37413755 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
37423756
......@@ -4053,7 +4067,10 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
40534067 defer func.gpa.free(liveness.deaths);
40544068
40554069 // 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 };
40574074 var case_list = try std.ArrayList(struct {
40584075 values: []const CaseValue,
40594076 body: []const Air.Inst.Index,
......@@ -4064,12 +4081,9 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
40644081
40654082 var lowest_maybe: ?i32 = null;
40664083 var highest_maybe: ?i32 = null;
4067
40684084 var it = switch_br.iterateCases();
40694085 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);
40734087 errdefer func.gpa.free(values);
40744088
40754089 for (case.items, 0..) |ref, i| {
......@@ -4081,7 +4095,30 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
40814095 if (highest_maybe == null or int_val > highest_maybe.?) {
40824096 highest_maybe = int_val;
40834097 }
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 } };
40854122 }
40864123
40874124 case_list.appendAssumeCapacity(.{ .values = values, .body = case.body });
......@@ -4134,7 +4171,12 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
41344171 const idx = blk: {
41354172 for (case_list.items, 0..) |case, idx| {
41364173 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 }
41384180 }
41394181 }
41404182 // 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 {
41504192 try func.endBlock();
41514193 }
41524194
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
41614195 try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body));
41624196 for (case_list.items, 0..) |case, index| {
41634197 // when sparse, we use if/else-chain, so emit conditional checks
41644198 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);
41764204 try func.addLabel(.br_if, 0);
41774205 } else {
41784206 // in multi-value prongs we must check if any prongs match the target value.
41794207 try func.startBlock(.block, blocktype);
41804208 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 }
41904223 try func.addLabel(.br_if, 0);
41914224 }
41924225 // value did not match any of the prong values