const std = @import("std"); const mem = std.mem; const assert = std.debug.assert; const ir = @import("ir.zig"); const Type = @import("type.zig").Type; const Value = @import("value.zig").Value; const Target = std.Target; pub fn generateSymbol( typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8), errors: *std.ArrayList(ir.ErrorMsg), ) !void { switch (typed_value.ty.zigTypeTag()) { .Fn => { const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; var function = Function{ .module = &module, .mod_fn = module_fn, .code = code, .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator), .errors = errors, }; defer function.inst_table.deinit(); for (module_fn.body.instructions) |inst| { const new_inst = function.genFuncInst(inst) catch |err| switch (err) { error.CodegenFail => { assert(function.errors.items.len != 0); break; }, else => |e| return e, }; try function.inst_table.putNoClobber(inst, new_inst); } return Symbol{ .errors = function.errors.toOwnedSlice() }; }, else => @panic("TODO implement generateSymbol for non-function decls"), } } const Function = struct { module: *const ir.Module, mod_fn: *const ir.Module.Fn, code: *std.ArrayList(u8), inst_table: std.AutoHashMap(*ir.Inst, MCValue), errors: *std.ArrayList(ir.ErrorMsg), const MCValue = union(enum) { none, unreach, /// A pointer-sized integer that fits in a register. immediate: u64, /// The constant was emitted into the code, at this offset. embedded_in_code: usize, /// The value is in a target-specific register. The value can /// be @intToEnum casted to the respective Reg enum. register: usize, }; fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { switch (inst.tag) { .breakpoint => return self.genBreakpoint(inst.src), .call => return self.genCall(inst.cast(ir.Inst.Call).?), .unreach => return MCValue{ .unreach = {} }, .constant => unreachable, // excluded from function bodies .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .ret => return self.genRet(inst.cast(ir.Inst.Ret).?), .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?), .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?), .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?), .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?), } } fn genBreakpoint(self: *Function, src: usize) !MCValue { switch (self.module.target.cpu.arch) { .i386, .x86_64 => { try self.code.append(0xcc); // int3 }, else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.module.target.cpu.arch}), } return .unreach; } fn genCall(self: *Function, inst: *ir.Inst.Call) !MCValue { switch (self.module.target.cpu.arch) { else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.module.target.cpu.arch}), } return .unreach; } fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { switch (self.module.target.cpu.arch) { .i386, .x86_64 => { try self.code.append(0xc3); // ret }, else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.module.target.cpu.arch}), } return .unreach; } fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { switch (self.module.target.cpu.arch) { else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}), } } fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue { switch (self.module.target.cpu.arch) { else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}), } } fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue { switch (self.module.target.cpu.arch) { else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}), } } fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue { // Here you can specialize this instruction if it makes sense to, otherwise the default // will call genIsNull and invert the result. switch (self.module.target.cpu.arch) { else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}), } } fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void { switch (self.module.target.cpu.arch) { .i386, .x86_64 => { // TODO x86 treats the operands as signed if (amount <= std.math.maxInt(u8)) { try self.code.resize(self.code.items.len + 2); self.code.items[self.code.items.len - 2] = 0xeb; self.code.items[self.code.items.len - 1] = @intCast(u8, amount); } else { try self.code.resize(self.code.items.len + 5); self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; mem.writeIntLittle(u32, imm_ptr, amount); } }, else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.module.target.cpu.arch}), } } fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { // TODO convert to inline function switch (self.module.target.cpu.arch) { .arm => return self.genAsmArch(.arm, inst), .armeb => return self.genAsmArch(.armeb, inst), .aarch64 => return self.genAsmArch(.aarch64, inst), .aarch64_be => return self.genAsmArch(.aarch64_be, inst), .aarch64_32 => return self.genAsmArch(.aarch64_32, inst), .arc => return self.genAsmArch(.arc, inst), .avr => return self.genAsmArch(.avr, inst), .bpfel => return self.genAsmArch(.bpfel, inst), .bpfeb => return self.genAsmArch(.bpfeb, inst), .hexagon => return self.genAsmArch(.hexagon, inst), .mips => return self.genAsmArch(.mips, inst), .mipsel => return self.genAsmArch(.mipsel, inst), .mips64 => return self.genAsmArch(.mips64, inst), .mips64el => return self.genAsmArch(.mips64el, inst), .msp430 => return self.genAsmArch(.msp430, inst), .powerpc => return self.genAsmArch(.powerpc, inst), .powerpc64 => return self.genAsmArch(.powerpc64, inst), .powerpc64le => return self.genAsmArch(.powerpc64le, inst), .r600 => return self.genAsmArch(.r600, inst), .amdgcn => return self.genAsmArch(.amdgcn, inst), .riscv32 => return self.genAsmArch(.riscv32, inst), .riscv64 => return self.genAsmArch(.riscv64, inst), .sparc => return self.genAsmArch(.sparc, inst), .sparcv9 => return self.genAsmArch(.sparcv9, inst), .sparcel => return self.genAsmArch(.sparcel, inst), .s390x => return self.genAsmArch(.s390x, inst), .tce => return self.genAsmArch(.tce, inst), .tcele => return self.genAsmArch(.tcele, inst), .thumb => return self.genAsmArch(.thumb, inst), .thumbeb => return self.genAsmArch(.thumbeb, inst), .i386 => return self.genAsmArch(.i386, inst), .x86_64 => return self.genAsmArch(.x86_64, inst), .xcore => return self.genAsmArch(.xcore, inst), .nvptx => return self.genAsmArch(.nvptx, inst), .nvptx64 => return self.genAsmArch(.nvptx64, inst), .le32 => return self.genAsmArch(.le32, inst), .le64 => return self.genAsmArch(.le64, inst), .amdil => return self.genAsmArch(.amdil, inst), .amdil64 => return self.genAsmArch(.amdil64, inst), .hsail => return self.genAsmArch(.hsail, inst), .hsail64 => return self.genAsmArch(.hsail64, inst), .spir => return self.genAsmArch(.spir, inst), .spir64 => return self.genAsmArch(.spir64, inst), .kalimba => return self.genAsmArch(.kalimba, inst), .shave => return self.genAsmArch(.shave, inst), .lanai => return self.genAsmArch(.lanai, inst), .wasm32 => return self.genAsmArch(.wasm32, inst), .wasm64 => return self.genAsmArch(.wasm64, inst), .renderscript32 => return self.genAsmArch(.renderscript32, inst), .renderscript64 => return self.genAsmArch(.renderscript64, inst), .ve => return self.genAsmArch(.ve, inst), } } fn genAsmArch(self: *Function, comptime arch: Target.Cpu.Arch, inst: *ir.Inst.Assembly) !MCValue { if (arch != .x86_64 and arch != .i386) { return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}); } for (inst.args.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); } const reg_name = input[1 .. input.len - 1]; const reg = parseRegName(arch, reg_name) orelse return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); const arg = try self.resolveInst(inst.args.args[i]); try self.genSetReg(inst.base.src, arch, reg, arg); } if (mem.eql(u8, inst.args.asm_source, "syscall")) { try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); } else { return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{}); } if (inst.args.output) |output| { if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); } const reg_name = output[2 .. output.len - 1]; const reg = parseRegName(arch, reg_name) orelse return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); return MCValue{ .register = @enumToInt(reg) }; } else { return MCValue.none; } } fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) !void { switch (arch) { .x86_64 => switch (reg) { .rax => switch (mcv) { .none, .unreach => unreachable, .immediate => |x| { // Setting the eax register zeroes the upper part of rax, so if the number is small // enough, that is preferable. // Best case: zero // 31 c0 xor eax,eax if (x == 0) { return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 }); } // Next best case: set eax with 4 bytes // b8 04 03 02 01 mov eax,0x01020304 if (x <= std.math.maxInt(u32)) { try self.code.resize(self.code.items.len + 5); self.code.items[self.code.items.len - 5] = 0xb8; const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); return; } // Worst case: set rax with 8 bytes // 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 try self.code.resize(self.code.items.len + 10); self.code.items[self.code.items.len - 10] = 0x48; self.code.items[self.code.items.len - 9] = 0xb8; const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; mem.writeIntLittle(u64, imm_ptr, x); return; }, .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}), .register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}), }, .rdx => switch (mcv) { .none, .unreach => unreachable, .immediate => |x| { // Setting the edx register zeroes the upper part of rdx, so if the number is small // enough, that is preferable. // Best case: zero // 31 d2 xor edx,edx if (x == 0) { return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 }); } // Next best case: set edx with 4 bytes // ba 04 03 02 01 mov edx,0x1020304 if (x <= std.math.maxInt(u32)) { try self.code.resize(self.code.items.len + 5); self.code.items[self.code.items.len - 5] = 0xba; const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); return; } // Worst case: set rdx with 8 bytes // 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708 try self.code.resize(self.code.items.len + 10); self.code.items[self.code.items.len - 10] = 0x48; self.code.items[self.code.items.len - 9] = 0xba; const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; mem.writeIntLittle(u64, imm_ptr, x); return; }, .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = embedded_in_code", .{}), .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = register", .{}), }, .rdi => switch (mcv) { .none, .unreach => unreachable, .immediate => |x| { // Setting the edi register zeroes the upper part of rdi, so if the number is small // enough, that is preferable. // Best case: zero // 31 ff xor edi,edi if (x == 0) { return self.code.appendSlice(&[_]u8{ 0x31, 0xff }); } // Next best case: set edi with 4 bytes // bf 04 03 02 01 mov edi,0x1020304 if (x <= std.math.maxInt(u32)) { try self.code.resize(self.code.items.len + 5); self.code.items[self.code.items.len - 5] = 0xbf; const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); return; } // Worst case: set rdi with 8 bytes // 48 bf 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 try self.code.resize(self.code.items.len + 10); self.code.items[self.code.items.len - 10] = 0x48; self.code.items[self.code.items.len - 9] = 0xbf; const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; mem.writeIntLittle(u64, imm_ptr, x); return; }, .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = embedded_in_code", .{}), .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = register", .{}), }, .rsi => switch (mcv) { .none, .unreach => unreachable, .immediate => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = immediate", .{}), .embedded_in_code => |code_offset| { // Examples: // lea rsi, [rip + 0x01020304] // lea rsi, [rip - 7] // f: 48 8d 35 04 03 02 01 lea rsi,[rip+0x1020304] # 102031a <_start+0x102031a> // 16: 48 8d 35 f9 ff ff ff lea rsi,[rip+0xfffffffffffffff9] # 16 <_start+0x16> // // We need the offset from RIP in a signed i32 twos complement. // The instruction is 7 bytes long and RIP points to the next instruction. try self.code.resize(self.code.items.len + 7); const rip = self.code.items.len; const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); const offset = @intCast(i32, big_offset); self.code.items[self.code.items.len - 7] = 0x48; self.code.items[self.code.items.len - 6] = 0x8d; self.code.items[self.code.items.len - 5] = 0x35; const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; mem.writeIntLittle(i32, imm_ptr, offset); return; }, .register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}), }, else => return self.fail(src, "TODO implement genSetReg for x86_64 '{}'", .{@tagName(reg)}), }, else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), } } fn genPtrToInt(self: *Function, inst: *ir.Inst.PtrToInt) !MCValue { // no-op return self.resolveInst(inst.args.ptr); } fn genBitCast(self: *Function, inst: *ir.Inst.BitCast) !MCValue { const operand = try self.resolveInst(inst.args.operand); return operand; } fn resolveInst(self: *Function, inst: *ir.Inst) !MCValue { if (self.inst_table.getValue(inst)) |mcv| { return mcv; } if (inst.cast(ir.Inst.Constant)) |const_inst| { const mcvalue = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); try self.inst_table.putNoClobber(inst, mcvalue); return mcvalue; } else { return self.inst_table.getValue(inst).?; } } fn genTypedValue(self: *Function, src: usize, typed_value: ir.TypedValue) !MCValue { switch (typed_value.ty.zigTypeTag()) { .Pointer => { const ptr_elem_type = typed_value.ty.elemType(); switch (ptr_elem_type.zigTypeTag()) { .Array => { // TODO more checks to make sure this can be emitted as a string literal const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator); defer self.code.allocator.free(bytes); const smaller_len = std.math.cast(u32, bytes.len) catch return self.fail(src, "TODO handle a larger string constant", .{}); // Emit the string literal directly into the code; jump over it. try self.genRelativeFwdJump(src, smaller_len); const offset = self.code.items.len; try self.code.appendSlice(bytes); return MCValue{ .embedded_in_code = offset }; }, else => |t| return self.fail(src, "TODO implement emitTypedValue for pointer to '{}'", .{@tagName(t)}), } }, .Int => { const info = typed_value.ty.intInfo(self.module.target); const ptr_bits = self.module.target.cpu.arch.ptrBitWidth(); if (info.bits > ptr_bits or info.signed) { return self.fail(src, "TODO const int bigger than ptr and signed int", .{}); } return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; }, .ComptimeInt => unreachable, // semantic analysis prevents this .ComptimeFloat => unreachable, // semantic analysis prevents this else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}), } } fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { @setCold(true); try self.errors.ensureCapacity(self.errors.items.len + 1); self.errors.appendAssumeCapacity(.{ .byte_offset = src, .msg = try std.fmt.allocPrint(self.errors.allocator, format, args), }); return error.CodegenFail; } }; fn Reg(comptime arch: Target.Cpu.Arch) type { return switch (arch) { .i386 => enum { eax, ebx, ecx, edx, ebp, esp, esi, edi, ax, bx, cx, dx, bp, sp, si, di, ah, bh, ch, dh, al, bl, cl, dl, }, .x86_64 => enum { rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15, eax, ebx, ecx, edx, ebp, esp, esi, edi, r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d, ax, bx, cx, dx, bp, sp, si, di, r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w, ah, bh, ch, dh, bph, sph, sih, dih, al, bl, cl, dl, bpl, spl, sil, dil, r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b, }, else => @compileError("TODO add more register enums"), }; } fn parseRegName(comptime arch: Target.Cpu.Arch, name: []const u8) ?Reg(arch) { return std.meta.stringToEnum(Reg(arch), name); }