Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7338d19
Migrate cost lens to complexity.dag
briansrls Apr 16, 2026
d78551f
Fix complexity lens clippy naming
briansrls Apr 16, 2026
26a1045
test: pin §3.3 four-fixture ownership regression suite (Lane 6)
briansrls Apr 16, 2026
bae3e11
Merge remote-tracking branch 'origin/main' into fast-ram-69
briansrls Apr 16, 2026
e702a63
test: verify §3.3 ParameterContract facts, not symptoms (Lane 6)
briansrls Apr 16, 2026
6f06235
Stabilize v2 CI bootstrap diffs
briansrls Apr 16, 2026
7a90c5e
Stabilize v2 CI bootstrap diffs
briansrls Apr 16, 2026
0ceda14
Lane 4: migrate provenance lens to .dag authority
briansrls Apr 16, 2026
38b9932
merge #486: four-fixture ownership regression suite (Lane 6)
briansrls Apr 16, 2026
639b3de
merge #485: migrate cost lens to complexity.dag
briansrls Apr 16, 2026
22651fe
merge #487: Lane 4 migrate provenance lens to .dag authority
briansrls Apr 16, 2026
b38f765
complexity.dag: fix double-decrement underreport + document fuel scaf…
briansrls Apr 16, 2026
d4854b9
complexity.dag: replace fuel counter with structural forward fold
briansrls Apr 17, 2026
0d51e5b
fmt: rustfmt regen_lens_cost.rs
briansrls Apr 17, 2026
58c391c
complexity.dag: fail-closed MissingCost; pin Bind via method doc
briansrls Apr 17, 2026
8bac897
tests: revert -D warnings + #[allow(warnings)] band-aids
briansrls Apr 17, 2026
28c27b0
m1_3: batch 14 rustc-roundtrip tests into two shared harnesses
briansrls Apr 17, 2026
d9ad4af
tests/common: extract RustcHarness — lazy DI / RAII for rustc roundtrips
briansrls Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/v3/compiler/src/bin/regen_lens_cost.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

use v3_compiler::compile_to_dag;
use v3_compiler::emit_rust::emit_rust_module;

const HEADER: &str = "// AUTO-GENERATED from `src/v3/lenses/complexity.dag` via\n\
// `emit_rust_module`. Regenerate instead of hand-editing.\n\n";

fn main() {
let lens_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("lenses")
.join("complexity.dag");
let source = std::fs::read_to_string(&lens_path).expect("read complexity.dag");
let dag = compile_to_dag(&source, lens_path.to_string_lossy().as_ref())
.expect("complexity.dag compiles");
let raw = emit_rust_module(&dag).expect("emit lens module");
let combined = format!("{HEADER}{raw}");

let mut child = Command::new("rustfmt")
.arg("--emit")
.arg("stdout")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("spawn rustfmt");
child
.stdin
.as_mut()
.unwrap()
.write_all(combined.as_bytes())
.unwrap();
let output = child.wait_with_output().expect("rustfmt");
assert!(output.status.success(), "rustfmt failed");
let formatted = String::from_utf8(output.stdout).expect("utf8");

let out_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("lens_cost_generated.rs");
std::fs::write(&out_path, &formatted).expect("write lens_cost_generated.rs");
println!("wrote {}", out_path.display());
}
7 changes: 7 additions & 0 deletions src/v3/compiler/src/dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,13 @@ pub struct BindNode {
}

