eval/stepopcode, which can advance arbitrarily nested representations of the VM in O(1) time (amortized)
stepsupport and/or metering for massive performance gains and minimalist semantics
RLP makes a good bytecode encoding because half of the one-byte values are serialized directly, and a list of these is serialized directly in a byte sequence. Naturally, you will want core ops that manipulate RLP. This gives you a nice homoiconic foundation for your deterministic VM.
A core subset of basic stack ops for specifying pure functions that are guaranteed to terminate is useful for any higher level language. You can statically determine worst-case execution cost.
This core has a
call that is equivalent to inlining, and a
eval for capturing and advancing emulation frames.
All items on the stack are RLP objects, recursive lists with buffers at the leafs. RLP operations all take constant time in the implementation, this requirement basically defines the set of operations. These are usable like cons lists or buffer, depending on node type.
00- 3f push/<k> 40 push/data <d> stack/dup stack/drop stack/swap stack/rot rlp/isBlob rlp/isList num64/cast, cast-try num64/add,sub,...,not,and,or num64/add-try, sub-try, etc (rlp/isBlob) blob/len blob/cat blob/slice [blob/zeros <k>] (rlp/isList) list/len list/cat list/cons list/head list/tail list/lempty list/wrap list/unwrap [control/case [k,f]* f?] [control/loop k f] [control/bind f* f] [control/call k] control/break // variants: (loop|case)*cleanstack? control/continue // variants: (loop|case)*cleanstack? control/ret // variants: cleanstack?*flag? control/trap // variants: many... 70 [eval/step k] // advance the state of the emulation frame on the data stack 71 [eval/frame k] // put binding k onto the data stack as a fresh evaluation frame 7f unreachable # extensions 80+ extended instruction set (2-byte+ operations)