5

I'm familiar with memory references of this form:

XXX ptr [base + index * size + displacement]

where XXX is some size (byte/word/dword/etc), both base and index are registers, size is a small power of two, and displacement is a signed value.

amd64 introduced rip-relative addressing. As I understand it, I should be able to use rip as a base register. However, when I try this with clang-900.0.39.2:

mov r8b, byte ptr [rip + rdi * 1 + Lsomething]

I get:

error: invalid base+index expression

mov r8b, byte ptr [rip + rdi * 1 + Lsomething]

Is it impossible to use an index register when using rip as the base register? Do I have to use lea to compute rip + Lsomething and then offset into that?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
zneak
  • 134,922
  • 42
  • 253
  • 328
  • 2
    RIP + Displacement is the only addressing allowed with RIP relative addressing. So you could use lea to get [rip + Lsomething] and place it in a general purpose register. That register can be used to complete the rest of the address calculation. No real way around the limitation. – Michael Petch Jan 06 '18 at 04:42
  • 1
    You can find it documented in the [Intel® 64 and IA-32 Architectures Software Developer’s Manual](https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf) section _2.2.1.6 RIP-Relative Addressing_ – Michael Petch Jan 06 '18 at 04:50
  • FYI, you *probably* want `movzx r8d, byte ptr [ ...]`, unless you specifically *want* to merge into the existing contents of R8 instead of starting a new dependency chain for that register. – Peter Cordes Jan 06 '18 at 08:13
  • @PeterCordes, thanks for the advice. I was writing a toy Meltdown PoC and this is the probe load, so the size or destination doesn't matter. – zneak Jan 06 '18 at 16:33

1 Answers1

6

No, [RIP + rel32] is the only addressing mode involving RIP. See also Referencing the contents of a memory location. (x86 addressing modes).

If you want maximum efficiency for indexing a static array, you need to make position-dependent code so you can use the table address as a 32-bit absolute disp32 in a normal addressing mode. This is allowed in Linux in position-depended executables, but not shared libraries (which have to be PIC). See 32-bit absolute addresses no longer allowed in x86-64 Linux? for how to use -fno-pie -no-pie when gcc is configured by default to make PIEs.

Otherwise for position-independent array indexing, lea rsi, [rip + Lsomething] and use pointer-increments or [rsi + rdi*1 + constant] addressing modes, or whatever other alternative works. (Non-indexed addressing modes sometimes save a uop on Intel CPUs so pointer-increments can be good, especially if you're unrolling so an add for each pointer can more than pay for itself, vs. using the same index for multiple arrays.)


It's not "RIP as a base register" in arbitrary addressing modes; there isn't room in the machine-code encoding for that. x86-64 has 16 possible base registers, encoded with 3 bits in the ModR/M or SIB byte + 1 bit in the optional REX prefix. Making RIP usable as a base with arbitrary addressing modes would have required bumping out some other register, and creating a lot of differences in effective-address decoding between 32 and 64-bit mode.

x86-32 has 2 redundant ways to encode [0x123456], i.e. no-base + disp32: with or without a SIB byte, because SIB has an encoding for no-base and no-index. See http://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing for a nice table, or see Intel's manuals.

The no-index SIB encoding makes it possible to encode [esp] instead of [esp+esp], because the ModR/M encoding that would mean base=RSP is the escape code that means "there's a SIB". They could have designed it so you could use esp as an index for bases other than esp, but nobody wants to use esp as an index in the first place. Fun fact: the no-base (with disp32) encoding uses what would have been the [ebp] with no displacement, which is why [ebp] is actually encoded as [ebp + disp8=0]. In x86-64 mode, this applies to R13 as well.

x86-64 re-purposes the shorter (no SIB) encoding for [disp32] into [RIP + disp32], aka [RIP + rel32].

32-bit absolute addresses ([disp32]) are still encodeable with the longer SIB encoding. (This is what NASM does by default, if you don't use default rel.) There's not even a [RIP + disp8] (e.g. for loading nearby code addresses). There is exactly one bit-pattern in the Mod and M fields of the ModR/M byte that encodes a RIP-relative address.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks for the detailed answer! Made me figure out [I had to fix the SIB disp32-only encoding](https://hg.ulukai.org/ecm/ldebug/rev/306b39bef3da). To be fair, I introduced that error when I [generalised the index=4 special case](https://hg.ulukai.org/ecm/ldebug/rev/a2b439f28bce) beyond supporting only SIB=24h (S=0 I=4 B=4), but failed to realise that mod=0 index=4 base=5 is both special cases together. Which reminds me, your text is not entirely accurate: As the S (scale) field is ignored, the SIB disp32-only encoding can actually be encoded in 4 different ways, with S equal to 0, 1, 2, or 3. – ecm Feb 03 '21 at 22:31
  • 1
    @ecm: Interesting point! There would have been room for a few new addressing modes, if any could be useful. But no clean way to involve a register number, so as far as being a good compiler target I can see why they wouldn't bother. Cramming in a limited choice of a few registers would just be a pain to decode, and there's no much I can see worth doing with just a 32-bit constant and RIP or not. More range by adding or subtracting (or scaling) the 32-bit number isn't needed enough to be worth it. – Peter Cordes Feb 03 '21 at 22:44