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
217 changes: 217 additions & 0 deletions .claude/board/entries/2026-10-21-dr256-iam-nested-groups.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion .claude/board/entries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately
opposite directions; the stranding this convention prevents shows up in
exactly one of them, never both.

413 entries, 2026-08-06 .. 2026-10-21.
414 entries, 2026-08-06 .. 2026-10-21.

| date | entry id | finding | file |
|---|---|---|---|
Expand All @@ -34,6 +34,7 @@ exactly one of them, never both.
| 2026-10-21 | `D-RU-3` | | [2026-10-21-u3-cross-rule-cse.md](2026-10-21-u3-cross-rule-cse.md) |
| 2026-10-21 | `D-RU-2` | | [2026-10-21-u2-let-planes.md](2026-10-21-u2-let-planes.md) |
| 2026-10-21 | `tuple-zero-copy-council` | | [2026-10-21-tuple-zero-copy-council.md](2026-10-21-tuple-zero-copy-council.md) |
| 2026-10-21 | `dr256-iam-nested-groups` | | [2026-10-21-dr256-iam-nested-groups.md](2026-10-21-dr256-iam-nested-groups.md) |
| 2026-10-21 | `D-RU-6-LAB-1` | | [2026-10-21-d-ru-6-tile-walk-probe.md](2026-10-21-d-ru-6-tile-walk-probe.md) |
| 2026-10-21 | `D-RU-6-LAB-2` | | [2026-10-21-d-ru-6-sparse-compute.md](2026-10-21-d-ru-6-sparse-compute.md) |
| 2026-10-21 | `D-RU-6-LAB-3` | | [2026-10-21-d-ru-6-dirty-frontier.md](2026-10-21-d-ru-6-dirty-frontier.md) |
Expand Down
5 changes: 5 additions & 0 deletions crates/dir-rmap-probe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ crepe = { git = "https://github.com/AdaWorldAPI/crepe", branch = "ccr-d3ce1263-i
# the contract is used.
[dev-dependencies]
lance-graph-contract = { path = "../lance-graph-contract" }
# Method folds over bound populations (tests/qr_focus.rs, examples/qr_focus.rs):
# a population is lent as a `Rel::Plane` and folded by relalg's existing
# mask-risc lowering. No evaluator of the probe's own.
lance-graph-relalg = { path = "../lance-graph-relalg" }
lance-graph-mask-risc = { path = "../lance-graph-mask-risc" }

[patch."https://github.com/AdaWorldAPI/ndarray"]
ndarray = { path = "../../../ndarray" }
Expand Down
208 changes: 208 additions & 0 deletions crates/dir-rmap-probe/examples/qr_amortize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//! Amortization of a masked count over several QR tiles (arm Q).
//!
//! ```text
//! cargo run --release --example qr_amortize # 65,000 users
//! cargo run --release --example qr_amortize -- 8000
//! ```
//!
//! One question ("how many users hold role r", or "are effectively in group
//! g") over `k` tiles of 256 users, selected by a tile mask spread across
//! the population, with rows inside each tile selected by a row mask (all
//! rows, or interlaced: even rows in even tiles, odd rows in odd tiles).
//! Routes, all checked equal before timing:
//!
//! * `page`: resolve each selected page on first touch, then fold its
//! resolved roles over the active rows;
//! * `warm`: the same fold over pages already resolved;
//! * `masked`: [`Qr::count_masked`]: each needed group's direct members are
//! read once and set bits only in selected tiles; popcount of tile AND row
//! mask. No page is resolved.
//! * `runs (cold)`: [`Runs::count`] with the per-slot tile pointer tables
//! built inside the timed call; `runs (warm)`: tables already built. Only
//! the selected tiles' runs are visited.

use dir_rmap_probe::World;
use dir_rmap_probe::fixture::{Fixture, Spec};
use dir_rmap_probe::focus::Row;
use dir_rmap_probe::prep::Prepared;
use dir_rmap_probe::qr::{Page, Qr, Runs};
use std::hint::black_box;
use std::time::{Duration, Instant};

const ALL: Row = [!0; 4];
const EVEN: Row = [0x5555_5555_5555_5555; 4];
const ODD: Row = [0xAAAA_AAAA_AAAA_AAAA; 4];

fn bit(r: &Row, i: usize) -> bool {
r[i >> 6] >> (i & 63) & 1 == 1
}

fn median(reps: usize, mut f: impl FnMut()) -> Duration {
let mut t: Vec<Duration> = (0..reps)
.map(|_| {
let s = Instant::now();
f();
s.elapsed()
})
.collect();
t.sort();
t[reps / 2]
}

fn fmt(d: Duration) -> String {
if d >= Duration::from_millis(1) {
"FAILED GATE (ms)".into()
} else {
format!("{:.1} us", d.as_secs_f64() * 1e6)
}
}

/// `k` tiles spread evenly over `pages`.
fn tiles(pages: usize, k: usize) -> Vec<u64> {
let mut m = vec![0u64; pages.div_ceil(64)];
for j in 0..k {
let p = j * pages / k;
m[p >> 6] |= 1 << (p & 63);
}
m
}

fn selected(t: &[u64], pages: usize) -> Vec<usize> {
(0..pages)
.filter(|&p| t[p >> 6] >> (p & 63) & 1 == 1)
.collect()
}

/// Count via resolved pages: `role` = Some(r) for a role question, else a
/// group slot `t` tested on the effective row.
fn fold(qr: &Qr, pages: &[(usize, &Page)], rows: &[Row], q: Q) -> u64 {
let mut n = 0;
for &(p, page) in pages {
for i in 0..256 {
if bit(&rows[p], i) {
n += u64::from(match q {
Q::Role(r) => page.roles[i] >> r & 1 == 1,
Q::Group(t) => bit(&qr.effective(&page.member[i]), t),
});
}
}
}
n
}

#[derive(Clone, Copy)]
enum Q {
Role(u32),
Group(usize),
}

fn main() {
let users: usize = std::env::args()
.nth(1)
.map_or(65_000, |s| s.parse().expect("user count"));
let mut spec = Spec::small();
spec.users = users;
let w = World::new(Fixture::generate(spec));
let b: Prepared = w.prepare();
let qr = Qr::bind(&b, &w.witness()).unwrap();
let np = qr.pages();
let all: Vec<Page> = (0..np).map(|p| qr.page(&b, p).unwrap()).collect();

// The role carried by the most slots, and the group reached from the
// most slots (the widest questions).
let r = (0..64)
.max_by_key(|&r| qr.role_slots[r].iter().map(|x| x.count_ones()).sum::<u32>())
.unwrap();
let g = *qr
.ids
.iter()
.max_by_key(|&&g| {
qr.slots_reaching(g)
.unwrap()
.iter()
.map(|x| x.count_ones())
.sum::<u32>()
})
.unwrap();
let pop = |row: &Row| row.iter().map(|x| x.count_ones()).sum::<u32>();
let questions = [
("role", qr.role_slots[r], Q::Role(r as u32)),
(
"group",
qr.slots_reaching(g).unwrap(),
Q::Group(qr.slot(g).unwrap()),
),
];
let row_sets: [(&str, Vec<Row>); 2] = [
("all rows", vec![ALL; np]),
(
"interlaced",
(0..np)
.map(|p| if p % 2 == 0 { EVEN } else { ODD })
.collect(),
),
];
println!(
"users {}, tiles {np}, security slots {}",
b.users,
qr.ids.len()
);
let mut scratch = vec![[0u64; 4]; np];
for (qname, slots, q) in questions {
let members: usize = qr
.ids
.iter()
.enumerate()
.filter(|&(s, _)| bit(&slots, s))
.map(|(_, &g)| b.group_users.row(g as usize).len())
.sum();
println!(
"\n{qname}: {} slots, {members} direct memberships read by the masked route",
pop(&slots)
);
for (rname, rows) in &row_sets {
println!(" {rname}:");
println!(
" {:>4} {:>18} {:>12} {:>12} {:>12} {:>12}",
"k", "page (first touch)", "warm page", "masked", "runs cold", "runs warm"
);
for k in [1, 2, 4, 8, 16, 32, 64, 128, np] {
let k = k.min(np);
let t = tiles(np, k);
let sel = selected(&t, np);
let warm: Vec<(usize, &Page)> = sel.iter().map(|&p| (p, &all[p])).collect();
let want = fold(&qr, &warm, rows, q);
let got = qr.count_masked(&b, &slots, &t, rows, &mut scratch).unwrap();
assert_eq!(got, want, "{qname} {rname} k={k}");
let t_page = median(9, || {
let fresh: Vec<Page> = sel.iter().map(|&p| qr.page(&b, p).unwrap()).collect();
let v: Vec<(usize, &Page)> = sel.iter().copied().zip(fresh.iter()).collect();
black_box(fold(&qr, &v, rows, q));
});
let t_warm = median(9, || {
black_box(fold(&qr, &warm, rows, q));
});
let t_mask = median(9, || {
black_box(qr.count_masked(&b, &slots, &t, rows, &mut scratch).unwrap());
});
let mut runs = Runs::new(&qr);
assert_eq!(runs.count(&qr, &b, &slots, &t, rows).unwrap(), want);
let t_cold = median(9, || {
let mut runs = Runs::new(&qr);
black_box(runs.count(&qr, &b, &slots, &t, rows).unwrap());
});
let t_runs = median(9, || {
black_box(runs.count(&qr, &b, &slots, &t, rows).unwrap());
});
println!(
" {k:>4} {:>18} {:>12} {:>12} {:>12} {:>12}",
fmt(t_page),
fmt(t_warm),
fmt(t_mask),
fmt(t_cold),
fmt(t_runs),
);
}
}
}
}
154 changes: 154 additions & 0 deletions crates/dir-rmap-probe/examples/qr_cost.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! Cost of arm Q (the DR256 register) against the compiled closure (B+).
//!
//! ```text
//! cargo run --release --example qr_cost # 8,000 users
//! cargo run --release --example qr_cost -- 65000
//! ```
//!
//! The group side is `Spec::small`'s (about 160 security groups, so the whole
//! chain fits one register); only the user count grows. Every route is
//! checked equal to dir-sim's own walk before it is timed. Preparation
//! (prepare, compile, bind) is reported per phase, never folded into a
//! per-user figure.
//!
//! The register resolves no user at bind. A question resolves the user's page
//! of 256 users; "one page" is that first-touch cost. "every page" is shown
//! only to say what an eager resolve of the whole population would cost —
//! nobody asks for that.

use dir_rmap_probe::fixture::{Fixture, Spec};
use dir_rmap_probe::prep::{Chain, Compiled, Ordinals};
use dir_rmap_probe::qr::{Page, Qr};
use dir_rmap_probe::{World, a_groups};
use ogar_dir_core::Guid128;
use std::collections::HashMap;
use std::hint::black_box;
use std::time::{Duration, Instant};

fn median(mut v: Vec<Duration>) -> Duration {
v.sort();
v[v.len() / 2]
}

fn per_user(n: usize, reps: usize, mut f: impl FnMut()) -> f64 {
let mut t = Vec::with_capacity(reps);
for _ in 0..reps {
let s = Instant::now();
f();
t.push(s.elapsed());
}
median(t).as_nanos() as f64 / n as f64
}

fn once(reps: usize, mut f: impl FnMut()) -> Duration {
let mut t = Vec::with_capacity(reps);
for _ in 0..reps {
let s = Instant::now();
f();
t.push(s.elapsed());
}
median(t)
}

/// P0: a millisecond result is a failed gate, never a measurement.
fn show(name: &str, d: Duration) {
if d >= Duration::from_millis(1) {
println!(" {name:<22} FAILED GATE (millisecond scale)");
} else {
println!(" {name:<22} {:>8.1} us", d.as_secs_f64() * 1e6);
}
}

fn main() {
let users: usize = std::env::args()
.nth(1)
.map_or(8_000, |s| s.parse().expect("user count"));
let mut spec = Spec::small();
spec.users = users;
let w = World::new(Fixture::generate(spec));
let v = w.view();
let ord = Ordinals::of(&w.f, &v);
let by_guid: HashMap<Guid128, u64> =
w.f.grants.iter().fold(HashMap::new(), |mut m, &(g, r)| {
*m.entry(w.f.groups[g as usize]).or_default() |= 1 << r;
m
});

let witness = w.witness();
let b = w.prepare();
let c = Compiled::compile(w.prepare());
let q = Qr::bind(&b, &witness).expect("security chain fits 256 slots");
let pages: Vec<Page> = (0..q.pages()).map(|p| q.page(&b, p).unwrap()).collect();
for (i, ug) in w.f.users.iter().enumerate() {
let want = a_groups(&v, ug, Chain::Security)
.iter()
.fold(0u64, |a, g| a | by_guid.get(g).copied().unwrap_or(0));
let uo = ord.user[i];
assert_eq!(c.roles_of(uo), want, "B+ {i}");
let (p, j) = (uo as usize / 256, uo as usize % 256);
assert_eq!(pages[p].roles_of(j), want, "Q page {i}");
assert_eq!(q.roles_live(&pages[p].member[j]), want, "Q live {i}");
}

let t_prepare = once(5, || {
black_box(w.prepare());
});
let t_compile = once(5, || {
black_box(Compiled::compile(w.prepare()));
});
let t_bind = once(15, || {
black_box(Qr::bind(&b, &witness).unwrap());
});
let t_page = once(51, || {
black_box(q.page(&b, black_box(0)).unwrap());
});
let t_all = once(5, || {
for p in 0..q.pages() {
black_box(q.page(&b, p).unwrap());
}
});

let n = w.f.users.len();
let reps = 15;
let t_fold = per_user(n, reps, || {
for &uo in &ord.user {
black_box(c.roles_of(black_box(uo)));
}
});
let at = |uo: u16| &pages[uo as usize / 256];
let t_q = per_user(n, reps, || {
for &uo in &ord.user {
black_box(at(uo).roles_of(black_box(uo) as usize % 256));
}
});
let t_live = per_user(n, reps, || {
for &uo in &ord.user {
black_box(q.roles_live(&at(uo).member[black_box(uo) as usize % 256]));
}
});
let t_eff = per_user(n, reps, || {
for &uo in &ord.user {
black_box(q.effective(&at(uo).member[black_box(uo) as usize % 256]));
}
});

println!(
"users {n}, groups {}, security slots {}, translations {}, all answers checked",
v.groups_len(),
q.ids.len(),
q.translations
);
println!("preparation, once per version:");
show("prepare", t_prepare);
show("prepare + compile (B+)", t_compile);
show("bind register (Q)", t_bind);
println!("on demand:");
show("one page (256 users)", t_page);
show(&format!("every page ({})", q.pages()), t_all);
println!("per user (median of runs):");
println!(" B+ fold {t_fold:>7.1} ns");
println!(" Q roles, resolved page {t_q:>7.1} ns");
println!(" Q roles, row x roles {t_live:>7.1} ns");
println!(" Q effective row (M x R) {t_eff:>7.1} ns");
println!("register bytes {}, page bytes {}", q.bytes(), Page::BYTES);
}
Loading
Loading