Seen is self-hosted: a known-good Seen compiler builds the next compiler, then that compiler builds the compiler again. The rebuild is accepted only when the new stages verify correctly.
scripts/safe_rebuild.sh.| File | Purpose |
|---|---|
bootstrap/stage1_frozen |
Known-good bootstrap compiler |
bootstrap/stage1_frozen.sha256 |
Integrity hash for the frozen compiler |
compiler_seen/target/seen |
Verified compiler output |
target/release/seen |
Release copy of the verified compiler |
scripts/safe_rebuild.sh |
Guarded staged rebuild |
scripts/seen_prebuild_gates.sh |
Early source/IR prebuild gates |
scripts/fix_ir.py |
Frozen-bootstrap IR repair guard for known malformed IR patterns |
scripts/safe_rebuild.sh has three tiers:
| Tier | Purpose | Output |
|---|---|---|
--tier quick |
Cache-enabled developer rebuild with smoke checks only. | compiler_seen/target/seen-dev |
--tier verify |
Cache-enabled production rebuild with prebuild gates, smoke checks, and targeted compiler checks before install. | compiler_seen/target/seen and target/release/seen |
--tier full |
Cold staged bootstrap verification with the existing Stage 1/2/3 and recovery semantics. This is still the no-argument default. | compiler_seen/target/seen, target/release/seen, and a full-release stamp |
Do not run a compiler rebuild without explicit memory limits. A typical guarded run derives a main cap from current memory and keeps optimizer work capped:
AVAIL_KB=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
MAIN_KB=$(( AVAIL_KB * 70 / 100 ))
if [ "$MAIN_KB" -gt 10485760 ]; then MAIN_KB=10485760; fi
ulimit -v "$MAIN_KB"
SEEN_LOW_MEMORY=1 \
SEEN_SKIP_LOW_MEMORY_SHORTCUT=1 \
SEEN_MAIN_VMEM_KB="$MAIN_KB" \
SEEN_OPT_VMEM_KB=2097152 \
SEEN_MEMORY_LIMIT_BYTES="$(( MAIN_KB * 1024 ))" \
./scripts/safe_rebuild.sh
The script runs prebuild gates before expensive compiler work unless explicitly disabled with SEEN_SKIP_PREBUILD_GATES=1.
For iterative work, use the same derived caps with a quicker tier:
SEEN_LOW_MEMORY=1 \
SEEN_MAIN_VMEM_KB="$MAIN_KB" \
SEEN_OPT_VMEM_KB=2097152 \
SEEN_MEMORY_LIMIT_BYTES="$(( MAIN_KB * 1024 ))" \
./scripts/safe_rebuild.sh --tier quick
--clean-cache explicitly removes .seen_cache/, /tmp/seen_ir_cache, /tmp/seen_thinlto_cache, and generated-test object caches. Quick and verify tiers do not clear useful caches during normal rebuilds.
Set SEEN_TRACE_BUILD=<path> to write JSONL build events. SEEN_BUILD_TRACE is accepted as a compatibility alias.
SEEN_TRACE_BUILD=/tmp/seen-build.jsonl ./scripts/safe_rebuild.sh --tier quick
The scripts also print a concise timing summary. Use scripts/perf_gate.sh to record and compare capped performance baselines. scripts/build_perf_gate.sh remains a compatibility wrapper for the build suite.
SEEN_LOW_MEMORY=1 scripts/perf_gate.sh --record-baseline --suite build --tier quick
SEEN_LOW_MEMORY=1 scripts/perf_gate.sh --compare --suite build --tier quick
scripts/perf_gate.sh --compare --suite stdlib
scripts/perf_gate.sh --compare --suite runtime
scripts/perf_gate.sh --compare --suite release-lto
scripts/perf_gate.sh --compare --suite packages --version 0.9.2-build
Schema-v2 baselines are stored under target/seen-build/perf-baselines/<suite>/. Build traces include guard state/status and peak RSS fields for guarded commands. Regression failures include the suite or benchmark name, threshold, observed value, and next action. Compiler module cache keys also include the active compiler binary hash, so a warm quick/verify rebuild keeps valid objects only when they were produced by a compatible compiler.
Benchmark suites use the verified compiler_seen/target/seen binary by default. Set SEEN_BENCH_USE_DEV=1 when the intent is to measure the current quick-tier compiler_seen/target/seen-dev output instead. Production benchmark scripts preserve warm caches unless SEEN_BENCH_COLD_CACHE=1 is set.
The rebuild script derives SEEN_JOBS and SEEN_OPT_JOBS from CPU count and the memory caps when the variables are not supplied. The compiler also accepts --jobs <n> and --opt-jobs <n>. --no-fork remains the explicit serial escape hatch.
Quick and verify tiers choose a trusted builder before doing frozen-bootstrap startup/hash checks or creating the bootstrap source overlay. Those frozen-only steps still run for full cold verification and for quick/verify fallback to a frozen compiler. Quick/verify smoke compiles use a signature-keyed cache, but the smoke executable is still run every time.
The prebuild gate catches failures that used to appear late in Stage 2/Stage 3:
fix_ir.pyRun it directly when changing compiler source or bootstrap scripts:
scripts/seen_prebuild_gates.sh
Package prebuild artifacts contain:
Seen.pkg.tomlobjects.tsvinterface.index.tsvDuring dependent builds, the compiler loads declarations from the artifact interface/index, links listed objects, and skips code generation for modules provided by the artifact.
When a rebuild succeeds, verify the fresh compiler can compile a minimal program before replacing any system-wide binary:
cat > /tmp/seen_hello.seen <<'SEEN'
fun main() {
println("hello")
}
SEEN
compiler_seen/target/seen compile /tmp/seen_hello.seen /tmp/seen_hello
/tmp/seen_hello
Only copy a compiler into a PATH location after Stage 2/Stage 3 verification and smoke tests pass. Then compare hashes:
sha256sum compiler_seen/target/seen target/release/seen "$(command -v seen)"
If the working compiler is broken, use the frozen compiler and the guarded rebuild scripts instead of retrying uncapped builds:
./bootstrap/stage1_frozen compile compiler_seen/src/main_compiler.seen recovery_compiler
If a rebuild fails, inspect the first concrete failing log/artifact and fix that cause before retrying.
