authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-01-21 16:04:26+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-01-22 00:46:35+01:00
log4a5e75245bc60f76a82bb6bcb70553bec7d7c4f2
tree82750d896591ef9737c8e86ec0d8f847d026b901
parentc9ae24503dc8da2e59f46619695bf4eb863fb3ac

stage2: clean up preserving callee regs on the stack

Instead of using `push` and `pop` combo, we now re-use our stack allocation mechanism which means we don't have to worry about 16-byte stack adjustments on macOS as it is handled automatically for us. Another benefit is that we don't have to backpatch stack offsets when pulling args from the stack.

4 files changed, 90 insertions(+), 95 deletions(-)

src/arch/x86_64/CodeGen.zig+46-58
...@@ -61,8 +61,6 @@ end_di_column: u32,...@@ -61,8 +61,6 @@ end_di_column: u32,
61/// which is a relative jump, based on the address following the reloc.61/// which is a relative jump, based on the address following the reloc.
62exitlude_jump_relocs: std.ArrayListUnmanaged(Mir.Inst.Index) = .{},62exitlude_jump_relocs: std.ArrayListUnmanaged(Mir.Inst.Index) = .{},
6363
64stack_args_relocs: std.ArrayListUnmanaged(Mir.Inst.Index) = .{},
65
66/// Whenever there is a runtime branch, we push a Branch onto this stack,64/// Whenever there is a runtime branch, we push a Branch onto this stack,
67/// and pop it off when the runtime branch joins. This provides an "overlay"65/// and pop it off when the runtime branch joins. This provides an "overlay"
68/// of the table of mappings from instructions to `MCValue` from within the branch.66/// of the table of mappings from instructions to `MCValue` from within the branch.
...@@ -286,7 +284,6 @@ pub fn generate(...@@ -286,7 +284,6 @@ pub fn generate(
286 defer function.exitlude_jump_relocs.deinit(bin_file.allocator);284 defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
287 defer function.mir_instructions.deinit(bin_file.allocator);285 defer function.mir_instructions.deinit(bin_file.allocator);
288 defer function.mir_extra.deinit(bin_file.allocator);286 defer function.mir_extra.deinit(bin_file.allocator);
289 defer function.stack_args_relocs.deinit(bin_file.allocator);
290 defer if (builtin.mode == .Debug) function.mir_to_air_map.deinit();287 defer if (builtin.mode == .Debug) function.mir_to_air_map.deinit();
291288
292 var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {289 var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
...@@ -378,13 +375,6 @@ pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 {...@@ -378,13 +375,6 @@ pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 {
378fn gen(self: *Self) InnerError!void {375fn gen(self: *Self) InnerError!void {
379 const cc = self.fn_type.fnCallingConvention();376 const cc = self.fn_type.fnCallingConvention();
380 if (cc != .Naked) {377 if (cc != .Naked) {
381 // push the callee_preserved_regs that were used
382 const backpatch_push_callee_preserved_regs_i = try self.addInst(.{
383 .tag = .push_regs_from_callee_preserved_regs,
384 .ops = undefined,
385 .data = .{ .regs_to_push_or_pop = undefined }, // to be backpatched
386 });
387
388 _ = try self.addInst(.{378 _ = try self.addInst(.{
389 .tag = .push,379 .tag = .push,
390 .ops = (Mir.Ops{380 .ops = (Mir.Ops{
...@@ -416,6 +406,15 @@ fn gen(self: *Self) InnerError!void {...@@ -416,6 +406,15 @@ fn gen(self: *Self) InnerError!void {
416 .data = undefined,406 .data = undefined,
417 });407 });
418408
409 // push the callee_preserved_regs that were used
410 const backpatch_push_callee_preserved_regs_i = try self.addInst(.{
411 .tag = .push_regs_from_callee_preserved_regs,
412 .ops = (Mir.Ops{
413 .reg1 = .rbp,
414 }).encode(),
415 .data = .{ .payload = undefined }, // to be backpatched
416 });
417
419 try self.genBody(self.air.getMainBody());418 try self.genBody(self.air.getMainBody());
420419
421 // TODO can single exitlude jump reloc be elided? What if it is not at the end of the code?420 // TODO can single exitlude jump reloc be elided? What if it is not at the end of the code?
...@@ -429,6 +428,33 @@ fn gen(self: *Self) InnerError!void {...@@ -429,6 +428,33 @@ fn gen(self: *Self) InnerError!void {
429 self.mir_instructions.items(.data)[jmp_reloc].inst = @intCast(u32, self.mir_instructions.len);428 self.mir_instructions.items(.data)[jmp_reloc].inst = @intCast(u32, self.mir_instructions.len);
430 }429 }
431430
431 // calculate the data for callee_preserved_regs to be pushed and popped
432 const callee_preserved_regs_payload = blk: {
433 var data = Mir.RegsToPushOrPop{
434 .regs = 0,
435 .disp = mem.alignForwardGeneric(u32, self.next_stack_offset, 8),
436 };
437 inline for (callee_preserved_regs) |reg, i| {
438 if (self.register_manager.isRegAllocated(reg)) {
439 data.regs |= 1 << @intCast(u5, i);
440 self.max_end_stack += 8;
441 }
442 }
443 break :blk try self.addExtra(data);
444 };
445
446 const data = self.mir_instructions.items(.data);
447 // backpatch the push instruction
448 data[backpatch_push_callee_preserved_regs_i].payload = callee_preserved_regs_payload;
449 // pop the callee_preserved_regs
450 _ = try self.addInst(.{
451 .tag = .pop_regs_from_callee_preserved_regs,
452 .ops = (Mir.Ops{
453 .reg1 = .rbp,
454 }).encode(),
455 .data = .{ .payload = callee_preserved_regs_payload },
456 });
457
432 _ = try self.addInst(.{458 _ = try self.addInst(.{
433 .tag = .dbg_epilogue_begin,459 .tag = .dbg_epilogue_begin,
434 .ops = undefined,460 .ops = undefined,
...@@ -450,34 +476,6 @@ fn gen(self: *Self) InnerError!void {...@@ -450,34 +476,6 @@ fn gen(self: *Self) InnerError!void {
450 .data = undefined,476 .data = undefined,
451 });477 });
452478
453 // calculate the data for callee_preserved_regs to be pushed and popped
454 var callee_preserved_regs_push_data: u32 = 0x0;
455 // TODO this is required on macOS since macOS actively checks for stack alignment
456 // at every extern call site. As far as I can tell, macOS accounts for the typical
457 // function prologue first 2 instructions of:
458 // ...
459 // push rbp
460 // mov rsp, rbp
461 // ...
462 // Thus we don't need to adjust the stack for the first push instruction. However,
463 // any subsequent push of values on the stack such as when preserving registers,
464 // needs to be taken into account here.
465 var stack_adjustment: u32 = 0;
466 inline for (callee_preserved_regs) |reg, i| {
467 if (self.register_manager.isRegAllocated(reg)) {
468 callee_preserved_regs_push_data |= 1 << @intCast(u5, i);
469 stack_adjustment += @divExact(reg.size(), 8);
470 }
471 }
472 const data = self.mir_instructions.items(.data);
473 // backpatch the push instruction
474 data[backpatch_push_callee_preserved_regs_i].regs_to_push_or_pop = callee_preserved_regs_push_data;
475 // pop the callee_preserved_regs
476 _ = try self.addInst(.{
477 .tag = .pop_regs_from_callee_preserved_regs,
478 .ops = undefined,
479 .data = .{ .regs_to_push_or_pop = callee_preserved_regs_push_data },
480 });
481 _ = try self.addInst(.{479 _ = try self.addInst(.{
482 .tag = .ret,480 .tag = .ret,
483 .ops = (Mir.Ops{481 .ops = (Mir.Ops{
...@@ -488,36 +486,28 @@ fn gen(self: *Self) InnerError!void {...@@ -488,36 +486,28 @@ fn gen(self: *Self) InnerError!void {
488486
489 // Adjust the stack487 // Adjust the stack
490 const stack_end = self.max_end_stack;488 const stack_end = self.max_end_stack;
491 if (stack_end > math.maxInt(i32) - stack_adjustment) {489 if (stack_end > math.maxInt(i32)) {
492 return self.failSymbol("too much stack used in call parameters", .{});490 return self.failSymbol("too much stack used in call parameters", .{});
493 }491 }
494 // TODO we should reuse this mechanism to align the stack when calling any function even if492 // TODO we should reuse this mechanism to align the stack when calling any function even if
495 // we do not pass any args on the stack BUT we still push regs to stack with `push` inst.493 // we do not pass any args on the stack BUT we still push regs to stack with `push` inst.
496 const aligned_stack_end = @intCast(u32, mem.alignForward(stack_end, self.stack_align));494 const aligned_stack_end = @intCast(u32, mem.alignForward(stack_end, self.stack_align));
497 if (aligned_stack_end > 0 or (stack_adjustment > 0 and self.target.isDarwin())) {495 if (aligned_stack_end > 0) {
498 const imm = if (self.target.isDarwin()) aligned_stack_end + stack_adjustment else aligned_stack_end;
499 self.mir_instructions.set(backpatch_stack_sub, .{496 self.mir_instructions.set(backpatch_stack_sub, .{
500 .tag = .sub,497 .tag = .sub,
501 .ops = (Mir.Ops{498 .ops = (Mir.Ops{
502 .reg1 = .rsp,499 .reg1 = .rsp,
503 }).encode(),500 }).encode(),
504 .data = .{ .imm = imm },501 .data = .{ .imm = aligned_stack_end },
505 });502 });
506 self.mir_instructions.set(backpatch_stack_add, .{503 self.mir_instructions.set(backpatch_stack_add, .{
507 .tag = .add,504 .tag = .add,
508 .ops = (Mir.Ops{505 .ops = (Mir.Ops{
509 .reg1 = .rsp,506 .reg1 = .rsp,
510 }).encode(),507 }).encode(),
511 .data = .{ .imm = imm },508 .data = .{ .imm = aligned_stack_end },
512 });509 });
513 }510 }
514 while (self.stack_args_relocs.popOrNull()) |index| {
515 // TODO like above, gotta figure out the alignment shenanigans for macOS, etc.
516 const adjustment = if (self.target.isDarwin()) 2 * stack_adjustment else stack_adjustment;
517 // +16 bytes to account for saved return address of the `call` instruction and
518 // `push rbp`.
519 self.mir_instructions.items(.data)[index].imm += adjustment + aligned_stack_end + 16;
520 }
521 } else {511 } else {
522 _ = try self.addInst(.{512 _ = try self.addInst(.{
523 .tag = .dbg_prologue_end,513 .tag = .dbg_prologue_end,
...@@ -2194,16 +2184,15 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {...@@ -2194,16 +2184,15 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
21942184
2195 if (abi_size <= 8) {2185 if (abi_size <= 8) {
2196 const reg = try self.register_manager.allocReg(inst, &.{});2186 const reg = try self.register_manager.allocReg(inst, &.{});
2197 const reloc = try self.addInst(.{2187 _ = try self.addInst(.{
2198 .tag = .mov,2188 .tag = .mov,
2199 .ops = (Mir.Ops{2189 .ops = (Mir.Ops{
2200 .reg1 = registerAlias(reg, @intCast(u32, abi_size)),2190 .reg1 = registerAlias(reg, @intCast(u32, abi_size)),
2201 .reg2 = .rsp,2191 .reg2 = .rbp,
2202 .flags = 0b01,2192 .flags = 0b01,
2203 }).encode(),2193 }).encode(),
2204 .data = .{ .imm = off },2194 .data = .{ .imm = off + 16 },
2205 });2195 });
2206 try self.stack_args_relocs.append(self.bin_file.allocator, reloc);
2207 break :blk .{ .register = reg };2196 break :blk .{ .register = reg };
2208 }2197 }
22092198
...@@ -2217,15 +2206,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {...@@ -2217,15 +2206,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
2217 try self.register_manager.getReg(.rax, null);2206 try self.register_manager.getReg(.rax, null);
2218 try self.register_manager.getReg(.rcx, null);2207 try self.register_manager.getReg(.rcx, null);
22192208
2220 const reloc = try self.addInst(.{2209 _ = try self.addInst(.{
2221 .tag = .lea,2210 .tag = .lea,
2222 .ops = (Mir.Ops{2211 .ops = (Mir.Ops{
2223 .reg1 = addr_reg.to64(),2212 .reg1 = addr_reg.to64(),
2224 .reg2 = .rsp,2213 .reg2 = .rbp,
2225 }).encode(),2214 }).encode(),
2226 .data = .{ .imm = off },2215 .data = .{ .imm = off + 16 },
2227 });2216 });
2228 try self.stack_args_relocs.append(self.bin_file.allocator, reloc);
22292217
2230 // TODO allow for abi_size to be u642218 // TODO allow for abi_size to be u64
2231 try self.genSetReg(Type.initTag(.u32), count_reg, .{ .immediate = @intCast(u32, abi_size) });2219 try self.genSetReg(Type.initTag(.u32), count_reg, .{ .immediate = @intCast(u32, abi_size) });
src/arch/x86_64/Emit.zig+18-16
...@@ -251,23 +251,25 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {...@@ -251,23 +251,25 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
251 }251 }
252}252}
253fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {253fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
254 const callee_preserved_regs = bits.callee_preserved_regs;254 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);
255 const regs = emit.mir.instructions.items(.data)[inst].regs_to_push_or_pop;255 const payload = emit.mir.instructions.items(.data)[inst].payload;
256 if (tag == .push) {256 const data = emit.mir.extraData(Mir.RegsToPushOrPop, payload).data;
257 for (callee_preserved_regs) |reg, i| {257 const regs = data.regs;
258 if ((regs >> @intCast(u5, i)) & 1 == 0) continue;258 var disp: u32 = data.disp + 8;
259 lowerToOEnc(.push, reg, emit.code) catch |err|259 for (bits.callee_preserved_regs) |reg, i| {
260 return emit.failWithLoweringError(err);260 if ((regs >> @intCast(u5, i)) & 1 == 0) continue;
261 }261 if (tag == .push) {
262 } else {262 lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{
263 // pop in the reverse direction263 .disp = @bitCast(u32, -@intCast(i32, disp)),
264 var i = callee_preserved_regs.len;264 .base = ops.reg1,
265 while (i > 0) : (i -= 1) {265 }), reg.to64(), emit.code) catch |err| return emit.failWithLoweringError(err);
266 const reg = callee_preserved_regs[i - 1];266 } else {
267 if ((regs >> @intCast(u5, i - 1)) & 1 == 0) continue;267 lowerToRmEnc(.mov, reg.to64(), RegisterOrMemory.mem(.qword_ptr, .{
268 lowerToOEnc(.pop, reg, emit.code) catch |err|268 .disp = @bitCast(u32, -@intCast(i32, disp)),
269 return emit.failWithLoweringError(err);269 .base = ops.reg1,
270 }), emit.code) catch |err| return emit.failWithLoweringError(err);
270 }271 }
272 disp += 8;
271 }273 }
272}274}
273275
src/arch/x86_64/Mir.zig+5-2
...@@ -333,8 +333,6 @@ pub const Inst = struct {...@@ -333,8 +333,6 @@ pub const Inst = struct {
333 got_entry: u32,333 got_entry: u32,
334 /// Index into `extra`. Meaning of what can be found there is context-dependent.334 /// Index into `extra`. Meaning of what can be found there is context-dependent.
335 payload: u32,335 payload: u32,
336 /// A bitfield of which callee_preserved_regs to push
337 regs_to_push_or_pop: u32,
338 };336 };
339337
340 // Make sure we don't accidentally make instructions bigger than expected.338 // Make sure we don't accidentally make instructions bigger than expected.
...@@ -346,6 +344,11 @@ pub const Inst = struct {...@@ -346,6 +344,11 @@ pub const Inst = struct {
346 }344 }
347};345};
348346
347pub const RegsToPushOrPop = struct {
348 regs: u32,
349 disp: u32,
350};
351
349pub const ImmPair = struct {352pub const ImmPair = struct {
350 dest_off: u32,353 dest_off: u32,
351 operand: u32,354 operand: u32,
src/arch/x86_64/PrintMir.zig+21-19
...@@ -180,26 +180,28 @@ fn mirPushPop(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: a...@@ -180,26 +180,28 @@ fn mirPushPop(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: a
180 try w.writeByte('\n');180 try w.writeByte('\n');
181}181}
182fn mirPushPopRegsFromCalleePreservedRegs(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: anytype) !void {182fn mirPushPopRegsFromCalleePreservedRegs(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: anytype) !void {
183 const callee_preserved_regs = bits.callee_preserved_regs;183 const ops = Mir.Ops.decode(print.mir.instructions.items(.ops)[inst]);
184 // PUSH/POP reg184 const payload = print.mir.instructions.items(.data)[inst].payload;
185185 const data = print.mir.extraData(Mir.RegsToPushOrPop, payload).data;
186 const regs = print.mir.instructions.items(.data)[inst].regs_to_push_or_pop;186 const regs = data.regs;
187 if (regs == 0) return w.writeAll("push/pop no regs from callee_preserved_regs\n");187 var disp: u32 = data.disp + 8;
188 if (tag == .push) {188 if (regs == 0) return w.writeAll("no regs from callee_preserved_regs\n");
189 try w.writeAll("push ");189 for (bits.callee_preserved_regs) |reg, i| {
190 for (callee_preserved_regs) |reg, i| {190 if ((regs >> @intCast(u5, i)) & 1 == 0) continue;
191 if ((regs >> @intCast(u5, i)) & 1 == 0) continue;191 if (tag == .push) {
192 try w.print("{s}, ", .{@tagName(reg)});192 try w.print("mov qword ptr [{s} + {d}], {s}", .{
193 }193 @tagName(ops.reg1),
194 } else {194 @bitCast(u32, -@intCast(i32, disp)),
195 // pop in the reverse direction195 @tagName(reg.to64()),
196 var i = callee_preserved_regs.len;196 });
197 try w.writeAll("pop ");197 } else {
198 while (i > 0) : (i -= 1) {198 try w.print("mov {s}, qword ptr [{s} + {d}]", .{
199 if ((regs >> @intCast(u5, i - 1)) & 1 == 0) continue;199 @tagName(reg.to64()),
200 const reg = callee_preserved_regs[i - 1];200 @tagName(ops.reg1),
201 try w.print("{s}, ", .{@tagName(reg)});201 @bitCast(u32, -@intCast(i32, disp)),
202 });
202 }203 }
204 disp += 8;
203 }205 }
204 try w.writeByte('\n');206 try w.writeByte('\n');
205}207}