| ... | ... | @@ -200,6 +200,34 @@ const Branch = struct { |
| 200 | 200 | self.inst_table.deinit(gpa); |
| 201 | 201 | self.* = undefined; |
| 202 | 202 | } |
| 203 | |
| 204 | const FormatContext = struct { |
| 205 | insts: []const Air.Inst.Index, |
| 206 | mcvs: []const MCValue, |
| 207 | }; |
| 208 | |
| 209 | fn fmt( |
| 210 | ctx: FormatContext, |
| 211 | comptime unused_format_string: []const u8, |
| 212 | options: std.fmt.FormatOptions, |
| 213 | writer: anytype, |
| 214 | ) @TypeOf(writer).Error!void { |
| 215 | _ = options; |
| 216 | comptime assert(unused_format_string.len == 0); |
| 217 | try writer.writeAll("Branch {\n"); |
| 218 | for (ctx.insts) |inst, i| { |
| 219 | const mcv = ctx.mcvs[i]; |
| 220 | try writer.print(" %{d} => {}\n", .{ inst, mcv }); |
| 221 | } |
| 222 | try writer.writeAll("}"); |
| 223 | } |
| 224 | |
| 225 | fn fmtDebug(self: @This()) std.fmt.Formatter(fmt) { |
| 226 | return .{ .data = .{ |
| 227 | .insts = self.inst_table.keys(), |
| 228 | .mcvs = self.inst_table.values(), |
| 229 | } }; |
| 230 | } |
| 203 | 231 | }; |
| 204 | 232 | |
| 205 | 233 | const StackAllocation = struct { |
| ... | ... | @@ -232,7 +260,7 @@ const BigTomb = struct { |
| 232 | 260 | fn finishAir(bt: *BigTomb, result: MCValue) void { |
| 233 | 261 | const is_used = !bt.function.liveness.isUnused(bt.inst); |
| 234 | 262 | if (is_used) { |
| 235 | | log.debug("%{d} => {}", .{ bt.inst, result }); |
| 263 | log.debug(" (saving %{d} => {})", .{ bt.inst, result }); |
| 236 | 264 | const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1]; |
| 237 | 265 | branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result); |
| 238 | 266 | } |
| ... | ... | @@ -795,6 +823,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 795 | 823 | fn processDeath(self: *Self, inst: Air.Inst.Index) void { |
| 796 | 824 | const air_tags = self.air.instructions.items(.tag); |
| 797 | 825 | if (air_tags[inst] == .constant) return; // Constants are immortal. |
| 826 | log.debug(" (processing death of %{d})", .{inst}); |
| 798 | 827 | // When editing this function, note that the logic must synchronize with `reuseOperand`. |
| 799 | 828 | const prev_value = self.getResolvedInstValue(inst); |
| 800 | 829 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| ... | ... | @@ -822,8 +851,10 @@ fn finishAirBookkeeping(self: *Self) void { |
| 822 | 851 | } |
| 823 | 852 | |
| 824 | 853 | fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Liveness.bpi - 1]Air.Inst.Ref) void { |
| 854 | log.debug("finishAir: %{d}, {}, {any}", .{ inst, result, operands }); |
| 825 | 855 | var tomb_bits = self.liveness.getTombBits(inst); |
| 826 | 856 | for (operands) |op| { |
| 857 | log.debug(" (processing {})", .{op}); |
| 827 | 858 | const dies = @truncate(u1, tomb_bits) != 0; |
| 828 | 859 | tomb_bits >>= 1; |
| 829 | 860 | if (!dies) continue; |
| ... | ... | @@ -834,7 +865,7 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live |
| 834 | 865 | } |
| 835 | 866 | const is_used = @truncate(u1, tomb_bits) == 0; |
| 836 | 867 | if (is_used) { |
| 837 | | log.debug("%{d} => {}", .{ inst, result }); |
| 868 | log.debug(" (saving %{d} => {})", .{ inst, result }); |
| 838 | 869 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 839 | 870 | branch.inst_table.putAssumeCapacityNoClobber(inst, result); |
| 840 | 871 | |
| ... | ... | @@ -4647,6 +4678,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4647 | 4678 | |
| 4648 | 4679 | const reloc = try self.genCondBrMir(cond_ty, cond); |
| 4649 | 4680 | |
| 4681 | log.debug("airCondBr: %{d}", .{inst}); |
| 4682 | |
| 4650 | 4683 | // If the condition dies here in this condbr instruction, process |
| 4651 | 4684 | // that death now instead of later as this has an effect on |
| 4652 | 4685 | // whether it needs to be spilled in the branches |
| ... | ... | @@ -4674,15 +4707,17 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4674 | 4707 | |
| 4675 | 4708 | // Revert to the previous register and stack allocation state. |
| 4676 | 4709 | |
| 4677 | | var saved_then_branch = self.branch_stack.pop(); |
| 4678 | | defer saved_then_branch.deinit(self.gpa); |
| 4710 | var then_branch = self.branch_stack.pop(); |
| 4711 | defer then_branch.deinit(self.gpa); |
| 4679 | 4712 | |
| 4680 | 4713 | self.revertState(saved_state); |
| 4681 | 4714 | |
| 4682 | 4715 | try self.performReloc(reloc); |
| 4683 | 4716 | |
| 4684 | | const else_branch = self.branch_stack.addOneAssumeCapacity(); |
| 4685 | | else_branch.* = .{}; |
| 4717 | try self.branch_stack.append(.{}); |
| 4718 | errdefer { |
| 4719 | _ = self.branch_stack.pop(); |
| 4720 | } |
| 4686 | 4721 | |
| 4687 | 4722 | try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len); |
| 4688 | 4723 | for (liveness_condbr.else_deaths) |operand| { |
| ... | ... | @@ -4690,6 +4725,9 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4690 | 4725 | } |
| 4691 | 4726 | try self.genBody(else_body); |
| 4692 | 4727 | |
| 4728 | var else_branch = self.branch_stack.pop(); |
| 4729 | defer else_branch.deinit(self.gpa); |
| 4730 | |
| 4693 | 4731 | // At this point, each branch will possibly have conflicting values for where |
| 4694 | 4732 | // each instruction is stored. They agree, however, on which instructions are alive/dead. |
| 4695 | 4733 | // We use the first ("then") branch as canonical, and here emit |
| ... | ... | @@ -4698,15 +4736,23 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4698 | 4736 | // that we can use all the code emitting abstractions. This is why at the bottom we |
| 4699 | 4737 | // assert that parent_branch.free_registers equals the saved_then_branch.free_registers |
| 4700 | 4738 | // rather than assigning it. |
| 4701 | | const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2]; |
| 4739 | const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 4702 | 4740 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count()); |
| 4703 | 4741 | |
| 4742 | log.debug("Upper branches:", .{}); |
| 4743 | for (self.branch_stack.items) |bs| { |
| 4744 | log.debug("{}", .{bs.fmtDebug()}); |
| 4745 | } |
| 4746 | |
| 4747 | log.debug("Then branch: {}", .{then_branch.fmtDebug()}); |
| 4748 | log.debug("Else branch: {}", .{else_branch.fmtDebug()}); |
| 4749 | |
| 4704 | 4750 | const else_slice = else_branch.inst_table.entries.slice(); |
| 4705 | 4751 | const else_keys = else_slice.items(.key); |
| 4706 | 4752 | const else_values = else_slice.items(.value); |
| 4707 | 4753 | for (else_keys) |else_key, else_idx| { |
| 4708 | 4754 | const else_value = else_values[else_idx]; |
| 4709 | | const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: { |
| 4755 | const canon_mcv = if (then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: { |
| 4710 | 4756 | // The instruction's MCValue is overridden in both branches. |
| 4711 | 4757 | parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value); |
| 4712 | 4758 | if (else_value == .dead) { |
| ... | ... | @@ -4718,7 +4764,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4718 | 4764 | if (else_value == .dead) |
| 4719 | 4765 | continue; |
| 4720 | 4766 | // The instruction is only overridden in the else branch. |
| 4721 | | var i: usize = self.branch_stack.items.len - 2; |
| 4767 | var i: usize = self.branch_stack.items.len - 1; |
| 4722 | 4768 | while (true) { |
| 4723 | 4769 | i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead? |
| 4724 | 4770 | if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| { |
| ... | ... | @@ -4733,8 +4779,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4733 | 4779 | try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value); |
| 4734 | 4780 | // TODO track the new register / stack allocation |
| 4735 | 4781 | } |
| 4736 | | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count()); |
| 4737 | | const then_slice = saved_then_branch.inst_table.entries.slice(); |
| 4782 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, then_branch.inst_table.count()); |
| 4783 | const then_slice = then_branch.inst_table.entries.slice(); |
| 4738 | 4784 | const then_keys = then_slice.items(.key); |
| 4739 | 4785 | const then_values = then_slice.items(.value); |
| 4740 | 4786 | for (then_keys) |then_key, then_idx| { |
| ... | ... | @@ -4746,7 +4792,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4746 | 4792 | if (then_value == .dead) |
| 4747 | 4793 | continue; |
| 4748 | 4794 | const parent_mcv = blk: { |
| 4749 | | var i: usize = self.branch_stack.items.len - 2; |
| 4795 | log.debug("{d}", .{self.branch_stack.items.len}); |
| 4796 | var i: usize = self.branch_stack.items.len - 1; |
| 4750 | 4797 | while (true) { |
| 4751 | 4798 | i -= 1; |
| 4752 | 4799 | if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| { |
| ... | ... | @@ -4762,11 +4809,6 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 4762 | 4809 | // TODO track the new register / stack allocation |
| 4763 | 4810 | } |
| 4764 | 4811 | |
| 4765 | | { |
| 4766 | | var item = self.branch_stack.pop(); |
| 4767 | | item.deinit(self.gpa); |
| 4768 | | } |
| 4769 | | |
| 4770 | 4812 | // We already took care of pl_op.operand earlier, so we're going |
| 4771 | 4813 | // to pass .none here |
| 4772 | 4814 | return self.finishAir(inst, .unreach, .{ .none, .none, .none }); |
| ... | ... | @@ -5139,6 +5181,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 5139 | 5181 | ); |
| 5140 | 5182 | defer self.gpa.free(liveness.deaths); |
| 5141 | 5183 | |
| 5184 | log.debug("airSwitch: %{d}", .{inst}); |
| 5185 | |
| 5142 | 5186 | // If the condition dies here in this switch instruction, process |
| 5143 | 5187 | // that death now instead of later as this has an effect on |
| 5144 | 5188 | // whether it needs to be spilled in the branches |
| ... | ... | @@ -5150,6 +5194,15 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 5150 | 5194 | } |
| 5151 | 5195 | } |
| 5152 | 5196 | |
| 5197 | var branch_stack = std.ArrayList(Branch).init(self.gpa); |
| 5198 | defer { |
| 5199 | for (branch_stack.items) |*bs| { |
| 5200 | bs.deinit(self.gpa); |
| 5201 | } |
| 5202 | branch_stack.deinit(); |
| 5203 | } |
| 5204 | try branch_stack.ensureTotalCapacityPrecise(switch_br.data.cases_len + 1); |
| 5205 | |
| 5153 | 5206 | while (case_i < switch_br.data.cases_len) : (case_i += 1) { |
| 5154 | 5207 | const case = self.air.extraData(Air.SwitchBr.Case, extra_index); |
| 5155 | 5208 | const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); |
| ... | ... | @@ -5179,10 +5232,9 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 5179 | 5232 | |
| 5180 | 5233 | try self.genBody(case_body); |
| 5181 | 5234 | |
| 5182 | | // Revert to the previous register and stack allocation state. |
| 5183 | | var saved_case_branch = self.branch_stack.pop(); |
| 5184 | | defer saved_case_branch.deinit(self.gpa); |
| 5235 | branch_stack.appendAssumeCapacity(self.branch_stack.pop()); |
| 5185 | 5236 | |
| 5237 | // Revert to the previous register and stack allocation state. |
| 5186 | 5238 | self.revertState(saved_state); |
| 5187 | 5239 | |
| 5188 | 5240 | for (relocs) |reloc| { |
| ... | ... | @@ -5192,10 +5244,13 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 5192 | 5244 | |
| 5193 | 5245 | if (switch_br.data.else_body_len > 0) { |
| 5194 | 5246 | const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; |
| 5247 | |
| 5248 | // Capture the state of register and stack allocation state so that we can revert to it. |
| 5249 | const saved_state = try self.captureState(); |
| 5250 | |
| 5195 | 5251 | try self.branch_stack.append(.{}); |
| 5196 | | defer { |
| 5197 | | var item = self.branch_stack.pop(); |
| 5198 | | item.deinit(self.gpa); |
| 5252 | errdefer { |
| 5253 | _ = self.branch_stack.pop(); |
| 5199 | 5254 | } |
| 5200 | 5255 | |
| 5201 | 5256 | const else_deaths = liveness.deaths.len - 1; |
| ... | ... | @@ -5206,8 +5261,29 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 5206 | 5261 | |
| 5207 | 5262 | try self.genBody(else_body); |
| 5208 | 5263 | |
| 5209 | | // TODO consolidate returned MCValues between prongs and else branch like we do |
| 5210 | | // in airCondBr. |
| 5264 | branch_stack.appendAssumeCapacity(self.branch_stack.pop()); |
| 5265 | |
| 5266 | // Revert to the previous register and stack allocation state. |
| 5267 | self.revertState(saved_state); |
| 5268 | } |
| 5269 | |
| 5270 | // Consolidate returned MCValues between prongs and else branch like we do |
| 5271 | // in airCondBr. |
| 5272 | log.debug("Upper branches:", .{}); |
| 5273 | for (self.branch_stack.items) |bs| { |
| 5274 | log.debug("{}", .{bs.fmtDebug()}); |
| 5275 | } |
| 5276 | for (branch_stack.items) |bs, i| { |
| 5277 | log.debug("Case-{d} branch: {}", .{ i, bs.fmtDebug() }); |
| 5278 | } |
| 5279 | |
| 5280 | // TODO: can we reduce the complexity of this algorithm? |
| 5281 | const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 5282 | var i: usize = branch_stack.items.len; |
| 5283 | while (i > 1) : (i -= 1) { |
| 5284 | const canon_branch = &branch_stack.items[i - 2]; |
| 5285 | const target_branch = &branch_stack.items[i - 1]; |
| 5286 | try self.canonicaliseBranches(parent_branch, canon_branch, target_branch); |
| 5211 | 5287 | } |
| 5212 | 5288 | |
| 5213 | 5289 | // We already took care of pl_op.operand earlier, so we're going |
| ... | ... | @@ -5215,6 +5291,72 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 5215 | 5291 | return self.finishAir(inst, .unreach, .{ .none, .none, .none }); |
| 5216 | 5292 | } |
| 5217 | 5293 | |
| 5294 | fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Branch, target_branch: *Branch) !void { |
| 5295 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, target_branch.inst_table.count()); |
| 5296 | |
| 5297 | const target_slice = target_branch.inst_table.entries.slice(); |
| 5298 | const target_keys = target_slice.items(.key); |
| 5299 | const target_values = target_slice.items(.value); |
| 5300 | |
| 5301 | for (target_keys) |target_key, target_idx| { |
| 5302 | const target_value = target_values[target_idx]; |
| 5303 | const canon_mcv = if (canon_branch.inst_table.fetchSwapRemove(target_key)) |canon_entry| blk: { |
| 5304 | // The instruction's MCValue is overridden in both branches. |
| 5305 | parent_branch.inst_table.putAssumeCapacity(target_key, canon_entry.value); |
| 5306 | if (target_value == .dead) { |
| 5307 | assert(canon_entry.value == .dead); |
| 5308 | continue; |
| 5309 | } |
| 5310 | break :blk canon_entry.value; |
| 5311 | } else blk: { |
| 5312 | if (target_value == .dead) |
| 5313 | continue; |
| 5314 | // The instruction is only overridden in the else branch. |
| 5315 | var i: usize = self.branch_stack.items.len - 1; |
| 5316 | while (true) { |
| 5317 | i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead? |
| 5318 | if (self.branch_stack.items[i].inst_table.get(target_key)) |mcv| { |
| 5319 | assert(mcv != .dead); |
| 5320 | break :blk mcv; |
| 5321 | } |
| 5322 | } |
| 5323 | }; |
| 5324 | log.debug("consolidating target_entry {d} {}=>{}", .{ target_key, target_value, canon_mcv }); |
| 5325 | // TODO make sure the destination stack offset / register does not already have something |
| 5326 | // going on there. |
| 5327 | try self.setRegOrMem(self.air.typeOfIndex(target_key), canon_mcv, target_value); |
| 5328 | // TODO track the new register / stack allocation |
| 5329 | } |
| 5330 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, canon_branch.inst_table.count()); |
| 5331 | const canon_slice = canon_branch.inst_table.entries.slice(); |
| 5332 | const canon_keys = canon_slice.items(.key); |
| 5333 | const canon_values = canon_slice.items(.value); |
| 5334 | for (canon_keys) |canon_key, canon_idx| { |
| 5335 | const canon_value = canon_values[canon_idx]; |
| 5336 | // We already deleted the items from this table that matched the target_branch. |
| 5337 | // So these are all instructions that are only overridden in the canon branch. |
| 5338 | parent_branch.inst_table.putAssumeCapacity(canon_key, canon_value); |
| 5339 | log.debug("canon_value = {}", .{canon_value}); |
| 5340 | if (canon_value == .dead) |
| 5341 | continue; |
| 5342 | const parent_mcv = blk: { |
| 5343 | var i: usize = self.branch_stack.items.len - 1; |
| 5344 | while (true) { |
| 5345 | i -= 1; |
| 5346 | if (self.branch_stack.items[i].inst_table.get(canon_key)) |mcv| { |
| 5347 | assert(mcv != .dead); |
| 5348 | break :blk mcv; |
| 5349 | } |
| 5350 | } |
| 5351 | }; |
| 5352 | log.debug("consolidating canon_entry {d} {}=>{}", .{ canon_key, parent_mcv, canon_value }); |
| 5353 | // TODO make sure the destination stack offset / register does not already have something |
| 5354 | // going on there. |
| 5355 | try self.setRegOrMem(self.air.typeOfIndex(canon_key), parent_mcv, canon_value); |
| 5356 | // TODO track the new register / stack allocation |
| 5357 | } |
| 5358 | } |
| 5359 | |
| 5218 | 5360 | fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { |
| 5219 | 5361 | const next_inst = @intCast(u32, self.mir_instructions.len); |
| 5220 | 5362 | switch (self.mir_instructions.items(.tag)[reloc]) { |