Skip to content

Latest commit

 

History

History
240 lines (154 loc) · 14 KB

File metadata and controls

240 lines (154 loc) · 14 KB

Onramp Assembly

Onramp Assembly is an assembly language used to produce object code for the Onramp virtual machine.

This document provides the specification of the assembly language. A description of the bootstrapping stages and their implementation is here.

Instruction Types

Onramp assembly has two different kinds of instructions: primitive instructions and compound instructions.

Primitive instructions are the instructions supported by the Onramp virtual machine. They are translated directly to their corresponding Onramp bytecode.

Compound instructions are converted by the assembler into one or more primitive instructions during assembly. For example, the compound instruction ret is converted to the primitive instruction ldw rip 0 rsp which is output as the bytecode 78 8F 00 8C.

You can think of compound instructions as macros, except they are not defined in the assembly source; they are defined in the assembler program itself. (Unlike modern assemblers, there is no way to define macros in Onramp assembly.)

Assembler Stages

The first stage Onramp assembler recognizes only the following primitive instructions:

  • Arithmetic: add, sub, mul, div
  • Logic: and, or, shl, shru
  • Memory: ldw, stw, ldb, stb
  • Control: ims, ltu, jz

Each of these instructions takes three bytes of arguments and maps directly to an instruction in Onramp bytecode. The behaviour and required arguments of each is documented below.

Most of the remaining instructions, but not all, are supported by the second stage assembler. The final stage supports all instructions. The tables below list which stages support which instructions.

In the first stage assembler, arguments can be passed as register names, as characters (provided they are in range), or as raw bytes.

In the second and final stage assembler, arguments can additionally be passed as numbers (decimal or hexadecimal) and a number argument may represent multiple bytes (for example as the 16-bit argument to ims or jz.)

Registers

The Onramp assembler recognizes the following register names:

  • r0, r1, r2, r3
  • r4, r5, r6, r7
  • r8, r9, ra, rb
  • rfp, rsp, rpp, rip

Register names are replaced by their literal bytes (80 through 8F.) These must only be used as the argument to an instruction where a register is accepted. The early stage assemblers may not verify that a register name makes sense in the context in which it is used.

Raw Hex Bytes

Raw hex bytes can be output by prefixing each one with '.

  • '<hex>: A raw hex byte.

The ' must be followed by two hexadecimal characters with no whitespace in between. The hexadecimal byte is output as-is (in plain text) without the leading '. For example, '00 outputs a null byte. This is typically used in combination with strings; see String Literals below.

When outputting multiple bytes, each one must be prefixed with '.

Comments

; starts a comment. The rest of the line is ignored.

Debug Info

# starts a debug info annotation that runs until the end of the line. The assembler may pass the annotation verbatim to the output, or it may ignore it (treating it as a comment.)

Valid debug info consists of #line and #pragma directives. See the Debug Info document for a description of the debug info format.

Labels and Symbols

The Onramp assembly language uses the same syntax for labels and symbols as Onramp object code. Unlike most assemblers, the Onramp assembler does not actually resolve labels. They are passed directly through to the linker.

Here's a quick reference table:

Syntax Meaning
^ invocation: absolute symbol, 32 bits
< invocation: absolute symbol, 16 high bits
> invocation: absolute symbol, 16 low bits
& invocation: relative label, 16-bit signed words
= definition: global symbol
@ definition: static symbol
: definition: label
? flag: weak definition or invocation
{ flag: constructor, optional priority 0-65535
} flag: destructor, optional priority 0-65535

For full usage details, see the Onramp object code spec.

String Literals

