kit

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

commit f7ac167dc65d43ed7b189508db8a2f6a4a82f14a
parent fb02a54edf304b1e8cad51f7985b123e1e809e2e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 15:20:47 -0700

Tighten C11 parse constraints

Diffstat:
Mdoc/C11_CONFORMANCE_CHECKLIST.md | 31+++++++++++++------------------
Adoc/RT_CFREERT_CHECKLIST.md | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/cfree/cg.h | 4++++
Mlang/c/parse/cg_adapter.c | 227++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mlang/c/parse/cg_public_compat.h | 51+++++++++++++++++++++++++++++++++++++++------------
Mlang/c/parse/parse.c | 6++++++
Mlang/c/parse/parse_expr.c | 421++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mlang/c/parse/parse_init.c | 8++++++--
Mlang/c/parse/parse_priv.h | 11+++++++++++
Mlang/c/parse/parse_stmt.c | 32++++++++++++++++++++++++++++++++
Mlang/c/parse/parse_type.c | 2++
Mrt/Makefile | 17+++++++++++++++--
Mrt/lib/coro/aarch64.c | 106++-----------------------------------------------------------------------------
Art/lib/coro/aarch64_elf.s | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Art/lib/coro/aarch64_macho.s | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/cg.c | 16++++++++++++++++
Mtest/parse/CORPUS.md | 4++++
Atest/parse/cases/builtin_22_ctz_long_widths.c | 5+++++
Atest/parse/cases/builtin_22_ctz_long_widths.expected | 1+
Atest/parse/cases/builtin_23_atomic_long_literal_convert.c | 6++++++
Atest/parse/cases/builtin_23_atomic_long_literal_convert.expected | 1+
Atest/parse/cases/builtin_24_atomic_lock_free.c | 20++++++++++++++++++++
Atest/parse/cases/builtin_24_atomic_lock_free.expected | 1+
Atest/parse/cases/builtin_25_atomic_fetch_nand.c | 6++++++
Atest/parse/cases/builtin_25_atomic_fetch_nand.expected | 1+
25 files changed, 986 insertions(+), 240 deletions(-)

