Skip to content

Commit 307cd41

Browse files
committed
Deduplicate runtime verification artifacts
1 parent e4c62b0 commit 307cd41

3 files changed

Lines changed: 105 additions & 59 deletions

File tree

‎TODO.md‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -671,14 +671,6 @@ concepts rather than maintaining a parallel inventory.
671671

672672
### Engine and emulator providers
673673

674-
- [ ] Propose a typed SNES provider from the Super Metroid evidence before
675-
implementing it. Compare ASR's higan, bsnes, Snes9x, BizHawk, RetroArch, and
676-
lsnes-bsnes discovery, moving mappings, address normalization, byte order,
677-
region/timing differences, and core-unload behavior. The public shape should
678-
match the existing `state GCN` / `state Genesis` provider model unless a
679-
concrete SNES constraint justifies a change; bring that proposal to the user
680-
first. A manual RetroArch memory root is an interim port, not the canonical
681-
endpoint.
682674
- [ ] Decide the source-defined provider refresh lifecycle before claiming
683675
parity for emulator cores that unload without their host process exiting.
684676
`state PS2` validates RetroArch's core mapping on every read and fails safely
@@ -1011,11 +1003,13 @@ remaining work is product hardening and distribution.
10111003
emitters with narrow inputs; separate call candidate collection, generic
10121004
solving/selection, and diagnostic construction. Move one family at a time
10131005
and do not replace one all-purpose context with nested bags of everything.
1014-
- [ ] Compile each unique runtime `(source, output, profile)` artifact once in
1006+
- [x] Compile each unique runtime `(source, output, profile)` artifact once in
10151007
`cargo xtask check`, validate it once, and run all argument/scenario variants
1016-
against that artifact. Model compilation separately from runtime scenarios
1017-
and reject conflicting output definitions or duplicate scenarios without
1018-
reducing the maintained host coverage.
1008+
against that artifact. Compilation is modeled separately from runtime
1009+
scenarios; fixture planning rejects conflicting output definitions and exact
1010+
duplicate scenarios without reducing maintained host coverage. The current
1011+
matrix compiles and validates 65 artifacts for 93 runtime scenarios instead
1012+
of rebuilding 28 duplicate artifacts.
10191013
- [ ] Add a small shared `instantiateRuntimeFixture` host for common Wasm
10201014
loading, text/memory helpers, default imports, `_start`, and `update`, while
10211015
keeping unusual host behavior and assertions local. Migrate harnesses only
@@ -1042,6 +1036,13 @@ remaining work is product hardening and distribution.
10421036

10431037
## P2 / deliberately deferred
10441038

1039+
- [ ] After ASR has a tested SNES provider, align SplitScript with its higan,
1040+
bsnes, Snes9x, BizHawk, RetroArch, and lsnes-bsnes discovery and memory
1041+
semantics rather than independently inventing them from the Super Metroid
1042+
port. The eventual source design should normally match the existing
1043+
`state GCN` / `state Genesis` provider model, but must still be brought to the
1044+
user before implementation. A manual RetroArch memory root remains an interim
1045+
port, not the canonical endpoint.
10451046
- [ ] Revisit native-state suggestions for typed emulator providers only if
10461047
future porting evidence shows this remains a recurring source of incorrect
10471048
scripts after the current provider documentation and search work. This is

‎docs/ROADMAP_ARCHIVE.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# SplitScript roadmap
22

3+
## 2026-08-30: deduplicated runtime verification artifacts
4+
5+
- Separated `cargo xtask check` artifact definitions from runtime scenarios.
6+
The verifier now compiles and validates each unique output once, then runs
7+
every scenario against that artifact; the current 93-scenario matrix needs
8+
65 builds instead of 93.
9+
- Added deterministic fixture-plan validation. Reusing an output for a
10+
different source or profile and repeating an exact runtime scenario now fail
11+
before compilation begins.
12+
- Moved the proposed SNES provider to the ASR-dependent deferred roadmap after
13+
confirming the current ASR tree has no SNES implementation to align with.
14+
315
## 2026-08-30: record literals gain identity-safe field shorthand
416

517
- Added record field initializer shorthand: `Point { x }` lowers through the

‎src/bin/xtask.rs‎

Lines changed: 80 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
mod documentation_site;
55

66
use std::{
7+
collections::{BTreeMap, BTreeSet},
78
env, fs,
89
path::{Path, PathBuf},
910
process::{Command, ExitCode},
1011
};
1112

13+
#[derive(Clone, Copy)]
1214
struct RuntimeFixture {
1315
source: &'static str,
1416
output: &'static str,
@@ -17,13 +19,30 @@ struct RuntimeFixture {
1719
extra_arguments: &'static [&'static str],
1820
}
1921

22+
#[derive(Clone, Copy)]
2023
struct CompileFixture {
2124
source: &'static str,
2225
output: &'static str,
2326
profile: &'static str,
2427
}
2528

