backend: i386 어셈블리를 내고 Windows 11 실행 파일을 만든다
fec --emit-asm -> wasm -> wlink -> .exe. 툴체인은 고정된 Open Watcom 그대로다. 레지스터 할당기가 없다. 임시값마다 스택 슬롯을 주고, 명령마다 피연산자를 고정 레지스터로 읽어 계산하고 다시 저장한다. 느린 코드지만 명백히 옳은 코드이고, 옳은 것이 먼저다. 나중에 할당기를 끼워도 나머지는 모른다 -- 임시값이 어디 사는지만 바뀐다. 런타임 fec/rt/start.asm 은 진입 스텁과 fe_trap 이다. trap 은 이유와 파일과 줄을 stderr 에 쓰고 3으로 끝낸다. 처음으로 Ferro 프로그램이 실행됐다: 1..10 합 -> 55 (7*6-2)/4 -> 10 루프+호출+분기 -> 1 tests/build.py 가 컴파일하고 링크하고 돌린다.
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
"""Compile a Ferro program to a Windows executable and run it.
|
||||
|
||||
fec --emit-asm -> wasm -> wlink (+ the runtime, + kernel32) -> .exe
|
||||
|
||||
The toolchain is the pinned Open Watcom under `.dosboxx/watcom`, hosted: the
|
||||
assembler and linker there produce PE binaries as happily as they produce DOS
|
||||
ones. Nothing about this step needs a virtual machine.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
WATCOM = ROOT / ".dosboxx" / "watcom"
|
||||
RUNTIME = ROOT / "fec" / "rt" / "start.asm"
|
||||
|
||||
|
||||
def _env() -> dict:
|
||||
env = dict(os.environ)
|
||||
env.update(WATCOM=str(WATCOM),
|
||||
INCLUDE=f"{WATCOM / 'h'};{WATCOM / 'h' / 'nt'}",
|
||||
PATH=f"{WATCOM / 'binnt'}{os.pathsep}{env.get('PATH', '')}")
|
||||
return env
|
||||
|
||||
|
||||
def _run(cmd, cwd) -> subprocess.CompletedProcess:
|
||||
return subprocess.run([str(c) for c in cmd], cwd=cwd, env=_env(),
|
||||
capture_output=True, text=True, timeout=120)
|
||||
|
||||
|
||||
def build(fec: Path, source: Path, out_dir: Path, no_checks: bool = False):
|
||||
"""Returns (exe_path, log). exe_path is None when a step failed."""
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
stem = source.stem
|
||||
asm = out_dir / f"{stem}.asm"
|
||||
log = []
|
||||
|
||||
cmd = [fec, "--emit-asm", source, "-o", asm]
|
||||
if no_checks:
|
||||
cmd.append("--no-checks")
|
||||
step = _run(cmd, out_dir)
|
||||
log.append(("fec", step.returncode, step.stdout + step.stderr))
|
||||
if step.returncode != 0 or not asm.is_file():
|
||||
return None, log
|
||||
|
||||
wasm = WATCOM / "binnt" / "wasm.exe"
|
||||
for src, obj in ((asm, out_dir / f"{stem}.obj"),
|
||||
(RUNTIME, out_dir / "start.obj")):
|
||||
step = _run([wasm, "-q", "-zq", src, f"-fo={obj}"], out_dir)
|
||||
log.append(("wasm " + src.name, step.returncode,
|
||||
step.stdout + step.stderr))
|
||||
if step.returncode != 0:
|
||||
return None, log
|
||||
|
||||
exe = out_dir / f"{stem}.exe"
|
||||
step = _run([WATCOM / "binnt" / "wlink.exe",
|
||||
"system", "nt",
|
||||
"file", out_dir / f"{stem}.obj",
|
||||
"file", out_dir / "start.obj",
|
||||
"library", WATCOM / "lib386" / "nt" / "kernel32.lib",
|
||||
"name", exe,
|
||||
"option", "quiet"], out_dir)
|
||||
log.append(("wlink", step.returncode, step.stdout + step.stderr))
|
||||
if step.returncode != 0 or not exe.is_file():
|
||||
return None, log
|
||||
return exe, log
|
||||
|
||||
|
||||
def run(exe: Path):
|
||||
done = subprocess.run([str(exe)], capture_output=True, text=True,
|
||||
timeout=30)
|
||||
return done.returncode, done.stdout + done.stderr
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print("usage: build.py <program.fe> [--no-checks]")
|
||||
return 2
|
||||
source = Path(sys.argv[1]).resolve()
|
||||
fec = ROOT / ".build" / "fec.exe"
|
||||
if not fec.is_file():
|
||||
print("build the front end first: uv run python tests/run.py")
|
||||
return 2
|
||||
exe, log = build(fec, source, ROOT / ".build" / "out",
|
||||
"--no-checks" in sys.argv)
|
||||
for name, code, text in log:
|
||||
if code != 0 or text.strip():
|
||||
print(f"--- {name} (exit {code})")
|
||||
print(text.rstrip())
|
||||
if not exe:
|
||||
return 1
|
||||
code, text = run(exe)
|
||||
if text:
|
||||
print(text, end="")
|
||||
print(f"{exe.name} exited {code}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user