Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
59ccf7f
c5: keep float compound-assign targets on the StoreLocal fast path
kromych Jul 6, 2026
49ae8a8
tests: refresh a stale data-offset snapshot
kromych Jul 6, 2026
b1633e3
c5: type f/F-suffixed floating constants float
kromych Jul 6, 2026
b8cddb2
placeholders for the link to the perf runs
kromych Jul 6, 2026
223a2af
tests: define NDEBUG in the optimized legs of the perf comparisons
kromych Jul 6, 2026
3fc9901
c5: apply intermediate casts in brace initializer elements
kromych Jul 6, 2026
7e14450
c5: run a static-local runtime array initializer exactly once
kromych Jul 6, 2026
8299a22
Merge branch 'perf/bugfix-365' into perf/campaign-2
kromych Jul 6, 2026
9ea42e3
c5: -O predefines NDEBUG and __OPTIMIZE__; demos build at -O
kromych Jul 6, 2026
0b0cc66
Merge branch 'perf/o-ndebug' into perf/campaign-2
kromych Jul 6, 2026
c218e9b
Merge commit '3fc9901e' into perf/campaign-2
kromych Jul 6, 2026
1590377
codegen: ignore dead-pure slot loads when sizing the locals region
kromych Jul 6, 2026
a3aa744
Merge branch 'perf/p1-batch' into perf/campaign-2
kromych Jul 6, 2026
d73720a
tests: refresh a stale data-offset snapshot
kromych Jul 6, 2026
37cba96
codegen: always apply the I8/I16 parameter entry conversion
kromych Jul 6, 2026
7421031
codegen: marshal every aarch64 indirect call through the host ABI
kromych Jul 6, 2026
03f1d58
tests: make the intermediate-cast fixture target-invariant
kromych Jul 6, 2026
05173eb
Merge branch 'perf/351-marshal' into perf/campaign-2
kromych Jul 6, 2026
c065de4
regalloc: derive class must_callee from CFG-precise per-value call-cr…
kromych Jul 6, 2026
07553b2
Merge branch 'perf/355-mustcallee' into perf/campaign-2
kromych Jul 6, 2026
9e73790
mid-end: forward frame-slot stores to same-block reloads
kromych Jul 6, 2026
2c7ed57
Merge branch 'perf/p1-batch' into perf/campaign-2
kromych Jul 6, 2026
6f336b4
vm: extract the SSA constant evaluator into vm/eval.rs
kromych Jul 6, 2026
3ec0c52
tests: regen snapshots at the integration head
kromych Jul 6, 2026
6208db2
mid-end: post-inline constant folding via the shared VM evaluator
kromych Jul 6, 2026
b9ac83e
regalloc: never share spill slots in functions calling returns-twice …
kromych Jul 6, 2026
b7e81f7
codegen: pair aarch64 callee saves as stp/ldp and fold the locals sub
kromych Jul 6, 2026
b7bca51
Merge branch 'perf/374-returns-twice' into perf/campaign-2
kromych Jul 6, 2026
6782360
Merge branch 'perf/353-pairing' into perf/campaign-2
kromych Jul 6, 2026
33e0470
tests: regenerate snapshots at the campaign-2 integration head
kromych Jul 6, 2026
efe992e
tests: gate the mixed int/fp indirect-call fixture off win-x64
kromych Jul 6, 2026
52b1446
tests: match both stp forms in the aarch64 prologue sentinel
kromych Jul 6, 2026
a1269a4
headers: declare the pthread stack-bounds functions
kromych Jul 6, 2026
7dd5d54
demos/python: real stack bounds and a wider recursion margin
kromych Jul 6, 2026
56635e8
vm: pass float arguments as f32 bit patterns in the SSA-VM argument c…
kromych Jul 6, 2026
1e0451b
mid-end: block layout pass: fallthrough chains, loop rotation, branch…
kromych Jul 6, 2026
1bd2821
codegen: lower dense switches through a text-embedded jump table
kromych Jul 6, 2026
f5627ff
Merge branch 'perf/357-layout' into perf/campaign-2
kromych Jul 6, 2026
f1da0bb
Merge branch 'perf/358-jumptable' into perf/campaign-2
kromych Jul 6, 2026
3e6a69d
mid-end: compose the layout pass with jump tables
kromych Jul 6, 2026
74152d2
tests: regenerate the switch snapshots under the composed layout pass
kromych Jul 6, 2026
9f8d1a9
mid-end: unroll constant-trip loops through the inliner's splice mach…
kromych Jul 6, 2026
c17d876
Merge branch 'perf/360-unroll' into perf/campaign-2
kromych Jul 6, 2026
b30ae54
tests: reconcile a switch fixture snapshot under the unroll pass
kromych Jul 6, 2026
6d1e398
tests: make the phi-join switch fixture LLP64-safe
kromych Jul 6, 2026
e81dae5
style: rustfmt the composed successors() call in layout.rs
kromych Jul 6, 2026
a8e6ee1
mid-end: promote unrolled constant-index local arrays to scalars
kromych Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ SSA snapshots of the test fixtures. The optimized binaries will run on any moder
ARM64 processor, and on x86_64 processors not older than Intel Haswell and AMD Zen
(circa 2013, the optimizer uses FMA3 instructions).

