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.