String literals can be provided by enclosing text in double quotes (").

"Hello world!"

The bytes of the string are output directly by the assembler.

You can concatenate strings and raw bytes however you like. The above is equivalent to:

"Hello" '20 "world" '21

A string can contain any printable ASCII characters except for the double-quote (") and the backslash (\). There are no escape sequences. To add other bytes to a string, concatenate it with the raw hex values as above.

No null-terminator is appended. If you want one you have to append it ('00) yourself.

Typically you would place the string in its own symbol so it can be referenced by the rest of the code. For example, here's a typical C string with a trailing line feed and null-terminator:

=hello_world
    "Hello world!" '0A '00

Single-character strings can be used as instruction arguments. For example, to place the character 'a' in register r0:

mov r0 "a"

Instructions

Instructions consist of an opcode and a number of arguments. Arguments can be integers, registers, quoted bytes, or single-character strings.

There are no commas separating arguments and whitespace is not significant (except where required to separate arguments.)

For most instructions (but not all), the destination register comes first, followed by the source values. For a few instructions (the stores and jumps), the source value comes first. This is done for simplicity and for symmetry with loads.

Compound instructions can assemble to different numbers of primitive instructions depending on the arguments and on the stage of the assembler. You should not assume the number of primitive instructions into which a compound instruction will expand.

Only primitive instructions can use the scratch registers (ra and rb) as they are clobbered by most compound instructions. The final stage assembler diagnoses misuse of scratch registers.

Instruction Reference

Arguments have the following types:

  • r: A register
  • m: A mix-type byte, either a register or an immediate value in the range [-112,127]
  • b: An immediate 8-bit byte
  • i: An immediate 32-bit integer or equivalent (e.g. an absolute linker invocation, a number, four literal bytes in little-endian)
  • c: An absolute linker invocation (i.e. ^ and a symbol name)
  • j: A relative linker invocation (i.e. & and a jump target)

The "Stage" column indicates the first assembler stage that supports the instruction.

Arithmetic:

Stage Opcode Arguments Description
0 add <r:dest> <m:src1> <m:src2> Adds src1 and src2, unsigned overflow
0 sub <r:dest> <m:src1> <m:src2> Subtracts src2 from src1, unsigned underflow
0 mul <r:dest> <m:src1> <m:src2> Multiplication
0 divu <r:dest> <m:src1> <m:src2> Divides src1 by src2 unsigned
1 divs <r:dest> <m:src1> <m:src2> Divides src1 by src2 signed
1 modu <r:dest> <m:src1> <m:src2> Modulus of src1 divided by src2 unsigned
1 mods <r:dest> <m:src1> <m:src2> Modulus of src1 divided by src2 signed
1 zero <r:dest> Sets the register to zero
1 inc <r:reg> Increments the register, unsigned overflow
1 dec <r:reg> Decrements the register, unsigned underflow
1 sxs <r:dest> <m:src> Sign-extends a short value (copies bit 15 to upper 16 bits)
1 sxb <r:dest> <m:src> Sign-extends a byte value (copies bit 7 to upper 24 bits)
1 trs <r:dest> <m:src> Truncates the value to a short (zeroes upper 16 bits)
1 trb <r:dest> <m:src> Truncates the value to a byte (zeroes upper 24 bits)

Logic:

Stage Opcode Arguments Description
0 and <r:dest> <m:src1> <m:src2> Bitwise and
0 or <r:dest> <m:src1> <m:src2> Bitwise or
1 xor <r:dest> <m:src1> <m:src2> Bitwise xor
1 not <r:dest> <m:src> Bitwise not (inverts all bits)
0 shl <r:dest> <m:src1> <m:src2> Bitwise shift left (low to high)
0 shru <r:dest> <m:src1> <m:src2> Bitwise logical shift right (unsigned, high to low)
1 shrs <r:dest> <m:src1> <m:src2> Bitwise arithmetic shift right (signed, high to low)
1 rol <r:dest> <m:src1> <m:src2> Bitwise rotate left (low to high)
1 ror <r:dest> <m:src1> <m:src2> Bitwise rotate right (high to low)
1 mov <r:dest> <m:src> Copies src to dest
1 bool <r:dest> <m:src> Sets dest to 1 if src is non-zero, 0 otherwise
1 isz <r:dest> <m:src> Sets dest to 0 if src is non-zero, 1 otherwise

Memory:

Stage Opcode Arguments Description
0 ldw <r:dest> <m:base> <m:offset> Loads an aligned 4-byte word from memory
0 ldb <r:dest> <m:base> <m:offset> Loads a byte from memory, zeroes upper 24 bits
1 lds * <r:dest> <m:base> <m:offset> Loads a 2-byte short from possibly unaligned memory, zeroes upper 16 bits
2 ldwu <r:dest> <m:base> <m:offset> Loads a 4-byte word from a possibly unaligned address in memory
0 stw <m:value> <m:base> <m:offset> Stores a 4-byte word in memory (aligned)
0 stb <m:value> <m:base> <m:offset> Stores a byte in memory, ignores upper 24 bits
1 sts * <m:value> <m:base> <m:offset> Stores a 2-byte short from possibly unaligned memory, ignores upper 16 bits
2 stwu <m:value> <m:base> <m:offset> Stores a 4-byte word at a possibly unaligned address in memory
1 push <m:value> Pushes a value to the stack
1 pop <r:reg> Pops the top stack value into the register
1 popd none Pops the top stack value and discards it

* The lds and sts instructions may be renamed to ldsu and stsu for consistency because they operate on unaligned memory addresses.

Control:

Stage Opcode Arguments Description
0 ims <r:reg> <b:low> <b:high> Shifts register up 16 bits, then loads a 16-bit immediate
1 imw <r:reg> <i:value> Loads a 32-bit immediate
1 * cmpu <r:dest> <m:src1> <m:src2> Compares src1 to src2 unsigned, placing -1, 0 or 1 in dest
1 * cmps <r:dest> <m:src1> <m:src2> Compares src1 to src2 signed, placing -1, 0 or 1 in dest
0 ltu <r:dest> <m:src1> <m:src2> Places 1 in dest if src1 is less than src2 unsigned, 0 otherwise
1 lts <r:dest> <m:src1> <m:src2> Places 1 in dest if src1 is less than src2 signed, 0 otherwise
0 jz <m:pred> <j:label> Jumps if the predicate is zero
1 jnz <m:pred> <j:label> Jumps if the predicate is not zero
1 jmp <j:label> or <c:function> Jumps unconditionally
1 call <c:function> Calls a function (pushing the return address to the stack)
1 ret none Returns from a function call
1 enter none Creates a stack frame
1 leave none Destroys the current stack frame

* The cmpu and cmps instructions are deprecated. They will most likely be removed soon.