commit fd17c4663d0fc98b08b76ea62c6810e5e8470cb6
parent 3164b12b2ead57f2e27ca6378976369d5c004373
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 12 Jun 2026 15:01:17 -0700
perf(c): fold constant field/element offsets into load/store displacement
A non-trivial lvalue load/store previously reduced the place to a single pointer
(materialize_lv_to_ptr) with the field offset baked in via ptr_to_int; +ofs;
int_to_ptr, then deref'd at 0 — so every p->field / s.member compiled to an
explicit address add plus a zero-offset load. The backend already supports
base+displacement addressing; the offset just never reached it.
pcg_lv_to_memop_place keeps a constant (scale==0, i32-range) offset as the deref
displacement instead: materialize the base pointer, reinterpret it to the field
type (a bitcast, free on the native backend now that same-width no-op casts
elide), then kit_cg_deref(ofs) so the place is INDIRECT[base + ofs] and the load
folds it into ldr w,[base,#ofs]. Indexed and out-of-range offsets keep the old
explicit-pointer fold. Used by both pcg_load and pcg_store.
On sqlite3.c -O0 (arm64-macOS) this raises base+displacement memops from 579 to
14770 (tcc emits 25566) and cuts object text another ~18% (2.97x -> 2.42x of
tcc's). Verified: test-toy 1392/0, test-parse-ok 3920/0, test-parse-err 129/0,
kit-built sqlite runs correctly.
Diffstat:
1 file changed, 38 insertions(+), 4 deletions(-)
diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c
@@ -453,6 +453,7 @@ static int pcg_lv_is_trivial_local(const PcgLvAux* lv) {
* into the address) as a single pointer rvalue. Defined below; the memops use
* it to build an explicit place before load/store. */
static void pcg_materialize_lv_to_ptr(Parser* p, const Type* result_ptr_ty);
+static void pcg_lv_to_memop_place(Parser* p, const Type* field_ty);
void pcg_push_local_typed(Parser* p, FrameSlot s, const Type* ty) {
if (pcg_emit_enabled(p)) kit_cg_push_local(p->cg, s);
@@ -490,8 +491,10 @@ void pcg_load(Parser* p) {
* then deref'd to a place. A bit-field place is then tagged with its bit
* geometry so the load extracts the field. */
if (!was_lvalue || !pcg_lv_is_trivial_local(lv)) {
- if (was_lvalue) pcg_materialize_lv_to_ptr(p, type_ptr(p->pool, ty));
- kit_cg_deref(p->cg, 0);
+ if (was_lvalue)
+ pcg_lv_to_memop_place(p, ty);
+ else
+ kit_cg_deref(p->cg, 0);
}
pcg_apply_bitfield(p, &bf);
kit_cg_load(p->cg, access);
@@ -572,6 +575,38 @@ static void pcg_materialize_lv_to_ptr(Parser* p, const Type* result_ptr_ty) {
}
}
+/* Build the PLACE a load/store reads, from the TOS lvalue, keeping a constant
+ * field/element offset as the deref displacement (so the backend folds it into
+ * the load/store: `ldr w, [base, #ofs]`) rather than baking it into an explicit
+ * `base + ofs` pointer the way pcg_materialize_lv_to_ptr + kit_cg_deref(0) does.
+ * Mirrors that pair's postcondition (CG TOS is the place; parser slot is the
+ * field pointer type with aux cleared). The pointer is reinterpreted to the
+ * field type with a bitcast — free on the native backend (a no-op same-width
+ * cast), an explicit cast on the C-source backend. Indexed (scale != 0) and
+ * out-of-displacement-range offsets fall back to the explicit-pointer fold.
+ * Caller guarantees emit is enabled. */
+static void pcg_lv_to_memop_place(Parser* p, const Type* field_ty) {
+ PcgLvAux* lv = pcg_top_lv_aux(p);
+ PcgLvBaseKind base_kind =
+ lv ? (PcgLvBaseKind)lv->base_kind : PCG_LV_BASE_LOCAL;
+ i64 ofs = lv ? lv->offset : 0;
+ u32 scale = lv ? lv->scale : 0u;
+ const Type* fptr = type_ptr(p->pool, field_ty);
+ if (scale != 0u || ofs < INT32_MIN || ofs > INT32_MAX) {
+ pcg_materialize_lv_to_ptr(p, fptr);
+ kit_cg_deref(p->cg, 0);
+ return;
+ }
+ if (base_kind == PCG_LV_BASE_LOCAL) kit_cg_addr(p->cg);
+ kit_cg_bitcast(p->cg, pcg_tid(p, fptr));
+ kit_cg_deref(p->cg, ofs);
+ pcg_retag_top(p, fptr);
+ {
+ PcgLvAux* out = pcg_top_lv_aux(p);
+ if (out) out->base_kind = PCG_LV_BASE_POINTER_RV;
+ }
+}
+
void pcg_addr(Parser* p) {
const Type* ty = pcg_top_type(p);
pcg_materialize_lv_to_ptr(p, type_ptr(p->pool, ty));
@@ -671,8 +706,7 @@ void pcg_store(Parser* p) {
* deref'd to the PLACE the strict store requires, then tagged with the
* bit-field geometry so the store inserts the field. */
pcg_drop_type(p);
- pcg_materialize_lv_to_ptr(p, type_ptr(p->pool, lv_ty)); /* [dst_ptr] */
- kit_cg_deref(p->cg, 0); /* [dst_place] */
+ pcg_lv_to_memop_place(p, lv_ty); /* [dst_place] */
pcg_apply_bitfield(p, &bf);
pcg_push_type(p, rv_ty);
}