{"id":112759,"date":"2026-10-07T07:00:00","date_gmt":"2026-10-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112759"},"modified":"2026-10-07T19:45:19","modified_gmt":"2026-10-08T02:45:19","slug":"20261007-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20261007-00\/?p=112759\/","title":{"rendered":"How can undefined opcodes <CODE>ud0<\/CODE> and <CODE>ud1<\/CODE> have parameters? How undefined were they?"},"content":{"rendered":"<p>Gunnar Dalsnes wondered <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260910-00\/?p=112689\/&amp;commentid=144727#comment-144727\"> how <code>ud0<\/code> and <code>ud1<\/code> could have parameters if they were undefined<\/a>? &#8220;Does it mean they were not completely undefined, just undocumented and not completely implemented?&#8221;<\/p>\n<p>The opcodes <code>ud0<\/code> and <code>ud1<\/code> have no definition, but that&#8217;s not the same as being architecturally an &#8220;undefined instruction&#8221;. They live in a purgatory where they were not assigned a meaning, but were also not officially declared to be meaningless.<\/p>\n<p>Originally, these byte sequences went through the instruction decoder and happened to slip through a few cracks before somebody finally noticed, &#8220;Wait a second, I don&#8217;t know how to execute this.&#8221;<\/p>\n<p>You can see this when you look at the instructions that are encoded as <code>00001111 11111xxx<\/code>, as of the Pentium III.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Bits<\/th>\n<th>Bytes<\/th>\n<th>Opcode<\/th>\n<th>Operand 1<\/th>\n<th>Operand 2<\/th>\n<th>Meaning<\/th>\n<\/tr>\n<tr>\n<td><tt>00001111 11111000<\/tt><\/td>\n<td><tt>0F F8<\/tt><\/td>\n<td><tt>PSUBB<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed bytes<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111001<\/tt><\/td>\n<td><tt>0F F9<\/tt><\/td>\n<td><tt>PSUBW<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed words<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111010<\/tt><\/td>\n<td><tt>0F FA<\/tt><\/td>\n<td><tt>PSUBD<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed dwords<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111011<\/tt><\/td>\n<td><tt>0F FB<\/tt><\/td>\n<td colspan=\"4\" align=\"center\">No meaning assigned<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111100<\/tt><\/td>\n<td><tt>0F FC<\/tt><\/td>\n<td><tt>PADDB<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Add packed bytes<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111101<\/tt><\/td>\n<td><tt>0F FD<\/tt><\/td>\n<td><tt>PADDW<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Add packed words<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111110<\/tt><\/td>\n<td><tt>0F FE<\/tt><\/td>\n<td><tt>PADDD<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Add packed dwords<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111111<\/tt><\/td>\n<td><tt>0F FF<\/tt><\/td>\n<td colspan=\"4\" align=\"center\">No meaning assigned<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The byte sequences <code>0F FB<\/code> and <code>0F FF<\/code> had yet to be assigned a meaning. You can see how the instruction decoder could take a shortcut and say, &#8220;Well, all the instructions in this range, or at least all the ones that I care about, take an mm registers and an mm\/m64 operand, so I&#8217;ll just save myself some transistors and decode all of them with two parameters (mm, mm\/m64).&#8221; And then after decoding, it would use bit 3 to decide whether to set up the arithmetic unit for an add or subtract, and it would use bits 0 and 1 to decide how to subdivide the bits into saturating units.<\/p>\n<p>And if you gave it a <code>0F FF<\/code>, it would be only in that last step that the decoder would realize &#8220;Oh dear, I don&#8217;t know what to do with a bit combination of <tt>11<\/tt>. I&#8217;ll raise an invalid opcode instruction.&#8221;<\/p>\n<p>The invalid opcode instruction got raised <i>after<\/i> the operands were parsed.<\/p>\n<p>You can see the trouble that <code>0F FF<\/code> created when those empty slots started to get filled in by the SSE instructions.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Bits<\/th>\n<th>Bytes<\/th>\n<th>Opcode<\/th>\n<th>Operand 1<\/th>\n<th>Operand 2<\/th>\n<th>Meaning<\/th>\n<\/tr>\n<tr>\n<td><tt>00001111 11111000<\/tt><\/td>\n<td><tt>0F F8<\/tt><\/td>\n<td><tt>PSUBB<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed bytes<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111001<\/tt><\/td>\n<td><tt>0F F9<\/tt><\/td>\n<td><tt>PSUBW<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed words<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111010<\/tt><\/td>\n<td><tt>0F FA<\/tt><\/td>\n<td><tt>PSUBD<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed dwords<\/td>\n<\/tr>\n<tr style=\"background-color: color(from currentcolor srgb r g b \/15%);\">\n<td><tt>00001111 11111011<\/tt><\/td>\n<td><tt>0F FB<\/tt><\/td>\n<td><tt>PSUBQ<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Subtract packed qwords<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111100<\/tt><\/td>\n<td><tt>0F FC<\/tt><\/td>\n<td><tt>PADDB<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Add packed bytes<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111101<\/tt><\/td>\n<td><tt>0F FD<\/tt><\/td>\n<td><tt>PADDW<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Add packed words<\/td>\n<\/tr>\n<tr>\n<td><tt>00001111 11111110<\/tt><\/td>\n<td><tt>0F FE<\/tt><\/td>\n<td><tt>PADDD<\/tt><\/td>\n<td>mm<\/td>\n<td>mm\/m64<\/td>\n<td>Add packed dwords<\/td>\n<\/tr>\n<tr style=\"background-color: color(from currentcolor srgb r g b \/15%);\">\n<td><tt>00001111 11111111<\/tt><\/td>\n<td><tt>0F FF<\/tt><\/td>\n<td colspan=\"4\" align=\"center\">I want to put <tt>PADDQ<\/tt> here but I can&#8217;t<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>the natural place to put the <code>PADDQ<\/code> instruction is <code>0F FF<\/code>, but people had already been using <code>0F FF<\/code> with the expectation that it raises an illegal instruction exception. Making it a valid instruction would break those programs, so Intel had to move <code>PADDQ<\/code> to the rather awkward location <code>0F D4<\/code>.<\/p>\n<p><b>Bonus chatter<\/b>: Undefined instructions with parameters are actually not uncommon. For example, on AArch64, <a title=\"The AArch64 processor (aka arm64), part 19: Miscellaneous instructions\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220822-00\/?p=107032\"> there is a range of 65,536 instructions set aside as permanently undefined<\/a>, so the <code>udf<\/code> instruction takes a 16-bit immediate to specify which invalid opcode you want. The PDP-10 reserved opcode 000 as a permanently illegal instruction, and it carries a register and a memory address as parameters. (Because <i>all<\/i> PDP-10 instructions carry a register and a memory address as parameters.)<\/p>\n<p>There are also so-called &#8220;unofficial opcodes&#8221; which are instructions that are not part of the instruction set architecture, but for which people reverse-engineered a consistent behavior and began to rely on it. (The 6502 processor is <a title=\"How MOS 6502 Illegal Opcodes really work\" href=\"https:\/\/www.pagetable.com\/31\"> well-known in nerd circles for having undergone this type of analysis<\/a>.) The <code>0F FF<\/code> is one of these &#8220;unofficial opcodes&#8221; that was popular enough that Intel felt pressure to maintain backward compatibility with it, even though it was never architecturally documented or supported.<\/p>\n<p><b>Bonus bonus chatter<\/b>: It appears that the mnemonic <code>ud1<\/code> was <a href=\"https:\/\/github.com\/netwide-assembler\/nasm\/blob\/nasm-0.98.x\/CHANGES#L827-L831\"> introduced by the nasm assembler<\/a>:<\/p>\n<pre>* Added the following new instructions: SYSENTER, SYSEXIT, FXSAVE,\r\n  FXRSTOR, UD1, UD2 (the latter two are two opcodes that Intel\r\n  guarantee will never be used; one of them is documented as UD2 in\r\n  Intel documentation, the other one just as \"Undefined Opcode\" --\r\n  <span style=\"border: solid 1px currentcolor;\">calling it UD1 seemed to make sense<\/span>.)\r\n<\/pre>\n<p>It seems obvious that <code>ud1<\/code> is also the name that Intel gave internally to that legacy instruction. Otherwise, there would be no need to call the new one <code>ud2<\/code>!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>They weren&#8217;t defined, not even as undefined.<\/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":[2],"class_list":["post-112759","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-history"],"acf":[],"blog_post_summary":"<p>They weren&#8217;t defined, not even as undefined.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112759","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=112759"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112759\/revisions"}],"predecessor-version":[{"id":112760,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112759\/revisions\/112760"}],"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=112759"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112759"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112759"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}