{"id":111390,"date":"2025-07-18T07:00:00","date_gmt":"2025-07-18T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111390"},"modified":"2025-07-18T07:50:11","modified_gmt":"2025-07-18T14:50:11","slug":"20250718-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250718-00\/?p=111390","title":{"rendered":"The case of the invalid instruction exception on an instruction that should never have executed"},"content":{"rendered":"<p>The image processing folks added specialized AVX2 versions of their code, but found that it was crashing with an illegal instruction exception. The code went something like this:<\/p>\n<pre>void SwizzleAVX2(uint32_t* source, uint32_t* destination, uint32_t count)\r\n{\r\n    \u27e6 do stuff using AVX-only instructions \u27e7\r\n    \u27e6 such as _mm256_cvtepu8_epi16 \u27e7\r\n}\r\n\r\nvoid SwizzleSSE4(uint32_t* source, uint32_t* destination, uint32_t count)\r\n{\r\n    \u27e6 do stuff using SSE4 instructions \u27e7\r\n    \u27e6 such as _mm_cvtepu8_epi16 \u27e7\r\n}\r\n\r\nbool hasAVX2; \/\/ initialized elsewhere\r\n\r\nvoid Swizzle(uint32_t* source, uint32_t* destination, uint32_t count)\r\n{\r\n    if (hasAVX2) {\r\n        SwizzleAVX2(source, destination, count);\r\n    } else {\r\n        SwizzleSSE4(source, destination, count);\r\n    }\r\n}\r\n<\/pre>\n<p>This looks good, doesn&#8217;t it? We check whether AVX2 instructions are available, and if so, we use the AVX2 version; otherwise we use the SSE4 version.<\/p>\n<p>But in fact, this code crashes with an invalid instruction exception on systems that do not have AVX2. How can that be?<\/p>\n<p>Compiler optimization.<\/p>\n<p>According to the &#8220;as-if&#8221; rule, the compiler is permitted to perform any optimization that a program cannot legitimately detect, where &#8220;legitimately&#8221; means &#8220;within the rules of the language&#8221;.<\/p>\n<p>What happened is that the compiler first inlined the <code>SwizzleAVX2<\/code> and <code>SwizzleSSE4<\/code> functions into the <code>Swizzle<\/code> function, and then it reordered the instructions so that some of the AVX2 instructions from <code>SwizzleAVX2<\/code> were moved in front of the test of the <code>hasAVX2<\/code> variable. For example, maybe <code>SwizzleAVX2<\/code> started by setting some registers to zero. The compiler might have decided to do this because profiling revealed that <code>hasAVX2<\/code> is usually true, so it wants to get the registers ready in anticipation of using them for the rest of the <code>SwizzleAVX2<\/code> function.<\/p>\n<p>Unfortunately, the compiler doesn&#8217;t realize that our test of <code>hasAVX2<\/code> was specifically intended to prevent any AVX2 instructions from running. The concept of &#8220;instructions that might not be available&#8221; does not arise in the C or C++ language specifications, so there is nothing in the language itself that addresses the matter.<\/p>\n<p>There are some directives you can use to tell the compiler that certain memory operations must occur in a specific order. For example, you can use interlocked operations with acquire or release semantics, or you can use <code>std::<wbr \/>atomic_<wbr \/>thread_<wbr \/>fence<\/code>, or you can use explicit memory barriers.<\/p>\n<p>However, none of them are of use here because the offending instruction isn&#8217;t a memory instruction, so memory ordering directives have no effect.<\/p>\n<p>The (somewhat unsatisfying) solution was to mark the AVX version as noinline so that the compiler cannot reorder instructions out of it.<\/p>\n<pre><span style=\"border: solid 1px currentcolor;\">__declspec(noinline)<\/span>\r\nvoid SwizzleAVX2(uint32_t* source, uint32_t* destination, uint32_t count)\r\n{\r\n    \u27e6 do stuff using AVX-only instructions \u27e7\r\n    \u27e6 such as _mm256_cvtepu8_epi16 \u27e7\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I don&#8217;t recall ever asking you to do that.<\/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-111390","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>I don&#8217;t recall ever asking you to do that.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111390","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=111390"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111390\/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=111390"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111390"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111390"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}