From 87dcacf75a573e646f1d74939fe0443f86b707ac Mon Sep 17 00:00:00 2001 From: nomaterials Date: Thu, 4 Jun 2026 14:17:58 +0200 Subject: [PATCH] docs: generate crate READMEs from lib.rs via cargo-rdme, checked in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The execution_graph README Quick Start had dropped `?` on `add_node`/`set_input_value` (both return `Result`), so the `fn main()` example failed to compile with two E0308s — a reader's first copy-paste would not build. Fix the root cause: make each crate's src/lib.rs doc comment the single source of truth and generate the README body from it via cargo-rdme (between `` markers), matching the convention used in other org repos (e.g. understory). A new `cargo rdme` CI job runs `--check` per crate so a README can never drift from its docs again. The richer README content (Model, Core Pieces, etc.) was migrated into the doc comments so nothing is lost and the docs.rs pages now carry the full guide; the Quick Start examples are compiled and run by the existing `cargo test --doc` step. --- .github/workflows/ci.yml | 25 +++++++ execution_graph/README.md | 35 ++++++---- execution_graph/src/lib.rs | 130 ++++++++++++++++++++++++++----------- execution_tape/README.md | 21 ++++-- execution_tape/src/lib.rs | 96 ++++++++++++++++++--------- 5 files changed, 221 insertions(+), 86 deletions(-) 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]