The Dauug House Wright State University logo
Dauug|36 minicomputer documentation

Registers

A Dauug|36 computer has 131,072 registers. These registers are partitioned so that 256 users can each have 512 registers. There is no way to change this partitioning, because it’s hard-wired. Recall that a user is a user of CPU resources; that is, a user is a running program. Unlike traditional architectures, Dauug|36 registers do not need to be saved or restored when switching from one running program to another, because users never share registers.

Small- to modest-sized programs will be able to keep all (or most) scalar variables in registers instead of in data memory. This considerably reduces the number of instructions a program needs to have, while simultaneously helping the program run faster. This is also for the best, because although Dauug|36 doesn’t have a memory bottleneck and therefore has no need for cache memory, the architecture’s tiny selection of addressing modes and almost-total absence of immediate operands would encroach significantly on efforts to use data memory for variables.

Declaring registers

Every running program is allocated 512 general-purpose registers, allowing a lot of computing without needing to save or load registers to or from primary storage. All registers have the same capability, so there is no need or provision to address them by register number via the assembler. There is also no indirect register access for user programs; for instance, a loop can’t be written to go through all of the registers. (Privileged programs can sometimes use the XANY (execute anything) instruction or self-modifying code to access registers indirectly.)

To allocate a register for a program, it has to be declared with a name and given a default assumption as to whether arithmetic operations should treat it an unsigned or signed quantity. If the register is used for non-numeric information (such as some flags or a string), consider specifying it as unsigned. The assembler offers keywords unsigned and signed for declaring registers. These declarations do not have to be prior to use, so long as they are declared in their intended Scopes. Examples:

unsigned five ten
signed                 ; Not specifying any names is legal (but useless).

five = 5
ten = 10
fifteen = five + ten
twenty = ten + ten     ; ERROR: Register "twenty" is not declared.

signed fifteen         ; It's legal to declare a register after use.

A register’s signedness refers to whether it is unsigned or signed. So in the example above, the signedness of five is said to be unsigned, and the signedness of fifteen is signed. Don’t feel locked in by a particular signedness declaration. Signedness actually applies to the operations being performed, not the physical register itself. The following section shows how to override a register’s declared signedness for a specific need.

Overriding register signedness

A register’s signedness affects the range of numbers it can represent without information loss. Through awareness of the signedness of their source and destination registers, operations such as addition and arithmetic shift are able to set error flags in the event of an out-of-range result.

Sometimes the signedness of a register that has been declared with the signed or unsigned keyword may not represent the programmer’s intent under an exceptional circumstance. To overcome this the assembler provides <signed> and <unsigned> casts. These casts don’t actually affect registers, but instead alter the register type information provided to operations like subtraction or assignment. For instance:

signed s
unsigned u z

z = 0              ; constant zero
s = ~5             ; no problem; s is now -5 by immediate assignment
s = -5             ; no problem; s is now -5 by subtraction from 0
u = z - s          ; no problem; u is now 5
u = s - z          ; underflow; sets the T and R flags
u = z + s          ; underflow; sets the T and R flags
u = <unsigned> s   ; no problem; u is now 2**36 - 5
<signed> u = s     ; no problem; u is now 2**36 - 5
s = u              ; overflow; sets the T and R flags

Purposeful wraparound

Sometimes a register is needed that never indicates an error if the result doesn’t fit. Instead, the register should silently “wrap around” every 236 values. For addition and subtraction, this is accomplished using a <wrap> cast. For addition and subtraction:

<wrap> up = up + 1
<wrap> down = down - 1

To shift a register without ever setting the R(ange) flag, use a logical shift instead an arithmetic shift. For example:

2x = x asl 010101_010101`o      ; sets R(ange) on overflow
x' = x lsl 010101_010101`o      ; never modifies R(ange)

For completeness specifying registers, it would make sense to have a wrap keyword for declaring registers, so that a <wrap> cast wouldn’t need to be used. I declined to include this in the architecture, because there are people who would start writing wrap in cases where unsigned or signed is required. I suspect that it’s safer to compel use of the cast.

Reusing registers

To stretch the limited number of available registers as far as practical, assemblers reserve the right to reuse out-of-scope registers that are not kept, as well as reuse registers that are not live. Neither practice is implemented as of 1 July 2023, because I have yet to hear of a Dauug|36 program approaching its 512-register limit.

Register initialization

Declaring a register does not cause any value to be assigned to it. The assembler does not initialize any register that you declare unsigned or signed. For security and reproducibility reasons, the operating system can and should preset all of a program’s registers to zero when the program starts, and guarantee this behavior. This task is intentionally not within the assembler’s responsibilities, because the operating system cannot control whether an assembler respects privacy or not. The operating system therefore needs to unilaterally zero all registers to preclude the possibility of leaking confidential information.

Register constants

In addition to being needed to hold variables, registers are used to supply numeric constants where they are needed. This is because most Dauug|36 instructions do not support immediate operands. Instead, the assembler collects whatever fixed numbers a program is going to use, allocates a register for each, and includes a glossary in the executable code that loads each 36-bit constant into its own register. These constants are folded to the extent possible; for example, every unsigned constant 236−1 and every signed constant −1 share a single register for the entire program.

Reducing register constant pressure

The assembler will not allocate a register for a constant unless an instruction requires it. The statement

f = f - 32

will require a register containing the number 32, so in providing one automatically, the assembler will reduce by one the number of registers that remain available. But a handful of Dauug|36 instructions can load an immediate value into a register directly. If instead of directly subtracting 32, the programmer instead writes

temp = 32
f = f - temp

these statements won’t cause a register to be allocated for 32, because an IMP (immediate positive) instruction can place 32 directly into temp, and register temp can be shared for other uses. So in exchange for a possible increase in code size and execution time, the number of constant registers used by a program can be reduced. Register pressure will not be reduced unless every use of a particular constant is written around in this way.


Marc W. Abel
Computer Science and Engineering
College of Engineering and Computer Science
marc.abel@wright.edu
Without secure hardware, there is no secure software.
937-775-3016