kit

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

commit 491137be4f696a8ca5f6cb75baaf0970c5248ced
parent 75e2d6ceff7bb7453a88eb26e6f3156040a1a6f4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 18:34:24 -0700

Complete C11 bit-field and static initializer semantics

Diffstat:
Mdoc/C11_CONFORMANCE_CHECKLIST.md | 23++++++++++++++++-------
Minclude/cfree/cg.h | 10++++++++++
Mlang/c/abi/c_abi.c | 5++++-
Mlang/c/parse/parse_init.c | 282++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mlang/c/type/type.c | 26++++++++++++++++++++++++++
Msrc/abi/abi.c | 57+++++++++++++++------------------------------------------
Msrc/api/cg.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/api/cg_type.h | 5+++++
Mtest/api/cg_type_test.c | 25+++++++++++++++++++++++++
Mtest/parse/CORPUS.md | 3+++
Atest/parse/cases/6_7_2_1_10_static_bitfield_pack.c | 8++++++++
Atest/parse/cases/6_7_2_1_10_static_bitfield_pack.expected | 1+
Atest/parse/cases/6_7_9_22_static_union_designated_nonfirst.c | 8++++++++
Atest/parse/cases/6_7_9_22_static_union_designated_nonfirst.expected | 1+
Atest/parse/cases/6_7_9_23_static_ptr_null_const_expr.c | 5+++++
Atest/parse/cases/6_7_9_23_static_ptr_null_const_expr.expected | 1+
Atest/parse/cases_err/6_7_9_static_ptr_nonzero.c | 5+++++
Atest/parse/cases_err/6_7_9_static_ptr_nonzero.errpat | 1+
18 files changed, 479 insertions(+), 106 deletions(-)

