On how different Windows ABIs choose how to pass 32-bit values in 64-bit registers
Surveying the options and looking for commonalities. The post On how different Windows ABIs choose how to pass 32-bit values in 64-bit registers appeared first on The Old New Thing.

Different 64-bit processor ABIs have different policies on how 32-bit bit values are passed in 64-bit registers. Let’s see if we can find a pattern among the Windows ABIs.
Processor | 32-bit signed value | 32-bit unsigned value |
---|---|---|
AArch64 | Garbage | Garbage |
Alpha AXP | Sign extend | Sign extend |
ia64 | Garbage | Garbage |
MIPS64 | Sign extend | Sign extend |
POWER3 | Sign extend | Zero extend |
RISC-V | Sign extend | Sign extend |
x86-64 | Garbage | Garbage |
There are basically three groups.
- Always sign extend (Alpha AXP, MIPS64, RISC-V)
- Extend based on signedness of 32-bit type (POWER3)
- Garbage (AArch64, ia64, x86-64)
Sign-extending unsigned 32-bit types sure feels weird. I wonder why that is.
Let’s regroup the processors by putting those with similar policies together. A pattern emerges when you add another column: Does this processor support comparison instructions (such as conditional branch instructions) that operate only on the lower 32 bits of a register?
Processor | Policy | Can compare 32-bit values? |
---|---|---|
Alpha AXP | Always sign extend | No |
MIPS64 | Always sign extend | No |
RISC-V | Always sign extend | No |
POWER3 | Use signedness of 32-bit type | Yes |
AArch64 | Garbage | Yes |
ia64 | Garbage | Yes |
x86-64 | Garbage | Yes |
The architectures whose ABIs require that 32-bit values be sign-extended (even for unsigned types) to 64-bit values are precisely those which do not have the ability to compare 32-bit values.
If your processor can only compare full 64-bit values, then sign extending everything (even unsigned types) is the way to go because that allows you to use the 64-bit comparison instruction for 32-bit comparison, too!
For signed comparisons, sign extending 32-bit values to 64-bit values preserves the mathematical values, so the 64-bit comparison produces the same result as the hypothetical 32-bit comparison.
For unsigned comparisons, sign extension changes the mathematical value of values greater than or equal to 2³², but in a consistent manner: They are all increased by 0xFFFFFFFF`00000000. The relative order of the values doesn’t change, so the results of comparisons did not change. The numbers are still in the same relative order.
Zero-extending 32-bit values to 64-bit values would result in negative 32-bit values comparing greater than positive 32-bit values when compared as 64-bit signed values, so that’s not going to work.
POWER3’s policy of extending the value according to the signed-ness of the underlying type would also work, and it also avoids the phenomenon of -1 > 0U
, which is something that catches out beginners. Unfortunately, the C and C++ languages actually require that -1 > 0U
due to the signed-to-unsigned conversion rules, so this benefit goes wasted in those languages.
But at least the original mystery is solved. If your processor doesn’t support 32-bit comparisons, then sign-extending all 32-bit values (even the unsigned ones) is the natural choice.
Bonus chatter: Of course, processor designers are aware of these issues when they design their instruction set. Nowadays, we don’t have people trying to retrofit an ABI onto a newly-released processor. Rather, processor designers realize, “If we recommend an ABI that requires 32-bit parameters to be sign-extended to 64-bit values, then we can remove all the 32-bit comparison instructions from our instruction set!”
Bonus reading: What are the dire consequences of having 32-bit values in non-canonical form on Alpha AXP.
The post On how different Windows ABIs choose how to pass 32-bit values in 64-bit registers appeared first on The Old New Thing.