10

When an ALU performs a floating point division operation using the non-restoring or the SRT algorithm, it maintains the current value of the "remainder" (in quotes, because it is not a true non-negative remainder) in a scratch register. After the operation, the quotient is presented in a user-visible location, and the contents of the scratch register are of no consequence.

However, if a user-visible register is used as the scratch, specifying its contents in all cases is too bothersome. For example, in the BESM-6, the instruction set manual leaves the contents of the accumulator extension register, normally holding the least significant bits of the mantissa after the FP additions or multiplications, unspecified after a division operation (another source explicitly says that it is undefined). I have not heard of attempts to reverse-engineer the value of that register after a division operation and to use it to speed up computation of the remainder.

A narrow-scope question: Is it possible to use the scratch register of a non-restoring or SRT division to figure out the remainder?

Update: As a matter of fact, it is the "positive" bits of the quotient which are left in a user-visible register. Unfortunately, there does not seem to be anything useful to be gleaned from comparing those bits with the final result.

My main question is, has there been another case of an unspecified data register contents after any arithmetic operation in another CPU architecture and were there any attempts to reverse-engineer them for a good use?

2
  • Assuming you mean "general purpose register" by "data register", this restricts the question to microcoded algorithms that use general purpose registers as scratch registers, and don't specify their contents after whatever operation was implemented is over. There are probably not many cases of that besides division on real old CPUs (maybe some other FP algorithms? Or crypto?), so a more interesting question would be "which CPUs do have such an instruction that uses a general purpose register in that way in the first place".
    – dirkt
    Nov 2, 2018 at 10:41
  • @dirkt Remember that there were CPUs with split sets of address and data registers. Such CPUs might not have general purpose registers at all.
    – Leo B.
    Nov 2, 2018 at 18:21

5 Answers 5

15

There are a lot of instructions on x86 that leave the flag(s) in an indeterminate state, which means the whole FLAGS register is also undefined. Most will have AF undefined, for example

  • TEST

    The OF and CF flags are set to 0. The SF, ZF, and PF flags are set according to the result (see the “Operation” section above). The state of the AF flag is undefined.

but there are also other instructions that leave other undefined flags like

The x87 FPU also has its own flags which may also be left unspecified after many operations

You can open any of the lists in the X86 Opcode and Instruction Reference like this one and look at the undef_f column to have a summary of undefined flags after each instruction


Normal registers can also have an unspecified state, too. Early x86 CPUs will leave the high bit of a mov Reg, Sreg undefined

When executing MOV Reg, Sreg, the processor copies the content of Sreg to the 16 least significant bits of the general-purpose register. The upper bits of the destination register are zero for most IA-32 processors (Pentium Pro processors and later) and all Intel 64 processors, with the exception that bits 31:16 are undefined for Intel Quark X1000 processors, Pentium and earlier processors.

https://www.felixcloutier.com/x86/mov.html (emphasis mine)

And nowadays SHLD/SHRD still have undefined result

If the count operand is CL, the shift count is the logical AND of CL and a count mask. In non-64-bit modes and default 64-bit mode; only bits 0 through 4 of the count are used. This masks the count to a value between 0 and 31. If a count is greater than the operand size, the result is undefined.

https://www.felixcloutier.com/x86/shld

Some other examples on x86

  • LAR: The following fields are returned only if the operand size is greater than 16 bits: Bits 19:16 are undefined
  • SMSW: In non-64-bit modes, when the destination operand is a 32-bit register, the low-order 16 bits of register CR0 are copied into the low-order 16 bits of the register and the high-order 16 bits are undefined
  • BSF, BSR, TZCNT: if source operand is zero, the content of destination operand are undefined
  • SLDT: The high-order 16 bits of the register [...] are undefined for Pentium, Intel486, and Intel386 processors

Not sure whether their intermediate results can be utilize for other purposes or not though


Division and multiplication also often leave unspecified results

For example on PowerPC we have

  • divw

    For the case of -2**31 / -1, and all other cases that cause overflow, the content of GPR RT is undefined.

  • and divd

    If an attempt is made to perform the divisions 0x8000_0000_0000_0000 / -1 or / 0, the contents of RT are undefined

Similarly on MIPS

  • for DIV/MOD/DIVU/MODU/DDIV/DDIVU,

    If the divisor in GPR rt is zero, the arithmetic result value is UNPREDICTABLE

  • In older MIPS ISAs there are also restrictions regarding the HI and LO registers

    In MIPS 1 through MIPS III, if either of the two instructions preceding the divide is an MFHI or MFLO, the result of the MFHI or MFLO is UNPREDICTABLE

