authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-12 23:04:24-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-13 00:08:21-07:00
logb75a51f94b3b2ba1dc240a27caced8e848db5e10
tree870c2a015cb519f356a183b2be2f5bf6ce415246
parent8fe63d504226d5b181e627361e997a160dee4908

stage2: implement function calling convention for calls


1 files changed, 185 insertions(+), 132 deletions(-)

src-self-hosted/codegen.zig+185-132
...@@ -53,10 +53,8 @@ pub fn generateSymbol(...@@ -53,10 +53,8 @@ pub fn generateSymbol(
53 const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());53 const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());
54 defer bin_file.allocator.free(param_types);54 defer bin_file.allocator.free(param_types);
55 fn_type.fnParamTypes(param_types);55 fn_type.fnParamTypes(param_types);
56 // A parameter may be broken into multiple machine code parameters, so we don't56 var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len);
57 // know the size up front.57 defer bin_file.allocator.free(mc_args);
58 var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len);
59 defer mc_args.deinit();
6058
61 var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator);59 var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator);
62 defer {60 defer {
...@@ -67,57 +65,6 @@ pub fn generateSymbol(...@@ -67,57 +65,6 @@ pub fn generateSymbol(
67 const branch = try branch_stack.addOne();65 const branch = try branch_stack.addOne();
68 branch.* = .{};66 branch.* = .{};
6967
70 switch (fn_type.fnCallingConvention()) {
71 .Naked => assert(mc_args.items.len == 0),
72 .Unspecified, .C => {
73 // Prepare the function parameters
74 switch (bin_file.options.target.cpu.arch) {
75 .x86_64 => {
76 const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
77 var next_int_reg: usize = 0;
78
79 for (param_types) |param_type, src_i| {
80 switch (param_type.zigTypeTag()) {
81 .Bool, .Int => {
82 if (next_int_reg >= integer_registers.len) {
83 try mc_args.append(.{ .stack_offset = branch.next_stack_offset });
84 branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target));
85 } else {
86 try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) });
87 next_int_reg += 1;
88 }
89 },
90 else => return Result{
91 .fail = try ErrorMsg.create(
92 bin_file.allocator,
93 src,
94 "TODO implement function parameters of type {}",
95 .{@tagName(param_type.zigTypeTag())},
96 ),
97 },
98 }
99 }
100 },
101 else => return Result{
102 .fail = try ErrorMsg.create(
103 bin_file.allocator,
104 src,
105 "TODO implement function parameters for {}",
106 .{bin_file.options.target.cpu.arch},
107 ),
108 },
109 }
110 },
111 else => return Result{
112 .fail = try ErrorMsg.create(
113 bin_file.allocator,
114 src,
115 "TODO implement {} calling convention",
116 .{fn_type.fnCallingConvention()},
117 ),
118 },
119 }
120
121 var function = Function{68 var function = Function{
122 .gpa = bin_file.allocator,69 .gpa = bin_file.allocator,
123 .target = &bin_file.options.target,70 .target = &bin_file.options.target,
...@@ -125,11 +72,17 @@ pub fn generateSymbol(...@@ -125,11 +72,17 @@ pub fn generateSymbol(
125 .mod_fn = module_fn,72 .mod_fn = module_fn,
126 .code = code,73 .code = code,
127 .err_msg = null,74 .err_msg = null,
128 .args = mc_args.items,75 .args = mc_args,
129 .branch_stack = &branch_stack,76 .branch_stack = &branch_stack,
77 .src = src,
78 };
79
80 const cc = fn_type.fnCallingConvention();
81 branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) {
82 error.CodegenFail => return Result{ .fail = function.err_msg.? },
83 else => |e| return e,
130 };84 };
13185
132 branch.max_end_stack = branch.next_stack_offset;
133 function.gen() catch |err| switch (err) {86 function.gen() catch |err| switch (err) {
134 error.CodegenFail => return Result{ .fail = function.err_msg.? },87 error.CodegenFail => return Result{ .fail = function.err_msg.? },
135 else => |e| return e,88 else => |e| return e,
...@@ -235,6 +188,65 @@ const InnerError = error{...@@ -235,6 +188,65 @@ const InnerError = error{
235 CodegenFail,188 CodegenFail,
236};189};
237190
191const MCValue = union(enum) {
192 /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
193 none,
194 /// Control flow will not allow this value to be observed.
195 unreach,
196 /// No more references to this value remain.
197 dead,
198 /// A pointer-sized integer that fits in a register.
199 immediate: u64,
200 /// The constant was emitted into the code, at this offset.
201 embedded_in_code: usize,
202 /// The value is in a target-specific register. The value can
203 /// be @intToEnum casted to the respective Reg enum.
204 register: usize,
205 /// The value is in memory at a hard-coded address.
206 memory: u64,
207 /// The value is one of the stack variables.
208 stack_offset: u64,
209 /// The value is in the compare flags assuming an unsigned operation,
210 /// with this operator applied on top of it.
211 compare_flags_unsigned: std.math.CompareOperator,
212 /// The value is in the compare flags assuming a signed operation,
213 /// with this operator applied on top of it.
214 compare_flags_signed: std.math.CompareOperator,
215
216 fn isMemory(mcv: MCValue) bool {
217 return switch (mcv) {
218 .embedded_in_code, .memory, .stack_offset => true,
219 else => false,
220 };
221 }
222
223 fn isImmediate(mcv: MCValue) bool {
224 return switch (mcv) {
225 .immediate => true,
226 else => false,
227 };
228 }
229
230 fn isMutable(mcv: MCValue) bool {
231 return switch (mcv) {
232 .none => unreachable,
233 .unreach => unreachable,
234 .dead => unreachable,
235
236 .immediate,
237 .embedded_in_code,
238 .memory,
239 .compare_flags_unsigned,
240 .compare_flags_signed,
241 => false,
242
243 .register,
244 .stack_offset,
245 => true,
246 };
247 }
248};
249
238const Function = struct {250const Function = struct {
239 gpa: *Allocator,251 gpa: *Allocator,
240 bin_file: *link.File.Elf,252 bin_file: *link.File.Elf,
...@@ -243,6 +255,7 @@ const Function = struct {...@@ -243,6 +255,7 @@ const Function = struct {
243 code: *std.ArrayList(u8),255 code: *std.ArrayList(u8),
244 err_msg: ?*ErrorMsg,256 err_msg: ?*ErrorMsg,
245 args: []MCValue,257 args: []MCValue,
258 src: usize,
246259
247 /// Whenever there is a runtime branch, we push a Branch onto this stack,260 /// Whenever there is a runtime branch, we push a Branch onto this stack,
248 /// and pop it off when the runtime branch joins. This provides an "overlay"261 /// and pop it off when the runtime branch joins. This provides an "overlay"
...@@ -284,65 +297,6 @@ const Function = struct {...@@ -284,65 +297,6 @@ const Function = struct {
284 size: u32,297 size: u32,
285 };298 };
286299
287 const MCValue = union(enum) {
288 /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
289 none,
290 /// Control flow will not allow this value to be observed.
291 unreach,
292 /// No more references to this value remain.
293 dead,
294 /// A pointer-sized integer that fits in a register.
295 immediate: u64,
296 /// The constant was emitted into the code, at this offset.
297 embedded_in_code: usize,
298 /// The value is in a target-specific register. The value can
299 /// be @intToEnum casted to the respective Reg enum.
300 register: usize,
301 /// The value is in memory at a hard-coded address.
302 memory: u64,
303 /// The value is one of the stack variables.
304 stack_offset: u64,
305 /// The value is in the compare flags assuming an unsigned operation,
306 /// with this operator applied on top of it.
307 compare_flags_unsigned: std.math.CompareOperator,
308 /// The value is in the compare flags assuming a signed operation,
309 /// with this operator applied on top of it.
310 compare_flags_signed: std.math.CompareOperator,
311
312 fn isMemory(mcv: MCValue) bool {
313 return switch (mcv) {
314 .embedded_in_code, .memory, .stack_offset => true,
315 else => false,
316 };
317 }
318
319 fn isImmediate(mcv: MCValue) bool {
320 return switch (mcv) {
321 .immediate => true,
322 else => false,
323 };
324 }
325
326 fn isMutable(mcv: MCValue) bool {
327 return switch (mcv) {
328 .none => unreachable,
329 .unreach => unreachable,
330 .dead => unreachable,
331
332 .immediate,
333 .embedded_in_code,
334 .memory,
335 .compare_flags_unsigned,
336 .compare_flags_signed,
337 => false,
338
339 .register,
340 .stack_offset,
341 => true,
342 };
343 }
344 };
345
346 fn gen(self: *Function) !void {300 fn gen(self: *Function) !void {
347 switch (self.target.cpu.arch) {301 switch (self.target.cpu.arch) {
348 .arm => return self.genArch(.arm),302 .arm => return self.genArch(.arm),
...@@ -400,7 +354,28 @@ const Function = struct {...@@ -400,7 +354,28 @@ const Function = struct {
400 }354 }
401355
402 fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void {356 fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void {
403 return self.genBody(self.mod_fn.analysis.success, arch);357 try self.code.ensureCapacity(self.code.items.len + 11);
358
359 // push rbp
360 // mov rbp, rsp
361 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 });
362
363 // sub rsp, x
364 const stack_end = self.branch_stack.items[0].max_end_stack;
365 if (stack_end > std.math.maxInt(i32)) {
366 return self.fail(self.src, "too much stack used in call parameters", .{});
367 } else if (stack_end > std.math.maxInt(i8)) {
368 // 48 83 ec xx sub rsp,0x10
369 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec });
370 const x = @intCast(u32, stack_end);
371 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
372 } else if (stack_end != 0) {
373 // 48 81 ec xx xx xx xx sub rsp,0x80
374 const x = @intCast(u8, stack_end);
375 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x });
376 }
377
378 try self.genBody(self.mod_fn.analysis.success, arch);
404 }379 }
405380
406 fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void {381 fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void {
...@@ -593,13 +568,42 @@ const Function = struct {...@@ -593,13 +568,42 @@ const Function = struct {
593 }568 }
594569
595 fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue {570 fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue {
571 const fn_ty = inst.args.func.ty;
572 const cc = fn_ty.fnCallingConvention();
573 const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
574 defer self.gpa.free(param_types);
575 fn_ty.fnParamTypes(param_types);
576 var mc_args = try self.gpa.alloc(MCValue, param_types.len);
577 defer self.gpa.free(mc_args);
578 const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args);
579
596 switch (arch) {580 switch (arch) {
597 .x86_64, .i386 => {581 .x86_64 => {
598 if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {582 for (mc_args) |mc_arg, arg_i| {
599 if (inst.args.args.len != 0) {583 const arg = inst.args.args[arg_i];
600 return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});584 const arg_mcv = try self.resolveInst(inst.args.args[arg_i]);
585 switch (mc_arg) {
586 .none => continue,
587 .register => |reg| {
588 try self.genSetReg(arg.src, arch, @intToEnum(Reg(arch), @intCast(u8, reg)), arg_mcv);
589 // TODO interact with the register allocator to mark the instruction as moved.
590 },
591 .stack_offset => {
592 // Here we need to emit instructions like this:
593 // mov qword ptr [rsp + stack_offset], x
594 return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
595 },
596 .immediate => unreachable,
597 .unreach => unreachable,
598 .dead => unreachable,
599 .embedded_in_code => unreachable,
600 .memory => unreachable,
601 .compare_flags_signed => unreachable,
602 .compare_flags_unsigned => unreachable,
601 }603 }
604 }
602605
606 if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
603 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {607 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
604 const func = func_val.func;608 const func = func_val.func;
605 const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];609 const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
...@@ -607,17 +611,11 @@ const Function = struct {...@@ -607,17 +611,11 @@ const Function = struct {
607 const ptr_bytes: u64 = @divExact(ptr_bits, 8);611 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
608 const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);612 const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);
609 // ff 14 25 xx xx xx xx call [addr]613 // ff 14 25 xx xx xx xx call [addr]
610 try self.code.resize(self.code.items.len + 7);614 try self.code.ensureCapacity(self.code.items.len + 7);
611 self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 };615 self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
612 mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr);616 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr);
613 const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType();
614 switch (return_type.zigTypeTag()) {
615 .Void => return MCValue{ .none = {} },
616 .NoReturn => return MCValue{ .unreach = {} },
617 else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
618 }
619 } else {617 } else {
620 return self.fail(inst.base.src, "TODO implement calling weird function values", .{});618 return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
621 }619 }
622 } else {620 } else {
623 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});621 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
...@@ -625,6 +623,13 @@ const Function = struct {...@@ -625,6 +623,13 @@ const Function = struct {
625 },623 },
626 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),624 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
627 }625 }
626
627 const return_type = fn_ty.fnReturnType();
628 switch (return_type.zigTypeTag()) {
629 .Void => return MCValue{ .none = {} },
630 .NoReturn => return MCValue{ .unreach = {} },
631 else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
632 }
628 }633 }
629634
630 fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue {635 fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue {
...@@ -632,9 +637,15 @@ const Function = struct {...@@ -632,9 +637,15 @@ const Function = struct {
632 return self.fail(src, "TODO implement return with non-void operand", .{});637 return self.fail(src, "TODO implement return with non-void operand", .{});
633 }638 }
634 switch (arch) {639 switch (arch) {
635 .i386, .x86_64 => {640 .i386 => {
636 try self.code.append(0xc3); // ret641 try self.code.append(0xc3); // ret
637 },642 },
643 .x86_64 => {
644 try self.code.appendSlice(&[_]u8{
645 0x5d, // pop rbp
646 0xc3, // ret
647 });
648 },
638 else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),649 else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
639 }650 }
640 return .unreach;651 return .unreach;
...@@ -1122,6 +1133,48 @@ const Function = struct {...@@ -1122,6 +1133,48 @@ const Function = struct {
1122 }1133 }
1123 }1134 }
11241135
1136 fn resolveParameters(
1137 self: *Function,
1138 src: usize,
1139 cc: std.builtin.CallingConvention,
1140 param_types: []const Type,
1141 results: []MCValue,
1142 ) !u32 {
1143 switch (self.target.cpu.arch) {
1144 .x86_64 => {
1145 switch (cc) {
1146 .Naked => {
1147 assert(results.len == 0);
1148 return 0;
1149 },
1150 .Unspecified, .C => {
1151 var next_int_reg: usize = 0;
1152 var next_stack_offset: u32 = 0;
1153
1154 const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
1155 for (param_types) |ty, i| {
1156 switch (ty.zigTypeTag()) {
1157 .Bool, .Int => {
1158 if (next_int_reg >= integer_registers.len) {
1159 results[i] = .{ .stack_offset = next_stack_offset };
1160 next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
1161 } else {
1162 results[i] = .{ .register = @enumToInt(integer_registers[next_int_reg]) };
1163 next_int_reg += 1;
1164 }
1165 },
1166 else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}),
1167 }
1168 }
1169 return next_stack_offset;
1170 },
1171 else => return self.fail(src, "TODO implement function parameters for {}", .{cc}),
1172 }
1173 },
1174 else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}),
1175 }
1176 }
1177
1125 fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {1178 fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
1126 @setCold(true);1179 @setCold(true);
1127 assert(self.err_msg == null);1180 assert(self.err_msg == null);