{"id":110988,"date":"2025-03-24T07:00:00","date_gmt":"2025-03-24T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110988"},"modified":"2025-03-26T09:09:40","modified_gmt":"2025-03-26T16:09:40","slug":"20250324-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250324-00\/?p=110988","title":{"rendered":"On how different Windows ABIs choose how to pass 32-bit values in 64-bit registers"},"content":{"rendered":"<p>Different 64-bit processor ABIs have different policies on how 32-bit bit values are passed in 64-bit registers. Let&#8217;s see if we can find a pattern among the Windows ABIs.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<caption>Contents of upper 32 bits of<br \/>\n64-bit register holding a 32-bit value<\/caption>\n<tbody>\n<tr>\n<th>Processor<\/th>\n<th>32-bit signed value<\/th>\n<th>32-bit unsigned value<\/th>\n<\/tr>\n<tr>\n<td>AArch64<\/td>\n<td>Garbage<\/td>\n<td>Garbage<\/td>\n<\/tr>\n<tr>\n<td>Alpha AXP<\/td>\n<td>Sign extend<\/td>\n<td>Sign extend<\/td>\n<\/tr>\n<tr>\n<td>ia64<\/td>\n<td>Garbage<\/td>\n<td>Garbage<\/td>\n<\/tr>\n<tr>\n<td>MIPS64<\/td>\n<td>Sign extend<\/td>\n<td>Sign extend<\/td>\n<\/tr>\n<tr>\n<td>POWER3<\/td>\n<td>Sign extend<\/td>\n<td>Zero extend<\/td>\n<\/tr>\n<tr>\n<td>RISC-V<\/td>\n<td>Sign extend<\/td>\n<td>Sign extend<\/td>\n<\/tr>\n<tr>\n<td>x86-64<\/td>\n<td>Garbage<\/td>\n<td>Garbage<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>There are basically three groups.<\/p>\n<ul>\n<li>Always sign extend (Alpha AXP, MIPS64, RISC-V)<\/li>\n<li>Extend based on signedness of 32-bit type (POWER3)<\/li>\n<li>Garbage (AArch64, ia64, x86-64)<\/li>\n<\/ul>\n<p>Sign-extending unsigned 32-bit types sure feels weird. I wonder why that is.<\/p>\n<p>Let&#8217;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?<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<caption>Contents of upper 32 bits of<br \/>\n64-bit register holding a 32-bit value<\/caption>\n<tbody>\n<tr>\n<th>Processor<\/th>\n<th>Policy<\/th>\n<th>Can compare 32-bit values?<\/th>\n<\/tr>\n<tr>\n<td>Alpha AXP<\/td>\n<td>Always sign extend<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>MIPS64<\/td>\n<td>Always sign extend<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>RISC-V<\/td>\n<td>Always sign extend<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td style=\"border: currentcolor 3px; border-style: solid none;\">POWER3<\/td>\n<td style=\"border: currentcolor 3px; border-style: solid none;\">Use signedness of 32-bit type<\/td>\n<td style=\"border: currentcolor 3px; border-style: solid none;\">Yes<\/td>\n<\/tr>\n<tr>\n<td>AArch64<\/td>\n<td>Garbage<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>ia64<\/td>\n<td>Garbage<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>x86-64<\/td>\n<td>Garbage<\/td>\n<td>Yes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>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.<\/p>\n<p>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!<\/p>\n<p>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.<\/p>\n<p>For unsigned comparisons, sign extension changes the mathematical value of values greater than or equal to 2\u00b3\u00b9, but in a consistent manner: They are all increased by <tt>0xFFFFFFFF`00000000<\/tt>. The relative order of the values doesn&#8217;t change, so the results of comparisons did not change. The numbers are still in the same relative order.<\/p>\n<p>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&#8217;s not going to work.<\/p>\n<p>POWER3&#8217;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 <code>-1 &gt; 0U<\/code>, which is something that catches out beginners. Unfortunately, the C and C++ languages actually require that <code>-1 &gt; 0U<\/code> due to the signed-to-unsigned conversion rules, so this benefit goes wasted in those languages.<\/p>\n<p>But at least the original mystery is solved. If your processor doesn&#8217;t support 32-bit comparisons, then sign-extending all 32-bit values (even the unsigned ones) is the natural choice.<\/p>\n<p><b>Bonus chatter<\/b>: Of course, processor designers are aware of these issues when they design their instruction set. Nowadays, we don&#8217;t have people trying to retrofit an ABI onto a newly-released processor. Rather, processor designers realize, &#8220;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!&#8221;<\/p>\n<p><b>Bonus reading<\/b>: <a title=\"The Alpha AXP, part 16: What are the dire consequences of having 32-bit values in non-canonical form?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20170829-00\/?p=96897\"> What are the dire consequences of having 32-bit values in non-canonical form on Alpha AXP<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Surveying the options and looking for commonalities.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110988","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Surveying the options and looking for commonalities.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110988","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110988"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110988\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110988"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110988"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110988"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}