On MIPS many floating-point instructions also leave unpredictable result in the registers, like

  • DIV.fmt

    The fields fs, ft, and fd must specify FPRs valid for operands of type fmt. If the fields are not valid, the result is UNPREDICABLE.

    The operands must be values in format fmt; if they are not, the result is UNPREDICTABLE and the value of the operand FPRs becomes UNPREDICTABLE.

  • or FLOOR.L.fmt

    The fields fs and fd must specify valid FPRs: fs for type fmt and fd for long fixed point. If the fields are not valid, the result is UNPREDICTABLE.

    The operand must be a value in format fmt; if it is not, the result is UNPREDICTABLE and the value of the operand FPR becomes UNPREDICTABLE.

    The result of this instruction is UNPREDICTABLE if the processor is executing in the FR=0 32-bit FPU register model; it is predictable if executing on a 64-bit FPU in the FR=1 mode, but not with FR=0, and not on a 32-bit FPU.

http://hades.mech.northwestern.edu/images/1/16/MIPS32_Architecture_Volume_II-A_Instruction_Set.pdf

5
  • In general, when a hardware specification states that a certain certain bits have an "undefined" value, that means that the bits may arbitrarily read as high or low, but those are the only two possibilities. That concept of "undefined" is very different from the C concept.
    – supercat
    Feb 3, 2019 at 21:39
  • The question asks about data registers after arithmetic operations.
    – Leo B.
    Feb 3, 2019 at 21:48
  • 1
    @LeoB. SHLD and SHRD can leave the data register undefined
    – phuclv
    Feb 4, 2019 at 0:14
  • On a RISC CPU without interlocks undefined behavior is par for the course, but the case of overflowing division producing undefined register contents does satisfy the criteria. Unfortunately, there is nothing to gain by analyzing the value.
    – Leo B.
    Feb 4, 2019 at 7:51
  • MIPS R2000 had a load delay slot. On a cache miss for a load, a delay slot instruction reading the load destination register would get the new value; if the pipeline was not disturbed, the old value was received. The architecture just let this undefined ("don't do this"). For the fast (relative to processing) memory of the time, using such for cache miss detection would probably not have been useful. Nov 20, 2021 at 13:32
6

Some arithmetic operations in the Xerox Alto would leave registers in unpredictable states, as the cycle time was shorter than the total propagation time of the ALU:

With a 137ns clock cycle time, the CP pushes the underlying hardware to its limits. As a result, some combinations of input sources requested by a microinstruction will not produce valid results because the data simply cannot all make it to its destination on time. Some combinations will produce garbage in all bits, but some will be correct only in the lower nibble or byte of the result, with the upper bits being undefined. (This is due to the ALU in the CP being comprised of four 4-bit ALUs chained together.)

-- https://engblg.livingcomputers.org/index.php/2019/01/19/introducing-darkstar-a-xerox-star-emulator/

2
  • That's at the microinstruction level, though. Was this observable at the ISA level?
    – Leo B.
    Feb 3, 2019 at 21:51
  • The distinction between microcode and "normal" code was rather fuzzy on the Alto -- the control store was writable, and many applications made use of this to add their own instructions.
    – user461
    Feb 3, 2019 at 21:56
6

This is a little bit outside the scope of the question. It's not really arithmetic instructions (though they are common instructions, and some of them do involve arithmetic), and the architecture in question isn't very "retro". But anyway, maybe it will be of interest to someone.

In the ARMv8 architecture, circa 2011, most instructions write to only a single register (or none). But there are a few exceptions:

  • Load (and store) instructions can use a writeback addressing mode, where the address register is post- or pre-incremented by a specified amount. So ldr x0, [x1], #8 loads a 64-bit doubleword from the address in register x1, puts the result in register x0, and adds 8 to x1. Much like x0 = *x1++. Likewise ldr x0, [x1, #8]! is the equivalent of x0 = *++x1.

  • There are load pair instructions (also store pair), which load two registers from adjacent memory addresses. For instance ldp x0, x1, [x2] loads x0 from the address in x2, and loads x1 from the same address plus 8.

With these instructions, it is possible to specify that a register should be written in two different ways. For instance ldr x0, [x0], #8: does x0 get left with the value loaded from memory, or the address plus 8, or the value loaded plus 8, or what? And with ldp x0, x0, [x2], does x0 end up with the first loaded dword, or the second one, or what?

The architecture resolves this ambiguity by defining the effect of such instructions to be "CONSTRAINED UNPREDICTABLE", meaning the machine can behave in any of several specified ways. One of those options is that the register in question may take an "UNKNOWN" value, defined in the glossary as "does not contain valid data, and can vary from implementation to implementation." (However, they are careful to say that it must not return privileged data that the code could not otherwise access.)

On the Cortex A-72 where I tested this, the results are unfortunately boring. The writeback loads just do the load and ignore the writeback; so ldr x0, [x0], #8 executes like ldr x0, [x0], and ldr x0, [x0, #8]! executes like ldr x0, [x0, #8]. (Yes, the syntax is obnoxious.) And ldp x0, x0, [x2] traps as an illegal instruction, which is another of the options allowed under CONSTRAINED UNPREDICTABLE. I don't know whether there are any actual machines where the results are interesting in any way, let alone useful.


