Search code examples
assemblyx86addressing-mode

Change this short assembler code so it works just without register-indexed


Task from an old exam (I can provide source if you don't believe me):

Change the following assembler code without using register-indexed addressing. Do not use more than 4 instructions.

mov eax, [ebx+8]
add eax, 1
add ebx, 8
mov [ebx], eax

I have changed that to:

mov eax, [ebx]
add eax, 9
add ebx, 8
mov [ebx], eax

I cannot imagine it's done that easily that's why I'm not sure :D

if that's right what I did, an alternative would be:

mov eax, [ebx]
add eax, 1
add ebx, 16
mov [ebx], eax

Or is it now completely wrong? Thank you very much everyone!

Edit: Corrected version:

mov eax, [ebx]
add [ebx], 9
add ebx, 8
mov [ebx], eax

Now?


Solution

  • First of all, none of the addressing modes in the original use an index register. [ebx+8] can (and will) use EBX as a base register, not an index register, because it can do that without a SIB byte in the machine-code encoding. So the addressing mode is base+disp8, so the encoding of the ModRM byte would be (in binary)

    • Mod=01 (base+disp8)
    • R/M = 011 (EBX)
    • REG = 000 (EAX as the register destination)

    So the ModRM byte would be 0x43, as per Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte, in Intel's instruction-set reference manual (Volume 2). (See the tag wiki for links to the PDFs).

    If there was a scale factor on EBX (like [ebx*2 + 8]), it would have to use the disp32 + index addressing mode. (See also x86 addressing modes).


    Presumably you actually mean you can't use a displacement in your addressing mode.

    In that case, the first instruction can't be the load, because you have to calculate the address in a register first. The question makes it easy for you, by later calculating the same ebx+8 value that you need as an address, using an ADD instruction. So you can just reorder, instead of having to modify EBX twice.

    add ebx, 8
    mov eax, [ebx]
    add eax, 1
    mov [ebx], eax
    

    Or slower but fewer instructions:

    add   ebx, 8
    add   dword [ebx], 1     ; read-modify-write
    mov   eax, [ebx]         ; and then reload
    

    x86 has many weird and wonderful instructions, including XADD. You could even do this:

                               ; LEA ebx, [ebx+8]  might count as breaking the rules, even though it's not a load.
    sub   ebx, -8              ; use a different instruction just for variety.  
    mov   eax, 1
    xadd  dword [ebx], eax     ; read-modify-write, leaving the old value in eax
    inc   eax                  ; do the same add again, so the register matches what xadd put into memory.
    

    But don't do this. XADD is slower than normal simple instructions. It's main purpose is multi-threaded synchronization. C++11's std::atomic::fetch_add is the same operation that XADD implements, so fetch_add() can compile efficiently on x86 to a lock xadd.