11

Many CPUs have "holes" in the ISA, where the bit pattern is left undefined.

Considering the Z80 and the 6502, many people have mapped out what these undocumented instructions actually do.

Take for example this cheatsheet of the Thumb instruction set (PDF warning, 116K). Some bit patterns are marked as "undefined" or "unpredictable". What is known about these instructions? Are any of them stable/useful enough for use, have they seen use in existing software? So far my google-fu hasn't turned anything obvious up.

9
  • 6
    Speaking as someone who writes programs for a living, you'd have to be nuts to ship code that depends on "unpredictable" behaviour, whether from hardware or your compiler; it may be different in the next release/stepping/whatever. Of course, if this is just an intellectual exercise at understanding an implementation for the joy of it, then that's a perfectly good motive. Jun 15, 2021 at 11:32
  • 2
    But is this retrocomputing? ARM7 is a 21st-century ISA. Jun 15, 2021 at 11:34
  • 4
    @another-dave Don't get confused between the ARM7 core from the 1990s (based on the ARMv4T architecture) and the ARMv7-A/R/M family architectures that are much more recent.
    – Chromatix
    Jun 15, 2021 at 11:42
  • 9
    @another-dave yeah the ARM nomenclature is batcrap insane. ARM7TDMI is ARMv4, not ARM7.
    – OmarL
    Jun 15, 2021 at 12:22
  • 1
    @Raffzahn Since the question is specifically about the behaviour of undefined instructions in Thumb mode, it cannot refer to the later Thumb-2 instruction set that was introduced close to the release of ARMv7, and which is what all "new" ARM cores implement. Thumb-2 specifically makes use of some previously illegal and undefined encodings to add new instructions. Also, though the GBA might be a c.2000 product, the ARM7TDMI was available for some years before that as an embeddable core (and still is today as a microcontroller).
    – Chromatix
    Jun 15, 2021 at 15:28

5 Answers 5

11

The Thumb instruction set is a compressed form of a subset of the full-size ARM instructions. As part of the fetch unit, there is a decoder which expands each 16-bit Thumb instruction into the equivalent 32-bit ARM instruction when the CPU is in Thumb mode, but passes the instruction words along unchanged when in ARM mode. This means the decoder's behaviour is the focus of the question.

In the given table, Thumb opcodes marked Undefined Instruction are expanded to ARM opcodes that are themselves Undefined Instruction, and will thus trigger a trap when the ARM instruction reaches the Decode stage of the pipeline.

However, those opcodes marked Unpredictable may be expanded to valid ARM opcodes, but are not part of the ARMv4T specification. This is the result of omitting gates that would have been needed to explicitly detect these opcodes and convert them to Undefined Instruction opcodes. Instead, they will be decoded similarly to nearby valid opcodes. There is no clear advantage to using the Unpredictable opcodes rather than the equivalent specified encodings.

Many of the Unpredictable and Undefined Instruction opcodes are re-specified explicitly in later versions of the Thumb instruction set, as used in the StrongARM, ARM9, ARM11, and subsequent Thumb-2 supporting cores. Thumb-2, in particular, has support for encoding 32-bit ARM instructions, less their conditional field, into a Thumb instruction stream without switching modes. This was necessary to enable the Cortex-M microcontrollers which always operate in Thumb mode, since they don't have an ARM mode. However, the specified decoding of these opcodes in later specifications is generally different from the "accidental" decode behaviour of the earlier ARM7 core.

2
  • 2
    So it seems like for the Gameboy Advance CPU, the Unpredictable ones are really aliased to other, existing instructions, is that right?
    – OmarL
    Jun 20, 2021 at 16:39
  • 1
    @OmarL I believe so. Notice that one of the Unpredictable bit patterns corresponds to an ADD Rd,Rn with the high bits of both Rd and Rn both low; this is redundant with a three-operand ADD Rd,Rn,Rm instruction encoding which can only access the low registers (where Rm would be the same as Rd). But the simplest and fastest possible decoder would decode the Unpredictable opcode as the corresponding two-operand ADD between two low registers. In fact this is formalised as a valid encoding of ADD in Thumb-2, but earlier CPUs are not guaranteed to decode it correctly.
    – Chromatix
    Jun 20, 2021 at 19:16
4

Undefined instructions are those not understood by the main ARM CPU core. If there is also no coprocessor that understands them, then an undefined instruction trap is executed to allow for software to for example use them as system calls or to emulate missing hardware.

But there are some instructions opcodes that are not defined, and these do not cause undefined instruction trap either. Manual says these instructions must not be used, because they can be defined later, and manual also says the behaviour what these instructions might do is unpredictable.

The instructions are unpredictable, because the fastest way is to just not to expect undefined instructions to appear in code. Different implementations can work differently and they are free to do so.

