Difference between revision 4 and current revision
No diff available.The contents of boot_reference as of 2017/03/27, just before a cleanup/removal of old-now-incorrect stuff/decision to remove 'optional' instructions:
:hardbreaks:
Version: unreleased/in-progress/under development. Working towards development version 0.0.0-0.
todo: i said this was RISC-y, but: + +
Instruction encoding: 16 bits, little endian, from LSB to MSB, for fields each 4 bits long: opcode, operands 0, 1, 2. Instructions are packed into 64-bit aligned quads. The least-significant-bit of each quad must be 0. + + Datatypes: u16 (unsigned 16-bit integer), ptr (pointer). + + Registers: 16 registers. First 8 are special: + + (todo see below this is out of date)
0. 0 register 1. PC 2. ERR 3. SCRATCH 4. HIDDENSTACK 5. DATASTACK 6. CALLSTACK 7. reserved for future use + + Instructions: todo: this is slightly out of date, copy updated version from below:
0. annotate (also nop, when operands are zero) 1. loadi (load immediate) 2. load (load from memory to register) 3. store (store from register to memory) 4. cpy (copy from register to register (in other architectures this is usually called "MOV")) 5. skipz (skip if zero) 6. skipnz (skip if non-zero) 7. calli (call indirect) 8. pop 9. push 10. add-u16 (addition of u16s) 11. sub-u16 (subtraction of u16s) 12. leq-u16 (less-than-or-equal of u16s) 13. add-ptr (add a u16 to a pointer) 14. sub-ptr (subtract a u16 from a pointer) 15. vmcall (other vm functionality)
The instruction encoding is fixed-length 16-bits, and instructions are grouped into 64-bit 'quads'. + Encodings are little-endian, and bits are given from LSB to MSB. + + The encoding of each instruction is:
There are five defined data types:
There are 16 registers. The first 8 registers are special.
0. 0 register 1. PC 2. ERR (or STATUS?) 3. TOS (alias to top of datastack) 4. CAP (or MODE?) 5. HIDDENSTACK 6. DATASTACK 7. CALLSTACK + + There are three stacks (hiddenstack, datastack, callstack). The stack pointers are in scare quotes because Boot programs are not allowed to directly manipulate these; all stack manipulation must be accomplished via the PUSH and POP and LOAD and STORE instructions. The stacks may or may not be stored in ordinary memory, and the "pointers" may or may not be ordinary pointers. + + In more detail: + +
Reading from the zero register always returns 0. Writing to the zero register has no effect. + +
The program counter. This is a pointer to the next instruction to be executed. It is read-only. A Boot program is not allowed to directly read or write program memory, or dereference pointers into program memory. Program memory may or may not be stored in ordinary memory.
This is used to return error codes. An error code of "0" means no error (success).
It is also used to return carry and overflow results from addition and subtraction.
A stack "pointer".
The capacity of the hiddenstack is 15 items.
The compiler must be able to resolve at compile time the stack offset of each access of the hidden stack, relative to the hiddenstack pointer at the beginning of function/code segment.
A stack "pointer".
A stack "pointer".
The 16 three-operand instruction opcodes and mnemonics and operand signatures are: 0. annotate: ? ? ? (this instruction is called BAD when all operands are zeros) 1. loadi: oi k k (load 8-bit u16 immediate) 2. loadlo: oi k k (load 8-bit immediate with multiply; multiply the destination register by 256 and then add the 8-bit immediate value. NOTE: this instruction is illegal except immediately following either a LOADI to the same destination register, or another LOADIM to the same destination register. NOTE: these allow you to load constant greater than 8 bits. The maximum constant size is platform-specific; a sequence of LOADI/LOADIM instructions that would create a larger constant than the platform supports is unsupported on that platform) 3. load: o k si (load from memory (plus constant offset) to register; atomic/tear-free) 4. store: so k i (store from register to memory (plus constant offset); atomic/tear-free) 5. TWO: ? ? k (two-operand instructions)) 6. jrel: 0 0 k (relative jump) 7. skipz: 0 0 ii (skip if zero) 8. skipnz: 0 0 ii (skip if non-zero) 9. add-u16: oi ii ii (addition of u16s) 10. addi-u16: oi ii k (add a constant to a u16) 11. sub-u16: oi ii ii (subtraction of u16s) 12. leq-u16: o ii ii (less-than-or-equal of u16s) 13. add-ptr: op ip ii (add a u16 to a pointer) 14. addi-ptr: op ip k (add a constant to a pointer) 15. cas-atomic: pio i i (compare-and-swap)
The 16 two-operand instruction opcodes and mnemonics and operand signatures are: 0. cpy: o i (copy from register to register (in other architectures this is usually called "MOV")) 1. or-u16: io i (bitwise OR: op0 = op0 OR op1) 2. and-u16: io i (bitwise AND: op0 = op0 AND op1) 3. xor-u16: io i (bitwise XOR: op0 = op0 XOR op1) 4. add-atomic-u16 pio i (value at memory location is atomically replaced with the sum of its previous value and a register) 5. add-atomic-ptr pio i 6. mul-u16: io i (multiplication: op0 = op0 MUL op1) 7. xchng-atomic: pio io (value at memory location is atomically exchanged with value of register) 8. picki: sm k (push a copy of the n-th item in a stack (zero-indexed) onto the top of the stack) (note: this can also do dup (with r0), over) 9. rolli: sm k (rotate the top n+2 elements on a stack) (note: this can also do swap (with k=0), rot) 10. slli-u16: io k (op0 = logical_shift_left(op0 by k bits)) 11. srli-u16: io k (op0 = logical_shift_right(op0 by k bits)) 12. library: k k (call to platform-specific library function identified by 8-bit constant (remember, each operand is only 4 bits); calling conventions are as with syscalls) 13. pop: o sim 14. push: som i 15. ONE: ? k (one-operand instructions)
The 16 one-operand instruction opcodes and mnemonics and operand signatures are: 0. jd: pc (dynamic jump) 1. stackload: sm (pop saved stack from DATASTACK, and load it) 2. stacksave: si (save stack, and push it to DATASTACK) 3. fence-acquire: p (the memory location p is ordered by an acquire memory barrier) 4. fence-release: p (the memory location p is ordered by a release memory barrier) 5. loadmulti-atomic: k (pop pointer (pi) from DATASTACK, atomically load k items onto DATASTACK) 6. storemulti-atomic: k (pop pointer (pi) from DATASTACK, atomically store k items from DATASTACK) 8. not-u16: io (bitwise NOT: op0 = NOT op0) 8. addstk-u16: sm (stack version of add-u16) 9. substk-u16 10. leqstk-u16 11. addstk-ptr: sm (stack version of add-ptr) 12. fence-seq: p (the memory location p is ordered by a sequential consistency memory barrier) 13. stackif: sm (pop top three items in stack, if top item was 0, push second item, otherwise push third item; C's ternary conditional operator) 14. ZERO: k (zero-operand misc operations) 15. syscall: k (zero-operand I/O and IPC)
The 16 zero-operand misc operations are: 1. 2. casstk: (stack version of CAS, on DATASTACK) 3. log: (log a message; loglevel k and then pointer to message is on top of DATASTACK) 4. mdealloc (deallocate memory (like C's free); ptr is on TOS) 5. 6. 7. 8. sra1stk-u16: (TOS = arithmetic_shift_right(TOS by 1 bit)) 9. modeset: (change the processor state in some way (eg set error handler); 2 arguments passed on stack) 10. malloc-shared: (allocate shared memory supporting atomic operations; amount of memory requested is popped from DATASTACK, result is pushed to DATASTACK) 11. mrealloc: (resize memory allocation (like C's realloc); pointer and new size is popped from top of DATASTACK) 12. malloc-local: (allocate local memory; amount of memory requested is popped from DATASTACK, result is pushed to DATASTACK) 13. regload: (pop k from datastack?, pop saved k registers from CALLSTACK and restore k registers starting with 8) 14. regsave: (pop k from datastack?, save registers greater than 7 and less than 7+k, and push to CALLSTACK; stored register format is opaque (but is it one or many entries on CALLSTACK?!? b/c we want to be able to manually manipulate CALLSTACK and even move saved registers elsewhere in memory, right?)) 15. break
The 16 zero-operand syscalls are:
0. sysinfo (does this take a 16-bit query argument on the stack, or just return all of the system info every time? probably the former) 1. delete 2. walk (like plan9's 9p's walk; todo is this actually different from ls/query, i forgot) 3. rand 4. sync/flush 5. seek 6. fork 7. forkdone (fork's join/wait) (todo can't we do this some other way, using channels, without burning an opcode?) 8. open 9. close 10. read 11. write 12. poll (todo: how exactly poll works) 13. time (takes a clock_spec on the stack, and returns one or more words on the stack according to the clock_spec; returns 0(s) and sets ERR condition if requested clock is not supported) 14. 15. halt (pop DATASTACK and terminate program execution, returning the popped value)
Many of the zero-operand instructions (esp. the syscall-y ones) also use DATASTACK to pass/return parameters.
todo:
maybe:
maybe (copied from ootLibrariesNotes6):
note: over and dup can be done with 'pick 1' and 'pick 0'
TODO see also those other lists of popular linux syscalls that i made, and my ideas regarding instructions for epoll, completion ports, etc
note: the reason for (malloc, mdealloc, open, close, read, write), is that they are fundamental and common. (walk) because it is fundamental. (poll sleep) because they are called a lot in tight loops and so you want them to be efficient.
Where the signature characters mean:
In addition, the mnemonic 'nop' refers to 'annotate' with all zero operands.
In the following, op0, op1, op2 are short for operand 0, operand 1, operand 2.
Used to embed metadata into code. Has no effect on execution; can be skipped by an interpreter or compiler.
A number is formed by bitwise concatenating op1 and op2, with op1 being MSB (that is, by multiplying the value of op1 by 16, and then adding this to op2). This number is then treated as a u16 and stored in the register indicated by op0.
The contents of the register indicated by op2 is taken to be a pointer. The memory location indicated by this pointer, plus op1, is looked at, and the value found there is stored in the register indicated by op0.
Note: by giving a stack pointer in op2, LOAD can be used to peek at stack items at indices from 0 to 15 without POPing. Stack "pointers" are allowed in op2.
The value in the register indicated by op2 is stored in (the memory location indicated by the contents of the register indicated by op0, plus op1).
Note: by giving a stack pointer in op2, LOAD can be used to overwrite stack items at indices from 0 to 15 without PUSHing. Stack "pointers" are allowed in op2.
If the value in the register indicated by op1 is zero, then the PC is incremented by 1.
Operands 0 and 1 must be zero; skipz instructions with non-zero operands 0 or 1 are reserved for the internal use of the implementation, and are syntax errors if found in Boot program code.
If the value in the register indicated by op1 is not zero, then the PC is incremented by 1.
Operands 0 and 1 must be zero; skipnz instructions with non-zero operands 0 or 1 are reserved for the internal use of the implementation, and are syntax errors if found in Boot program code.
If k > 7, increment the PC by k-7. Otherwise, increment the PC by k - 9.
The two-operand instruction identified by op2 is called with operands op0 and op1. See section below for a list of two-operand instructions.
The n-th item on the stack specified by op2 is popped (reading the value, then reducing the stack size by 1 and moving each item above the n-th down by 1), where n is op1. The value that was popped is placed into the register specified by op0.
The value in the register specified by op2 is pushed into the stack at the n-th index, where n is given by op1 (increasing the stack size by 1 and moving each item above the n-th up by 1, then writing the value).
if the result is larger than op0 can hold, op0 will be set to the result mod its op0's maximum value, and ERR will be set to 1; otherwise ERR will be set to 0
todo: this instruction was deleted, i think
The current PC is PUSHed onto CALLSTACK. The contents of the register specified by op1 is PUSHed onto DATASTACK. The PC is set to the contents of the register specified by op2, and execution continues until a RET is encountered when the TOS of CALLSTACK is equal to the next instruction. At that time, the register specified by op0 will be set to the return value of the RET, and execution resumes.
See also RET.
todo: some of the numbers below are wrong
The value in the register indicated by op2 is copied into in the register indicated by op0.
The values in the registers specified by op0 and op1 are swapped.
Memory whose size is the value in the register indicated by op1 is allocated. Note: each location in memory must be able to hold at least a 16-bit word (but could hold more, depending on the implementation). So the units of the amount of memory requested is not bytes. If the allocation is successful, a pointer to the new memory is placed into the register indicated by op0. If the requested amount of memory is not available, ERR will be set to the code for OOM (out-of-memory) and the value in the register specified by op0 is undefined.
See also MDEALLOC.
The k-th platform-specific subroutine is called. See section 'calling convention'.
The one-operand instruction identified by op1 is called with operand op0. See section below for a list of one-operand instructions.
CALLSTACK is POPped and the PC is set the value popped, and the value in the register indicated by op0 is returned.
See also CALLI.
If this pointer was not returned by a previous MALLOC, or if this memory region has already been deallocated (and has not been subsequently returned by another MALLOC), the result is undefined.
See also MALLOC.
Program execution is terminated, and the value in the register indicated by op0 is returned.
0. Boot SYSINFO version. Returns 0. Note: this version number is distinct from the Boot specification version identifier (SdVer?