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 {...@@ -662,6 +662,8 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
662 label: u32,662 label: u32,
663 value: WValue,663 value: WValue,
664}) = .{},664}) = .{},
665/// Maps `loop` instructions to their label. `br` to here repeats the loop.
666loops: std.AutoHashMapUnmanaged(Air.Inst.Index, u32) = .{},
665/// `bytes` contains the wasm bytecode belonging to the 'code' section.667/// `bytes` contains the wasm bytecode belonging to the 'code' section.
666code: *ArrayList(u8),668code: *ArrayList(u8),
667/// The index the next local generated will have669/// The index the next local generated will have
...@@ -751,6 +753,7 @@ pub fn deinit(func: *CodeGen) void {...@@ -751,6 +753,7 @@ pub fn deinit(func: *CodeGen) void {
751 }753 }
752 func.branches.deinit(func.gpa);754 func.branches.deinit(func.gpa);
753 func.blocks.deinit(func.gpa);755 func.blocks.deinit(func.gpa);
756 func.loops.deinit(func.gpa);
754 func.locals.deinit(func.gpa);757 func.locals.deinit(func.gpa);
755 func.simd_immediates.deinit(func.gpa);758 func.simd_immediates.deinit(func.gpa);
756 func.mir_instructions.deinit(func.gpa);759 func.mir_instructions.deinit(func.gpa);
...@@ -1903,7 +1906,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1903,7 +1906,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1903 .trap => func.airTrap(inst),1906 .trap => func.airTrap(inst),
1904 .breakpoint => func.airBreakpoint(inst),1907 .breakpoint => func.airBreakpoint(inst),
1905 .br => func.airBr(inst),1908 .br => func.airBr(inst),
1906 .repeat => return func.fail("TODO implement `repeat`", .{}),1909 .repeat => func.airRepeat(inst),
1907 .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}),1910 .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}),
1908 .int_from_bool => func.airIntFromBool(inst),1911 .int_from_bool => func.airIntFromBool(inst),
1909 .cond_br => func.airCondBr(inst),1912 .cond_br => func.airCondBr(inst),
...@@ -3537,10 +3540,11 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3537,10 +3540,11 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3537 // result type of loop is always 'noreturn', meaning we can always3540 // result type of loop is always 'noreturn', meaning we can always
3538 // emit the wasm type 'block_empty'.3541 // emit the wasm type 'block_empty'.
3539 try func.startBlock(.loop, wasm.block_empty);3542 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 instead3544 try func.loops.putNoClobber(func.gpa, inst, func.block_depth);
3543 try func.addLabel(.br, 0);3545 defer assert(func.loops.remove(inst));
3546
3547 try func.genBody(body);
3544 try func.endBlock();3548 try func.endBlock();
35453549
3546 return func.finishAir(inst, .none, &.{});3550 return func.finishAir(inst, .none, &.{});
...@@ -3737,6 +3741,16 @@ fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3737,6 +3741,16 @@ fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3737 return func.finishAir(inst, .none, &.{br.operand});3741 return func.finishAir(inst, .none, &.{br.operand});
3738}3742}
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
3740fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {3754fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3741 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3755 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 {...@@ -4053,7 +4067,10 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4053 defer func.gpa.free(liveness.deaths);4067 defer func.gpa.free(liveness.deaths);
40544068
4055 // a list that maps each value with its value and body based on the order inside the list.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 var case_list = try std.ArrayList(struct {4074 var case_list = try std.ArrayList(struct {
4058 values: []const CaseValue,4075 values: []const CaseValue,
4059 body: []const Air.Inst.Index,4076 body: []const Air.Inst.Index,
...@@ -4064,12 +4081,9 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4064,12 +4081,9 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
40644081
4065 var lowest_maybe: ?i32 = null;4082 var lowest_maybe: ?i32 = null;
4066 var highest_maybe: ?i32 = null;4083 var highest_maybe: ?i32 = null;
4067
4068 var it = switch_br.iterateCases();4084 var it = switch_br.iterateCases();
4069 while (it.next()) |case| {4085 while (it.next()) |case| {
4070 if (case.ranges.len > 0) return func.fail("TODO: switch with ranges", .{});4086 const values = try func.gpa.alloc(CaseValue, case.items.len + case.ranges.len);
4071
4072 const values = try func.gpa.alloc(CaseValue, case.items.len);
4073 errdefer func.gpa.free(values);4087 errdefer func.gpa.free(values);
40744088
4075 for (case.items, 0..) |ref, i| {4089 for (case.items, 0..) |ref, i| {
...@@ -4081,7 +4095,30 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4081,7 +4095,30 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4081 if (highest_maybe == null or int_val > highest_maybe.?) {4095 if (highest_maybe == null or int_val > highest_maybe.?) {
4082 highest_maybe = int_val;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 }
40864123
4087 case_list.appendAssumeCapacity(.{ .values = values, .body = case.body });4124 case_list.appendAssumeCapacity(.{ .values = values, .body = case.body });
...@@ -4134,7 +4171,12 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4134,7 +4171,12 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4134 const idx = blk: {4171 const idx = blk: {
4135 for (case_list.items, 0..) |case, idx| {4172 for (case_list.items, 0..) |case, idx| {
4136 for (case.values) |case_value| {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 // error sets are almost always sparse so we use the default case4182 // 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,43 +4192,34 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4150 try func.endBlock();4192 try func.endBlock();
4151 }4193 }
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
4161 try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body));4195 try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body));
4162 for (case_list.items, 0..) |case, index| {4196 for (case_list.items, 0..) |case, index| {
4163 // when sparse, we use if/else-chain, so emit conditional checks4197 // when sparse, we use if/else-chain, so emit conditional checks
4164 if (is_sparse) {4198 if (is_sparse) {
4165 // for single value prong we can emit a simple if4199 // for single value prong we can emit a simple condition
4166 if (case.values.len == 1) {4200 if (case.values.len == 1 and case.values[0] == .singular) {
4167 try func.emitWValue(target);4201 const val = try func.lowerConstant(case.values[0].singular.value, target_ty);
4168 const val = try func.lowerConstant(case.values[0].value, target_ty);4202 // not equal, because we want to jump out of this block if it does not match the condition.
4169 try func.emitWValue(val);4203 _ = try func.cmp(target, val, target_ty, .neq);
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));
4176 try func.addLabel(.br_if, 0);4204 try func.addLabel(.br_if, 0);
4177 } else {4205 } else {
4178 // in multi-value prongs we must check if any prongs match the target value.4206 // in multi-value prongs we must check if any prongs match the target value.
4179 try func.startBlock(.block, blocktype);4207 try func.startBlock(.block, blocktype);
4180 for (case.values) |value| {4208 for (case.values) |value| {
4181 try func.emitWValue(target);4209 switch (value) {
4182 const val = try func.lowerConstant(value.value, target_ty);4210 .singular => |single_val| {
4183 try func.emitWValue(val);4211 const val = try func.lowerConstant(single_val.value, target_ty);
4184 const opcode = buildOpcode(.{4212 _ = try func.cmp(target, val, target_ty, .eq);
4185 .valtype1 = typeToValtype(target_ty, pt, func.target.*),4213 },
4186 .op = .eq,4214 .range => |range| {
4187 .signedness = signedness,4215 const min_val = try func.lowerConstant(range.min_value, target_ty);
4188 });4216 const max_val = try func.lowerConstant(range.max_value, target_ty);
4189 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));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 try func.addLabel(.br_if, 0);4223 try func.addLabel(.br_if, 0);
4191 }4224 }
4192 // value did not match any of the prong values4225 // value did not match any of the prong values