Here are the links the the perf run in CI: TODO

`badc` emits position-independent code and the real native binaries (macOS Mach-O,
Linux ELF, or Windows PE32+), on any of five targets, from any host:

Expand Down
3 changes: 2 additions & 1 deletion demos/chibicc/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def compile_one(badc: Path, src: Path, out: Path) -> tuple[bool, str]:
proc = subprocess.run(
[
str(badc),
"-O",
"-I",
str(CHIBICC_DIR),
"-c",
Expand Down Expand Up @@ -160,7 +161,7 @@ def main() -> int:
src_files = [str(CHIBICC_DIR / name) for name in TU_STATE]
out_path = work / ("chibicc.exe" if WIN else "chibicc")
proc = subprocess.run(
[str(badc), "-I", str(CHIBICC_DIR), "-o", str(out_path), *src_files],
[str(badc), "-O", "-I", str(CHIBICC_DIR), "-o", str(out_path), *src_files],
capture_output=True,
text=True,
check=False,
Expand Down
2 changes: 1 addition & 1 deletion demos/gui_hello/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def main() -> int:
for target, source, suffix in BUILDS:
src = GUI_DIR / source
out = work / f"hello-{target}{suffix}"
cmd = [str(badc), f"--target={target}", str(src), "-o", str(out)]
cmd = [str(badc), "-O", f"--target={target}", str(src), "-o", str(out)]
if target.startswith("windows"):
cmd.extend(["-include", "msvc_compat.h"])
proc = subprocess.run(cmd, capture_output=True, text=True)
Expand Down
2 changes: 1 addition & 1 deletion demos/nt_loader/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def pe_subsystem(path: Path) -> int:


def build_pe(badc: Path, target: str, source: Path, out: Path) -> None:
cmd = [str(badc), f"--target={target}", str(source), "-o", str(out)]
cmd = [str(badc), "-O", f"--target={target}", str(source), "-o", str(out)]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
print(
Expand Down
25 changes: 22 additions & 3 deletions demos/python/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,21 @@ def _fix_winsock_fd_gates() -> None:
sys.exit("build: select() fd-range gate not found in selectmodule.c")


def _widen_stack_margin() -> None:
"""badc's register allocator does not yet split live ranges, so
_PyEval_EvalFrameDefault's frame is several times larger than the
reference compilers'. CPython requires the recursion-check margin to
exceed one eval frame (pycore_pythonrun.h); raise it from 2^11 to
2^13 words until the allocator work shrinks the frame."""
pythonrun = SRC / "Include/internal/pycore_pythonrun.h"
text = pythonrun.read_text()
needle = "# define _PyOS_LOG2_STACK_MARGIN 11"
if needle in text:
pythonrun.write_text(text.replace(needle, "# define _PyOS_LOG2_STACK_MARGIN 13", 1))
elif "# define _PyOS_LOG2_STACK_MARGIN 13" not in text:
sys.exit("build: _PyOS_LOG2_STACK_MARGIN not found in pycore_pythonrun.h")


def _check_frozen() -> None:
missing = [
h for h in re.findall(r'frozen_modules/[\w.]+\.h', (SRC / "Python/frozen.c").read_text())
Expand All @@ -316,6 +331,7 @@ def ensure_inputs(target: str, log) -> None:
sys.stderr.write(r.stdout + r.stderr)
sys.exit("build: setup.py failed")
cfg = TARGETS[target]
_widen_stack_margin()
if cfg.get("windows"):
# Windows derives its config from the in-tree PC/ files rather than a
# committed manifest: disable mimalloc, wire the extra builtins into the
Expand Down Expand Up @@ -446,7 +462,7 @@ def build(target: str, do_link: bool, log) -> Path | None:
out = PY_DIR / ".cache" / f"obj-{target}"
out.mkdir(parents=True, exist_ok=True)
dbg = ["-g"] if os.environ.get("BADC_PY_G") else []
opt = ["-O"] if os.environ.get("BADC_PY_O") else []
opt = ["-O"]

entries = _entries(target)
jobs = [(badc, target, src, defs, incs, dbg, opt, str(out), str(SRC)) for src, defs, incs in entries]
Expand All @@ -473,7 +489,7 @@ def build(target: str, do_link: bool, log) -> Path | None:
# the core defines (no CPython includes needed).
for helper in _WIN_HELPERS:
hobj = out / (helper[:-2] + ".o")
hcmd = [badc, "--gnu", "-c", f"--target={target}", "-UHAVE_GCC_UINT128_T", *dbg,
hcmd = [badc, "--gnu", "-c", f"--target={target}", "-UHAVE_GCC_UINT128_T", *dbg, *opt,
*[f"-D{d}" for d in _WIN_DEFINES], str(PY_DIR / helper), "-o", str(hobj)]
r = run(hcmd, timeout=120)
if r.returncode != 0:
Expand Down Expand Up @@ -738,7 +754,10 @@ def run_tests(target: str, py: Path, log, tier2: bool = False) -> int:
r = run([str(exe), "-m", "test", "-q", "-w", *ignore, *slice_], cwd=str(cwd), env=env, timeout=1800)
print(f"build: test slice {' '.join(slice_)} exit={r.returncode}")
if r.returncode != 0:
sys.stderr.write((r.stdout + r.stderr)[-3000:])
# Keep the head too: a fatal-error dump puts the message and the
# aborting frame above hundreds of repeated traceback lines.
blob = r.stdout + r.stderr
sys.stderr.write(blob[:3000] + ("\n...\n" + blob[-3000:] if len(blob) > 3000 else ""))
return 1
return 0

Expand Down
5 changes: 3 additions & 2 deletions demos/python/compare_compilers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Build CPython from the same recipe with badc and the platform reference
compiler (clang on POSIX, cl on Windows), at -O and without, and report
compiler (clang on POSIX, cl on Windows), at -O and without (badc -O
implies NDEBUG; the clang/cl -O legs define it explicitly), and report
per-binary section sizes plus a runtime microbenchmark.

The recipe -- the per-TU source list, defines, and includes -- comes from
Expand Down Expand Up @@ -75,7 +76,7 @@ def _compile_cmd(cc_kind, cc, target, src, defs, incs, obj, opt, reenable):
return [cc, "--gnu", "-c", f"--target={target}", "-UHAVE_GCC_UINT128_T",
'-DCOMPILER="[badc]"', *o, *defs, *incs, src, "-o", obj]
if cc_kind == "clang":
o = ["-O2"] if opt else ["-O0"]
o = ["-O2", "-DNDEBUG"] if opt else ["-O0"]
redef = [f"-D{m}=1" for m in reenable]
# Force the same no-__int128 path badc takes, so the bigint code is
# identical and the comparison isolates the compiler, not the dialect.
Expand Down
9 changes: 3 additions & 6 deletions demos/python/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,10 @@ def compile_and_link(badc: str, trace: Path, out: Path, log) -> Path:
src, flags = compiles[obj]
dst = out / (obj.replace("/", "_"))
dbg = ["-g"] if os.environ.get("BADC_PY_G") else []
# BADC_PY_O builds each TU with the optimizer, for a benchmarkable
# interpreter and to exercise the optimization passes across the
# whole source. The host build trace's own -O flags are dropped by
# Each TU builds with the optimizer (badc -O also implies NDEBUG).
# The host build trace's own -O flags are dropped by
# parse_commands, so this is the only optimization control.
do_opt = bool(os.environ.get("BADC_PY_O"))
if do_opt and opt_only is not None:
do_opt = os.path.basename(src) in opt_only
do_opt = opt_only is None or os.path.basename(src) in opt_only
opt = ["-O"] if do_opt else []
# `--gnu` mirrors the reference clang build: __GNUC__ makes the
# struct layouts (packed tracemalloc) match the clang-built
Expand Down
6 changes: 6 additions & 0 deletions demos/python/targets/linux-aarch64/pyconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,12 @@

/* Define to 1 if you have the 'pthread_getattr_np' function. */
#define HAVE_PTHREAD_GETATTR_NP 1
/* badc links the GNU C library on these targets; CPython gates the
pthread_getattr_np stack-bounds path on the glibc macro. */
#ifndef __GLIBC__
#define __GLIBC__ 2
#define __GLIBC_MINOR__ 17
#endif

/* Define to 1 if you have the 'pthread_getcpuclockid' function. */
#define HAVE_PTHREAD_GETCPUCLOCKID 1
Expand Down
6 changes: 6 additions & 0 deletions demos/python/targets/linux-x64/pyconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,12 @@

/* Define to 1 if you have the 'pthread_getattr_np' function. */
#define HAVE_PTHREAD_GETATTR_NP 1
/* badc links the GNU C library on these targets; CPython gates the
pthread_getattr_np stack-bounds path on the glibc macro. */
#ifndef __GLIBC__
#define __GLIBC__ 2
#define __GLIBC_MINOR__ 17
#endif

/* Define to 1 if you have the 'pthread_getcpuclockid' function. */
#define HAVE_PTHREAD_GETCPUCLOCKID 1
Expand Down
4 changes: 2 additions & 2 deletions demos/quickjs/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def main() -> int:
# `--export-all` puts the engine's API in the executable's dynamic
# symbol table so a dlopen'd native module resolves its references to
# the host (JS_NewObject, JS_ToIndex, ...) from the global scope.
cmd = [str(badc), "--export-all"]
cmd = [str(badc), "-O", "--export-all"]
for d in DEFINES:
cmd += ["-D", d]
cmd += ["-I", str(QJS_DIR)]
Expand Down Expand Up @@ -158,7 +158,7 @@ def main() -> int:
)
module_passed = 0
for src, so, test, cwd in modules:
build = [str(badc), "--export-all", "--shared"]
build = [str(badc), "-O", "--export-all", "--shared"]
for d in DEFINES:
build += ["-D", d]
build += ["-D", "JS_SHARED_LIBRARY", "-I", str(QJS_DIR), str(src), "-o", str(so)]
Expand Down
8 changes: 4 additions & 4 deletions demos/raylib/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def platform_build(badc: Path, work: Path) -> bool:
objs = []
for mod in modules:
obj = work / f"{mod}.o"
cmd = [str(badc), "-c", *defines]
cmd = [str(badc), "-O", "-c", *defines]
if mod == "rcore":
cmd += rcore_extra
if mod == "raudio":
Expand All @@ -183,7 +183,7 @@ def platform_build(badc: Path, work: Path) -> bool:
return False

logic_obj = work / "loderunner_logic.o"
if run([str(badc), "-c", "-I", str(RAYLIB_DIR),
if run([str(badc), "-O", "-c", "-I", str(RAYLIB_DIR),
"-o", str(logic_obj), str(RAYLIB_DIR / "loderunner_logic.c")]).returncode != 0:
return False

Expand All @@ -192,7 +192,7 @@ def platform_build(badc: Path, work: Path) -> bool:
# `#pragma subsystem(windows)` drives the PE subsystem (a pragma in a
# separately-compiled object is not visible at link). loderunner_logic.c
# is still separately compiled to exercise the multi-object link.
if run([str(badc), *defines, "-I", str(inc), "-I", str(src),
if run([str(badc), "-O", *defines, "-I", str(inc), "-I", str(src),
str(RAYLIB_DIR / "loderunner.c"), str(logic_obj),
str(archive), "-o", str(game)]).returncode != 0:
print("smoke FAIL: standalone link", file=sys.stderr)
Expand Down Expand Up @@ -283,7 +283,7 @@ def _validate_win_imports(badc: Path, game: Path, work: Path) -> bool:
lines.append("return miss?1:0;}")
src.write_text("\n".join(lines) + "\n")
exe = work / f"import_probe{EXE}"
if run([str(badc), str(src), "-o", str(exe)]).returncode != 0:
if run([str(badc), "-O", str(src), "-o", str(exe)]).returncode != 0:
print("smoke FAIL: could not build the import probe", file=sys.stderr)
return False
p = run([str(exe)], capture_output=True, text=True)
Expand Down
4 changes: 2 additions & 2 deletions demos/tcl/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def compile_units(badc: str, unix: Path, generic: Path, out: Path, log) -> list[
objs, fails = [], []
for src, obj, flags in cmds:
objp = out / f"{obj}.o"
cmd = [badc, "-c", "-UHAVE_CPUID", *extra, *flags, *includes, src, "-o", str(objp)]
cmd = [badc, "-O", "-c", "-UHAVE_CPUID", *extra, *flags, *includes, src, "-o", str(objp)]
r = run(cmd, timeout=180)
if r.returncode != 0:
msg = (r.stderr.strip().splitlines() or [f"rc{r.returncode}"])[-1]
Expand All @@ -127,7 +127,7 @@ def compile_units(badc: str, unix: Path, generic: Path, out: Path, log) -> list[
zflags = ["-DHAVE_ZLIB=1", "-DBUILD_tcl", "-DSTDC", "-I" + str(zdir)]
for name in ZLIB_UNITS:
objp = out / f"Z{name}.o"
r = run([badc, "-c", *zflags, str(zdir / f"{name}.c"), "-o", str(objp)], timeout=180)
r = run([badc, "-O", "-c", *zflags, str(zdir / f"{name}.c"), "-o", str(objp)], timeout=180)
if r.returncode != 0:
fails.append((f"zlib {name}", (r.stderr.strip().splitlines() or ["rc"])[-1][:160]))
else:
Expand Down
4 changes: 2 additions & 2 deletions demos/tinycc/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def compile_one(
target: str | None,
) -> tuple[bool, str]:
"""Run badc -c against `src`. Returns (ok, captured_stderr_head)."""
cmd = [str(badc), "-q", "-I", str(TINYCC_DIR)]
cmd = [str(badc), "-q", "-O", "-I", str(TINYCC_DIR)]
if target is not None:
cmd.append(f"--target={target}")
for d in cpp_defs:
Expand Down Expand Up @@ -373,7 +373,7 @@ def main() -> int:
src_files = [str(TINYCC_DIR / name) for name in active]
link_target_is_win = key[0] == "Windows"
out_path = work / ("tcc.exe" if link_target_is_win else "tcc")
cmd = [str(badc), "-q", "-I", str(TINYCC_DIR)]
cmd = [str(badc), "-q", "-O", "-I", str(TINYCC_DIR)]
if target is not None:
cmd.append(f"--target={target}")
for d in cpp_defs:
Expand Down
12 changes: 12 additions & 0 deletions headers/include/pthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
#pragma binding(libc::pthread_cond_broadcast, "_pthread_cond_broadcast")
#pragma binding(libc::pthread_attr_init, "_pthread_attr_init")
#pragma binding(libc::pthread_attr_destroy, "_pthread_attr_destroy")
#pragma binding(libc::pthread_attr_getguardsize, "_pthread_attr_getguardsize")
#pragma binding(libc::pthread_attr_getstack, "_pthread_attr_getstack")
#pragma binding(libc::pthread_attr_setdetachstate, "_pthread_attr_setdetachstate")
#pragma binding(libc::pthread_attr_setstacksize, "_pthread_attr_setstacksize")
#pragma binding(libc::pthread_attr_setscope, "_pthread_attr_setscope")
Expand Down Expand Up @@ -128,6 +130,9 @@
#pragma binding(libc::pthread_attr_setschedparam, "pthread_attr_setschedparam")
#pragma binding(libc::pthread_attr_getschedparam, "pthread_attr_getschedparam")
#pragma binding(libc::pthread_attr_setinheritsched, "pthread_attr_setinheritsched")
#pragma binding(libc::pthread_attr_getguardsize, "pthread_attr_getguardsize")
#pragma binding(libc::pthread_attr_getstack, "pthread_attr_getstack")
#pragma binding(libc::pthread_getattr_np, "pthread_getattr_np")
// The Linux C library's pthread_atfork lives in libc_nonshared.a (a static stub),
// not as a dynamic export of libc.so.6 -- x86_64 keeps a weak legacy
// alias but aarch64 does not, so a dynamic import resolves on one and
Expand Down Expand Up @@ -314,6 +319,13 @@ int pthread_attr_setscope(char *attr, int scope);
int pthread_attr_setschedpolicy(char *attr, int policy);
int pthread_attr_setschedparam(char *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const char *attr, struct sched_param *param);
int pthread_attr_getguardsize(const char *attr, unsigned long *guardsize);
int pthread_attr_getstack(const char *attr, void **stackaddr, unsigned long *stacksize);
#if defined(__linux__)
// GNU extension: the running thread's attributes, including the real
// stack bounds (the portable route is pthread_attr_getstack on these).
int pthread_getattr_np(unsigned long thread, char *attr);
#endif
int pthread_attr_setinheritsched(char *attr, int inheritsched);
#ifdef __linux__
// Linux names the calling-convention pair (pthread_t, name).
Expand Down
Loading
Loading