vihaco / virtual ISA & machine framework

Quick Start

Build a machine that adds two numbers and returns 42. It declares an arithmetic dialect, implements a calculator component, and connects it to program execution through an authored composite.

Begin with the version and dependency setup, then save the SST program as src/first-machine.sst and the Rust code as src/main.rs. Run cargo run. The first machine walkthrough explains each part.

The SST program

sst v1
.section(root):
    .header(root):
        metadata {
            strings: ["main", "first-machine.sst"],
            functions: [
                {
                    name: 0,
                    signature: { params: [], ret: [u64] },
                    local_count: 0,
                    start_address: 0,
                    end_address: 4,
                    file: 1,
                },
            ],
            labels: [],
            constants: [],
            main_function: 0,
            source_symbols: [],
            extra: {},
        };
    .header(root).
    .text(root):
        fn @main() -> u64 {
            execution::program::const_u64 20
            execution::program::const_u64 22
            calculator::arithmetic::add
            execution::program::ret 1
        }
    .text(root).
.section(root).

The Rust machine

// SPDX-FileCopyrightText: 2026 The vihaco Authors
// SPDX-License-Identifier: MIT

use vihaco::{
    Apply, BuildMessage, Execute, ExecutionState, HasInstructionSet, Image, Loadable, NoContext,
    PopFromStack, PushToStack, SstFile, Step, Type, Value, component, composite, dialect, field,
    loader::MetadataLoweringContext,
    module::{LocalModule, ProgramMetadata},
    syntax::{HeaderDeclarations, ParsedModule, lower_functions_with_metadata},
};

#[dialect]
pub mod arithmetic {
    #[instruction]
    pub struct Add;
}

#[component]
#[instructions { arithmetic }]
#[derive(Default)]
pub struct Calculator {
    additions: usize,
}

impl Execute<arithmetic::Add> for Calculator {
    type Message = PopFromStack<(u64, u64)>;
    type Effect = PushToStack<(u64,)>;
    type Error = eyre::Report;

    fn execute(
        &mut self,
        _: &arithmetic::Add,
        PopFromStack((lhs, rhs)): Self::Message,
    ) -> eyre::Result<Self::Effect> {
        let sum = lhs
            .checked_add(rhs)
            .ok_or_else(|| eyre::eyre!("addition overflow"))?;
        self.additions += 1;
        Ok(PushToStack((sum,)))
    }
}

#[composite]
pub struct Machine {
    #[device(0)]
    #[program]
    execution: vihaco_stdlib::ExecutionState,
    #[device(1)]
    calculator: Calculator,
}

type Instruction = <Machine as HasInstructionSet>::Runtime;

type SourceInstruction = <Machine as HasInstructionSet>::Syntax;

type Program = LocalModule<Instruction, Value, Type>;

impl<I> Step<field!(Machine::execution), I, Program> for Machine
where
    ExecutionState: Execute<I, Effect = (), Error = eyre::Report>,
    Self: BuildMessage<field!(Machine::execution), I, Program>,
{
    fn step(&mut self, instruction: &I, module: &Program) -> eyre::Result<()> {
        let message =
            <Self as BuildMessage<field!(Machine::execution), I, Program>>::build_message(
                self,
                instruction,
                module,
            )?;
        self.execution.execute(instruction, message)
    }
}

impl<I> Step<field!(Machine::calculator), I, Program> for Machine
where
    Calculator: Execute<I, Error = eyre::Report>,
    Self: BuildMessage<field!(Machine::calculator), I, Program>,
    ExecutionState: Apply<<Calculator as Execute<I>>::Effect>,
{
    fn step(&mut self, instruction: &I, module: &Program) -> eyre::Result<()> {
        let message =
            <Self as BuildMessage<field!(Machine::calculator), I, Program>>::build_message(
                self,
                instruction,
                module,
            )?;
        let effect = self.calculator.execute(instruction, message)?;
        self.execution.apply(effect);
        Ok(())
    }
}

pub fn main() -> eyre::Result<()> {
    let source = include_str!("first-machine.sst");
    let file = SstFile::<NoContext>::from_text(source)?;
    let ParsedModule { header, functions } = ParsedModule::<
        SourceInstruction,
        Type,
        HeaderDeclarations<ProgramMetadata>,
    >::parse_section(file.root())?;

    let mut declarations = header.into_iter();
    let metadata = declarations
        .next()
        .ok_or_else(|| eyre::eyre!("missing program metadata"))?;
    eyre::ensure!(
        declarations.next().is_none(),
        "expected one metadata declaration"
    );
    let mut context = MetadataLoweringContext::new(&metadata)?;
    let code = lower_functions_with_metadata(functions, &metadata, &mut context)?;
    let module = metadata.into_module(code)?;
    let machine = Machine {
        execution: ExecutionState::new(),
        calculator: Calculator::default(),
    };
    let mut loaded = Loadable::new(Image::new(module), machine);
    loaded.execute(&[])?;
    assert_eq!(loaded.machine().execution.stack(), &[42]);
    assert_eq!(loaded.machine().calculator.additions, 1);
    Ok(())
}

Continue through the mental model and author’s guide, or look up individual types in the API reference.