use std::fmt;
use std::sync::Arc;
use brink_compiler::{AnalysisOptions, Dialect, TypePolicy};
use brink_format::StoryData;
use brink_runtime::{DotNetRng, Program, Stats, Step, Story};
struct Scenario {
name: &'static str,
ink: &'static str,
inputs: Vec<usize>,
}
impl fmt::Display for Scenario {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name)
}
}
const MINIMAL_INK: &str = "../../tests/tier1/basics/I001-minimal-story/story.ink";
const HANOI_3_INK: &str = "../../tests/tier3/lists/tower-of-hanoi/story.ink";
const HANOI_3_INPUT: &str = include_str!("../../../tests/tier3/lists/tower-of-hanoi/input.txt");
const HANOI_10_INK: &str = "../../benchmarks/stories/hanoi-10/story.ink";
const HANOI_10_INPUT: &str = include_str!("../../../benchmarks/stories/hanoi-10/input.txt");
const CRUCIBLE_8_INK: &str = "../../benchmarks/stories/crucible-8/story.ink";
const CRUCIBLE_8_INPUT: &str = include_str!("../../../benchmarks/stories/crucible-8/input.txt");
const LOOP_APPEND_10K_INK: &str = "../../benchmarks/stories/loop-append-10k/story.ink";
const LOOP_APPEND_FIELD_10K_INK: &str = "../../benchmarks/stories/loop-append-field-10k/story.ink";
const SHARE_THEN_MUTATE_5K_INK: &str = "../../benchmarks/stories/share-then-mutate-5k/story.ink";
const FN_CREATION_DENSITY_10K_INK: &str =
"../../benchmarks/stories/fn-creation-density-10k/story.ink";
const FN_BIND_CHAIN_SHALLOW_INK: &str = "../../benchmarks/stories/fn-bind-chain-shallow/story.ink";
const FN_BIND_CHAIN_DEEP_INK: &str = "../../benchmarks/stories/fn-bind-chain-deep/story.ink";
const DYNAMIC_DISPATCH_10K_INK: &str = "../../benchmarks/stories/dynamic-dispatch-10k/story.ink";
const DIRECT_CALL_10K_INK: &str = "../../benchmarks/stories/direct-call-10k/story.ink";
const STRUCT_FIELD_ACCESS_10K_INK: &str =
"../../benchmarks/stories/struct-field-access-10k/story.ink";
const SAVE_STATE_SMALL_INK: &str = "../../benchmarks/stories/save-state-small/story.ink";
const SAVE_STATE_MEDIUM_INK: &str = "../../benchmarks/stories/save-state-medium/story.ink";
const SAVE_STATE_LARGE_INK: &str = "../../benchmarks/stories/save-state-large/story.ink";
const SNAPSHOT_RETENTION_G10_M10_INK: &str =
"../../benchmarks/stories/snapshot-retention-g10-m10/story.ink";
const SNAPSHOT_RETENTION_G10_M100_INK: &str =
"../../benchmarks/stories/snapshot-retention-g10-m100/story.ink";
const SNAPSHOT_RETENTION_G100_M10_INK: &str =
"../../benchmarks/stories/snapshot-retention-g100-m10/story.ink";
const SNAPSHOT_RETENTION_G100_M100_INK: &str =
"../../benchmarks/stories/snapshot-retention-g100-m100/story.ink";
#[expect(clippy::unwrap_used)]
fn parse_inputs(s: &str) -> Vec<usize> {
s.lines()
.filter(|l| !l.is_empty())
.map(|l| l.trim().parse().unwrap())
.collect()
}
fn scenarios() -> &'static [Scenario] {
static SCENARIOS: std::sync::OnceLock<Vec<Scenario>> = std::sync::OnceLock::new();
SCENARIOS
.get_or_init(|| {
vec![
Scenario {
name: "minimal",
ink: MINIMAL_INK,
inputs: vec![],
},
Scenario {
name: "hanoi-3",
ink: HANOI_3_INK,
inputs: parse_inputs(HANOI_3_INPUT),
},
Scenario {
name: "hanoi-10",
ink: HANOI_10_INK,
inputs: parse_inputs(HANOI_10_INPUT),
},
Scenario {
name: "crucible-8",
ink: CRUCIBLE_8_INK,
inputs: parse_inputs(CRUCIBLE_8_INPUT),
},
]
})
.as_slice()
}
#[expect(clippy::unwrap_used)]
fn compile_story(ink_rel: &str) -> StoryData {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(ink_rel);
brink_compiler::compile_path(&path).unwrap().data
}
#[expect(clippy::unwrap_used)]
fn compile_story_brink(ink_rel: &str) -> StoryData {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(ink_rel);
let options = AnalysisOptions {
dialect: Dialect::Brink,
types: Some(brink_compiler::TypePolicy::Gradual),
..AnalysisOptions::default()
};
brink_compiler::compile_path_with_options(&path, options)
.unwrap()
.data
}
#[expect(clippy::unwrap_used)]
fn compile_story_brink_typed(ink_rel: &str, types: TypePolicy) -> StoryData {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(ink_rel);
let options = AnalysisOptions {
dialect: Dialect::Brink,
types: Some(types),
..AnalysisOptions::default()
};
brink_compiler::compile_path_with_options(&path, options)
.unwrap()
.data
}
#[expect(clippy::unwrap_used)]
fn run_to_completion(
program: &Arc<Program>,
line_tables: Vec<Vec<brink_format::LineEntry>>,
inputs: &[usize],
) -> Stats {
let mut story = Story::<DotNetRng>::new(Arc::clone(program), line_tables);
let mut input_idx = 0;
loop {
let mut done = false;
for line in story.continue_maximally().unwrap() {
match line {
Step::Line(_) => {}
Step::Done | Step::End | Step::Suspended => {
done = true;
}
Step::Choices(choices) => {
if input_idx >= inputs.len() {
done = true;
break;
}
let idx = inputs[input_idx];
input_idx += 1;
assert!(idx < choices.len());
story.choose(idx).unwrap();
}
}
}
if done {
break;
}
}
story.stats().clone()
}
#[expect(clippy::unwrap_used)]
fn run_to_completion_keep_story(
program: &Arc<Program>,
line_tables: Vec<Vec<brink_format::LineEntry>>,
inputs: &[usize],
) -> Story<DotNetRng> {
let mut story = Story::<DotNetRng>::new(Arc::clone(program), line_tables);
let mut input_idx = 0;
loop {
let mut done = false;
for line in story.continue_maximally().unwrap() {
match line {
Step::Line(_) => {}
Step::Done | Step::End | Step::Suspended => {
done = true;
}
Step::Choices(choices) => {
if input_idx >= inputs.len() {
done = true;
break;
}
let idx = inputs[input_idx];
input_idx += 1;
assert!(idx < choices.len());
story.choose(idx).unwrap();
}
}
}
if done {
break;
}
}
story
}
mod compiler_bench {
use super::{Scenario, compile_story, scenarios};
#[divan::bench(args = scenarios())]
fn compile(bencher: divan::Bencher, scenario: &Scenario) {
bencher.bench_local(|| compile_story(scenario.ink));
}
}
mod linker_bench {
use super::{Scenario, compile_story, scenarios};
#[divan::bench(args = scenarios())]
#[expect(clippy::unwrap_used)]
fn link(bencher: divan::Bencher, scenario: &Scenario) {
let data = compile_story(scenario.ink);
bencher.bench_local(|| brink_runtime::link(&data).unwrap());
}
}
mod runtime_step {
use super::{Scenario, compile_story, run_to_completion, scenarios};
#[divan::bench(args = scenarios())]
fn run(bencher: divan::Bencher, scenario: &Scenario) {
let data = compile_story(scenario.ink);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &scenario.inputs));
}
}
mod loop_append_bench {
use super::{LOOP_APPEND_10K_INK, compile_story_brink, run_to_completion};
#[divan::bench]
fn push_10k(bencher: divan::Bencher) {
let data = compile_story_brink(LOOP_APPEND_10K_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
}
mod loop_append_field_bench {
use super::{LOOP_APPEND_FIELD_10K_INK, compile_story_brink, run_to_completion};
#[divan::bench]
fn push_field_10k(bencher: divan::Bencher) {
let data = compile_story_brink(LOOP_APPEND_FIELD_10K_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
}
mod cow_sharing_bench {
use super::{SHARE_THEN_MUTATE_5K_INK, compile_story_brink, run_to_completion};
#[divan::bench]
fn share_then_mutate_5k(bencher: divan::Bencher) {
let data = compile_story_brink(SHARE_THEN_MUTATE_5K_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
}
mod fn_value_bench {
use super::{
DIRECT_CALL_10K_INK, DYNAMIC_DISPATCH_10K_INK, FN_BIND_CHAIN_DEEP_INK,
FN_BIND_CHAIN_SHALLOW_INK, FN_CREATION_DENSITY_10K_INK, compile_story_brink,
run_to_completion,
};
#[divan::bench]
fn creation_density_10k(bencher: divan::Bencher) {
let data = compile_story_brink(FN_CREATION_DENSITY_10K_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn bind_chain_shallow(bencher: divan::Bencher) {
let data = compile_story_brink(FN_BIND_CHAIN_SHALLOW_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn bind_chain_deep(bencher: divan::Bencher) {
let data = compile_story_brink(FN_BIND_CHAIN_DEEP_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn dynamic_dispatch_10k(bencher: divan::Bencher) {
let data = compile_story_brink(DYNAMIC_DISPATCH_10K_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn direct_call_10k(bencher: divan::Bencher) {
let data = compile_story_brink(DIRECT_CALL_10K_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
}
mod struct_field_access_bench {
use super::{
STRUCT_FIELD_ACCESS_10K_INK, TypePolicy, compile_story_brink_typed, run_to_completion,
};
#[divan::bench]
fn strict_static_offset(bencher: divan::Bencher) {
let data = compile_story_brink_typed(STRUCT_FIELD_ACCESS_10K_INK, TypePolicy::Strict);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn gradual_dynamic_fallback(bencher: divan::Bencher) {
let data = compile_story_brink_typed(STRUCT_FIELD_ACCESS_10K_INK, TypePolicy::Gradual);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
}
mod save_state_bench {
use super::{
SAVE_STATE_LARGE_INK, SAVE_STATE_MEDIUM_INK, SAVE_STATE_SMALL_INK, compile_story_brink,
run_to_completion_keep_story,
};
#[expect(clippy::unwrap_used)]
fn save_state_for(ink: &str) -> brink_format::SaveState {
let data = compile_story_brink(ink);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
let story = run_to_completion_keep_story(&program, line_tables, &[]);
story.save_state()
}
#[divan::bench]
fn serialize_small(bencher: divan::Bencher) {
let save = save_state_for(SAVE_STATE_SMALL_INK);
#[expect(clippy::unwrap_used)]
bencher.bench_local(|| serde_json::to_vec(&save).unwrap());
}
#[divan::bench]
fn serialize_medium(bencher: divan::Bencher) {
let save = save_state_for(SAVE_STATE_MEDIUM_INK);
#[expect(clippy::unwrap_used)]
bencher.bench_local(|| serde_json::to_vec(&save).unwrap());
}
#[divan::bench]
fn serialize_large(bencher: divan::Bencher) {
let save = save_state_for(SAVE_STATE_LARGE_INK);
#[expect(clippy::unwrap_used)]
bencher.bench_local(|| serde_json::to_vec(&save).unwrap());
}
}
mod snapshot_retention_bench {
use super::{
SNAPSHOT_RETENTION_G10_M10_INK, SNAPSHOT_RETENTION_G10_M100_INK,
SNAPSHOT_RETENTION_G100_M10_INK, SNAPSHOT_RETENTION_G100_M100_INK, compile_story_brink,
run_to_completion,
};
#[divan::bench]
fn g10_m10(bencher: divan::Bencher) {
let data = compile_story_brink(SNAPSHOT_RETENTION_G10_M10_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn g10_m100(bencher: divan::Bencher) {
let data = compile_story_brink(SNAPSHOT_RETENTION_G10_M100_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn g100_m10(bencher: divan::Bencher) {
let data = compile_story_brink(SNAPSHOT_RETENTION_G100_M10_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
#[divan::bench]
fn g100_m100(bencher: divan::Bencher) {
let data = compile_story_brink(SNAPSHOT_RETENTION_G100_M100_INK);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
bencher.bench_local(|| run_to_completion(&program, line_tables.clone(), &[]));
}
}
mod ptr_eq_bench {
use std::sync::Arc;
use brink_format::Value;
const N: i32 = 20_000;
fn big_array() -> Value {
Value::Array(Arc::new((0..N).map(Value::Int).collect()))
}
#[divan::bench]
fn same_arc(bencher: divan::Bencher) {
let a = big_array();
let b = a.clone();
bencher.bench_local(|| a == b);
}
#[divan::bench]
fn distinct_but_equal(bencher: divan::Bencher) {
let a = big_array();
let b = big_array();
bencher.bench_local(|| a == b);
}
}
mod end_to_end {
use super::{Scenario, compile_story, run_to_completion, scenarios};
#[divan::bench(args = scenarios())]
fn full_pipeline(bencher: divan::Bencher, scenario: &Scenario) {
bencher.bench_local(|| {
let data = compile_story(scenario.ink);
#[expect(clippy::unwrap_used)]
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &scenario.inputs);
});
}
#[divan::bench(args = scenarios())]
#[expect(clippy::unwrap_used)]
fn precompiled(bencher: divan::Bencher, scenario: &Scenario) {
let data = compile_story(scenario.ink);
bencher.bench_local(|| {
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &scenario.inputs);
});
}
}
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_hanoi_10_stats() {
let data = compile_story(HANOI_10_INK);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
let inputs = parse_inputs(HANOI_10_INPUT);
let stats = run_to_completion(&program, line_tables, &inputs);
eprintln!("\n── hanoi-10 VM stats ──────────────────────────");
eprintln!(" opcodes: {:>10}", stats.opcodes);
eprintln!(" steps: {:>10}", stats.steps);
eprintln!(" threads_created: {:>10}", stats.threads_created);
eprintln!(" threads_completed: {:>10}", stats.threads_completed);
eprintln!(" frames_pushed: {:>10}", stats.frames_pushed);
eprintln!(" frames_popped: {:>10}", stats.frames_popped);
eprintln!(" choices_presented: {:>10}", stats.choices_presented);
eprintln!(" choices_selected: {:>10}", stats.choices_selected);
eprintln!(" snapshot_cache_hits: {:>10}", stats.snapshot_cache_hits);
eprintln!(
" snapshot_cache_misses:{:>10}",
stats.snapshot_cache_misses
);
eprintln!(" materializations: {:>10}", stats.materializations);
eprintln!("───────────────────────────────────────────────\n");
}
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_crucible_8_stats() {
let data = compile_story(CRUCIBLE_8_INK);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
let inputs = parse_inputs(CRUCIBLE_8_INPUT);
let stats = run_to_completion(&program, line_tables, &inputs);
eprintln!("\n── crucible-8 VM stats ────────────────────────");
eprintln!(" opcodes: {:>10}", stats.opcodes);
eprintln!(" steps: {:>10}", stats.steps);
eprintln!(" threads_created: {:>10}", stats.threads_created);
eprintln!(" threads_completed: {:>10}", stats.threads_completed);
eprintln!(" frames_pushed: {:>10}", stats.frames_pushed);
eprintln!(" frames_popped: {:>10}", stats.frames_popped);
eprintln!(" choices_presented: {:>10}", stats.choices_presented);
eprintln!(" choices_selected: {:>10}", stats.choices_selected);
eprintln!(" snapshot_cache_hits: {:>10}", stats.snapshot_cache_hits);
eprintln!(
" snapshot_cache_misses:{:>10}",
stats.snapshot_cache_misses
);
eprintln!(" materializations: {:>10}", stats.materializations);
eprintln!("───────────────────────────────────────────────\n");
}
#[cfg(feature = "bench-counters")]
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_bench_counters() {
use brink_runtime::bench_counters;
bench_counters::reset();
let data = compile_story_brink(LOOP_APPEND_10K_INK);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &[]);
let loop_append = bench_counters::snapshot();
bench_counters::reset();
let data = compile_story_brink(SHARE_THEN_MUTATE_5K_INK);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &[]);
let share_then_mutate = bench_counters::snapshot();
bench_counters::reset();
let data = compile_story_brink(LOOP_APPEND_FIELD_10K_INK);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &[]);
let loop_append_field = bench_counters::snapshot();
bench_counters::reset();
let data = compile_story_brink_typed(STRUCT_FIELD_ACCESS_10K_INK, TypePolicy::Strict);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &[]);
let struct_strict = bench_counters::snapshot();
bench_counters::reset();
let data = compile_story_brink_typed(STRUCT_FIELD_ACCESS_10K_INK, TypePolicy::Gradual);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &[]);
let struct_gradual = bench_counters::snapshot();
eprintln!("\n── bench-counters (Arc-clone / COW-copy events) ──");
eprintln!(
" loop-append-10k: cow_copies={:>6} arc_clones={:>6}",
loop_append.cow_copies, loop_append.arc_clones
);
eprintln!(
" share-then-mutate-5k: cow_copies={:>6} arc_clones={:>6}",
share_then_mutate.cow_copies, share_then_mutate.arc_clones
);
eprintln!(
" loop-append-field-10k: cow_copies={:>6} arc_clones={:>6}",
loop_append_field.cow_copies, loop_append_field.arc_clones
);
eprintln!(
" struct-field-access-10k (strict): cow_copies={:>6} arc_clones={:>6}",
struct_strict.cow_copies, struct_strict.arc_clones
);
eprintln!(
" struct-field-access-10k (gradual): cow_copies={:>6} arc_clones={:>6}",
struct_gradual.cow_copies, struct_gradual.arc_clones
);
eprintln!(
" (fn-value benches: not instrumented — bench-counters covers \
Array/Map/Record COW only, not Closure allocation; see \
docs/runtime-bench.md's honest-mechanism-isolation note)"
);
eprintln!("───────────────────────────────────────────────────\n");
}
#[cfg(feature = "bench-counters")]
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_snapshot_retention_counters() {
use brink_runtime::bench_counters;
let combos: [(&str, &str); 4] = [
("g10-m10", SNAPSHOT_RETENTION_G10_M10_INK),
("g10-m100", SNAPSHOT_RETENTION_G10_M100_INK),
("g100-m10", SNAPSHOT_RETENTION_G100_M10_INK),
("g100-m100", SNAPSHOT_RETENTION_G100_M100_INK),
];
eprintln!("\n── snapshot-retention bench-counters (G, M matrix) ──");
for (name, ink) in combos {
bench_counters::reset();
let data = compile_story_brink(ink);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
run_to_completion(&program, line_tables, &[]);
let snap = bench_counters::snapshot();
eprintln!(
" {name:<10} cow_copies={:>6} arc_clones={:>6}",
snap.cow_copies, snap.arc_clones
);
}
eprintln!(
" (expect cow_copies == G + 1 for each point, independent of M \
— the §8 bounded-retention claim: one COW copy per retained \
generation, not per mutation. The '+1' is a one-time cost paid \
by `history`'s own first push, diverging from the shared \
empty-array-literal pool `#[]` starts from — the same \
mechanism loop-append-10k's single cow_copies=1 already proves \
— not part of the G-generations retention loop under test; see \
docs/runtime-bench.md.)"
);
eprintln!("──────────────────────────────────────────────────────\n");
}
#[cfg(feature = "bench-counters")]
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_save_load_counter_deltas() {
use brink_runtime::bench_counters;
let data = compile_story_brink(SAVE_STATE_MEDIUM_INK);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
let mut story = run_to_completion_keep_story(&program, line_tables, &[]);
bench_counters::reset();
let saved = story.save_state();
let after_save = bench_counters::snapshot();
bench_counters::reset();
let _report = story.load_state(&saved);
let after_load = bench_counters::snapshot();
eprintln!("\n── save/load bench-counters (save-state-medium) ──");
eprintln!(
" save_state(): cow_copies={:>6} arc_clones={:>6}",
after_save.cow_copies, after_save.arc_clones
);
eprintln!(
" load_state(): cow_copies={:>6} arc_clones={:>6}",
after_load.cow_copies, after_load.arc_clones
);
eprintln!(
" (save/load only reads/replaces whole global slots — it never \
calls array_make_mut/map_make_mut/record_make_mut, so \
cow_copies == 0 here is expected, not a gap in coverage)"
);
eprintln!("───────────────────────────────────────────────────\n");
}
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_save_state_wire_sizes() {
let shapes: [(&str, &str); 3] = [
("small", SAVE_STATE_SMALL_INK),
("medium", SAVE_STATE_MEDIUM_INK),
("large", SAVE_STATE_LARGE_INK),
];
eprintln!("\n── SaveState wire sizes (serde_json, wasm-boundary encoding) ──");
for (name, ink) in shapes {
let data = compile_story_brink(ink);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
let story = run_to_completion_keep_story(&program, line_tables, &[]);
let save = story.save_state();
let bytes = serde_json::to_vec(&save).unwrap();
eprintln!(" {name:<8} {:>10} bytes", bytes.len());
}
eprintln!("─────────────────────────────────────────────────────────────\n");
}
fn current_rss_kb() -> Option<u64> {
let pid = std::process::id();
let output = std::process::Command::new("ps")
.args(["-o", "rss=", "-p", &pid.to_string()])
.output()
.ok()?;
if !output.status.success() {
return None;
}
String::from_utf8(output.stdout).ok()?.trim().parse().ok()
}
#[expect(clippy::unwrap_used, clippy::print_stderr)]
fn print_snapshot_retention_rss() {
let combos: [(&str, &str); 4] = [
("g10-m10", SNAPSHOT_RETENTION_G10_M10_INK),
("g10-m100", SNAPSHOT_RETENTION_G10_M100_INK),
("g100-m10", SNAPSHOT_RETENTION_G100_M10_INK),
("g100-m100", SNAPSHOT_RETENTION_G100_M100_INK),
];
eprintln!("\n── snapshot-retention RSS deltas (coarse — see caveat above main()) ──");
match current_rss_kb() {
Some(_) => {
for (name, ink) in combos {
let before = current_rss_kb();
let data = compile_story_brink(ink);
let (program, line_tables) = brink_runtime::link(&data).unwrap();
let program = std::sync::Arc::new(program);
let story = run_to_completion_keep_story(&program, line_tables, &[]);
let after = current_rss_kb();
drop(story);
let delta = after
.zip(before)
.map(|(a, b)| a.cast_signed() - b.cast_signed());
eprintln!(
" {name:<10} rss_before={before:>8?}KB rss_after={after:>8?}KB delta={delta:>8?}KB"
);
}
}
None => {
eprintln!(" (ps -o rss= unavailable on this platform — skipped)");
}
}
eprintln!("────────────────────────────────────────────────────────────────────\n");
}
fn main() {
let _ = scenarios();
print_hanoi_10_stats();
print_crucible_8_stats();
print_save_state_wire_sizes();
print_snapshot_retention_rss();
#[cfg(feature = "bench-counters")]
{
print_bench_counters();
print_snapshot_retention_counters();
print_save_load_counter_deltas();
}
divan::main();
}