impl BindNode {
/// Structural alias for `self.value`. `std/substrate.dag` names this
/// field `result_port` across all behavior variants; the Rust struct
/// kept the historical name `value` for BindNode only. `.dag`-generated
/// lenses read `bind.result_port` (see `lenses/complexity.dag` Bind
/// branch); hand-written Rust reads `bind.value` (see
/// `tests/m2_lens_cost_migration_test.rs`). This method is the single
/// point of agreement between the two.
pub fn result_port(&self) -> PortId {
self.value
}
Expand Down
90 changes: 25 additions & 65 deletions src/v3/compiler/src/lens_cost.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,21 @@
// Cost lens — counts structural operations in the sub-DAG rooted at
// a port. A pure-reader lens in the same template as `lens_depth`
// and `lens_provenance`: takes `&Dag`, returns a computed value, no
// storage, no side tables.
//
// Third observational lens. Purpose: validate that PR-B's substrate
// is still under the "new lens lands in tens of lines" success bar
// set by M0.3/M0.4. Depth was ~50 lines, provenance was ~40 lines;
// cost is another data point on the same curve.
//
// Cost semantics:
// - Value literal → 0 (leaf, no work)
// - Function parameter → 0 (leaf, no work)
// - Transform → 1 + sum(input costs) (one op + its args)
// - Branch → 1 + cond cost + max(path costs) (runtime
// fires exactly one path, so paths use max,
// not sum; the branch itself costs 1)
// - Loop → 1 + source + init (one op + its setup)
// - Bind → pass through (naming is free)
//
// Size budget: ~80 lines. If this balloons, the substrate is gapped
// for structural counting — fix the substrate before growing the
// lens.
//
// NOT a writer lens. The cost is computed on demand by query, not
// cached. When real performance benchmarks force caching, the
// "how do lenses store results" question from lens_depth's comment
// fires and gets answered uniformly for every lens at once.
use crate::dag::PortId;
use crate::Dag;

use crate::dag::{Behavior, Dag, NodeId, PortId};
mod generated {
#![allow(
dead_code,
unused_imports,
unused_parens,
unused_variables,
clippy::clone_on_copy,
clippy::collapsible_else_if
)]

use crate::dag::*;
use crate::diagnostics::*;

include!("lens_cost_generated.rs");
}

pub struct CostLens<'a> {
dag: &'a Dag,
Expand All @@ -38,43 +26,15 @@ impl<'a> CostLens<'a> {
Self { dag }
}

/// Structural cost for the sub-DAG rooted at `port`. Counts
/// Transform / Branch / Loop operations recursively; Value and
/// function-parameter leaves contribute 0; Bind passes through
/// to the bound value.
pub fn cost_of(&self, port: PortId) -> usize {
match self.dag.port(port).produced_by {
None => 0,
Some(node_id) => match self.dag.node(node_id) {
Behavior::Value(_) => 0,
_ => self.node_cost(node_id),
},
}
}

fn node_cost(&self, node_id: NodeId) -> usize {
match self.dag.node(node_id) {
// Unreachable — cost_of short-circuits Value above. Kept
// for match exhaustiveness and parity with lens_depth.
Behavior::Value(_) => 0,
Behavior::Transform(t) => {
let input_cost: usize = t.inputs.iter().map(|&p| self.cost_of(p)).sum();
1 + input_cost
}
Behavior::Branch(b) => {
let cond = self.cost_of(b.input);
let paths_max = b
.paths
.iter()
.map(|path| self.cost_of(path.output))
.max()
.unwrap_or(0);
1 + cond + paths_max
}
Behavior::Loop(l) => 1 + self.cost_of(l.source) + self.cost_of(l.init),
// Bind passes through — a named binding does no work,
// it just labels an existing port for downstream reads.
Behavior::Bind(b) => self.cost_of(b.value),
match generated::cost_of(self.dag, &port) {
generated::CostLookup::FoundCost { _0: cost } => usize::try_from(cost)
.expect("compiled complexity lens should emit a non-negative cost"),
generated::CostLookup::MissingCost => panic!(
"complexity lens returned MissingCost for port {port:?}; \
the DAG references a port whose producer is not in `d.nodes` \
and is not a bind parameter — malformed substrate"
),
}
}
}
151 changes: 151 additions & 0 deletions src/v3/compiler/src/lens_cost_generated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// AUTO-GENERATED from `src/v3/lenses/complexity.dag` via
// `emit_rust_module`. Regenerate instead of hand-editing.

