kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 796293a771df0878c28f90f91e19fca054054552
parent 5098244ac67063d5fd9879937551d40974fa6137
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 12 Jun 2026 10:23:01 -0700

fix(c): aggregate-typed conditional with mismatched qualifiers

A struct-typed ?: whose arms differ only in qualification (e.g. a const
struct lvalue '*top' from a const T* vs a plain struct subobject
's->stop.regs') reconciled the arms with pcg_convert, which had no
aggregate case and fell into the scalar bitcast path — pushing the
aggregate as a value. The CG rejects that ('aggregate must be a place');
on aarch64 it surfaced as 'scalar too large'. The trigger is exactly
src/dbg/symbolic.c's 'start = top ? *top : s->stop.regs'.

C has no struct/union conversion, so guard pcg_convert: an aggregate on
the stack only reconciles a qualifier difference — retag the place and
emit nothing.

Diffstat:
Mlang/c/parse/cg_adapter.c | 12++++++++++++
Atest/parse/cases/6_5_15_06_cond_struct_qualifier_mismatch.c | 43+++++++++++++++++++++++++++++++++++++++++++
Atest/parse/cases/6_5_15_06_cond_struct_qualifier_mismatch.expected | 1+
3 files changed, 56 insertions(+), 0 deletions(-)

diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -846,6 +846,18 @@ void pcg_convert(Parser* p, const Type* dst) { KitCgTypeId id = pcg_tid(p, dst); int emit = pcg_emit_enabled(p); if (src == dst) return; + /* C has no struct/union conversion: a pcg_convert reaching here with an + * aggregate on the stack is only reconciling a qualifier difference between + * two compatible types — e.g. the arms of a `?:` where one is a `const` + * struct lvalue (`*top`, top a `const T*`) and the other is not. The + * representation is identical, so retag the place to dst and emit nothing. + * Falling through would hand the aggregate to the scalar bitcast path, which + * pushes it as a value — illegal for an aggregate (api_push rejects it). The + * flag-preserving retag keeps it a place so the caller can copy it. */ + if (src->kind == TY_STRUCT || src->kind == TY_UNION) { + pcg_retag_keep_flags(p, 0, dst); + return; + } /* Conversion to _Bool is "value != 0", not a truncation: a value whose set * bits all lie above the bool storage width (e.g. 256, or the sign bit of a * 128-bit operand) must still become 1. Emit an explicit compare-against-zero diff --git a/test/parse/cases/6_5_15_06_cond_struct_qualifier_mismatch.c b/test/parse/cases/6_5_15_06_cond_struct_qualifier_mismatch.c @@ -0,0 +1,43 @@ +/* §6.5.15: when both operands of a conditional operator are structures, the + * result has their common, unqualified structure type. The arms may differ in + * qualification — e.g. a `const` struct lvalue reached through a `const T*` + * (`*cp`) versus a plain struct subobject (`s->n.f`). + * + * Regression: the parser reconciled the two arms with pcg_convert, which — + * having no aggregate case — fell into the scalar bitcast path and pushed the + * struct as a value. The CG rejects that ("aggregate must be a place, not a + * value"); on aarch64 it instead surfaced as "scalar too large". The trigger + * is exactly src/dbg/symbolic.c's `start = top ? *top : s->stop.regs`. */ +typedef struct { + int a, b; +} P; +struct N { + P f; +}; +struct S { + struct N n; +}; + +/* const-deref arm vs nested-member arm (the symbolic.c shape). */ +static int pick(const P* cp, struct S* s, int useptr) { + P r = useptr ? *cp : s->n.f; + return r.a * 10 + r.b; +} + +/* const-deref arm vs non-const-deref arm: same qualifier mismatch, both arms + * materialized. */ +static int pick2(const P* cp, P* np, int useconst) { + P r = useconst ? *cp : *np; + return r.a * 100 + r.b; +} + +int test_main(void) { + P p = {3, 4}; + P q = {7, 8}; + struct S s = {{{5, 6}}}; + int viaptr = pick(&p, &s, 1); /* *cp -> 34 */ + int viamem = pick(&p, &s, 0); /* s->n.f -> 56 */ + int c = pick2(&p, &q, 1); /* *cp -> 304 */ + int n = pick2(&p, &q, 0); /* *np -> 708 */ + return (viaptr == 34 && viamem == 56 && c == 304 && n == 708) ? 0 : 1; +} diff --git a/test/parse/cases/6_5_15_06_cond_struct_qualifier_mismatch.expected b/test/parse/cases/6_5_15_06_cond_struct_qualifier_mismatch.expected @@ -0,0 +1 @@ +0