Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Decision (operator, 2026-10-13): the contract's only dependency becomes mask-ris
| D-id | scope | status | gate / falsifier |
|---|---|---|---|
| **D-FOLD-CORE-a** | `lance-graph-r2il` becomes a workspace member; it, `lance-graph-nal` and `r2il-mask-abi-probe` take OGAR by git `main` (the `lance-graph-ogar` coordinate) instead of `../../../OGAR` paths; the root patches the contract's git URL to the member | Shipped #1534 `0a6708b` | `cargo metadata`: one `lance-graph-contract`, one `ogar-loco`, one `ogar-r2il`; tests: r2il 19, nal 8, probe 40, all green |
| **D-FOLD-CORE-b** | `crates/lance-graph` depends on mask-risc, quack and R2IL by default | Queued | core builds and tests green with the three in the default graph |
| **D-FOLD-CORE-b** | `crates/lance-graph` depends on mask-risc, quack and R2IL by default | In PR | core builds and tests green with the three in the default graph; `tests/fold_path.rs` runs a Quack query and an R2IL count through `lance_graph::fold` against row loops (disable run: dropping one row range turns it red, 303 vs 201) |
| **D-FOLD-CORE-c** | NAL as a module of `crates/lance-graph`; `crates/lance-graph-nal` removed; W2 tests moved unchanged | Queued | the 8 W2 tests pass unchanged in core |

## D-PROD-V4 — production build tiers (2026-10-13)
Expand Down
8 changes: 8 additions & 0 deletions crates/lance-graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ datafusion-functions-aggregate = { workspace = true }
futures = "0.3"
lance-graph-catalog = { path = "../lance-graph-catalog", version = "0.5.4" }
lance-graph-contract = { path = "../lance-graph-contract" }
# The fold path, as a core capability (D-FOLD-CORE-b): mask-risc is the only
# bulk execution engine; Quack and R2IL are the two frontends that lower onto
# it (Quack for set/aggregate queries, R2IL for procedures such as NAL).
# Unconditional on purpose: the polyfill is part of core, not an add-on.
# Re-exported as `lance_graph::fold`.
lance-graph-mask-risc = { path = "../lance-graph-mask-risc" }
lance-graph-quack = { path = "../lance-graph-quack" }
lance-graph-r2il = { path = "../lance-graph-r2il" }
lance = { workspace = true }
lance-linalg = { workspace = true }
lance-namespace = "13.*"
Expand Down
15 changes: 15 additions & 0 deletions crates/lance-graph/src/fold.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! The fold path: mask-risc and the two frontends that lower onto it.
//!
//! - [`mask_risc`]: the bulk execution engine. Programs over resident mask
//! planes and value lanes; compiled once, run over a row extent.
//! - [`quack`]: the set/aggregate frontend. A `Query` (filter + aggregate)
//! lowers to one mask-risc program.
//! - [`r2il`]: the procedure frontend. R2IL calls emit mask-risc programs
//! through `FoldDialect`; NAL operations are procedures over it.
//!
//! These are re-exports, not wrappers: core adds no second algebra over them.
//! A capability that is missing here is missing in one of the three crates.

pub use lance_graph_mask_risc as mask_risc;
pub use lance_graph_quack as quack;
pub use lance_graph_r2il as r2il;
1 change: 1 addition & 0 deletions crates/lance-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod csr_index;
pub mod datafusion_planner;
pub mod dev_s3_env;
pub mod error;
pub mod fold;
pub mod graph;
pub mod lance_native_planner;
pub mod lance_vector_search;
Expand Down
80 changes: 80 additions & 0 deletions crates/lance-graph/tests/fold_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! D-FOLD-CORE-b: the fold path is a core capability.
//!
//! `lance-graph` reaches mask-risc, Quack and R2IL through `lance_graph::fold`
//! with no extra dependency or feature. Each test runs a query through one
//! frontend and checks it against a plain row loop over the same lane.

use lance_graph::fold::mask_risc::{self, Foreign, LaneRef, Planes, Pred, Scratch, Value};
use lance_graph::fold::quack::{self, Agg, Cmp, Col, Filter, Query};
use lance_graph::fold::r2il::{procs, FoldDialect, Val};

/// A deterministic lane whose values cover `0..1000` in scrambled row order,
/// so a value predicate selects scattered rows, not a row range.
fn lane() -> Vec<i32> {
(0..4096i32)
.map(|i| (i.wrapping_mul(7919)) % 1000)
.collect()
}

/// A range of ROWS (`Cmp::Range` is a row range on the ordered axis, not a
/// value range).
fn rows(lo: u32, hi: u32) -> Filter {
Filter::Cmp(Col(0), Cmp::Range { lo, hi })
}

/// FAILS IF: core cannot lower a Quack query and run it on mask-risc, the two
/// row ranges are not intersected, or the value predicate is not applied
/// inside the intersection.
#[test]
fn quack_through_core_equals_the_row_loop() {
let v = lane();
let lanes = [LaneRef::I32(&v)];
let planes = Planes {
n_rows: v.len(),
masks: &[],
lanes: &lanes,
};
let q = Query {
filter: Filter::and([
rows(100, 700),
rows(300, 900),
Filter::Cmp(Col(0), Cmp::GeI32(500)),
]),
agg: Agg::Count,
};
let program = quack::lower_fused(&q).expect("lowers");
let mut scratch = Scratch::for_program(&program, v.len()).expect("scratch");
let got = mask_risc::execute(&program, &planes, &mut scratch, None).expect("runs");

let want = (300..700).filter(|&r| v[r] >= 500).count();
// Anti-vacuity: the value predicate cuts inside the row intersection.
assert!(want > 0 && want < 400, "want = {want}");
assert_eq!(got, Value::Count(want));
}

/// FAILS IF: core cannot run an R2IL procedure over mask-risc: a row range
/// counted through the fold dialect must equal its width.
#[test]
fn r2il_through_core_counts_a_row_range() {
let v = lane();
let lanes = [LaneRef::I32(&v)];
let planes = Planes {
n_rows: v.len(),
masks: &[],
lanes: &lanes,
};
let foreign = Foreign {
planes: &[],
lanes: &[],
};
let (lo, hi) = (130u32, 3001u32);
let mut d = FoldDialect::new(&planes, &foreign);
let mut stack: Vec<Val> = Vec::new();
d.emit_pred(Pred::Range { lo, hi }, &mut stack);
procs::count(&mut d, &mut stack).expect("count");
assert!(d.poison().is_none());
match stack.as_slice() {
[Val::Scalar(n)] => assert_eq!(*n, i64::from(hi - lo)),
other => panic!("expected one scalar, got {other:?}"),
}
}
Loading