std/machine/lib
std/machine/src/lib.trb
The kernel the bytecode VM of torb run --vm runs on: raw 64-bit words, and the C runtime behind them
(docs/design/VM.md section 6).
The VM keeps its registers in one ArrayList<Int64>, and a value there is the runtime's own bytes - a String is
the two words of a torb_text, a list the two of a torb_list, a counted block its pointer. Machine is what does
the part TorbScript cannot do on such words: call a function of the runtime with them, read and write a word of a
counted block, and place a text or a float constant into them.
This package is not for programs. Its operations trust their operands the way machine code does - a wrong register or address is a wrong write, not a panic - and a sandboxed script never gets it, because a sandbox's capabilities are its imports.
type Machine
native type Machine
The ten natives of the VM's kernel. Every one of them is runtime/machine.c or runtime/include/torb.h.
fn operate
native static fn operate(var words: ArrayList<Int64>, base: Int64, code: ArrayList<Int64>, at: Int64): Int64
Runs one operation of the kernel on the registers, and answers one word. code[at] is the operation's number - an
operation of the kernel's own table below 1024, a function of the runtime from 1024 on - and the words after it
are its operands: register offsets from base, location indices, sizes.
It is native because it is the runtime: every function of runtime/ is reached through it, with the words of the
registers as its arguments, so the VM and a native binary share one implementation of every native.
Pitfalls
Nothing is checked. A register outside of words is memory that belongs to somebody else.
fn load
native static fn load(address: Int64): Int64
The word at address: a field of a counted block of the VM. It is native because an address is raw storage.
Pitfalls
An address that is no field of a live block is undefined behaviour, exactly as it is in C.
fn store
native static fn store(address: Int64, value: Int64)
Writes the word at address. Native for the reason Machine.load is.
Pitfalls
The block has to be unique before a write goes through it; the VM makes it so first, as the IR says.
fn loadAt
native static fn loadAt(base: Int64, index: Int64): Int64
The word index words after base: a register of a frame, a word of the code, a field of a block. It is
Machine.load of base + 8 * index, and native because the machine computes that address, wrapping, where
TorbScript would check the add and the multiply for an overflow that an address of the VM's own storage never has.
Pitfalls
Nothing is checked, the address arithmetic least of all: a word that is no word of a live block is undefined behaviour, exactly as it is in C.
fn storeAt
native static fn storeAt(base: Int64, index: Int64, value: Int64)
Writes the word index words after base: Machine.store of base + 8 * index, computed as Machine.loadAt
computes it.
Pitfalls
The block has to be unique before a write goes through it, as for Machine.store.
fn countUp
native static fn countUp(block: Int64): Bool
One count more of block, a counted block of the runtime or of the VM, or 0 for none: the retain of a value of
one pointer without a call of the kernel. Answers whether it was done; false leaves the retain to the kernel's
Retain, which is right for every block.
It counts a block of this thread alone. A shared block - a worker may change its count at the same moment - a count
that would reach the shared bit, and every count while a sandbox is open, whose every operation of the kernel checks
the script's memory first, answer false and leave the block as it was. 0 and an immortal block are nothing to
count and answer true.
Pitfalls
block has to be the counted word of a value, 0, or the address of a live block's header: anything else is
undefined behaviour, exactly as it is in C.
fn countDown
native static fn countDown(block: Int64): Bool
One count less of block, as Machine.countUp counts one more: the release of a value of one pointer without a call
of the kernel, where it is not the last count. Answers whether it was done; false leaves the release to the
kernel's Release - the last count, which runs the destructor, releases the fields and frees the block, a count of
zero, which panics, and everything Machine.countUp leaves to the kernel as well.
Pitfalls
The same as Machine.countUp's.
fn placeText
native static fn placeText(var words: ArrayList<Int64>, at: Int64, text: String)
An immortal copy of text as the two words of a torb_text, at the register at of words (an absolute index).
It is how the constant pool gets its strings, and it is native because the storage of a text is the runtime's.
fn placeFloat
native static fn placeFloat(var words: ArrayList<Int64>, at: Int64, value: Float64)
The bits of value as one word at the register at. It is how the constant pool gets its floats, and it is native
because the language has no way to see the bits of a float.
fn install
native static fn install(interpreter: (Int64) => Int64)
Hands the kernel the one function it calls back into the interpreter with: a test body runtime/test.c runs behind
its recovery point, an equals or a hash of the program's own a map asks for, a task the scheduler resumes. The
kernel calls interpreter with the address of a request it describes, and the answer is the request's one word.
It is native because the runtime is what calls it: a closure handed to C is the one way C reaches TorbScript. The
kernel keeps interpreter until the next install replaces it.
Pitfalls
A request is only valid while the call that made it runs, and the interpreter has to answer it before anything else of the kernel runs on the same registers.