So, these instructions are well-defined in that sense - do not use, if you do it might do anything, as they are reserved for future use.

3
  • Doesn't really answer the question: What do they do?
    – user253751
    Jun 26, 2021 at 0:21
  • It explains why the question can't be answered without reference to specific hardware (they probably "do" something different in different revisions of hardware - perhaps even in different production runs). Dec 17, 2021 at 13:19
  • 1
    @TobySpeight: Some processor core designs have gone through numerous revisions that treat undocumented opcodes differently, but some historical designs have been very consistent. All NMOS 6502 cores that don't document any new instructions, for example, process most undocumented opcodes consistently.
    – supercat
    Dec 19, 2021 at 18:00
3

After the 8 bit processors (6510, Z80 & so on), the decode units tend to do one of 2 things. They either map illegal op-codes to become NOP instructions (as the 65C816 did) or trigger an exception. The ARM processors actually have a CPU mode that is reserved for managing illegal instructions. The number of gates to do this became, in relative to the total number of gates on the processor, trivial.

The ARM solution is ACTUALLY very clever because it makes it possible to create your own instructions. They wouldn't be very fast but they would work.

As for Thumb specifically, well as others have noted, the ARM7 simply converts each 16-bit Thumb instruction into an ARM 32 bit instruction. On the other hand, the Cortex series of processors actually work using the raw 16 (and occasionally 32) bit instructions. I did perform a test and the M0 & M0+ processors indeed cause an exception.

What is MORE intriguing about Thumb is that there are a few instructions that can produce unpredictable results. The ones that spring to mind are:

RORS Rd,Rd - i.e. rotate Rd by the value in bits 0-7 of Rd. WHY it uses bits 0-7 I do not know since 32 is the maximum one would generally use. The other shift instructions also use bits 0-7.

I am a HUGE fan of Sophie Wilson and her decision to use bits 0-7 WILL have had a logical foundation but nobody else is aware of it.

Another Thumb instruction that intrigues me is the MULS which multiplies together two registers i.e. 32-bit x 32-bit & returns the bottom 32 bits. Now, on earlier versions, the Z & N flags were set based on the result but the C & V flags were corrupted. I can only guess WHY the flags were corrupted but more importantly, I could never figure out any use for the flags that were returned. So many people mistakenly thought that C flagged the result being >32 bits.... but it doesn't.

Finally, I think that the SVC (formally SWI i.e. software interrupt) instruction baers some investigation. It allows an immediate 8-bit value to be moved to and from the processor status. I strongly suspect that the control(s) that this instruction support are chosen by the maker of each specific ARM Cortex based design. I don't know if you have come across any M0 implementations that have a cache. The one I saw was only 32 bytes & was direct mapped but it meant that a CPU performing a copy wouldn't tie up the bus if DMA or such also needed RAM access.

I don't know how familiar you are with ARMs TCM but I for one would have much preferred a 64-byte scratch pad to a 32-byte cache. MY personal reason is that it would be big enough to store a 32-bit x 32-bit ---> 64-bit signed multiply. I'm writing an MP2/MP2.5/MP3/MP4/MELP/ACELP audio decode suite and the KEY presumption in their designs is that the host CPU has a fast multiply. As it is, I've managed to get the code into 17 instructions & 17 cycles. If my MULSHIFT32 (it actually returns the top 32 bits of the 64 bit result) left the bus free, it would quickly be used by the DMA....