diff --git a/doc/C11_CONFORMANCE_CHECKLIST.md b/doc/C11_CONFORMANCE_CHECKLIST.md @@ -10,13 +10,9 @@ 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. -- [ ] `make test-parse-err` passes with expanded C11 constraint coverage: - currently 48/56 pass, 8 fail. Current red cases are - `6_5_addr_of_bitfield`, `6_5_cast_scalar_from_struct`, - `6_5_cond_incompatible_ptr`, `6_5_pointer_add_pointer`, - `6_5_rel_incompatible_ptr`, `6_5_scalar_required_if`, - `6_5_sizeof_bitfield`, and `6_7_2_1_bitfield_bad_type`. -- [ ] `make test-parse` passes without skips: currently 2504 pass, 0 fail, +- [x] `make test-parse-err` passes with expanded C11 constraint coverage: + currently 56/56 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. - [ ] `make rt` builds the default runtime archives. Currently fails in @@ -71,9 +67,9 @@ are implemented. - [x] Reject non-power-of-two positive `aligned(N)` values. Test: `test/parse/cases_err/attr_p2_aligned_not_pow2.c`. Code: attribute argument parsing in `parse_type.c`. -- [ ] Reject the newly covered expression/type constraint failures now exposed +- [x] Reject the newly covered expression/type constraint failures now exposed by `test/parse/cases_err/6_5_*`. - Current red: address of bit-field, cast struct to scalar, incompatible + Covered cases: address of bit-field, cast struct to scalar, incompatible conditional pointer arms, pointer-plus-pointer, incompatible pointer relational compare, struct used as scalar condition, and `sizeof` on a bit-field. @@ -81,9 +77,8 @@ are implemented. non-constant static initializer, excess scalar initializer, invalid array/struct/union designators, wrong-kind tag redeclaration, functions returning array/function, and variadic marker not last. -- [ ] Decide whether floating-point bit-fields are an extension. The - conformance test `6_7_2_1_bitfield_bad_type` currently expects rejection - and is red because `float f : 1` is accepted. +- [x] Reject non-integer bit-field types. + Test: `test/parse/cases_err/6_7_2_1_bitfield_bad_type.c`. Suggested cadence: @@ -143,7 +138,7 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse - [ ] 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 is still red. + cases pass; `float` bit-field rejection now passes. ## Expressions and conversions @@ -157,11 +152,11 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse no incomplete object type, no function type, no bit-field, VLA operand evaluated, non-VLA operand not evaluated. New coverage: `sizeof(function)` is rejected and VLA/deref pointer - positive cases pass; `sizeof(bit-field)` is red because it is accepted. + positive cases pass; `sizeof(bit-field)` is rejected. - [ ] Complete conditional operator usual-conversion behavior for arithmetic and pointer/null arms. Positive arithmetic and pointer/null cases pass; incompatible pointer - arms are red because they are accepted. + arms are rejected. - [ ] Complete pointer compound assignment (`p += n`, `p -= n`). Positive `p += n` coverage passes. - [ ] Expand `_Generic` tests for default selection, compatible types, and @@ -170,9 +165,9 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse - [ ] Add negative tests for invalid pointer arithmetic, invalid relational comparisons, invalid casts, modifying non-lvalues, and scalar-required operators. - New negative tests are checked in. Current red: pointer-plus-pointer, - incompatible pointer relational compare, struct-to-int cast, and struct - condition accepted; array assignment is rejected. + New negative tests are checked in. Pointer-plus-pointer, incompatible + pointer relational compare, struct-to-int cast, struct condition, and + array assignment are rejected. ## Constant expressions and initializers diff --git a/doc/RT_CFREERT_CHECKLIST.md b/doc/RT_CFREERT_CHECKLIST.md @@ -0,0 +1,107 @@ +# Building libcfree_rt.a With cfree + +Goal: build the runtime archive with cfree's own `cc`, `as`, and `ar` instead +of clang/llvm-ar. This is separate from stage-2 self-hosting of the main +compiler binary. + +Current focused probe: `aarch64-apple-darwin`. This variant is useful because +it covers LP64, int128 declarations, coroutine assembly, Mach-O symbol names, +and the freestanding runtime headers without requiring a system SDK. + +## Probe Command + +```sh +make bin +rm -rf rt/build/aarch64-apple-darwin +env CC="$PWD/build/cfree cc" \ + RT_AS="$PWD/build/cfree as" \ + RT_AR="$PWD/build/cfree ar" \ + make -e -k rt-aarch64-apple-darwin \ + RT_COMMON_CFLAGS= \ + RT_AS_COMPILE_FLAGS= +``` + +`RT_COMMON_CFLAGS=` drops flags cfree does not accept yet: +`-ffreestanding -fno-builtin -std=c11 -Wpedantic -Wall -Wextra -Werror`. +`RT_AS_COMPILE_FLAGS=` drops clang's `-c`, which `cfree as` does not use. + +## Current Status + +As of this probe, cfree builds or assembles: + +- [x] `rt/lib/int/int.c` +- [ ] `rt/lib/fp/fp.c` +- [x] `rt/lib/mem/mem.c` +- [x] `rt/lib/atomic/atomic_freestanding.c` +- [x] `rt/lib/cfree/ifunc_init.c` +- [x] `rt/lib/int64/int64.c` +- [x] `rt/lib/coro/aarch64.c` +- [x] `rt/lib/coro/coro.c` +- [x] `rt/lib/coro/aarch64_macho.s` + +That is 8 / 9 compile or assemble steps green for this variant. The archive +does not build yet because `rt/lib/fp/fp.c` still fails. + +## Completed + +- [x] Split AArch64 file-scope coroutine assembly out of + `rt/lib/coro/aarch64.c`. +- [x] Added standalone AArch64 coroutine assembly sources: + `rt/lib/coro/aarch64_elf.s` and `rt/lib/coro/aarch64_macho.s`. +- [x] Added runtime Makefile support for assembling `.s` / `.S` sources via + `RT_AS`, so cfree can use `cfree as` while the default clang build can keep + using `clang -c`. +- [x] Switched the runtime target flag from `--target=TRIPLE` to + `-target TRIPLE`, accepted by both clang and cfree. +- [x] Wired `__builtin_ctzl` and `__builtin_ctzll` through the existing + `INTRIN_CTZ` path. `rt/lib/int64/int64.c` now compiles in the focused + aarch64-apple-darwin cfree probe. +- [x] Converted `__atomic_*_n` value operands to the atomic object type before + lowering. This clears the pointer-sized literal mismatch in + `__atomic_store_n((uintptr_t*)l, 0, __ATOMIC_RELEASE)` and the same class for + RMW / compare-exchange desired operands. +- [x] Added target-aware folding for `__atomic_always_lock_free(size, ptr)` and + constant-size `__atomic_is_lock_free(size, ptr)`. The parser asks + `cfree_cg_atomic_is_lock_free` for representative scalar types, so results + follow the active target instead of an aarch64-only table. +- [x] Exposed a general `cfree_cg_top_const_int` query for compile-time-known + integer-like values on the CG value stack. +- [x] Used that query in parser `if`, `&&`, and `||` lowering. Dead arms are + still parsed for semantic/type-stack effects, but target code emission is + suppressed, so constant-false 16-byte atomic fast paths no longer trip the + 8-byte lock-free backend limit. +- [x] Lowered `__builtin_memcpy`, `__builtin_memmove`, `__builtin_memset`, and + `__builtin_memcmp` as builtins even when their byte count is runtime-sized. + The parser now synthesizes the standard libc call directly, instead of + rewriting the token to an undeclared plain identifier. +- [x] Added parser support for `__atomic_fetch_nand`, mapped through the + existing target-independent atomic NAND RMW operation. + +## Remaining Blockers + +- [ ] Fix the `rt/lib/fp/fp.c` compiler crash. + Current symptom: cfree segfaults while compiling this file; lldb stops in + `cfree_arena_alloc`. Reduce to the smallest function or included helper that + reproduces the stack growth / allocator crash. + +- [ ] Lift remaining coroutine file-scope assembly. + Current file-scope asm remains in: + `rt/lib/coro/x86_64.c`, `rt/lib/coro/x86_64_win.c`, + `rt/lib/coro/i386.c`, `rt/lib/coro/riscv64.c`, + `rt/lib/coro/riscv32.c`, `rt/lib/coro/arm32.c`, and + `rt/lib/coro/arm32_thumb1.c`. + +- [ ] Decide whether cfree should eventually accept the standard runtime C + flags, or whether the runtime Makefile should grow a first-class + cfree-toolchain mode that drops/translates them. + +- [ ] Run the same cfree-toolchain probe for the other default runtime + variants after `aarch64-apple-darwin` archives cleanly. + +## Notes + +The ordinary clang `rt-aarch64-apple-darwin` target currently also stops in +`atomic_freestanding.c` because clang treats several `__atomic_*` library +entry points as builtins. That is separate from the cfree bootstrap path, but +it means the default target is not a clean regression signal for the assembly +split until the atomic source/build flags are addressed. diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -483,6 +483,10 @@ void cfree_cg_swap(CfreeCg*); void cfree_cg_drop(CfreeCg*); void cfree_cg_rot3(CfreeCg*); /* [..., a, b, c] -> [..., b, c, a] */ +/* Returns nonzero when the current top-of-stack value is a compile-time known + * integer-like immediate. The value is not popped. */ +int cfree_cg_top_const_int(CfreeCg*, int64_t* out_value); + void cfree_cg_push_int(CfreeCg*, uint64_t value, CfreeCgTypeId type); void cfree_cg_push_float(CfreeCg*, double value, CfreeCgTypeId type); void cfree_cg_push_null(CfreeCg*, CfreeCgTypeId ptr_type); diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -12,6 +12,8 @@ static u32 pcg_alignof(Parser* p, const Type* ty) { return (u32)cfree_cg_type_align(p->c, pcg_tid(p->c, ty)); } +#define PCG_VALUE_BITFIELD 1u + CfreeCgMemAccess pcg_mem(Parser* p, const Type* ty) { CfreeCgMemAccess m; memset(&m, 0, sizeof m); @@ -23,22 +25,31 @@ CfreeCgMemAccess pcg_mem(Parser* p, const Type* ty) { static void pcg_stack_grow(Parser* p, u32 want) { const Type** ns; + u8* nf; u32 nc; if (p->cg_type_cap >= want) return; nc = p->cg_type_cap ? p->cg_type_cap * 2u : 64u; while (nc < want) nc *= 2u; ns = arena_array(p->pool->arena, const Type*, nc); if (!ns) perr(p, "out of memory in CG type stack"); + nf = arena_zarray(p->pool->arena, u8, nc); + if (!nf) perr(p, "out of memory in CG value stack"); if (p->cg_type_stack && p->cg_type_sp) { memcpy(ns, p->cg_type_stack, sizeof(*ns) * p->cg_type_sp); } + if (p->cg_value_flags && p->cg_type_sp) { + memcpy(nf, p->cg_value_flags, sizeof(*nf) * p->cg_type_sp); + } p->cg_type_stack = ns; + p->cg_value_flags = nf; p->cg_type_cap = nc; } void pcg_push_type(Parser* p, const Type* ty) { pcg_stack_grow(p, p->cg_type_sp + 1u); - p->cg_type_stack[p->cg_type_sp++] = ty; + p->cg_type_stack[p->cg_type_sp] = ty; + p->cg_value_flags[p->cg_type_sp] = 0; + ++p->cg_type_sp; } void pcg_drop_type(Parser* p) { @@ -47,23 +58,35 @@ void pcg_drop_type(Parser* p) { void pcg_dup_type(Parser* p) { const Type* ty = pcg_top_type(p); + u8 flags = p->cg_type_sp ? p->cg_value_flags[p->cg_type_sp - 1u] : 0; pcg_push_type(p, ty); + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] = flags; } void pcg_swap_type(Parser* p) { if (p->cg_type_sp >= 2) { const Type* a = p->cg_type_stack[p->cg_type_sp - 1u]; + u8 af = p->cg_value_flags[p->cg_type_sp - 1u]; p->cg_type_stack[p->cg_type_sp - 1u] = p->cg_type_stack[p->cg_type_sp - 2u]; + p->cg_value_flags[p->cg_type_sp - 1u] = + p->cg_value_flags[p->cg_type_sp - 2u]; p->cg_type_stack[p->cg_type_sp - 2u] = a; + p->cg_value_flags[p->cg_type_sp - 2u] = af; } } void pcg_rot3_type(Parser* p) { if (p->cg_type_sp >= 3) { const Type* a = p->cg_type_stack[p->cg_type_sp - 3u]; + u8 af = p->cg_value_flags[p->cg_type_sp - 3u]; p->cg_type_stack[p->cg_type_sp - 3u] = p->cg_type_stack[p->cg_type_sp - 2u]; + p->cg_value_flags[p->cg_type_sp - 3u] = + p->cg_value_flags[p->cg_type_sp - 2u]; p->cg_type_stack[p->cg_type_sp - 2u] = p->cg_type_stack[p->cg_type_sp - 1u]; + p->cg_value_flags[p->cg_type_sp - 2u] = + p->cg_value_flags[p->cg_type_sp - 1u]; p->cg_type_stack[p->cg_type_sp - 1u] = a; + p->cg_value_flags[p->cg_type_sp - 1u] = af; } } @@ -76,7 +99,31 @@ const Type* pcg_top2_type(Parser* p) { } void pcg_retag_top(Parser* p, const Type* ty) { - if (p->cg_type_sp) p->cg_type_stack[p->cg_type_sp - 1u] = ty; + if (p->cg_type_sp) { + p->cg_type_stack[p->cg_type_sp - 1u] = ty; + p->cg_value_flags[p->cg_type_sp - 1u] = 0; + } +} + +int pcg_top_is_bitfield(Parser* p) { + return p->cg_type_sp && + (p->cg_value_flags[p->cg_type_sp - 1u] & PCG_VALUE_BITFIELD) != 0; +} + +void pcg_set_top_bitfield(Parser* p) { + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] |= PCG_VALUE_BITFIELD; +} + +int pcg_emit_enabled(Parser* p) { return p && p->suppress_codegen == 0; } + +void pcg_codegen_suppress_push(Parser* p) { + if (p) ++p->suppress_codegen; +} + +void pcg_codegen_suppress_pop(Parser* p) { + if (!p) return; + if (!p->suppress_codegen) perr(p, "internal parser codegen suppression underflow"); + --p->suppress_codegen; } int pcg_type_is_fp(const Type* ty) { @@ -220,6 +267,7 @@ CfreeCgMemOrder pcg_mem_order(MemOrder ord) { return (CfreeCgMemOrder)ord; } FrameSlot pcg_local(Parser* p, const FrameSlotDesc* fsd) { CfreeCgLocalAttrs attrs; memset(&attrs, 0, sizeof attrs); + if (!pcg_emit_enabled(p)) return FRAME_SLOT_NONE; attrs.name = fsd->name; attrs.align = fsd->align; if (fsd->flags & FSF_ADDR_TAKEN) attrs.flags |= CFREE_CG_LOCAL_ADDRESS_TAKEN; @@ -241,34 +289,39 @@ void pcg_param(Parser* p, const CGParamDesc* pd) { } void pcg_func_begin(Parser* p, const CGFuncDesc* fd) { - cfree_cg_func_begin(p->cg, fd->sym); + if (pcg_emit_enabled(p)) cfree_cg_func_begin(p->cg, fd->sym); } void pcg_push_int(Parser* p, i64 v, const Type* ty) { - cfree_cg_push_int(p->cg, (uint64_t)v, pcg_tid(p->c, ty)); + if (pcg_emit_enabled(p)) { + cfree_cg_push_int(p->cg, (uint64_t)v, pcg_tid(p->c, ty)); + } pcg_push_type(p, ty); } void pcg_push_float(Parser* p, double v, const Type* ty) { - cfree_cg_push_float(p->cg, v, pcg_tid(p->c, ty)); + if (pcg_emit_enabled(p)) cfree_cg_push_float(p->cg, v, pcg_tid(p->c, ty)); pcg_push_type(p, ty); } void pcg_push_local_typed(Parser* p, FrameSlot s, const Type* ty) { - cfree_cg_push_local(p->cg, s); + if (pcg_emit_enabled(p)) cfree_cg_push_local(p->cg, s); pcg_push_type(p, ty); } void pcg_push_global(Parser* p, ObjSymId sym, const Type* ty) { - cfree_cg_push_symbol_lvalue(p->cg, sym, 0); + if (pcg_emit_enabled(p)) cfree_cg_push_symbol_lvalue(p->cg, sym, 0); pcg_push_type(p, ty); } -void pcg_load(Parser* p) { cfree_cg_load(p->cg, pcg_mem(p, pcg_top_type(p))); } +void pcg_load(Parser* p) { + if (pcg_emit_enabled(p)) cfree_cg_load(p->cg, pcg_mem(p, pcg_top_type(p))); + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] = 0; +} void pcg_addr(Parser* p) { const Type* ty = pcg_top_type(p); - cfree_cg_addr(p->cg); + if (pcg_emit_enabled(p)) cfree_cg_addr(p->cg); pcg_retag_top(p, type_ptr(p->pool, ty)); } @@ -276,52 +329,59 @@ void pcg_store(Parser* p) { const Type* lv_ty = pcg_top2_type(p); const Type* rv_ty = pcg_top_type(p); const Type* mem_ty = lv_ty; + int emit = pcg_emit_enabled(p); if (rv_ty && type_is_ptr(rv_ty) && (!lv_ty || !type_is_ptr(lv_ty))) { mem_ty = rv_ty; } - cfree_cg_dup(p->cg); + if (emit) cfree_cg_dup(p->cg); pcg_dup_type(p); - cfree_cg_rot3(p->cg); + if (emit) cfree_cg_rot3(p->cg); pcg_rot3_type(p); - cfree_cg_swap(p->cg); + if (emit) cfree_cg_swap(p->cg); pcg_swap_type(p); - cfree_cg_store(p->cg, pcg_mem(p, mem_ty ? mem_ty : rv_ty)); + if (emit) cfree_cg_store(p->cg, pcg_mem(p, mem_ty ? mem_ty : rv_ty)); pcg_drop_type(p); pcg_drop_type(p); pcg_push_type(p, rv_ty); } void pcg_deref(Parser* p, const Type* pointee) { - cfree_cg_indirect(p->cg); + if (pcg_emit_enabled(p)) cfree_cg_indirect(p->cg); pcg_retag_top(p, pointee); } void pcg_binop(Parser* p, BinOp op) { const Type* result = pcg_top2_type(p); - if (op == BO_FADD || op == BO_FSUB || op == BO_FMUL || op == BO_FDIV) - cfree_cg_fp_binop(p->cg, pcg_fp_binop(op), CFREE_CG_FP_NONE); - else - cfree_cg_int_binop(p->cg, pcg_int_binop(op), CFREE_CG_INTOP_NONE); + if (op == BO_FADD || op == BO_FSUB || op == BO_FMUL || op == BO_FDIV) { + if (pcg_emit_enabled(p)) { + cfree_cg_fp_binop(p->cg, pcg_fp_binop(op), CFREE_CG_FP_NONE); + } + } else { + if (pcg_emit_enabled(p)) { + cfree_cg_int_binop(p->cg, pcg_int_binop(op), CFREE_CG_INTOP_NONE); + } + } pcg_drop_type(p); pcg_retag_top(p, result); } void pcg_unop(Parser* p, UnOp op) { if (op == UO_NEG && pcg_type_is_fp(pcg_top_type(p))) { - cfree_cg_fp_unop(p->cg, CFREE_CG_FP_NEG, CFREE_CG_FP_NONE); + if (pcg_emit_enabled(p)) cfree_cg_fp_unop(p->cg, CFREE_CG_FP_NEG, CFREE_CG_FP_NONE); } else { CfreeCgIntUnOp iop = op == UO_NOT ? CFREE_CG_INT_NOT : op == UO_BNOT ? CFREE_CG_INT_BNOT : CFREE_CG_INT_NEG; - cfree_cg_int_unop(p->cg, iop, CFREE_CG_INTOP_NONE); + if (pcg_emit_enabled(p)) cfree_cg_int_unop(p->cg, iop, CFREE_CG_INTOP_NONE); } } void pcg_cmp(Parser* p, CmpOp op) { - if (op == CMP_LT_F || op == CMP_LE_F || op == CMP_GT_F || op == CMP_GE_F) - cfree_cg_fp_cmp(p->cg, pcg_fp_cmp(op)); - else - cfree_cg_int_cmp(p->cg, pcg_int_cmp(op)); + if (op == CMP_LT_F || op == CMP_LE_F || op == CMP_GT_F || op == CMP_GE_F) { + if (pcg_emit_enabled(p)) cfree_cg_fp_cmp(p->cg, pcg_fp_cmp(op)); + } else { + if (pcg_emit_enabled(p)) cfree_cg_int_cmp(p->cg, pcg_int_cmp(op)); + } pcg_drop_type(p); pcg_retag_top(p, type_prim(p->pool, TY_BOOL)); } @@ -335,72 +395,96 @@ void pcg_convert(Parser* p, const Type* dst) { int sf = pcg_type_is_fp(src); int df = pcg_type_is_fp(dst); CfreeCgTypeId id = pcg_tid(p->c, dst); + int emit = pcg_emit_enabled(p); if (src == dst) return; if (type_is_ptr(src) && type_is_ptr(dst)) { - cfree_cg_bitcast(p->cg, id); + if (emit) cfree_cg_bitcast(p->cg, id); pcg_retag_top(p, dst); return; } if (si && di) { - if (ds < ss) - cfree_cg_trunc(p->cg, id); - else if (ds > ss && type_is_int(src) && pcg_type_is_signed(src)) - cfree_cg_sext(p->cg, id); - else if (ds > ss) - cfree_cg_zext(p->cg, id); + if (ds < ss) { + if (emit) cfree_cg_trunc(p->cg, id); + } else if (ds > ss && type_is_int(src) && pcg_type_is_signed(src)) { + if (emit) cfree_cg_sext(p->cg, id); + } else if (ds > ss) { + if (emit) cfree_cg_zext(p->cg, id); + } } else if (type_is_int(src) && df) { - if (pcg_type_is_signed(src)) - cfree_cg_sint_to_float(p->cg, id, CFREE_CG_ROUND_DEFAULT); - else - cfree_cg_uint_to_float(p->cg, id, CFREE_CG_ROUND_DEFAULT); + if (pcg_type_is_signed(src)) { + if (emit) cfree_cg_sint_to_float(p->cg, id, CFREE_CG_ROUND_DEFAULT); + } else { + if (emit) cfree_cg_uint_to_float(p->cg, id, CFREE_CG_ROUND_DEFAULT); + } } else if (sf && type_is_int(dst)) { - if (pcg_type_is_signed(dst)) - cfree_cg_float_to_sint(p->cg, id, CFREE_CG_ROUND_DEFAULT); - else - cfree_cg_float_to_uint(p->cg, id, CFREE_CG_ROUND_DEFAULT); + if (pcg_type_is_signed(dst)) { + if (emit) cfree_cg_float_to_sint(p->cg, id, CFREE_CG_ROUND_DEFAULT); + } else { + if (emit) cfree_cg_float_to_uint(p->cg, id, CFREE_CG_ROUND_DEFAULT); + } } else if (sf && df) { - if (ds > ss) - cfree_cg_fpext(p->cg, id); - else if (ds < ss) - cfree_cg_fptrunc(p->cg, id); + if (ds > ss) { + if (emit) cfree_cg_fpext(p->cg, id); + } else if (ds < ss) { + if (emit) cfree_cg_fptrunc(p->cg, id); + } } else { - cfree_cg_bitcast(p->cg, id); + if (emit) cfree_cg_bitcast(p->cg, id); } pcg_retag_top(p, dst); } void pcg_inc_dec(Parser* p, BinOp op, int post) { const Type* ty = pcg_top_type(p); - cfree_cg_inc_dec(p->cg, pcg_int_binop(op), post, pcg_tid(p->c, ty), - pcg_mem(p, ty)); + if (pcg_emit_enabled(p)) { + cfree_cg_inc_dec(p->cg, pcg_int_binop(op), post, pcg_tid(p->c, ty), + pcg_mem(p, ty)); + } } void pcg_call(Parser* p, u32 nargs, const Type* fn_type) { - cfree_cg_call_default(p->cg, nargs, pcg_tid(p->c, fn_type)); + if (pcg_emit_enabled(p)) { + cfree_cg_call_default(p->cg, nargs, pcg_tid(p->c, fn_type)); + } for (u32 i = 0; i < nargs + 1u; ++i) pcg_drop_type(p); if (fn_type && fn_type->kind == TY_FUNC && fn_type->fn.ret->kind != TY_VOID) { pcg_push_type(p, fn_type->fn.ret); } } +void pcg_call_symbol(Parser* p, CfreeCgSym sym, u32 nargs, + const Type* fn_type) { + if (pcg_emit_enabled(p)) { + CfreeCgCallAttrs attrs; + memset(&attrs, 0, sizeof attrs); + cfree_cg_call_symbol(p->cg, sym, nargs, attrs); + } + for (u32 i = 0; i < nargs; ++i) pcg_drop_type(p); + if (fn_type && fn_type->kind == TY_FUNC && fn_type->fn.ret->kind != TY_VOID) { + pcg_push_type(p, fn_type->fn.ret); + } +} + void pcg_ret(Parser* p, int has_value) { if (has_value) { - cfree_cg_ret(p->cg); + if (pcg_emit_enabled(p)) cfree_cg_ret(p->cg); pcg_drop_type(p); } else { - cfree_cg_ret_void(p->cg); + if (pcg_emit_enabled(p)) cfree_cg_ret_void(p->cg); } } void pcg_alloca(Parser* p) { - cfree_cg_alloca(p->cg, 16, - pcg_tid(p->c, type_ptr(p->pool, type_void(p->pool)))); + if (pcg_emit_enabled(p)) { + cfree_cg_alloca(p->cg, 16, + pcg_tid(p->c, type_ptr(p->pool, type_void(p->pool)))); + } pcg_drop_type(p); pcg_push_type(p, type_ptr(p->pool, type_void(p->pool))); } void pcg_va_arg(Parser* p, const Type* ty) { - cfree_cg_vararg_next(p->cg, pcg_tid(p->c, ty)); + if (pcg_emit_enabled(p)) cfree_cg_vararg_next(p->cg, pcg_tid(p->c, ty)); pcg_drop_type(p); pcg_push_type(p, ty); } @@ -408,14 +492,18 @@ void pcg_va_arg(Parser* p, const Type* ty) { void pcg_atomic_load(Parser* p, MemOrder ord) { const Type* pty = pcg_top_type(p); const Type* ty = (pty && pty->kind == TY_PTR) ? pty->ptr.pointee : pty; - cfree_cg_atomic_load(p->cg, pcg_mem(p, ty), pcg_mem_order(ord)); + if (pcg_emit_enabled(p)) { + cfree_cg_atomic_load(p->cg, pcg_mem(p, ty), pcg_mem_order(ord)); + } pcg_retag_top(p, ty); } void pcg_atomic_store(Parser* p, MemOrder ord) { const Type* pty = pcg_top2_type(p); const Type* ty = (pty && pty->kind == TY_PTR) ? pty->ptr.pointee : pty; - cfree_cg_atomic_store(p->cg, pcg_mem(p, ty), pcg_mem_order(ord)); + if (pcg_emit_enabled(p)) { + cfree_cg_atomic_store(p->cg, pcg_mem(p, ty), pcg_mem_order(ord)); + } pcg_drop_type(p); pcg_drop_type(p); } @@ -423,21 +511,25 @@ void pcg_atomic_store(Parser* p, MemOrder ord) { void pcg_atomic_rmw(Parser* p, AtomicOp op, MemOrder ord) { const Type* pty = pcg_top2_type(p); const Type* ty = (pty && pty->kind == TY_PTR) ? pty->ptr.pointee : pty; - cfree_cg_atomic_rmw(p->cg, pcg_mem(p, ty), pcg_atomic_op(op), - pcg_mem_order(ord)); + if (pcg_emit_enabled(p)) { + cfree_cg_atomic_rmw(p->cg, pcg_mem(p, ty), pcg_atomic_op(op), + pcg_mem_order(ord)); + } pcg_drop_type(p); pcg_retag_top(p, ty); } void pcg_atomic_cas(Parser* p, MemOrder succ, MemOrder fail) { const Type* ty = pcg_top_type(p); - cfree_cg_atomic_cmpxchg(p->cg, pcg_mem(p, ty), pcg_mem_order(succ), - pcg_mem_order(fail), 0); + if (pcg_emit_enabled(p)) { + cfree_cg_atomic_cmpxchg(p->cg, pcg_mem(p, ty), pcg_mem_order(succ), + pcg_mem_order(fail), 0); + } pcg_retag_top(p, type_prim(p->pool, TY_BOOL)); } void pcg_fence(Parser* p, MemOrder ord) { - cfree_cg_atomic_fence(p->cg, pcg_mem_order(ord)); + if (pcg_emit_enabled(p)) cfree_cg_atomic_fence(p->cg, pcg_mem_order(ord)); } void pcg_intrinsic_unary_to_int(Parser* p, IntrinKind k) { @@ -445,15 +537,20 @@ void pcg_intrinsic_unary_to_int(Parser* p, IntrinKind k) { : k == INTRIN_CTZ ? CFREE_CG_INTRIN_CTZ : CFREE_CG_INTRIN_POPCOUNT; const Type* ity = type_prim(p->pool, TY_INT); - cfree_cg_intrinsic(p->cg, ck, 1, pcg_tid(p->c, ity)); + if (pcg_emit_enabled(p)) { + cfree_cg_intrinsic(p->cg, ck, 1, pcg_tid(p->c, ity)); + } pcg_retag_top(p, ity); } void pcg_intrinsic_void(Parser* p, IntrinKind k) { - if (k == INTRIN_UNREACHABLE) - cfree_cg_unreachable(p->cg); - else - cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_TRAP, 0, CFREE_CG_TYPE_NONE); + if (k == INTRIN_UNREACHABLE) { + if (pcg_emit_enabled(p)) cfree_cg_unreachable(p->cg); + } else { + if (pcg_emit_enabled(p)) { + cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_TRAP, 0, CFREE_CG_TYPE_NONE); + } + } } void pcg_inline_asm(Parser* p, const char* tmpl, const AsmConstraint* outs, @@ -494,5 +591,5 @@ void pcg_inline_asm(Parser* p, const char* tmpl, const AsmConstraint* outs, a.ninputs = nin; a.clobbers = cl; a.nclobbers = nclob; - cfree_cg_inline_asm(p->cg, a); + if (pcg_emit_enabled(p)) cfree_cg_inline_asm(p->cg, a); } diff --git a/lang/c/parse/cg_public_compat.h b/lang/c/parse/cg_public_compat.h @@ -173,7 +173,12 @@ void pcg_push_type(Parser*, const Type*); void pcg_drop_type(Parser*); void pcg_dup_type(Parser*); void pcg_swap_type(Parser*); +int pcg_top_is_bitfield(Parser*); +void pcg_set_top_bitfield(Parser*); void pcg_rot3_type(Parser*); +int pcg_emit_enabled(Parser*); +void pcg_codegen_suppress_push(Parser*); +void pcg_codegen_suppress_pop(Parser*); CfreeCgIntBinOp pcg_int_binop(BinOp); CfreeCgFpBinOp pcg_fp_binop(BinOp); @@ -202,6 +207,7 @@ void pcg_cmp(Parser*, CmpOp); void pcg_convert(Parser*, const Type*); void pcg_inc_dec(Parser*, BinOp, int); void pcg_call(Parser*, u32, const Type*); +void pcg_call_symbol(Parser*, CfreeCgSym, u32, const Type*); void pcg_ret(Parser*, int); void pcg_alloca(Parser*); void pcg_va_arg(Parser*, const Type*); @@ -215,11 +221,17 @@ void pcg_intrinsic_void(Parser*, IntrinKind); void pcg_inline_asm(Parser*, const char*, const AsmConstraint*, u32, const AsmConstraint*, u32, const Sym*, u32); -#define cg_set_loc(g, loc) cfree_cg_set_loc((g), (loc)) +#define cg_set_loc(g, loc) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_set_loc((g), (loc)); \ + } while (0) #define cg_local(g, fsd) pcg_local(p, (fsd)) #define cg_param(g, pd) pcg_param(p, (pd)) #define cg_func_begin(g, fd) pcg_func_begin(p, (fd)) -#define cg_func_end(g) cfree_cg_func_end((g)) +#define cg_func_end(g) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_func_end((g)); \ + } while (0) #define cg_push_int(g, v, ty) pcg_push_int(p, (v), (ty)) #define cg_push_float(g, v, ty) pcg_push_float(p, (v), (ty)) #define cg_push_local_typed(g, s, ty) pcg_push_local_typed(p, (s), (ty)) @@ -228,17 +240,17 @@ void pcg_inline_asm(Parser*, const char*, const AsmConstraint*, u32, #define cg_addr(g) pcg_addr(p) #define cg_dup(g) \ do { \ - cfree_cg_dup((g)); \ + if (pcg_emit_enabled(p)) cfree_cg_dup((g)); \ pcg_dup_type(p); \ } while (0) #define cg_swap(g) \ do { \ - cfree_cg_swap((g)); \ + if (pcg_emit_enabled(p)) cfree_cg_swap((g)); \ pcg_swap_type(p); \ } while (0) #define cg_drop(g) \ do { \ - cfree_cg_drop((g)); \ + if (pcg_emit_enabled(p)) cfree_cg_drop((g)); \ pcg_drop_type(p); \ } while (0) #define cg_store(g) pcg_store(p) @@ -254,9 +266,18 @@ void pcg_inline_asm(Parser*, const char*, const AsmConstraint*, u32, #define cg_call(g, nargs, fn_type) pcg_call(p, (nargs), (fn_type)) #define cg_ret(g, has_value) pcg_ret(p, (has_value)) #define cg_alloca(g) pcg_alloca(p) -#define cg_va_start_(g) cfree_cg_vararg_start((g)) -#define cg_va_end_(g) cfree_cg_vararg_end((g)) -#define cg_va_copy_(g) cfree_cg_vararg_copy((g)) +#define cg_va_start_(g) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_vararg_start((g)); \ + } while (0) +#define cg_va_end_(g) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_vararg_end((g)); \ + } while (0) +#define cg_va_copy_(g) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_vararg_copy((g)); \ + } while (0) #define cg_va_arg_(g, ty) pcg_va_arg(p, (ty)) #define cg_atomic_load(g, ord) pcg_atomic_load(p, (ord)) #define cg_atomic_store(g, ord) pcg_atomic_store(p, (ord)) @@ -266,16 +287,22 @@ void pcg_inline_asm(Parser*, const char*, const AsmConstraint*, u32, #define cg_intrinsic_unary_to_int(g, k) pcg_intrinsic_unary_to_int(p, (k)) #define cg_intrinsic_void(g, k) pcg_intrinsic_void(p, (k)) #define cg_label_new(g) cfree_cg_label_new((g)) -#define cg_label_place(g, l) cfree_cg_label_place((g), (l)) -#define cg_jump(g, l) cfree_cg_jump((g), (l)) +#define cg_label_place(g, l) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_label_place((g), (l)); \ + } while (0) +#define cg_jump(g, l) \ + do { \ + if (pcg_emit_enabled(p)) cfree_cg_jump((g), (l)); \ + } while (0) #define cg_branch_true(g, l) \ do { \ - cfree_cg_branch_true((g), (l)); \ + if (pcg_emit_enabled(p)) cfree_cg_branch_true((g), (l)); \ pcg_drop_type(p); \ } while (0) #define cg_branch_false(g, l) \ do { \ - cfree_cg_branch_false((g), (l)); \ + if (pcg_emit_enabled(p)) cfree_cg_branch_false((g), (l)); \ pcg_drop_type(p); \ } while (0) #define cg_inline_asm(g, tmpl, outs, nout, ins, nin, clob, nclob) \ diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -1202,6 +1202,8 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg) { p.sym_b_alloca = pool_intern_cstr(p.pool, "__builtin_alloca"); p.sym_b_ctz = pool_intern_cstr(p.pool, "__builtin_ctz"); + p.sym_b_ctzl = pool_intern_cstr(p.pool, "__builtin_ctzl"); + p.sym_b_ctzll = pool_intern_cstr(p.pool, "__builtin_ctzll"); p.sym_b_clz = pool_intern_cstr(p.pool, "__builtin_clz"); p.sym_b_clzl = pool_intern_cstr(p.pool, "__builtin_clzl"); p.sym_b_clzll = pool_intern_cstr(p.pool, "__builtin_clzll"); @@ -1235,7 +1237,11 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg) { p.sym_a_fetch_and = pool_intern_cstr(p.pool, "__atomic_fetch_and"); p.sym_a_fetch_or = pool_intern_cstr(p.pool, "__atomic_fetch_or"); p.sym_a_fetch_xor = pool_intern_cstr(p.pool, "__atomic_fetch_xor"); + p.sym_a_fetch_nand = pool_intern_cstr(p.pool, "__atomic_fetch_nand"); p.sym_a_cas_n = pool_intern_cstr(p.pool, "__atomic_compare_exchange_n"); + p.sym_a_always_lock_free = + pool_intern_cstr(p.pool, "__atomic_always_lock_free"); + p.sym_a_is_lock_free = pool_intern_cstr(p.pool, "__atomic_is_lock_free"); p.sym_a_thread_fence = pool_intern_cstr(p.pool, "__atomic_thread_fence"); p.sym_a_signal_fence = pool_intern_cstr(p.pool, "__atomic_signal_fence"); diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -35,6 +35,49 @@ static void require_sizeof_type(Parser* p, const Type* ty) { } } +static int type_is_void_ptr(const Type* ty) { + return ty && ty->kind == TY_PTR && ty->ptr.pointee && + ty->ptr.pointee->kind == TY_VOID; +} + +static int pointer_pointees_compatible(Parser* p, const Type* lhs, + const Type* rhs) { + const Type* lp; + const Type* rp; + if (!lhs || !rhs || lhs->kind != TY_PTR || rhs->kind != TY_PTR) return 0; + lp = lhs->ptr.pointee; + rp = rhs->ptr.pointee; + if (!lp || !rp) return 0; + return type_compatible(type_unqual(p->pool, lp), type_unqual(p->pool, rp)); +} + +static int null_pointer_constant(Parser* p, const Type* ty) { + i64 v = 1; + return type_is_int(ty) && cfree_cg_top_const_int(p->cg, &v) && v == 0; +} + +static void require_scalar(Parser* p, const Type* ty, const char* what) { + if (!c_type_is_scalar(ty)) perr(p, "%s requires scalar operand", what); +} + +static void require_arith(Parser* p, const Type* ty, const char* what) { + if (!type_is_arith(ty)) perr(p, "%s requires arithmetic operand", what); +} + +static const Type* conditional_pointer_type(Parser* p, const Type* then_ty, + int then_null, + const Type* else_ty, + int else_null) { + if (then_ty && then_ty->kind == TY_PTR && else_null) return then_ty; + if (else_ty && else_ty->kind == TY_PTR && then_null) return else_ty; + if (!then_ty || !else_ty || then_ty->kind != TY_PTR || else_ty->kind != TY_PTR) + return NULL; + if (type_is_void_ptr(then_ty)) return then_ty; + if (type_is_void_ptr(else_ty)) return else_ty; + if (pointer_pointees_compatible(p, then_ty, else_ty)) return then_ty; + return NULL; +} + /* ============================================================ * Literal parsing * ============================================================ */ @@ -463,6 +506,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { parse_unary(p); { const Type* ty = cg_top_type(p->cg); + if (pcg_top_is_bitfield(p)) perr(p, "sizeof bit-field"); require_sizeof_type(p, ty); i64 sz = (i64)c_abi_sizeof(p->abi, ty); cg_drop(p->cg); @@ -559,7 +603,7 @@ i64 eval_const_int(Parser* p, SrcLoc loc) { return cexpr_bor(p, loc); } static void decay_array_to_pointer(Parser* p, const Type* arr_ty) { const Type* ptr_ty = type_ptr(p->pool, arr_ty->arr.elem); - cfree_cg_addr_offset(p->cg, 0, pcg_tid(p->c, ptr_ty)); + if (pcg_emit_enabled(p)) cfree_cg_addr_offset(p->cg, 0, pcg_tid(p->c, ptr_ty)); pcg_retag_top(p, ptr_ty); } @@ -596,6 +640,158 @@ void coerce_top_to_lvalue(Parser* p) { } } +static void coerce_top_to_type(Parser* p, const Type* dst) { + const Type* src = cg_top_type(p->cg); + if (!src || !dst || src == dst) return; + if (type_is_arith(src) && type_is_arith(dst)) { + cg_convert(p->cg, dst); + } else if (type_is_arith(src) && type_is_ptr(dst)) { + cg_convert(p->cg, dst); + } else if (type_is_ptr(src) && type_is_ptr(dst)) { + cg_convert(p->cg, dst); + } +} + +static const Type* atomic_pointee_type(Parser* p, const Type* ptr_ty, + const char* who) { + if (!ptr_ty || ptr_ty->kind != TY_PTR) { + perr(p, "%s: pointer argument must have pointer type", who); + } + return ptr_ty->ptr.pointee; +} + +static const Type* atomic_lock_free_type_for_size(Parser* p, i64 size) { + const Type* ty = NULL; + switch (size) { + case 1: + ty = type_prim(p->pool, TY_UCHAR); + break; + case 2: + ty = type_prim(p->pool, TY_USHORT); + break; + case 4: + ty = type_prim(p->pool, TY_UINT); + break; + case 8: + ty = type_prim(p->pool, TY_ULLONG); + break; + case 16: + ty = type_prim(p->pool, TY_UINT128); + break; + default: + return NULL; + } + return c_abi_sizeof(p->abi, ty) == (u32)size ? ty : NULL; +} + +static int atomic_lock_free_for_const_size(Parser* p, i64 size) { + if (size <= 0 || size > 16) return 0; + const Type* ty = atomic_lock_free_type_for_size(p, size); + return ty ? cfree_cg_atomic_is_lock_free(p->c, pcg_mem(p, ty)) : 0; +} + +static CfreeCgSym builtin_libcall_sym(Parser* p, const char* name, + const Type* fn_ty) { + CfreeCgDecl decl; + Sym source_name = pool_intern_cstr(p->pool, name); + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_FUNC; + decl.display_name = source_name; + decl.linkage_name = cfree_cg_c_linkage_name(p->c, source_name); + decl.type = pcg_tid(p->c, fn_ty); + decl.sym.bind = CFREE_SB_GLOBAL; + decl.sym.visibility = CFREE_CG_VIS_DEFAULT; + return cfree_cg_decl(p->cg, decl); +} + +static int parse_builtin_mem_call(Parser* p, Sym name, SrcLoc loc) { + const Type* void_ty = type_void(p->pool); + const Type* void_ptr_ty = type_ptr(p->pool, void_ty); + const Type* const_void_ptr_ty = + type_ptr(p->pool, type_qualified(p->pool, void_ty, Q_CONST)); + const Type* size_ty = ty_size_t(p); + const Type* int_ty = ty_int(p); + const Type* params[3]; + const Type* fn_ty; + const char* libname; + CfreeCgSym sym; + + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after builtin"); + + if (name == p->sym_b_memcpy || name == p->sym_b_memmove) { + libname = name == p->sym_b_memcpy ? "memcpy" : "memmove"; + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, void_ptr_ty); + expect_punct(p, ',', "',' in memory builtin"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, const_void_ptr_ty); + expect_punct(p, ',', "',' in memory builtin"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, size_ty); + expect_punct(p, ')', "')' after memory builtin"); + params[0] = void_ptr_ty; + params[1] = const_void_ptr_ty; + params[2] = size_ty; + fn_ty = type_func(p->pool, void_ptr_ty, params, 3, 0); + } else if (name == p->sym_b_memset) { + libname = "memset"; + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, void_ptr_ty); + expect_punct(p, ',', "',' in __builtin_memset"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, int_ty); + expect_punct(p, ',', "',' in __builtin_memset"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, size_ty); + expect_punct(p, ')', "')' after __builtin_memset"); + params[0] = void_ptr_ty; + params[1] = int_ty; + params[2] = size_ty; + fn_ty = type_func(p->pool, void_ptr_ty, params, 3, 0); + } else { + libname = "memcmp"; + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, const_void_ptr_ty); + expect_punct(p, ',', "',' in __builtin_memcmp"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, const_void_ptr_ty); + expect_punct(p, ',', "',' in __builtin_memcmp"); + parse_assign_expr(p); + to_rvalue(p); + coerce_top_to_type(p, size_ty); + expect_punct(p, ')', "')' after __builtin_memcmp"); + params[0] = const_void_ptr_ty; + params[1] = const_void_ptr_ty; + params[2] = size_ty; + fn_ty = type_func(p->pool, int_ty, params, 3, 0); + } + + sym = pcg_emit_enabled(p) ? builtin_libcall_sym(p, libname, fn_ty) + : CFREE_CG_SYM_NONE; + cg_set_loc(p->cg, loc); + pcg_call_symbol(p, sym, 3, fn_ty); + return 1; +} + +static MemOrder parse_atomic_mem_order(Parser* p) { + if (p->cur.kind == TOK_NUM) { + return (MemOrder)eval_const_int(p, p->cur.loc); + } + parse_assign_expr(p); + to_rvalue(p); + cg_drop(p->cg); + return MO_SEQ_CST; +} + /* ============================================================ * Builtin call handling * ============================================================ */ @@ -663,24 +859,13 @@ static int try_parse_builtin_call(Parser* p) { Sym name = p->cur.v.ident; SrcLoc loc = p->cur.loc; - /* `__builtin_mem{cpy,move,cmp,set}` are GCC/Clang's compiler-inlinable - * aliases for the libc functions. cfree's INTRIN_MEMCPY/MEMMOVE - * backend paths only handle constant byte counts, but the rt code - * calls them with runtime sizes. Rewrite each builtin into a plain - * call and let the normal function-call path handle it. The caller - * (parse_primary) reports a clean "undeclared identifier" if the TU - * forgot to declare the underlying libc function. */ if (name == p->sym_b_memcpy || name == p->sym_b_memmove || name == p->sym_b_memcmp || name == p->sym_b_memset) { - const char* libname = (name == p->sym_b_memcpy) ? "memcpy" - : (name == p->sym_b_memmove) ? "memmove" - : (name == p->sym_b_memcmp) ? "memcmp" - : "memset"; - p->cur.v.ident = pool_intern_cstr(p->pool, libname); - return 0; + return parse_builtin_mem_call(p, name, loc); } - if (name != p->sym_b_alloca && name != p->sym_b_ctz && name != p->sym_b_clz && + 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 && name != p->sym_b_trap && name != p->sym_b_unreachable && name != p->sym_b_expect && name != p->sym_b_offsetof && @@ -690,8 +875,9 @@ static int try_parse_builtin_call(Parser* p) { name != p->sym_a_exchange_n && name != p->sym_a_fetch_add && name != p->sym_a_fetch_sub && name != p->sym_a_fetch_and && name != p->sym_a_fetch_or && name != p->sym_a_fetch_xor && - name != p->sym_a_cas_n && name != p->sym_a_thread_fence && - name != p->sym_a_signal_fence) { + name != p->sym_a_fetch_nand && name != p->sym_a_cas_n && + name != p->sym_a_always_lock_free && name != p->sym_a_is_lock_free && + name != p->sym_a_thread_fence && name != p->sym_a_signal_fence) { return 0; } advance(p); /* IDENT */ @@ -726,7 +912,7 @@ static int try_parse_builtin_call(Parser* p) { return 1; } - if (name == p->sym_b_ctz) { + if (name == p->sym_b_ctz || name == p->sym_b_ctzl || name == p->sym_b_ctzll) { parse_assign_expr(p); to_rvalue(p); expect_punct(p, ')', "')' after __builtin_ctz"); @@ -811,40 +997,56 @@ static int try_parse_builtin_call(Parser* p) { parse_assign_expr(p); to_rvalue(p); expect_punct(p, ',', "',' in __atomic_load_n"); - i64 ord = eval_const_int(p, p->cur.loc); + MemOrder ord = parse_atomic_mem_order(p); expect_punct(p, ')', "')' after __atomic_load_n"); cg_set_loc(p->cg, loc); - cg_atomic_load(p->cg, (MemOrder)ord); + cg_atomic_load(p->cg, ord); return 1; } if (name == p->sym_a_store_n) { parse_assign_expr(p); to_rvalue(p); + const Type* val_ty = + atomic_pointee_type(p, cg_top_type(p->cg), "__atomic_store_n"); expect_punct(p, ',', "',' in __atomic_store_n"); parse_assign_expr(p); to_rvalue(p); + coerce_top_to_type(p, val_ty); expect_punct(p, ',', "',' in __atomic_store_n"); - i64 ord = eval_const_int(p, p->cur.loc); + MemOrder ord = parse_atomic_mem_order(p); expect_punct(p, ')', "')' after __atomic_store_n"); cg_set_loc(p->cg, loc); - cg_atomic_store(p->cg, (MemOrder)ord); + cg_atomic_store(p->cg, ord); cg_push_int(p->cg, 0, ty_int(p)); return 1; } if (name == p->sym_a_thread_fence || name == p->sym_a_signal_fence) { - i64 ord = eval_const_int(p, p->cur.loc); + MemOrder ord = parse_atomic_mem_order(p); expect_punct(p, ')', "')' after atomic fence"); cg_set_loc(p->cg, loc); - cg_fence(p->cg, (MemOrder)ord); + cg_fence(p->cg, ord); cg_push_int(p->cg, 0, ty_int(p)); return 1; } + if (name == p->sym_a_always_lock_free || name == p->sym_a_is_lock_free) { + i64 size = eval_const_int(p, p->cur.loc); + expect_punct(p, ',', "',' in atomic lock-free builtin"); + parse_assign_expr(p); + to_rvalue(p); + cg_drop(p->cg); + expect_punct(p, ')', "')' after atomic lock-free builtin"); + cg_push_int(p->cg, atomic_lock_free_for_const_size(p, size), ty_int(p)); + return 1; + } + if (name == p->sym_a_cas_n) { parse_assign_expr(p); to_rvalue(p); /* ptr */ + const Type* obj_ty = atomic_pointee_type(p, cg_top_type(p->cg), + "__atomic_compare_exchange_n"); expect_punct(p, ',', "',' in __atomic_compare_exchange_n"); parse_assign_expr(p); @@ -854,6 +1056,9 @@ static int try_parse_builtin_call(Parser* p) { perr(p, "__atomic_compare_exchange_n: arg 2 must be a pointer"); } const Type* val_ty = eptr_ty->ptr.pointee; + if (val_ty != obj_ty) { + val_ty = obj_ty; + } FrameSlotDesc fsd; memset(&fsd, 0, sizeof fsd); @@ -875,17 +1080,18 @@ static int try_parse_builtin_call(Parser* p) { expect_punct(p, ',', "',' in __atomic_compare_exchange_n"); parse_assign_expr(p); to_rvalue(p); /* desired */ + coerce_top_to_type(p, val_ty); expect_punct(p, ',', "',' in __atomic_compare_exchange_n"); (void)eval_const_int(p, p->cur.loc); /* weak */ expect_punct(p, ',', "',' in __atomic_compare_exchange_n"); - i64 succ = eval_const_int(p, p->cur.loc); + MemOrder succ = parse_atomic_mem_order(p); expect_punct(p, ',', "',' in __atomic_compare_exchange_n"); - i64 fail = eval_const_int(p, p->cur.loc); + MemOrder fail = parse_atomic_mem_order(p); expect_punct(p, ')', "')' after __atomic_compare_exchange_n"); cg_set_loc(p->cg, loc); - cg_atomic_cas(p->cg, (MemOrder)succ, (MemOrder)fail); + cg_atomic_cas(p->cg, succ, fail); const Type* ok_ty = cg_top_type(p->cg); FrameSlotDesc okd; @@ -943,20 +1149,25 @@ static int try_parse_builtin_call(Parser* p) { op = AO_OR; else if (name == p->sym_a_fetch_xor) op = AO_XOR; + else if (name == p->sym_a_fetch_nand) + op = AO_NAND; else { perr(p, "internal: unhandled builtin"); } parse_assign_expr(p); to_rvalue(p); + const Type* val_ty = + atomic_pointee_type(p, cg_top_type(p->cg), "__atomic read-modify-write"); expect_punct(p, ',', "',' in atomic builtin"); parse_assign_expr(p); to_rvalue(p); + coerce_top_to_type(p, val_ty); expect_punct(p, ',', "',' in atomic builtin"); - i64 ord = eval_const_int(p, p->cur.loc); + MemOrder ord = parse_atomic_mem_order(p); expect_punct(p, ')', "')' after atomic builtin"); cg_set_loc(p->cg, loc); - cg_atomic_rmw(p->cg, op, (MemOrder)ord); + cg_atomic_rmw(p->cg, op, ord); return 1; } @@ -1078,7 +1289,8 @@ static void parse_primary(Parser* p) { static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, const Type** out_ty, u32 path[2], - u32* out_depth) { + u32* out_depth, + const Field** out_field) { const ABIRecordLayout* L; if (!rec_ty || (rec_ty->kind != TY_STRUCT && rec_ty->kind != TY_UNION)) return 0; @@ -1090,6 +1302,7 @@ static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, *out_ty = f->type; path[0] = i; *out_depth = 1; + if (out_field) *out_field = f; return 1; } if ((f->flags & FIELD_ANON) && @@ -1103,6 +1316,7 @@ static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, path[0] = i; path[1] = j; *out_depth = 2; + if (out_field) *out_field = ff; return 1; } } @@ -1112,11 +1326,13 @@ static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, } static void cg_record_member_path(Parser* p, const Type* member_ty, - const u32* path, u32 depth) { + const u32* path, u32 depth, + const Field* field) { for (u32 i = 0; i < depth; ++i) { - cfree_cg_field(p->cg, path[i]); + if (pcg_emit_enabled(p)) cfree_cg_field(p->cg, path[i]); } pcg_retag_top(p, member_ty); + if (field && (field->flags & FIELD_BITFIELD)) pcg_set_top_bitfield(p); } static void parse_postfix(Parser* p) { @@ -1213,6 +1429,7 @@ static void parse_postfix(Parser* p) { const Type* lt = cg_top_type(p->cg); Sym mname; const Type* mty = NULL; + const Field* mf = NULL; u32 path[2]; u32 depth = 0; advance(p); /* '.' */ @@ -1225,9 +1442,9 @@ static void parse_postfix(Parser* p) { } mname = p->cur.v.ident; advance(p); - if (!find_record_member_path(p, lt, mname, &mty, path, &depth)) + if (!find_record_member_path(p, lt, mname, &mty, path, &depth, &mf)) perr(p, "no such member"); - cg_record_member_path(p, mty, path, depth); + cg_record_member_path(p, mty, path, depth, mf); continue; } if (is_punct(&t, P_ARROW)) { @@ -1235,6 +1452,7 @@ static void parse_postfix(Parser* p) { const Type* rec_ty; Sym mname; const Type* mty = NULL; + const Field* mf = NULL; u32 path[2]; u32 depth = 0; advance(p); /* `->` */ @@ -1252,10 +1470,10 @@ static void parse_postfix(Parser* p) { } mname = p->cur.v.ident; advance(p); - if (!find_record_member_path(p, rec_ty, mname, &mty, path, &depth)) + if (!find_record_member_path(p, rec_ty, mname, &mty, path, &depth, &mf)) perr(p, "no such member"); cg_deref(p->cg, rec_ty); - cg_record_member_path(p, mty, path, depth); + cg_record_member_path(p, mty, path, depth, mf); continue; } break; @@ -1297,12 +1515,15 @@ void parse_unary(Parser* p) { } parse_unary(p); to_rvalue(p); + src = cg_top_type(p->cg); if (dst && dst->kind == TY_VOID) { cg_drop(p->cg); cg_push_int(p->cg, 0, ty_int(p)); return; } - src = cg_top_type(p->cg); + if (!c_type_is_scalar(dst) || !c_type_is_scalar(src)) { + perr(p, "cast requires scalar type"); + } if (src && src->kind == TY_PTR && dst->kind == TY_PTR) { cg_convert(p->cg, dst); return; @@ -1315,12 +1536,14 @@ void parse_unary(Parser* p) { advance(p); parse_unary(p); to_rvalue(p); + require_arith(p, cg_top_type(p->cg), "unary '+'"); return; } if (is_punct(&t, '-')) { advance(p); parse_unary(p); to_rvalue(p); + require_arith(p, cg_top_type(p->cg), "unary '-'"); cg_unop(p->cg, UO_NEG); return; } @@ -1328,6 +1551,7 @@ void parse_unary(Parser* p) { advance(p); parse_unary(p); to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "unary '!'"); cg_push_int(p->cg, 0, ty_int(p)); cg_cmp(p->cg, CMP_EQ); return; @@ -1336,12 +1560,16 @@ void parse_unary(Parser* p) { advance(p); parse_unary(p); to_rvalue(p); + if (!type_is_int(cg_top_type(p->cg))) { + perr(p, "unary '~' requires integer operand"); + } cg_unop(p->cg, UO_BNOT); return; } if (is_punct(&t, '&')) { advance(p); parse_unary(p); + if (pcg_top_is_bitfield(p)) perr(p, "cannot take address of bit-field"); cg_addr(p->cg); return; } @@ -1384,6 +1612,7 @@ void parse_unary(Parser* p) { parse_unary(p); ty = cg_top_type(p->cg); vla_slot = p->last_pushed_vla_slot; + if (pcg_top_is_bitfield(p)) perr(p, "sizeof bit-field"); cg_drop(p->cg); } } else { @@ -1391,6 +1620,7 @@ void parse_unary(Parser* p) { parse_unary(p); ty = cg_top_type(p->cg); vla_slot = p->last_pushed_vla_slot; + if (pcg_top_is_bitfield(p)) perr(p, "sizeof bit-field"); cg_drop(p->cg); } if (vla_slot != FRAME_SLOT_NONE) { @@ -1618,6 +1848,12 @@ static void parse_mul(Parser* p) { const Type* lt = cg_top2_type(p->cg); const Type* rt = cg_top_type(p->cg); const Type* common = common_fp_type(p, lt, rt); + if (bop == BO_SREM) { + if (!type_is_int(lt) || !type_is_int(rt)) + perr(p, "operator '%' requires integer operands"); + } else if (!type_is_arith(lt) || !type_is_arith(rt)) { + perr(p, "multiplicative operator requires arithmetic operands"); + } if (common) { emit_fp_binop(p, bop, common); } else { @@ -1632,6 +1868,9 @@ static void emit_add_or_sub(Parser* p, BinOp bop) { int l_is_ptr = lt && lt->kind == TY_PTR; int r_is_ptr = rt && rt->kind == TY_PTR; if (bop == BO_IADD) { + if (l_is_ptr && r_is_ptr) { + perr(p, "invalid operands to binary +"); + } if (l_is_ptr && type_is_int(rt)) { u32 esz = c_abi_sizeof(p->abi, lt->ptr.pointee); if (esz != 1) { @@ -1662,6 +1901,9 @@ static void emit_add_or_sub(Parser* p, BinOp bop) { return; } if (l_is_ptr && r_is_ptr) { + if (!pointer_pointees_compatible(p, lt, rt)) { + perr(p, "subtraction of incompatible pointer types"); + } u32 esz = c_abi_sizeof(p->abi, lt->ptr.pointee); cg_binop(p->cg, BO_ISUB); if (esz != 1) { @@ -1671,7 +1913,13 @@ static void emit_add_or_sub(Parser* p, BinOp bop) { return; } } + if (l_is_ptr || r_is_ptr) { + perr(p, "invalid operands to additive operator"); + } const Type* common = common_fp_type(p, lt, rt); + if (!common && (!type_is_arith(lt) || !type_is_arith(rt))) { + perr(p, "additive operator requires arithmetic operands"); + } if (common) { emit_fp_binop(p, bop, common); return; @@ -1715,6 +1963,9 @@ static void parse_shift(Parser* p) { to_rvalue(p); parse_add(p); to_rvalue(p); + if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) { + perr(p, "shift operator requires integer operands"); + } cg_binop(p->cg, bop); } } @@ -1739,6 +1990,17 @@ static void parse_rel(Parser* p) { to_rvalue(p); parse_shift(p); to_rvalue(p); + { + const Type* lt = cg_top2_type(p->cg); + const Type* rt = cg_top_type(p->cg); + if (lt && lt->kind == TY_PTR && rt && rt->kind == TY_PTR) { + if (!pointer_pointees_compatible(p, lt, rt)) { + perr(p, "comparison of incompatible pointer types"); + } + } else if (!type_is_arith(lt) || !type_is_arith(rt)) { + perr(p, "relational operator requires arithmetic or compatible pointer operands"); + } + } cg_cmp(p->cg, cop); } } @@ -1755,10 +2017,27 @@ static void parse_eq(Parser* p) { } else { break; } + int lhs_null = null_pointer_constant(p, cg_top_type(p->cg)); advance(p); to_rvalue(p); parse_rel(p); to_rvalue(p); + { + const Type* lt = cg_top2_type(p->cg); + const Type* rt = cg_top_type(p->cg); + int lnull = lhs_null; + int rnull = null_pointer_constant(p, rt); + if (lt && lt->kind == TY_PTR && rt && rt->kind == TY_PTR) { + if (!type_is_void_ptr(lt) && !type_is_void_ptr(rt) && + !pointer_pointees_compatible(p, lt, rt)) { + perr(p, "comparison of incompatible pointer types"); + } + } else if ((lt && lt->kind == TY_PTR) || (rt && rt->kind == TY_PTR)) { + if (!lnull && !rnull) perr(p, "comparison between pointer and integer"); + } else if (!type_is_arith(lt) || !type_is_arith(rt)) { + perr(p, "equality operator requires scalar operands"); + } + } cg_cmp(p->cg, cop); } } @@ -1796,6 +2075,14 @@ static void parse_bor(Parser* p) { } } +static int cg_top_const_truth(Parser* p, int* truth_out) { + i64 v = 0; + if (!pcg_emit_enabled(p)) return 0; + if (!cfree_cg_top_const_int(p->cg, &v)) return 0; + *truth_out = v != 0; + return 1; +} + static FrameSlot ll_tmp_slot(Parser* p, const Type* ty) { FrameSlotDesc fsd; memset(&fsd, 0, sizeof fsd); @@ -1821,11 +2108,33 @@ static void parse_land(Parser* p) { CGLabel L_end = cg_label_new(p->cg); const Type* result_ty = ty_int(p); FrameSlot tmp = ll_tmp_slot(p, result_ty); + int lhs_known; + int lhs_truth = 0; + int rhs_truth = 0; advance(p); to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "logical '&&'"); + lhs_known = cg_top_const_truth(p, &lhs_truth); + if (lhs_known && !lhs_truth) { + cg_drop(p->cg); + pcg_codegen_suppress_push(p); + parse_bor(p); + to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "logical '&&'"); + cg_drop(p->cg); + pcg_codegen_suppress_pop(p); + cg_push_int(p->cg, 0, result_ty); + continue; + } cg_branch_false(p->cg, L_false); parse_bor(p); to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "logical '&&'"); + if (lhs_known && lhs_truth && cg_top_const_truth(p, &rhs_truth)) { + cg_drop(p->cg); + cg_push_int(p->cg, rhs_truth ? 1 : 0, result_ty); + continue; + } cg_branch_false(p->cg, L_false); ll_store_const(p, tmp, result_ty, 1); cg_jump(p->cg, L_end); @@ -1843,11 +2152,33 @@ static void parse_lor(Parser* p) { CGLabel L_end = cg_label_new(p->cg); const Type* result_ty = ty_int(p); FrameSlot tmp = ll_tmp_slot(p, result_ty); + int lhs_known; + int lhs_truth = 0; + int rhs_truth = 0; advance(p); to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "logical '||'"); + lhs_known = cg_top_const_truth(p, &lhs_truth); + if (lhs_known && lhs_truth) { + cg_drop(p->cg); + pcg_codegen_suppress_push(p); + parse_land(p); + to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "logical '||'"); + cg_drop(p->cg); + pcg_codegen_suppress_pop(p); + cg_push_int(p->cg, 1, result_ty); + continue; + } cg_branch_true(p->cg, L_true); parse_land(p); to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "logical '||'"); + if (lhs_known && !lhs_truth && cg_top_const_truth(p, &rhs_truth)) { + cg_drop(p->cg); + cg_push_int(p->cg, rhs_truth ? 1 : 0, result_ty); + continue; + } cg_branch_true(p->cg, L_true); ll_store_const(p, tmp, result_ty, 0); cg_jump(p->cg, L_end); @@ -1866,14 +2197,17 @@ static void parse_ternary(Parser* p) { CGLabel L_else = cg_label_new(p->cg); CGLabel L_end = cg_label_new(p->cg); const Type* result_ty = ty_int(p); + int then_null = 0; FrameSlot tmp; FrameSlotDesc fsd; advance(p); /* '?' */ to_rvalue(p); + require_scalar(p, cg_top_type(p->cg), "conditional operator"); cg_branch_false(p->cg, L_else); parse_assign_expr(p); to_rvalue(p); result_ty = cg_top_type(p->cg); + then_null = null_pointer_constant(p, result_ty); if (!result_ty) result_ty = ty_int(p); memset(&fsd, 0, sizeof fsd); fsd.type = result_ty; @@ -1893,6 +2227,19 @@ static void parse_ternary(Parser* p) { to_rvalue(p); const Type* else_ty = cg_top_type(p->cg); const Type* common = common_fp_type(p, result_ty, else_ty); + const Type* ptr_result = + conditional_pointer_type(p, result_ty, then_null, else_ty, + null_pointer_constant(p, else_ty)); + if ((result_ty && result_ty->kind == TY_PTR) || + (else_ty && else_ty->kind == TY_PTR)) { + if (!ptr_result) perr(p, "conditional operator pointer type mismatch"); + result_ty = ptr_result; + } else if (type_is_arith(result_ty) && type_is_arith(else_ty)) { + /* Usual arithmetic conversions are handled by the existing `common` + * materialization below; keep the first temporary's type stable here. */ + } else if (!type_compatible(result_ty, else_ty)) { + perr(p, "conditional operator type mismatch"); + } if (cg_top_type(p->cg) != result_ty) { cg_convert(p->cg, result_ty); } diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -53,7 +53,9 @@ void push_subobject_lv(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset, const Type* elem_ty) { const Type* elem_ptr_ty = type_ptr(p->pool, elem_ty); cg_push_local_typed(p->cg, slot, arr_ty); - cfree_cg_addr_offset(p->cg, (i64)offset, pcg_tid(p->c, elem_ptr_ty)); + if (pcg_emit_enabled(p)) { + cfree_cg_addr_offset(p->cg, (i64)offset, pcg_tid(p->c, elem_ptr_ty)); + } pcg_retag_top(p, elem_ptr_ty); cg_deref(p->cg, elem_ty); } @@ -67,7 +69,9 @@ static void emit_copy_leaf(Parser* p, FrameSlot dst_slot, push_subobject_lv(p, dst_slot, dst_arr_ty, dst_off, leaf_ty); cg_push_local_typed(p->cg, src_ptr_slot, src_ptr_ty); cg_load(p->cg); - cfree_cg_addr_offset(p->cg, (i64)src_off, pcg_tid(p->c, leaf_ptr_ty)); + if (pcg_emit_enabled(p)) { + cfree_cg_addr_offset(p->cg, (i64)src_off, pcg_tid(p->c, leaf_ptr_ty)); + } pcg_retag_top(p, leaf_ptr_ty); cg_deref(p->cg, leaf_ty); cg_load(p->cg); diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -168,6 +168,7 @@ typedef struct Parser { Pool* pool; const Type** cg_type_stack; + u8* cg_value_flags; u32 cg_type_sp; u32 cg_type_cap; @@ -182,6 +183,8 @@ typedef struct Parser { Sym sym_b_alloca; Sym sym_b_ctz; + Sym sym_b_ctzl; + Sym sym_b_ctzll; Sym sym_b_clz; Sym sym_b_clzl; Sym sym_b_clzll; @@ -218,7 +221,10 @@ typedef struct Parser { Sym sym_a_fetch_and; Sym sym_a_fetch_or; Sym sym_a_fetch_xor; + Sym sym_a_fetch_nand; Sym sym_a_cas_n; + Sym sym_a_always_lock_free; + Sym sym_a_is_lock_free; Sym sym_a_thread_fence; Sym sym_a_signal_fence; @@ -237,6 +243,7 @@ typedef struct Parser { FrameSlot last_pushed_vla_slot; u8 in_param_decl; + u32 suppress_codegen; u32 static_local_counter; @@ -340,6 +347,10 @@ static inline int is_kw(const Parser* p, const Tok* t, CKw k) { return 0; } +static inline int c_type_is_scalar(const Type* ty) { + return type_is_arith(ty) || type_is_ptr(ty); +} + /* ============================================================ * Shared types (needed across multiple modules) * ============================================================ */ diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -23,6 +23,12 @@ static int accept_kw_stmt(Parser* p, CKw k) { return 1; } +static void parse_stmt_suppressed(Parser* p) { + pcg_codegen_suppress_push(p); + parse_stmt(p); + pcg_codegen_suppress_pop(p); +} + /* ============================================================ * Statement parsers * ============================================================ */ @@ -30,10 +36,27 @@ static int accept_kw_stmt(Parser* p, CKw k) { static void parse_if_stmt(Parser* p) { CGLabel L_else = cg_label_new(p->cg); CGLabel L_end = cg_label_new(p->cg); + i64 cond = 0; + int cond_known; expect_punct(p, '(', "'('"); parse_expr(p); to_rvalue(p); + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "if condition requires scalar type"); + } + cond_known = cfree_cg_top_const_int(p->cg, &cond); expect_punct(p, ')', "')'"); + if (cond_known) { + cg_drop(p->cg); + if (cond) { + parse_stmt(p); + if (accept_kw_stmt(p, KW_ELSE)) parse_stmt_suppressed(p); + } else { + parse_stmt_suppressed(p); + if (accept_kw_stmt(p, KW_ELSE)) parse_stmt(p); + } + return; + } cg_branch_false(p->cg, L_else); parse_stmt(p); if (accept_kw_stmt(p, KW_ELSE)) { @@ -55,6 +78,9 @@ static void parse_while_stmt(Parser* p) { cg_label_place(p->cg, L_top); parse_expr(p); to_rvalue(p); + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "while condition requires scalar type"); + } expect_punct(p, ')', "')'"); cg_branch_false(p->cg, L_end); p->cur_break = L_end; @@ -92,6 +118,9 @@ static void parse_for_stmt(Parser* p) { if (!is_punct(&p->cur, ';')) { parse_expr(p); to_rvalue(p); + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "for condition requires scalar type"); + } cg_branch_false(p->cg, L_end); } expect_punct(p, ';', "';'"); @@ -173,6 +202,9 @@ static void parse_do_stmt(Parser* p) { expect_punct(p, '(', "'('"); parse_expr(p); to_rvalue(p); + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "do-while condition requires scalar type"); + } expect_punct(p, ')', "')' after do-while condition"); expect_punct(p, ';', "';' after do-while"); cg_branch_true(p->cg, L_top); diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -698,6 +698,7 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { memset(&f, 0, sizeof f); if (is_punct(&p->cur, ':')) { advance(p); + if (!type_is_int(specs.type)) perr(p, "bit-field has non-integer type"); i64 w = eval_const_int(p, mloc); if (w < 0) perr(p, "negative bit-field width"); if (w > (i64)c_abi_sizeof(p->abi, specs.type) * 8) { @@ -718,6 +719,7 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { mty = parse_declarator_full_ex(p, specs.type, /*allow_abstract=*/0, &mname, &mloc, &mattrs); if (accept_punct(p, ':')) { + if (!type_is_int(mty)) perr(p, "bit-field has non-integer type"); i64 w = eval_const_int(p, mloc); if (w < 0) perr(p, "negative bit-field width"); if (w == 0 && mname != 0) diff --git a/rt/Makefile b/rt/Makefile @@ -1,6 +1,8 @@ # Included by the repository-root Makefile. Paths are root-relative. RT_AR ?= llvm-ar +RT_AS ?= $(CC) +RT_AS_COMPILE_FLAGS ?= -c RT_BUILD_DIR = rt/build RT_COMMON_CFLAGS = -ffreestanding -fno-builtin -std=c11 -Wpedantic -Wall -Wextra -Werror @@ -62,11 +64,13 @@ RT_aarch64-linux_ABI = lp64 RT_aarch64-linux_INT128 = 1 RT_aarch64-linux_CORO = aarch64 RT_aarch64-linux_LDBL128 = 1 +RT_EXTRA_SRCS_aarch64-linux = rt/lib/coro/aarch64_elf.s RT_aarch64-apple-darwin_TARGET = aarch64-apple-darwin RT_aarch64-apple-darwin_ABI = lp64 RT_aarch64-apple-darwin_INT128 = 1 RT_aarch64-apple-darwin_CORO = aarch64 +RT_EXTRA_SRCS_aarch64-apple-darwin = rt/lib/coro/aarch64_macho.s RT_riscv64-linux_TARGET = riscv64-linux-gnu RT_riscv64-linux_ABI = lp64 @@ -172,10 +176,11 @@ RT_SRCS_$(1) := \ $$(RT_CORO_SRCS_$$(RT_$(1)_CORO)) \ $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_SRCS)) \ $$(if $$(RT_$(1)_SAVE_RESTORE),$$(RT_SAVE_RESTORE_SRCS_$$(RT_$(1)_ABI))) \ - $$(RT_AEABI_SRCS_$$(RT_$(1)_AEABI)) + $$(RT_AEABI_SRCS_$$(RT_$(1)_AEABI)) \ + $$(RT_EXTRA_SRCS_$(1)) RT_CFLAGS_$(1) := \ $$(RT_COMMON_CFLAGS) $$(RT_LIB_INCS) \ - --target=$$(RT_$(1)_TARGET) \ + -target $$(RT_$(1)_TARGET) \ -DHAS_INT128=$$(RT_$(1)_INT128) \ $$(RT_ABI_INC_$$(RT_$(1)_ABI)) \ $$(RT_$(1)_ARCH_FLAGS) \ @@ -191,6 +196,14 @@ $$(RT_BUILD_DIR)/$(1)/libcfree_rt.a: $$(RT_OBJS_$(1)) @mkdir -p $$(dir $$@) $$(RT_AR) rcs $$@ $$^ +$$(RT_BUILD_DIR)/$(1)/%.s.o: rt/lib/%.s + @mkdir -p $$(dir $$@) + $$(RT_AS) $$(RT_CFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@ + +$$(RT_BUILD_DIR)/$(1)/%.S.o: rt/lib/%.S + @mkdir -p $$(dir $$@) + $$(RT_AS) $$(RT_CFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@ + $$(RT_BUILD_DIR)/$(1)/%.o: rt/lib/% @mkdir -p $$(dir $$@) $$(CC) $$(RT_CFLAGS_$(1)) -c $$< -o $$@ diff --git a/rt/lib/coro/aarch64.c b/rt/lib/coro/aarch64.c @@ -14,13 +14,8 @@ * sizeof = 176 (alignof-16 padded), 16-byte aligned. Fits in the * 256-byte storage carved out by jmp_buf and coro_ctx. * - * SAVE_/RESTORE_ are C string-concat macros so the same byte - * sequence is emitted in setjmp, longjmp, and __cfree_coro_switch without - * any duplication or gas-specific .macro tricks. - * - * Symbol naming uses __USER_LABEL_PREFIX__ so labels match the C - * compiler's call-site mangling on both ELF (no prefix) and Mach-O - * (leading "_"). + * The assembly primitives live in aarch64_elf.s / aarch64_macho.s so this + * translation unit stays plain C and does not require file-scope asm support. */ #include <cfree/coro.h> @@ -61,100 +56,3 @@ void __cfree_coro_ctx_init(coro_ctx* ctx, void* stack_base, size_t stack_len, c->regs[11] = (uintptr_t)__cfree_coro_trampoline; /* lr */ c->regs[12] = top; /* sp */ } - -#define STR_(x) #x -#define STR(x) STR_(x) -#define SYM(n) STR(__USER_LABEL_PREFIX__) #n - -/* Save callee-saved state into [reg]; clobbers x9 (caller-saved). */ -#define SAVE_INTO(reg) \ - " stp x19, x20, [" reg \ - ", #0]\n" \ - " stp x21, x22, [" reg \ - ", #16]\n" \ - " stp x23, x24, [" reg \ - ", #32]\n" \ - " stp x25, x26, [" reg \ - ", #48]\n" \ - " stp x27, x28, [" reg \ - ", #64]\n" \ - " stp x29, x30, [" reg \ - ", #80]\n" \ - " mov x9, sp\n" \ - " str x9, [" reg \ - ", #96]\n" \ - " stp d8, d9, [" reg \ - ", #104]\n" \ - " stp d10, d11, [" reg \ - ", #120]\n" \ - " stp d12, d13, [" reg \ - ", #136]\n" \ - " stp d14, d15, [" reg ", #152]\n" - -/* Restore callee-saved state from [reg]; clobbers x9. */ -#define RESTORE_FROM(reg) \ - " ldp d8, d9, [" reg \ - ", #104]\n" \ - " ldp d10, d11, [" reg \ - ", #120]\n" \ - " ldp d12, d13, [" reg \ - ", #136]\n" \ - " ldp d14, d15, [" reg \ - ", #152]\n" \ - " ldp x19, x20, [" reg \ - ", #0]\n" \ - " ldp x21, x22, [" reg \ - ", #16]\n" \ - " ldp x23, x24, [" reg \ - ", #32]\n" \ - " ldp x25, x26, [" reg \ - ", #48]\n" \ - " ldp x27, x28, [" reg \ - ", #64]\n" \ - " ldp x29, x30, [" reg \ - ", #80]\n" \ - " ldr x9, [" reg \ - ", #96]\n" \ - " mov sp, x9\n" - -__asm__ ( - ".text\n" - ".align 4\n" - - /* setjmp(env) -- env in x0. lr at call time is the return address - into the caller, exactly what longjmp must restore. */ - ".globl " SYM(setjmp) "\n" - SYM(setjmp) ":\n" - SAVE_INTO("x0") - " mov x0, #0\n" - " ret\n" - - /* longjmp(env, val) -- env in x0, val in x1. - longjmp(_, 0) must deliver 1 (C11 7.13.2.1p4); csinc gives - x0 = (x1 != 0) ? x1 : 1 branch-free. */ - ".globl " SYM(longjmp) "\n" - SYM(longjmp) ":\n" - RESTORE_FROM("x0") - " cmp x1, #0\n" - " csinc x0, x1, xzr, ne\n" - " ret\n" - - /* __cfree_coro_switch(from, to, value) -- x0, x1, x2. Save into [x0], - restore from [x1], deliver x2 in x0 (which is both the return - register here and the first-arg register the trampoline reads - on a fresh context's first run). */ - ".globl " SYM(__cfree_coro_switch) "\n" - SYM(__cfree_coro_switch) ":\n" - SAVE_INTO("x0") - RESTORE_FROM("x1") - " mov x0, x2\n" - " ret\n" - - /* __cfree_coro_trampoline -- on first entry x0 = value (delivered), - x19 = entry fn (set by __cfree_coro_ctx_init), sp aligned to 16. brk if entry - returns. */ - ".globl " SYM(__cfree_coro_trampoline) "\n" - SYM(__cfree_coro_trampoline) ":\n" - " blr x19\n" - " brk #0\n" -); diff --git a/rt/lib/coro/aarch64_elf.s b/rt/lib/coro/aarch64_elf.s @@ -0,0 +1,71 @@ +.text +.align 4 + +.globl setjmp +setjmp: + stp x19, x20, [x0, #0] + stp x21, x22, [x0, #16] + stp x23, x24, [x0, #32] + stp x25, x26, [x0, #48] + stp x27, x28, [x0, #64] + stp x29, x30, [x0, #80] + mov x9, sp + str x9, [x0, #96] + stp d8, d9, [x0, #104] + stp d10, d11, [x0, #120] + stp d12, d13, [x0, #136] + stp d14, d15, [x0, #152] + mov x0, #0 + ret + +.globl longjmp +longjmp: + ldp d8, d9, [x0, #104] + ldp d10, d11, [x0, #120] + ldp d12, d13, [x0, #136] + ldp d14, d15, [x0, #152] + ldp x19, x20, [x0, #0] + ldp x21, x22, [x0, #16] + ldp x23, x24, [x0, #32] + ldp x25, x26, [x0, #48] + ldp x27, x28, [x0, #64] + ldp x29, x30, [x0, #80] + ldr x9, [x0, #96] + mov sp, x9 + cmp x1, #0 + csinc x0, x1, xzr, ne + ret + +.globl __cfree_coro_switch +__cfree_coro_switch: + stp x19, x20, [x0, #0] + stp x21, x22, [x0, #16] + stp x23, x24, [x0, #32] + stp x25, x26, [x0, #48] + stp x27, x28, [x0, #64] + stp x29, x30, [x0, #80] + mov x9, sp + str x9, [x0, #96] + stp d8, d9, [x0, #104] + stp d10, d11, [x0, #120] + stp d12, d13, [x0, #136] + stp d14, d15, [x0, #152] + ldp d8, d9, [x1, #104] + ldp d10, d11, [x1, #120] + ldp d12, d13, [x1, #136] + ldp d14, d15, [x1, #152] + ldp x19, x20, [x1, #0] + ldp x21, x22, [x1, #16] + ldp x23, x24, [x1, #32] + ldp x25, x26, [x1, #48] + ldp x27, x28, [x1, #64] + ldp x29, x30, [x1, #80] + ldr x9, [x1, #96] + mov sp, x9 + mov x0, x2 + ret + +.globl __cfree_coro_trampoline +__cfree_coro_trampoline: + blr x19 + brk #0 diff --git a/rt/lib/coro/aarch64_macho.s b/rt/lib/coro/aarch64_macho.s @@ -0,0 +1,71 @@ +.text +.align 4 + +.globl _setjmp +_setjmp: + stp x19, x20, [x0, #0] + stp x21, x22, [x0, #16] + stp x23, x24, [x0, #32] + stp x25, x26, [x0, #48] + stp x27, x28, [x0, #64] + stp x29, x30, [x0, #80] + mov x9, sp + str x9, [x0, #96] + stp d8, d9, [x0, #104] + stp d10, d11, [x0, #120] + stp d12, d13, [x0, #136] + stp d14, d15, [x0, #152] + mov x0, #0 + ret + +.globl _longjmp +_longjmp: + ldp d8, d9, [x0, #104] + ldp d10, d11, [x0, #120] + ldp d12, d13, [x0, #136] + ldp d14, d15, [x0, #152] + ldp x19, x20, [x0, #0] + ldp x21, x22, [x0, #16] + ldp x23, x24, [x0, #32] + ldp x25, x26, [x0, #48] + ldp x27, x28, [x0, #64] + ldp x29, x30, [x0, #80] + ldr x9, [x0, #96] + mov sp, x9 + cmp x1, #0 + csinc x0, x1, xzr, ne + ret + +.globl ___cfree_coro_switch +___cfree_coro_switch: + stp x19, x20, [x0, #0] + stp x21, x22, [x0, #16] + stp x23, x24, [x0, #32] + stp x25, x26, [x0, #48] + stp x27, x28, [x0, #64] + stp x29, x30, [x0, #80] + mov x9, sp + str x9, [x0, #96] + stp d8, d9, [x0, #104] + stp d10, d11, [x0, #120] + stp d12, d13, [x0, #136] + stp d14, d15, [x0, #152] + ldp d8, d9, [x1, #104] + ldp d10, d11, [x1, #120] + ldp d12, d13, [x1, #136] + ldp d14, d15, [x1, #152] + ldp x19, x20, [x1, #0] + ldp x21, x22, [x1, #16] + ldp x23, x24, [x1, #32] + ldp x25, x26, [x1, #48] + ldp x27, x28, [x1, #64] + ldp x29, x30, [x1, #80] + ldr x9, [x1, #96] + mov sp, x9 + mov x0, x2 + ret + +.globl ___cfree_coro_trampoline +___cfree_coro_trampoline: + blr x19 + brk #0 diff --git a/src/api/cg.c b/src/api/cg.c @@ -3732,6 +3732,22 @@ void cfree_cg_drop(CfreeCg *g) { api_release(g, &v); } +int cfree_cg_top_const_int(CfreeCg *g, int64_t *out_value) { + ApiSValue *v; + CfreeCgTypeId ty; + u32 width; + if (!g || !out_value || !g->sp) + return 0; + v = &g->stack[g->sp - 1u]; + if (v->kind != SV_OPERAND || v->op.kind != OPK_IMM) + return 0; + ty = api_sv_type(v); + if (!api_foldable_int_like_type(g->c, ty, &width)) + return 0; + *out_value = api_fold_result(g->c, ty, (u64)v->op.v.imm, width); + return 1; +} + void cfree_cg_rot3(CfreeCg *g) { ApiSValue a, b, c; if (!g || g->sp < 3) diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -495,6 +495,10 @@ ordinary calls. | `builtin_19_atomic_cas_loop` | ★ | lock-free increment via CAS retry loop (ACQ_REL/ACQUIRE pair) | 42 | | `builtin_20_ctz` | ★ | `__builtin_ctz(1u<<5) + 37` — count trailing zeros, low-bit case | 42 | | `builtin_21_ctz_high` | ★ | `__builtin_ctz(0x80000000u) - 31 + 42` — high-bit case (31 trailing zeros) | 42 | +| `builtin_22_ctz_long_widths` | ★ | `__builtin_ctzl` + `__builtin_ctzll`; operand type selects intrinsic width | 42 | +| `builtin_23_atomic_long_literal_convert` | ★ | store/RMW integer literals converted to unsigned long atomic object type | 42 | +| `builtin_24_atomic_lock_free` | ★ | target-aware lock-free folding through `if`, `&&`, and `||`; dead 16-byte atomic arms suppress codegen | 42 | +| `builtin_25_atomic_fetch_nand` | ★ | `__atomic_fetch_nand` lowers to atomic NAND RMW | 42 | | `builtin_99_syscall0` | (deferred) | `__cfree_syscall0` requires linking against the syscall stub; covered in `test/libc` | — | ## Variadic coverage diff --git a/test/parse/cases/builtin_22_ctz_long_widths.c b/test/parse/cases/builtin_22_ctz_long_widths.c @@ -0,0 +1,5 @@ +int test_main(void) { + unsigned long a = 1ul << 4; + unsigned long long b = 1ull << 6; + return __builtin_ctzl(a) + __builtin_ctzll(b) + 32; +} diff --git a/test/parse/cases/builtin_22_ctz_long_widths.expected b/test/parse/cases/builtin_22_ctz_long_widths.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_23_atomic_long_literal_convert.c b/test/parse/cases/builtin_23_atomic_long_literal_convert.c @@ -0,0 +1,6 @@ +int test_main(void) { + unsigned long x = 99; + __atomic_store_n(&x, 0, __ATOMIC_RELEASE); + unsigned long old = __atomic_fetch_add(&x, 1, __ATOMIC_ACQ_REL); + return (int)(old + x + 41); +} diff --git a/test/parse/cases/builtin_23_atomic_long_literal_convert.expected b/test/parse/cases/builtin_23_atomic_long_literal_convert.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_24_atomic_lock_free.c b/test/parse/cases/builtin_24_atomic_lock_free.c @@ -0,0 +1,20 @@ +int test_main(void) { + int x = 0; + long y = 0; + int score = 0; + if (__atomic_always_lock_free(1, &x)) score += 1; + if (__atomic_always_lock_free(2, &x)) score += 2; + if (__atomic_always_lock_free(4, &x)) score += 4; + if (__atomic_is_lock_free(8, &y)) score += 8; + if (!__atomic_always_lock_free(16, &y)) score += 27; + if (__atomic_always_lock_free(32, 0)) { + unsigned __int128 *wide = (unsigned __int128 *)0; + score += 90 + (int)__atomic_load_n(wide, __ATOMIC_SEQ_CST); + } + if (__atomic_always_lock_free(32, 0) || + (__atomic_always_lock_free(32, 0) && ((unsigned long)&score % 16) == 0)) { + unsigned __int128 *wide = (unsigned __int128 *)0; + score += 90 + (int)__atomic_load_n(wide, __ATOMIC_SEQ_CST); + } + return score; +} diff --git a/test/parse/cases/builtin_24_atomic_lock_free.expected b/test/parse/cases/builtin_24_atomic_lock_free.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_25_atomic_fetch_nand.c b/test/parse/cases/builtin_25_atomic_fetch_nand.c @@ -0,0 +1,6 @@ +int test_main(void) { + int x = -43; + int prior = __atomic_fetch_nand(&x, -1, __ATOMIC_SEQ_CST); + (void)prior; + return x; +} diff --git a/test/parse/cases/builtin_25_atomic_fetch_nand.expected b/test/parse/cases/builtin_25_atomic_fetch_nand.expected @@ -0,0 +1 @@ +42