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 {
200200 self.inst_table.deinit(gpa);
201201 self.* = undefined;
202202 }
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 }
203231};
204232
205233const StackAllocation = struct {
......@@ -232,7 +260,7 @@ const BigTomb = struct {
232260 fn finishAir(bt: *BigTomb, result: MCValue) void {
233261 const is_used = !bt.function.liveness.isUnused(bt.inst);
234262 if (is_used) {
235 log.debug("%{d} => {}", .{ bt.inst, result });
263 log.debug(" (saving %{d} => {})", .{ bt.inst, result });
236264 const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1];
237265 branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result);
238266 }
......@@ -795,6 +823,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
795823fn processDeath(self: *Self, inst: Air.Inst.Index) void {
796824 const air_tags = self.air.instructions.items(.tag);
797825 if (air_tags[inst] == .constant) return; // Constants are immortal.
826 log.debug(" (processing death of %{d})", .{inst});
798827 // When editing this function, note that the logic must synchronize with `reuseOperand`.
799828 const prev_value = self.getResolvedInstValue(inst);
800829 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
......@@ -822,8 +851,10 @@ fn finishAirBookkeeping(self: *Self) void {
822851}
823852
824853fn 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 });
825855 var tomb_bits = self.liveness.getTombBits(inst);
826856 for (operands) |op| {
857 log.debug(" (processing {})", .{op});
827858 const dies = @truncate(u1, tomb_bits) != 0;
828859 tomb_bits >>= 1;
829860 if (!dies) continue;
......@@ -834,7 +865,7 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live
834865 }
835866 const is_used = @truncate(u1, tomb_bits) == 0;
836867 if (is_used) {
837 log.debug("%{d} => {}", .{ inst, result });
868 log.debug(" (saving %{d} => {})", .{ inst, result });
838869 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
839870 branch.inst_table.putAssumeCapacityNoClobber(inst, result);
840871
......@@ -4647,6 +4678,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
46474678
46484679 const reloc = try self.genCondBrMir(cond_ty, cond);
46494680
4681 log.debug("airCondBr: %{d}", .{inst});
4682
46504683 // If the condition dies here in this condbr instruction, process
46514684 // that death now instead of later as this has an effect on
46524685 // whether it needs to be spilled in the branches
......@@ -4674,15 +4707,17 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
46744707
46754708 // Revert to the previous register and stack allocation state.
46764709
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);
46794712
46804713 self.revertState(saved_state);
46814714
46824715 try self.performReloc(reloc);
46834716
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 }
46864721
46874722 try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len);
46884723 for (liveness_condbr.else_deaths) |operand| {
......@@ -4690,6 +4725,9 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
46904725 }
46914726 try self.genBody(else_body);
46924727
4728 var else_branch = self.branch_stack.pop();
4729 defer else_branch.deinit(self.gpa);
4730
46934731 // At this point, each branch will possibly have conflicting values for where
46944732 // each instruction is stored. They agree, however, on which instructions are alive/dead.
46954733 // We use the first ("then") branch as canonical, and here emit
......@@ -4698,15 +4736,23 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
46984736 // that we can use all the code emitting abstractions. This is why at the bottom we
46994737 // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
47004738 // 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];
47024740 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
47044750 const else_slice = else_branch.inst_table.entries.slice();
47054751 const else_keys = else_slice.items(.key);
47064752 const else_values = else_slice.items(.value);
47074753 for (else_keys) |else_key, else_idx| {
47084754 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: {
47104756 // The instruction's MCValue is overridden in both branches.
47114757 parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value);
47124758 if (else_value == .dead) {
......@@ -4718,7 +4764,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
47184764 if (else_value == .dead)
47194765 continue;
47204766 // 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;
47224768 while (true) {
47234769 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
47244770 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 {
47334779 try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value);
47344780 // TODO track the new register / stack allocation
47354781 }
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();
47384784 const then_keys = then_slice.items(.key);
47394785 const then_values = then_slice.items(.value);
47404786 for (then_keys) |then_key, then_idx| {
......@@ -4746,7 +4792,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
47464792 if (then_value == .dead)
47474793 continue;
47484794 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;
47504797 while (true) {
47514798 i -= 1;
47524799 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 {
47624809 // TODO track the new register / stack allocation
47634810 }
47644811
4765 {
4766 var item = self.branch_stack.pop();
4767 item.deinit(self.gpa);
4768 }
4769
47704812 // We already took care of pl_op.operand earlier, so we're going
47714813 // to pass .none here
47724814 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
......@@ -5139,6 +5181,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
51395181 );
51405182 defer self.gpa.free(liveness.deaths);
51415183
5184 log.debug("airSwitch: %{d}", .{inst});
5185
51425186 // If the condition dies here in this switch instruction, process
51435187 // that death now instead of later as this has an effect on
51445188 // whether it needs to be spilled in the branches
......@@ -5150,6 +5194,15 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
51505194 }
51515195 }
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
51535206 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
51545207 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
51555208 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 {
51795232
51805233 try self.genBody(case_body);
51815234
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());
51855236
5237 // Revert to the previous register and stack allocation state.
51865238 self.revertState(saved_state);
51875239
51885240 for (relocs) |reloc| {
......@@ -5192,10 +5244,13 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
51925244
51935245 if (switch_br.data.else_body_len > 0) {
51945246 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
51955251 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();
51995254 }
52005255
52015256 const else_deaths = liveness.deaths.len - 1;
......@@ -5206,8 +5261,29 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
52065261
52075262 try self.genBody(else_body);
52085263
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);
52115287 }
52125288
52135289 // 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 {
52155291 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
52165292}
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
52185360fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void {
52195361 const next_inst = @intCast(u32, self.mir_instructions.len);
52205362 switch (self.mir_instructions.items(.tag)[reloc]) {