diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69927f0..0c630b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -316,6 +316,31 @@ jobs: env: RUSTDOCFLAGS: '--cfg docsrs -D warnings' + cargo-rdme: + name: cargo rdme + # Checks that each crate's README.md is in sync with its crate-level docs (src/lib.rs). + # To fix a failure, edit the doc comment in src/lib.rs and regenerate the README by running + # the same command without `--check`. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: install stable toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_STABLE_VER }} + + - name: install cargo-rdme + uses: taiki-e/install-action@v2 + with: + tool: cargo-rdme + + - name: check execution_tape README + run: cargo rdme --workspace-project=execution_tape --heading-base-level=0 --check + + - name: check execution_graph README + run: cargo rdme --workspace-project=execution_graph --heading-base-level=0 --check + # If this fails, consider changing your text or adding something to .typos.toml. typos: runs-on: ubuntu-latest diff --git a/execution_graph/README.md b/execution_graph/README.md index 9847098..bb4786c 100644 --- a/execution_graph/README.md +++ b/execution_graph/README.md @@ -1,5 +1,12 @@ # execution_graph + + + + Incremental execution graph built on `execution_tape`. This crate provides a small `no_std` graph that executes verified `execution_tape` programs as @@ -52,8 +59,8 @@ fn main() -> Result<(), Box> { let program = Arc::new(builder.build_verified()?); let mut graph = ExecutionGraph::new(NoHost, Limits::default()); - let node = graph.add_node(program, entry, vec!["x".into()]); - graph.set_input_value(node, "x", Value::I64(41)); + let node = graph.add_node(program, entry, vec!["x".into()])?; + graph.set_input_value(node, "x", Value::I64(41))?; let summary = graph.run_all()?; assert_eq!(summary.executed_nodes, 1); @@ -73,22 +80,22 @@ fn main() -> Result<(), Box> { `ResourceKey::Input("foo")` dirty, which may trigger re-execution of transitive dependents. Input names are part of the dependency key space: the string you pass to `set_input_value(node, -"foo", ..)` must match the string you pass to `invalidate_input("foo")` for incremental scheduling -to work. +"foo", ..)` must match the string you pass to `invalidate_input("foo")` for incremental +scheduling to work. Host state invalidation uses the same key space: if a host op records a -`ResourceKeyRef::HostState { op, key }` read during execution, you can invalidate that state later -via `ExecutionGraph::invalidate_tape_key(...)` (or by constructing the corresponding owned +`ResourceKeyRef::HostState { op, key }` read during execution, you can invalidate that state +later via `ExecutionGraph::invalidate_tape_key(...)` (or by constructing the corresponding owned `execution_graph::ResourceKey` and calling `ExecutionGraph::invalidate(...)`). Graph construction is checked at the public API boundary: `add_node`, `set_input_value`, and -`connect` return `GraphError` values for unknown entry functions, input arity mismatches, unknown -input names, and unknown output names. +`connect` return `GraphError` values for unknown entry functions, input arity mismatches, +unknown input names, and unknown output names. ## Execution behavior `run_node` drains and executes only the dirty work within the dependency closure of the target -node’s outputs, leaving unrelated dirty work dirty to be handled by a later `run_all`. +node's outputs, leaving unrelated dirty work dirty to be handled by a later `run_all`. For low overhead telemetry, `run_all` / `run_node` return only an executed-node summary. @@ -113,12 +120,14 @@ cargo run -p execution_graph_examples --bin tax -- --dot ## Current limitations -- `execution_graph` intentionally stays close to the VM: traps expose `execution_tape::vm::TrapInfo` - rather than source-language diagnostics. +- `execution_graph` intentionally stays close to the VM: traps expose + `execution_tape::vm::TrapInfo` rather than source-language diagnostics. - VM traps are still collapsed to `GraphError::Trap` at the graph boundary. Missing inputs, missing upstream outputs, bad output arity, and strict-deps failures are reported with context. -- Graph nodes are currently `execution_tape` entrypoints only; custom dispatch can be layered later - without changing the resource-key model. +- Graph nodes are currently `execution_tape` entrypoints only; custom dispatch can be layered + later without changing the resource-key model. + + ## Minimum supported Rust Version (MSRV) diff --git a/execution_graph/src/lib.rs b/execution_graph/src/lib.rs index 5dd36c5..7229c4e 100644 --- a/execution_graph/src/lib.rs +++ b/execution_graph/src/lib.rs @@ -1,22 +1,19 @@ // Copyright 2026 the Execution Tape Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +// After you edit the crate's doc comment, regenerate README.md by running: +// cargo rdme --workspace-project=execution_graph --heading-base-level=0 + //! Incremental execution graph built on `execution_tape`. //! -//! This crate will provide a graph whose nodes are verified "tapes" (program entrypoints) and -//! whose edges represent data dependencies, enabling sound incremental re-execution via dirty -//! tracking. -//! -//! ## Input key semantics +//! This crate provides a small `no_std` graph that executes verified `execution_tape` programs as +//! nodes and re-executes only the nodes that are affected by changes. //! -//! Incremental invalidation is keyed by [`ResourceKey::Input`]. The same input name string must be -//! used consistently: the name passed to [`ExecutionGraph::set_input_value`] must match the name -//! passed to [`ExecutionGraph::invalidate_input`] (otherwise the invalidation will not affect the -//! reads recorded by runs). +//! ## Quick Start //! -//! ## Example +//! Use `execution_tape` to build verified programs, then wire them as graph nodes: //! -//! ``` +//! ```rust //! use std::sync::Arc; //! //! use execution_graph::ExecutionGraph; @@ -41,34 +38,91 @@ //! } //! } //! -//! let mut asm = Asm::new(); -//! asm.const_i64(2, 1); -//! asm.i64_add(3, 1, 2); -//! asm.ret(0, &[3]); -//! -//! let mut builder = ProgramBuilder::new(); -//! let entry = builder.push_function_checked( -//! asm, -//! FunctionSig { -//! arg_types: vec![ValueType::I64], -//! ret_types: vec![ValueType::I64], -//! }, -//! )?; -//! builder.set_function_output_name(entry, 0, "y")?; -//! let program = Arc::new(builder.build_verified()?); -//! -//! let mut graph = ExecutionGraph::new(NoHost, Limits::default()); -//! let node = graph.add_node(program, entry, vec!["x".into()])?; -//! graph.set_input_value(node, "x", Value::I64(41))?; -//! -//! let summary = graph.run_all()?; -//! assert_eq!(summary.executed_nodes, 1); -//! assert_eq!( -//! graph.node_outputs(node).unwrap().get("y"), -//! Some(&Value::I64(42)) -//! ); -//! # Ok::<(), Box>(()) +//! fn main() -> Result<(), Box> { +//! let mut asm = Asm::new(); +//! asm.const_i64(2, 1); +//! asm.i64_add(3, 1, 2); +//! asm.ret(0, &[3]); +//! +//! let mut builder = ProgramBuilder::new(); +//! let entry = builder.push_function_checked( +//! asm, +//! FunctionSig { +//! arg_types: vec![ValueType::I64], +//! ret_types: vec![ValueType::I64], +//! }, +//! )?; +//! builder.set_function_output_name(entry, 0, "y")?; +//! let program = Arc::new(builder.build_verified()?); +//! +//! let mut graph = ExecutionGraph::new(NoHost, Limits::default()); +//! let node = graph.add_node(program, entry, vec!["x".into()])?; +//! graph.set_input_value(node, "x", Value::I64(41))?; +//! +//! let summary = graph.run_all()?; +//! assert_eq!(summary.executed_nodes, 1); +//! assert_eq!(graph.node_outputs(node).unwrap().get("y"), Some(&Value::I64(42))); +//! Ok(()) +//! } //! ``` +//! +//! ## Model +//! +//! - **Nodes** are `(VerifiedProgram, entry FuncId)` pairs. +//! - **Edges** represent data dependencies; they are recorded dynamically from each node run: +//! - reading an external input records `ResourceKey::Input(name)` +//! - reading another node's output records `ResourceKey::NodeOutput { node, output }` +//! - host ops can record additional dependencies via `execution_tape::host::AccessSink` +//! - **Invalidation** is done by name: calling `invalidate_input("foo")` marks the input key +//! `ResourceKey::Input("foo")` dirty, which may trigger re-execution of transitive dependents. +//! +//! Input names are part of the dependency key space: the string you pass to `set_input_value(node, +//! "foo", ..)` must match the string you pass to `invalidate_input("foo")` for incremental +//! scheduling to work. +//! +//! Host state invalidation uses the same key space: if a host op records a +//! `ResourceKeyRef::HostState { op, key }` read during execution, you can invalidate that state +//! later via `ExecutionGraph::invalidate_tape_key(...)` (or by constructing the corresponding owned +//! `execution_graph::ResourceKey` and calling `ExecutionGraph::invalidate(...)`). +//! +//! Graph construction is checked at the public API boundary: `add_node`, `set_input_value`, and +//! `connect` return `GraphError` values for unknown entry functions, input arity mismatches, +//! unknown input names, and unknown output names. +//! +//! ## Execution behavior +//! +//! `run_node` drains and executes only the dirty work within the dependency closure of the target +//! node's outputs, leaving unrelated dirty work dirty to be handled by a later `run_all`. +//! +//! For low overhead telemetry, `run_all` / `run_node` return only an executed-node summary. +//! +//! For debugging and instrumentation: +//! - `run_all_with_report` / `run_node_with_report` accept a `ReportDetailMask` so you can choose +//! cheaper detail levels (for example, node + immediate cause key without path tracing). +//! - Use `ReportDetailMask::FULL` when you want full per-node cause paths. +//! +//! ## Demo +//! +//! Run the demo with: +//! +//! ```sh +//! cargo run -p execution_graph_examples --bin tax +//! ``` +//! +//! Emit Graphviz DOT for the same graph: +//! +//! ```sh +//! cargo run -p execution_graph_examples --bin tax -- --dot +//! ``` +//! +//! ## Current limitations +//! +//! - `execution_graph` intentionally stays close to the VM: traps expose +//! `execution_tape::vm::TrapInfo` rather than source-language diagnostics. +//! - VM traps are still collapsed to `GraphError::Trap` at the graph boundary. Missing inputs, +//! missing upstream outputs, bad output arity, and strict-deps failures are reported with context. +//! - Graph nodes are currently `execution_tape` entrypoints only; custom dispatch can be layered +//! later without changing the resource-key model. #![no_std] diff --git a/execution_tape/README.md b/execution_tape/README.md index dd3a1d5..683f19c 100644 --- a/execution_tape/README.md +++ b/execution_tape/README.md @@ -1,13 +1,21 @@ # `execution_tape` + + + + Portable, verifiable bytecode container format and register VM runtime (draft). `execution_tape` is the low-level execution layer for already-lowered programs. It owns the -portable program format, verifier, register VM, host-call ABI, aggregate values, tracing hooks, and -disassembly tools. It does not own language semantics, graph authoring, or host object lifetimes. +portable program format, verifier, register VM, host-call ABI, aggregate values, tracing hooks, +and disassembly tools. It does not own language semantics, graph authoring, or host object +lifetimes. -The crate is `no_std + alloc` by default. The `std` feature is currently reserved for integrations -that need standard-library support. +The crate is `no_std + alloc` by default. The `std` feature is currently reserved for +integrations that need standard-library support. ## Quick Start @@ -73,7 +81,8 @@ fn main() -> Result<(), Box> { - `program`: serialized program model, type tables, constants, host signatures, and names. - `verifier`: validation and lowering into an execution-ready `VerifiedProgram`. - `vm`: bounded interpreter for verified programs. -- `host`: host-call trait, borrowed argument views, aggregate readers, and access recording hooks. +- `host`: host-call trait, borrowed argument views, aggregate readers, and access recording + hooks. - `trace`: low-overhead tracing events for profiling and diagnostics. - `disasm`: human-readable disassembly for verified programs. @@ -92,6 +101,8 @@ Print disassembly for a small branching program: cargo run -p execution_tape --example disasm ``` + + ## Minimum supported Rust Version (MSRV) This crate has been verified to compile with **Rust 1.88** and later. diff --git a/execution_tape/src/lib.rs b/execution_tape/src/lib.rs index 798ec91..eb5aefd 100644 --- a/execution_tape/src/lib.rs +++ b/execution_tape/src/lib.rs @@ -1,19 +1,27 @@ // Copyright 2026 the Execution Tape Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! `execution_tape`: a portable, verifiable bytecode format and register VM runtime. +// After you edit the crate's doc comment, regenerate README.md by running: +// cargo rdme --workspace-project=execution_tape --heading-base-level=0 + +//! Portable, verifiable bytecode container format and register VM runtime (draft). +//! +//! `execution_tape` is the low-level execution layer for already-lowered programs. It owns the +//! portable program format, verifier, register VM, host-call ABI, aggregate values, tracing hooks, +//! and disassembly tools. It does not own language semantics, graph authoring, or host object +//! lifetimes. //! -//! This crate is in early design/implementation. The current v1 draft spec lives in: -//! - `docs/v1_spec.md` -//! - `docs/overview.md` +//! The crate is `no_std + alloc` by default. The `std` feature is currently reserved for +//! integrations that need standard-library support. //! -//! ## Example +//! ## Quick Start //! -//! ```no_run +//! Build, verify, and run a one-function program: +//! +//! ```rust //! extern crate alloc; //! //! use alloc::vec; -//! use alloc::vec::Vec; //! //! use execution_tape::asm::{Asm, FunctionSig, ProgramBuilder}; //! use execution_tape::host::{Host, HostContext, HostError, SigHash, ValueRef}; @@ -37,29 +45,57 @@ //! } //! } //! -//! let mut a = Asm::new(); -//! a.const_i64(2, 1); -//! a.i64_add(3, 1, 2); -//! a.ret(0, &[3]); -//! -//! let mut pb = ProgramBuilder::new(); -//! let entry = pb.push_function_checked( -//! a, -//! FunctionSig { -//! arg_types: vec![ValueType::I64], -//! ret_types: vec![ValueType::I64], -//! }, -//! )?; -//! pb.set_function_input_name(entry, 0, "x")?; -//! pb.set_function_output_name(entry, 0, "y")?; -//! let program = pb.build_verified()?; -//! -//! let mut vm = Vm::new(NoHost, Limits::default()); -//! let out = vm -//! .run(&program, entry, &[Value::I64(7)], TraceMask::NONE, None) -//! .unwrap(); -//! assert_eq!(out, vec![Value::I64(8)]); -//! # Ok::<(), execution_tape::asm::BuildError>(()) +//! fn main() -> Result<(), Box> { +//! let mut asm = Asm::new(); +//! asm.const_i64(2, 1); +//! asm.i64_add(3, 1, 2); +//! asm.ret(0, &[3]); +//! +//! let mut builder = ProgramBuilder::new(); +//! builder.set_program_name("add_one"); +//! let entry = builder.push_function_checked( +//! asm, +//! FunctionSig { +//! arg_types: vec![ValueType::I64], +//! ret_types: vec![ValueType::I64], +//! }, +//! )?; +//! builder.set_function_input_name(entry, 0, "x")?; +//! builder.set_function_output_name(entry, 0, "y")?; +//! +//! let program = builder.build_verified()?; +//! let mut vm = Vm::new(NoHost, Limits::default()); +//! let out = vm.run(&program, entry, &[Value::I64(41)], TraceMask::NONE, None)?; +//! assert_eq!(out, vec![Value::I64(42)]); +//! Ok(()) +//! } +//! ``` +//! +//! ## Core Pieces +//! +//! - `asm`: ergonomic builders for functions, call signatures, constants, host signatures, and +//! bytecode emission. +//! - `program`: serialized program model, type tables, constants, host signatures, and names. +//! - `verifier`: validation and lowering into an execution-ready `VerifiedProgram`. +//! - `vm`: bounded interpreter for verified programs. +//! - `host`: host-call trait, borrowed argument views, aggregate readers, and access recording +//! hooks. +//! - `trace`: low-overhead tracing events for profiling and diagnostics. +//! - `disasm`: human-readable disassembly for verified programs. +//! +//! ## Design Docs +//! +//! The repository-level design notes live outside the packaged crate: +//! +//! - +//! - +//! +//! ## Examples +//! +//! Print disassembly for a small branching program: +//! +//! ```sh +//! cargo run -p execution_tape --example disasm //! ``` #![no_std]