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

Assembler keywords

The Dauug|36 assembler has a few keywords that you should recognize. Note these words are not reserved as keywords exclusively, so you are free to also use these keywords as register names if warranted. Here are the keywords in alphabetical order:

emit

In rare instances, we need to insert hand-assembled instructions within a program. This sometimes happens in OS kernels where hand-chosen register numbers are needed, or opcodes need to be inserted as immediate values. The emit keyword generates instructions as four nine-bit fields containing literal values like this:

emit 111000111`b 101101101`b 000010000`b 110010011`b

This inserts the instruction 111000111_101101101_000010000_110010011`b in a program. Any radix can be used for the four fields, so the same result would come from:

emit 455 365 16 403

emit can look up opcode values for any of its four operands. For example, the statements

nop
emit nop 1 2 txor

both produce NOP instructions, but the emit version customizes its ignored register numbers. The generated instructions are:

010011001_000000000_000000000_000000000`b
010011001_000000001_000000010_001110000`b

This binary is current as of 20 March 2024 when NOP and TXOR had opcodes 153 and 112 respectively. Using TXOR’s opcode as a right operand register is a whimsical illustration. For a real use of emit with opcodes, see wipe.user.registers:: in the Osmin source (netsim/os.a).

keep

Registers that are declared within a scope are only accessible within the scope, unless they are “kept” using the keep keyword. You may list any number of registers (even zero registers) after the keyword. For example:

mycode::
    signed w x y z
    unsigned a b c d
    keep c
    keep d z w

Now mycode contains two groups of registers.

a, b, x, and y are unkept, only exist while mycode is in scope, and are only accessible from within mycode. Note that the assembler never initializes any registers for you, so you should always set these registers prior to use with every call to mycode. The values of these registers may change between calls to mycode, because the assembler is free to reuse a, b, x, and y for other purposes whenever mycode is not being called. Even if the operating system initializes these registers to zeros, you cannot count on this happening, because register sharing may un-initialize them via another scope.

c, d, w, and z are kept, are reserved for exclusive use by mycode, and therefore cannot “spontaneously” change values. mycode can alter or read these registers directly, and other scopes can alter or read these registers by preceding them with their scope, i.e.,

mycode::w = 12345

Again, the assembler will never initialize c, d, w, and z for you, but the operating system may (and should be guaranteed to) initialize them to zero.

To force use of specific register numbers

Osmin’s API requires coordination of register numbers between the kernel and user programs. Here are the register numbers that sometimes have special meaning as of 20 March 2024:

Reg. No. Name Purpose
0 0 “zero register” containing the constant 0
1 api::request API request to Osmin kernel
2 api::result API result from Osmin kernel

The zero register is used for operations such as negation, which the assembler translates into subtraction from zero.

Programs use the keep keyword to force register numbers where absolutely necessary. Here is a real-life example from the Osmin kernel:

((  ------------------------------------------------------------------------
    Data-only scope for user-superuser intercommunication.
    ------------------------------------------------------------------------ ))
api::
    u. request              ; INPUT
    u. result               ; OUTPUT
    keep request 1 result 2
    ; No code goes in this scope.

opcode

The opcode keyword is infrequently used. Ordinarily it would only appear in privileged programs that need to compute an instruction to place in code memory or execute from a register via the XANY (execute anything) instruction.

opcode converts an opcode’s assembler name to its numeric equivalent, which will be in the range 0–511 inclusive, and shifts the result left 27 bits. opcode only works in assignments statements, and is useful because the assembler name for an opcode is easier to remember and synchronize within source code than its numeric value. Example:

unsigned nop_inst           ; oc_NOP is 231`o as of 19 July 2023.
nop_inst = opcode nop       ; nop_inst now 231_000_000_000`o.
xany nop_inst               ; Execute the NOP.

The assembler name is defined by the contents of the name field of struct instruction in opcode.c. Alternatively, the Dauug|36 electrical simulation has a command line option to list all assembler names alongside their numeric opcodes. You can invoke this as:

$ ./ns -l

s.

s. is an abbreviation for signed. The period is mandatory.

scope

scope is a proposed keyword to replace the :: symbol. The reason for this change is that although scopes are the principal separators of a program’s sections, their notation with :: is so compact as to be nearly invisible. It doesn’t help that labels within a scope, which are notated with :, look very similar. So instead of writing:

decrypt::
    unsigned in out key.1 key.2
    keep in out key.1 key.2

    out =  in xim key.2
    out = out xim key.1
    return

you would change the first line so the scope reads as:

scope decrypt
    unsigned in out key.1 key.2
    keep in out key.1 key.2

    out =  in xim key.2
    out = out xim key.1
    return

The decision to implement scope as a keyword has not been finalized, and the change has not been implemented. If the change goes through, the :: operator for assignments would also change. For example, a privileged program that needs the address of decrypt in a register would now read

addr = scope decrypt

instead of

addr = ::decrypt

signed

signed is followed by zero or more names, and declares these names as signed registers within the present scope. Examples:

signed
signed a b c
signed offset account_balance

signed is also used in exceptional cases as a cast on either operand or the destination of ALU arithmetic instructions to override a register’s declared signage. Like this:

unsigned i j k

<signed> i = j - k
i = <signed> j + k
i = j + <signed> k

See also Registers.

u.

u. is an abbreviation for unsigned. The period is mandatory.

unsigned

unsigned is followed by zero or more names, and declares these names as unsigned registers within the present scope. Examples:

unsigned
unsigned i j k
unsigned absolute_value distance magnitude

unsigned is also used in exceptional cases as a cast on either operand or the destination of ALU arithmetic instructions to override a register’s declared signage. Like this:

signed i j k

<unsigned> i = j - k
i = <unsigned> j + k
i = j + <unsigned> k

See also Registers.

wrap

wrap is used as a cast on the destination register of ALU arithmetic instructions to indicate that the result may wrap around without being identified as out-of-range. Example:

unsigned downcounter, upcounter

<wrap> downcounter = downcounter - 1
<wrap> upcounter = upcounter + 1

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