authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-29 00:08:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-29 02:29:37-07:00
log1bbfa36b76271e907cac88e83cec8dee1e3d69f7
tree3cfa960bb8f95a5bb97afaac13e7c0e4e4dd33f1
parent64a1a280ef2b5858aa9d5ec659badf3e5236b5f9

stage2: improved codegen

* multiple returns jump to one canonical function exitlude. This is in preparation for the defer feature. * simple elision of trivial jump relocs. * omit prelude/exitlude for naked calling convention functions. * fix not switching on arch for prelude/exitlude * fix swapped registers when setting stack mem from a register

1 files changed, 90 insertions(+), 28 deletions(-)

src-self-hosted/codegen.zig+90-28
......@@ -214,6 +214,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
214214 src: usize,
215215 stack_align: u32,
216216
217 /// The value is an offset into the `Function` `code` from the beginning.
218 /// To perform the reloc, write 32-bit signed little-endian integer
219 /// which is a relative jump, based on the address following the reloc.
220 exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
221
217222 /// Whenever there is a runtime branch, we push a Branch onto this stack,
218223 /// and pop it off when the runtime branch joins. This provides an "overlay"
219224 /// of the table of mappings from instructions to `MCValue` from within the branch.
......@@ -376,6 +381,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
376381 .src = src,
377382 .stack_align = undefined,
378383 };
384 defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
379385
380386 var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
381387 error.CodegenFail => return Result{ .fail = function.err_msg.? },
......@@ -401,29 +407,78 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
401407 }
402408
403409 fn gen(self: *Self) !void {
404 try self.code.ensureCapacity(self.code.items.len + 11);
405
406 // TODO omit this for naked functions
407 // push rbp
408 // mov rbp, rsp
409 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 });
410
411 // sub rsp, x
412 const stack_end = self.branch_stack.items[0].max_end_stack;
413 if (stack_end > math.maxInt(i32)) {
414 return self.fail(self.src, "too much stack used in call parameters", .{});
415 } else if (stack_end > math.maxInt(i8)) {
416 // 48 83 ec xx sub rsp,0x10
417 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec });
418 const x = @intCast(u32, stack_end);
419 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
420 } else if (stack_end != 0) {
421 // 48 81 ec xx xx xx xx sub rsp,0x80
422 const x = @intCast(u8, stack_end);
423 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x });
424 }
410 switch (arch) {
411 .x86_64 => {
412 try self.code.ensureCapacity(self.code.items.len + 11);
413
414 const cc = self.fn_type.fnCallingConvention();
415 if (cc != .Naked) {
416 // We want to subtract the aligned stack frame size from rsp here, but we don't
417 // yet know how big it will be, so we leave room for a 4-byte stack size.
418 // TODO During semantic analysis, check if there are no function calls. If there
419 // are none, here we can omit the part where we subtract and then add rsp.
420 self.code.appendSliceAssumeCapacity(&[_]u8{
421 // push rbp
422 0x55,
423 // mov rbp, rsp
424 0x48,
425 0x89,
426 0xe5,
427 // sub rsp, imm32 (with reloc)
428 0x48,
429 0x81,
430 0xec,
431 });
432 const reloc_index = self.code.items.len;
433 self.code.items.len += 4;
434
435 try self.genBody(self.mod_fn.analysis.success);
436
437 const stack_end = self.branch_stack.items[0].max_end_stack;
438 if (stack_end > math.maxInt(i32))
439 return self.fail(self.src, "too much stack used in call parameters", .{});
440 const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
441 mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end));
442
443 if (self.code.items.len >= math.maxInt(i32)) {
444 return self.fail(self.src, "unable to perform relocation: jump too far", .{});
445 }
446 for (self.exitlude_jump_relocs.items) |jmp_reloc| {
447 const amt = self.code.items.len - (jmp_reloc + 4);
448 // If it wouldn't jump at all, elide it.
449 if (amt == 0) {
450 self.code.items.len -= 5;
451 continue;
452 }
453 const s32_amt = @intCast(i32, amt);
454 mem.writeIntLittle(i32, self.code.items[jmp_reloc..][0..4], s32_amt);
455 }
456
457 try self.code.ensureCapacity(self.code.items.len + 9);
458 // add rsp, x
459 if (aligned_stack_end > math.maxInt(i8)) {
460 // example: 48 81 c4 ff ff ff 7f add rsp,0x7fffffff
461 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xc4 });
462 const x = @intCast(u32, aligned_stack_end);
463 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
464 } else if (aligned_stack_end != 0) {
465 // example: 48 83 c4 7f add rsp,0x7f
466 const x = @intCast(u8, aligned_stack_end);
467 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xc4, x });
468 }
425469
426 try self.genBody(self.mod_fn.analysis.success);
470 self.code.appendSliceAssumeCapacity(&[_]u8{
471 0x5d, // pop rbp
472 0xc3, // ret
473 });
474 } else {
475 try self.genBody(self.mod_fn.analysis.success);
476 }
477 },
478 else => {
479 try self.genBody(self.mod_fn.analysis.success);
480 },
481 }
427482 }
428483
429484 fn genBody(self: *Self, body: ir.Body) InnerError!void {
......@@ -987,10 +1042,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
9871042 try self.code.append(0xc3); // ret
9881043 },
9891044 .x86_64 => {
990 try self.code.appendSlice(&[_]u8{
991 0x5d, // pop rbp
992 0xc3, // ret
993 });
1045 // TODO when implementing defer, this will need to jump to the appropriate defer expression.
1046 // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction
1047 // which is available if the jump is 127 bytes or less forward.
1048 try self.code.resize(self.code.items.len + 5);
1049 self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32
1050 try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4);
9941051 },
9951052 else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
9961053 }
......@@ -1130,6 +1187,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
11301187 switch (reloc) {
11311188 .rel32 => |pos| {
11321189 const amt = self.code.items.len - (pos + 4);
1190 // If it wouldn't jump at all, elide it.
1191 if (amt == 0) {
1192 self.code.items.len -= 5;
1193 return;
1194 }
11331195 const s32_amt = math.cast(i32, amt) catch
11341196 return self.fail(src, "unable to perform relocation: jump too far", .{});
11351197 mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
......@@ -1296,13 +1358,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
12961358 const reg_id: u8 = @truncate(u3, reg.id());
12971359 if (stack_offset <= 128) {
12981360 // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx
1299 const RM = @as(u8, 0b01_101_000) | reg_id;
1361 const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
13001362 const negative_offset = @intCast(i8, -@intCast(i32, stack_offset));
13011363 const twos_comp = @bitCast(u8, negative_offset);
13021364 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp });
13031365 } else if (stack_offset <= 2147483648) {
13041366 // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx
1305 const RM = @as(u8, 0b10_101_000) | reg_id;
1367 const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
13061368 const negative_offset = @intCast(i32, -@intCast(i33, stack_offset));
13071369 const twos_comp = @bitCast(u32, negative_offset);
13081370 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM });