kit

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

commit 64e882134660367b2546da279ad4d2adbab6158f
parent 24d477f4ce2d0de89590909ce1fdbfb2c84f0ffa
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 15:51:25 -0700

Implement typed C constant expression evaluation

Diffstat:
Mdoc/C11_CONFORMANCE_CHECKLIST.md | 23++++++++++++++++++-----
Mlang/c/parse/parse_expr.c | 656++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mlang/c/parse/parse_priv.h | 6++++++
Atest/parse/cases/6_6_11_unsigned_const_expr.c | 11+++++++++++
Atest/parse/cases/6_6_11_unsigned_const_expr.expected | 1+
Atest/parse/cases_err/6_6_shift_count_out_of_range.c | 5+++++
Atest/parse/cases_err/6_6_shift_count_out_of_range.errpat | 1+
7 files changed, 591 insertions(+), 112 deletions(-)

diff --git a/doc/C11_CONFORMANCE_CHECKLIST.md b/doc/C11_CONFORMANCE_CHECKLIST.md @@ -11,7 +11,7 @@ make the implementation pass it. - [x] `make test-lex` passes: 16/16. - [x] `make test-pp test-pp-err` passes: 82/82 and 15/15. - [x] `make test-parse-err` passes with expanded C11 constraint coverage: - currently 56/56 pass. + currently 57/57 pass. - [ ] `make test-parse` passes without skips: currently 2528 pass, 0 fail, 4 skip. Skips are `long double` and file-scope `asm`. - [x] `make test-cg-api test-opt test-dwarf test-debug` passes. @@ -171,10 +171,22 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse ## Constant expressions and initializers -- [ ] Replace the current narrow integer evaluator with a C11-aware constant - expression evaluator that tracks type, value category, and allowed forms. -- [ ] Accept `_Alignof` in integer constant expressions. +- [x] Replace the previous narrow `i64` integer evaluator with a typed, + target-width integer constant-expression evaluator. + Covered cases include integer literal type selection by suffix/base/value, + integer promotions, usual arithmetic conversions, logical operators, + conditional expressions, integer casts, immediate floating-constant casts, + and shift-count diagnostics. + Tests: `6_6_10_logical_cond_const`, + `6_6_11_unsigned_const_expr`, and + `6_6_shift_count_out_of_range`. + Code: `eval_const_int_typed`, `CConstInt`, and `eval_const_int` in + `parse_expr.c`. +- [x] Accept `_Alignof` in integer constant expressions. Positive array-bound coverage passes. +- [ ] 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: object address, function address, array plus/minus integer constant, and null pointer constants. @@ -250,7 +262,8 @@ conformance needs a mode story. Ordinary identifier redeclarations, tentative/defined state, and composite object/function types are covered; tag completion remains pending. 4. Finish bit-fields: diagnostics first, then layout/codegen. -5. Finish `sizeof`/constant-expression/static-initializer semantics. +5. Finish `sizeof` edge cases and unify static-initializer/address-constant + semantics with the typed constant-expression evaluator. 6. Unskip `long double` or explicitly narrow the supported C profile until runtime/CG support exists. 7. Bring `rt` and freestanding header tests into the default conformance gate. diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -82,21 +82,28 @@ static const Type* conditional_pointer_type(Parser* p, const Type* then_ty, * Literal parsing * ============================================================ */ -i64 parse_int_literal(Parser* p, const Tok* t) { +static u32 cint_bits(Parser* p, const Type* ty); +static int cint_signed(Parser* p, const Type* ty); + +static u64 parse_int_literal_u64(Parser* p, const Tok* t, int* decimal_out) { size_t len = 0; const char* s = pool_str(p->pool, t->spelling, &len); size_t i = 0; - i64 base = 10; - i64 acc = 0; + u64 base = 10; + u64 acc = 0; + int decimal = 1; if (!s) perr(p, "bad numeric literal"); if (len >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; + decimal = 0; i = 2; } else if (len >= 2 && s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) { base = 2; + decimal = 0; i = 2; } else if (len >= 1 && s[0] == '0') { base = 8; + decimal = 0; i = 1; } for (; i < len; ++i) { @@ -111,26 +118,80 @@ i64 parse_int_literal(Parser* p, const Tok* t) { dv = c - 'A' + 10; else perr(p, "bad digit in numeric literal"); - if (dv >= base) perr(p, "digit out of range for base"); + if ((u64)dv >= base) perr(p, "digit out of range for base"); + if (acc > (~0ull - (u64)dv) / base) + perr(p, "integer literal too large"); acc = acc * base + dv; } + if (decimal_out) *decimal_out = decimal; return acc; } +static int uint_fits_type(Parser* p, u64 v, const Type* ty) { + u32 nb = cint_bits(p, ty); + if (cint_signed(p, ty)) { + if (nb >= 64) return v <= 0x7fffffffffffffffull; + return v <= ((1ull << (nb - 1u)) - 1ull); + } + if (nb >= 64) return 1; + return v <= ((1ull << nb) - 1ull); +} + +static const Type* first_fitting_type(Parser* p, u64 v, const TypeKind* kinds, + u32 nkinds) { + u32 i; + for (i = 0; i < nkinds; ++i) { + const Type* ty = type_prim(p->pool, kinds[i]); + if (uint_fits_type(p, v, ty)) return ty; + } + perr(p, "integer literal too large for supported integer types"); +} + +i64 parse_int_literal(Parser* p, const Tok* t) { + return (i64)parse_int_literal_u64(p, t, NULL); +} + static const Type* int_literal_type(Parser* p, const Tok* t) { int u = (t->flags & TF_INT_U) != 0; int l = (t->flags & TF_INT_L) != 0; int ll = (t->flags & TF_INT_LL) != 0; - TypeKind k; - if (ll) - k = u ? TY_ULLONG : TY_LLONG; - else if (l) - k = u ? TY_ULONG : TY_LONG; - else if (u) - k = TY_UINT; - else - k = TY_INT; - return type_prim(p->pool, k); + int decimal = 1; + u64 v = parse_int_literal_u64(p, t, &decimal); + if (u && ll) { + static const TypeKind order[] = {TY_ULLONG}; + return first_fitting_type(p, v, order, (u32)(sizeof order / sizeof order[0])); + } + if (!u && ll) { + static const TypeKind order[] = {TY_LLONG}; + return first_fitting_type(p, v, order, (u32)(sizeof order / sizeof order[0])); + } + if (u && l) { + static const TypeKind order[] = {TY_ULONG, TY_ULLONG}; + return first_fitting_type(p, v, order, (u32)(sizeof order / sizeof order[0])); + } + if (!u && l) { + static const TypeKind dec_order[] = {TY_LONG, TY_LLONG}; + static const TypeKind other_order[] = {TY_LONG, TY_ULONG, TY_LLONG, + TY_ULLONG}; + return decimal ? first_fitting_type(p, v, dec_order, + (u32)(sizeof dec_order / + sizeof dec_order[0])) + : first_fitting_type(p, v, other_order, + (u32)(sizeof other_order / + sizeof other_order[0])); + } + if (u) { + static const TypeKind order[] = {TY_UINT, TY_ULONG, TY_ULLONG}; + return first_fitting_type(p, v, order, (u32)(sizeof order / sizeof order[0])); + } + if (decimal) { + static const TypeKind order[] = {TY_INT, TY_LONG, TY_LLONG}; + return first_fitting_type(p, v, order, (u32)(sizeof order / sizeof order[0])); + } else { + static const TypeKind order[] = {TY_INT, TY_UINT, TY_LONG, + TY_ULONG, TY_LLONG, TY_ULLONG}; + return first_fitting_type(p, v, order, (u32)(sizeof order / sizeof order[0])); + } } static double parse_float_literal(Parser* p, const Tok* t) { @@ -388,136 +449,385 @@ CfreeCgSym emit_string_to_rodata(Parser* p, const u8* bytes, size_t n) { * Constant expression evaluator (cexpr_*) * ============================================================ */ -static i64 cexpr_unary(Parser* p, SrcLoc loc); -static i64 cexpr_cond(Parser* p, SrcLoc loc); +static CConstInt cexpr_unary(Parser* p, SrcLoc loc); +static CConstInt cexpr_cond(Parser* p, SrcLoc loc); static const Type* offsetof_designator(Parser* p, const Type* base, u32* off); -static i64 cexpr_mul(Parser* p, SrcLoc loc) { - i64 v = cexpr_unary(p, loc); +static u32 cint_bits(Parser* p, const Type* ty) { + u32 sz = ty ? c_abi_sizeof(p->abi, ty) : 8u; + if (sz >= 8) return 64; + return sz * 8u; +} + +static u64 cint_mask_for_bits(u32 bits) { + if (bits >= 64) return ~0ull; + return (1ull << bits) - 1ull; +} + +static int cint_signed(Parser* p, const Type* ty) { + if (!ty) return 1; + return c_abi_type_info(p->abi, ty).signed_ != 0; +} + +static CConstInt cint_make(Parser* p, const Type* ty, u64 bits) { + CConstInt v; + u32 nb; + if (!ty) ty = ty_int(p); + nb = cint_bits(p, ty); + v.type = ty; + v.bits = bits & cint_mask_for_bits(nb); + if (ty->kind == TY_BOOL) v.bits = v.bits ? 1u : 0u; + return v; +} + +i64 const_int_as_i64(Parser* p, CConstInt v) { + u32 nb = cint_bits(p, v.type); + u64 mask = cint_mask_for_bits(nb); + u64 u = v.bits & mask; + if (cint_signed(p, v.type) && nb < 64) { + u64 sign = 1ull << (nb - 1u); + if (u & sign) u |= ~mask; + } + return (i64)u; +} + +static CConstInt cint_cast(Parser* p, CConstInt v, const Type* ty) { + const Type* dst = type_unqual(p->pool, ty); + if (!dst || !type_is_int(dst)) { + perr(p, "integer constant expression cast requires integer type"); + } + return cint_make(p, dst, v.bits); +} + +static u32 cint_rank(const Type* ty) { + if (!ty) return 0; + switch ((TypeKind)ty->kind) { + case TY_BOOL: + return 1; + case TY_CHAR: + case TY_SCHAR: + case TY_UCHAR: + return 2; + case TY_SHORT: + case TY_USHORT: + return 3; + case TY_INT: + case TY_UINT: + case TY_ENUM: + return 4; + case TY_LONG: + case TY_ULONG: + return 5; + case TY_LLONG: + case TY_ULLONG: + return 6; + case TY_INT128: + case TY_UINT128: + return 7; + default: + return 0; + } +} + +static const Type* cint_unsigned_variant(Parser* p, const Type* ty) { + if (!ty) return type_prim(p->pool, TY_UINT); + switch ((TypeKind)ty->kind) { + case TY_BOOL: + case TY_CHAR: + case TY_SCHAR: + case TY_UCHAR: + case TY_SHORT: + case TY_USHORT: + case TY_INT: + case TY_UINT: + case TY_ENUM: + return type_prim(p->pool, TY_UINT); + case TY_LONG: + case TY_ULONG: + return type_prim(p->pool, TY_ULONG); + case TY_LLONG: + case TY_ULLONG: + return type_prim(p->pool, TY_ULLONG); + case TY_INT128: + case TY_UINT128: + return type_prim(p->pool, TY_UINT128); + default: + return type_prim(p->pool, TY_UINT); + } +} + +static const Type* cint_promote_type(Parser* p, const Type* ty) { + const Type* u = type_unqual(p->pool, ty); + if (u && u->kind == TY_ENUM) return type_prim(p->pool, TY_INT); + return type_promoted(p->pool, u); +} + +static const Type* cint_common_type(Parser* p, const Type* a, const Type* b) { + const Type* ap = cint_promote_type(p, a); + const Type* bp = cint_promote_type(p, b); + int as = cint_signed(p, ap); + int bs = cint_signed(p, bp); + u32 ar = cint_rank(ap); + u32 br = cint_rank(bp); + if (type_compatible(ap, bp)) return ap; + if (as == bs) return ar >= br ? ap : bp; + if (!as && ar >= br) return ap; + if (!bs && br >= ar) return bp; + if (as && cint_bits(p, ap) > cint_bits(p, bp)) return ap; + if (bs && cint_bits(p, bp) > cint_bits(p, ap)) return bp; + return cint_unsigned_variant(p, as ? ap : bp); +} + +static CConstInt cint_convert(Parser* p, CConstInt v, const Type* ty) { + return cint_make(p, ty, v.bits); +} + +static int cint_truth(Parser* p, CConstInt v) { + (void)p; + return v.bits != 0; +} + +static CConstInt cint_bool(Parser* p, int truth) { + return cint_make(p, ty_int(p), truth ? 1u : 0u); +} + +static CConstInt cexpr_mul(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_unary(p, loc); for (;;) { - if (accept_punct(p, '*')) - v = v * cexpr_unary(p, loc); - else if (accept_punct(p, '/')) { - i64 r = cexpr_unary(p, loc); - if (r == 0) compiler_panic(p->c, loc, "division by zero in constant"); - v = v / r; + int op = 0; + CConstInt r; + const Type* ct; + if (accept_punct(p, '*')) { + op = '*'; + } else if (accept_punct(p, '/')) { + op = '/'; } else if (accept_punct(p, '%')) { - i64 r = cexpr_unary(p, loc); - if (r == 0) compiler_panic(p->c, loc, "modulo by zero in constant"); - v = v % r; - } else + op = '%'; + } else { break; + } + r = cexpr_unary(p, loc); + ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + if (op == '*') { + v = cint_make(p, ct, v.bits * r.bits); + } else { + if (r.bits == 0) + compiler_panic(p->c, loc, op == '/' ? "division by zero in constant" + : "modulo by zero in constant"); + if (cint_signed(p, ct)) { + i64 lv = const_int_as_i64(p, v); + i64 rv = const_int_as_i64(p, r); + v = cint_make(p, ct, op == '/' ? (u64)(lv / rv) : (u64)(lv % rv)); + } else { + v = cint_make(p, ct, op == '/' ? v.bits / r.bits : v.bits % r.bits); + } + } } return v; } -static i64 cexpr_add(Parser* p, SrcLoc loc) { - i64 v = cexpr_mul(p, loc); +static CConstInt cexpr_add(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_mul(p, loc); for (;;) { - if (accept_punct(p, '+')) - v = v + cexpr_mul(p, loc); - else if (accept_punct(p, '-')) - v = v - cexpr_mul(p, loc); - else + int sub = 0; + CConstInt r; + const Type* ct; + if (accept_punct(p, '+')) { + sub = 0; + } else if (accept_punct(p, '-')) { + sub = 1; + } else { break; + } + r = cexpr_mul(p, loc); + ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + v = cint_make(p, ct, sub ? v.bits - r.bits : v.bits + r.bits); } return v; } -static i64 cexpr_shift(Parser* p, SrcLoc loc) { - i64 v = cexpr_add(p, loc); +static CConstInt cexpr_shift(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_add(p, loc); for (;;) { - if (accept_punct(p, P_SHL)) - v = v << cexpr_add(p, loc); - else if (accept_punct(p, P_SHR)) - v = v >> cexpr_add(p, loc); - else + int left = 0; + CConstInt r; + i64 sh; + const Type* vt; + if (accept_punct(p, P_SHL)) { + left = 1; + } else if (accept_punct(p, P_SHR)) { + left = 0; + } else { break; + } + r = cexpr_add(p, loc); + vt = cint_promote_type(p, v.type); + v = cint_convert(p, v, vt); + sh = const_int_as_i64(p, r); + if (sh < 0 || sh >= (i64)cint_bits(p, vt)) + perr(p, "shift count out of range in constant expression"); + if (left) { + if (cint_signed(p, vt) && const_int_as_i64(p, v) < 0) + perr(p, "left shift of negative value in constant expression"); + v = cint_make(p, vt, v.bits << (u32)sh); + } else if (cint_signed(p, vt)) { + v = cint_make(p, vt, (u64)(const_int_as_i64(p, v) >> (u32)sh)); + } else { + v = cint_make(p, vt, v.bits >> (u32)sh); + } } return v; } -static i64 cexpr_rel(Parser* p, SrcLoc loc) { - i64 v = cexpr_shift(p, loc); +static CConstInt cexpr_rel(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_shift(p, loc); for (;;) { - if (accept_punct(p, P_LE)) - v = v <= cexpr_shift(p, loc); - else if (accept_punct(p, P_GE)) - v = v >= cexpr_shift(p, loc); - else if (is_punct(&p->cur, '<')) { + int op = 0; + CConstInt r; + const Type* ct; + int res; + if (accept_punct(p, P_LE)) { + op = P_LE; + } else if (accept_punct(p, P_GE)) { + op = P_GE; + } else if (is_punct(&p->cur, '<')) { advance(p); - v = v < cexpr_shift(p, loc); + op = '<'; } else if (is_punct(&p->cur, '>')) { advance(p); - v = v > cexpr_shift(p, loc); - } else + op = '>'; + } else { break; + } + r = cexpr_shift(p, loc); + ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + if (cint_signed(p, ct)) { + i64 lv = const_int_as_i64(p, v); + i64 rv = const_int_as_i64(p, r); + res = op == P_LE ? lv <= rv + : op == P_GE ? lv >= rv + : op == '<' ? lv < rv + : lv > rv; + } else { + res = op == P_LE ? v.bits <= r.bits + : op == P_GE ? v.bits >= r.bits + : op == '<' ? v.bits < r.bits + : v.bits > r.bits; + } + v = cint_bool(p, res); } return v; } -static i64 cexpr_eq(Parser* p, SrcLoc loc) { - i64 v = cexpr_rel(p, loc); +static CConstInt cexpr_eq(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_rel(p, loc); for (;;) { - if (accept_punct(p, P_EQ)) - v = (v == cexpr_rel(p, loc)); - else if (accept_punct(p, P_NE)) - v = (v != cexpr_rel(p, loc)); - else + int ne = 0; + CConstInt r; + const Type* ct; + if (accept_punct(p, P_EQ)) { + ne = 0; + } else if (accept_punct(p, P_NE)) { + ne = 1; + } else { break; + } + r = cexpr_rel(p, loc); + ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + v = cint_bool(p, ne ? v.bits != r.bits : v.bits == r.bits); } return v; } -static i64 cexpr_band(Parser* p, SrcLoc loc) { - i64 v = cexpr_eq(p, loc); +static CConstInt cexpr_band(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_eq(p, loc); while (is_punct(&p->cur, '&') && !is_punct(&p->cur, P_AND)) { + CConstInt r; + const Type* ct; advance(p); - v = v & cexpr_eq(p, loc); + r = cexpr_eq(p, loc); + ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + v = cint_make(p, ct, v.bits & r.bits); } return v; } -static i64 cexpr_bxor(Parser* p, SrcLoc loc) { - i64 v = cexpr_band(p, loc); - while (accept_punct(p, '^')) v = v ^ cexpr_band(p, loc); +static CConstInt cexpr_bxor(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_band(p, loc); + while (accept_punct(p, '^')) { + CConstInt r = cexpr_band(p, loc); + const Type* ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + v = cint_make(p, ct, v.bits ^ r.bits); + } return v; } -static i64 cexpr_bor(Parser* p, SrcLoc loc) { - i64 v = cexpr_bxor(p, loc); +static CConstInt cexpr_bor(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_bxor(p, loc); while (is_punct(&p->cur, '|') && !is_punct(&p->cur, P_OR)) { + CConstInt r; + const Type* ct; advance(p); - v = v | cexpr_bxor(p, loc); + r = cexpr_bxor(p, loc); + ct = cint_common_type(p, v.type, r.type); + v = cint_convert(p, v, ct); + r = cint_convert(p, r, ct); + v = cint_make(p, ct, v.bits | r.bits); } return v; } -static i64 cexpr_land(Parser* p, SrcLoc loc) { - i64 v = cexpr_bor(p, loc); +static CConstInt cexpr_land(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_bor(p, loc); while (accept_punct(p, P_AND)) { - i64 r = cexpr_bor(p, loc); - v = (v != 0 && r != 0) ? 1 : 0; + CConstInt r = cexpr_bor(p, loc); + v = cint_bool(p, cint_truth(p, v) && cint_truth(p, r)); } return v; } -static i64 cexpr_lor(Parser* p, SrcLoc loc) { - i64 v = cexpr_land(p, loc); +static CConstInt cexpr_lor(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_land(p, loc); while (accept_punct(p, P_OR)) { - i64 r = cexpr_land(p, loc); - v = (v != 0 || r != 0) ? 1 : 0; + CConstInt r = cexpr_land(p, loc); + v = cint_bool(p, cint_truth(p, v) || cint_truth(p, r)); } return v; } -static i64 cexpr_cond(Parser* p, SrcLoc loc) { - i64 v = cexpr_lor(p, loc); +static CConstInt cexpr_cond(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_lor(p, loc); if (accept_punct(p, '?')) { - i64 then_v = cexpr_cond(p, loc); - i64 else_v; + CConstInt then_v = cexpr_cond(p, loc); + CConstInt else_v; + const Type* ct; expect_punct(p, ':', "':' in constant conditional expression"); else_v = cexpr_cond(p, loc); - v = v ? then_v : else_v; + ct = cint_common_type(p, then_v.type, else_v.type); + then_v = cint_convert(p, then_v, ct); + else_v = cint_convert(p, else_v, ct); + v = cint_truth(p, v) ? then_v : else_v; } return v; } -static i64 cexpr_unary(Parser* p, SrcLoc loc) { +static CConstInt cexpr_unary(Parser* p, SrcLoc loc) { if (accept_punct(p, '+')) return cexpr_unary(p, loc); - if (accept_punct(p, '-')) return -cexpr_unary(p, loc); - if (accept_punct(p, '~')) return ~cexpr_unary(p, loc); - if (accept_punct(p, '!')) return cexpr_unary(p, loc) ? 0 : 1; + if (accept_punct(p, '-')) { + CConstInt v = cexpr_unary(p, loc); + const Type* pt = cint_promote_type(p, v.type); + v = cint_convert(p, v, pt); + return cint_make(p, pt, (u64)(-const_int_as_i64(p, v))); + } + if (accept_punct(p, '~')) { + CConstInt v = cexpr_unary(p, loc); + const Type* pt = cint_promote_type(p, v.type); + v = cint_convert(p, v, pt); + return cint_make(p, pt, ~v.bits); + } + if (accept_punct(p, '!')) return cint_bool(p, !cint_truth(p, cexpr_unary(p, loc))); if (accept_kw(p, KW_SIZEOF)) { if (is_punct(&p->cur, '(')) { Tok n = peek1(p); @@ -527,7 +837,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { const Type* t = parse_type_name(p); expect_punct(p, ')', "')' after sizeof type-name"); require_sizeof_type(p, t); - return (i64)c_abi_sizeof(p->abi, t); + return cint_make(p, ty_size_t(p), c_abi_sizeof(p->abi, t)); } } } @@ -538,7 +848,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { require_sizeof_type(p, ty); i64 sz = (i64)c_abi_sizeof(p->abi, ty); cg_drop(p->cg); - return sz; + return cint_make(p, ty_size_t(p), (u64)sz); } } if (accept_kw(p, KW_ALIGNOF)) { @@ -549,7 +859,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { { const Type* t = parse_type_name(p); expect_punct(p, ')', "')' after _Alignof type-name"); - return (i64)c_abi_alignof(p->abi, t); + return cint_make(p, ty_size_t(p), c_abi_alignof(p->abi, t)); } } } @@ -558,7 +868,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { const Type* ty = cg_top_type(p->cg); i64 al = (i64)c_abi_alignof(p->abi, ty); cg_drop(p->cg); - return al; + return cint_make(p, ty_size_t(p), (u64)al); } } if (accept_punct(p, '(')) { @@ -566,37 +876,36 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { const Type* t = parse_type_name(p); expect_punct(p, ')', "')' after cast type-name"); { - i64 v = cexpr_unary(p, loc); - u32 sz = c_abi_sizeof(p->abi, t); - int is_signed = c_abi_type_info(p->abi, t).signed_; - if (sz < 8) { - u64 mask = (1ull << (sz * 8u)) - 1ull; - u64 uv = (u64)v & mask; - if (is_signed) { - u64 sign = 1ull << (sz * 8u - 1u); - v = (i64)((uv ^ sign) - sign); - } else { - v = (i64)uv; + const Type* tu = type_unqual(p->pool, t); + if (p->cur.kind == TOK_FLT) { + double fv; + if (!tu || !type_is_int(tu)) { + perr(p, "integer constant expression cast requires integer type"); } + fv = parse_float_literal(p, &p->cur); + advance(p); + return cint_make(p, tu, (u64)(i64)fv); } - return v; + CConstInt v = cexpr_unary(p, loc); + return cint_cast(p, v, t); } } { - i64 v = cexpr_cond(p, loc); + CConstInt v = cexpr_cond(p, loc); expect_punct(p, ')', "')' in constant expression"); return v; } } if (p->cur.kind == TOK_NUM) { i64 v = parse_int_literal(p, &p->cur); + const Type* ty = int_literal_type(p, &p->cur); advance(p); - return v; + return cint_make(p, ty, (u64)v); } if (p->cur.kind == TOK_CHR) { i64 v = decode_char_literal(p, &p->cur); advance(p); - return v; + return cint_make(p, ty_int(p), (u64)v); } if (p->cur.kind == TOK_IDENT) { Sym name = p->cur.v.ident; @@ -609,13 +918,13 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { expect_punct(p, ',', "',' in __builtin_offsetof"); (void)offsetof_designator(p, root, &off); expect_punct(p, ')', "')' after __builtin_offsetof"); - return (i64)off; + return cint_make(p, ty_size_t(p), off); } { SymEntry* e = scope_lookup(p, name); if (e && e->kind == SEK_ENUM_CST) { advance(p); - return e->v.enum_value; + return cint_make(p, e->type ? e->type : ty_int(p), (u64)e->v.enum_value); } } compiler_panic(p->c, loc, "non-constant identifier in constant expression"); @@ -623,7 +932,15 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { compiler_panic(p->c, loc, "expected constant expression"); } -i64 eval_const_int(Parser* p, SrcLoc loc) { return cexpr_cond(p, loc); } +CConstInt eval_const_int_typed(Parser* p, SrcLoc loc) { + CConstInt v = cexpr_cond(p, loc); + if (!type_is_int(v.type)) perr(p, "integer constant expression required"); + return v; +} + +i64 eval_const_int(Parser* p, SrcLoc loc) { + return const_int_as_i64(p, eval_const_int_typed(p, loc)); +} /* ============================================================ * to_rvalue @@ -883,6 +1200,129 @@ static const Type* offsetof_designator(Parser* p, const Type* base, u32* off) { return cur; } +typedef struct BuiltinOverflowInfo { + CfreeCgIntrinsic intrin; + TypeKind type_kind; + const char* name; +} BuiltinOverflowInfo; + +static int sym_eq_cstr(Parser* p, Sym sym, const char* want) { + size_t got_len = 0; + size_t want_len = strlen(want); + const char* got = pool_str(p->pool, sym, &got_len); + return got && got_len == want_len && memcmp(got, want, want_len) == 0; +} + +static int builtin_overflow_info(Parser* p, Sym name, + BuiltinOverflowInfo* out) { + static const BuiltinOverflowInfo infos[] = { + {CFREE_CG_INTRIN_SADD_OVERFLOW, TY_INT, "__builtin_sadd_overflow"}, + {CFREE_CG_INTRIN_SADD_OVERFLOW, TY_LONG, "__builtin_saddl_overflow"}, + {CFREE_CG_INTRIN_SADD_OVERFLOW, TY_LLONG, "__builtin_saddll_overflow"}, + {CFREE_CG_INTRIN_UADD_OVERFLOW, TY_UINT, "__builtin_uadd_overflow"}, + {CFREE_CG_INTRIN_UADD_OVERFLOW, TY_ULONG, "__builtin_uaddl_overflow"}, + {CFREE_CG_INTRIN_UADD_OVERFLOW, TY_ULLONG, "__builtin_uaddll_overflow"}, + {CFREE_CG_INTRIN_SSUB_OVERFLOW, TY_INT, "__builtin_ssub_overflow"}, + {CFREE_CG_INTRIN_SSUB_OVERFLOW, TY_LONG, "__builtin_ssubl_overflow"}, + {CFREE_CG_INTRIN_SSUB_OVERFLOW, TY_LLONG, "__builtin_ssubll_overflow"}, + {CFREE_CG_INTRIN_USUB_OVERFLOW, TY_UINT, "__builtin_usub_overflow"}, + {CFREE_CG_INTRIN_USUB_OVERFLOW, TY_ULONG, "__builtin_usubl_overflow"}, + {CFREE_CG_INTRIN_USUB_OVERFLOW, TY_ULLONG, "__builtin_usubll_overflow"}, + {CFREE_CG_INTRIN_SMUL_OVERFLOW, TY_INT, "__builtin_smul_overflow"}, + {CFREE_CG_INTRIN_SMUL_OVERFLOW, TY_LONG, "__builtin_smull_overflow"}, + {CFREE_CG_INTRIN_SMUL_OVERFLOW, TY_LLONG, "__builtin_smulll_overflow"}, + {CFREE_CG_INTRIN_UMUL_OVERFLOW, TY_UINT, "__builtin_umul_overflow"}, + {CFREE_CG_INTRIN_UMUL_OVERFLOW, TY_ULONG, "__builtin_umull_overflow"}, + {CFREE_CG_INTRIN_UMUL_OVERFLOW, TY_ULLONG, "__builtin_umulll_overflow"}, + }; + size_t i; + for (i = 0; i < sizeof(infos) / sizeof(infos[0]); ++i) { + if (sym_eq_cstr(p, name, infos[i].name)) { + if (out) *out = infos[i]; + return 1; + } + } + return 0; +} + +static FrameSlot builtin_tmp_slot(Parser* p, const Type* ty) { + FrameSlotDesc fsd; + memset(&fsd, 0, sizeof fsd); + fsd.type = ty; + fsd.size = c_abi_sizeof(p->abi, ty); + fsd.align = c_abi_alignof(p->abi, ty); + fsd.kind = FS_LOCAL; + return cg_local(p->cg, &fsd); +} + +static int parse_builtin_overflow_call(Parser* p, Sym name, SrcLoc loc) { + BuiltinOverflowInfo info; + const Type* op_ty; + const Type* ptr_ty; + const Type* out_ty; + const Type* bool_ty; + FrameSlot ptr_slot; + FrameSlot ov_slot; + if (!builtin_overflow_info(p, name, &info)) return 0; + + op_ty = type_prim(p->pool, info.type_kind); + bool_ty = type_prim(p->pool, TY_BOOL); + + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after overflow builtin"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, op_ty); + expect_punct(p, ',', "',' in overflow builtin"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, op_ty); + expect_punct(p, ',', "',' in overflow builtin"); + parse_assign_expr(p); + to_rvalue(p); + ptr_ty = cg_top_type(p->cg); + if (!ptr_ty || ptr_ty->kind != TY_PTR) { + perr(p, "overflow builtin result argument must be a pointer"); + } + out_ty = ptr_ty->ptr.pointee; + if (!type_compatible(type_unqual(p->pool, out_ty), op_ty)) { + perr(p, "overflow builtin result pointer type mismatch"); + } + expect_punct(p, ')', "')' after overflow builtin"); + + ptr_slot = builtin_tmp_slot(p, ptr_ty); + cg_push_local_typed(p->cg, ptr_slot, ptr_ty); + cg_swap(p->cg); + cg_store(p->cg); + cg_drop(p->cg); + + cg_set_loc(p->cg, loc); + if (pcg_emit_enabled(p)) { + cfree_cg_intrinsic(p->cg, info.intrin, 2, pcg_tid(p->c, op_ty)); + } + pcg_drop_type(p); + pcg_drop_type(p); + pcg_push_type(p, op_ty); + pcg_push_type(p, bool_ty); + + ov_slot = builtin_tmp_slot(p, bool_ty); + cg_push_local_typed(p->cg, ov_slot, bool_ty); + cg_swap(p->cg); + cg_store(p->cg); + cg_drop(p->cg); + + cg_push_local_typed(p->cg, ptr_slot, ptr_ty); + cg_load(p->cg); + cg_deref(p->cg, out_ty); + cg_swap(p->cg); + cg_store(p->cg); + cg_drop(p->cg); + + cg_push_local_typed(p->cg, ov_slot, bool_ty); + cg_load(p->cg); + return 1; +} + static int try_parse_builtin_call(Parser* p) { Sym name = p->cur.v.ident; SrcLoc loc = p->cur.loc; @@ -892,6 +1332,8 @@ static int try_parse_builtin_call(Parser* p) { return parse_builtin_mem_call(p, name, loc); } + if (parse_builtin_overflow_call(p, name, loc)) return 1; + if (name != p->sym_b_alloca && name != p->sym_b_ctz && name != p->sym_b_ctzl && name != p->sym_b_ctzll && name != p->sym_b_clz && name != p->sym_b_clzl && name != p->sym_b_clzll && diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -416,7 +416,13 @@ void parse_expr(Parser* p); void parse_assign_expr(Parser* p); void parse_cond_expr(Parser* p); void parse_unary(Parser* p); +typedef struct CConstInt { + const Type* type; + u64 bits; +} CConstInt; +CConstInt eval_const_int_typed(Parser* p, SrcLoc loc); i64 eval_const_int(Parser* p, SrcLoc loc); +i64 const_int_as_i64(Parser* p, CConstInt v); i64 parse_int_literal(Parser* p, const Tok* t); i64 decode_char_literal(Parser* p, const Tok* t); u8* decode_string_literal(Parser* p, const Tok* t, size_t* nlen_out); diff --git a/test/parse/cases/6_6_11_unsigned_const_expr.c b/test/parse/cases/6_6_11_unsigned_const_expr.c @@ -0,0 +1,11 @@ +_Static_assert(0u - 1u > 1u, "unsigned constants wrap modulo width"); +_Static_assert(!(-1 < 1u), "usual arithmetic conversions choose unsigned"); +_Static_assert(2147483648 > 0, "decimal constants choose a wider signed type"); +_Static_assert(0xffffffff > 0, "hex constants may choose unsigned int"); +_Static_assert((int)1.75 == 1, "floating constant may be cast in an ICE"); + +enum { K = (unsigned char)260 + (1 ? 38 : 0) }; + +int test_main(void) { + return K; +} diff --git a/test/parse/cases/6_6_11_unsigned_const_expr.expected b/test/parse/cases/6_6_11_unsigned_const_expr.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases_err/6_6_shift_count_out_of_range.c b/test/parse/cases_err/6_6_shift_count_out_of_range.c @@ -0,0 +1,5 @@ +enum { X = 1 << 32 }; + +int test_main(void) { + return X; +} diff --git a/test/parse/cases_err/6_6_shift_count_out_of_range.errpat b/test/parse/cases_err/6_6_shift_count_out_of_range.errpat @@ -0,0 +1 @@ +shift count out of range in constant expression