A dialect of C with defer and automatic zero-initialization.
Prism is a lightweight and very fast transpiler that makes C safer without changing how you write it.
- 1276 tests — edge cases, control flow, nightmares, trying hard to break Prism
- Building Real C — OpenSSL, SQLite, Bash, GNU Coreutils, Make, Curl
- Proper transpiler — tracks typedefs, respects scope, catches unsafe patterns
- Opt-out features Disable parts of the transpiler, like zero-init, with CLI flags
- Drop-in overlay Use
CC=prismin any build system — GCC-compatible flags pass through automatically - Single Repo — 7k lines, zero dependencies, easy to audit
Prism is a proper transpiler, not a preprocessor macro.
- Track Types: It parses
typedefs to distinguish pointer declarations from multiplication (the "lexer hack"), ensuring correct zero-initialization. - Respect Scope: It understands braces
{}, statement expressions({ ... }), and switch-case fallthrough, ensuringdeferfires exactly when it should. - Detect Errors: It catches unsafe patterns (like jumping into a scope with
goto) before they become runtime bugs.
cc prism.c -flto -s -O3 -o /tmp/prism && /tmp/prism install && rm /tmp/prismOpen a Developer Command Prompt (or run vcvars64.bat) and build:
cl /Fe:prism.exe prism.c /O2 /D_CRT_SECURE_NO_WARNINGS /nologoRequires Visual Studio Build Tools with the Desktop development with C++ workload.
Note: Prism builds and runs natively on Windows. The transpiler output is GCC/Clang-first — features like
__auto_typeindeferreturn values are not yet MSVC-compatible. UseCC=gccorCC=clangas the backend compiler on Windows for full transpiled-code support.
The problem: C requires manual cleanup at every exit point. Miss one and you leak.
// Standard C — 3 places to forget cleanup
int process(const char *path) {
FILE *f = fopen(path, "r");
if (!f) return -1;
void *buf = malloc(4096);
if (!buf) { fclose(f); return -1; } // Easy to forget fclose here
if (parse(buf) < 0) { free(buf); fclose(f); return -1; } // And here
free(buf);
fclose(f);
return 0;
}With Prism: Write cleanup once. It runs on every exit.
int process(const char *path) {
FILE *f = fopen(path, "r");
if (!f) return -1;
defer fclose(f);
void *buf = malloc(4096);
if (!buf) return -1; // fclose(f) runs automatically
defer free(buf);
if (parse(buf) < 0) return -1; // both cleanup, in reverse order
return 0;
}Defers execute in LIFO order (last defer runs first) at scope exit — whether via return, break, continue, goto, or reaching }.
Edge cases handled:
- Statement expressions
({ ... })— defers fire at inner scope, not outer switchfallthrough — defers don't double-fire between cases- Nested loops —
break/continueunwind the correct scope
Forbidden patterns: Functions using setjmp/longjmp, vfork, or inline assembly are rejected to prevent resource leaks from non-local jumps.
Opt-out: prism -fno-defer src.c
The problem: Uninitialized reads are the #1 source of C vulnerabilities. Compilers don't require initialization, and -Wall only catches obvious cases.
// Standard C — compiles fine, undefined behavior at runtime
int sum_positive(int *arr, int n) {
int total; // uninitialized — could be anything
for (int i = 0; i < n; i++)
if (arr[i] > 0) total += arr[i];
return total; // UB: total was never set if no positives
}With Prism: All locals start at zero. The above code just works.
void example() {
int x; // 0
char *ptr; // NULL
int arr[10]; // {0, 0, ...}
struct { int a; float b; } s; // {0, 0.0}
}Typedef tracking: Prism parses headers to recognize size_t, uint8_t, FILE *, pthread_mutex_t, etc. This distinguishes size_t x; (declaration → initialize) from size_t * x; (expression → don't touch).
VLA support: Variable-length arrays get memset at runtime.
Opt-out: prism -fno-zeroinit src.c or per-variable with raw.
The raw keyword opts out of zero-initialization for a specific variable.
void example() {
raw int x; // Uninitialized
raw char buf[65536]; // No memset overhead
raw struct large data; // Skip zeroing
}When to use:
- Large buffers that will be immediately overwritten (
read(),recv()) - Performance-critical inner loops where zeroing is measurable overhead
- Interfacing with APIs that fully initialize the data
Safety interaction: Variables marked raw can be safely jumped over by goto — since they're not initialized anyway, skipping them isn't undefined behavior.
void allowed() {
goto skip;
raw int x; // OK: raw opts out of initialization
skip:
return;
}Prism acts as a static analysis tool, turning common C pitfalls into compile-time errors.
Standard C allows goto to skip variable initialization, leading to undefined behavior. Prism performs a single-pass dominator analysis to forbid this:
// THIS WILL FAIL TO COMPILE
void unsafe() {
goto skip;
int x; // Prism guarantees x is zero-initialized
skip:
printf("%d", x);
}
// Error: goto 'skip' would skip over variable declaration 'x'Prism rejects defer in functions that use non-local control flow:
void bad() {
jmp_buf buf;
defer cleanup(); // Error: defer forbidden with setjmp
if (setjmp(buf)) return;
}This prevents resource leaks when longjmp bypasses defer cleanup.
Use -fno-safety to turn safety errors into warnings (for gradual adoption):
prism -fno-safety legacy.c # Compiles with warnings instead of errorsPrism handles real-world build scenarios:
# Multiple source files
prism main.c utils.c -o app
# Mix with assembly
prism main.c boot.s -o kernel
# C++ files pass through untouched (uses g++/clang++ automatically)
prism main.c helper.cpp -o mixedPassthrough files: .s, .S (assembly), .cc, .cpp, .cxx, .mm (C++), .m (Objective-C) are passed directly to the compiler without transpilation.
Prism emits #line directives so compiler errors point to your original source, not the transpiled output:
main.c:42:5: error: use of undeclared identifier 'foo'
Not:
/tmp/prism_xyz.c:1847:5: error: use of undeclared identifier 'foo'
Disable: prism -fno-line-directives src.c (useful for debugging transpiler output)
Prism uses a GCC-compatible interface — most flags pass through to the backend compiler.
Prism v0.110.0 - Robust C transpiler
Usage: prism [options] source.c... [-o output]
GCC-Compatible Options:
-c Compile only, dont link
-o <file> Output file
-O0/-O1/-O2/-O3/-Os Optimization level (passed to CC)
-g Debug info (passed to CC)
-W... Warnings (passed to CC)
-I/-D/-U/-L/-l Include/define/lib flags (passed to CC)
-std=... Language standard (passed to CC)
Prism Options:
-fno-defer Disable defer feature
-fno-zeroinit Disable zero-initialization
-fno-line-directives Disable #line directives in output
-fflatten-headers Flatten included headers into single output file
-fno-flatten-headers Disable header flattening
-fno-safety Safety checks warn instead of error
--prism-cc=<compiler> Use specific compiler (default: $CC or cc)
--prism-verbose Show transpile and compile commands
Commands:
run <src.c> Transpile, compile, and execute
transpile <src.c> Output transpiled C to stdout
install Install prism to /usr/local/bin/prism
--help, -h Show this help
--version, -v Show version
Environment:
CC C compiler to use (default: cc)
PRISM_CC Override CC for prism specifically
Examples:
prism foo.c Compile to a.out (GCC-compatible)
prism foo.c -o foo Compile to 'foo'
prism run foo.c Compile and run immediately
prism transpile foo.c Output transpiled C
prism transpile foo.c -o out.c Transpile to file
prism -c foo.c -o foo.o Compile to object file
prism -O2 -Wall foo.c -o foo With optimization and warnings
CC=clang prism foo.c Use clang as backend
Apache 2.0 license (c) Dawn Larsson 2026
https://github.com/dawnlarsson/prismPrism can replace gcc or clang in any build system:
# Instead of:
CC=gcc make
# Use:
CC=prism makeAll standard compiler flags (-O2, -Wall, -I, -L, -l, etc.) pass through automatically to the backend compiler.
Prism can be compiled as a library for embedding in other tools:
# Compile as library (excludes CLI)
cc -DPRISM_LIB_MODE -c prism.c -o prism.oAPI:
PrismFeatures prism_defaults(void);
PrismResult prism_transpile_file(const char *path, PrismFeatures features);
void prism_free(PrismResult *r);Note: Library mode is currently limited and not thread-safe. The transpiler uses global state internally. Call from a single thread only, or protect with a mutex.
Apache 2.0 license (c) Dawn Larsson 2026