Prism v1.1.5
Prism 1.1.5 fixes 200+ bugs since v1.1.4, Prism gets faster ~2.02x end-to-end warm cache; 1.65x on the stress TU with the cache disabled.
New: preprocessed-output cache, prism check <analyzer>, @file response files, stdin input.
Four fixes address bugs that corrupt a build: a uint16 scope-cap that dropped the scope tree past 32,768 scopes with no diagnostic; orelse reaching the C backend intact inside a case label; a braceless defer declaration that copied every following statement into the cleanup; and orelse on a volatile or _Atomic object reading it up to three times.
Performance
Preprocessed-output cache. cc -E was 39–99% of wall time (8.19 of 8.28 ms on a no-include file; 103.8 of 262.8 ms on test.c). Prism now caches the preprocessor's output and skips the spawn when nothing it read changed.
The preprocess phase drops 10–25x on a hit, scaling with how much output has to be read back: 6.5 → 0.4 ms for a 57 KB entry, 29.0 → 1.2 ms for 328 KB, 46.2 → 1.8 ms for 1.2 MB. End-to-end that is 2.02x v1.1.4 on a warm cache (70.9 → 35.0 ms, best-of-3 over four suite files).
Transpiler. Gains are workload-dependent, and the cache accounts for most of the end-to-end figure above. With PRISM_NO_PP_CACHE=1, on a 2,500-function defer/orelse/-fbounds-check stress TU it is 1.65x (70.9 → 43.0 ms); across four ordinary suite files 1.07x, ranging 1.20x on test.torture.c down to 0.94x on test.defer.c. Earlier notes reported ~65% on the phase timings of a stress TU; that holds for that shape, not as a general figure.
On Arch/9950X, GCC -O2 -g -DNDEBUG, CPU 8 pinned, 80-run perf stat batches: the 6,091,701-byte procedural TU moves 77.66 → 70.32 ms task-clock (−9.45%), cycles −10.26%, instructions −10.36%. The 1,109,463-byte self-host TU moves 15.94 → 15.05 ms (−5.58%). Outputs byte-identical.
Bug Fixes
defer
- Scope-cap
uint16truncation: a TU with more than 32,768 scopes lost the rest of the scope tree with no error and mis-lowered file-scope declarations (b007d1e) - A declaration as a braceless body scanned past its own semicolon and pasted every statement up to the block close into the deferred code, so they ran a second time at scope exit. The prior test only checked the output contained
int t = 0, so it never saw the duplication (16bba07) - Missing-semicolon detection inspected only the body's first token:
defer break;was rejected,defer f() break;was accepted and emitted asf() break;with the keyword stripped, so the backend error pointed at code the user never wrote (unreleased) - Labeled break/continue unwound the wrong scope, including GNU
__label__(f6bff02) - Mid-expression
deferafter a comma or inreturn(x=1, defer g();,return defer g(), 0;) leaked the keyword in release builds;reject_defer_in_expr_contextwasPRISM_DEBUG-only (78f9c8a) - Expression-context splices through
sizeof,!,~,&, casts and?:(86c20fc) - Scope/block depth confusion over-unwound defers across
gotoboundaries (c0a4935) - Braceless bodies under name shadows mis-parsed;
defer;, for-init and++forms had to stay identifiers (06bb332) - Braceless control flow in defer/orelse bodies broke brace injection (fc098bb)
{ defer }brace-init false reject (86c20fc)typeof(defer)(fc3716d)defer _Pragma(c0a4935)- File-scope
deferwas not rejected (5732201) in_defer_emitthread-local was not reset after apparse_errorlongjmp skipped the restore (a661b6b)- Attribute labels in a defer chain (a661b6b)
- Unevaluated
sizeofdefer was not a no-op (f2cf4aa) - Zero-init-off
orelseinside a defer (6bf4f84) - Braceless
defer die();missed__builtin_unreachable()injection: the synthesized;skipped the noreturn probe (78f9c8a)
orelse
- Reached the C backend intact inside a
caselabel:case (x orelse 1):was accepted and the literal token passed through. Found by the tag-alphabet totality suite on its first run (16bba07) - On a
volatileor_Atomicobject, read the object up to three times; single-eval hoisting now covers atomic typedefs (ee1fc71) typeof(int[N])/_Atomic(int[N])accepted because arrayness lived on the type rather than the declarator, emitting brokenif (!a){…}(c0a4935)- Dimension holes in
_Alignof,_Generic,_Static_assertand nestedtypeof(78f9c8a) - Return-expression leak:
return (x) orelse 1leaked because the backward walk crossed(x)toreturnand applied thereturn orelse;identifier exemption (78f9c8a) static/extern/_Thread_localdimension orelse lowered to illegal static VLAs (17329c2)_Atomic(int[n orelse …])leaked: type-specifier dims were skipped and the type emitter never walked_Atomic((17329c2).orelse orelsedecl-init (06bb332)- Mid-chain empty
orelse orelse;orelse()now allowed (86c20fc) - Mid-chain
continue/ block-form reject (f2cf4aa) - Struct-value and chain-after-control-flow rejects (b007d1e)
- Keyword leak via
static rawverbatim bypass - Raw string literal
R"(...)"inside#if 0desynchronized block-comment state __attribute__bracket-orelse queue desynchronizationinit-castVLA crash on variably-modified types- Bare comma split escaped braceless control flow; paren comma corruption in chained expressions
- Preprocessor-conditional mangling in bracket orelse
- Designator side effects, compound-literal VLA, and nested
_Static_assertleak (f6bff02) - Non-ICE designator-dimension orelse; false control-flow diagnostic (06bb332)
- Soft type-spelling orelse values (06bb332)
const _Atomic/volatileif-hoist;_Nonnull/_Nullablequalifier scoping (86c20fc)typeof(_Atomic(int) orelse 0)leaked:_Atomic(T)was not treated as a type-specifier constructortypedef int (*F)(int a[0 orelse 1])ternary-lowered parameter dims; the declarator check jumped the(*F)group and typedef walks never called the prototype reject- GNU range designators
[0 ... 2 orelse 3]ternary-destroyed the...into illegal C constexpr int a[0 orelse 1]hoisted a runtime temporary into a constexpr dimension- Empty-orelse emit leaks;
return+ defer subscript orelse (65b0ee7) - Paren-led bare orelse after a control statement (97a9fba, b20455e)
- Multi-label and attribute bare-orelse peel (e03ee60)
- Brace-init designator leak;
_Atomicside-effect register; GNU attribute-between-dims FIFO (f2cf4aa) typeof(enum)did not setis_enum(f2cf4aa)enum E : typeof(T) { orelse }andenum E : _Atomic(int) { orelse }(c0a4935, 86c20fc)asmsymbolic[orelse]operand names (c0a4935)- Array variable orelse that can never trigger is now diagnosed rather than silently lowered
bounds-check
- Local pointers, including qualified, typedef, pointer-to-array and
rawforms, could shadow a tracked file-scope array without creating a typedef-table shadow, sog[i]inherited the outer array and was wrapped withsizeof(pointer)/sizeof(pointer[0]) - Block-scope incomplete
extern int g[]inherited the complete outer bounds, generating an invalidsizeof(g) - Decayed parameters wrapped against the file-scope array:
int g[10]; f(int g[20]){ return g[i]; }, same for K&R andint *gparams that skipped the shadow optimization - Wraps skipped when the index itself used
orelse(a[i orelse 0]) becausetry_bracket_orelseran before the subscript check in the emit loop; they compose now, with the ternary index inside__prism_bchk(78f9c8a) - Silent bypasses:
(i)[tern],((i))[tern],(2)[tern](last emitted was));*((T*)(a+i))and*(((T*)(a+i)))(a grouping paren hid the cast+additive pair); bareidx[&arr[0]]without an outer paren;(*&(a))[i]and(*a)[j](c0a4935, 78f9c8a) p[a[i]]false-rejected as commutativeidx[arr]despite the subscripted-index exemption (c0a4935)- Cast×subscript used naive
sizeof(arr)/sizeof(arr[0])instead of the cast element size - Cast-prefix unary
&one-past ((void)&a[n],(int*)&a[n]) mistook the cast)for a binary-&operand (78f9c8a) (a+i)[0],0[a+i], cast-deref and_Atomic·typeofdims (f6bff02)- One-past parenthesized bounds (aa1b961); paren-deref (b007d1e)
- for/if/switch-init declarations never registered array bindings (missing wraps) or shadows (pointer init-stmt inheriting outer
sizeof) (c0a4935) - Block-scope
static/constexprinitializers wrappedg[0]into a non-ICE (c0a4935) rawVLAs were still re-registered (c0a4935)- Pointer-arith
*(a+i)bypass after the registry split; the lookup still consulted only the typedef table typeof(fixed_array)bounds registration; VLA-of-pointers false wrapsemit_statementsdid not run bounds checks (e03ee60)- C89
__prism_bchkform (fc3716d)
zero-init
- Empty and
[0]aggregate memset (06bb332) - Const-subobject memset was undefined behaviour (aa1b961)
typeof(_BitInt(N))sole-FAMand nested-empty{0}, plus register/const × memset rejects- for/if-init
_Atomicandregisterrejects (86c20fc) - Member-side const-union false reject (86c20fc)
_Atomicaggregate and union/VLA const cases now diagnosed rather than silently mis-lowered- Soft-keyword bitfields (fc3716d)
SUE {scope tagging (ee1fc71)
auto-static / auto-unreachable
- Auto-static promoted mutable
typeof(int[N]); typeof-as-const applies to orelse temps only (c0a4935) - Auto-static missed leading
cleanup/[[attr]], typedef-const, unbraced string initializers, C23true/false,typeof(int[N])(86c20fc) - Statement-final noreturn through casts, grouping, comma tails and call arguments (
(void)(0, die());,die(), 1;,foo(die());) missed__builtin_unreachable(); detection required an immediate;after the call's)(78f9c8a) - Unevaluated operands falsely marked unreachable:
sizeof die();and no-parensizeof +die()(78f9c8a) - Contextual noreturn/taint poisoning via soft-keyword callee, attribute name,
asm __volatile__ goto, declaration shadow (aa1b961) - User TU definitions of
exit/setjmp/… false-tainted and false-unreached _Noreturn void a(), b();tagged only the first declaratorasmsoft-keyword taint (fc3716d)
raw
raw { … }still lowereddefer/orelseor injected auto-static, bounds and unreachabletypedef int raw; raw raw xkeyword strip (c0a4935)- Statement-form
raw { … }suppress blocks (c0a4935) rawon a VLA is still subject to the goto check, since jumping past a VLA bypasses stack allocation regardless of initialization- An all-
rawdeclaration must emit identically to the same declaration withrawdeleted and zero-init off. That differential failed 32/229 cells on first run and exposed__attribute__((unused)) const raw int v;emittingconst const int v;, invalid C that Phase 1 accepted. The attribute prelude was bounded at therawtoken even whenrawsat inside the specifiers
tokenizer / preprocessor
- Function-like macros were silently dropped from re-emitted defines while object-like ones survived:
#define MAX(a,b)did not round-trip a transpile (unreleased) .iinput://\line splice, UTF-8 BOM, universal character names in identifiers (06bb332).itranslation units were dropped entirely (fc3716d)- Digraph
%:line markers (86c20fc); digraph/trigraph#define(f6bff02) - UTF-16 BOM now rejected with a diagnostic instead of mis-tokenized (b007d1e)
- Sticky
# N "a.h" 1 3system-skip dropped the rest of a user TU - Hard type keywords used as declarator names (06bb332)
- Parenthesized
_Genericfold (06bb332); glibc_Genericdefault-fold declarations (5732201) - GNU nested-function
is_func_bodyCFG classification (06bb332); nested-function verbatim emission (fc3716d) goto"assigned-first" analysis acceptedx = x + 1(86c20fc)gotointo C23if/switchinit when the init-semicolon search returned NULL- Brace-unsafe walk could scan past
type_endon large TUs (86c20fc) - Enum-walk underflow (65b0ee7)
- Ill-formed
while (decl),do decl,for (raw {…}), scalarfor (raw int x = 1),while(defer 0),if(defer 0)in control parens
driver / CLI
@fileresponse files treated a backslash as a path separator on Windows, eatingC:\paths (1d18a57)- Force-include re-injection; split
-Iunder-fpreprocessed;bits/re-include (fc3716d) -Mdependency routing (b007d1e)-U_GNU_SOURCEordering; clang#… 3under no-flatten (f6bff02)- No-flatten
#includepath escaping - C++ mix driver preprocess/skip, C++ link,
-x ctemps, cross-compiler prefix (65b0ee7) sos_ensure_doOOM dangling pointer (5732201);sos_*thread-local free (a661b6b)- Calling-convention return capture (6bf4f84)
- Newline emission before declarations after statement boundaries (ee1fc71)
- Control-paren scope tracking in
emit_statements; for-initis_looppreservation (a661b6b);else/doparen-led forms anddoat-statement-start (b20455e); GNU attribute control-paren tracking (e03ee60)
Windows / MSVC
- memstream, handle inheritance,
PATH,mkstemps, install (aa1b961) build_clean_environwas used bywindows.cbefore its declaration; MSVC infersint()and then rejects the real declaration with C2040. GCC 14+ would reject it too (unreleased)win32_memstream_pathremoved; suite tests rewritten forDELETE_ON_CLOSE(06bb332)mkstempsand noreturn shims (5732201)CLI_PUSHunder-allocation (65b0ee7)__assume(0)now counted as an unreachable marker alongside__builtin_unreachable(17329c2)- Fake
cltest used bash; Alpine has no/bin/bash(1d18a57)
assertions / harness
PRISM_DEBUGfalse assertions: balanced-group checks treated initializer braces and index brackets as parentheses, and the missing-declaration-recipe assertion forgot that Phase 1 excludes GNU nested-function locals from the outer CFG table (78f9c8a)- Harness accounting: a local
totalintest_win_env_scrubbingshadowed the runner's thread-local CHECK counter, so three checks incrementedpassedbut nottotal. The invarianttotal == passed + failedis now itself a counted meta-test - Four leaked temp/result ownership paths found by LeakSanitizer; undefined
_Boolfeature-mask write; UB-based OOB "trap" oracles replaced with wrapper-presence and zero-survivor checks
Testing
104 generative tiers Prism test suit moves to more procedural tests to cover much wider set of edge cases.
suites test.autostatic.c, test.autounreach.c, test.golf.c, test.cert.c and test.raw.c were absorbed into generative tiers.
Code organization
C-language logic moved out of prism.c into parse.c, which is moving towards a reusable C-parsing library with Prism dialect support. parse.c 4,595 → 12,928 lines; prism.c 12,336 → 9,696.
Stats
- 200+ bugs fixed since v1.1.4
- 40 commits; 36 files changed, +37,133 / −17,479
- 104 generative tiers, 3,820 CHECK sites (876 at v1.1.4)
- Suite: 14,543+ platform-inclusive CHECK inventory, up from 6,035 in v1.1.4. Audited: 14,286/14,286 Darwin/Clang, 14,355/14,355 Arch/GCC 16, the latter also clean under ASan + UBSan + LeakSanitizer. 11,327/11,327 verified under GCC across 18 of 19 tiers.
test.parse.crequires Clang, since it usesfor (typedef double x;;), which C11 6.8.5.3 permits only forauto/register - Self-host: stage0 → stage1 → stage2 → stage3 transpile output byte-identical (1,058,598 bytes)
--prism-verifypasses onprism.c,parse.c, and all 21 corpus sources- 2.02x end-to-end warm cache; 1.65x on the stress TU with the cache disabled