diff --git a/doc/C11_CONFORMANCE_CHECKLIST.md b/doc/C11_CONFORMANCE_CHECKLIST.md @@ -132,13 +132,18 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse New coverage: invalid variadic placement, function returning function, and function returning array all pass as diagnostics; positive function parameter adjustment is covered by `6_7_6_14_func_param_adjust`. -- [ ] Decide and document implementation-defined bit-field behavior: +- [x] Decide and document implementation-defined bit-field behavior: plain `int` signedness, allowed extended bit-field types, allocation order, straddling, and alignment. -- [ ] Add positive bit-field lowering cases from `test/parse/CORPUS.md`, +- [x] Add positive bit-field lowering cases from `test/parse/CORPUS.md`, including zero-width bit-fields. Positive bit-field, signed bit-field, zero-width, and `_Bool` bit-field - cases pass; `float` bit-field rejection now passes. + cases pass; `float` bit-field rejection now passes. Current policy: + plain `int` bit-fields are signed, integer types accepted by the + frontend are accepted as bit-field base types, allocation proceeds from + low to high bit offsets within little-endian storage units, zero-width + fields force a fresh storage unit aligned as their declared type, and + fields do not straddle storage units. ## Expressions and conversions @@ -184,14 +189,18 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse `parse_expr.c`. - [x] Accept `_Alignof` in integer constant expressions. Positive array-bound coverage passes. -- [ ] Generalize constant-expression classification beyond integer ICE call +- [x] Generalize constant-expression classification beyond integer ICE call sites so arithmetic constants, address constants, null pointer constants, and static initializer validation share one semantic evaluator. -- [ ] Complete static initializer address constants: + Static scalar, pointer, address, null pointer, and bit-field + initializers now flow through one `CStaticConst` classifier layered on + the typed integer constant evaluator. +- [x] Complete static initializer address constants: object address, function address, array plus/minus integer constant, and null pointer constants. - Positive object/function/array-plus-integer address constants pass. -- [ ] Implement static-storage union initialization or document a temporary + Positive object/function/array-plus-integer address constants pass, and + null pointer constants now include full integer constant expressions. +- [x] Implement static-storage union initialization or document a temporary nonconformance gate. Positive non-first union designated initializer passes. - [ ] Complete designated initializers: diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -109,8 +109,18 @@ typedef struct CfreeCgField { CfreeSym name; /* 0 for anonymous fields/tuple elements */ CfreeCgTypeId type; uint32_t align_override; /* 0 = natural, 1 = packed, >1 explicit align */ + uint32_t flags; /* CfreeCgFieldFlag */ + uint16_t bit_width; /* valid for bit-fields, may be 0 for barriers */ + uint16_t bit_offset; /* filled by record-field queries */ + uint32_t bit_storage_size; /* bytes, filled by record-field queries */ + int bit_signed; /* signed extraction for bit-field loads */ } CfreeCgField; +typedef enum CfreeCgFieldFlag { + CFREE_CG_FIELD_BITFIELD = 1u << 0, + CFREE_CG_FIELD_ZERO_WIDTH = 1u << 1, +} CfreeCgFieldFlag; + typedef struct CfreeCgEnumValue { CfreeSym name; uint64_t value; /* bit pattern interpreted using the enum's integer base */ diff --git a/lang/c/abi/c_abi.c b/lang/c/abi/c_abi.c @@ -78,9 +78,12 @@ const ABIRecordLayout* c_abi_record_layout(TargetABI* a, Pool* p, memset(&f, 0, sizeof(f)); if (cfree_cg_type_record_field(a, id, i, &f, &off) != 0) return NULL; fl[i].offset = (u32)off; - fl[i].storage_size = (u32)cfree_cg_type_size(a, f.type); + fl[i].storage_size = f.bit_storage_size + ? f.bit_storage_size + : (u32)cfree_cg_type_size(a, f.type); if (t->rec.fields[i].flags & FIELD_BITFIELD) { fl[i].bit_width = t->rec.fields[i].bitfield_width; + fl[i].bit_offset = f.bit_offset; } } } diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -6,7 +6,7 @@ * push_subobject_lv, emit_copy_leaf, emit_walk_copy, * emit_struct_copy_into_slot, zero_init_at) * - Static-storage object definition (parse_static_init_at, - * parse_static_string_at, try_parse_addr_const, encode_int_le, + * parse_static_string_at, parse_static_const, encode_int_le, * pick_object_section, define_static_object, srl_push) */ @@ -60,6 +60,16 @@ void push_subobject_lv(Parser* p, FrameSlot slot, const Type* arr_ty, cg_deref(p->cg, elem_ty); } +static void push_record_field_lv(Parser* p, FrameSlot slot, const Type* arr_ty, + u32 rec_offset, const Type* rec_ty, + u32 field_index) { + const Field* f = &rec_ty->rec.fields[field_index]; + push_subobject_lv(p, slot, arr_ty, rec_offset, rec_ty); + if (pcg_emit_enabled(p)) cfree_cg_field(p->cg, field_index); + pcg_retag_top(p, f->type); + if (f->flags & FIELD_BITFIELD) pcg_set_top_bitfield(p); +} + /* Emit a load+store for one scalar leaf. */ static void emit_copy_leaf(Parser* p, FrameSlot dst_slot, const Type* dst_arr_ty, u32 dst_off, @@ -155,6 +165,14 @@ static void zero_init_at(Parser* p, FrameSlot slot, const Type* arr_ty, const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, ty); for (u16 i = 0; i < ty->rec.nfields; ++i) { const Field* f = &ty->rec.fields[i]; + if (f->flags & FIELD_ZERO_WIDTH) continue; + if (f->flags & FIELD_BITFIELD) { + push_record_field_lv(p, slot, arr_ty, offset, ty, i); + cg_push_int(p->cg, 0, f->type); + cg_store(p->cg); + cg_drop(p->cg); + continue; + } zero_init_at(p, slot, arr_ty, offset + L->fields[i].offset, f->type); } return; @@ -174,6 +192,29 @@ static void zero_init_at(Parser* p, FrameSlot slot, const Type* arr_ty, cg_drop(p->cg); } +static void init_field_at(Parser* p, FrameSlot slot, const Type* arr_ty, + u32 rec_offset, const Type* rec_ty, u32 field_index) { + const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, rec_ty); + const Field* f = &rec_ty->rec.fields[field_index]; + if (f->flags & FIELD_ZERO_WIDTH) return; + if (f->flags & FIELD_BITFIELD) { + push_record_field_lv(p, slot, arr_ty, rec_offset, rec_ty, field_index); + parse_assign_expr(p); + to_rvalue(p); + { + const Type* rhs = cg_top_type(p->cg); + CSemCheck chk = + c_sem_check_assignment(p->pool, f->type, rhs, C_SEM_ASSIGN_INIT); + if (!chk.ok) perr(p, "%s", chk.message); + } + coerce_top_to_lvalue(p); + cg_store(p->cg); + cg_drop(p->cg); + return; + } + init_at(p, slot, arr_ty, rec_offset + L->fields[field_index].offset, f->type); +} + /* Emit byte stores for a string literal initializing a char-array sub-object. */ static void init_string_at(Parser* p, FrameSlot slot, const Type* arr_ty, @@ -284,8 +325,6 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty, u32 i = start_field; u32 zero_lo = start_field; for (; i < ty->rec.nfields; ++i) { - const Field* f = &ty->rec.fields[i]; - u32 foff = offset + L->fields[i].offset; if (braced && (is_punct(&p->cur, '}') || p->cur.kind == TOK_EOF)) break; if (braced && is_punct(&p->cur, '.')) { const Type* sub_ty; @@ -294,8 +333,17 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty, parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx); while (zero_lo < top_idx) { const Field* zf = &ty->rec.fields[zero_lo]; - u32 zoff = offset + L->fields[zero_lo].offset; - zero_init_at(p, slot, arr_ty, zoff, zf->type); + if (zf->flags & FIELD_BITFIELD) { + if (!(zf->flags & FIELD_ZERO_WIDTH)) { + push_record_field_lv(p, slot, arr_ty, offset, ty, zero_lo); + cg_push_int(p->cg, 0, zf->type); + cg_store(p->cg); + cg_drop(p->cg); + } + } else { + u32 zoff = offset + L->fields[zero_lo].offset; + zero_init_at(p, slot, arr_ty, zoff, zf->type); + } ++zero_lo; } init_at(p, slot, arr_ty, sub_off, sub_ty); @@ -303,7 +351,7 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty, if (zero_lo <= top_idx) zero_lo = top_idx + 1; goto next_item_struct; } - init_at(p, slot, arr_ty, foff, f->type); + init_field_at(p, slot, arr_ty, offset, ty, i); if (zero_lo <= i) zero_lo = i + 1; if (!braced) { ++i; @@ -323,8 +371,17 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty, u32 j; for (j = zero_lo; j < ty->rec.nfields; ++j) { const Field* f = &ty->rec.fields[j]; - u32 foff = offset + L->fields[j].offset; - zero_init_at(p, slot, arr_ty, foff, f->type); + if (f->flags & FIELD_BITFIELD) { + if (!(f->flags & FIELD_ZERO_WIDTH)) { + push_record_field_lv(p, slot, arr_ty, offset, ty, j); + cg_push_int(p->cg, 0, f->type); + cg_store(p->cg); + cg_drop(p->cg); + } + } else { + u32 foff = offset + L->fields[j].offset; + zero_init_at(p, slot, arr_ty, foff, f->type); + } } } return i; @@ -487,6 +544,22 @@ void encode_int_le(u8* dst, u32 size, i64 v) { } } +static u64 decode_uint_le(const u8* src, u32 size) { + u64 v = 0; + if (size > 8) size = 8; + for (u32 i = 0; i < size; ++i) { + v |= (u64)src[i] << (8u * i); + } + return v; +} + +static void encode_uint_le(u8* dst, u32 size, u64 v) { + if (size > 8) size = 8; + for (u32 i = 0; i < size; ++i) { + dst[i] = (u8)((v >> (8u * i)) & 0xffu); + } +} + /* Encode a string literal at *buf+offset for a char-array sub-object. */ static void parse_static_string_at(Parser* p, u8* buf, u32 buflen, u32 offset, u32 count) { @@ -522,12 +595,63 @@ void srl_push(Parser* p, u32 offset, u32 size, ObjSymId target, i64 addend) { ++p->static_relocs_len; } -/* Try to parse the current expression as an address constant. */ -static int try_parse_addr_const(Parser* p, const Type* ty, u8* buf, u32 offset, - u32 sz) { +typedef enum CStaticConstKind { + C_STATIC_CONST_INT, + C_STATIC_CONST_NULL_PTR, + C_STATIC_CONST_ADDR, +} CStaticConstKind; + +typedef struct CStaticConst { + CStaticConstKind kind; + CConstInt int_value; + ObjSymId target; + i64 addend; +} CStaticConst; + +static u64 int_bits_for_type(Parser* p, CConstInt v, const Type* ty) { + u32 sz = c_abi_sizeof(p->abi, ty); + u64 bits = v.bits; + if (sz < 8u) { + bits &= (1ull << (sz * 8u)) - 1ull; + } + if (ty && ty->kind == TY_BOOL) bits = bits ? 1u : 0u; + return bits; +} + +static CConstInt parse_null_pointer_constant(Parser* p, SrcLoc loc) { + if (is_punct(&p->cur, '(')) { + Tok n = peek1(p); + if (starts_type_name(p, &n)) { + const Type* cast_ty; + const Type* cast_unqual; + advance(p); + cast_ty = parse_type_name(p); + cast_unqual = type_unqual(p->pool, cast_ty); + expect_punct(p, ')', "')' after cast in null pointer constant"); + if (!cast_unqual || + (cast_unqual->kind != TY_PTR && cast_unqual->kind != TY_VOID && + !type_is_int(cast_unqual))) { + perr(p, "invalid cast in null pointer constant"); + } + return parse_null_pointer_constant(p, loc); + } + if (is_punct(&n, '(')) { + advance(p); + { + CConstInt v = parse_null_pointer_constant(p, loc); + expect_punct(p, ')', "')' in null pointer constant"); + return v; + } + } + } + return eval_const_int_typed(p, loc); +} + +/* Try to parse the current expression as a static initializer address + * constant. Leaves non-address expressions untouched. */ +static int try_parse_static_address_const(Parser* p, CStaticConst* out) { Tok t = p->cur; Sym name = 0; - SrcLoc nloc = tok_loc_init(&p->cur); int saw_amp = 0; i64 element_addend = 0; i64 byte_addend = 0; @@ -540,9 +664,9 @@ static int try_parse_addr_const(Parser* p, const Type* ty, u8* buf, u32 offset, ObjSymId str_sym = emit_string_to_rodata(p, bytes, n); cfree_compiler_heap(p->c)->free(cfree_compiler_heap(p->c), bytes, 0); advance(p); - (void)ty; - (void)buf; - srl_push(p, offset, sz, str_sym, 0); + out->kind = C_STATIC_CONST_ADDR; + out->target = str_sym; + out->addend = 0; return 1; } if (is_punct(&t, '&')) { @@ -553,7 +677,6 @@ static int try_parse_addr_const(Parser* p, const Type* ty, u8* buf, u32 offset, perr(p, "expected identifier after '&' in static initializer"); } name = p->cur.v.ident; - nloc = tok_loc_init(&p->cur); advance(p); } else if (t.kind == TOK_IDENT && ident_kw_init(p, t.v.ident) == KW_NONE) { name = t.v.ident; @@ -598,39 +721,56 @@ static int try_parse_addr_const(Parser* p, const Type* ty, u8* buf, u32 offset, byte_addend += v; } } - (void)nloc; - (void)ty; - (void)buf; - srl_push(p, offset, sz, tgt, byte_addend); + out->kind = C_STATIC_CONST_ADDR; + out->target = tgt; + out->addend = byte_addend; return 1; } -static int try_parse_null_pointer_const(Parser* p) { - if (p->cur.kind == TOK_NUM) { - if (parse_int_literal(p, &p->cur) != 0) return 0; - advance(p); - return 1; - } - if (is_punct(&p->cur, '+')) { - advance(p); - return try_parse_null_pointer_const(p); - } - if (is_punct(&p->cur, '(')) { - advance(p); - if (starts_type_name(p, &p->cur)) { - const Type* cast_ty = type_unqual(p->pool, parse_type_name(p)); - expect_punct(p, ')', "')' after cast in null pointer constant"); - if (!cast_ty || (cast_ty->kind != TY_PTR && cast_ty->kind != TY_VOID && - !type_is_int(cast_ty))) { - return 0; - } - return try_parse_null_pointer_const(p); - } - if (!try_parse_null_pointer_const(p)) return 0; - expect_punct(p, ')', "')' in null pointer constant"); - return 1; - } - return 0; +static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) { + CStaticConst r; + memset(&r, 0, sizeof r); + r.kind = C_STATIC_CONST_INT; + if (ty && ty->kind == TY_PTR) { + if (try_parse_static_address_const(p, &r)) return r; + r.int_value = parse_null_pointer_constant(p, loc); + if (const_int_as_i64(p, r.int_value) != 0) { + perr(p, "static pointer initializer is not a null pointer constant"); + } + r.kind = C_STATIC_CONST_NULL_PTR; + return r; + } + r.int_value = eval_const_int_typed(p, loc); + return r; +} + +static void parse_static_bitfield_at(Parser* p, u8* buf, u32 buflen, + u32 rec_offset, + const ABIFieldLayout* fl, + const Type* field_ty) { + SrcLoc cloc = tok_loc_init(&p->cur); + CStaticConst parsed = parse_static_const(p, field_ty, cloc); + u32 storage_off = rec_offset + fl->offset; + u32 storage_size = fl->storage_size; + u32 width = fl->bit_width; + u32 lsb = fl->bit_offset; + u64 ones; + u64 mask; + u64 cur; + u64 val; + if (parsed.kind != C_STATIC_CONST_INT) { + perr(p, "bit-field initializer requires integer constant expression"); + } + if (width == 0) return; + if (storage_size > 8) perr(p, "bit-field storage unit too wide"); + if (storage_off > buflen || storage_size > buflen - storage_off) + perr(p, "initializer overflows object"); + ones = width >= 64u ? ~(u64)0 : (((u64)1 << width) - 1u); + mask = ones << lsb; + cur = decode_uint_le(buf + storage_off, storage_size); + val = (int_bits_for_type(p, parsed.int_value, field_ty) & ones) << lsb; + cur = (cur & ~mask) | val; + encode_uint_le(buf + storage_off, storage_size, cur); } void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset, @@ -699,8 +839,15 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset, if (!accept_punct(p, ',')) break; continue; } - parse_static_init_at(p, buf, buflen, offset + L->fields[i].offset, - f->type); + if (f->flags & FIELD_BITFIELD) { + if (!(f->flags & FIELD_ZERO_WIDTH)) { + parse_static_bitfield_at(p, buf, buflen, offset, &L->fields[i], + f->type); + } + } else { + parse_static_init_at(p, buf, buflen, offset + L->fields[i].offset, + f->type); + } ++i; if (!accept_punct(p, ',')) break; } @@ -708,21 +855,48 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset, return; } if (ty->kind == TY_UNION) { - perr(p, "static-storage union initializer not supported in Phase 4"); + int had_brace = accept_punct(p, '{'); + if (ty->rec.nfields == 0) { + if (had_brace) expect_punct(p, '}', "'}' after union initializer"); + return; + } + if (had_brace && is_punct(&p->cur, '.')) { + const Type* sub_ty; + u32 sub_off; + u32 top_idx = 0; + parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx); + (void)top_idx; + parse_static_init_at(p, buf, buflen, sub_off, sub_ty); + } else { + const Field* f = &ty->rec.fields[0]; + if (!(f->flags & FIELD_BITFIELD)) { + parse_static_init_at(p, buf, buflen, offset, f->type); + } else if (!(f->flags & FIELD_ZERO_WIDTH)) { + const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, ty); + parse_static_bitfield_at(p, buf, buflen, offset, &L->fields[0], + f->type); + } + } + if (had_brace) { + accept_punct(p, ','); + expect_punct(p, '}', "'}' after union initializer"); + } + return; } /* Scalar / pointer. */ { int had_brace = accept_punct(p, '{'); SrcLoc cloc = tok_loc_init(&p->cur); u32 sz = c_abi_sizeof(p->abi, ty); + CStaticConst cv; if (offset + sz > buflen) perr(p, "initializer overflows object"); - if (ty->kind == TY_PTR && try_parse_addr_const(p, ty, buf, offset, sz)) { - /* Address constant recorded as a reloc. */ - } else if (ty->kind == TY_PTR && try_parse_null_pointer_const(p)) { + cv = parse_static_const(p, ty, cloc); + if (cv.kind == C_STATIC_CONST_ADDR) { + srl_push(p, offset, sz, cv.target, cv.addend); + } else if (cv.kind == C_STATIC_CONST_NULL_PTR) { encode_int_le(buf + offset, sz, 0); } else { - i64 v = eval_const_int(p, cloc); - encode_int_le(buf + offset, sz, v); + encode_uint_le(buf + offset, sz, int_bits_for_type(p, cv.int_value, ty)); } if (had_brace) { accept_punct(p, ','); diff --git a/lang/c/type/type.c b/lang/c/type/type.c @@ -444,6 +444,23 @@ int type_is_arith(const Type* t) { int type_is_ptr(const Type* t) { return t && t->kind == TY_PTR; } +static int type_is_signed_integer_for_cg(const Type* t) { + if (!t) return 0; + switch ((TypeKind)t->kind) { + case TY_CHAR: + case TY_SCHAR: + case TY_SHORT: + case TY_INT: + case TY_LONG: + case TY_LLONG: + case TY_INT128: + case TY_ENUM: + return 1; + default: + return 0; + } +} + static CfreeCgTypeId type_cg_builtin(CfreeCompiler* c, TypeKind kind) { CfreeCgBuiltinTypes b = cfree_cg_builtin_types(c); switch (kind) { @@ -526,6 +543,15 @@ static CfreeCgTypeId type_cg_id_walk(CfreeCompiler* c, Pool* p, const Type* t, fields[i].name = t->rec.fields[i].name; fields[i].type = type_cg_id_walk(c, p, t->rec.fields[i].type, t); fields[i].align_override = t->rec.fields[i].align_override; + if (t->rec.fields[i].flags & FIELD_BITFIELD) { + fields[i].flags |= CFREE_CG_FIELD_BITFIELD; + fields[i].bit_width = t->rec.fields[i].bitfield_width; + fields[i].bit_signed = + type_is_signed_integer_for_cg(t->rec.fields[i].type); + if (t->rec.fields[i].flags & FIELD_ZERO_WIDTH) { + fields[i].flags |= CFREE_CG_FIELD_ZERO_WIDTH; + } + } if (t->rec.fields[i].packed && fields[i].align_override == 0) { fields[i].align_override = 1; } diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -102,9 +102,10 @@ u32 abi_cg_alignof(TargetABI* a, CfreeCgTypeId id) { /* ---- record layout (struct/union) ---- * - * Shared by all currently supported ABIs: storage-unit-based layout with - * natural alignment, no bitfield packing extensions. When a Windows-x64 - * (MSVC bitfield rules) ABI lands, promote this into the vtable. */ + * The CG type constructor computes the shared source-facing record layout. + * The ABI cache exposes that immutable layout to codegen passes. When an ABI + * with different source bit-field rules lands, record construction should be + * routed through an ABI-specific layout hook before the type is committed. */ static ABIRecordLayout* compute_record_layout(TargetABI* a, CfreeCgTypeId id) { ABIRecordLayout* L = arena_new(a->c->tu, ABIRecordLayout); @@ -118,46 +119,18 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, CfreeCgTypeId id) { memset(fl, 0, sizeof(ABIFieldLayout) * t->record.nfields); } - u32 max_align = 1; - if (!t->record.is_union) { - u32 off = 0; - for (u32 i = 0; i < t->record.nfields; ++i) { - const CgTypeField* f = &t->record.fields[i]; - ABITypeInfo fi = abi_cg_type_info(a, f->type); - if (f->align_override == 1) fi.align = 1; - if (f->align_override > fi.align) fi.align = f->align_override; - if (fi.align > max_align) max_align = fi.align; - u32 mask = fi.align ? fi.align - 1 : 0; - off = (off + mask) & ~mask; - fl[i].offset = off; - fl[i].bit_offset = 0; - fl[i].bit_width = 0; - fl[i].storage_size = fi.size; - off += fi.size; - } - u32 mask = max_align - 1; - L->size = (off + mask) & ~mask; - } else { - u32 mx = 0; - for (u32 i = 0; i < t->record.nfields; ++i) { - const CgTypeField* f = &t->record.fields[i]; - ABITypeInfo fi = abi_cg_type_info(a, f->type); - if (f->align_override == 1) fi.align = 1; - if (f->align_override > fi.align) fi.align = f->align_override; - if (fi.align > max_align) max_align = fi.align; - if (fi.size > mx) mx = fi.size; - fl[i].offset = 0; - fl[i].storage_size = fi.size; - } - u32 mask = max_align - 1; - L->size = (mx + mask) & ~mask; - } - L->align = max_align; - if (t->record.align_override > L->align) { - L->align = t->record.align_override; - u32 mask = L->align - 1; - L->size = (L->size + mask) & ~mask; + for (u32 i = 0; i < t->record.nfields; ++i) { + const CgTypeField* f = &t->record.fields[i]; + fl[i].offset = (u32)f->offset; + fl[i].bit_offset = f->bit_offset; + fl[i].bit_width = + (f->flags & CFREE_CG_FIELD_BITFIELD) ? f->bit_width : 0; + fl[i].storage_size = f->bit_storage_size + ? f->bit_storage_size + : (u32)abi_cg_sizeof(a, f->type); } + L->size = (u32)t->size; + L->align = t->align; L->nfields = t->record.nfields; L->fields = fl; return L; diff --git a/src/api/cg.c b/src/api/cg.c @@ -434,6 +434,9 @@ static CgTypeField *copy_cg_fields(Compiler *c, const CfreeCgField *src, dst[i].name = src[i].name; dst[i].type = src[i].type; dst[i].align_override = src[i].align_override; + dst[i].flags = src[i].flags; + dst[i].bit_width = src[i].bit_width; + dst[i].bit_signed = src[i].bit_signed != 0; } return dst; } @@ -459,12 +462,24 @@ static int cg_type_layout_record(Compiler *c, CgType *cg) { } if (falign > max_align) max_align = falign; + if ((f->flags & CFREE_CG_FIELD_BITFIELD) != 0) { + f->offset = 0; + f->bit_offset = 0; + f->bit_storage_size = (u32)fsize; + if (f->bit_width == 0) + continue; + } if (fsize > size) size = fsize; f->offset = 0; } } else { u64 off = 0; + int active_bitfield_unit = 0; + u64 unit_off = 0; + u32 unit_bits = 0; + u32 unit_size = 0; + u32 next_bit = 0; for (u32 i = 0; i < cg->record.nfields; ++i) { CgTypeField *f = &cg->record.fields[i]; u64 fsize = cg_type_size(c, f->type); @@ -478,6 +493,41 @@ static int cg_type_layout_record(Compiler *c, CgType *cg) { } if (falign > max_align) max_align = falign; + if ((f->flags & CFREE_CG_FIELD_BITFIELD) != 0) { + if (fsize > UINT32_MAX / 8u) + return 0; + if (f->bit_width == 0) { + if (active_bitfield_unit) + off = unit_off + unit_size; + off = cg_align_to(off, falign); + f->offset = off; + f->bit_offset = 0; + f->bit_storage_size = (u32)fsize; + active_bitfield_unit = 0; + next_bit = 0; + continue; + } + if (f->bit_width > fsize * 8u) + return 0; + if (!active_bitfield_unit || unit_size != (u32)fsize || + next_bit + f->bit_width > unit_bits) { + if (active_bitfield_unit) + off = unit_off + unit_size; + off = cg_align_to(off, falign); + unit_off = off; + unit_size = (u32)fsize; + unit_bits = unit_size * 8u; + next_bit = 0; + active_bitfield_unit = 1; + } + f->offset = unit_off; + f->bit_offset = (u16)next_bit; + f->bit_storage_size = unit_size; + next_bit += f->bit_width; + off = unit_off + unit_size; + continue; + } + active_bitfield_unit = 0; off = cg_align_to(off, falign); f->offset = off; off += fsize; @@ -901,6 +951,11 @@ int cfree_cg_type_record_field(CfreeCompiler *c, CfreeCgTypeId id, out->name = f->name; out->type = f->type; out->align_override = f->align_override; + out->flags = f->flags; + out->bit_width = f->bit_width; + out->bit_offset = f->bit_offset; + out->bit_storage_size = f->bit_storage_size; + out->bit_signed = f->bit_signed; } if (offset_out) *offset_out = f->offset; @@ -1029,13 +1084,14 @@ typedef struct ApiSValue { union { ApiDelayedCmp cmp; ApiDelayedArith arith; + BitFieldAccess bitfield; } delayed; CfreeCgTypeId type; u8 kind; u8 res; u8 pinned; u8 lvalue; - u8 pad; + u8 bitfield_lvalue; FrameSlot spill_slot; CfreeCgLocal source_local; } ApiSValue; @@ -1379,7 +1435,7 @@ static int api_sv_op_is_reg_or_imm(const ApiSValue *sv) { static int api_is_lvalue_sv(const ApiSValue *sv) { return sv->lvalue && - (api_operand_can_address(&sv->op) || + (sv->bitfield_lvalue || api_operand_can_address(&sv->op) || (sv->source_local != CFREE_CG_LOCAL_NONE && sv->op.kind == OPK_REG)); } @@ -3491,6 +3547,17 @@ void cfree_cg_load(CfreeCg *g, CfreeCgMemAccess access) { return; } ty = api_mem_access_type(g, access, api_sv_type(&v), "load"); + if (v.bitfield_lvalue) { + CfreeCgTypeId load_ty = ty; + Reg rr; + api_require_scalar_mem_type(g, "load", load_ty); + rr = api_alloc_reg_or_spill(g, RC_INT, load_ty); + dst = api_op_reg(rr, load_ty); + g->target->bitfield_load(g->target, dst, v.op, v.delayed.bitfield); + api_release(g, &v); + api_push(g, api_make_sv(dst, load_ty)); + return; + } if (cg_type_is_aggregate(g->c, api_sv_type(&v))) { u32 access_size; u32 lvalue_size; @@ -3559,6 +3626,11 @@ void cfree_cg_addr(CfreeCg *g) { if (!g) return; v = api_pop(g); + if (v.bitfield_lvalue) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: cannot take address of bit-field"); + return; + } pty = cg_type_ptr_to(g->c, api_sv_type(&v)); if (v.source_local != CFREE_CG_LOCAL_NONE) api_local_const_address_taken(g, v.source_local); @@ -3586,6 +3658,22 @@ void cfree_cg_store(CfreeCg *g, CfreeCgMemAccess access) { return; } ty = api_mem_access_type(g, access, api_sv_type(&lv), "store"); + if (lv.bitfield_lvalue) { + api_validate_memory_value(g, "store", ty, api_sv_type(&rv)); + if (lv.op.kind == OPK_INDIRECT || lv.op.kind == OPK_GLOBAL || + (access.flags & CFREE_CG_MEM_VOLATILE)) { + api_local_const_memory_boundary(g); + } + if (api_sv_op_is_reg_or_imm(&rv)) { + src = rv.op; + } else { + src = api_force_reg(g, &rv, api_sv_type(&rv)); + } + T->bitfield_store(T, lv.op, src, lv.delayed.bitfield); + api_release(g, &lv); + api_release(g, &rv); + return; + } if (cg_type_is_aggregate(g->c, api_sv_type(&lv)) && !cg_type_is_aggregate(g->c, api_sv_type(&rv)) && !cg_type_is_aggregate(g->c, ty)) { @@ -5382,6 +5470,33 @@ void cfree_cg_field(CfreeCg *g, uint32_t field_index) { field_ty = rec_info->record.fields[field_index].type; rec_ptr_ty = cg_type_ptr_to(g->c, rec_ty); field_offset = layout->fields[field_index].offset; + if (layout->fields[field_index].bit_width != 0 || + (rec_info->record.fields[field_index].flags & + CFREE_CG_FIELD_BITFIELD) != 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; + } + 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; + } if (base.op.kind == OPK_GLOBAL) { result = api_op_global(base.op.v.global.sym, diff --git a/src/api/cg_type.h b/src/api/cg_type.h @@ -10,6 +10,11 @@ typedef struct CgTypeField { CfreeCgTypeId type; u64 offset; u32 align_override; + u32 flags; + u16 bit_width; + u16 bit_offset; + u32 bit_storage_size; + int bit_signed; } CgTypeField; typedef struct CgType { diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -1290,6 +1290,7 @@ int main(void) { EXPECT(cfree_cg_type_size(c, alias) == cfree_cg_type_size(c, i32_ty), "alias size mismatch"); + memset(fields, 0, sizeof fields); fields[0].name = cfree_sym_intern(c, "a"); fields[0].type = i32_ty; fields[0].align_override = 0; @@ -1311,6 +1312,30 @@ int main(void) { "record field data mismatch"); EXPECT(field_off == 4, "record field offset mismatch"); + memset(fields, 0, sizeof fields); + fields[0].name = cfree_sym_intern(c, "lo"); + fields[0].type = i32_ty; + fields[0].flags = CFREE_CG_FIELD_BITFIELD; + fields[0].bit_width = 5; + fields[1].name = cfree_sym_intern(c, "hi"); + fields[1].type = i32_ty; + fields[1].flags = CFREE_CG_FIELD_BITFIELD; + fields[1].bit_width = 3; + rec_ex = cfree_cg_type_record(c, cfree_sym_intern(c, "BF"), fields, 2); + EXPECT(rec_ex != CFREE_CG_TYPE_NONE, "bit-field record type failed"); + EXPECT(cfree_cg_type_size(c, rec_ex) == 4, "bit-field record size mismatch"); + EXPECT(cfree_cg_type_record_field(c, rec_ex, 1, &field_out, &field_off) == 0, + "bit-field record query failed"); + EXPECT(field_off == 0 && field_out.bit_offset == 5 && + field_out.bit_width == 3 && field_out.bit_storage_size == 4, + "bit-field metadata mismatch"); + + memset(fields, 0, sizeof fields); + fields[0].name = cfree_sym_intern(c, "a"); + fields[0].type = i32_ty; + fields[1].name = cfree_sym_intern(c, "b"); + fields[1].type = ptr_i32; + fields[1].align_override = 1; memset(&rdesc, 0, sizeof rdesc); rdesc.tag = cfree_sym_intern(c, "U"); rdesc.fields = fields; diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -282,6 +282,7 @@ members, self-reference through pointers, and forward declarations. | `6_7_2_1_07_signed_bitfield` | ★ | `struct {signed int s:8;} b={-1}; return (b.s<0)?42:0;` — signed bitfield sign extension | 42 | | `6_7_2_1_08_zero_width_bitfield` | ★ | `struct {unsigned a:4; unsigned:0; unsigned b:4;}` — zero-width bitfield forces next field into new storage unit | 42 | | `6_7_2_1_09_bool_bitfield` | ★ | `struct {_Bool b:1;} s; s.b=1; return s.b?42:0;` — `_Bool` bitfield | 42 | +| `6_7_2_1_10_static_bitfield_pack` | ★ | `static struct {unsigned a:5; unsigned b:3;} g={2,5}; return g.b*8+g.a;` — static packed bit-field initializer | 42 | ## §6.7.2.2 Enumeration specifiers @@ -395,6 +396,8 @@ cover compound typedef targets. | `6_7_9_19_static_init_string_array` | ★ | `static const char* const names[2] = {"a","b"};` — array-of-pointer static init with string literals | 42 | | `6_7_9_20_array_of_struct_with_array_field` | ★ | `static const S t[] = { {10u,{1,2}}, {15u,{3,11}} };` — file-scope incomplete array of struct with trailing fixed-size array field | 42 | | `6_7_9_21_array_of_struct_size_from_init` | ★ | `static const S t[] = { {10,12}, {7,13} };` — file-scope incomplete-array-of-struct sized from init list | 44 | +| `6_7_9_22_static_union_designated_nonfirst` | ★ | `static union U{int a; int b;} g={.b=42}; return g.b;` — static-storage union designated init | 42 | +| `6_7_9_23_static_ptr_null_const_expr` | ★ | `static int* p = 1 - 1; return p == 0 ? 42 : 0;` — null pointer constant via integer constant expression | 42 | ## §6.7.10 Static assertions diff --git a/test/parse/cases/6_7_2_1_10_static_bitfield_pack.c b/test/parse/cases/6_7_2_1_10_static_bitfield_pack.c @@ -0,0 +1,8 @@ +static struct { + unsigned a : 5; + unsigned b : 3; +} g = {2, 5}; + +int test_main(void) { + return g.b * 8 + g.a; +} diff --git a/test/parse/cases/6_7_2_1_10_static_bitfield_pack.expected b/test/parse/cases/6_7_2_1_10_static_bitfield_pack.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_7_9_22_static_union_designated_nonfirst.c b/test/parse/cases/6_7_9_22_static_union_designated_nonfirst.c @@ -0,0 +1,8 @@ +static union U { + int a; + int b; +} g = {.b = 42}; + +int test_main(void) { + return g.b; +} diff --git a/test/parse/cases/6_7_9_22_static_union_designated_nonfirst.expected b/test/parse/cases/6_7_9_22_static_union_designated_nonfirst.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_7_9_23_static_ptr_null_const_expr.c b/test/parse/cases/6_7_9_23_static_ptr_null_const_expr.c @@ -0,0 +1,5 @@ +static int* p = 1 - 1; + +int test_main(void) { + return p == 0 ? 42 : 0; +} diff --git a/test/parse/cases/6_7_9_23_static_ptr_null_const_expr.expected b/test/parse/cases/6_7_9_23_static_ptr_null_const_expr.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases_err/6_7_9_static_ptr_nonzero.c b/test/parse/cases_err/6_7_9_static_ptr_nonzero.c @@ -0,0 +1,5 @@ +static int* p = 1; + +int test_main(void) { + return p != 0; +} diff --git a/test/parse/cases_err/6_7_9_static_ptr_nonzero.errpat b/test/parse/cases_err/6_7_9_static_ptr_nonzero.errpat @@ -0,0 +1 @@ +not a null pointer constant