#[derive(Clone, Debug)]
pub struct CostEntry {
pub port: PortId,
pub cost: CostLookup,
}
#[derive(Clone, Debug)]
pub enum CostLookup {
MissingCost,
FoundCost { _0: i64 },
}
pub fn cost_of(p0: &Dag, p1: &PortId) -> CostLookup {
lookup_cost(&(compute_costs(p0)), p1)
}
pub fn compute_costs(p0: &Dag) -> Vec<CostEntry> {
((p0).nodes())
.iter()
.fold(seed_bind_params((p0).nodes()), |__fold_acc, __fold_item| {
let mut __list = (__fold_acc).clone();
__list.insert(0, entry_for(&__fold_acc, __fold_item));
__list
})
}
pub fn seed_bind_params(p0: &[Behavior]) -> Vec<CostEntry> {
match p0 {
[] => Vec::new(),
[__list_head, __list_tail @ ..] => {
let mut __left = params_of(__list_head);
__left.extend(seed_bind_params(__list_tail));
__left
}
}
}
pub fn params_of(p0: &Behavior) -> Vec<CostEntry> {
match p0 {
Behavior::Value(v) => Vec::new(),
Behavior::Transform(t) => Vec::new(),
Behavior::Branch(b) => Vec::new(),
Behavior::Loop(l) => Vec::new(),
Behavior::Bind(bind) => param_entries(&((bind).params)),
}
}
pub fn param_entries(p0: &[PortId]) -> Vec<CostEntry> {
match p0 {
[] => Vec::new(),
[__list_head, __list_tail @ ..] => {
let mut __list = param_entries(__list_tail);
__list.insert(
0,
CostEntry {
port: (*(__list_head)),
cost: CostLookup::FoundCost { _0: 0 },
},
);
__list
}
}
}
pub fn entry_for(p0: &[CostEntry], p1: &Behavior) -> CostEntry {
match p1 {
Behavior::Value(v) => CostEntry {
port: (v).result_port(),
cost: CostLookup::FoundCost { _0: 0 },
},
Behavior::Transform(t) => CostEntry {
port: (t).result_port(),
cost: add_one(&(sum_costs(p0, &((t).inputs)))),
},
Behavior::Branch(b) => CostEntry {
port: (b).result_port(),
cost: add_one(
&(add_cost(
&(lookup_cost(p0, &((b).input))),
&(max_path_cost(p0, &((b).paths))),
)),
),
},
Behavior::Loop(l) => CostEntry {
port: (l).result_port(),
cost: add_one(
&(add_cost(
&(lookup_cost(p0, &((l).source))),
&(lookup_cost(p0, &((l).init))),
)),
),
},
Behavior::Bind(bind) => CostEntry {
port: (bind).result_port(),
cost: lookup_cost(p0, &((bind).result_port())),
},
}
}
pub fn sum_costs(p0: &[CostEntry], p1: &[PortId]) -> CostLookup {
(p1).iter().fold(
CostLookup::FoundCost { _0: 0 },
|__fold_acc, __fold_item| add_cost(&__fold_acc, &(lookup_cost(p0, __fold_item))),
)
}
pub fn max_path_cost(p0: &[CostEntry], p1: &[Path]) -> CostLookup {
(p1).iter().fold(
CostLookup::FoundCost { _0: 0 },
|__fold_acc, __fold_item| {
max_cost(
&__fold_acc,
&(lookup_cost(p0, &((__fold_item).result_port()))),
)
},
)
}
pub fn lookup_cost(p0: &[CostEntry], p1: &PortId) -> CostLookup {
match p0 {
[] => CostLookup::MissingCost,
[__list_head, __list_tail @ ..] => {
if ((__list_head).port == (*(p1))) {
((__list_head).cost).clone()
} else {
lookup_cost(__list_tail, p1)
}
}
}
}
pub fn add_one(p0: &CostLookup) -> CostLookup {
match p0 {
CostLookup::MissingCost => CostLookup::MissingCost,
CostLookup::FoundCost { _0: n } => CostLookup::FoundCost { _0: (1 + (*(n))) },
}
}
pub fn add_cost(p0: &CostLookup, p1: &CostLookup) -> CostLookup {
match p0 {
CostLookup::MissingCost => CostLookup::MissingCost,
CostLookup::FoundCost { _0: x } => match p1 {
CostLookup::MissingCost => CostLookup::MissingCost,
CostLookup::FoundCost { _0: y } => CostLookup::FoundCost {
_0: ((*(x)) + (*(y))),
},
},
}
}
pub fn max_cost(p0: &CostLookup, p1: &CostLookup) -> CostLookup {
match p0 {
CostLookup::MissingCost => CostLookup::MissingCost,
CostLookup::FoundCost { _0: x } => match p1 {
CostLookup::MissingCost => CostLookup::MissingCost,
CostLookup::FoundCost { _0: y } => CostLookup::FoundCost {
_0: if ((*(x)) > (*(y))) { (*(x)) } else { (*(y)) },
},
},
}
}
76 changes: 0 additions & 76 deletions src/v3/compiler/src/lens_provenance.rs

This file was deleted.

Loading
Loading