commit 9380df48697dc0c4091e58e2bf7958cbcf44cfb3
parent 83dac6d332d09a32e1faa6bedcad0f7b23df8733
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 21 May 2026 08:02:11 -0700
Enable optimized toy cc coverage
Diffstat:
5 files changed, 37 insertions(+), 19 deletions(-)
diff --git a/doc/OPT.md b/doc/OPT.md
@@ -416,8 +416,10 @@ The optimizer is no longer just a stub:
- `src/opt/ir.c` and `src/opt/ir.h` implement the recorded IR container.
- `src/opt/pass_cfg.c` implements CFG construction.
- `src/opt/pass_ssa.c` implements current SSA construction.
-- `test/cg/run.sh` supports `CFREE_OPT_LEVELS`; default is `0 1`, while
- level `2` is opt-in today.
+- `cfree cc` forwards `-O0`/`-O1`/`-O2` into `CfreeCodeOptions`, so native
+ frontends exercise the optimizer through the normal compile driver.
+- `test/toy/run.sh` supports `CFREE_TOY_OPT_LEVELS`; default is `0 1 2`, so
+ `make test-toy` exercises direct, O1, and O2 paths by default.
Current behavior:
@@ -426,8 +428,8 @@ Current behavior:
combines, runs post-RA DCE, cleans jumps/layout, and emits through the
wrapped target.
- Level `2` has its own scheduler entry point, but until production SSA/value
- passes land it deliberately routes through the same proven lowering path as
- `-O1`.
+ passes land it only runs the current SSA build/conventionalize/undo verifier
+ path before routing through the same proven lowering path as `-O1`.
- `opt_regalloc(..., allow_live_range_split)` accepts the O2 policy bit, but
live-range splitting is not implemented yet.
- The current `opt_build_ssa` remains a shape-checking mem2reg prototype: it
@@ -475,7 +477,8 @@ Keep the allocator simple but not naive:
Exit criteria:
-- `CFREE_OPT_LEVELS="0 1" make test-cg` passes for targeted AArch64 cases.
+- `CFREE_TOY_OPT_LEVELS="0 1" make test-toy` passes for targeted AArch64
+ cases.
- Add focused allocation cases for call-clobber saves, stack spills, tied
hard regs from inline asm, and values live through branches.
@@ -553,7 +556,7 @@ that fails red without the pass or its bug fix.
Exit criteria:
-- `CFREE_OPT_LEVELS="0 1 2" make test-cg` passes for the relevant arch.
+- `CFREE_TOY_OPT_LEVELS="0 1 2" make test-toy` passes for the relevant arch.
- Pass-local dump tests prove the intended rewrite fires.
### Phase E - Inlining and Cleanup
@@ -666,9 +669,9 @@ specifics belong behind `Target`, `TargetABI`, or explicit helper hooks.
Targeted commands:
```
-CFREE_OPT_LEVELS="0 1" make test-cg
-CFREE_OPT_LEVELS="0 1 2" make test-cg
-CFREE_TEST_FILTER=<case> CFREE_OPT_LEVELS="0 1 2" make test-cg
+CFREE_TOY_OPT_LEVELS="0 1 2" make test-toy
+CFREE_TEST_FILTER=<case> CFREE_TOY_OPT_LEVELS="0 1 2" make test-toy
+make test-opt
```
Use pass dumps for red-green tests:
@@ -684,10 +687,12 @@ Use pass dumps for red-green tests:
Before broad runs, redirect output to a file and inspect the failure slice:
```
-CFREE_TEST_FILTER=<case> CFREE_OPT_LEVELS="0 1 2" make test-cg >build/opt-test.log 2>&1
+CFREE_TEST_FILTER=<case> CFREE_TOY_OPT_LEVELS="0 1 2" make test-toy >build/opt-test.log 2>&1
tail -200 build/opt-test.log
```
-The full CG corpus remains the equivalence oracle: levels `1` and `2` must
-produce the same observable `test_main` result as level `0`. DWARF equivalence
-can start weaker, but `set_loc` forwarding must not regress line-row emission.
+The full toy corpus remains the end-to-end equivalence oracle for frontend
+coverage: levels `1` and `2` must produce the same observable result as level
+`0`. The API and opt unit suites remain the narrower pass/CG contract oracle.
+DWARF equivalence can start weaker, but `set_loc` forwarding must not regress
+line-row emission.
diff --git a/driver/cc.c b/driver/cc.c
@@ -1865,9 +1865,7 @@ static void cc_fill_c_opts(const CcOptions* o, const CfreePreprocessOptions* pp,
CfreeCCompileOptions* copts) {
CfreeCCompileOptions zero = {0};
*copts = zero;
- /* Accept -O flags for driver compatibility, but keep hosted cc builds on
- * direct codegen until the optimizer path is robust for large C libraries. */
- copts->code.opt_level = 0;
+ copts->code.opt_level = o->opt_level;
copts->code.debug_info = o->debug_info;
copts->code.emit_c_source = o->emit_c_source ? true : false;
copts->code.epoch = o->epoch;
diff --git a/src/cg/data.c b/src/cg/data.c
@@ -495,6 +495,8 @@ void cfree_cg_data_symdiff(CfreeCg* g, CfreeCgSym lhs, CfreeCgSym rhs,
u8 pad[8];
RelocKind add_kind;
RelocKind sub_kind;
+ const ObjSym* lhs_sym;
+ const ObjSym* rhs_sym;
if (!g || width > sizeof(pad)) return;
if (g->data_local_static_target) {
compiler_panic(g->c, g->cur_loc,
@@ -502,6 +504,20 @@ void cfree_cg_data_symdiff(CfreeCg* g, CfreeCgSym lhs, CfreeCgSym rhs,
"is not yet supported by this target");
return;
}
+ lhs_sym = obj_symbol_get(g->obj, (ObjSymId)lhs);
+ rhs_sym = obj_symbol_get(g->obj, (ObjSymId)rhs);
+ if (lhs_sym && rhs_sym && lhs_sym->section_id != OBJ_SEC_NONE &&
+ lhs_sym->section_id == rhs_sym->section_id) {
+ u64 value = lhs_sym->value - rhs_sym->value + (u64)addend;
+ for (u32 i = 0; i < width; ++i) pad[i] = (u8)(value >> (i * 8u));
+ if (g->data_tls_collect) {
+ api_data_tls_write(g, pad, width);
+ } else {
+ obj_write(g->obj, g->data_sec, pad, width);
+ g->data_size += width;
+ }
+ return;
+ }
switch (width) {
case 1:
add_kind = R_RV_ADD8;
diff --git a/src/opt/pass_emit.c b/src/opt/pass_emit.c
@@ -959,7 +959,6 @@ static void collect_known_frame(Func* f, CGTarget* w, CGKnownFrameDesc* out) {
continue;
}
if ((aux->desc.flags & CG_CALL_TAIL) == 0) out->has_call = 1;
- if ((aux->desc.flags & CG_CALL_TAIL) != 0) continue;
if (!w->call_stack_size) continue;
u32 need = w->call_stack_size(w, &aux->desc);
if (need > out->max_outgoing) out->max_outgoing = need;
diff --git a/test/toy/run.sh b/test/toy/run.sh
@@ -25,7 +25,7 @@
# CFREE_TEST_FILTER / CFREE_TEST_PATHS, where paths is a subset of "RLXC".
# X is opt-in cross-arch cc+ld+exec for aa64, x64, and rv64.
# C is opt-in C-source emit; default paths are "RL".
-# CFREE_TOY_OPT_LEVELS selects optimization levels, default "0 1".
+# CFREE_TOY_OPT_LEVELS selects optimization levels, default "0 1 2".
set -u
@@ -41,7 +41,7 @@ case "$PATHS" in *L*) RUN_L=1;; *) RUN_L=0;; esac
case "$PATHS" in *X*) RUN_X=1;; *) RUN_X=0;; esac
case "$PATHS" in *C*) RUN_C=1;; *) RUN_C=0;; esac
TOY_CROSS_ARCHS="${CFREE_TOY_CROSS_ARCHS:-aa64 x64 rv64}"
-TOY_OPT_LEVELS="${CFREE_TOY_OPT_LEVELS:-0 1}"
+TOY_OPT_LEVELS="${CFREE_TOY_OPT_LEVELS:-0 1 2}"
HOST_CC="${CC:-cc}"
mkdir -p "$BUILD_DIR"