Add bounded reachable main-EXE discovery

Adapt Douglas's opt-in reachable discovery onto current exact/static dispatch. Validate configured static-analysis bounds before truncation, preserve the whole-image default and canonical page reservations, and leave unresolved indirect or out-of-bound targets to runtime fallback.

Source: https://github.com/douglasjv/mm8

Source-commit: https://github.com/douglasjv/mm8/commit/8415f3a95458c41c7f48fbe36caaf0ee82730720

Co-authored-by: douglasjv <vanderveerdouglas@gmail.com>
This commit is contained in:
Matthew Stanley
2026-07-13 21:56:43 -07:00
parent dde268dc0f
commit bd705bfcec
12 changed files with 478 additions and 9 deletions
+16 -1
View File
@@ -71,7 +71,7 @@ respective files.
| `exe` | game | path to PS-X EXE file, relative to project root |
| `load_address` | both | hex string, virtual address of first byte (`"0xBFC00000"` BIOS, `"0x80010000"` typical game) |
| `entry_pc` | both | hex string, first PC to execute |
| `text_size` | both | hex string, size in bytes of code segment (used by audits to bound region walks) |
| `text_size` | both | hex string, size in bytes of the static region. For games this also bounds main-EXE analysis and establishes the overlay floor. A smaller-than-header bound must be verified non-code and 4 KiB aligned. |
| `stack_base` | game | hex string, initial `$sp` value for the game |
| `disc` | game (single-disc) | path to .cue, relative to project root |
| `discs` | game (multi-disc) | array of .cue paths; `disc` is sugar for `discs = [disc]` |
@@ -85,6 +85,7 @@ seeds = "seeds/ghidra_funcs.txt" # game (note: game se
bios_thunks = "seeds/tomba_bios_thunks.txt" # game-only
out_dir = "generated" # both
strict = true # both — currently always true
discovery = "whole-image" # game-only: "whole-image" or "reachable"
out_stem = "SCPH1001" # optional; overrides the auto-derived stem
```
@@ -93,6 +94,20 @@ Output filenames: `<out_dir>/<out_stem>_full.c` and
from the `rom`/`exe` file basename with the trailing `.BIN` or `.EXE`
stripped (`Path.stem` is NOT used because it mishandles `SCUS_942.36`).
Game `discovery` defaults to `"whole-image"`, preserving the existing sweep and
pointer-table heuristics. Opt-in `"reachable"` starts at the executable entry
and evidence-backed seed roots, then follows callable direct `jal` targets. It
does not sweep arbitrary bytes for prologues or return-shaped words. Unresolved
`jalr`/indirect targets and unseen callbacks fail closed to runtime
interpretation; add an evidence-backed seed (or a `dispatch_root` seed for a
proven nonstandard boundary) when they should be compiled.
When `game.text_size` is smaller than the PS-X EXE header size, recompilation
uses it as a static-analysis bound. The value must be nonzero, instruction- and
4 KiB-aligned, retain `entry_pc`, and not extend past PS1 RAM. The original EXE
is still loaded from the user's disc. A generated config's canonical final-page
reservation may be slightly larger than the header; it does not widen analysis.
## Runtime block
Consumed by the cmake macro `psxrecomp_v4_add_runtime_target` (eventually)
@@ -0,0 +1,28 @@
# Douglas MM8: reachable main-EXE discovery
Evaluated and adapted on 2026-07-13.
## Provenance
The opt-in reachable main-executable discovery and `game.text_size` analysis
bound were rebuilt from douglasjv's
[Mega Man 8 PSXrecomp project](https://github.com/douglasjv/mm8), specifically
commit
[`8415f3a95458c41c7f48fbe36caaf0ee82730720`](https://github.com/douglasjv/mm8/commit/8415f3a95458c41c7f48fbe36caaf0ee82730720)
(`Add reachable main executable discovery`). The implementation was adapted to
current master rather than copied verbatim: bounds are validated before
mutation, canonical page-rounded existing configs remain compatible, the EXE
entry is an explicit root, and main-image codegen retains the newer exact
static-range dispatch checks from `b39387e`.
Reachable discovery is fail-closed. It follows callable direct `jal` targets
from verified roots; an unresolved `jalr`, unseen callback, rejected seed, or
target outside the verified bound does not create a compiled entry. Such PCs
remain eligible for the runtime interpreter and overlay capture paths.
## Deliberately excluded
No MM8 addresses, identifiers, `bg2d` or other widescreen configuration,
game-specific seeds/configuration, executable patches, generated game code, or
framework pin updates were imported. All other MM8 repository and dependency
changes are outside this focused branch.
+13
View File
@@ -206,3 +206,16 @@ target_include_directories(l2_structural_test PRIVATE
lib/fmt/include
)
target_link_libraries(l2_structural_test PRIVATE fmt rabbitizer)
# Reachable main-EXE discovery test: parser-bound validation plus exact-entry
# analysis behavior for direct, indirect, seeded, and out-of-bound targets.
add_executable(reachable_discovery_test
tests/reachable_discovery_test.cpp
src/function_analysis.cpp
src/ps1_exe_parser.cpp
)
target_include_directories(reachable_discovery_test PRIVATE
include
lib/fmt/include
)
target_link_libraries(reachable_discovery_test PRIVATE fmt)
+4 -3
View File
@@ -48,9 +48,10 @@ public:
// Scan entire executable for function boundaries
FunctionAnalysisResult analyze();
// Analyze only explicit entry points and direct JAL targets reachable from
// them. This is for runtime-loaded overlays where scanning the whole blob
// treats data and basic-block targets as standalone functions.
// Analyze only explicit entry points and callable direct-JAL targets
// reachable from them. Used by runtime-loaded overlays and opt-in main-EXE
// reachable discovery. Unresolved jalr/indirect targets do not mint
// functions; evidence-backed entries must be supplied explicitly.
FunctionAnalysisResult analyze_exact_entries(const std::vector<uint32_t>& entries);
// Add a forced entry point address that is treated as a function start
+9
View File
@@ -106,4 +106,13 @@ public:
static bool validate_header(const PS1ExeHeader& header, std::string& error_msg);
};
// Restrict a parsed main executable to a title-verified static-analysis size.
// The original disc image is not modified; only this in-memory analysis view
// is shortened. Returns false without mutation when the configured bound is
// invalid. A canonical page-rounded size above the header size is accepted as
// an existing runtime reservation and leaves the analysis view unchanged.
bool apply_static_analysis_bound(PS1Executable& exe,
uint32_t configured_size,
std::string& error_msg);
} // namespace PSXRecomp
+11
View File
@@ -702,6 +702,16 @@ GameConfig load_game_config(const fs::path& config_path_in) {
? toml::find<bool>(recomp, "strict")
: true;
const std::string discovery =
recomp.contains("discovery")
? toml::find<std::string>(recomp, "discovery")
: std::string{"whole-image"};
if (discovery != "whole-image" && discovery != "reachable") {
throw std::runtime_error(fmt::format(
"{}: [recompiler].discovery must be 'whole-image' or 'reachable'",
config_path.string()));
}
std::string out_stem;
if (recomp.contains("out_stem")) {
out_stem = toml::find<std::string>(recomp, "out_stem");
@@ -939,6 +949,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
/*bios_thunks_path*/ bios_thunks_path,
/*out_dir*/ out_dir,
/*strict*/ strict,
/*discovery*/ discovery,
/*out_stem*/ out_stem,
/*runtime*/ parse_runtime_block(cfg, root),
/*ws_sprite_tag_funcs*/ ws_sprite_tag_funcs,
+1
View File
@@ -386,6 +386,7 @@ struct GameConfig {
std::filesystem::path bios_thunks_path; // optional; empty if not set
std::filesystem::path out_dir;
bool strict;
std::string discovery; // "whole-image" (default) or "reachable"
std::string out_stem; // derived if not explicit
// [runtime] block (optional)
+1 -1
View File
@@ -549,7 +549,7 @@ FunctionAnalysisResult FunctionAnalyzer::analyze_exact_entries(const std::vector
result.call_discovered_count = 0;
result.state_continuation_count = 0;
fmt::print("\n=== Exact Overlay Function Analysis ===\n\n");
fmt::print("\n=== Exact-Entry Function Analysis ===\n\n");
auto in_exe = [&](uint32_t addr) {
return addr >= exe_.header.load_address && addr < exe_.end_address() && (addr & 3u) == 0;
+30 -4
View File
@@ -119,6 +119,7 @@ int main(int argc, char** argv) {
const char* extra_funcs_path = nullptr;
bool inspect_mode = false;
bool overlay_mode = false;
bool reachable_discovery = false;
std::set<uint32_t> ws_tag_funcs; // [widescreen] sprite_tag_funcs
std::set<uint32_t> ws_cull_bias, ws_cull_range, ws_cull_a1; // [widescreen.cull]
std::set<uint32_t> ws_cull_screen_x; // [widescreen.cull] screen_x_sites
@@ -138,10 +139,13 @@ int main(int argc, char** argv) {
ws_bg2d_cap_site = 0,
ws_bg2d_init_func = 0; // [widescreen.bg2d]
std::filesystem::path out_dir = "generated";
uint32_t configured_text_size = 0;
if (!config_path.empty()) {
const auto cfg = PSXRecompV4::load_game_config(config_path);
exe_path = cfg.exe_path;
configured_text_size = cfg.text_size;
reachable_discovery = cfg.discovery == "reachable";
extra_funcs_storage = cfg.seeds_path.string();
extra_funcs_path = extra_funcs_storage.c_str();
out_dir = cfg.out_dir;
@@ -253,6 +257,24 @@ int main(int argc, char** argv) {
return 1;
}
// [game].text_size is the title-owned static-analysis bound. It may trim a
// verified data/padding tail, but it may never widen the parsed payload or
// exclude the executable entry. Runtime disc loading remains unchanged.
if (!config_path.empty()) {
const uint32_t parsed_size = exe->header.file_size;
std::string bound_error;
if (!PSXRecomp::apply_static_analysis_bound(
*exe, configured_text_size, bound_error)) {
fmt::print(stderr, "Invalid static analysis bound: {}\n", bound_error);
return 1;
}
if (exe->header.file_size < parsed_size) {
fmt::print("Static analysis bound: 0x{:X} bytes from game.text_size "
"(PS-X EXE header: 0x{:X})\n",
exe->header.file_size, parsed_size);
}
}
fmt::print("✓ Successfully parsed PS1-EXE!\n\n");
// Print header information
@@ -427,8 +449,8 @@ int main(int argc, char** argv) {
PSXRecomp::FunctionAnalysisResult analysis_result;
if (overlay_mode) {
// ── Overlay exact mode: partition seeds into walk roots and interior
if (overlay_mode || reachable_discovery) {
// ── Exact-entry mode: partition seeds into walk roots and interior
// alias candidates. `interior`-marked seeds (classifier-proven
// dispatch targets without a callable boundary) are NEVER walk roots —
// a root inside a host function hard-caps (truncates) it, the
@@ -453,7 +475,11 @@ int main(int argc, char** argv) {
std::set<uint32_t> roots;
std::set<uint32_t> interior;
for (uint32_t a : exact_entries) {
if (trusted_root_seeds.count(a)) {
if (reachable_discovery && a == exe->header.initial_pc) {
// The PS-X EXE header is direct execution evidence for the
// main entry even when it uses a nonstandard prologue.
roots.insert(a);
} else if (trusted_root_seeds.count(a)) {
/* Classifier-proven dispatch root (install-slot class): the
* static code tail-dispatches into this PC, so it is a real
* execution root despite having no callable boundary. */
@@ -546,7 +572,7 @@ int main(int argc, char** argv) {
"(range-ownership completeness)\n", jal_alias_added);
materialize_alias_groups(analysis_result, alias_entries);
fmt::print("Overlay alias entries emitted: {}\n\n", alias_entries.size());
fmt::print("Exact-entry alias entries emitted: {}\n\n", alias_entries.size());
} else {
// ── Iterative discovery: seed classification + static data-table scan ──
//
+70
View File
@@ -5,6 +5,76 @@
namespace PSXRecomp {
bool apply_static_analysis_bound(PS1Executable& exe,
uint32_t configured_size,
std::string& error_msg) {
const uint32_t image_size = exe.header.file_size;
const uint64_t configured_end =
static_cast<uint64_t>(exe.header.load_address) + configured_size;
if (configured_size == 0) {
error_msg = "game.text_size must be nonzero";
return false;
}
if ((configured_size & 3u) != 0) {
error_msg = fmt::format(
"game.text_size 0x{:X} is not instruction-aligned", configured_size);
return false;
}
if (configured_end > 0x80200000ull) {
error_msg = fmt::format(
"game.text_size 0x{:X} extends past PS1 RAM", configured_size);
return false;
}
const uint32_t effective_size =
configured_size < image_size ? configured_size : image_size;
const uint64_t effective_end =
static_cast<uint64_t>(exe.header.load_address) + effective_size;
if (exe.header.initial_pc < exe.header.load_address ||
static_cast<uint64_t>(exe.header.initial_pc) >= effective_end) {
error_msg = fmt::format(
"game.text_size 0x{:X} excludes entry_pc 0x{:08X}",
configured_size, exe.header.initial_pc);
return false;
}
if (configured_size > image_size) {
// Existing config generation reserves the remainder of the final 4 KiB
// page for the runtime overlay floor. That value is not evidence that
// bytes beyond the EXE payload exist, so it must not widen analysis.
const uint64_t rounded =
(static_cast<uint64_t>(image_size) + 0xFFFull) & ~0xFFFull;
if (configured_size != rounded) {
error_msg = fmt::format(
"game.text_size 0x{:X} exceeds PS-X EXE size 0x{:X} "
"and is not its canonical page-rounded reservation",
configured_size, image_size);
return false;
}
return true;
}
if (configured_size == image_size) return true;
if ((configured_size & 0xFFFu) != 0) {
error_msg = fmt::format(
"truncated game.text_size 0x{:X} must be 4 KiB page-aligned",
configured_size);
return false;
}
if (exe.code_data.size() != image_size) {
error_msg = fmt::format(
"cannot apply analysis bound: EXE payload is {} bytes, header is {}",
exe.code_data.size(), image_size);
return false;
}
exe.header.file_size = configured_size;
exe.code_data.resize(configured_size);
return true;
}
// Validate PS1-EXE header
bool PS1ExeParser::validate_header(const PS1ExeHeader& header, std::string& error_msg) {
// Check magic identifier
@@ -0,0 +1,139 @@
#include "function_analysis.h"
#include "ps1_exe_parser.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <set>
#include <string>
#include <vector>
namespace {
constexpr uint32_t kLoad = 0x80010000u;
int failures = 0;
#define CHECK(cond, message) do { \
if (!(cond)) { \
std::fprintf(stderr, "FAIL: %s\n", (message)); \
failures++; \
} \
} while (0)
void put32(std::vector<uint8_t>& data, size_t offset, uint32_t value) {
data[offset + 0] = static_cast<uint8_t>(value);
data[offset + 1] = static_cast<uint8_t>(value >> 8);
data[offset + 2] = static_cast<uint8_t>(value >> 16);
data[offset + 3] = static_cast<uint8_t>(value >> 24);
}
uint32_t jal(uint32_t target) {
return 0x0C000000u | ((target >> 2) & 0x03FFFFFFu);
}
std::vector<uint8_t> make_exe_buffer(uint32_t image_size,
uint32_t entry = kLoad) {
std::vector<uint8_t> data(2048u + image_size, 0);
std::memcpy(data.data(), "PS-X EXE", 8);
put32(data, 0x10, entry);
put32(data, 0x18, kLoad);
put32(data, 0x1C, image_size);
return data;
}
PSXRecomp::PS1Executable parse(std::vector<uint8_t> data) {
std::string error;
auto exe = PSXRecomp::PS1ExeParser::parse_buffer(data, error);
CHECK(exe.has_value(), error.c_str());
return exe.value();
}
std::set<uint32_t> starts(const PSXRecomp::FunctionAnalysisResult& result) {
std::set<uint32_t> out;
for (const auto& function : result.functions) out.insert(function.start_addr);
return out;
}
} // namespace
int main() {
auto image = make_exe_buffer(0x2000);
const size_t text = 2048;
// Root: direct call to a callable target, unresolved jalr, direct call
// beyond the eventual bound, then return.
put32(image, text + 0x00, jal(kLoad + 0x100));
put32(image, text + 0x04, 0x00000000u);
put32(image, text + 0x08, 0x0320F809u); // jalr $ra,$t9 (unresolved)
put32(image, text + 0x0C, 0x00000000u);
put32(image, text + 0x10, jal(kLoad + 0x1100));
put32(image, text + 0x14, 0x00000000u);
put32(image, text + 0x18, 0x03E00008u);
put32(image, text + 0x1C, 0x00000000u);
// Callable direct target.
put32(image, text + 0x100, 0x27BDFFF0u);
put32(image, text + 0x104, 0x03E00008u);
put32(image, text + 0x108, 0x27BD0010u);
// Valid-looking callback not referenced by a direct call.
put32(image, text + 0x200, 0x27BDFFF0u);
put32(image, text + 0x204, 0x03E00008u);
put32(image, text + 0x208, 0x27BD0010u);
// Callable target outside the verified static bound.
put32(image, text + 0x1100, 0x27BDFFF0u);
put32(image, text + 0x1104, 0x03E00008u);
put32(image, text + 0x1108, 0x27BD0010u);
auto exe = parse(image);
std::string error;
CHECK(PSXRecomp::apply_static_analysis_bound(exe, 0x1000, error),
"valid page-aligned analysis bound is accepted");
CHECK(exe.code_size() == 0x1000 && exe.code_data.size() == 0x1000,
"accepted bound truncates header and analysis payload together");
PSXRecomp::FunctionAnalyzer analyzer(exe);
const auto reachable = starts(analyzer.analyze_exact_entries({kLoad}));
CHECK(reachable == std::set<uint32_t>({kLoad, kLoad + 0x100}),
"reachable analysis follows direct callable JAL only");
CHECK(!reachable.count(kLoad + 0x200),
"unseen indirect callback fails closed");
CHECK(!reachable.count(kLoad + 0x1100),
"direct target beyond verified bound fails closed");
PSXRecomp::FunctionAnalyzer seeded_analyzer(exe);
const auto seeded = starts(
seeded_analyzer.analyze_exact_entries({kLoad, kLoad + 0x200}));
CHECK(seeded.count(kLoad + 0x200),
"evidence-backed explicit callback seed is compiled");
auto bad = parse(image);
const auto original_size = bad.code_data.size();
CHECK(!PSXRecomp::apply_static_analysis_bound(bad, 0x1800, error),
"non-page-aligned truncation is rejected");
CHECK(bad.code_size() == 0x2000 && bad.code_data.size() == original_size,
"invalid bound leaves parsed image unchanged");
CHECK(!PSXRecomp::apply_static_analysis_bound(bad, 0x3000, error),
"oversized noncanonical bound is rejected");
CHECK(!PSXRecomp::apply_static_analysis_bound(bad, 0, error),
"zero bound is rejected");
auto excludes_entry = parse(image);
excludes_entry.header.initial_pc = kLoad + 0x1000;
CHECK(!PSXRecomp::apply_static_analysis_bound(excludes_entry, 0x1000, error),
"bound excluding entry point is rejected");
auto rounded = parse(make_exe_buffer(0x1800));
CHECK(PSXRecomp::apply_static_analysis_bound(rounded, 0x2000, error),
"canonical generated final-page reservation remains compatible");
CHECK(rounded.code_size() == 0x1800,
"page reservation does not widen static analysis payload");
if (failures) {
std::fprintf(stderr, "FAILED (%d)\n", failures);
return 1;
}
std::puts("ALL PASS");
return 0;
}
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""End-to-end config and codegen checks for reachable main-EXE discovery.
Usage: python test_reachable_discovery_codegen.py \
[--recompiler <psxrecomp-game.exe>]
"""
import argparse
import os
import struct
import subprocess
import sys
import tempfile
LOAD = 0x80010000
def jal(target):
return 0x0C000000 | ((target >> 2) & 0x03FFFFFF)
def put32(data, offset, value):
struct.pack_into("<I", data, offset, value)
def make_psxexe():
size = 0x2000
header = bytearray(2048)
header[0:8] = b"PS-X EXE"
put32(header, 0x10, LOAD)
put32(header, 0x18, LOAD)
put32(header, 0x1C, size)
text = bytearray(size)
root = [
jal(LOAD + 0x100), 0, # reachable direct call
0x0320F809, 0, # unresolved jalr $ra,$t9
jal(LOAD + 0x1100), 0, # outside configured bound
0x03E00008, 0,
]
for i, word in enumerate(root):
put32(text, i * 4, word)
for offset in (0x100, 0x200, 0x1100):
for i, word in enumerate((0x27BDFFF0, 0x03E00008, 0x27BD0010)):
put32(text, offset + i * 4, word)
return bytes(header + text)
def write_config(path, discovery="reachable", text_size="0x1000"):
discovery_line = (f'discovery = "{discovery}"\n'
if discovery is not None else "")
with open(path, "w", encoding="utf-8") as f:
f.write(f'''[game]
name = "Reachable discovery test"
exe = "test.exe"
load_address = "0x80010000"
entry_pc = "0x80010000"
text_size = "{text_size}"
stack_base = "0x801FFFF0"
[recompiler]
seeds = "seeds.txt"
out_dir = "generated"
{discovery_line}
''')
def run(recompiler, config):
return subprocess.run(
[recompiler, "--config", config], capture_output=True, text=True)
def main():
here = os.path.dirname(os.path.abspath(__file__))
default_recompiler = os.path.normpath(os.path.join(
here, "..", "build", "psxrecomp-game.exe"))
parser = argparse.ArgumentParser()
parser.add_argument("--recompiler", default=default_recompiler)
args = parser.parse_args()
if not os.path.isfile(args.recompiler):
raise SystemExit(f"recompiler not found: {args.recompiler}")
failures = []
with tempfile.TemporaryDirectory() as tmp:
exe = os.path.join(tmp, "test.exe")
seeds = os.path.join(tmp, "seeds.txt")
config = os.path.join(tmp, "game.toml")
with open(exe, "wb") as f:
f.write(make_psxexe())
with open(seeds, "w", encoding="utf-8") as f:
f.write("0x80010000\n")
write_config(config)
result = run(args.recompiler, config)
output = result.stdout + result.stderr
if result.returncode != 0:
failures.append(f"reachable recompile failed:\n{output}")
else:
full_path = os.path.join(tmp, "generated", "test.exe_full.c")
dispatch_path = os.path.join(
tmp, "generated", "test.exe_dispatch.c")
with open(full_path, encoding="utf-8") as f:
full = f.read()
with open(dispatch_path, encoding="utf-8") as f:
dispatch = f.read()
if "void func_80010000" not in full:
failures.append("main EXE entry was not emitted")
if "void func_80010100" not in full:
failures.append("reachable direct-JAL target was not emitted")
if "void func_80010200" in full:
failures.append("unseen indirect callback was emitted")
if "void func_80011100" in full:
failures.append("target beyond analysis bound was emitted")
if "phys < 0x00011000u" not in dispatch:
failures.append("dispatch text range did not use verified bound")
if "dirty_ram_text_native_ok_ranges(" not in dispatch:
failures.append("exact static-range dispatch guard was lost")
if "psx_native_bad_entry(" in full:
failures.append("reachable main image incorrectly used overlay codegen")
if "=== Exact-Entry Function Analysis ===" not in output:
failures.append("reachable config did not select exact-entry analysis")
# Omitting the key must retain the established whole-image default.
write_config(config, discovery=None, text_size="0x2000")
default_mode = run(args.recompiler, config)
default_output = default_mode.stdout + default_mode.stderr
if default_mode.returncode != 0 or "=== Function Boundary Detection ===" not in default_output:
failures.append("omitted discovery key did not preserve whole-image default")
write_config(config, discovery="invent-functions")
invalid_mode = run(args.recompiler, config)
if invalid_mode.returncode == 0 or "must be 'whole-image' or 'reachable'" not in (
invalid_mode.stdout + invalid_mode.stderr):
failures.append("invalid discovery mode did not fail closed")
write_config(config, text_size="0x1800")
invalid_bound = run(args.recompiler, config)
if invalid_bound.returncode == 0 or "must be 4 KiB page-aligned" not in (
invalid_bound.stdout + invalid_bound.stderr):
failures.append("invalid analysis bound did not fail closed")
write_config(config, text_size="0x0")
zero_bound = run(args.recompiler, config)
if zero_bound.returncode == 0 or "game.text_size must be nonzero" not in (
zero_bound.stdout + zero_bound.stderr):
failures.append("zero analysis bound did not fail closed")
if failures:
for failure in failures:
print("FAIL:", failure)
return 1
print("PASS: reachable parser, analysis, and exact static codegen behavior")
return 0
if __name__ == "__main__":
sys.exit(main())