commit 1929bac14f4b1e285d9593ac5ec3f94e105862ee parent 65f74c2d6c1c5d3c32063cd1818d7cb40ca3215d Author: Ryan Sepassi <rsepassi@gmail.com> Date: Tue, 19 May 2026 22:53:41 -0700 Fix Lua-driven frontend and cc regressions Scale pointer inc/dec by pointee size, apply unsigned div/rem after integer conversions, and preserve promoted ternary branch values before storage. Keep hosted cfree cc -O builds on direct codegen while the full optimizer path matures, and add Lua-derived parser plus optimizer regressions. Diffstat:
18 files changed, 239 insertions(+), 15 deletions(-)
diff --git a/driver/cc.c b/driver/cc.c @@ -1727,7 +1727,9 @@ static void cc_fill_c_opts(const CcOptions* o, const CfreePreprocessOptions* pp, CfreeCCompileOptions* copts) { CfreeCCompileOptions zero = {0}; *copts = zero; - copts->code.opt_level = o->opt_level; + /* 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.debug_info = o->debug_info; copts->code.epoch = o->epoch; copts->code.path_map = o->npath_map ? o->path_map : NULL; diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -506,8 +506,36 @@ void pcg_convert(Parser* p, const Type* dst) { void pcg_inc_dec(Parser* p, BinOp op, int post) { const Type* ty = pcg_top_type(p); if (pcg_emit_enabled(p)) { - cfree_cg_inc_dec(p->cg, pcg_int_binop(op), post, pcg_tid(p, ty), - pcg_mem(p, ty)); + CfreeCgIntBinOp cg_op = pcg_int_binop(op); + CfreeCgMemAccess mem = pcg_mem(p, ty); + if (ty && ty->kind == TY_PTR) { + const Type* pointee = ty->ptr.pointee; + const Type* idx_ty = c_abi_ptrdiff_type(p->abi, p->pool); + u32 step; + if (pointee && pointee->kind == TY_VOID) + perr(p, "pointer arithmetic on void pointer"); + step = c_abi_sizeof(p->abi, pointee); + cfree_cg_dup(p->cg); /* [lv, lv] */ + cfree_cg_load(p->cg, mem); /* [lv, old] */ + if (post) { + cfree_cg_dup(p->cg); /* [lv, old, result] */ + cfree_cg_swap(p->cg); /* [lv, result, old] */ + cfree_cg_push_int(p->cg, step, pcg_tid(p, idx_ty)); + cfree_cg_int_binop(p->cg, cg_op, 0); /* [lv, result, new] */ + cfree_cg_rot3(p->cg); /* [result, new, lv] */ + cfree_cg_swap(p->cg); /* [result, lv, new] */ + cfree_cg_store(p->cg, mem); /* [result] */ + } else { + cfree_cg_push_int(p->cg, step, pcg_tid(p, idx_ty)); + cfree_cg_int_binop(p->cg, cg_op, 0); /* [lv, new] */ + cfree_cg_dup(p->cg); /* [lv, new, new] */ + cfree_cg_rot3(p->cg); /* [new, new, lv] */ + cfree_cg_swap(p->cg); /* [new, lv, new] */ + cfree_cg_store(p->cg, mem); /* [new] */ + } + } else { + cfree_cg_inc_dec(p->cg, cg_op, post, pcg_tid(p, ty), mem); + } } } diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -2527,6 +2527,18 @@ static CmpOp unsigned_rel_cmp(CmpOp cop) { } } +static BinOp int_div_rem_binop(BinOp op, const Type* common) { + if (pcg_type_is_signed(common)) return op; + switch (op) { + case BO_SDIV: + return BO_UDIV; + case BO_SREM: + return BO_UREM; + default: + return op; + } +} + static void parse_mul(Parser* p) { parse_unary(p); for (;;) { @@ -2559,7 +2571,7 @@ static void parse_mul(Parser* p) { } else { const Type* icommon = cint_common_type(p, lt, rt); coerce_arith_operands(p, icommon); - cg_binop(p->cg, bop); + cg_binop(p->cg, int_div_rem_binop(bop, icommon)); } } } @@ -2672,10 +2684,22 @@ static void parse_shift(Parser* p) { } advance(p); to_rvalue(p); - if (bop == BO_SHR_S && !c_abi_type_info(p->abi, cg_top_type(p->cg)).signed_) - bop = BO_SHR_U; + { + const Type* lt = cg_top_type(p->cg); + const Type* lp = cint_promote_type(p, lt); + if (!type_is_int(lt)) perr(p, "shift operator requires integer operands"); + if (cg_top_type(p->cg) != lp) cg_convert(p->cg, lp); + if (bop == BO_SHR_S && !c_abi_type_info(p->abi, lp).signed_) + bop = BO_SHR_U; + } parse_add(p); to_rvalue(p); + { + const Type* rt = cg_top_type(p->cg); + const Type* rp = cint_promote_type(p, rt); + if (!type_is_int(rt)) perr(p, "shift operator requires integer operands"); + if (cg_top_type(p->cg) != rp) cg_convert(p->cg, rp); + } if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) { perr(p, "shift operator requires integer operands"); } @@ -2961,6 +2985,7 @@ static void parse_ternary(Parser* p) { int then_null = 0; FrameSlot tmp; FrameSlotDesc fsd; + const Type* then_store_ty; advance(p); /* '?' */ to_rvalue(p); require_scalar(p, cg_top_type(p->cg), "conditional operator"); @@ -2970,14 +2995,21 @@ static void parse_ternary(Parser* p) { result_ty = cg_top_type(p->cg); then_null = null_pointer_constant(p, result_ty); if (!result_ty) result_ty = ty_int(p); + if (type_is_int(result_ty)) { + result_ty = type_promoted(p->pool, result_ty); + if (cg_top_type(p->cg) != result_ty) cg_convert(p->cg, result_ty); + } + then_store_ty = then_null ? type_ptr(p->pool, type_prim(p->pool, TY_VOID)) + : result_ty; memset(&fsd, 0, sizeof fsd); - fsd.type = result_ty; - fsd.size = c_abi_sizeof(p->abi, result_ty); - fsd.align = c_abi_alignof(p->abi, result_ty); + fsd.type = then_store_ty; + fsd.size = c_abi_sizeof(p->abi, then_store_ty); + fsd.align = c_abi_alignof(p->abi, then_store_ty); fsd.kind = FS_LOCAL; fsd.flags = FSF_NONE; tmp = cg_local(p->cg, &fsd); - cg_push_local_typed(p->cg, tmp, result_ty); + if (cg_top_type(p->cg) != then_store_ty) cg_convert(p->cg, then_store_ty); + cg_push_local_typed(p->cg, tmp, then_store_ty); cg_swap(p->cg); cg_store(p->cg); cg_drop(p->cg); diff --git a/src/opt/opt.c b/src/opt/opt.c @@ -1224,7 +1224,10 @@ static void w_func_end(CGTarget* t) { if (!o->f) return; opt_frame_home_addr_taken_locals(o->f); - if (o->level == 1) { + if (o->level >= 1) { + /* Full -O2 is still reserved design space. Until the SSA/value pipeline has + * a real lowering allocator, route public -O2 through the implemented O1 + * schedule instead of the incomplete SSA replay path. */ metrics_scope_begin(o->c, "opt.o1.total"); metrics_count(o->c, "opt.funcs", 1); metrics_count(o->c, "opt.blocks", o->f->nblocks); @@ -1305,10 +1308,6 @@ static void w_func_end(CGTarget* t) { opt_emit(o->c, o->f, o->target); metrics_scope_end(o->c, "opt.emit"); metrics_scope_end(o->c, "opt.o1.total"); - } else if (o->level >= 2) { - opt_build_cfg(o->f); - opt_build_ssa(o->f); - opt_replay(o->c, o->f, o->target); } else { opt_replay(o->c, o->f, o->target); } diff --git a/test/opt/o2_many_values.c b/test/opt/o2_many_values.c @@ -0,0 +1,12 @@ +int f(int a) { + int s = a; + s = s + 0; + s = s + 1; + s = s + 2; + s = s + 3; + s = s + 4; + s = s + 5; + s = s + 6; + s = s + 7; + return s; +} diff --git a/test/opt/run.sh b/test/opt/run.sh @@ -3,3 +3,9 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" "$ROOT/build/test/opt_test" + +mkdir -p "$ROOT/build/test/opt" +"$ROOT/build/cfree" cc -target aarch64-linux-gnu \ + -isystem "$ROOT/rt/include" -isystem "$ROOT/rt/include/libc" \ + -O2 -c "$ROOT/test/opt/o2_many_values.c" \ + -o "$ROOT/build/test/opt/o2_many_values.o" diff --git a/test/parse/cases/6_5_07_shift_u8_promote.c b/test/parse/cases/6_5_07_shift_u8_promote.c @@ -0,0 +1,14 @@ +typedef unsigned char u8; + +static int get2(const u8 *x) { return (x[0] << 8) | x[1]; } + +int test_main(void) { + u8 page[2]; + page[0] = 0x10; + page[1] = 0x00; + if (get2(page) != 4096) return 1; + page[0] = 0x0f; + page[1] = 0x94; + if (get2(page) != 3988) return 2; + return 42; +} diff --git a/test/parse/cases/6_5_07_shift_u8_promote.expected b/test/parse/cases/6_5_07_shift_u8_promote.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_15_02_conditional_uchar_eof.c b/test/parse/cases/6_5_15_02_conditional_uchar_eof.c @@ -0,0 +1,20 @@ +typedef unsigned long cfree_size_t; + +struct Zio { + cfree_size_t n; + const char *p; +}; + +static int fill(void) { + return -1; +} + +static int get(struct Zio *z) { + return ((z)->n-- > 0) ? (unsigned char)(*(z)->p++) : fill(); +} + +int test_main(void) { + struct Zio z = {0, ""}; + int c = get(&z); + return c == -1 ? 42 : c; +} diff --git a/test/parse/cases/6_5_15_02_conditional_uchar_eof.expected b/test/parse/cases/6_5_15_02_conditional_uchar_eof.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_15_ternary_null_func_ptr_slot.c b/test/parse/cases/6_5_15_ternary_null_func_ptr_slot.c @@ -0,0 +1,40 @@ +typedef int (*Callback)(int); + +struct WalkerLike { + void *parse; + Callback expr; + Callback select; + Callback select2; + int depth; + unsigned short code; + unsigned short flags; + void *ctx; +}; + +static int callback(int x) { return x + 7; } + +static int run_case(int no_select) { + struct WalkerLike w; + w.parse = (void *)0x12345678UL; + w.expr = callback; + w.select = no_select ? 0 : callback; + w.select2 = 0; + w.depth = 3; + w.code = 4; + w.flags = 5; + w.ctx = &w; + if (w.parse != (void *)0x12345678UL) return 1; + if (no_select) { + if (w.select != 0) return 2; + } else { + if (w.select != callback) return 3; + if (w.select(35) != 42) return 4; + } + return 42; +} + +int test_main(void) { + if (run_case(0) != 42) return 1; + if (run_case(1) != 42) return 2; + return 42; +} diff --git a/test/parse/cases/6_5_15_ternary_null_func_ptr_slot.expected b/test/parse/cases/6_5_15_ternary_null_func_ptr_slot.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_19b_post_inc_struct_ptr.c b/test/parse/cases/6_5_19b_post_inc_struct_ptr.c @@ -0,0 +1,12 @@ +struct Pair { + int a; + int b; +}; + +int test_main(void) { + struct Pair xs[3] = {{1, 2}, {3, 4}, {5, 6}}; + struct Pair *p = xs; + p++; + if (p != xs + 1) return 20; + return p->b; +} diff --git a/test/parse/cases/6_5_19b_post_inc_struct_ptr.expected b/test/parse/cases/6_5_19b_post_inc_struct_ptr.expected @@ -0,0 +1 @@ +4 diff --git a/test/parse/cases/6_5_2_2_09_funcptr_field_first_arg.c b/test/parse/cases/6_5_2_2_09_funcptr_field_first_arg.c @@ -0,0 +1,36 @@ +typedef unsigned long cfree_size_t; + +typedef void *(*AllocFn)(void *ud, void *ptr, cfree_size_t old_size, + cfree_size_t new_size); + +struct Global { + AllocFn alloc; + void *ud; +}; + +struct State { + struct Global *g; +}; + +static void *test_alloc(void *ud, void *ptr, cfree_size_t old_size, + cfree_size_t new_size) { + (void)ptr; + return old_size == 3 && new_size == 4 ? ud : (void *)0; +} + +static void *call_alloc(struct Global *g) { + return (*g->alloc)(g->ud, (void *)0, 3, 4); +} + +static void *state_alloc(struct State *s, cfree_size_t size, int tag) { + struct Global *g = s->g; + return (*g->alloc)(g->ud, (void *)0, tag, size); +} + +int test_main(void) { + struct Global g = {test_alloc, (void *)42}; + struct State s = {&g}; + void *p = call_alloc(&g); + void *q = state_alloc(&s, 4, 3); + return p == (void *)42 && q == (void *)42 ? 42 : 0; +} diff --git a/test/parse/cases/6_5_2_2_09_funcptr_field_first_arg.expected b/test/parse/cases/6_5_2_2_09_funcptr_field_first_arg.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_64_unsigned_size_division.c b/test/parse/cases/6_5_64_unsigned_size_division.c @@ -0,0 +1,17 @@ +typedef unsigned long cfree_size_t; + +struct Node { + char bytes[24]; +}; + +#define MAX_SIZE_T ((cfree_size_t)~(cfree_size_t)0) +#define LIMIT_N(n, t) \ + ((cfree_size_t)(n) <= MAX_SIZE_T / sizeof(t) ? (n) : \ + (unsigned int)(MAX_SIZE_T / sizeof(t))) + +int test_main(void) { + unsigned int n = 1u << 30; + unsigned int limit = LIMIT_N(n, struct Node); + if (limit != n) return 10; + return (MAX_SIZE_T / sizeof(struct Node)) > n ? 42 : 20; +} diff --git a/test/parse/cases/6_5_64_unsigned_size_division.expected b/test/parse/cases/6_5_64_unsigned_size_division.expected @@ -0,0 +1 @@ +42