| ... | ... | @@ -14,6 +14,7 @@ const Allocator = mem.Allocator; |
| 14 | 14 | const trace = @import("tracy.zig").trace; |
| 15 | 15 | const DW = std.dwarf; |
| 16 | 16 | const leb128 = std.debug.leb; |
| 17 | const log = std.log.scoped(.codegen); |
| 17 | 18 | |
| 18 | 19 | // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. |
| 19 | 20 | // zig fmt: off |
| ... | ... | @@ -344,6 +345,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 344 | 345 | |
| 345 | 346 | const Branch = struct { |
| 346 | 347 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, |
| 348 | /// The key must be canonical register. |
| 347 | 349 | registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, |
| 348 | 350 | free_registers: FreeRegInt = math.maxInt(FreeRegInt), |
| 349 | 351 | |
| ... | ... | @@ -381,9 +383,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 381 | 383 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 382 | 384 | const reg = callee_preserved_regs[free_index]; |
| 383 | 385 | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); |
| 386 | log.debug("alloc {} => {*}", .{reg, inst}); |
| 384 | 387 | return reg; |
| 385 | 388 | } |
| 386 | 389 | |
| 390 | /// Does not track the register. |
| 391 | fn findUnusedReg(self: *Branch) ?Register { |
| 392 | const free_index = @ctz(FreeRegInt, self.free_registers); |
| 393 | if (free_index >= callee_preserved_regs.len) { |
| 394 | return null; |
| 395 | } |
| 396 | return callee_preserved_regs[free_index]; |
| 397 | } |
| 398 | |
| 387 | 399 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 388 | 400 | self.inst_table.deinit(gpa); |
| 389 | 401 | self.registers.deinit(gpa); |
| ... | ... | @@ -570,8 +582,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 570 | 582 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 571 | 583 | const inst_table = &branch.inst_table; |
| 572 | 584 | for (body.instructions) |inst| { |
| 573 | | const new_inst = try self.genFuncInst(inst); |
| 574 | | try inst_table.putNoClobber(self.gpa, inst, new_inst); |
| 585 | const mcv = try self.genFuncInst(inst); |
| 586 | log.debug("{*} => {}", .{inst, mcv}); |
| 587 | // TODO don't put void or dead things in here |
| 588 | try inst_table.putNoClobber(self.gpa, inst, mcv); |
| 575 | 589 | |
| 576 | 590 | var i: ir.Inst.DeathsBitIndex = 0; |
| 577 | 591 | while (inst.getOperand(i)) |operand| : (i += 1) { |
| ... | ... | @@ -714,7 +728,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 714 | 728 | return self.allocMem(inst, abi_size, abi_align); |
| 715 | 729 | } |
| 716 | 730 | |
| 717 | | fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue { |
| 731 | fn allocRegOrMem(self: *Self, inst: *ir.Inst, reg_ok: bool) !MCValue { |
| 718 | 732 | const elem_ty = inst.ty; |
| 719 | 733 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { |
| 720 | 734 | return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); |
| ... | ... | @@ -724,30 +738,73 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 724 | 738 | self.stack_align = abi_align; |
| 725 | 739 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 726 | 740 | |
| 727 | | // Make sure the type can fit in a register before we try to allocate one. |
| 728 | | const ptr_bits = arch.ptrBitWidth(); |
| 729 | | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 730 | | if (abi_size <= ptr_bytes) { |
| 731 | | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 732 | | if (branch.allocReg(inst)) |reg| { |
| 733 | | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| 741 | if (reg_ok) { |
| 742 | // Make sure the type can fit in a register before we try to allocate one. |
| 743 | const ptr_bits = arch.ptrBitWidth(); |
| 744 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 745 | if (abi_size <= ptr_bytes) { |
| 746 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 747 | if (branch.allocReg(inst)) |reg| { |
| 748 | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| 749 | } |
| 734 | 750 | } |
| 735 | 751 | } |
| 736 | 752 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); |
| 737 | 753 | return MCValue{ .stack_offset = stack_offset }; |
| 738 | 754 | } |
| 739 | 755 | |
| 740 | | /// Does not "move" the instruction. |
| 741 | | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { |
| 756 | /// Copies a value to a register without tracking the register. The register is not considered |
| 757 | /// allocated. A second call to `copyToTmpRegister` may return the same register. |
| 758 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 759 | fn copyToTmpRegister(self: *Self, src: usize, mcv: MCValue) !Register { |
| 760 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 761 | |
| 762 | const reg = branch.findUnusedReg() orelse b: { |
| 763 | // We'll take over the first register. Move the instruction that was previously |
| 764 | // there to a stack allocation. |
| 765 | const reg = callee_preserved_regs[0]; |
| 766 | const regs_entry = branch.registers.remove(reg).?; |
| 767 | const spilled_inst = regs_entry.value.inst; |
| 768 | |
| 769 | const stack_mcv = try self.allocRegOrMem(spilled_inst, false); |
| 770 | const inst_entry = branch.inst_table.getEntry(spilled_inst).?; |
| 771 | const reg_mcv = inst_entry.value; |
| 772 | assert(reg == toCanonicalReg(reg_mcv.register)); |
| 773 | inst_entry.value = stack_mcv; |
| 774 | try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv); |
| 775 | |
| 776 | break :b reg; |
| 777 | }; |
| 778 | try self.genSetReg(src, reg, mcv); |
| 779 | return reg; |
| 780 | } |
| 781 | |
| 782 | /// Allocates a new register and copies `mcv` into it. |
| 783 | /// `reg_owner` is the instruction that gets associated with the register in the register table. |
| 784 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 785 | fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue { |
| 742 | 786 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 743 | 787 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 744 | 788 | |
| 745 | | const reg = branch.allocReg(inst) orelse |
| 746 | | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); |
| 747 | | const old_mcv = branch.inst_table.get(inst).?; |
| 748 | | const new_mcv: MCValue = .{ .register = reg }; |
| 749 | | try self.genSetReg(inst.src, reg, old_mcv); |
| 750 | | return new_mcv; |
| 789 | const reg = branch.allocReg(reg_owner) orelse b: { |
| 790 | // We'll take over the first register. Move the instruction that was previously |
| 791 | // there to a stack allocation. |
| 792 | const reg = callee_preserved_regs[0]; |
| 793 | const regs_entry = branch.registers.getEntry(reg).?; |
| 794 | const spilled_inst = regs_entry.value.inst; |
| 795 | regs_entry.value = .{ .inst = reg_owner }; |
| 796 | |
| 797 | const stack_mcv = try self.allocRegOrMem(spilled_inst, false); |
| 798 | const inst_entry = branch.inst_table.getEntry(spilled_inst).?; |
| 799 | const reg_mcv = inst_entry.value; |
| 800 | assert(reg == toCanonicalReg(reg_mcv.register)); |
| 801 | inst_entry.value = stack_mcv; |
| 802 | try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv); |
| 803 | |
| 804 | break :b reg; |
| 805 | }; |
| 806 | try self.genSetReg(reg_owner.src, reg, mcv); |
| 807 | return MCValue{ .register = reg }; |
| 751 | 808 | } |
| 752 | 809 | |
| 753 | 810 | fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { |
| ... | ... | @@ -868,13 +925,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 868 | 925 | } |
| 869 | 926 | } |
| 870 | 927 | |
| 871 | | fn reuseOperand(inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool { |
| 872 | | if (!inst.operandDies(op_index) or !mcv.isMutable()) |
| 928 | fn reuseOperand(self: *Self, inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool { |
| 929 | if (!inst.operandDies(op_index)) |
| 873 | 930 | return false; |
| 874 | 931 | |
| 875 | | // OK we're going to do it, but we need to clear the operand death bit so that |
| 876 | | // it stays allocated. |
| 932 | switch (mcv) { |
| 933 | .register => |reg| { |
| 934 | // If it's in the registers table, need to associate the register with the |
| 935 | // new instruction. |
| 936 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 937 | const entry = branch.registers.getEntry(toCanonicalReg(reg)).?; |
| 938 | entry.value = .{ .inst = inst }; |
| 939 | log.debug("reusing {} => {*}", .{reg, inst}); |
| 940 | }, |
| 941 | .stack_offset => |off| { |
| 942 | log.debug("reusing stack offset {} => {*}", .{off, inst}); |
| 943 | return true; |
| 944 | }, |
| 945 | else => return false, |
| 946 | } |
| 947 | |
| 948 | // Prevent the operand deaths processing code from deallocating it. |
| 877 | 949 | inst.clearOperandDeath(op_index); |
| 950 | |
| 878 | 951 | return true; |
| 879 | 952 | } |
| 880 | 953 | |
| ... | ... | @@ -887,11 +960,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 887 | 960 | if (inst.base.isUnused() and !is_volatile) |
| 888 | 961 | return MCValue.dead; |
| 889 | 962 | const dst_mcv: MCValue = blk: { |
| 890 | | if (reuseOperand(&inst.base, 0, ptr)) { |
| 963 | if (self.reuseOperand(&inst.base, 0, ptr)) { |
| 891 | 964 | // The MCValue that holds the pointer can be re-used as the value. |
| 892 | 965 | break :blk ptr; |
| 893 | 966 | } else { |
| 894 | | break :blk try self.allocRegOrMem(&inst.base); |
| 967 | break :blk try self.allocRegOrMem(&inst.base, true); |
| 895 | 968 | } |
| 896 | 969 | }; |
| 897 | 970 | switch (ptr) { |
| ... | ... | @@ -985,23 +1058,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 985 | 1058 | var dst_mcv: MCValue = undefined; |
| 986 | 1059 | var src_mcv: MCValue = undefined; |
| 987 | 1060 | var src_inst: *ir.Inst = undefined; |
| 988 | | if (reuseOperand(inst, 0, lhs)) { |
| 1061 | if (self.reuseOperand(inst, 0, lhs)) { |
| 989 | 1062 | // LHS dies; use it as the destination. |
| 990 | 1063 | // Both operands cannot be memory. |
| 991 | 1064 | src_inst = op_rhs; |
| 992 | 1065 | if (lhs.isMemory() and rhs.isMemory()) { |
| 993 | | dst_mcv = try self.copyToNewRegister(op_lhs); |
| 1066 | dst_mcv = try self.copyToNewRegister(inst, lhs); |
| 994 | 1067 | src_mcv = rhs; |
| 995 | 1068 | } else { |
| 996 | 1069 | dst_mcv = lhs; |
| 997 | 1070 | src_mcv = rhs; |
| 998 | 1071 | } |
| 999 | | } else if (reuseOperand(inst, 1, rhs)) { |
| 1072 | } else if (self.reuseOperand(inst, 1, rhs)) { |
| 1000 | 1073 | // RHS dies; use it as the destination. |
| 1001 | 1074 | // Both operands cannot be memory. |
| 1002 | 1075 | src_inst = op_lhs; |
| 1003 | 1076 | if (lhs.isMemory() and rhs.isMemory()) { |
| 1004 | | dst_mcv = try self.copyToNewRegister(op_rhs); |
| 1077 | dst_mcv = try self.copyToNewRegister(inst, rhs); |
| 1005 | 1078 | src_mcv = lhs; |
| 1006 | 1079 | } else { |
| 1007 | 1080 | dst_mcv = rhs; |
| ... | ... | @@ -1009,11 +1082,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1009 | 1082 | } |
| 1010 | 1083 | } else { |
| 1011 | 1084 | if (lhs.isMemory()) { |
| 1012 | | dst_mcv = try self.copyToNewRegister(op_lhs); |
| 1085 | dst_mcv = try self.copyToNewRegister(inst, lhs); |
| 1013 | 1086 | src_mcv = rhs; |
| 1014 | 1087 | src_inst = op_rhs; |
| 1015 | 1088 | } else { |
| 1016 | | dst_mcv = try self.copyToNewRegister(op_rhs); |
| 1089 | dst_mcv = try self.copyToNewRegister(inst, rhs); |
| 1017 | 1090 | src_mcv = lhs; |
| 1018 | 1091 | src_inst = op_lhs; |
| 1019 | 1092 | } |
| ... | ... | @@ -1026,18 +1099,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1026 | 1099 | switch (src_mcv) { |
| 1027 | 1100 | .immediate => |imm| { |
| 1028 | 1101 | if (imm > math.maxInt(u31)) { |
| 1029 | | src_mcv = try self.copyToNewRegister(src_inst); |
| 1102 | src_mcv = MCValue{ .register = try self.copyToTmpRegister(src_inst.src, src_mcv) }; |
| 1030 | 1103 | } |
| 1031 | 1104 | }, |
| 1032 | 1105 | else => {}, |
| 1033 | 1106 | } |
| 1034 | 1107 | |
| 1035 | | try self.genX8664BinMathCode(inst.src, dst_mcv, src_mcv, opx, mr); |
| 1108 | try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, opx, mr); |
| 1036 | 1109 | |
| 1037 | 1110 | return dst_mcv; |
| 1038 | 1111 | } |
| 1039 | 1112 | |
| 1040 | | fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { |
| 1113 | fn genX8664BinMathCode( |
| 1114 | self: *Self, |
| 1115 | src: usize, |
| 1116 | dst_ty: Type, |
| 1117 | dst_mcv: MCValue, |
| 1118 | src_mcv: MCValue, |
| 1119 | opx: u8, |
| 1120 | mr: u8, |
| 1121 | ) !void { |
| 1041 | 1122 | switch (dst_mcv) { |
| 1042 | 1123 | .none => unreachable, |
| 1043 | 1124 | .undef => unreachable, |
| ... | ... | @@ -1087,12 +1168,60 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1087 | 1168 | }, |
| 1088 | 1169 | } |
| 1089 | 1170 | }, |
| 1090 | | .embedded_in_code, .memory, .stack_offset => { |
| 1171 | .stack_offset => |off| { |
| 1172 | switch (src_mcv) { |
| 1173 | .none => unreachable, |
| 1174 | .undef => return self.genSetStack(src, dst_ty, off, .undef), |
| 1175 | .dead, .unreach => unreachable, |
| 1176 | .ptr_stack_offset => unreachable, |
| 1177 | .ptr_embedded_in_code => unreachable, |
| 1178 | .register => |src_reg| { |
| 1179 | try self.genX8664ModRMRegToStack(src, dst_ty, off, src_reg, mr + 0x1); |
| 1180 | }, |
| 1181 | .immediate => |imm| { |
| 1182 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source immediate", .{}); |
| 1183 | }, |
| 1184 | .embedded_in_code, .memory, .stack_offset => { |
| 1185 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); |
| 1186 | }, |
| 1187 | .compare_flags_unsigned => { |
| 1188 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); |
| 1189 | }, |
| 1190 | .compare_flags_signed => { |
| 1191 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); |
| 1192 | }, |
| 1193 | } |
| 1194 | }, |
| 1195 | .embedded_in_code, .memory => { |
| 1091 | 1196 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{}); |
| 1092 | 1197 | }, |
| 1093 | 1198 | } |
| 1094 | 1199 | } |
| 1095 | 1200 | |
| 1201 | fn genX8664ModRMRegToStack(self: *Self, src: usize, ty: Type, off: u32, reg: Register, opcode: u8) !void { |
| 1202 | const abi_size = ty.abiSize(self.target.*); |
| 1203 | const adj_off = off + abi_size; |
| 1204 | try self.code.ensureCapacity(self.code.items.len + 7); |
| 1205 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); |
| 1206 | const reg_id: u8 = @truncate(u3, reg.id()); |
| 1207 | if (adj_off <= 128) { |
| 1208 | // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx |
| 1209 | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); |
| 1210 | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); |
| 1211 | const twos_comp = @bitCast(u8, negative_offset); |
| 1212 | self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM, twos_comp }); |
| 1213 | } else if (adj_off <= 2147483648) { |
| 1214 | // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx |
| 1215 | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); |
| 1216 | const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); |
| 1217 | const twos_comp = @bitCast(u32, negative_offset); |
| 1218 | self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM }); |
| 1219 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); |
| 1220 | } else { |
| 1221 | return self.fail(src, "stack offset too large", .{}); |
| 1222 | } |
| 1223 | } |
| 1224 | |
| 1096 | 1225 | fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue { |
| 1097 | 1226 | if (FreeRegInt == u0) { |
| 1098 | 1227 | return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch}); |
| ... | ... | @@ -1109,7 +1238,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1109 | 1238 | const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; |
| 1110 | 1239 | switch (result) { |
| 1111 | 1240 | .register => |reg| { |
| 1112 | | branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = &inst.base }); |
| 1241 | branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base }); |
| 1113 | 1242 | branch.markRegUsed(reg); |
| 1114 | 1243 | |
| 1115 | 1244 | try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len); |
| ... | ... | @@ -1304,13 +1433,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1304 | 1433 | // Either one, but not both, can be a memory operand. |
| 1305 | 1434 | // Source operand can be an immediate, 8 bits or 32 bits. |
| 1306 | 1435 | const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) |
| 1307 | | try self.copyToNewRegister(inst.lhs) |
| 1436 | try self.copyToNewRegister(&inst.base, lhs) |
| 1308 | 1437 | else |
| 1309 | 1438 | lhs; |
| 1310 | 1439 | // This instruction supports only signed 32-bit immediates at most. |
| 1311 | 1440 | const src_mcv = try self.limitImmediateType(inst.rhs, i32); |
| 1312 | 1441 | |
| 1313 | | try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); |
| 1442 | try self.genX8664BinMathCode(inst.base.src, inst.base.ty, dst_mcv, src_mcv, 7, 0x38); |
| 1314 | 1443 | const info = inst.lhs.ty.intInfo(self.target.*); |
| 1315 | 1444 | if (info.signed) { |
| 1316 | 1445 | return MCValue{ .compare_flags_signed = op }; |
| ... | ... | @@ -1584,6 +1713,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1584 | 1713 | /// resulting REX is meaningful, but will remain the same if it is not. |
| 1585 | 1714 | /// * Deliberately inserting a "meaningless REX" requires explicit usage of |
| 1586 | 1715 | /// 0x40, and cannot be done via this function. |
| 1716 | /// W => 64 bit mode |
| 1717 | /// R => extension to the MODRM.reg field |
| 1718 | /// X => extension to the SIB.index field |
| 1719 | /// B => extension to the MODRM.rm field or the SIB.base field |
| 1587 | 1720 | fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { |
| 1588 | 1721 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. |
| 1589 | 1722 | var value: u8 = 0x40; |
| ... | ... | @@ -1681,27 +1814,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1681 | 1814 | return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); |
| 1682 | 1815 | }, |
| 1683 | 1816 | .register => |reg| { |
| 1684 | | const abi_size = ty.abiSize(self.target.*); |
| 1685 | | const adj_off = stack_offset + abi_size; |
| 1686 | | try self.code.ensureCapacity(self.code.items.len + 7); |
| 1687 | | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1688 | | const reg_id: u8 = @truncate(u3, reg.id()); |
| 1689 | | if (adj_off <= 128) { |
| 1690 | | // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx |
| 1691 | | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); |
| 1692 | | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); |
| 1693 | | const twos_comp = @bitCast(u8, negative_offset); |
| 1694 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp }); |
| 1695 | | } else if (adj_off <= 2147483648) { |
| 1696 | | // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx |
| 1697 | | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); |
| 1698 | | const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); |
| 1699 | | const twos_comp = @bitCast(u32, negative_offset); |
| 1700 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM }); |
| 1701 | | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); |
| 1702 | | } else { |
| 1703 | | return self.fail(src, "stack offset too large", .{}); |
| 1704 | | } |
| 1817 | try self.genX8664ModRMRegToStack(src, ty, stack_offset, reg, 0x89); |
| 1705 | 1818 | }, |
| 1706 | 1819 | .memory => |vaddr| { |
| 1707 | 1820 | return self.fail(src, "TODO implement set stack variable from memory vaddr", .{}); |
| ... | ... | @@ -1709,7 +1822,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1709 | 1822 | .stack_offset => |off| { |
| 1710 | 1823 | if (stack_offset == off) |
| 1711 | 1824 | return; // Copy stack variable to itself; nothing to do. |
| 1712 | | return self.fail(src, "TODO implement copy stack variable to stack variable", .{}); |
| 1825 | |
| 1826 | const reg = try self.copyToTmpRegister(src, mcv); |
| 1827 | return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg }); |
| 1713 | 1828 | }, |
| 1714 | 1829 | }, |
| 1715 | 1830 | else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}), |
| ... | ... | @@ -2027,7 +2142,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2027 | 2142 | }, |
| 2028 | 2143 | }); |
| 2029 | 2144 | if (imm >= math.maxInt(U)) { |
| 2030 | | return self.copyToNewRegister(inst); |
| 2145 | return MCValue{ .register = try self.copyToTmpRegister(inst.src, mcv) }; |
| 2031 | 2146 | } |
| 2032 | 2147 | }, |
| 2033 | 2148 | else => {}, |