authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-09-02 17:09:07+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-09-07 22:42:56+02:00
logc0e288c78248870bf9881b25ff8354c67753bbe2
treeb587eba72a4c887dd32098ff8d54cb50bb411dca
parent3a4c69c01824fb6f72e90433a5683a8df09ad4c1

x86_64: implement canonicalising branches in switch expression


1 files changed, 167 insertions(+), 25 deletions(-)

src/arch/x86_64/CodeGen.zig+167-25
...@@ -200,6 +200,34 @@ const Branch = struct {...@@ -200,6 +200,34 @@ const Branch = struct {
200 self.inst_table.deinit(gpa);200 self.inst_table.deinit(gpa);
201 self.* = undefined;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};
204232
205const StackAllocation = struct {233const StackAllocation = struct {
...@@ -232,7 +260,7 @@ const BigTomb = struct {...@@ -232,7 +260,7 @@ const BigTomb = struct {
232 fn finishAir(bt: *BigTomb, result: MCValue) void {260 fn finishAir(bt: *BigTomb, result: MCValue) void {
233 const is_used = !bt.function.liveness.isUnused(bt.inst);261 const is_used = !bt.function.liveness.isUnused(bt.inst);
234 if (is_used) {262 if (is_used) {
235 log.debug("%{d} => {}", .{ bt.inst, result });263 log.debug(" (saving %{d} => {})", .{ bt.inst, result });
236 const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1];264 const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1];
237 branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result);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,6 +823,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
795fn processDeath(self: *Self, inst: Air.Inst.Index) void {823fn processDeath(self: *Self, inst: Air.Inst.Index) void {
796 const air_tags = self.air.instructions.items(.tag);824 const air_tags = self.air.instructions.items(.tag);
797 if (air_tags[inst] == .constant) return; // Constants are immortal.825 if (air_tags[inst] == .constant) return; // Constants are immortal.
826 log.debug(" (processing death of %{d})", .{inst});
798 // When editing this function, note that the logic must synchronize with `reuseOperand`.827 // When editing this function, note that the logic must synchronize with `reuseOperand`.
799 const prev_value = self.getResolvedInstValue(inst);828 const prev_value = self.getResolvedInstValue(inst);
800 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];829 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
...@@ -822,8 +851,10 @@ fn finishAirBookkeeping(self: *Self) void {...@@ -822,8 +851,10 @@ fn finishAirBookkeeping(self: *Self) void {
822}851}
823852
824fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Liveness.bpi - 1]Air.Inst.Ref) void {853fn 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 var tomb_bits = self.liveness.getTombBits(inst);855 var tomb_bits = self.liveness.getTombBits(inst);
826 for (operands) |op| {856 for (operands) |op| {
857 log.debug(" (processing {})", .{op});
827 const dies = @truncate(u1, tomb_bits) != 0;858 const dies = @truncate(u1, tomb_bits) != 0;
828 tomb_bits >>= 1;859 tomb_bits >>= 1;
829 if (!dies) continue;860 if (!dies) continue;
...@@ -834,7 +865,7 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live...@@ -834,7 +865,7 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live
834 }865 }
835 const is_used = @truncate(u1, tomb_bits) == 0;866 const is_used = @truncate(u1, tomb_bits) == 0;
836 if (is_used) {867 if (is_used) {
837 log.debug("%{d} => {}", .{ inst, result });868 log.debug(" (saving %{d} => {})", .{ inst, result });
838 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];869 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
839 branch.inst_table.putAssumeCapacityNoClobber(inst, result);870 branch.inst_table.putAssumeCapacityNoClobber(inst, result);
840871
...@@ -4647,6 +4678,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -4647,6 +4678,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
46474678
4648 const reloc = try self.genCondBrMir(cond_ty, cond);4679 const reloc = try self.genCondBrMir(cond_ty, cond);
46494680
4681 log.debug("airCondBr: %{d}", .{inst});
4682
4650 // If the condition dies here in this condbr instruction, process4683 // If the condition dies here in this condbr instruction, process
4651 // that death now instead of later as this has an effect on4684 // that death now instead of later as this has an effect on
4652 // whether it needs to be spilled in the branches4685 // whether it needs to be spilled in the branches
...@@ -4674,15 +4707,17 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -4674,15 +4707,17 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
46744707
4675 // Revert to the previous register and stack allocation state.4708 // Revert to the previous register and stack allocation state.
46764709
4677 var saved_then_branch = self.branch_stack.pop();4710 var then_branch = self.branch_stack.pop();
4678 defer saved_then_branch.deinit(self.gpa);4711 defer then_branch.deinit(self.gpa);
46794712
4680 self.revertState(saved_state);4713 self.revertState(saved_state);
46814714
4682 try self.performReloc(reloc);4715 try self.performReloc(reloc);
46834716
4684 const else_branch = self.branch_stack.addOneAssumeCapacity();4717 try self.branch_stack.append(.{});
4685 else_branch.* = .{};4718 errdefer {
4719 _ = self.branch_stack.pop();
4720 }
46864721
4687 try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len);4722 try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len);
4688 for (liveness_condbr.else_deaths) |operand| {4723 for (liveness_condbr.else_deaths) |operand| {
...@@ -4690,6 +4725,9 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -4690,6 +4725,9 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4690 }4725 }
4691 try self.genBody(else_body);4726 try self.genBody(else_body);
46924727
4728 var else_branch = self.branch_stack.pop();
4729 defer else_branch.deinit(self.gpa);
4730
4693 // At this point, each branch will possibly have conflicting values for where4731 // At this point, each branch will possibly have conflicting values for where
4694 // each instruction is stored. They agree, however, on which instructions are alive/dead.4732 // each instruction is stored. They agree, however, on which instructions are alive/dead.
4695 // We use the first ("then") branch as canonical, and here emit4733 // 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,15 +4736,23 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4698 // that we can use all the code emitting abstractions. This is why at the bottom we4736 // that we can use all the code emitting abstractions. This is why at the bottom we
4699 // assert that parent_branch.free_registers equals the saved_then_branch.free_registers4737 // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
4700 // rather than assigning it.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 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count());4740 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count());
47034741
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 const else_slice = else_branch.inst_table.entries.slice();4750 const else_slice = else_branch.inst_table.entries.slice();
4705 const else_keys = else_slice.items(.key);4751 const else_keys = else_slice.items(.key);
4706 const else_values = else_slice.items(.value);4752 const else_values = else_slice.items(.value);
4707 for (else_keys) |else_key, else_idx| {4753 for (else_keys) |else_key, else_idx| {
4708 const else_value = else_values[else_idx];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 // The instruction's MCValue is overridden in both branches.4756 // The instruction's MCValue is overridden in both branches.
4711 parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value);4757 parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value);
4712 if (else_value == .dead) {4758 if (else_value == .dead) {
...@@ -4718,7 +4764,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -4718,7 +4764,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4718 if (else_value == .dead)4764 if (else_value == .dead)
4719 continue;4765 continue;
4720 // The instruction is only overridden in the else branch.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 while (true) {4768 while (true) {
4723 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?4769 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
4724 if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| {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,8 +4779,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4733 try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value);4779 try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value);
4734 // TODO track the new register / stack allocation4780 // TODO track the new register / stack allocation
4735 }4781 }
4736 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count());4782 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, then_branch.inst_table.count());
4737 const then_slice = saved_then_branch.inst_table.entries.slice();4783 const then_slice = then_branch.inst_table.entries.slice();
4738 const then_keys = then_slice.items(.key);4784 const then_keys = then_slice.items(.key);
4739 const then_values = then_slice.items(.value);4785 const then_values = then_slice.items(.value);
4740 for (then_keys) |then_key, then_idx| {4786 for (then_keys) |then_key, then_idx| {
...@@ -4746,7 +4792,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -4746,7 +4792,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4746 if (then_value == .dead)4792 if (then_value == .dead)
4747 continue;4793 continue;
4748 const parent_mcv = blk: {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 while (true) {4797 while (true) {
4751 i -= 1;4798 i -= 1;
4752 if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| {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,11 +4809,6 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4762 // TODO track the new register / stack allocation4809 // TODO track the new register / stack allocation
4763 }4810 }
47644811
4765 {
4766 var item = self.branch_stack.pop();
4767 item.deinit(self.gpa);
4768 }
4769
4770 // We already took care of pl_op.operand earlier, so we're going4812 // We already took care of pl_op.operand earlier, so we're going
4771 // to pass .none here4813 // to pass .none here
4772 return self.finishAir(inst, .unreach, .{ .none, .none, .none });4814 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
...@@ -5139,6 +5181,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {...@@ -5139,6 +5181,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
5139 );5181 );
5140 defer self.gpa.free(liveness.deaths);5182 defer self.gpa.free(liveness.deaths);
51415183
5184 log.debug("airSwitch: %{d}", .{inst});
5185
5142 // If the condition dies here in this switch instruction, process5186 // If the condition dies here in this switch instruction, process
5143 // that death now instead of later as this has an effect on5187 // that death now instead of later as this has an effect on
5144 // whether it needs to be spilled in the branches5188 // whether it needs to be spilled in the branches
...@@ -5150,6 +5194,15 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {...@@ -5150,6 +5194,15 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
5150 }5194 }
5151 }5195 }
51525196
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 while (case_i < switch_br.data.cases_len) : (case_i += 1) {5206 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
5154 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);5207 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
5155 const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);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,10 +5232,9 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
51795232
5180 try self.genBody(case_body);5233 try self.genBody(case_body);
51815234
5182 // Revert to the previous register and stack allocation state.5235 branch_stack.appendAssumeCapacity(self.branch_stack.pop());
5183 var saved_case_branch = self.branch_stack.pop();
5184 defer saved_case_branch.deinit(self.gpa);
51855236
5237 // Revert to the previous register and stack allocation state.
5186 self.revertState(saved_state);5238 self.revertState(saved_state);
51875239
5188 for (relocs) |reloc| {5240 for (relocs) |reloc| {
...@@ -5192,10 +5244,13 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {...@@ -5192,10 +5244,13 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
51925244
5193 if (switch_br.data.else_body_len > 0) {5245 if (switch_br.data.else_body_len > 0) {
5194 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];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 try self.branch_stack.append(.{});5251 try self.branch_stack.append(.{});
5196 defer {5252 errdefer {
5197 var item = self.branch_stack.pop();5253 _ = self.branch_stack.pop();
5198 item.deinit(self.gpa);
5199 }5254 }
52005255
5201 const else_deaths = liveness.deaths.len - 1;5256 const else_deaths = liveness.deaths.len - 1;
...@@ -5206,8 +5261,29 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {...@@ -5206,8 +5261,29 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
52065261
5207 try self.genBody(else_body);5262 try self.genBody(else_body);
52085263
5209 // TODO consolidate returned MCValues between prongs and else branch like we do5264 branch_stack.appendAssumeCapacity(self.branch_stack.pop());
5210 // in airCondBr.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 }
52125288
5213 // We already took care of pl_op.operand earlier, so we're going5289 // 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,6 +5291,72 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
5215 return self.finishAir(inst, .unreach, .{ .none, .none, .none });5291 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
5216}5292}
52175293
5294fn 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
5218fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void {5360fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void {
5219 const next_inst = @intCast(u32, self.mir_instructions.len);5361 const next_inst = @intCast(u32, self.mir_instructions.len);
5220 switch (self.mir_instructions.items(.tag)[reloc]) {5362 switch (self.mir_instructions.items(.tag)[reloc]) {