diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2022-01-26 16:14:57 +0100 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2022-01-26 16:14:57 +0100 |
| commit | 9f224ebd9a4f26d0dc5b0937d3d5df8fe78f1c9d (patch) | |
| tree | e3bbcb01c6c19953004d0a28efc789a5015e9f4a /src | |
| parent | 075e2eaaafb1038ce623713d9dc4947171d5c255 (diff) | |
| download | zig-9f224ebd9a4f26d0dc5b0937d3d5df8fe78f1c9d.tar.gz zig-9f224ebd9a4f26d0dc5b0937d3d5df8fe78f1c9d.zip | |
stage2: add zero- and sign-extend moves to x86_64
* remove `LoweringError` error set from `Emit.zig` - it actually
was less than helpful; it's better to either not throw an error
since there can be instructions with mismatching operand sizes
such as `movsx` or assert on a by instruction-basis. Currently,
let's just pass through and see how we fare.
* when moving integers into registers, check for signedness and move
with zero- or sign-extension if source operand is smaller than 8
bytes. The destination operand is always assumed to be full-width,
i.e., 8 bytes.
* clean up `airTrunc` a little to match the rest of CodeGen inst
implementations.
Diffstat (limited to 'src')
| -rw-r--r-- | src/arch/x86_64/CodeGen.zig | 165 | ||||
| -rw-r--r-- | src/arch/x86_64/Emit.zig | 276 | ||||
| -rw-r--r-- | src/arch/x86_64/Mir.zig | 13 |
3 files changed, 255 insertions, 199 deletions
diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 822a2d0ad1..0cc17f3a90 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -769,6 +769,11 @@ fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u /// Use a pointer instruction as the basis for allocating stack memory. fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 { const elem_ty = self.air.typeOfIndex(inst).elemType(); + + if (!elem_ty.hasRuntimeBits()) { + return self.allocMem(inst, 8, 8); + } + const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { return self.fail("type '{}' too big to fit into stack frame", .{elem_ty}); }; @@ -904,30 +909,28 @@ fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); - const operand = try self.resolveInst(ty_op.operand); - const src_ty = self.air.typeOf(ty_op.operand); const dst_ty = self.air.typeOfIndex(inst); + const operand = try self.resolveInst(ty_op.operand); const src_ty_size = src_ty.abiSize(self.target.*); const dst_ty_size = dst_ty.abiSize(self.target.*); + if (src_ty_size > 8 or dst_ty_size > 8) { return self.fail("TODO implement trunc for abi sizes larger than 8", .{}); } - const src_reg = if (operand == .register) operand.register else blk: { - const tmp_reg = try self.register_manager.allocReg(inst, &.{}); - try self.genSetReg(dst_ty, tmp_reg, operand); - break :blk tmp_reg; + const dst_mcv = blk: { + const reg = switch (operand) { + .register => |reg| reg, + else => inner: { + const reg = try self.register_manager.allocReg(inst, &.{}); + try self.genSetReg(src_ty, reg, operand); + break :inner reg; + }, + }; + break :blk .{ .register = registerAlias(reg, @intCast(u32, dst_ty_size)) }; }; - const dst_reg = try self.register_manager.allocReg(inst, &.{}); - - // "convert" the src register to the smaller type so only those bytes are moved - const small_src = MCValue{ .register = registerAlias(src_reg, @intCast(u32, dst_ty_size)) }; - - try self.genSetReg(dst_ty, dst_reg, small_src); - - const result = MCValue{ .register = registerAlias(dst_reg, @intCast(u32, dst_ty_size)) }; // when truncating a `u16` to `u5`, for example, those top 3 bits in the result // have to be removed. this only happens if the dst if not a power-of-two size. @@ -935,16 +938,10 @@ fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { const is_power_of_two = (dst_bit_size & (dst_bit_size - 1)) == 0; if (!is_power_of_two or dst_bit_size < 8) { const mask = (~@as(u64, 0)) >> @intCast(u6, (64 - dst_ty.bitSize(self.target.*))); - try self.genBinMathOpMir( - .@"and", - dst_ty, - dst_ty.intInfo(self.target.*).signedness, - result, - .{ .immediate = mask }, - ); + try self.genBinMathOpMir(.@"and", dst_ty, dst_mcv, .{ .immediate = mask }); } - return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + return self.finishAir(inst, dst_mcv, .{ ty_op.operand, .none, .none }); } fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { @@ -1406,7 +1403,7 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { // TODO we could allocate register here, but need to except addr register and potentially // offset register. const dst_mcv = try self.allocRegOrMem(inst, false); - try self.genBinMathOpMir(.add, slice_ptr_field_type, .unsigned, .{ .register = addr_reg.to64() }, .{ + try self.genBinMathOpMir(.add, slice_ptr_field_type, .{ .register = addr_reg.to64() }, .{ .register = offset_reg.to64(), }); try self.load(dst_mcv, .{ .register = addr_reg.to64() }, slice_ptr_field_type); @@ -1454,13 +1451,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { // TODO we could allocate register here, but need to except addr register and potentially // offset register. const dst_mcv = try self.allocRegOrMem(inst, false); - try self.genBinMathOpMir( - .add, - array_ty, - .unsigned, - .{ .register = addr_reg.to64() }, - .{ .register = offset_reg.to64() }, - ); + try self.genBinMathOpMir(.add, array_ty, .{ .register = addr_reg.to64() }, .{ .register = offset_reg.to64() }); try self.load(dst_mcv, .{ .register = addr_reg.to64() }, array_ty); break :result dst_mcv; }; @@ -1498,7 +1489,7 @@ fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void { else => return self.fail("TODO implement ptr_elem_ptr when ptr is {}", .{ptr}), } }; - try self.genBinMathOpMir(.add, ptr_ty, .unsigned, dst_mcv, .{ .register = offset_reg }); + try self.genBinMathOpMir(.add, ptr_ty, dst_mcv, .{ .register = offset_reg }); break :result dst_mcv; }; return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); @@ -1929,11 +1920,11 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: const dst_ty = self.air.typeOfIndex(inst); const air_tags = self.air.instructions.items(.tag); switch (air_tags[inst]) { - .add, .addwrap, .ptr_add => try self.genBinMathOpMir(.add, dst_ty, .unsigned, dst_mcv, src_mcv), - .bool_or, .bit_or => try self.genBinMathOpMir(.@"or", dst_ty, .unsigned, dst_mcv, src_mcv), - .bool_and, .bit_and => try self.genBinMathOpMir(.@"and", dst_ty, .unsigned, dst_mcv, src_mcv), - .sub, .subwrap => try self.genBinMathOpMir(.sub, dst_ty, .unsigned, dst_mcv, src_mcv), - .xor, .not => try self.genBinMathOpMir(.xor, dst_ty, .unsigned, dst_mcv, src_mcv), + .add, .addwrap, .ptr_add => try self.genBinMathOpMir(.add, dst_ty, dst_mcv, src_mcv), + .bool_or, .bit_or => try self.genBinMathOpMir(.@"or", dst_ty, dst_mcv, src_mcv), + .bool_and, .bit_and => try self.genBinMathOpMir(.@"and", dst_ty, dst_mcv, src_mcv), + .sub, .subwrap => try self.genBinMathOpMir(.sub, dst_ty, dst_mcv, src_mcv), + .xor, .not => try self.genBinMathOpMir(.xor, dst_ty, dst_mcv, src_mcv), .mul, .mulwrap => try self.genIMulOpMir(dst_ty, dst_mcv, src_mcv), else => unreachable, } @@ -1941,14 +1932,7 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: return dst_mcv; } -fn genBinMathOpMir( - self: *Self, - mir_tag: Mir.Inst.Tag, - dst_ty: Type, - signedness: std.builtin.Signedness, - dst_mcv: MCValue, - src_mcv: MCValue, -) !void { +fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) !void { switch (dst_mcv) { .none => unreachable, .undef => unreachable, @@ -1965,16 +1949,11 @@ fn genBinMathOpMir( .ptr_stack_offset => unreachable, .ptr_embedded_in_code => unreachable, .register => |src_reg| { - // TODO think more carefully about this: is this actually correct? - const reg_size = if (mir_tag == .cmp and signedness == .signed) - @divExact(dst_reg.size(), 8) - else - @divExact(src_reg.size(), 8); _ = try self.addInst(.{ .tag = mir_tag, .ops = (Mir.Ops{ - .reg1 = registerAlias(dst_reg, reg_size), - .reg2 = registerAlias(src_reg, reg_size), + .reg1 = registerAlias(dst_reg, @divExact(src_reg.size(), 8)), + .reg2 = src_reg, }).encode(), .data = undefined, }); @@ -2521,7 +2500,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { // This instruction supports only signed 32-bit immediates at most. const src_mcv = try self.limitImmediateType(bin_op.rhs, i32); - try self.genBinMathOpMir(.cmp, ty, signedness, dst_mcv, src_mcv); + try self.genBinMathOpMir(.cmp, ty, dst_mcv, src_mcv); break :result switch (signedness) { .signed => MCValue{ .compare_flags_signed = op }, .unsigned => MCValue{ .compare_flags_unsigned = op }, @@ -2738,7 +2717,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { } fn isNull(self: *Self, ty: Type, operand: MCValue) !MCValue { - try self.genBinMathOpMir(.cmp, ty, .unsigned, operand, MCValue{ .immediate = 0 }); + try self.genBinMathOpMir(.cmp, ty, operand, MCValue{ .immediate = 0 }); return MCValue{ .compare_flags_unsigned = .eq }; } @@ -2755,7 +2734,7 @@ fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue { return MCValue{ .immediate = 0 }; // always false } else if (!payload_type.hasRuntimeBits()) { if (err_type.abiSize(self.target.*) <= 8) { - try self.genBinMathOpMir(.cmp, err_type, .unsigned, operand, MCValue{ .immediate = 0 }); + try self.genBinMathOpMir(.cmp, err_type, operand, MCValue{ .immediate = 0 }); return MCValue{ .compare_flags_unsigned = .gt }; } else { return self.fail("TODO isErr for errors with size larger than register size", .{}); @@ -3627,10 +3606,10 @@ fn genInlineMemset(self: *Self, ty: Type, stack_offset: i32, value: MCValue) Inn } fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { + const abi_size = ty.abiSize(self.target.*); switch (mcv) { .dead => unreachable, .ptr_stack_offset => |unadjusted_off| { - const ptr_abi_size = ty.abiSize(self.target.*); const elem_ty = ty.childType(); const elem_abi_size = elem_ty.abiSize(self.target.*); const off = unadjusted_off + @intCast(i32, elem_abi_size); @@ -3640,7 +3619,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .lea, .ops = (Mir.Ops{ - .reg1 = registerAlias(reg, @intCast(u32, ptr_abi_size)), + .reg1 = registerAlias(reg, @intCast(u32, abi_size)), .reg2 = .rbp, }).encode(), .data = .{ .imm = @bitCast(u32, -off) }, @@ -3694,15 +3673,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .xor, .ops = (Mir.Ops{ - .reg1 = reg.to64(), - .reg2 = reg.to64(), + .reg1 = reg.to32(), + .reg2 = reg.to32(), }).encode(), .data = undefined, }); return; } if (x <= math.maxInt(i32)) { - const abi_size = ty.abiSize(self.target.*); // Next best case: if we set the lower four bytes, the upper four will be zeroed. _ = try self.addInst(.{ .tag = .mov, @@ -3746,6 +3724,34 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (src_reg.id() == reg.id()) return; + if (ty.zigTypeTag() == .Int) blk: { + switch (ty.intInfo(self.target.*).signedness) { + .signed => { + if (abi_size > 4) break :blk; + _ = try self.addInst(.{ + .tag = .mov_sign_extend, + .ops = (Mir.Ops{ + .reg1 = reg.to64(), + .reg2 = src_reg, + }).encode(), + .data = undefined, + }); + }, + .unsigned => { + if (abi_size > 2) break :blk; + _ = try self.addInst(.{ + .tag = .mov_zero_extend, + .ops = (Mir.Ops{ + .reg1 = reg.to64(), + .reg2 = src_reg, + }).encode(), + .data = undefined, + }); + }, + } + return; + } + _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ @@ -3819,11 +3825,50 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .stack_offset => |unadjusted_off| { - const abi_size = ty.abiSize(self.target.*); const off = unadjusted_off + @intCast(i32, abi_size); if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } + + if (ty.zigTypeTag() == .Int) blk: { + switch (ty.intInfo(self.target.*).signedness) { + .signed => { + const flags: u2 = switch (abi_size) { + 1 => 0b01, + 2 => 0b10, + 4 => 0b11, + else => break :blk, + }; + _ = try self.addInst(.{ + .tag = .mov_sign_extend, + .ops = (Mir.Ops{ + .reg1 = reg.to64(), + .reg2 = .rbp, + .flags = flags, + }).encode(), + .data = .{ .imm = @bitCast(u32, -off) }, + }); + }, + .unsigned => { + const flags: u2 = switch (abi_size) { + 1 => 0b01, + 2 => 0b10, + else => break :blk, + }; + _ = try self.addInst(.{ + .tag = .mov_zero_extend, + .ops = (Mir.Ops{ + .reg1 = reg.to64(), + .reg2 = .rbp, + .flags = flags, + }).encode(), + .data = .{ .imm = @bitCast(u32, -off) }, + }); + }, + } + return; + } + _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e18807846d..aa1798c0f6 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -125,6 +125,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .cmp_mem_index_imm => try emit.mirArithMemIndexImm(.cmp, inst), .mov_mem_index_imm => try emit.mirArithMemIndexImm(.mov, inst), + .mov_sign_extend => try emit.mirMovSignExtend(inst), + .mov_zero_extend => try emit.mirMovZeroExtend(inst), + .movabs => try emit.mirMovabs(inst), .lea => try emit.mirLea(inst), @@ -188,14 +191,6 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { return error.EmitFail; } -fn failWithLoweringError(emit: *Emit, err: LoweringError) InnerError { - return switch (err) { - error.RaxOperandExpected => emit.fail("Register.rax expected as destination operand", .{}), - error.OperandSizeMismatch => emit.fail("operand size mismatch", .{}), - else => |e| e, - }; -} - fn fixupRelocs(emit: *Emit) InnerError!void { // TODO this function currently assumes all relocs via JMP/CALL instructions are 32bit in size. // This should be reversed like it is done in aarch64 MIR emit code: start with the smallest @@ -210,15 +205,15 @@ fn fixupRelocs(emit: *Emit) InnerError!void { } fn mirBrk(emit: *Emit) InnerError!void { - return lowerToZoEnc(.brk, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToZoEnc(.brk, emit.code); } fn mirNop(emit: *Emit) InnerError!void { - return lowerToZoEnc(.nop, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToZoEnc(.nop, emit.code); } fn mirSyscall(emit: *Emit) InnerError!void { - return lowerToZoEnc(.syscall, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToZoEnc(.syscall, emit.code); } fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { @@ -226,7 +221,7 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { switch (ops.flags) { 0b00 => { // PUSH/POP reg - return lowerToOEnc(tag, ops.reg1, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToOEnc(tag, ops.reg1, emit.code); }, 0b01 => { // PUSH/POP r/m64 @@ -238,14 +233,13 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm, .base = ops.reg1, - }), emit.code) catch |err| emit.failWithLoweringError(err); + }), emit.code); }, 0b10 => { // PUSH imm32 assert(tag == .push); const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.push, imm, emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToIEnc(.push, imm, emit.code); }, 0b11 => unreachable, } @@ -259,15 +253,15 @@ fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.I for (bits.callee_preserved_regs) |reg, i| { if ((regs >> @intCast(u5, i)) & 1 == 0) continue; if (tag == .push) { - lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ + try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ .disp = @bitCast(u32, -@intCast(i32, disp)), .base = ops.reg1, - }), reg.to64(), emit.code) catch |err| return emit.failWithLoweringError(err); + }), reg.to64(), emit.code); } else { - lowerToRmEnc(.mov, reg.to64(), RegisterOrMemory.mem(.qword_ptr, .{ + try lowerToRmEnc(.mov, reg.to64(), RegisterOrMemory.mem(.qword_ptr, .{ .disp = @bitCast(u32, -@intCast(i32, disp)), .base = ops.reg1, - }), emit.code) catch |err| return emit.failWithLoweringError(err); + }), emit.code); } disp += 8; } @@ -279,8 +273,7 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { 0b00 => { const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; - lowerToDEnc(tag, 0, emit.code) catch |err| - return emit.failWithLoweringError(err); + try lowerToDEnc(tag, 0, emit.code); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = target, @@ -296,11 +289,10 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { 16 => .word_ptr, else => .qword_ptr, }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm }), emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm }), emit.code); } // JMP/CALL reg - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); }, 0b10 => { // JMP/CALL r/m64 @@ -308,7 +300,7 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { return lowerToMEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.fromBits(ops.reg1.size()), .{ .disp = imm, .base = ops.reg1, - }), emit.code) catch |err| emit.failWithLoweringError(err); + }), emit.code); }, 0b11 => return emit.fail("TODO unused JMP/CALL variant 0b11", .{}), } @@ -337,8 +329,7 @@ fn mirCondJmp(emit: *Emit, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerErr else => unreachable, }; const source = emit.code.items.len; - lowerToDEnc(tag, 0, emit.code) catch |err| - return emit.failWithLoweringError(err); + try lowerToDEnc(tag, 0, emit.code); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = target, @@ -368,8 +359,7 @@ fn mirCondSetByte(emit: *Emit, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) Inne }, else => unreachable, }; - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code); } fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -385,11 +375,9 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { if (ops.reg1.to64() == .rax) { // TEST rax, imm32 // I - return lowerToIEnc(.@"test", imm, emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToIEnc(.@"test", imm, emit.code); } - return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code); } // TEST r/m64, r64 return emit.fail("TODO TEST r/m64, r64", .{}); @@ -407,19 +395,19 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // RETF imm16 // I const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_far, imm, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToIEnc(.ret_far, imm, emit.code); }, 0b01 => { - return lowerToZoEnc(.ret_far, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToZoEnc(.ret_far, emit.code); }, 0b10 => { // RET imm16 // I const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_near, imm, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToIEnc(.ret_near, imm, emit.code); }, 0b11 => { - return lowerToZoEnc(.ret_near, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToZoEnc(.ret_near, emit.code); }, } } @@ -432,13 +420,11 @@ fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { // mov reg1, imm32 // MI const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); } // mov reg1, reg2 // RM - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); }, 0b01 => { // mov reg1, [reg2 + imm32] @@ -448,7 +434,7 @@ fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.fromBits(ops.reg1.size()), .{ .disp = imm, .base = src_reg, - }), emit.code) catch |err| emit.failWithLoweringError(err); + }), emit.code); }, 0b10 => { if (ops.reg2 == .none) { @@ -460,7 +446,7 @@ fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.fromBits(ops.reg2.size()), .{ .disp = imm, .base = ops.reg1, - }), ops.reg2, emit.code) catch |err| emit.failWithLoweringError(err); + }), ops.reg2, emit.code); }, 0b11 => { return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); @@ -482,7 +468,7 @@ fn mirArithMemImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm_pair.dest_off, .base = ops.reg1, - }), imm_pair.operand, emit.code) catch |err| emit.failWithLoweringError(err); + }), imm_pair.operand, emit.code); } inline fn setRexWRegister(reg: Register) bool { @@ -517,7 +503,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void .disp = imm, .base = ops.reg2, .scale_index = scale_index, - }), emit.code) catch |err| emit.failWithLoweringError(err); + }), emit.code); } fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { @@ -534,14 +520,14 @@ fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void .disp = 0, .base = ops.reg1, .scale_index = scale_index, - }), imm, emit.code) catch |err| emit.failWithLoweringError(err); + }), imm, emit.code); } // OP [reg1 + scale*rax + imm32], reg2 return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.fromBits(ops.reg2.size()), .{ .disp = imm, .base = ops.reg1, .scale_index = scale_index, - }), ops.reg2, emit.code) catch |err| emit.failWithLoweringError(err); + }), ops.reg2, emit.code); } fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { @@ -558,7 +544,7 @@ fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void .disp = imm_pair.dest_off, .base = ops.reg1, .scale_index = scale_index, - }), imm_pair.operand, emit.code) catch |err| emit.failWithLoweringError(err); + }), imm_pair.operand, emit.code); } fn mirArithMemIndexImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { @@ -581,7 +567,65 @@ fn mirArithMemIndexImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!v .disp = imm_pair.dest_off, .base = ops.reg1, .scale_index = scale_index, - }), imm_pair.operand, emit.code) catch |err| emit.failWithLoweringError(err); + }), imm_pair.operand, emit.code); +} + +fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const mir_tag = emit.mir.instructions.items(.tag)[inst]; + assert(mir_tag == .mov_sign_extend); + const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); + const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined; + switch (ops.flags) { + 0b00 => { + const tag: Tag = if (ops.reg2.size() == 32) .movsxd else .movsx; + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + }, + 0b01 => { + return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{ + .disp = imm, + .base = ops.reg2, + }), emit.code); + }, + 0b10 => { + return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{ + .disp = imm, + .base = ops.reg2, + }), emit.code); + }, + 0b11 => { + return lowerToRmEnc(.movsxd, ops.reg1, RegisterOrMemory.mem(.dword_ptr, .{ + .disp = imm, + .base = ops.reg2, + }), emit.code); + }, + } +} + +fn mirMovZeroExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const mir_tag = emit.mir.instructions.items(.tag)[inst]; + assert(mir_tag == .mov_zero_extend); + const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); + const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined; + switch (ops.flags) { + 0b00 => { + return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + }, + 0b01 => { + return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{ + .disp = imm, + .base = ops.reg2, + }), emit.code); + }, + 0b10 => { + return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{ + .disp = imm, + .base = ops.reg2, + }), emit.code); + }, + 0b11 => { + return emit.fail("TODO unused variant: movzx 0b11", .{}); + }, + } } fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -596,16 +640,16 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { if (ops.flags == 0b00) { // movabs reg, imm64 // OI - return lowerToOiEnc(.mov, ops.reg1, imm, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToOiEnc(.mov, ops.reg1, imm, emit.code); } if (ops.reg1 == .none) { // movabs moffs64, rax // TD - return lowerToTdEnc(.mov, imm, ops.reg2, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToTdEnc(.mov, imm, ops.reg2, emit.code); } // movabs rax, moffs64 // FD - return lowerToFdEnc(.mov, ops.reg1, imm, emit.code) catch |err| emit.failWithLoweringError(err); + return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); } fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -614,13 +658,11 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); }, 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code) catch |err| - emit.failWithLoweringError(err); + return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code); }, else => return emit.fail("TODO implement imul", .{}), } @@ -644,18 +686,18 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .base = src_reg, }), emit.code, - ) catch |err| emit.failWithLoweringError(err); + ); }, 0b01 => { // lea reg1, [rip + imm32] // RM const start_offset = emit.code.items.len; - lowerToRmEnc( + try lowerToRmEnc( .lea, ops.reg1, RegisterOrMemory.rip(Memory.PtrSize.fromBits(ops.reg1.size()), 0), emit.code, - ) catch |err| return emit.failWithLoweringError(err); + ); const end_offset = emit.code.items.len; // Backpatch the displacement const payload = emit.mir.instructions.items(.data)[inst].payload; @@ -666,12 +708,12 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { 0b10 => { // lea reg1, [rip + reloc] // RM - lowerToRmEnc( + try lowerToRmEnc( .lea, ops.reg1, RegisterOrMemory.rip(Memory.PtrSize.fromBits(ops.reg1.size()), 0), emit.code, - ) catch |err| return emit.failWithLoweringError(err); + ); const end_offset = emit.code.items.len; const got_entry = emit.mir.instructions.items(.data)[inst].got_entry; if (emit.bin_file.cast(link.File.MachO)) |macho_file| { @@ -710,7 +752,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .scale_index = scale_index, }), emit.code, - ) catch |err| emit.failWithLoweringError(err); + ); }, } } @@ -721,8 +763,7 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const n_strx = emit.mir.instructions.items(.data)[inst].extern_fn; const offset = blk: { // callq - lowerToDEnc(.call_near, 0, emit.code) catch |err| - return emit.failWithLoweringError(err); + try lowerToDEnc(.call_near, 0, emit.code); break :blk @intCast(u32, emit.code.items.len) - 4; }; if (emit.bin_file.cast(link.File.MachO)) |macho_file| { @@ -934,6 +975,9 @@ const Tag = enum { sbb, cmp, mov, + movsx, + movsxd, + movzx, lea, jmp_near, call_near, @@ -1200,6 +1244,9 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { .sbb => OpCode.oneByte(if (is_one_byte) 0x1a else 0x1b), .cmp => OpCode.oneByte(if (is_one_byte) 0x3a else 0x3b), .mov => OpCode.oneByte(if (is_one_byte) 0x8a else 0x8b), + .movsx => OpCode.twoByte(0x0f, if (is_one_byte) 0xbe else 0xbf), + .movsxd => OpCode.oneByte(0x63), + .movzx => OpCode.twoByte(0x0f, if (is_one_byte) 0xb6 else 0xb7), .lea => OpCode.oneByte(if (is_one_byte) 0x8c else 0x8d), .imul => OpCode.twoByte(0x0f, 0xaf), else => null, @@ -1367,6 +1414,10 @@ const Memory = struct { encoder.disp32(@bitCast(i32, mem_op.disp)); } } + + fn size(memory: Memory) u64 { + return memory.ptr_size.size(); + } }; fn encodeImm(encoder: Encoder, imm: u32, size: u64) void { @@ -1411,21 +1462,22 @@ const RegisterOrMemory = union(enum) { }, }; } -}; -const LoweringError = error{ - OutOfMemory, - OperandSizeMismatch, - RaxOperandExpected, + fn size(reg_or_mem: RegisterOrMemory) u64 { + return switch (reg_or_mem) { + .register => |reg| reg.size(), + .memory => |memory| memory.size(), + }; + } }; -fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .zo, false).?; const encoder = try Encoder.init(code, 1); opc.encode(encoder); } -fn lowerToIEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToIEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { if (tag == .ret_far or tag == .ret_near) { const encoder = try Encoder.init(code, 3); const opc = getOpCode(tag, .i, false).?; @@ -1442,10 +1494,7 @@ fn lowerToIEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) LoweringError!void encodeImm(encoder, imm, immOpSize(imm)); } -fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) LoweringError!void { - if (reg.size() != 16 and reg.size() != 64) { - return error.OperandSizeMismatch; // TODO correct for push/pop, but is it universal? - } +fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .o, false).?; const encoder = try Encoder.init(code, 3); if (reg.size() == 16) { @@ -1458,26 +1507,18 @@ fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) LoweringError! opc.encodeWithReg(encoder, reg); } -fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .d, false).?; const encoder = try Encoder.init(code, 6); opc.encode(encoder); encoder.imm32(@bitCast(i32, imm)); } -fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .m, false).?; const modrm_ext = getModRmExt(tag).?; switch (reg_or_mem) { .register => |reg| { - const op_size_mismatch = blk: { - if (tag.isSetCC() and reg.size() == 8) - break :blk false; - break :blk reg.size() != 64 and reg.size() != 16; - }; - if (op_size_mismatch) { - return error.OperandSizeMismatch; - } const encoder = try Encoder.init(code, 4); if (reg.size() == 16) { encoder.prefix16BitMode(); @@ -1493,17 +1534,11 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) encoder.modRm_direct(modrm_ext, reg.lowId()); }, .memory => |mem_op| { - if (mem_op.ptr_size != .qword_ptr and mem_op.ptr_size != .word_ptr) { - return error.OperandSizeMismatch; - } const encoder = try Encoder.init(code, 8); if (mem_op.ptr_size == .word_ptr) { encoder.prefix16BitMode(); } if (mem_op.base) |base| { - if (base.size() != 64) { - return error.OperandSizeMismatch; - } encoder.rex(.{ .w = false, .b = base.isExtended(), @@ -1515,18 +1550,15 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) } } -fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { return lowerToTdFdEnc(tag, reg, moffs, code, true); } -fn lowerToFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8)) InnerError!void { return lowerToTdFdEnc(tag, reg, moffs, code, false); } -fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8), td: bool) LoweringError!void { - if (reg.lowId() != Register.rax.lowId()) { - return error.RaxOperandExpected; - } +fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8), td: bool) InnerError!void { const opc = if (td) getOpCode(tag, .td, reg.size() == 8).? else @@ -1548,7 +1580,7 @@ fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8), } } -fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .oi, reg.size() == 8).?; const encoder = try Encoder.init(code, 10); if (reg.size() == 16) { @@ -1568,11 +1600,11 @@ fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) Low } } -fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) LoweringError!void { +fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) InnerError!void { const modrm_ext = getModRmExt(tag).?; + const opc = getOpCode(tag, .mi, reg_or_mem.size() == 8).?; switch (reg_or_mem) { .register => |dst_reg| { - const opc = getOpCode(tag, .mi, dst_reg.size() == 8).?; const encoder = try Encoder.init(code, 7); if (dst_reg.size() == 16) { // 0x66 prefix switches to the non-default size; here we assume a switch from @@ -1589,15 +1621,11 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.Arr encodeImm(encoder, imm, dst_reg.size()); }, .memory => |dst_mem| { - const opc = getOpCode(tag, .mi, dst_mem.ptr_size == .byte_ptr).?; const encoder = try Encoder.init(code, 12); if (dst_mem.ptr_size == .word_ptr) { encoder.prefix16BitMode(); } if (dst_mem.base) |base| { - if (base.size() != 64) { - return error.OperandSizeMismatch; - } encoder.rex(.{ .w = dst_mem.ptr_size == .qword_ptr, .b = base.isExtended(), @@ -1619,13 +1647,10 @@ fn lowerToRmEnc( reg: Register, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8), -) LoweringError!void { - const opc = getOpCode(tag, .rm, reg.size() == 8).?; +) InnerError!void { + const opc = getOpCode(tag, .rm, reg.size() == 8 or reg_or_mem.size() == 8).?; switch (reg_or_mem) { .register => |src_reg| { - if (reg.size() != src_reg.size()) { - return error.OperandSizeMismatch; - } const encoder = try Encoder.init(code, 4); encoder.rex(.{ .w = setRexWRegister(reg) or setRexWRegister(src_reg), @@ -1636,9 +1661,6 @@ fn lowerToRmEnc( encoder.modRm_direct(reg.lowId(), src_reg.lowId()); }, .memory => |src_mem| { - if (reg.size() != src_mem.ptr_size.size()) { - return error.OperandSizeMismatch; - } const encoder = try Encoder.init(code, 9); if (reg.size() == 16) { encoder.prefix16BitMode(); @@ -1646,9 +1668,6 @@ fn lowerToRmEnc( if (src_mem.base) |base| { // TODO handle 32-bit base register - requires prefix 0x67 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - if (base.size() != 64) { - return error.OperandSizeMismatch; - } encoder.rex(.{ .w = setRexWRegister(reg), .r = reg.isExtended(), @@ -1671,13 +1690,10 @@ fn lowerToMrEnc( reg_or_mem: RegisterOrMemory, reg: Register, code: *std.ArrayList(u8), -) LoweringError!void { - const opc = getOpCode(tag, .mr, reg.size() == 8).?; +) InnerError!void { + const opc = getOpCode(tag, .mr, reg.size() == 8 or reg_or_mem.size() == 8).?; switch (reg_or_mem) { .register => |dst_reg| { - if (dst_reg.size() != reg.size()) { - return error.OperandSizeMismatch; - } const encoder = try Encoder.init(code, 3); encoder.rex(.{ .w = setRexWRegister(dst_reg) or setRexWRegister(reg), @@ -1688,17 +1704,11 @@ fn lowerToMrEnc( encoder.modRm_direct(reg.lowId(), dst_reg.lowId()); }, .memory => |dst_mem| { - if (dst_mem.ptr_size.size() != reg.size()) { - return error.OperandSizeMismatch; - } const encoder = try Encoder.init(code, 9); if (reg.size() == 16) { encoder.prefix16BitMode(); } if (dst_mem.base) |base| { - if (base.size() != 64) { - return error.OperandSizeMismatch; - } encoder.rex(.{ .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), .r = reg.isExtended(), @@ -1722,10 +1732,7 @@ fn lowerToRmiEnc( reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8), -) LoweringError!void { - if (reg.size() == 8) { - return error.OperandSizeMismatch; - } +) InnerError!void { const opc = getOpCode(tag, .rmi, false).?; const encoder = try Encoder.init(code, 13); if (reg.size() == 16) { @@ -1733,9 +1740,6 @@ fn lowerToRmiEnc( } switch (reg_or_mem) { .register => |src_reg| { - if (reg.size() != src_reg.size()) { - return error.OperandSizeMismatch; - } encoder.rex(.{ .w = setRexWRegister(reg) or setRexWRegister(src_reg), .r = reg.isExtended(), @@ -1748,12 +1752,6 @@ fn lowerToRmiEnc( if (src_mem.base) |base| { // TODO handle 32-bit base register - requires prefix 0x67 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - if (base.size() != 64) { - return error.OperandSizeMismatch; - } - if (src_mem.ptr_size == .byte_ptr) { - return error.OperandSizeMismatch; - } encoder.rex(.{ .w = setRexWRegister(reg), .r = reg.isExtended(), diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index e3b79a8329..181d4c92e0 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -186,6 +186,19 @@ pub const Inst = struct { mov_mem_index_imm, /// ops flags: form: + /// 0b00 reg1, reg2, + /// 0b01 reg1, byte ptr [reg2 + imm32] + /// 0b10 reg1, word ptr [reg2 + imm32] + /// 0b11 reg1, dword ptr [reg2 + imm32] + mov_sign_extend, + + /// ops flags: form: + /// 0b00 reg1, reg2 + /// 0b01 reg1, byte ptr [reg2 + imm32] + /// 0b10 reg1, word ptr [reg2 + imm32] + mov_zero_extend, + + /// ops flags: form: /// 0b00 reg1, [reg2 + imm32] /// 0b00 reg1, [ds:imm32] /// 0b01 reg1, [rip + imm32] |
