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.

Mar 25, 2025 - 01:12
 0
On how different Windows ABIs choose how to pass 32-bit values in 64-bit registers

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.

Contents of upper 32 bits of
64-bit register holding a 32-bit value
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?

Contents of upper 32 bits of
64-bit register holding a 32-bit value
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.