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);
|
need_m4=node_uses_m4(e->check->ast->root);
|
||||||
for (type=e->check->types.types; type; type=type->next)
|
for (type=e->check->types.types; type; type=type->next)
|
||||||
if (strcmp(type->name,"io.Writer")==0) need_m4=1;
|
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)
|
if (e->pointer_bits==16)
|
||||||
fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out);
|
fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out);
|
||||||
else
|
else
|
||||||
|
|||||||
+4
-1
@@ -1327,7 +1327,10 @@ void fe_emit_c_program(FeEmitter *e)
|
|||||||
need_m4=node_uses_m4(e->check->ast->root);
|
need_m4=node_uses_m4(e->check->ast->root);
|
||||||
for (type=e->check->types.types;type;type=type->next)
|
for (type=e->check->types.types;type;type=type->next)
|
||||||
if (strcmp(type->name,"io.Writer")==0) need_m4=1;
|
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)
|
if (e->pointer_bits==16)
|
||||||
fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out);
|
fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -151,10 +151,14 @@ def _rc_batch() -> str:
|
|||||||
return "\r\n".join(lines)
|
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
|
# 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.
|
# 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"
|
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 = [
|
lines = [
|
||||||
"@echo off", "if not exist RESULTS md RESULTS", "if not exist OUT md OUT",
|
"@echo off", "if not exist RESULTS md RESULTS", "if not exist OUT md OUT",
|
||||||
"set WATCOM=W:", "set INCLUDE=W:\\H",
|
"set WATCOM=W:", "set INCLUDE=W:\\H",
|
||||||
@@ -247,6 +251,23 @@ class SuiteRun:
|
|||||||
shutil.rmtree(self.root, ignore_errors=True)
|
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,
|
def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
|
||||||
trace_dos: bool = False) -> SuiteRun:
|
trace_dos: bool = False) -> SuiteRun:
|
||||||
dosbox, watcom = resolve_tools()
|
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))
|
run_root = Path(tempfile.mkdtemp(prefix="suite-", dir=RUNS))
|
||||||
result = SuiteRun(run_root, cases, keep)
|
result = SuiteRun(run_root, cases, keep)
|
||||||
fec = result.fec
|
fec = result.fec
|
||||||
|
cached = CACHE / "compilers" / f"{_compiler_key()}.exe"
|
||||||
try:
|
try:
|
||||||
shutil.copytree(ROOT / "fec" / "src", fec / "SRC")
|
shutil.copytree(ROOT / "fec" / "src", fec / "SRC")
|
||||||
shutil.copytree(ROOT / "fec" / "std", fec / "STD")
|
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",
|
f"[cpu]\ncore=dynamic\ncycles=max\n",
|
||||||
encoding="ascii",
|
encoding="ascii",
|
||||||
)
|
)
|
||||||
|
if cached.is_file():
|
||||||
|
shutil.copy2(cached, fec / "FEC.EXE")
|
||||||
(fec / "RUN.BAT").write_text(
|
(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="",
|
encoding="ascii", newline="",
|
||||||
)
|
)
|
||||||
(fec / "RC.BAT").write_text(_rc_batch(), 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}")
|
raise DosboxError(f"DOSBox-X exited with status {completed.returncode}")
|
||||||
if not (fec / "RUN.OK").is_file():
|
if not (fec / "RUN.OK").is_file():
|
||||||
raise DosboxError("DOSBox-X did not complete the test batch")
|
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
|
return result
|
||||||
except Exception:
|
except Exception:
|
||||||
result.keep = True
|
result.keep = True
|
||||||
|
|||||||
Reference in New Issue
Block a user