Assembly is Too High-Level: TEST r32, r/m32, exists in assembly, but not the machine

…And the TEST r32, r/m32 that exists in assembly is more just kind of a lie…

An interesting thing about instructions that use the ModR/M encoding is that both the source and destination operands can be a register, but not both can be a memory location. When it comes to the registers, this has been the source of a lot of cool redundancies. This post is about a cool memory encoding redundancy though. As you’ll note below, it usually takes two different machine encodings of the CMP instruction to use a 32-bit memory location for both the source or destination operands.

This is what the instructions would look like in assembly:

This is me looking at them in a debugger:

You’ll see both 0x39 and 0x3b encodings in the debugger screenshot (although out of order, that’s just because that’s the order I wrote them in the assembly source file).

Everything looks consistent so far. But now let’s look at test…

Assembly:

From Debugger:

What? It’s literally the same machine code for both instructions. Why is this? This actually isn’t something stupid, but something fairly smart that Intel did. First I’ll note that the Intel manual has an entry for this form of TEST:

However, they do not have a corresponding entry for a ‘r32, r/m32’ form. But really, is there any reason we need that other form? The result of ANDing 0x55 by 0xAA is the same as ANDing 0xAA by 0x55 (the result is 0x00 for both). This is not the case with CMP, where subtraction is implicitly done, and order of operands would change the result. There are only so many one-byte opcodes to go around, and Intel saves one of them by not making an encoding for the completely redundant ModR/M encoding.