mirror of
https://github.com/imjasonh/nescript
synced 2026-07-07 00:22:50 +00:00
tooling: add --no-opt CLI flag and criterion compile benchmarks
Adds two items from the "Code quality / tooling" section of docs/future-work.md. Both make it easier to chase regressions without touching codegen. - `nescript build --no-opt` skips the IR optimizer pass so optimizer-introduced miscompiles can be bisected against the unoptimized output. Threaded through CompileOptions and gated at the single optimizer call site in src/main.rs. Covered by a new integration test that compiles the same program twice (opt on / opt off) and asserts both outputs are valid iNES ROMs with matching headers and reset vectors. - A criterion-based `benches/compile.rs` harness that times the full parse -> analyze -> lower -> optimize -> codegen -> link pipeline on every examples/*.ne file. Sources are pre-read into memory so file I/O stays off the hot loop, and each example gets its own Criterion group for easy regression spotting. Committed ROM bytes under examples/*.nes are unchanged; the emulator goldens under tests/emulator/goldens/ are untouched.
This commit is contained in:
parent
d2cfce595b
commit
65a63f9a68
5 changed files with 740 additions and 2 deletions
450
Cargo.lock
generated
450
Cargo.lock
generated
|
|
@ -8,6 +8,21 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
|
|
@ -80,6 +95,12 @@ version = "2.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
|
|
@ -92,12 +113,45 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
|
|
@ -153,12 +207,85 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
|
|
@ -178,12 +305,29 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
|
|
@ -197,12 +341,60 @@ dependencies = [
|
|||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
|
|
@ -229,6 +421,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"ariadne",
|
||||
"clap",
|
||||
"criterion",
|
||||
"image",
|
||||
"pretty_assertions",
|
||||
]
|
||||
|
|
@ -242,12 +435,52 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
|
|
@ -295,6 +528,113 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
|
|
@ -318,6 +658,16 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
@ -336,6 +686,80 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
|
|
@ -356,3 +780,29 @@ name = "yansi"
|
|||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ image = { version = "0.25", default-features = false, features = ["png"] }
|
|||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
criterion = "0.5"
|
||||
|
||||
[[bench]]
|
||||
name = "compile"
|
||||
harness = false
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
|||
162
benches/compile.rs
Normal file
162
benches/compile.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
//! End-to-end compilation benchmarks.
|
||||
//!
|
||||
//! Each `examples/*.ne` file becomes its own Criterion group that
|
||||
//! times the full `parse → analyze → lower → optimize → codegen →
|
||||
//! peephole → link` pipeline the `nescript build` CLI runs. The goal
|
||||
//! is to catch compile-time regressions — today every example
|
||||
//! compiles in well under 100 ms, so a change that doubles that
|
||||
//! shows up as a large red bar in `cargo bench`'s output.
|
||||
//!
|
||||
//! The harness pre-reads every source file into memory before any
|
||||
//! measurement starts. Criterion's sample iterations then run only
|
||||
//! the in-memory compile path, so disk I/O never shows up on the
|
||||
//! hot loop.
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
|
||||
use nescript::analyzer;
|
||||
use nescript::assets;
|
||||
use nescript::codegen::{peephole, IrCodeGen};
|
||||
use nescript::ir;
|
||||
use nescript::linker::{Linker, PrgBank};
|
||||
use nescript::optimizer;
|
||||
use nescript::parser;
|
||||
use nescript::parser::ast::BankType;
|
||||
|
||||
/// Pre-loaded `.ne` source plus the directory it was read from. The
|
||||
/// directory matters because sprite `@binary` / `@chr` paths resolve
|
||||
/// relative to the source file — the current examples all use inline
|
||||
/// CHR, but resolving relative to the right directory keeps the bench
|
||||
/// honest if an example later grows an external asset.
|
||||
struct Example {
|
||||
name: String,
|
||||
source: String,
|
||||
source_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Scan `examples/*.ne` at the repo root and load every source file
|
||||
/// into memory. Sorted by file name so the benchmark output is
|
||||
/// reproducible across runs.
|
||||
fn load_examples() -> Vec<Example> {
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let examples_dir = manifest_dir.join("examples");
|
||||
|
||||
let mut entries: Vec<PathBuf> = fs::read_dir(&examples_dir)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", examples_dir.display()))
|
||||
.filter_map(Result::ok)
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().is_some_and(|ext| ext == "ne"))
|
||||
.collect();
|
||||
entries.sort();
|
||||
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let source = fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let source_dir = path
|
||||
.parent()
|
||||
.map_or_else(|| PathBuf::from("."), Path::to_path_buf);
|
||||
Example {
|
||||
name,
|
||||
source,
|
||||
source_dir,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Run the full CLI compile pipeline on an in-memory source string.
|
||||
/// Mirrors `compile` in `src/main.rs`: parse → analyze → IR lower →
|
||||
/// optimize → IR codegen → peephole → link. Panics on any error so
|
||||
/// a regression that breaks the pipeline surfaces immediately instead
|
||||
/// of silently skewing the measurements.
|
||||
fn compile_pipeline(source: &str, source_dir: &Path) -> Vec<u8> {
|
||||
let preprocessed = parser::preprocess_source(source, None)
|
||||
.unwrap_or_else(|e| panic!("preprocess failed: {e}"));
|
||||
|
||||
let (program, parse_diags) = parser::parse(&preprocessed);
|
||||
assert!(
|
||||
!parse_diags
|
||||
.iter()
|
||||
.any(nescript::errors::Diagnostic::is_error),
|
||||
"parse errors: {parse_diags:?}"
|
||||
);
|
||||
let program = program.expect("parse produced no program");
|
||||
|
||||
let analysis = analyzer::analyze(&program);
|
||||
assert!(
|
||||
!analysis
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(nescript::errors::Diagnostic::is_error),
|
||||
"analysis errors: {:?}",
|
||||
analysis.diagnostics
|
||||
);
|
||||
|
||||
let mut ir_program = ir::lower(&program, &analysis);
|
||||
optimizer::optimize(&mut ir_program);
|
||||
|
||||
let sprites = assets::resolve_sprites(&program, source_dir).expect("sprite resolution failed");
|
||||
let sfx = assets::resolve_sfx(&program).expect("sfx resolution failed");
|
||||
let music = assets::resolve_music(&program).expect("music resolution failed");
|
||||
let palettes = assets::resolve_palettes(&program);
|
||||
let backgrounds = assets::resolve_backgrounds(&program);
|
||||
|
||||
let mut instructions = IrCodeGen::new(&analysis.var_allocations, &ir_program)
|
||||
.with_sprites(&sprites)
|
||||
.with_audio(&sfx, &music)
|
||||
.generate(&ir_program);
|
||||
peephole::optimize(&mut instructions);
|
||||
|
||||
let linker = Linker::with_mapper(program.game.mirroring, program.game.mapper);
|
||||
let switchable_banks: Vec<PrgBank> = program
|
||||
.banks
|
||||
.iter()
|
||||
.filter(|b| b.bank_type == BankType::Prg)
|
||||
.map(|b| PrgBank::empty(&b.name))
|
||||
.collect();
|
||||
linker.link_banked_with_ppu(
|
||||
&instructions,
|
||||
&sprites,
|
||||
&sfx,
|
||||
&music,
|
||||
&palettes,
|
||||
&backgrounds,
|
||||
&switchable_banks,
|
||||
)
|
||||
}
|
||||
|
||||
/// Criterion entry point. One benchmark group per example so the
|
||||
/// HTML report groups them sensibly and so individual regressions
|
||||
/// are easy to spot.
|
||||
fn bench_compile(c: &mut Criterion) {
|
||||
let examples = load_examples();
|
||||
assert!(
|
||||
!examples.is_empty(),
|
||||
"no examples found under examples/*.ne — benchmark would measure nothing"
|
||||
);
|
||||
|
||||
for example in &examples {
|
||||
let mut group = c.benchmark_group(format!("compile/{}", example.name));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(&example.name),
|
||||
example,
|
||||
|b, ex| {
|
||||
b.iter(|| compile_pipeline(&ex.source, &ex.source_dir));
|
||||
},
|
||||
);
|
||||
group.finish();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_compile);
|
||||
criterion_main!(benches);
|
||||
19
src/main.rs
19
src/main.rs
|
|
@ -42,6 +42,13 @@ enum Cli {
|
|||
/// Dump a call graph showing which functions call which.
|
||||
#[arg(long)]
|
||||
call_graph: bool,
|
||||
|
||||
/// Skip the IR optimizer pass. Useful for bisecting
|
||||
/// optimizer-introduced miscompiles: if a program misbehaves
|
||||
/// with the optimizer on but works with `--no-opt`, the bug
|
||||
/// lives in `src/optimizer/`.
|
||||
#[arg(long)]
|
||||
no_opt: bool,
|
||||
},
|
||||
/// Type-check a source file without building
|
||||
Check {
|
||||
|
|
@ -62,6 +69,7 @@ fn main() {
|
|||
dump_ir,
|
||||
memory_map,
|
||||
call_graph,
|
||||
no_opt,
|
||||
} => {
|
||||
let output = output.unwrap_or_else(|| input.with_extension("nes"));
|
||||
match compile(
|
||||
|
|
@ -72,6 +80,7 @@ fn main() {
|
|||
dump_ir,
|
||||
memory_map,
|
||||
call_graph,
|
||||
no_opt,
|
||||
},
|
||||
) {
|
||||
Ok(rom) => {
|
||||
|
|
@ -220,6 +229,7 @@ struct CompileOptions {
|
|||
dump_ir: bool,
|
||||
memory_map: bool,
|
||||
call_graph: bool,
|
||||
no_opt: bool,
|
||||
}
|
||||
|
||||
fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
|
||||
|
|
@ -228,6 +238,7 @@ fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
|
|||
let dump_ir = opts.dump_ir;
|
||||
let memory_map = opts.memory_map;
|
||||
let call_graph = opts.call_graph;
|
||||
let no_opt = opts.no_opt;
|
||||
let raw_source = std::fs::read_to_string(input).map_err(|e| {
|
||||
eprintln!("error: failed to read {}: {e}", input.display());
|
||||
})?;
|
||||
|
|
@ -265,9 +276,13 @@ fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
|
|||
return Err(());
|
||||
}
|
||||
|
||||
// IR lowering and optimization
|
||||
// IR lowering and (optionally) optimization. `--no-opt` skips
|
||||
// the IR optimizer pass entirely so optimizer-introduced
|
||||
// miscompiles can be bisected against the unoptimized output.
|
||||
let mut ir_program = ir::lower(&program, &analysis);
|
||||
optimizer::optimize(&mut ir_program);
|
||||
if !no_opt {
|
||||
optimizer::optimize(&mut ir_program);
|
||||
}
|
||||
|
||||
if dump_ir {
|
||||
print!("{}", ir_program.pretty());
|
||||
|
|
|
|||
|
|
@ -1722,3 +1722,109 @@ fn e2e_banked_chr_rom_is_preserved() {
|
|||
// Default smiley is non-zero in its first 16 bytes.
|
||||
assert_ne!(&rom[chr_start..chr_start + 16], &[0u8; 16]);
|
||||
}
|
||||
|
||||
/// Same as `compile_banked` but lets the caller toggle whether the IR
|
||||
/// optimizer runs. Used to cover the `--no-opt` CLI flag: compiling
|
||||
/// with the optimizer disabled must still produce a valid iNES ROM.
|
||||
fn compile_banked_with_opts(source: &str, optimize: bool) -> Vec<u8> {
|
||||
let (program, diags) = nescript::parser::parse(source);
|
||||
assert!(
|
||||
diags.is_empty(),
|
||||
"unexpected parse errors: {diags:?}\nsource:\n{source}"
|
||||
);
|
||||
let program = program.expect("parse should succeed");
|
||||
|
||||
let analysis = analyzer::analyze(&program);
|
||||
assert!(
|
||||
analysis.diagnostics.iter().all(|d| !d.is_error()),
|
||||
"unexpected analysis errors: {:?}",
|
||||
analysis.diagnostics
|
||||
);
|
||||
|
||||
let mut ir_program = ir::lower(&program, &analysis);
|
||||
if optimize {
|
||||
nescript::optimizer::optimize(&mut ir_program);
|
||||
}
|
||||
|
||||
let sprites = assets::resolve_sprites(&program, Path::new("."))
|
||||
.expect("sprite resolution should succeed");
|
||||
let sfx = assets::resolve_sfx(&program).expect("sfx resolution should succeed");
|
||||
let music = assets::resolve_music(&program).expect("music resolution should succeed");
|
||||
let palettes = assets::resolve_palettes(&program);
|
||||
let backgrounds = assets::resolve_backgrounds(&program);
|
||||
|
||||
let codegen = IrCodeGen::new(&analysis.var_allocations, &ir_program)
|
||||
.with_sprites(&sprites)
|
||||
.with_audio(&sfx, &music);
|
||||
let mut instructions = codegen.generate(&ir_program);
|
||||
nescript::codegen::peephole::optimize(&mut instructions);
|
||||
|
||||
let linker = Linker::with_mapper(program.game.mirroring, program.game.mapper);
|
||||
let switchable_banks: Vec<PrgBank> = program
|
||||
.banks
|
||||
.iter()
|
||||
.filter(|b| b.bank_type == BankType::Prg)
|
||||
.map(|b| PrgBank::empty(&b.name))
|
||||
.collect();
|
||||
linker.link_banked_with_ppu(
|
||||
&instructions,
|
||||
&sprites,
|
||||
&sfx,
|
||||
&music,
|
||||
&palettes,
|
||||
&backgrounds,
|
||||
&switchable_banks,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_opt_still_produces_valid_rom() {
|
||||
// Acceptance test for the `--no-opt` CLI flag. Skipping the IR
|
||||
// optimizer must still produce a byte-valid iNES ROM that links
|
||||
// against the runtime, uses the declared mapper, and carries a
|
||||
// plausible vector table. This guards the compile path the flag
|
||||
// opens up so optimizer bisection remains a usable workflow.
|
||||
let source = r#"
|
||||
game "NoOpt" { mapper: NROM }
|
||||
|
||||
var counter: u8 = 0
|
||||
var doubled: u8 = 0
|
||||
|
||||
fun double(x: u8) -> u8 {
|
||||
return x + x
|
||||
}
|
||||
|
||||
on frame {
|
||||
counter += 1
|
||||
doubled = double(counter)
|
||||
if button.a {
|
||||
counter = 0
|
||||
}
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#;
|
||||
|
||||
let rom_opt = compile_banked_with_opts(source, true);
|
||||
let rom_noopt = compile_banked_with_opts(source, false);
|
||||
|
||||
// Both outputs must be valid iNES ROMs with matching headers —
|
||||
// the optimizer only affects PRG codegen, not the CHR/header
|
||||
// layout the linker produces.
|
||||
let info_opt = rom::validate_ines(&rom_opt).expect("opt ROM should be valid iNES");
|
||||
let info_noopt = rom::validate_ines(&rom_noopt).expect("noopt ROM should be valid iNES");
|
||||
assert_eq!(info_opt.mapper, 0);
|
||||
assert_eq!(info_noopt.mapper, 0);
|
||||
assert_eq!(info_opt.prg_banks, info_noopt.prg_banks);
|
||||
assert_eq!(info_opt.chr_banks, info_noopt.chr_banks);
|
||||
assert_eq!(rom_opt.len(), rom_noopt.len());
|
||||
|
||||
// The reset vector should still point into the fixed PRG bank
|
||||
// in both builds — the optimizer has no say in where the reset
|
||||
// handler lands.
|
||||
let prg_end = 16 + 16384;
|
||||
let reset_opt = u16::from_le_bytes([rom_opt[prg_end - 4], rom_opt[prg_end - 3]]);
|
||||
let reset_noopt = u16::from_le_bytes([rom_noopt[prg_end - 4], rom_noopt[prg_end - 3]]);
|
||||
assert_eq!(reset_opt, 0xC000);
|
||||
assert_eq!(reset_noopt, 0xC000);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue