vihaco / virtual ISA & machine framework
Author’s guide
  1. Start here
  2. The mental model
  3. Your first machine
  4. Essential concepts
  5. Dialects and instructions
  6. Writing components
  7. Preparing messages
  8. Stack inputs and outputs
  9. Writing composites
  10. Executing one instruction
  11. Handling effects
  12. Running programs
  13. Images and Loadables
  14. Parsing source
  15. Loading SST programs
  16. Emitting SST from a compiler
  17. Binary formats
  18. Patterns for larger machines
  19. Testing and extending
  20. Pattern parser reference

Executing one instruction

A component can execute an operation on its own. A step is where you connect that operation to the rest of your machine: get the input ready, call the device, and decide what happens to its result. You can read other devices along the way.

Responsibilities of the execution traits

The trait names describe different parts of that work:

TraitWho supplies itWhat it does
Dispatch<Context>Composite macroChooses a route and calls its step
Step<F, I, Context>You, on the compositeConnects input preparation, execution, and result handling
BuildMessage<F, I, Context, M>Standard protocols or youPrepares the child’s required message
Execute<I>You, on the componentExecutes a borrowed instruction
Apply<E>Effect/storage protocols or youApplies one owned effect to a destination

The component’s associated types define its execution inputs and outputs. Step and Dispatch return eyre::Result<()>, so a step can propagate or convert errors along the way. Apply::apply returns (); write fallible result handling directly in the step when you need it.

Trace the addition

In your first machine, dispatch chooses field!(Machine::calculator) and arithmetic::Add. The step also receives a borrowed Program module as its context. It then does the following:

  1. BuildMessage takes two operands and returns PopFromStack<(u64, u64)>.
  2. Execute checks the addition and returns PushToStack<(u64,)>.
  3. Apply puts the answer in ExecutionState.

The runner takes care of moving to the next instruction. The calculator just needs its two numbers; it doesn’t access module metadata or execution storage.

Share a step by contract

The calculator’s step is generic over I. Its bounds say that the device can execute I, the composite can build its message, and execution state can apply its result. You can reuse that step for any instruction meeting those bounds.

Keep separate steps when instructions need different result handling. Reducing boilerplate isn’t worth hiding that difference. Sometimes a shared effect contract makes one step a good fit; sometimes specific steps are clearer.

You can also construct a message directly instead of calling BuildMessage. A step might update another device, validate more data, or perform several follow-up actions rather than apply a result to the stack. You still need to write the step: a builder only prepares input, and Execute only runs the component.

Context and borrowing

Dispatch, Step, and BuildMessage share a borrowed context. In the runner, that’s the module, held separately from the mutable machine. Instructions also borrow the image, so execution doesn’t need to clone them. Execute receives the borrowed instruction and the prepared message.

For a typed tool or interactive command, you can call a chosen Step directly without constructing an aggregate instruction. Make sure its requirements are already satisfied. A direct step call won’t set up an invocation or manage instruction progression for you.

Errors are visible where they occur

Preparing input, executing the component, or handling a result can fail. Earlier operand consumption, device changes, and applied effects stay in place. Propagate failures from result handling and keep useful eyre context. Before retrying a step, check whether doing so would repeat work that already succeeded.

Handling effects covers the choices for result handling.

On this page
  1. Responsibilities of the execution traits
  2. Trace the addition
  3. Share a step by contract
  4. Context and borrowing
  5. Errors are visible where they occur