| ... | @@ -14,6 +14,7 @@ const Allocator = mem.Allocator; | ... | @@ -14,6 +14,7 @@ const Allocator = mem.Allocator; |
| 14 | const trace = @import("tracy.zig").trace; | 14 | const trace = @import("tracy.zig").trace; |
| 15 | const DW = std.dwarf; | 15 | const DW = std.dwarf; |
| 16 | const leb128 = std.debug.leb; | 16 | const leb128 = std.debug.leb; |
| | 17 | const log = std.log.scoped(.codegen); |
| 17 | | 18 | |
| 18 | // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. | 19 | // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. |
| 19 | // zig fmt: off | 20 | // zig fmt: off |
| ... | @@ -344,6 +345,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -344,6 +345,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 344 | | 345 | |
| 345 | const Branch = struct { | 346 | const Branch = struct { |
| 346 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | 347 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, |
| | 348 | /// The key must be canonical register. |
| 347 | registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, | 349 | registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, |
| 348 | free_registers: FreeRegInt = math.maxInt(FreeRegInt), | 350 | free_registers: FreeRegInt = math.maxInt(FreeRegInt), |
| 349 | | 351 | |
| ... | @@ -381,9 +383,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -381,9 +383,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 381 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); | 383 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 382 | const reg = callee_preserved_regs[free_index]; | 384 | const reg = callee_preserved_regs[free_index]; |
| 383 | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); | 385 | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); |
| | 386 | log.debug("alloc {} => {*}", .{reg, inst}); |
| 384 | return reg; | 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 | fn deinit(self: *Branch, gpa: *Allocator) void { | 399 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 388 | self.inst_table.deinit(gpa); | 400 | self.inst_table.deinit(gpa); |
| 389 | self.registers.deinit(gpa); | 401 | self.registers.deinit(gpa); |
| ... | @@ -570,8 +582,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -570,8 +582,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 570 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | 582 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 571 | const inst_table = &branch.inst_table; | 583 | const inst_table = &branch.inst_table; |
| 572 | for (body.instructions) |inst| { | 584 | for (body.instructions) |inst| { |
| 573 | const new_inst = try self.genFuncInst(inst); | 585 | const mcv = try self.genFuncInst(inst); |
| 574 | try inst_table.putNoClobber(self.gpa, inst, new_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 | var i: ir.Inst.DeathsBitIndex = 0; | 590 | var i: ir.Inst.DeathsBitIndex = 0; |
| 577 | while (inst.getOperand(i)) |operand| : (i += 1) { | 591 | while (inst.getOperand(i)) |operand| : (i += 1) { |
| ... | @@ -714,7 +728,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -714,7 +728,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 714 | return self.allocMem(inst, abi_size, abi_align); | 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 | const elem_ty = inst.ty; | 732 | const elem_ty = inst.ty; |
| 719 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { | 733 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { |
| 720 | return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); | 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,30 +738,73 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 724 | self.stack_align = abi_align; | 738 | self.stack_align = abi_align; |
| 725 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | 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. | 741 | if (reg_ok) { |
| 728 | const ptr_bits = arch.ptrBitWidth(); | 742 | // Make sure the type can fit in a register before we try to allocate one. |
| 729 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | 743 | const ptr_bits = arch.ptrBitWidth(); |
| 730 | if (abi_size <= ptr_bytes) { | 744 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 731 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | 745 | if (abi_size <= ptr_bytes) { |
| 732 | if (branch.allocReg(inst)) |reg| { | 746 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 733 | return MCValue{ .register = registerAlias(reg, abi_size) }; | 747 | if (branch.allocReg(inst)) |reg| { |
| | 748 | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| | 749 | } |
| 734 | } | 750 | } |
| 735 | } | 751 | } |
| 736 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); | 752 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); |
| 737 | return MCValue{ .stack_offset = stack_offset }; | 753 | return MCValue{ .stack_offset = stack_offset }; |
| 738 | } | 754 | } |
| 739 | | 755 | |
| 740 | /// Does not "move" the instruction. | 756 | /// Copies a value to a register without tracking the register. The register is not considered |
| 741 | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { | 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 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | 786 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 743 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | 787 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 744 | | 788 | |
| 745 | const reg = branch.allocReg(inst) orelse | 789 | const reg = branch.allocReg(reg_owner) orelse b: { |
| 746 | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); | 790 | // We'll take over the first register. Move the instruction that was previously |
| 747 | const old_mcv = branch.inst_table.get(inst).?; | 791 | // there to a stack allocation. |
| 748 | const new_mcv: MCValue = .{ .register = reg }; | 792 | const reg = callee_preserved_regs[0]; |
| 749 | try self.genSetReg(inst.src, reg, old_mcv); | 793 | const regs_entry = branch.registers.getEntry(reg).?; |
| 750 | return new_mcv; | 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 | fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { | 810 | fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { |
| ... | @@ -868,13 +925,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -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 { | 928 | fn reuseOperand(self: *Self, inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool { |
| 872 | if (!inst.operandDies(op_index) or !mcv.isMutable()) | 929 | if (!inst.operandDies(op_index)) |
| 873 | return false; | 930 | return false; |
| 874 | | 931 | |
| 875 | // OK we're going to do it, but we need to clear the operand death bit so that | 932 | switch (mcv) { |
| 876 | // it stays allocated. | 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 | inst.clearOperandDeath(op_index); | 949 | inst.clearOperandDeath(op_index); |
| | 950 | |
| 878 | return true; | 951 | return true; |
| 879 | } | 952 | } |
| 880 | | 953 | |
| ... | @@ -887,11 +960,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -887,11 +960,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 887 | if (inst.base.isUnused() and !is_volatile) | 960 | if (inst.base.isUnused() and !is_volatile) |
| 888 | return MCValue.dead; | 961 | return MCValue.dead; |
| 889 | const dst_mcv: MCValue = blk: { | 962 | const dst_mcv: MCValue = blk: { |
| 890 | if (reuseOperand(&inst.base, 0, ptr)) { | 963 | if (self.reuseOperand(&inst.base, 0, ptr)) { |
| 891 | // The MCValue that holds the pointer can be re-used as the value. | 964 | // The MCValue that holds the pointer can be re-used as the value. |
| 892 | break :blk ptr; | 965 | break :blk ptr; |
| 893 | } else { | 966 | } else { |
| 894 | break :blk try self.allocRegOrMem(&inst.base); | 967 | break :blk try self.allocRegOrMem(&inst.base, true); |
| 895 | } | 968 | } |
| 896 | }; | 969 | }; |
| 897 | switch (ptr) { | 970 | switch (ptr) { |
| ... | @@ -985,23 +1058,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -985,23 +1058,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 985 | var dst_mcv: MCValue = undefined; | 1058 | var dst_mcv: MCValue = undefined; |
| 986 | var src_mcv: MCValue = undefined; | 1059 | var src_mcv: MCValue = undefined; |
| 987 | var src_inst: *ir.Inst = undefined; | 1060 | var src_inst: *ir.Inst = undefined; |
| 988 | if (reuseOperand(inst, 0, lhs)) { | 1061 | if (self.reuseOperand(inst, 0, lhs)) { |
| 989 | // LHS dies; use it as the destination. | 1062 | // LHS dies; use it as the destination. |
| 990 | // Both operands cannot be memory. | 1063 | // Both operands cannot be memory. |
| 991 | src_inst = op_rhs; | 1064 | src_inst = op_rhs; |
| 992 | if (lhs.isMemory() and rhs.isMemory()) { | 1065 | if (lhs.isMemory() and rhs.isMemory()) { |
| 993 | dst_mcv = try self.copyToNewRegister(op_lhs); | 1066 | dst_mcv = try self.copyToNewRegister(inst, lhs); |
| 994 | src_mcv = rhs; | 1067 | src_mcv = rhs; |
| 995 | } else { | 1068 | } else { |
| 996 | dst_mcv = lhs; | 1069 | dst_mcv = lhs; |
| 997 | src_mcv = rhs; | 1070 | src_mcv = rhs; |
| 998 | } | 1071 | } |
| 999 | } else if (reuseOperand(inst, 1, rhs)) { | 1072 | } else if (self.reuseOperand(inst, 1, rhs)) { |
| 1000 | // RHS dies; use it as the destination. | 1073 | // RHS dies; use it as the destination. |
| 1001 | // Both operands cannot be memory. | 1074 | // Both operands cannot be memory. |
| 1002 | src_inst = op_lhs; | 1075 | src_inst = op_lhs; |
| 1003 | if (lhs.isMemory() and rhs.isMemory()) { | 1076 | if (lhs.isMemory() and rhs.isMemory()) { |
| 1004 | dst_mcv = try self.copyToNewRegister(op_rhs); | 1077 | dst_mcv = try self.copyToNewRegister(inst, rhs); |
| 1005 | src_mcv = lhs; | 1078 | src_mcv = lhs; |
| 1006 | } else { | 1079 | } else { |
| 1007 | dst_mcv = rhs; | 1080 | dst_mcv = rhs; |
| ... | @@ -1009,11 +1082,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1009,11 +1082,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1009 | } | 1082 | } |
| 1010 | } else { | 1083 | } else { |
| 1011 | if (lhs.isMemory()) { | 1084 | if (lhs.isMemory()) { |
| 1012 | dst_mcv = try self.copyToNewRegister(op_lhs); | 1085 | dst_mcv = try self.copyToNewRegister(inst, lhs); |
| 1013 | src_mcv = rhs; | 1086 | src_mcv = rhs; |
| 1014 | src_inst = op_rhs; | 1087 | src_inst = op_rhs; |
| 1015 | } else { | 1088 | } else { |
| 1016 | dst_mcv = try self.copyToNewRegister(op_rhs); | 1089 | dst_mcv = try self.copyToNewRegister(inst, rhs); |
| 1017 | src_mcv = lhs; | 1090 | src_mcv = lhs; |
| 1018 | src_inst = op_lhs; | 1091 | src_inst = op_lhs; |
| 1019 | } | 1092 | } |
| ... | @@ -1026,18 +1099,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1026,18 +1099,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1026 | switch (src_mcv) { | 1099 | switch (src_mcv) { |
| 1027 | .immediate => |imm| { | 1100 | .immediate => |imm| { |
| 1028 | if (imm > math.maxInt(u31)) { | 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 | else => {}, | 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 | return dst_mcv; | 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 | switch (dst_mcv) { | 1122 | switch (dst_mcv) { |
| 1042 | .none => unreachable, | 1123 | .none => unreachable, |
| 1043 | .undef => unreachable, | 1124 | .undef => unreachable, |
| ... | @@ -1087,12 +1168,60 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -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 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{}); | 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 | fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue { | 1225 | fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue { |
| 1097 | if (FreeRegInt == u0) { | 1226 | if (FreeRegInt == u0) { |
| 1098 | return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch}); | 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,7 +1238,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1109 | const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; | 1238 | const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; |
| 1110 | switch (result) { | 1239 | switch (result) { |
| 1111 | .register => |reg| { | 1240 | .register => |reg| { |
| 1112 | branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = &inst.base }); | 1241 | branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base }); |
| 1113 | branch.markRegUsed(reg); | 1242 | branch.markRegUsed(reg); |
| 1114 | | 1243 | |
| 1115 | try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len); | 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,13 +1433,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1304 | // Either one, but not both, can be a memory operand. | 1433 | // Either one, but not both, can be a memory operand. |
| 1305 | // Source operand can be an immediate, 8 bits or 32 bits. | 1434 | // Source operand can be an immediate, 8 bits or 32 bits. |
| 1306 | const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) | 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 | else | 1437 | else |
| 1309 | lhs; | 1438 | lhs; |
| 1310 | // This instruction supports only signed 32-bit immediates at most. | 1439 | // This instruction supports only signed 32-bit immediates at most. |
| 1311 | const src_mcv = try self.limitImmediateType(inst.rhs, i32); | 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 | const info = inst.lhs.ty.intInfo(self.target.*); | 1443 | const info = inst.lhs.ty.intInfo(self.target.*); |
| 1315 | if (info.signed) { | 1444 | if (info.signed) { |
| 1316 | return MCValue{ .compare_flags_signed = op }; | 1445 | return MCValue{ .compare_flags_signed = op }; |
| ... | @@ -1584,6 +1713,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1584,6 +1713,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1584 | /// resulting REX is meaningful, but will remain the same if it is not. | 1713 | /// resulting REX is meaningful, but will remain the same if it is not. |
| 1585 | /// * Deliberately inserting a "meaningless REX" requires explicit usage of | 1714 | /// * Deliberately inserting a "meaningless REX" requires explicit usage of |
| 1586 | /// 0x40, and cannot be done via this function. | 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 | fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { | 1720 | fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { |
| 1588 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. | 1721 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. |
| 1589 | var value: u8 = 0x40; | 1722 | var value: u8 = 0x40; |
| ... | @@ -1681,27 +1814,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1681,27 +1814,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1681 | return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); | 1814 | return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); |
| 1682 | }, | 1815 | }, |
| 1683 | .register => |reg| { | 1816 | .register => |reg| { |
| 1684 | const abi_size = ty.abiSize(self.target.*); | 1817 | try self.genX8664ModRMRegToStack(src, ty, stack_offset, reg, 0x89); |
| 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 | } | | |
| 1705 | }, | 1818 | }, |
| 1706 | .memory => |vaddr| { | 1819 | .memory => |vaddr| { |
| 1707 | return self.fail(src, "TODO implement set stack variable from memory vaddr", .{}); | 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,7 +1822,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1709 | .stack_offset => |off| { | 1822 | .stack_offset => |off| { |
| 1710 | if (stack_offset == off) | 1823 | if (stack_offset == off) |
| 1711 | return; // Copy stack variable to itself; nothing to do. | 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 | else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}), | 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,7 +2142,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2027 | }, | 2142 | }, |
| 2028 | }); | 2143 | }); |
| 2029 | if (imm >= math.maxInt(U)) { | 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 | else => {}, | 2148 | else => {}, |