Preface
Designing a VM is no small task. A wide variety of different examples and design choices can be found in the literature, but none of them are likely to suit our context exactly – we need to pick and choose the pieces that make sense and assemble them together into a coherent whole. In order to know how to select the correct pieces and connect them together, we must have a clear idea of our boundary constraints: design choices, desired features and properties, and decisions we do and do not want to make. This post is an attempt to assemble a clear list thereof.
Basic context
- The basic units of state and history in the AVM are objects. Objects can send and receive messages, and each object has a history.
- Objects do not have mutable state. An object is either a code object or a data object. Data objects can be created exactly once, read any number of times, and destroyed exactly once. Code objects run a program when they are called.
- At any point in time, execution is taking place within one object. Execution can move between objects with calls.
Distributed execution
The AVM is executed by many machines. At any point in time, execution is taking place on one machine. The AVM must support teleportation, where programs can move the current execution context to another machine.
Objects are controlled, each by a unique machine at any point in the history of the object. Each machine tracks the histories of other machines and synchronizes state, and each AVM execution is tied to a particular historical snapshot. Programs interacting with an object which is locally controlled (controlled by the machine on which the program is executing) are guaranteed to see the latest version of the object, which programs interacting with an object which is not locally controlled may see an out-of-date version.
AVM programs must be able to:
- read the current (latest) history identifier from the executing machine
- switch to a target history identifier
Do code objects need to be moved between controllers? Or are data objects (which exist on exactly one controller, since they’re immutable) sufficient?
Pure computation
The AVM must not make decisions about how pure computations (functions) are represented. Instead, the AVM should support an arbitrary set of known functions which can be appended to ver time.
Transactionality
The AVM must support transactional semantics, where programs can:
- begin a transaction (scoped to a specific controller), starting a transaction context which collects pending state changes
- commit a transaction context (attempting to atomically commit all pending state changes)
- revert a transaction context (resulting in no state changes)
Preference-directed non-determinism (aka intents)
The AVM must support preference-directed non-determinism, where programs can:
- request an arbitrary input, given a specified preference distribution over the range
- constrain it in arbitrary ways (such that the transaction will fail if the constraints are not met)
This should be a sufficient substrate on top of which to implement Prolog-like logic programming.
Multi-party transactions
The AVM must support multi-party transaction constraints, where programs can:
- create linear constraints which must be satisfied by message sends in the same transaction in order for the transaction to be committed, where a given message send can only be used to satisfy one linear constraint
The combination of preference-directed non-determinism and multi-party transactions must be sufficient to allow programs to implement arbitrary intents.