26-
const COMPILE_FIXTURES: &[CompileFixture] = &[];
29+
const COMPILE_FIXTURES: &[CompileFixture] = &[
30+
CompileFixture {
31+
source: "examples/lunistice.split",
32+
output: "lunistice.release.wasm",
33+
profile: "release",
34+
},
35+
CompileFixture {
36+
source: "tests/debug_profile.split",
37+
output: "debug_profile.debug.wasm",
38+
profile: "debug",
39+
},
40+
CompileFixture {
41+
source: "tests/debug_profile.split",
42+
output: "debug_profile.release.wasm",
43+
profile: "release",
44+
},
45+
];
2746

2847
const RUNTIME_FIXTURES: &[RuntimeFixture] = &[
2948
RuntimeFixture {
@@ -847,16 +866,13 @@ fn check() -> Result<(), String> {
847866
} else {
848867
"target/debug/splitc"
849868
});
850-
for fixture in RUNTIME_FIXTURES {
851-
compile_once(
852-
&root,
853-
&compiler,
854-
fixture.source,
855-
&outputs.join(fixture.output),
856-
fixture.profile,
857-
)?;
858-
}
859-
for fixture in COMPILE_FIXTURES {
869+
let artifacts = verification_artifacts(RUNTIME_FIXTURES, COMPILE_FIXTURES)?;
870+
println!(
871+
"compiling {} unique verification artifacts for {} runtime scenarios",
872+
artifacts.len(),
873+
RUNTIME_FIXTURES.len()
874+
);
875+
for fixture in &artifacts {
860876
compile_once(
861877
&root,
862878
&compiler,
@@ -865,48 +881,14 @@ fn check() -> Result<(), String> {
865881
fixture.profile,
866882
)?;
867883
}
868-
let lunistice_release = outputs.join("lunistice.release.wasm");
869-
compile_once(
870-
&root,
871-
&compiler,
872-
"examples/lunistice.split",
873-
&lunistice_release,
874-
"release",
875-
)?;
876884
let debug = outputs.join("debug_profile.debug.wasm");
877885
let release = outputs.join("debug_profile.release.wasm");
878-
compile_once(
879-
&root,
880-
&compiler,
881-
"tests/debug_profile.split",
882-
&debug,
883-
"debug",
884-
)?;
885-
compile_once(
886-
&root,
887-
&compiler,
888-
"tests/debug_profile.split",
889-
&release,
890-
"release",
891-
)?;
892886

893-
let mut validated = RUNTIME_FIXTURES
894-
.iter()
895-
.map(|fixture| outputs.join(fixture.output))
896-
.collect::<Vec<_>>();
897-
validated.extend(
898-
COMPILE_FIXTURES
899-
.iter()
900-
.map(|fixture| outputs.join(fixture.output)),
901-
);
902-
validated.extend([lunistice_release, debug.clone(), release.clone()]);
903-
validated.sort();
904-
validated.dedup();
905-
for module in &validated {
887+
for module in artifacts.iter().map(|fixture| outputs.join(fixture.output)) {
906888
run(
907889
&root,
908890
"wasm-tools",
909-
&["validate", "--features", "all", path_text(module)?],
891+
&["validate", "--features", "all", path_text(&module)?],
910892
)?;
911893
}
912894

@@ -929,6 +911,57 @@ fn check() -> Result<(), String> {
929911
Ok(())
930912
}
931913

914+
fn verification_artifacts(
915+
runtime_fixtures: &[RuntimeFixture],
916+
compile_fixtures: &[CompileFixture],
917+
) -> Result<Vec<CompileFixture>, String> {
918+
let mut scenarios = BTreeSet::new();
919+
for fixture in runtime_fixtures {
920+
if !scenarios.insert((
921+
fixture.source,
922+
fixture.output,
923+
fixture.profile,
924+
fixture.harness,
925+
fixture.extra_arguments,
926+
)) {
927+
return Err(format!(
928+
"duplicate runtime scenario for `{}` with harness `{}` and arguments {:?}",
929+
fixture.output, fixture.harness, fixture.extra_arguments
930+
));
931+
}
932+
}
933+
934+
let mut artifacts = BTreeMap::new();
935+
for fixture in runtime_fixtures.iter().map(|fixture| CompileFixture {
936+
source: fixture.source,
937+
output: fixture.output,
938+
profile: fixture.profile,
939+
}) {
940+
insert_artifact(&mut artifacts, fixture)?;
941+
}
942+
for &fixture in compile_fixtures {
943+
insert_artifact(&mut artifacts, fixture)?;
944+
}
945+
Ok(artifacts.into_values().collect())
946+
}
947+
948+
fn insert_artifact(
949+
artifacts: &mut BTreeMap<&'static str, CompileFixture>,
950+
fixture: CompileFixture,
951+
) -> Result<(), String> {
952+
if let Some(previous) = artifacts.get(fixture.output) {
953+
if previous.source != fixture.source || previous.profile != fixture.profile {
954+
return Err(format!(
955+
"conflicting artifact `{}`: `{}` ({}) and `{}` ({})",
956+
fixture.output, previous.source, previous.profile, fixture.source, fixture.profile
957+
));
958+
}
959+
} else {
960+
artifacts.insert(fixture.output, fixture);
961+
}
962+
Ok(())
963+
}
964+
932965
fn compile_once(
933966
root: &Path,
934967
compiler: &Path,

0 commit comments

Comments
 (0)