5
  • One nice feature of having shift use more than 5 bits is that mov r0, #32 ; lsls r1, r0 will return the mathematically correct 0, instead of being an annoying no-op. (And having mov r0, #33 ; lsls r1, r0 do a left-shift by 1 is even more annoying.) x86 does mask off all but 4/5/6 bits, and I think all programmers hate it. This is also why uint32_t x; y = x << 32; has to be undefined behavior in C; because different machines handle it differently. Dec 15, 2021 at 5:45
  • (Oh, that's right - x86 is even worse. It masks to 5 bits for 8/16/32 bit shifts, and 6 for 64 bits. So mov cl, 16 ; shl ax, cl would properly give you 0, but mov cl, 32 ; shl eax, cl will not. And what's worse is they changed this behavior between 8086 and 80286; 8086 didn't mask at all and mov cl, 129 ; shl ax, cl would return 0, but take about 129 clock cycles to do it.) Dec 15, 2021 at 5:52
  • I think it was the fact that different machines handled >regsize shifts differently that caused C standard fabricators to devise those shifts finally as undefined behavior, not the reverse. And now it becomes a self-sustaining endless loop of instruction set designers looking into C standard and vice versa. So the good thing of >regsize shifts is now completely clobbered.
    – lvd
    Dec 15, 2021 at 12:06
  • 1
    @NateEldredge: I suspect classification as UB was motivated more by chips like the Transputer, which from what I read would process 1<<-1 by processing 4294967295 shifts operations with interrupts disabled, which is rather worse than the Intel behavior. Note that the authors of the Standard recognized that nobody wanting to sell compilers would interpret the Standard's failure to require that implementations behave more sanely on commonplace platforms as an invitation for compilers to behave in gratuitously nonsensical fashion; they thus saw no reason not to classify such shifts as UB.
    – supercat
    Dec 15, 2021 at 16:18
  • @NateEldredge Actually, your mov cl, 129 ; shl ax, cl example wouldn't need about 129 clocks, but about 520 clocks, because the microcoded loop in the 8086 needs four clocks per shift/rotate iteration, see os2museum.com/wp/undocumented-8086-opcodes-part-i/… . Dec 18, 2021 at 18:17
1

Preface:

It is important to keep in mind that ARM instruction sets, as defined by ARM, are just that, definitions, not implementations. While ARM does provide templates, manufactures do their implementation independent of each other and ARM. So anything not defined by ARM is implementation and chip specific and may change even within the same manufacturer and type in follow up versions.


First it might be useful to remember that this is neither an official ARM table or naming, nor a default ARM core, but a third party compilation for the GBA's custom ARM7TDMI implementation (*1)

Beside that the distinction is rather obvious encodings marked with

  • unpredictable are redundant encodings, while
  • undefined mark simply what it says, instructions that have (at that time) no function assigned.

The unpredictable are all encodings of the ADD/CMP/MOV group meant to access the 'higher' registers (R8..15) with both high bits zeroed, making them work between lower registers (R0..R7) only. These are simply redundant to regular ADD/CMP/MOV (*2).

Within ARM manuals, they are all Undefined. (*3)

For these instructions it's up to the implementation if they get translated into their counter parts, like early Thumb implementations did, or not.

Since they were defined by ARM as unused, they could and did redefine them - like with ARMv5 for MOV. The redundant MOV was made not only made a real opcode, but changed in function as well. The high register versions of MOV are now copy (CPY) instead of move - meaning no flags are touched.

Maybe that's why the authors of that table marked them as 'unpredictable'

The undefined in contrast are simply the remaining encodings that have no assigned function. As well Undefined in genuine ARM documentation.

Like with all undefined behaviour, they may or may not have some working depending on silicon and their workings may not only change between manufacturers, but as well between chip versions - not to mention assigning functions in later designs. For example the undefined version of BX (*4) was turned into BLX with ARMv5.

Beside these clearly marked as undefined, there are some other instruction set holes: Wherever a field is marked as (SB)Z - all encodings other than zeroes can (and will) be used for future extensions.


P.S.: Have you already tried to ask that on SO, which might be a more appropriate audience?


*1 - An easy visible oddity is that this table includes all 16 conditions for 8 bit relative branches, whereas Thumb excludes always and never (14/15) - with never later (v5) be redefined as software interrupt.

*2 - Check the line right above each.

*3 - With a capital 'U' :)

*4 - Interestingly called unpredictable in the ARMv5E Architecture Reference Manual.

3
  • In the architecture manual for the DEC Alpha, and I think for other CPUs as well, there is a substantial difference between "unpredictable behavior" and "undefined behavior": if a program which invokes unpredictable behavior is invoked in a certain security context, its behavior will be limited to operations which would be allowable within that context. If a program invokes genuinely undefined behavior, all bets are off, but that would require either using instructions from a group that are marked as supervisor-use only (and whose behavior would be defined as trapping in other modes) or...
    – supercat
    Jun 15, 2021 at 17:21
  • ...performing other actions which are only available in supervisor mode (if code in supervisor mode sets an invalid user-mode configuration, that might cause code running in what was expected to be user mode invoke Undefined Behavior, but that would really be a consequence of the supervisor mode code's actions).
    – supercat
    Jun 15, 2021 at 17:25
  • @supercat all quite fine, but above table is not a manual compiled by the manufacturer of said chip not the one designing the core.
    – Raffzahn
    Jun 15, 2021 at 17:26
0

Ever tried putting an undefined instruction into any ARM (from 2 onwards)? It actually goes into a processor state specifically for dealing with such instructions.

Unpredictable is what it says - generally it's flag conditions after certain instructions.

I do love the fact that ROR Rx,Rx IS valid although AFAIK it's never been used although it would allow a few 32 bit numbers to be set up using an 8 bit immediate & a ROR i.e. 32 bits to set up a 32-bit value.

I did ask ARM about the above i.e. should they put it into their C compilers.... they thought I was mad.

1
  • As mentioned above, it does for the "undefined" instructions, but it does not do that for the "unpredictable" instructions as they don't trigger the undefined instruction trap.
    – Justme
    Jan 3 at 19:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.