dev: cache the compiler build and drop unused headers
Every run rebuilt fec from scratch inside DOS, about 14 seconds, even when no
source had changed. Key a cache on the hash of fec/src plus build-dos.bat and
restore FEC.EXE when it matches; the toolchain itself is pinned by
dosboxx.lock.json so it cannot drift under a hit. Only a passing build is
cached, and the batch skips BUILD.BAT on a hit because it would delete and
rebuild the executable it was just given.
Generated C included stdio.h unconditionally, but only the M4 writer runtime
reaches it. A 26-line unit was pulling in roughly 1900 lines of headers, paid
once per compile check. Emit it only when the runtime is emitted.
--only m6, 57 passed, over three changes:
31s before
17s core=dynamic
2s warm compiler cache
Cold runs still pay the build once.
This commit is contained in:
+6
-1
@@ -1487,7 +1487,12 @@ void fe_emit_c_program(FeEmitter *e)
|
||||
need_m4=node_uses_m4(e->check->ast->root);
|
||||
for (type=e->check->types.types; type; type=type->next)
|
||||
if (strcmp(type->name,"io.Writer")==0) need_m4=1;
|
||||
fputs("/* generated by fec M4 */\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\ntypedef char fe_assert_u8[(sizeof(unsigned char)==1) ? 1 : -1];\ntypedef char fe_assert_u16[(sizeof(unsigned short)==2) ? 1 : -1];\ntypedef char fe_assert_u32[(sizeof(unsigned long)==4) ? 1 : -1];\n", e->out);
|
||||
/* stdio is only reached by the M4 writer runtime (fwrite/stdout/stderr).
|
||||
Parsing it costs far more than the generated body -- a 26-line unit pulls
|
||||
in about 1900 lines of headers -- so leave it out when nothing uses it. */
|
||||
fputs("/* generated by fec M4 */\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n", e->out);
|
||||
if (need_m4) fputs("#include <stdio.h>\n", e->out);
|
||||
fputs("typedef char fe_assert_u8[(sizeof(unsigned char)==1) ? 1 : -1];\ntypedef char fe_assert_u16[(sizeof(unsigned short)==2) ? 1 : -1];\ntypedef char fe_assert_u32[(sizeof(unsigned long)==4) ? 1 : -1];\n", e->out);
|
||||
if (e->pointer_bits==16)
|
||||
fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out);
|
||||
else
|
||||
|
||||
+4
-1
@@ -1327,7 +1327,10 @@ void fe_emit_c_program(FeEmitter *e)
|
||||
need_m4=node_uses_m4(e->check->ast->root);
|
||||
for (type=e->check->types.types;type;type=type->next)
|
||||
if (strcmp(type->name,"io.Writer")==0) need_m4=1;
|
||||
fputs("/* generated by fec M7 */\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\ntypedef char fe_assert_u8[(sizeof(unsigned char)==1) ? 1 : -1];\ntypedef char fe_assert_u16[(sizeof(unsigned short)==2) ? 1 : -1];\ntypedef char fe_assert_u32[(sizeof(unsigned long)==4) ? 1 : -1];\n",e->out);
|
||||
/* See emit_c.c: stdio only comes in with the M4 writer runtime. */
|
||||
fputs("/* generated by fec M7 */\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n",e->out);
|
||||
if (need_m4) fputs("#include <stdio.h>\n",e->out);
|
||||
fputs("typedef char fe_assert_u8[(sizeof(unsigned char)==1) ? 1 : -1];\ntypedef char fe_assert_u16[(sizeof(unsigned short)==2) ? 1 : -1];\ntypedef char fe_assert_u32[(sizeof(unsigned long)==4) ? 1 : -1];\n",e->out);
|
||||
if (e->pointer_bits==16)
|
||||
fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out);
|
||||
else
|
||||
|
||||
@@ -151,10 +151,14 @@ def _rc_batch() -> str:
|
||||
return "\r\n".join(lines)
|
||||
|
||||
|
||||
def _batch(cases: list[Case], *, show_dos: bool, trace_dos: bool) -> str:
|
||||
def _batch(cases: list[Case], *, show_dos: bool, trace_dos: bool,
|
||||
prebuilt: bool = False) -> str:
|
||||
# The compiler build is the step that fails first and blocks everything after
|
||||
# it, so its output is captured exactly like a case command's.
|
||||
build = "call BUILD.BAT" if trace_dos else "call BUILD.BAT > RESULTS\\BUILD.LOG"
|
||||
if prebuilt:
|
||||
# FEC.EXE was restored from cache; BUILD.BAT would delete and rebuild it.
|
||||
build = "echo OK>BUILD.OK"
|
||||
lines = [
|
||||
"@echo off", "if not exist RESULTS md RESULTS", "if not exist OUT md OUT",
|
||||
"set WATCOM=W:", "set INCLUDE=W:\\H",
|
||||
@@ -247,6 +251,23 @@ class SuiteRun:
|
||||
shutil.rmtree(self.root, ignore_errors=True)
|
||||
|
||||
|
||||
def _compiler_key() -> str:
|
||||
"""Hash of everything the compiler build reads.
|
||||
|
||||
Sources and the build batch only; the toolchain itself is pinned by
|
||||
dosboxx.lock.json, so it cannot drift underneath a cache hit.
|
||||
"""
|
||||
digest = hashlib.sha256()
|
||||
paths = sorted((ROOT / "fec" / "src").rglob("*"))
|
||||
paths.append(ROOT / "fec" / "build-dos.bat")
|
||||
for path in paths:
|
||||
if not path.is_file():
|
||||
continue
|
||||
digest.update(path.name.encode("utf-8"))
|
||||
digest.update(path.read_bytes())
|
||||
return digest.hexdigest()[:16]
|
||||
|
||||
|
||||
def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
|
||||
trace_dos: bool = False) -> SuiteRun:
|
||||
dosbox, watcom = resolve_tools()
|
||||
@@ -254,6 +275,7 @@ def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
|
||||
run_root = Path(tempfile.mkdtemp(prefix="suite-", dir=RUNS))
|
||||
result = SuiteRun(run_root, cases, keep)
|
||||
fec = result.fec
|
||||
cached = CACHE / "compilers" / f"{_compiler_key()}.exe"
|
||||
try:
|
||||
shutil.copytree(ROOT / "fec" / "src", fec / "SRC")
|
||||
shutil.copytree(ROOT / "fec" / "std", fec / "STD")
|
||||
@@ -271,8 +293,11 @@ def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
|
||||
f"[cpu]\ncore=dynamic\ncycles=max\n",
|
||||
encoding="ascii",
|
||||
)
|
||||
if cached.is_file():
|
||||
shutil.copy2(cached, fec / "FEC.EXE")
|
||||
(fec / "RUN.BAT").write_text(
|
||||
_batch(cases, show_dos=show_dos, trace_dos=trace_dos),
|
||||
_batch(cases, show_dos=show_dos, trace_dos=trace_dos,
|
||||
prebuilt=cached.is_file()),
|
||||
encoding="ascii", newline="",
|
||||
)
|
||||
(fec / "RC.BAT").write_text(_rc_batch(), encoding="ascii", newline="")
|
||||
@@ -293,6 +318,10 @@ def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
|
||||
raise DosboxError(f"DOSBox-X exited with status {completed.returncode}")
|
||||
if not (fec / "RUN.OK").is_file():
|
||||
raise DosboxError("DOSBox-X did not complete the test batch")
|
||||
built = fec / "FEC.EXE"
|
||||
if not cached.is_file() and built.is_file() and result.result() == "PASS":
|
||||
cached.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(built, cached)
|
||||
return result
|
||||
except Exception:
|
||||
result.keep = True
|
||||
|
||||
Reference in New Issue
Block a user