kit

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

commit d08e794c51bffbb4075e83df6dcb0d1bb3f2fc64
parent b8de5c0b45f3724531f7f6a797f82cb719da4e87
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  2 Jun 2026 04:47:01 -0700

cg: bitfield as a PLACE subkind, drop the MemAccess bitfield rider (Track 3b)

Diffstat:
Minclude/cfree/cg.h | 41++++++++++++++++++++++++++++++-----------
Mlang/c/parse/cg_adapter.c | 42++++++++++++++++++++++++++----------------
Msrc/cg/control.c | 30+++++++++++++++++++++++++-----
Msrc/cg/internal.h | 19+++++++++++++++++++
Msrc/cg/memory.c | 26++++----------------------
Msrc/cg/value.c | 34+++++++++++++++++++++++++++++++---
6 files changed, 135 insertions(+), 57 deletions(-)

diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -257,13 +257,6 @@ typedef struct CfreeCgMemAccess { uint32_t align; /* 0 = natural for type */ uint32_t address_space; /* normally inherited from pointer type */ uint32_t flags; /* CfreeCgMemAccessFlag */ - /* Bit-field metadata. When bit_width != 0 the memop performs a bit-field - * access at bit range [bit_offset, bit_offset + bit_width) within a - * storage_size-byte container located at the EA. */ - uint16_t bit_offset; - uint16_t bit_width; - uint32_t storage_size; - int bit_signed; } CfreeCgMemAccess; /* ============================================================ @@ -625,9 +618,33 @@ CFREE_API void cfree_cg_deref(CfreeCg*, int64_t offset); /* PLACE(record) -> PLACE(field): project a record place to one of its fields by * index; the byte offset and field type come from the record's layout (CG owns * layout). Stack: [place] -> [place]. Errors if TOS is not a record PLACE — for - * `p->f` the frontend does `deref; field`, not `field` on a pointer. */ + * `p->f` the frontend does `deref; field`, not `field` on a pointer. + * + * Bit-field PLACE subkind: when field_index names a bit-field, `field` projects + * to a *bit-field place* — a PLACE that addresses the field's storage unit and + * additionally carries the bit-field descriptor (bit offset/width, storage-unit + * size, signedness) from the record layout. A bit-field place is a place like + * any other: a plain `load` reads the field (extract + sign/zero-extend) and a + * plain `store` writes it (read-modify-write insert). There is no separate + * bit-field memop and no bit-field rider on CfreeCgMemAccess; the place is the + * one and only carrier of bit-field geometry. `addr` and aggregate access on a + * bit-field place are not legal (a bit-field has no addressable byte location); + * the frontend rejects `&` / `sizeof` on a bit-field before reaching CG. */ CFREE_API void cfree_cg_field(CfreeCg*, uint32_t field_index); +/* PLACE -> PLACE(bit-field): tag the TOS place as a bit-field place carrying the + * given bit geometry. This is the manual-addressing counterpart to the implicit + * bit-field projection `field` performs: a frontend that builds the storage-unit + * place itself (deref/elem onto the storage unit) calls this to attach the + * descriptor, after which a plain load/store extracts/inserts the field. The + * place must address the field's storage unit. bit_storage_size is the storage- + * unit size in bytes; bit_signed selects signed extraction on load. Stack: + * [place] -> [place]. There is no separate bit-field memop — the place is the + * one carrier of bit-field geometry. */ +CFREE_API void cfree_cg_field_bits(CfreeCg*, uint16_t bit_offset, + uint16_t bit_width, uint32_t bit_storage_size, + int bit_signed); + /* (VALUE(ptr to T), index VALUE) -> PLACE(T): the element at `*(base + * index*sizeof(T) + offset)`. The index is scaled by sizeof(T) (the pointer's * pointee) and the constant `offset` byte displacement is folded in, so the @@ -639,9 +656,11 @@ CFREE_API void cfree_cg_elem(CfreeCg*, int64_t offset); /* Load a VALUE from a PLACE / store a VALUE into a PLACE. The PLACE carries all * addressing (built via push_local / deref / field / elem); the memop takes no - * effective-address rider. Bit-field metadata rides in `access`. Both error if - * the addressed operand is not a PLACE — a pointer VALUE must be `deref`'d - * first. + * effective-address rider. When the PLACE is a bit-field place (produced by + * `field` on a bit-field), the load/store extract/insert the field per the + * descriptor the place carries — `access` then describes only the access type, + * alignment, and volatility, never bit geometry. Both error if the addressed + * operand is not a PLACE — a pointer VALUE must be `deref`'d first. * * Stack effects: * load: [place] -> [value] diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -437,19 +437,16 @@ void pcg_push_float(Parser* p, double v, const Type* ty) { pcg_push_type(p, ty); } -/* Fill `access` (CfreeCgMemAccess) for a memop against an lvalue carrying the - * given aux. Bit-field metadata rides in the access; addressing is no longer a - * rider — it is built explicitly with deref/elem/field before the load/store. */ -static CfreeCgMemAccess pcg_access_for(Parser* p, const Type* access_ty, - const PcgLvAux* lv) { - CfreeCgMemAccess access = pcg_mem(p, access_ty); +/* Tag the TOS place as a bit-field place when `lv` carries bit-field geometry. + * The frontend builds the storage-unit place itself (materialize + deref), so it + * attaches the descriptor here; a plain load/store then extracts/inserts the + * field. Must be called on the place each time a fresh one is built (each deref + * yields a plain place). No-op for non-bit-field lvalues. */ +static void pcg_apply_bitfield(Parser* p, const PcgLvAux* lv) { if (lv && lv->bit_width) { - access.bit_offset = lv->bit_offset; - access.bit_width = lv->bit_width; - access.storage_size = lv->storage_size; - access.bit_signed = lv->bit_signed; + cfree_cg_field_bits(p->cg, lv->bit_offset, lv->bit_width, lv->storage_size, + lv->bit_signed); } - return access; } /* A trivially-addressable lvalue: a bare local slot with no pending field @@ -493,15 +490,19 @@ void pcg_load(Parser* p) { int was_lvalue = pcg_top_is_lvalue(p); if (pcg_emit_enabled(p)) { PcgLvAux* lv = pcg_top_lv_aux(p); - CfreeCgMemAccess access = pcg_access_for(p, ty, lv); + CfreeCgMemAccess access = pcg_mem(p, ty); + /* Snapshot bit-field geometry before materialize clears the aux. */ + PcgLvAux bf = lv ? *lv : (PcgLvAux){0}; /* Build the PLACE the strict load requires. A trivial local already has its * PLACE on the CG stack (push_local) and loads directly; anything else is * reduced to a single pointer (materialize, or a pointer-rvalue base) and - * then deref'd to a place. */ + * 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)); cfree_cg_deref(p->cg, 0); } + pcg_apply_bitfield(p, &bf); cfree_cg_load(p->cg, access); } if (was_lvalue && p->cg_type_sp) { @@ -634,11 +635,13 @@ void pcg_store(Parser* p) { int emit = pcg_emit_enabled(p); /* The aux to consume lives on the lvalue slot at parser depth 1. */ PcgLvAux* lv = pcg_lv_aux_at(p, 1); + /* Snapshot bit-field geometry before materialize clears the aux. */ + PcgLvAux bf = lv ? *lv : (PcgLvAux){0}; CfreeCgMemAccess access; if (rv_ty && type_is_ptr(rv_ty) && (!lv_ty || !type_is_ptr(lv_ty))) { mem_ty = rv_ty; } - access = pcg_access_for(p, mem_ty ? mem_ty : rv_ty, lv); + access = pcg_mem(p, mem_ty ? mem_ty : rv_ty); if (emit) { int wide = rv_ty && (rv_ty->kind == TY_INT128 || rv_ty->kind == TY_UINT128 || rv_ty->kind == TY_LDOUBLE); @@ -669,10 +672,12 @@ void pcg_store(Parser* p) { if (!trivial) { /* Materialize operates on the parser-TOS lvalue; drop the rv type slot * so the lvalue is on top and matches the CG [base..]. The pointer is - * deref'd to the PLACE the strict store requires. */ + * 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] */ cfree_cg_deref(p->cg, 0); /* [dst_place] */ + pcg_apply_bitfield(p, &bf); pcg_push_type(p, rv_ty); } cfree_cg_push_local(p->cg, tmp); @@ -927,7 +932,9 @@ void pcg_inc_dec(Parser* p, BinOp op, int post) { { CfreeCgIntBinOp cg_op = pcg_int_binop(op); PcgLvAux* lv = pcg_top_lv_aux(p); - CfreeCgMemAccess access = pcg_access_for(p, ty, lv); + /* Snapshot bit-field geometry before materialize clears the aux. */ + PcgLvAux bf = lv ? *lv : (PcgLvAux){0}; + CfreeCgMemAccess access = pcg_mem(p, ty); const Type* step_ty = ty; u32 step = 1; if (ty && ty->kind == TY_PTR) { @@ -953,6 +960,7 @@ void pcg_inc_dec(Parser* p, BinOp op, int post) { tmp = pcg_local(p, &fsd); cfree_cg_dup(p->cg); /* [ptr, ptr] */ cfree_cg_deref(p->cg, 0); /* [ptr, place] */ + pcg_apply_bitfield(p, &bf); cfree_cg_load(p->cg, access); /* [ptr, old] */ if (post) { /* Stash old, compute new, store, then re-load old as result. */ @@ -963,6 +971,7 @@ void pcg_inc_dec(Parser* p, BinOp op, int post) { pcg_emit_inc_step(p, ty, op, cg_op, step_ty, step); /* [ptr, new] */ cfree_cg_swap(p->cg); /* [new, ptr] */ cfree_cg_deref(p->cg, 0); /* [new, place] */ + pcg_apply_bitfield(p, &bf); cfree_cg_swap(p->cg); /* [place, new] */ cfree_cg_store(p->cg, access); /* [] */ cfree_cg_push_local(p->cg, tmp); @@ -976,6 +985,7 @@ void pcg_inc_dec(Parser* p, BinOp op, int post) { cfree_cg_store(p->cg, r_access); /* [ptr, new] */ cfree_cg_swap(p->cg); /* [new, ptr] */ cfree_cg_deref(p->cg, 0); /* [new, place] */ + pcg_apply_bitfield(p, &bf); cfree_cg_swap(p->cg); /* [place, new] */ cfree_cg_store(p->cg, access); /* [] */ cfree_cg_push_local(p->cg, tmp); diff --git a/src/cg/control.c b/src/cg/control.c @@ -966,13 +966,17 @@ void cfree_cg_field(CfreeCg* g, uint32_t field_index) { 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. */ + /* Project to a bit-field PLACE: the place addresses the enclosing storage + * unit and carries the bit-field geometry from the record layout. A plain + * load/store on this place performs the extract/insert; there is no separate + * bit-field memop and no bit-field rider on CfreeCgMemAccess. */ base_addr = api_lvalue_addr(g, &base, rec_ptr_ty); sv = api_make_lv(base_addr, field_ty); + sv.bitfield.bit_offset = layout->fields[field_index].bit_offset; + sv.bitfield.bit_width = layout->fields[field_index].bit_width; + sv.bitfield.bit_storage_size = layout->fields[field_index].storage_size; + sv.bitfield.bit_signed = + rec_info->record.fields[field_index].bit_signed ? 1u : 0u; api_release(g, &base); api_push(g, sv); return; @@ -1010,6 +1014,22 @@ void cfree_cg_field(CfreeCg* g, uint32_t field_index) { } } +void cfree_cg_field_bits(CfreeCg* g, uint16_t bit_offset, uint16_t bit_width, + uint32_t bit_storage_size, int bit_signed) { + ApiSValue* top; + if (!g || g->sp == 0) return; + top = &g->stack[g->sp - 1u]; + if (!api_is_lvalue_sv(top)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: field_bits requires a place destination"); + return; + } + top->bitfield.bit_offset = bit_offset; + top->bitfield.bit_width = bit_width; + top->bitfield.bit_storage_size = bit_storage_size; + top->bitfield.bit_signed = bit_signed ? 1u : 0u; +} + /* ============================================================ * Calls / return * ============================================================ */ diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -59,6 +59,19 @@ typedef struct ApiDelayedArith { u8 pad; } ApiDelayedArith; +/* Bit-field geometry carried by a bit-field PLACE. `cfree_cg_field` fills this + * from the record layout when it projects to a bit-field; a plain load/store on + * the carrying place then performs the extract/insert. The storage MemAccess is + * rebuilt from the place operand + field type at load/store time, so only the + * bit geometry needs to ride on the place. width == 0 means "not a bit-field". */ +typedef struct ApiBitField { + u16 bit_offset; /* target-endian bit offset within the storage unit */ + u16 bit_width; /* 0 => the place is not a bit-field place */ + u32 bit_storage_size; /* storage-unit size in bytes */ + u8 bit_signed; /* signed extraction on load */ + u8 pad[3]; +} ApiBitField; + typedef struct ApiSValue { Operand op; union { @@ -71,6 +84,7 @@ typedef struct ApiSValue { u8 pinned; u8 lvalue; CfreeCgLocal source_local; + ApiBitField bitfield; /* bit_width != 0 marks a bit-field PLACE subkind */ } ApiSValue; #define API_CG_STACK_INITIAL 16u @@ -261,6 +275,8 @@ void cfree_cg_push_label_addr(CfreeCg* g, CfreeCgLabel label, void cfree_cg_computed_goto(CfreeCg* g, const CfreeCgLabel* valid_targets, uint32_t ntargets); +void cfree_cg_field_bits(CfreeCg* g, uint16_t bit_offset, uint16_t bit_width, + uint32_t bit_storage_size, int bit_signed); void cfree_cg_unreachable(CfreeCg* g); CfreeCgScope api_scope_handle(u32 idx, u32 generation); ApiCgScope* api_scope_from_handle(CfreeCg* g, CfreeCgScope scope, @@ -384,6 +400,9 @@ int api_operand_can_address(const Operand* o); int api_sv_op_is(const ApiSValue* sv, OpKind kind); int api_sv_op_is_local_or_imm(const ApiSValue* sv); int api_is_lvalue_sv(const ApiSValue* sv); +int api_sv_is_bitfield(const ApiSValue* sv); +BitFieldAccess api_bitfield_access(CfreeCg* g, const ApiSValue* sv, + const Operand* storage, CfreeCgTypeId field_ty); void api_stack_grow(CfreeCg* g, u32 want); void api_push(CfreeCg* g, ApiSValue v); ApiSValue api_pop(CfreeCg* g); diff --git a/src/cg/memory.c b/src/cg/memory.c @@ -142,22 +142,6 @@ void cfree_cg_push_symbol_addr(CfreeCg* g, CfreeCgSym sym, int64_t addend) { } -/* Build a BitFieldAccess descriptor from the CfreeCgMemAccess metadata. */ -static BitFieldAccess bf_from_access(CfreeCg* g, CfreeCgMemAccess access, - CfreeCgTypeId field_ty, - const Operand* addr) { - BitFieldAccess bf; - memset(&bf, 0, sizeof bf); - bf.field_type = field_ty; - bf.storage = api_mem_for_lvalue(g, addr, field_ty); - if (access.storage_size) bf.storage.size = access.storage_size; - bf.storage_offset = 0; - bf.bit_offset = access.bit_offset; - bf.bit_width = access.bit_width; - bf.signed_ = access.bit_signed ? 1 : 0; - return bf; -} - static int api_sv_local_storage_is_aggregate(CfreeCg* g, const ApiSValue* sv) { ApiSourceLocal* rec; if (!sv || sv->op.kind != OPK_LOCAL || @@ -203,13 +187,12 @@ void cfree_cg_load(CfreeCg* g, CfreeCgMemAccess access) { T = g->target; if (access.flags & CFREE_CG_MEM_VOLATILE) api_local_const_memory_boundary(g); - is_bitfield = (access.bit_width != 0); - base = api_pop(g); if (!api_is_lvalue_sv(&base)) { compiler_panic(g->c, g->cur_loc, "CfreeCg: load requires a place; deref the pointer first"); } + is_bitfield = api_sv_is_bitfield(&base); /* Aggregate place: an aggregate-typed access returns the place itself; a * scalar access reads a scalar sub-object and falls through. */ @@ -273,7 +256,7 @@ void cfree_cg_load(CfreeCg* g, CfreeCgMemAccess access) { } if (is_bitfield) { - BitFieldAccess bf = bf_from_access(g, access, access_ty, &mem_op); + BitFieldAccess bf = api_bitfield_access(g, &base, &mem_op, access_ty); CGLocal rr = api_alloc_temp_local(g, access_ty); dst = api_op_local(rr, access_ty); T->bitfield_load(T, dst, mem_op, bf); @@ -359,8 +342,6 @@ void cfree_cg_store(CfreeCg* g, CfreeCgMemAccess access) { T = g->target; if (access.flags & CFREE_CG_MEM_VOLATILE) api_local_const_memory_boundary(g); - is_bitfield = (access.bit_width != 0); - /* Stack: [base, value] - pop value, then base. */ rv = api_pop(g); base = api_pop(g); @@ -371,6 +352,7 @@ void cfree_cg_store(CfreeCg* g, CfreeCgMemAccess access) { "CfreeCg: store requires a place destination; deref first"); return; } + is_bitfield = api_sv_is_bitfield(&base); ty = api_mem_access_type(g, access, api_sv_type(&base), "store"); access_ty = ty; @@ -501,7 +483,7 @@ void cfree_cg_store(CfreeCg* g, CfreeCgMemAccess access) { } if (is_bitfield) { - BitFieldAccess bf = bf_from_access(g, access, access_ty, &mem_op); + BitFieldAccess bf = api_bitfield_access(g, &base, &mem_op, access_ty); T->bitfield_store(T, mem_op, src, bf); } else { T->store(T, mem_op, src, api_mem_from_access(g, &mem_op, access)); diff --git a/src/cg/value.c b/src/cg/value.c @@ -132,14 +132,42 @@ int api_sv_op_is_local_or_imm(const ApiSValue* sv) { * 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. */ + * a bit-field place is an ordinary addressable place (it addresses the storage + * unit) that additionally carries a bit descriptor — see api_sv_is_bitfield and + * 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->kind == SV_OPERAND && api_operand_can_address(&sv->op); } +/* A bit-field PLACE is a PLACE that additionally carries bit geometry (set by + * cfree_cg_field when it projects to a bit-field). The plain load/store detect + * this and perform the extract/insert; there is no separate bit-field memop. */ +int api_sv_is_bitfield(const ApiSValue* sv) { + return sv->bitfield.bit_width != 0; +} + +/* Rebuild the full BitFieldAccess the extract/insert helper consumes from the + * geometry the place carries plus the storage operand it addresses. The storage + * MemAccess is derived from the operand + field type, the way the old + * bf_from_access did, so the place need only carry the bit geometry. */ +BitFieldAccess api_bitfield_access(CfreeCg* g, const ApiSValue* sv, + const Operand* storage, + CfreeCgTypeId field_ty) { + BitFieldAccess bf; + memset(&bf, 0, sizeof bf); + bf.field_type = field_ty; + bf.storage = api_mem_for_lvalue(g, storage, field_ty); + if (sv->bitfield.bit_storage_size) + bf.storage.size = sv->bitfield.bit_storage_size; + bf.storage_offset = 0; + bf.bit_offset = sv->bitfield.bit_offset; + bf.bit_width = sv->bitfield.bit_width; + bf.signed_ = sv->bitfield.bit_signed ? 1 : 0; + return bf; +} + void api_stack_grow(CfreeCg* g, u32 want) { Heap* h = g->c->ctx->heap; u32 cap = g->cap;