commit a0397c63a404f4589469e97260336eeaffc04549
parent dce052f5cd1a9b361a65cb2e7573b35c0f6fd85f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 2 Jun 2026 03:32:26 -0700
cg: explicit PLACE predicate + forbid aggregate VALUEs (Track 7.1/7.2)
Diffstat:
3 files changed, 27 insertions(+), 16 deletions(-)
diff --git a/src/cg/control.c b/src/cg/control.c
@@ -961,24 +961,18 @@ void cfree_cg_field(CfreeCg* g, uint32_t field_index) {
0) {
Operand base_addr;
ApiSValue sv;
- BitFieldAccess bf;
if (layout->fields[field_index].bit_width == 0) {
compiler_panic(g->c, g->cur_loc, "CfreeCg: zero-width bit-field access");
api_release(g, &base);
return;
}
+ /* Push the record-base address as a place of the field type. The bit-field
+ * geometry (storage size/offset, bit offset/width, signedness) is carried
+ * by the CfreeCgMemAccess the frontend supplies on the following load/store,
+ * which rebuilds the descriptor via bf_from_access; the place itself only
+ * needs to address the enclosing storage. */
base_addr = api_lvalue_addr(g, &base, rec_ptr_ty);
- memset(&bf, 0, sizeof bf);
- bf.field_type = field_ty;
- bf.storage = api_mem_for_lvalue(g, &base_addr, field_ty);
- bf.storage.size = layout->fields[field_index].storage_size;
- bf.storage_offset = layout->fields[field_index].offset;
- bf.bit_offset = layout->fields[field_index].bit_offset;
- bf.bit_width = layout->fields[field_index].bit_width;
- bf.signed_ = rec_info->record.fields[field_index].bit_signed != 0;
sv = api_make_lv(base_addr, field_ty);
- sv.bitfield_lvalue = 1;
- sv.delayed.bitfield = bf;
api_release(g, &base);
api_push(g, sv);
return;
diff --git a/src/cg/internal.h b/src/cg/internal.h
@@ -64,14 +64,12 @@ typedef struct ApiSValue {
union {
ApiDelayedCmp cmp;
ApiDelayedArith arith;
- BitFieldAccess bitfield;
} delayed;
CfreeCgTypeId type;
u8 kind;
u8 res;
u8 pinned;
u8 lvalue;
- u8 bitfield_lvalue;
CfreeCgLocal source_local;
} ApiSValue;
diff --git a/src/cg/value.c b/src/cg/value.c
@@ -125,10 +125,19 @@ int api_sv_op_is_local_or_imm(const ApiSValue* sv) {
(sv->op.kind == OPK_IMM || sv->op.kind == OPK_LOCAL);
}
+/* A PLACE (lvalue) is exactly an SV_OPERAND whose operand addresses storage:
+ * op.kind in { OPK_LOCAL, OPK_GLOBAL, OPK_INDIRECT } (api_operand_can_address).
+ * This is a kind-based predicate, not a heuristic: the `lvalue` flag marks the
+ * value as a place, SV_OPERAND guarantees `op` is meaningful (SV_CMP/SV_ARITH
+ * never carry lvalue=1 — see fold.c, which clears it on materialization), and
+ * the operand kind decides addressability. The former heuristic OR'd in
+ * `bitfield_lvalue` and a (source_local && OPK_LOCAL) term, both subsumed here:
+ * a bitfield lvalue is built on the OPK_LOCAL address returned by
+ * api_lvalue_addr (see cfree_cg_field), and source_local only co-occurs with
+ * OPK_LOCAL — so api_operand_can_address already covers both. */
int api_is_lvalue_sv(const ApiSValue* sv) {
- return sv->lvalue &&
- (sv->bitfield_lvalue || api_operand_can_address(&sv->op) ||
- (sv->source_local != CFREE_CG_LOCAL_NONE && sv->op.kind == OPK_LOCAL));
+ return sv->lvalue && sv->kind == SV_OPERAND &&
+ api_operand_can_address(&sv->op);
}
void api_stack_grow(CfreeCg* g, u32 want) {
@@ -147,6 +156,16 @@ void api_stack_grow(CfreeCg* g, u32 want) {
}
void api_push(CfreeCg* g, ApiSValue v) {
+ /* An aggregate (record) can only ever be a PLACE: it is addressed, loaded,
+ * and passed by SRET/BYVAL/BYREF, never materialized as a scalar VALUE. Catch
+ * any aggregate VALUE at the point it would enter the stack. i128/f128 are
+ * scalars (cg_type_is_aggregate is false for them), so they remain valid
+ * VALUEs and are unaffected. */
+ if (cg_type_is_aggregate(g->c, api_sv_type(&v)) && !api_is_lvalue_sv(&v)) {
+ compiler_panic(g->c, g->cur_loc,
+ "CfreeCg: aggregate must be a place, not a value; load the "
+ "place or pass it by reference");
+ }
api_stack_grow(g, g->sp + 1);
g->stack[g->sp++] = v;
}