| ... | ... | @@ -53,10 +53,8 @@ pub fn generateSymbol( |
| 53 | 53 | const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen()); |
| 54 | 54 | defer bin_file.allocator.free(param_types); |
| 55 | 55 | fn_type.fnParamTypes(param_types); |
| 56 | | // A parameter may be broken into multiple machine code parameters, so we don't |
| 57 | | // know the size up front. |
| 58 | | var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len); |
| 59 | | defer mc_args.deinit(); |
| 56 | var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len); |
| 57 | defer bin_file.allocator.free(mc_args); |
| 60 | 58 | |
| 61 | 59 | var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator); |
| 62 | 60 | defer { |
| ... | ... | @@ -67,57 +65,6 @@ pub fn generateSymbol( |
| 67 | 65 | const branch = try branch_stack.addOne(); |
| 68 | 66 | branch.* = .{}; |
| 69 | 67 | |
| 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 | 68 | var function = Function{ |
| 122 | 69 | .gpa = bin_file.allocator, |
| 123 | 70 | .target = &bin_file.options.target, |
| ... | ... | @@ -125,11 +72,17 @@ pub fn generateSymbol( |
| 125 | 72 | .mod_fn = module_fn, |
| 126 | 73 | .code = code, |
| 127 | 74 | .err_msg = null, |
| 128 | | .args = mc_args.items, |
| 75 | .args = mc_args, |
| 129 | 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 | }; |
| 131 | 85 | |
| 132 | | branch.max_end_stack = branch.next_stack_offset; |
| 133 | 86 | function.gen() catch |err| switch (err) { |
| 134 | 87 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| 135 | 88 | else => |e| return e, |
| ... | ... | @@ -235,6 +188,65 @@ const InnerError = error{ |
| 235 | 188 | CodegenFail, |
| 236 | 189 | }; |
| 237 | 190 | |
| 191 | const 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 | |
| 238 | 250 | const Function = struct { |
| 239 | 251 | gpa: *Allocator, |
| 240 | 252 | bin_file: *link.File.Elf, |
| ... | ... | @@ -243,6 +255,7 @@ const Function = struct { |
| 243 | 255 | code: *std.ArrayList(u8), |
| 244 | 256 | err_msg: ?*ErrorMsg, |
| 245 | 257 | args: []MCValue, |
| 258 | src: usize, |
| 246 | 259 | |
| 247 | 260 | /// Whenever there is a runtime branch, we push a Branch onto this stack, |
| 248 | 261 | /// and pop it off when the runtime branch joins. This provides an "overlay" |
| ... | ... | @@ -284,65 +297,6 @@ const Function = struct { |
| 284 | 297 | size: u32, |
| 285 | 298 | }; |
| 286 | 299 | |
| 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 | 300 | fn gen(self: *Function) !void { |
| 347 | 301 | switch (self.target.cpu.arch) { |
| 348 | 302 | .arm => return self.genArch(.arm), |
| ... | ... | @@ -400,7 +354,28 @@ const Function = struct { |
| 400 | 354 | } |
| 401 | 355 | |
| 402 | 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 | } |
| 405 | 380 | |
| 406 | 381 | fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void { |
| ... | ... | @@ -593,13 +568,42 @@ const Function = struct { |
| 593 | 568 | } |
| 594 | 569 | |
| 595 | 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 | 580 | switch (arch) { |
| 597 | | .x86_64, .i386 => { |
| 598 | | if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { |
| 599 | | if (inst.args.args.len != 0) { |
| 600 | | return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); |
| 581 | .x86_64 => { |
| 582 | for (mc_args) |mc_arg, arg_i| { |
| 583 | const arg = inst.args.args[arg_i]; |
| 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 | } |
| 602 | 605 | |
| 606 | if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { |
| 603 | 607 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { |
| 604 | 608 | const func = func_val.func; |
| 605 | 609 | const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?]; |
| ... | ... | @@ -607,17 +611,11 @@ const Function = struct { |
| 607 | 611 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 608 | 612 | const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes); |
| 609 | 613 | // ff 14 25 xx xx xx xx call [addr] |
| 610 | | try self.code.resize(self.code.items.len + 7); |
| 611 | | self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 }; |
| 612 | | mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..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 | | } |
| 614 | try self.code.ensureCapacity(self.code.items.len + 7); |
| 615 | self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); |
| 616 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr); |
| 619 | 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 | 620 | } else { |
| 623 | 621 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| ... | ... | @@ -625,6 +623,13 @@ const Function = struct { |
| 625 | 623 | }, |
| 626 | 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 | } |
| 629 | 634 | |
| 630 | 635 | fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue { |
| ... | ... | @@ -632,9 +637,15 @@ const Function = struct { |
| 632 | 637 | return self.fail(src, "TODO implement return with non-void operand", .{}); |
| 633 | 638 | } |
| 634 | 639 | switch (arch) { |
| 635 | | .i386, .x86_64 => { |
| 640 | .i386 => { |
| 636 | 641 | 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 | 649 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 639 | 650 | } |
| 640 | 651 | return .unreach; |
| ... | ... | @@ -1122,6 +1133,48 @@ const Function = struct { |
| 1122 | 1133 | } |
| 1123 | 1134 | } |
| 1124 | 1135 | |
| 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 | 1178 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { |
| 1126 | 1179 | @setCold(true); |
| 1127 | 1180 | assert(self.err_msg == null); |