4

In instruction encoding we have an optional field REX prefix which is split like this:

Field contains
0100 – fixed constant value 
W – 1 when using 64-bit data
R – expands the Reg field to 4 bit 
X – expands Index field to 4 bit
B – expands the R/M field or Base to 4 bit

I asked my professor what "when using 64-bit data" really means and every time I get different answer:

  1. When using the new registers added to x64 like r9, r10, r9d etc...

  2. When using 64 - size register like rax, rbx, r9 etc...

  3. When the instruction uses (read/write) 64 bit data from memory.

I'm really confused, what's the correct answer (it could be none of these as I don't trust my professor anymore).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    Consult the instruction set reference. But yeah, for the newly added registers you need the RXB bits, and for 64 bit data you need the W bit. – Jester Jul 31 '21 at 17:34

1 Answers1

4

All three cases need a REX prefix:

  • #2 and #3 need the W bit set in the REX because they're "using 64-bit data" (operand-size)
  • #1 needs one of the other bits set, depending on which register(s) are "high" registers

For instance, addq %rax, %rbx, addq %rax, long_var, and addq $123, long_var all need the REX prefix with W bit set, since they all have a 64-bit operand size. addq %rax, %r9 needs both W and B because of the use of the "new" register.

However, addl %eax, %r9d doesn't need the W bit, since its operand size is 32 bits. It does still does need the B bit to use the new register r9d, so the prefix byte is still needed, but not because of "using 64-bit data". (Case 1)

It's possible to have multiple cases at once. addq (%r8, %r9, 8), %r10 would need all four bits set in the REX prefix, including W.

An example of pure case #3 would be movq $123, 0x1000 to store an 8-byte integer 123 (sign-extended from 32-bit) to memory at absolute address 0x1000, no registers at all involved, but the 64-bit operand-size can only be set with a REX prefix with W=1. More normally you'd movq $123, symbol(%rip) to reference static storage, and that still doesn't include any general-purpose integer registers, or movq $123, 8(%rsp) where the addressing mode involves are register but neither the source nor destination actually are registers.

See also https://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix

A few opcodes default to 64-bit operand-size and don't need a REX prefix; for example pushq $1 assembles to 6a 01. (And pushl is not encodeable; a REX.W=0 can't override it back to 32-bit. Only the 66 operand-size prefix can change it, to 16-bit so pushw is encodeable.) This is mostly stack operations, including call / ret and indirect calls / jumps, like ff 17 callq *(%rdi) that loads a new 64-bit RIP from memory, as well as pushing a 64-bit return address.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Are you sure 3 is right too? because I read: default address size is 64 bit –  Jul 31 '21 at 17:37
  • #3 does not deal with address size, it deals with operand size. – Jester Jul 31 '21 at 17:39
  • so what exactly "address size" let's say when it's 32 bit can I see an example? @Jester –  Jul 31 '21 at 17:40
  • @coolmo: The default **address** size is 64 bits. The W bit sets the size of the data. For instance `movl %eax, (%rbx)` has 32 bit data but 64 bit address, as the 64-bit register `rbx` is used for the address, and that's what you would get without any prefixes. If you want to do `movq %rax, (%rbx)` you need REX.W. If you added an address override `0x67`, you'd get `movl %eax, (%ebx)` or `movq %rax, (%ebx)` instead depending on if you also used REX.W. These are rarely useful in 64-bit code because addresses don't usually fit in 32 bits. – Nate Eldredge Jul 31 '21 at 17:41
  • 1) what about movl %eax, 0x1213 is this 32 or 64 address? –  Jul 31 '21 at 17:43
  • 2) and why default address size is 64 bits? in x86 memory addresses were 32 bits... –  Jul 31 '21 at 17:44
  • Unless specified otherwise it's 64 bits. Why? Because that was one of the reasons for switching to 64 bit, to give programs larger memory address space. `movl %eax, (%edx)` would be 32 bit address size. – Jester Jul 31 '21 at 17:47
  • 2
    @coolmo: (1) The question is irrelevant since `0x1213` would fit in either 32 or 64 bits. But most instructions can only encode a 32-bit displacement in an effective address. `mov reg, 0xdeadbeefdeadbeef` would be a 64-bit address, but it's not encodeable except in the special case when the register `reg` is `al/ax/eax/rax`. The address size override only affects how registers are used in effective addressing, not displacements. – Nate Eldredge Jul 31 '21 at 17:47
  • @coolmo (2) Because we want to be able to use more than 4 GB of memory! That was, for the most part, the entire point of introducing 64-bit CPUs, and it's why you're coding for x86-64 instead of old 32-bit x86. – Nate Eldredge Jul 31 '21 at 17:48
  • @NateEldredge so even when registers were locked to 32 bits the OS was using 64 addresses? that's strange since no full address can fit in 1 register –  Jul 31 '21 at 17:50
  • @coolmo: What do you mean by "registers locked to 32 bits"? Do you mean on 32-bit x86? On 32-bit x86 all addresses were 32 bits, period, and there was no such thing as a 64-bit address (not counting [PAE](https://en.wikipedia.org/wiki/Physical_Address_Extension)). Keep in mind that x86 (i.e. the 80386 and successors) and x86-64 are **different** and **incompatible** architectures, though they have many similarities, and x86-64 chips are able to run in an x86 mode. – Nate Eldredge Jul 31 '21 at 17:53
  • @NateEldredge you said: "Because we want to be able to use more than 4 GB of memory!" when I was talking bout x86... –  Jul 31 '21 at 17:55
  • @coolmo: Oh, you're asking about how 32-bit x86 chips were able to use more than 4 GB of memory? That's what PAE did, see my link above. Software continued to use 32-bit virtual addresses, but they were translated to 64-bit physical addresses by the CPU's paging mechanism. The OS did have to work with 64-bit physical addresses in order to set up the page tables, and yes, it would have to use two registers to hold a 64-bit physical address. The result was that, although each individual process was still limited to 4 GB, you could have several of them in memory at once. – Nate Eldredge Jul 31 '21 at 17:58
  • @coolmo: It was definitely awkward and not very satisfying. I think Intel regarded PAE as a stopgap until true 64-bit chips could take over the market. Intel hoped Itanium would do that, but of course it was eventually AMD64 that won. – Nate Eldredge Jul 31 '21 at 18:00
  • @NateEldredge thanks a lot, please see my last question (hope to get answer before my exam in 12 hours :) ) https://stackoverflow.com/questions/68604572/instruction-encoding-operand-size –  Jul 31 '21 at 18:01
  • @NateEldredge what you said is wrong, this command movw $8, 4(%r8d,%esi,4) has w turnned off –  Jul 31 '21 at 19:06
  • @NateEldredge although condition 3 is true here, can you update your answer –  Jul 31 '21 at 19:08
  • 1
    @coolmo: Condition 3 is false in your example. `movw $8, 4(%r8d,%esi,4)` is not writing 64 bit data to memory, it is writing 16 bit data (and by the way using a 32 bit address). If you want to write 64 bit data, use `movq` instead and you will get REX.W. I still believe my answer is correct. – Nate Eldredge Jul 31 '21 at 19:12