authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-10-14 21:45:05+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-10-16 15:54:16+02:00
loge62bb1d6892e0ca4afe921bee2eb8baa778b51b5
tree840686f7ff9abba6b4ec83794a1446a3bebfd7ee
parent576bb3f0a965cd7ff3dad6076567657f18d6675e
signaturelock-open Commit is signed but in an unrecognized format.

wasm: implement branching

Upon a branch, we only allow locals to be freed which were allocated within the same branch as where they die. This ensures that when two or more branches target the same operand we do not try to free it more than once. This does however not implement freeing the local upon branch merging yet.

1 files changed, 132 insertions(+), 20 deletions(-)

src/arch/wasm/CodeGen.zig+132-20
......@@ -582,7 +582,7 @@ pub const Result = union(enum) {
582582};
583583
584584/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
585pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Ref, WValue);
585pub const ValueTable = std.AutoArrayHashMapUnmanaged(Air.Inst.Ref, WValue);
586586
587587const Self = @This();
588588
......@@ -598,8 +598,13 @@ liveness: Liveness,
598598gpa: mem.Allocator,
599599debug_output: codegen.DebugInfoOutput,
600600mod_fn: *const Module.Fn,
601/// Contains a list of current branches.
602/// When we return from a branch, the branch will be popped from this list,
603/// which means branches can only contain references from within its own branch,
604/// or a branch higher (lower index) in the tree.
605branches: std.ArrayListUnmanaged(Branch) = .{},
601606/// Table to save `WValue`'s generated by an `Air.Inst`
602values: ValueTable,
607// values: ValueTable,
603608/// Mapping from Air.Inst.Index to block ids
604609blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
605610 label: u32,
......@@ -682,7 +687,11 @@ const InnerError = error{
682687};
683688
684689pub fn deinit(self: *Self) void {
685 self.values.deinit(self.gpa);
690 for (self.branches.items) |*branch| {
691 branch.deinit(self.gpa);
692 }
693 self.branches.deinit(self.gpa);
694 // self.values.deinit(self.gpa);
686695 self.blocks.deinit(self.gpa);
687696 self.locals.deinit(self.gpa);
688697 self.mir_instructions.deinit(self.gpa);
......@@ -705,11 +714,21 @@ fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {
705714/// Resolves the `WValue` for the given instruction `inst`
706715/// When the given instruction has a `Value`, it returns a constant instead
707716fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!WValue {
708 const gop = try self.values.getOrPut(self.gpa, ref);
709 if (gop.found_existing) return gop.value_ptr.*;
717 var branch_index = self.branches.items.len;
718 while (branch_index > 0) : (branch_index -= 1) {
719 const branch = self.branches.items[branch_index - 1];
720 if (branch.values.get(ref)) |value| {
721 return value;
722 }
723 }
710724
711725 // when we did not find an existing instruction, it
712726 // means we must generate it from a constant.
727 // We always store constants in the most outer branch as they must never
728 // be removed. The most outer branch is always at index 0.
729 const gop = try self.branches.items[0].values.getOrPut(self.gpa, ref);
730 assert(!gop.found_existing);
731
713732 const val = self.air.value(ref).?;
714733 const ty = self.air.typeOf(ref);
715734 if (!ty.hasRuntimeBitsIgnoreComptime() and !ty.isInt() and !ty.isError()) {
......@@ -745,7 +764,8 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: WValue, operands: []cons
745764 // results of `none` can never be referenced.
746765 if (result != .none) {
747766 assert(result != .stack); // it's illegal to store a stack value as we cannot track its position
748 self.values.putAssumeCapacityNoClobber(Air.indexToRef(inst), result);
767 const branch = self.currentBranch();
768 branch.values.putAssumeCapacityNoClobber(Air.indexToRef(inst), result);
749769 }
750770
751771 if (builtin.mode == .Debug) {
......@@ -753,6 +773,18 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: WValue, operands: []cons
753773 }
754774}
755775
776const Branch = struct {
777 values: ValueTable = .{},
778
779 fn deinit(branch: *Branch, gpa: Allocator) void {
780 branch.values.deinit(gpa);
781 }
782};
783
784inline fn currentBranch(self: *Self) *Branch {
785 return &self.branches.items[self.branches.items.len - 1];
786}
787
756788const BigTomb = struct {
757789 gen: *Self,
758790 inst: Air.Inst.Index,
......@@ -768,7 +800,7 @@ const BigTomb = struct {
768800 fn finishAir(bt: *BigTomb, result: WValue) void {
769801 assert(result != .stack);
770802 if (result != .none) {
771 bt.gen.values.putAssumeCapacityNoClobber(Air.indexToRef(bt.inst), result);
803 bt.gen.currentBranch().values.putAssumeCapacityNoClobber(Air.indexToRef(bt.inst), result);
772804 }
773805
774806 if (builtin.mode == .Debug) {
......@@ -778,7 +810,7 @@ const BigTomb = struct {
778810};
779811
780812fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb {
781 try self.values.ensureUnusedCapacity(self.gpa, @intCast(u32, operand_count + 1));
813 try self.currentBranch().values.ensureUnusedCapacity(self.gpa, @intCast(u32, operand_count + 1));
782814 return BigTomb{
783815 .gen = self,
784816 .inst = inst,
......@@ -789,7 +821,10 @@ fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigT
789821fn processDeath(self: *Self, ref: Air.Inst.Ref) void {
790822 const inst = Air.refToIndex(ref) orelse return;
791823 if (self.air.instructions.items(.tag)[inst] == .constant) return;
792 const value = self.values.getPtr(ref) orelse return;
824 // Branches are currently only allowed to free locals allocated
825 // within their own branch.
826 // TODO: Upon branch consolidation free any locals if needed.
827 const value = self.currentBranch().values.getPtr(ref) orelse return;
793828 if (value.* != .local) return;
794829 std.debug.print("Decreasing reference for ref: %{?d}\n", .{Air.refToIndex(ref)});
795830 value.local.references -= 1; // if this panics, a call to `reuseOperand` was forgotten by the developer
......@@ -924,17 +959,28 @@ fn emitWValue(self: *Self, value: WValue) InnerError!void {
924959/// returns the given `operand` itself instead.
925960fn reuseOperand(self: *Self, ref: Air.Inst.Ref, operand: WValue) WValue {
926961 if (operand != .local and operand != .stack_offset) return operand;
927 var copy = operand;
928 switch (copy) {
962 var new_value = operand;
963 switch (new_value) {
929964 .local => |*local| local.references += 1,
930965 .stack_offset => |*stack_offset| stack_offset.references += 1,
931966 else => unreachable,
932967 }
933
934 const gop = self.values.getOrPutAssumeCapacity(ref);
935 assert(gop.found_existing);
936 gop.value_ptr.* = copy;
937 return copy;
968 const old_value = self.getResolvedInst(ref);
969 old_value.* = new_value;
970 return new_value;
971}
972
973/// From a reference, returns its resolved `WValue`.
974/// It's illegal to provide a `Air.Inst.Ref` that hasn't been resolved yet.
975fn getResolvedInst(self: *Self, ref: Air.Inst.Ref) *WValue {
976 var index = self.branches.items.len;
977 while (index > 0) : (index -= 1) {
978 const branch = self.branches.items[index - 1];
979 if (branch.values.getPtr(ref)) |value| {
980 return value;
981 }
982 }
983 unreachable; // developer-error: This can only be called on resolved instructions. Use `resolveInst` instead.
938984}
939985
940986/// Creates one locals for a given `Type`.
......@@ -1035,7 +1081,7 @@ pub fn generate(
10351081 .gpa = bin_file.allocator,
10361082 .air = air,
10371083 .liveness = liveness,
1038 .values = .{},
1084 // .values = .{},
10391085 .code = code,
10401086 .decl_index = func.owner_decl,
10411087 .decl = bin_file.options.module.?.declPtr(func.owner_decl),
......@@ -1070,8 +1116,13 @@ fn genFunc(self: *Self) InnerError!void {
10701116
10711117 try self.addTag(.dbg_prologue_end);
10721118
1119 try self.branches.append(self.gpa, .{});
10731120 // Generate MIR for function body
10741121 try self.genBody(self.air.getMainBody());
1122
1123 // clean up outer branch
1124 _ = self.branches.pop();
1125
10751126 // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
10761127 // we emit an unreachable instruction to tell the stack validator that part will never be reached.
10771128 if (func_type.returns.len != 0 and self.air.instructions.len > 0) {
......@@ -1837,7 +1888,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) InnerError!void {
18371888fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
18381889 for (body) |inst| {
18391890 const old_bookkeeping_value = self.air_bookkeeping;
1840 try self.values.ensureUnusedCapacity(self.gpa, Liveness.bpi);
1891 try self.currentBranch().values.ensureUnusedCapacity(self.gpa, Liveness.bpi);
18411892 try self.genInst(inst);
18421893
18431894 if (builtin.mode == .Debug and self.air_bookkeeping < old_bookkeeping_value + 1) {
......@@ -2779,7 +2830,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
27792830 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
27802831 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
27812832 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
2782 // const liveness_condbr = self.liveness.getCondBr(inst);
2833 const liveness_condbr = self.liveness.getCondBr(inst);
27832834
27842835 // result type is always noreturn, so use `block_empty` as type.
27852836 try self.startBlock(.block, wasm.block_empty);
......@@ -2791,15 +2842,70 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
27912842 // and continue with the then codepath
27922843 try self.addLabel(.br_if, 0);
27932844
2845 try self.branches.ensureUnusedCapacity(self.gpa, 2);
2846
2847 const else_stack = self.branches.addOneAssumeCapacity();
2848 else_stack.* = .{};
2849 defer else_stack.deinit(self.gpa);
2850
2851 try self.currentBranch().values.ensureUnusedCapacity(self.gpa, @intCast(u32, liveness_condbr.else_deaths.len));
2852 for (liveness_condbr.else_deaths) |death| {
2853 self.processDeath(Air.indexToRef(death));
2854 std.debug.print("Death inst: %{d}\n", .{death});
2855 }
27942856 try self.genBody(else_body);
27952857 try self.endBlock();
2858 else_stack.* = self.branches.pop();
27962859
27972860 // Outer block that matches the condition
2861 const then_stack = self.branches.addOneAssumeCapacity();
2862 then_stack.* = .{};
2863 defer then_stack.deinit(self.gpa);
2864
2865 try self.currentBranch().values.ensureUnusedCapacity(self.gpa, @intCast(u32, liveness_condbr.then_deaths.len));
2866 for (liveness_condbr.then_deaths) |death| {
2867 self.processDeath(Air.indexToRef(death));
2868 std.debug.print("Death inst: %{d}\n", .{death});
2869 }
27982870 try self.genBody(then_body);
2871 then_stack.* = self.branches.pop();
2872
2873 try self.canonicaliseBranches(then_stack, else_stack);
27992874
2875 // TODO: Branch consilidation to process deaths from branches
28002876 self.finishAir(inst, .none, &.{});
28012877}
28022878
2879fn canonicaliseBranches(self: *Self, canon_branch: *Branch, target_branch: *Branch) !void {
2880 const parent = self.currentBranch();
2881
2882 const target_slice = target_branch.values.entries.slice();
2883 const target_keys = target_slice.items(.key);
2884 const target_values = target_slice.items(.value);
2885
2886 try parent.values.ensureUnusedCapacity(self.gpa, target_branch.values.count());
2887 for (target_keys) |key, index| {
2888 const value = target_values[index];
2889 const canon_value = if (canon_branch.values.fetchSwapRemove(key)) |canon_entry| {
2890 // try parent.values.putAssumeCapacity(key, canon_entry.value);
2891 _ = canon_entry;
2892 // _ = result_value;
2893 @panic("HMMMM THIS occurs");
2894 // break :result_value canon_entry.value;
2895 } else value;
2896
2897 parent.values.putAssumeCapacity(key, canon_value);
2898 }
2899
2900 try parent.values.ensureUnusedCapacity(self.gpa, canon_branch.values.count());
2901 const canon_slice = canon_branch.values.entries.slice();
2902 const canon_keys = canon_slice.items(.key);
2903 const canon_values = canon_slice.items(.value);
2904 for (canon_keys) |key, index| {
2905 parent.values.putAssumeCapacity(key, canon_values[index]);
2906 }
2907}
2908
28032909fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!void {
28042910 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
28052911 if (self.liveness.isUnused(inst)) return self.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
......@@ -3176,6 +3282,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
31763282 break :blk target_ty.intInfo(self.target).signedness;
31773283 };
31783284
3285 try self.branches.ensureUnusedCapacity(self.gpa, case_list.items.len + @boolToInt(has_else_body));
31793286 for (case_list.items) |case| {
31803287 // when sparse, we use if/else-chain, so emit conditional checks
31813288 if (is_sparse) {
......@@ -3211,13 +3318,18 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
32113318 try self.endBlock();
32123319 }
32133320 }
3321 // try self.branches.items
3322 self.branches.appendAssumeCapacity(.{});
32143323 try self.genBody(case.body);
32153324 try self.endBlock();
3325 _ = self.branches.pop();
32163326 }
32173327
32183328 if (has_else_body) {
3329 self.branches.appendAssumeCapacity(.{});
32193330 try self.genBody(else_body);
32203331 try self.endBlock();
3332 _ = self.branches.pop();
32213333 }
32223334 self.finishAir(inst, .none, &.{});
32233335}
......@@ -3722,7 +3834,7 @@ fn airPtrToInt(self: *Self, inst: Air.Inst.Index) InnerError!void {
37223834 const result = switch (operand) {
37233835 // for stack offset, return a pointer to this offset.
37243836 .stack_offset => try self.buildPointerOffset(operand, 0, .new),
3725 else => operand,
3837 else => self.reuseOperand(un_op, operand),
37263838 };
37273839 self.finishAir(inst, result, &.{un_op});
37283840}