Edit: Looking a little at the 32-bit ARM instruction set, which I'm not as familiar with, it looks like there are some more examples, including some that are truly arithmetic instructions. For instance, the widening Multiply Long instructions write a 64-bit result to a pair of 32-bit register. If they are the same register, as in umull r0, r0, r1, r2, the result is CONSTRAINED UNPREDICTABLE as able, with the possibility of an UNKNOWN value being written to r0.

5

One example are the two undocumented 8085 flags that can be found by reverse-engineering the silicon (well, it's part of a register, not a complete register). They have uses for signed overflow in arithmetic operations and signed comparison. There are actually a few undocumented opcodes that make use of at least the K flag.

They were put in deliberately (so it's not a chance effect like in the undocumented opcodes for the 6502 - one could argue that the contents of the BESM-6 scratch register would also be a "chance effect", though a deliberate one that depends on the internal algorithm), but not documented for "political" reasons.

5
  • That's more about deliberately put undocumented instructions. The fact that only they use particular flags, and therefore the flags are also undocumented, is a second-order effect. I'm asking about documented instructions which leave documented registers (or flags, for that matter) unspecified or undefined. I think that a reason not to document the contents of the acc. extension register was to provide for the possibility to improve/speed-up the division algorithm in future versions of the CPU.
    – Leo B.
    Oct 30, 2018 at 21:25
  • 1
    The documented arithmetic instructions leave the documented status register in an unspecified state for some of the bits, and reverse engineering figured out that it is actually a specified state, so I think it matches your requirement. That there are additionally undocumented instructions which use the specified state is just icing on the cake. In the same way, leaving the bits unspecified would provide for the possibility of different flags in the future (for the current use seemed to be abandoned for "political" reasons).
    – dirkt
    Oct 31, 2018 at 7:17
  • I've qualified my question to ask exactly what I've wanted, about data registers.
    – Leo B.
    Nov 2, 2018 at 2:46
  • 2
    @LeoB. - note that the 8085 PUSH PSW instruction is a documented instruction whose behaviour includes copying undefined bits into memory, whose value is determined from previously executed documented instructions in a way which is not specified in the documentation. This only seems to not match what you're asking because you have to go via memory to get to the undefined values, rather than directly access them in a register, which is a pretty arbitrary distinction...
    – Jules
    Nov 4, 2018 at 4:50
  • @Jules If it's still unclear that I'm asking about leaving a data register used as scratch in an unspecified state after an arithmetic instruction, please suggest an edit.
    – Leo B.
    Nov 6, 2018 at 0:30
3

And then there are data registers of various FPUs leaving one or more entries in unspecified state after an operation. Well known for example the AMD 9511 series where next to all higher FP functions like SIN or LOG working on a single argument leave at least two entries in undefined states (*1).


*1 - Two operand functions always leave one entry undefined due to their nature. Or as njuffa summarized from the manual:

The Am9511 uses a stack-oriented architecture. So when executing a two-operand operation like FADD which operates on the two registers A and B at the current top of stack, the result R goes into the register that formerly held B, which is the new top of stack, and the contents of the register formerly holding A is undefined. Operations like EXP require registers A, B, C, and D, with A closest to top of stack. Afterwards, the result R replaces A, B is unchanged, and the contents of C and D are now undefined (used for scratchpad storage)

10
  • 1
    Unfortunately, I couldn't find an instruction set reference sheet for it. And I don't understand the footnote. What's that "nature" of two operand functions that causes them to leave one register undefined?
    – Leo B.
    Feb 3, 2019 at 23:11
  • 2
    @LeoB. From the manual: The Am9511 uses a stack-oriented architecture. So when executing a two-operand operation like FADD which operates on the two registers A and B at the current top of stack, the result R goes into the register that formerly held B, which is the new top of stack, and the contents of the register formerly holding A is undefined. Operations like EXP require registers A, B, C, and D, with A closest to top of stack. Afterwards, the result R replaces A, B is unchanged, and the contents of C and D are now undefined (used for scratchpad storage).
    – njuffa
    Nov 23, 2021 at 23:03
  • @njuffa Oh. Nice. Thanks for looking this up. Do you mind if I copy this into the footnote?
    – Raffzahn
    Nov 23, 2021 at 23:07
  • @njuffa I see. So using user-addressable registers as scratchpads and leaving them in an undefined state rather than zeroing them out for the sake of full definition of the behavior, is, while not common, an acceptable practice.
    – Leo B.
    Nov 23, 2021 at 23:25
  • 1
    @Raffzahn I only have a dead-tree copy from 1986 on my bookshelf which I consulted. For every operation the manual actually has a nice graph depicting the stack before and after the operation (which is easier to grasp than a textual description), and it describes register content as "lost" rather than "undefined".
    – njuffa
    Nov 23, 2021 at 23:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .