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) {...@@ -582,7 +582,7 @@ pub const Result = union(enum) {
582};582};
583583
584/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`584/// 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
587const Self = @This();587const Self = @This();
588588
...@@ -598,8 +598,13 @@ liveness: Liveness,...@@ -598,8 +598,13 @@ liveness: Liveness,
598gpa: mem.Allocator,598gpa: mem.Allocator,
599debug_output: codegen.DebugInfoOutput,599debug_output: codegen.DebugInfoOutput,
600mod_fn: *const Module.Fn,600mod_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) = .{},
601/// Table to save `WValue`'s generated by an `Air.Inst`606/// Table to save `WValue`'s generated by an `Air.Inst`
602values: ValueTable,607// values: ValueTable,
603/// Mapping from Air.Inst.Index to block ids608/// Mapping from Air.Inst.Index to block ids
604blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {609blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
605 label: u32,610 label: u32,
...@@ -682,7 +687,11 @@ const InnerError = error{...@@ -682,7 +687,11 @@ const InnerError = error{
682};687};
683688
684pub fn deinit(self: *Self) void {689pub 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);
686 self.blocks.deinit(self.gpa);695 self.blocks.deinit(self.gpa);
687 self.locals.deinit(self.gpa);696 self.locals.deinit(self.gpa);
688 self.mir_instructions.deinit(self.gpa);697 self.mir_instructions.deinit(self.gpa);
...@@ -705,11 +714,21 @@ fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {...@@ -705,11 +714,21 @@ fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {
705/// Resolves the `WValue` for the given instruction `inst`714/// Resolves the `WValue` for the given instruction `inst`
706/// When the given instruction has a `Value`, it returns a constant instead715/// When the given instruction has a `Value`, it returns a constant instead
707fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!WValue {716fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!WValue {
708 const gop = try self.values.getOrPut(self.gpa, ref);717 var branch_index = self.branches.items.len;
709 if (gop.found_existing) return gop.value_ptr.*;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
711 // when we did not find an existing instruction, it725 // when we did not find an existing instruction, it
712 // means we must generate it from a constant.726 // 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
713 const val = self.air.value(ref).?;732 const val = self.air.value(ref).?;
714 const ty = self.air.typeOf(ref);733 const ty = self.air.typeOf(ref);
715 if (!ty.hasRuntimeBitsIgnoreComptime() and !ty.isInt() and !ty.isError()) {734 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...@@ -745,7 +764,8 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: WValue, operands: []cons
745 // results of `none` can never be referenced.764 // results of `none` can never be referenced.
746 if (result != .none) {765 if (result != .none) {
747 assert(result != .stack); // it's illegal to store a stack value as we cannot track its position766 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);
749 }769 }
750770
751 if (builtin.mode == .Debug) {771 if (builtin.mode == .Debug) {
...@@ -753,6 +773,18 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: WValue, operands: []cons...@@ -753,6 +773,18 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: WValue, operands: []cons
753 }773 }
754}774}
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
756const BigTomb = struct {788const BigTomb = struct {
757 gen: *Self,789 gen: *Self,
758 inst: Air.Inst.Index,790 inst: Air.Inst.Index,
...@@ -768,7 +800,7 @@ const BigTomb = struct {...@@ -768,7 +800,7 @@ const BigTomb = struct {
768 fn finishAir(bt: *BigTomb, result: WValue) void {800 fn finishAir(bt: *BigTomb, result: WValue) void {
769 assert(result != .stack);801 assert(result != .stack);
770 if (result != .none) {802 if (result != .none) {
771 bt.gen.values.putAssumeCapacityNoClobber(Air.indexToRef(bt.inst), result);803 bt.gen.currentBranch().values.putAssumeCapacityNoClobber(Air.indexToRef(bt.inst), result);
772 }804 }
773805
774 if (builtin.mode == .Debug) {806 if (builtin.mode == .Debug) {
...@@ -778,7 +810,7 @@ const BigTomb = struct {...@@ -778,7 +810,7 @@ const BigTomb = struct {
778};810};
779811
780fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb {812fn 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));
782 return BigTomb{814 return BigTomb{
783 .gen = self,815 .gen = self,
784 .inst = inst,816 .inst = inst,
...@@ -789,7 +821,10 @@ fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigT...@@ -789,7 +821,10 @@ fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigT
789fn processDeath(self: *Self, ref: Air.Inst.Ref) void {821fn processDeath(self: *Self, ref: Air.Inst.Ref) void {
790 const inst = Air.refToIndex(ref) orelse return;822 const inst = Air.refToIndex(ref) orelse return;
791 if (self.air.instructions.items(.tag)[inst] == .constant) return;823 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;
793 if (value.* != .local) return;828 if (value.* != .local) return;
794 std.debug.print("Decreasing reference for ref: %{?d}\n", .{Air.refToIndex(ref)});829 std.debug.print("Decreasing reference for ref: %{?d}\n", .{Air.refToIndex(ref)});
795 value.local.references -= 1; // if this panics, a call to `reuseOperand` was forgotten by the developer830 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 {...@@ -924,17 +959,28 @@ fn emitWValue(self: *Self, value: WValue) InnerError!void {
924/// returns the given `operand` itself instead.959/// returns the given `operand` itself instead.
925fn reuseOperand(self: *Self, ref: Air.Inst.Ref, operand: WValue) WValue {960fn reuseOperand(self: *Self, ref: Air.Inst.Ref, operand: WValue) WValue {
926 if (operand != .local and operand != .stack_offset) return operand;961 if (operand != .local and operand != .stack_offset) return operand;
927 var copy = operand;962 var new_value = operand;
928 switch (copy) {963 switch (new_value) {
929 .local => |*local| local.references += 1,964 .local => |*local| local.references += 1,
930 .stack_offset => |*stack_offset| stack_offset.references += 1,965 .stack_offset => |*stack_offset| stack_offset.references += 1,
931 else => unreachable,966 else => unreachable,
932 }967 }
933968 const old_value = self.getResolvedInst(ref);
934 const gop = self.values.getOrPutAssumeCapacity(ref);969 old_value.* = new_value;
935 assert(gop.found_existing);970 return new_value;
936 gop.value_ptr.* = copy;971}
937 return copy;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.
938}984}
939985
940/// Creates one locals for a given `Type`.986/// Creates one locals for a given `Type`.
...@@ -1035,7 +1081,7 @@ pub fn generate(...@@ -1035,7 +1081,7 @@ pub fn generate(
1035 .gpa = bin_file.allocator,1081 .gpa = bin_file.allocator,
1036 .air = air,1082 .air = air,
1037 .liveness = liveness,1083 .liveness = liveness,
1038 .values = .{},1084 // .values = .{},
1039 .code = code,1085 .code = code,
1040 .decl_index = func.owner_decl,1086 .decl_index = func.owner_decl,
1041 .decl = bin_file.options.module.?.declPtr(func.owner_decl),1087 .decl = bin_file.options.module.?.declPtr(func.owner_decl),
...@@ -1070,8 +1116,13 @@ fn genFunc(self: *Self) InnerError!void {...@@ -1070,8 +1116,13 @@ fn genFunc(self: *Self) InnerError!void {
10701116
1071 try self.addTag(.dbg_prologue_end);1117 try self.addTag(.dbg_prologue_end);
10721118
1119 try self.branches.append(self.gpa, .{});
1073 // Generate MIR for function body1120 // Generate MIR for function body
1074 try self.genBody(self.air.getMainBody());1121 try self.genBody(self.air.getMainBody());
1122
1123 // clean up outer branch
1124 _ = self.branches.pop();
1125
1075 // In case we have a return value, but the last instruction is a noreturn (such as a while loop)1126 // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
1076 // we emit an unreachable instruction to tell the stack validator that part will never be reached.1127 // we emit an unreachable instruction to tell the stack validator that part will never be reached.
1077 if (func_type.returns.len != 0 and self.air.instructions.len > 0) {1128 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 {...@@ -1837,7 +1888,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) InnerError!void {
1837fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {1888fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
1838 for (body) |inst| {1889 for (body) |inst| {
1839 const old_bookkeeping_value = self.air_bookkeeping;1890 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);
1841 try self.genInst(inst);1892 try self.genInst(inst);
18421893
1843 if (builtin.mode == .Debug and self.air_bookkeeping < old_bookkeeping_value + 1) {1894 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 {...@@ -2779,7 +2830,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
2779 const extra = self.air.extraData(Air.CondBr, pl_op.payload);2830 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
2780 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];2831 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
2781 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];2832 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
2784 // result type is always noreturn, so use `block_empty` as type.2835 // result type is always noreturn, so use `block_empty` as type.
2785 try self.startBlock(.block, wasm.block_empty);2836 try self.startBlock(.block, wasm.block_empty);
...@@ -2791,15 +2842,70 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void {...@@ -2791,15 +2842,70 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
2791 // and continue with the then codepath2842 // and continue with the then codepath
2792 try self.addLabel(.br_if, 0);2843 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 }
2794 try self.genBody(else_body);2856 try self.genBody(else_body);
2795 try self.endBlock();2857 try self.endBlock();
2858 else_stack.* = self.branches.pop();
27962859
2797 // Outer block that matches the condition2860 // 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 }
2798 try self.genBody(then_body);2870 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
2800 self.finishAir(inst, .none, &.{});2876 self.finishAir(inst, .none, &.{});
2801}2877}
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
2803fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!void {2909fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!void {
2804 const bin_op = self.air.instructions.items(.data)[inst].bin_op;2910 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2805 if (self.liveness.isUnused(inst)) return self.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });2911 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 {...@@ -3176,6 +3282,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
3176 break :blk target_ty.intInfo(self.target).signedness;3282 break :blk target_ty.intInfo(self.target).signedness;
3177 };3283 };
31783284
3285 try self.branches.ensureUnusedCapacity(self.gpa, case_list.items.len + @boolToInt(has_else_body));
3179 for (case_list.items) |case| {3286 for (case_list.items) |case| {
3180 // when sparse, we use if/else-chain, so emit conditional checks3287 // when sparse, we use if/else-chain, so emit conditional checks
3181 if (is_sparse) {3288 if (is_sparse) {
...@@ -3211,13 +3318,18 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!void {...@@ -3211,13 +3318,18 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
3211 try self.endBlock();3318 try self.endBlock();
3212 }3319 }
3213 }3320 }
3321 // try self.branches.items
3322 self.branches.appendAssumeCapacity(.{});
3214 try self.genBody(case.body);3323 try self.genBody(case.body);
3215 try self.endBlock();3324 try self.endBlock();
3325 _ = self.branches.pop();
3216 }3326 }
32173327
3218 if (has_else_body) {3328 if (has_else_body) {
3329 self.branches.appendAssumeCapacity(.{});
3219 try self.genBody(else_body);3330 try self.genBody(else_body);
3220 try self.endBlock();3331 try self.endBlock();
3332 _ = self.branches.pop();
3221 }3333 }
3222 self.finishAir(inst, .none, &.{});3334 self.finishAir(inst, .none, &.{});
3223}3335}
...@@ -3722,7 +3834,7 @@ fn airPtrToInt(self: *Self, inst: Air.Inst.Index) InnerError!void {...@@ -3722,7 +3834,7 @@ fn airPtrToInt(self: *Self, inst: Air.Inst.Index) InnerError!void {
3722 const result = switch (operand) {3834 const result = switch (operand) {
3723 // for stack offset, return a pointer to this offset.3835 // for stack offset, return a pointer to this offset.
3724 .stack_offset => try self.buildPointerOffset(operand, 0, .new),3836 .stack_offset => try self.buildPointerOffset(operand, 0, .new),
3725 else => operand,3837 else => self.reuseOperand(un_op, operand),
3726 };3838 };
3727 self.finishAir(inst, result, &.{un_op});3839 self.finishAir(inst, result, &.{un_op});
3728}3840}