Every pragma and what it changes
#pragma — what the compiler readsEvery pragma described on this page is one the compiler or runtime actually reads. Until v3.1.038.1 this file was a 2026-05 PORTING TODO published under a reference's name: it tracked which PB-era pragmas the then-new C emitter still ignored, and most of the names on it were read by nothing. Those sections are gone — including the float→int rounding switch, which was written up here as RESOLVED with a worked example, while that policy was eliminated 2026-06-29.(int)fis C's cast today:(int)3.7is3, andround()is how you get4. This page is not the complete list, and does not claim to be. The complete live list is whatcx -P <bogus>prints: it names the key you got wrong and then names every key the compiler reads, exactly spelled. That list is derived from the actualcxc_preproc_get_pragmacall sites, so it cannot drift from the compiler; this page explains the ones with something to explain. A pragma the compiler does not read is not an error.#pragmais open-ended by design — a directive a future consumer will read is a normal thing to write — so an unknown name is collected and silently ignored. That is exactly why a dead name must never be documented: nothing at compile time or run time will tell a reader the line did nothing. One name is neither live nor ignored:#pragma targetis a hard error (CX-E5043) in any casing. It is a command-line key, and the platform constants are seeded before your file is opened, so a source line cannot be early enough to move them — recording it silently would leave a file that reads as though it had chosen a target and compiled for the host.
#pragma decimals N — RESOLVED 2026-05-24What it does in CX: sets default precision for %f in print and printf. Default = 3.
Native behavior: C printf("%f", x) defaults to 6 digits.
Resolution shipped: emit_pragma_decimals() helper reads gPragmas("decimals") (default 3); rewrite_printf_format() rewrites bare %f → %.Nf (and %lf → %.Nlf) using the pragma value. Explicit precision specs like %.6f left untouched.
Verified: printf_test.cx now emits 100 / 4 = 25.000 matching CX VM exactly.
Does it cost anything? Asked by the user 2026-07-31 ("even a nano-second counts"), and MEASURED rather than reasoned about. Two things are worth knowing, and the second is the surprising one:
cx_print_decimals = N; at the top of main and nothing else changes.cx_apply_decimals) runs on every printf/sprintf regardless, because CX's default of 3 already differs from C's 6 — so a bare %f needs rewriting whether or not the pragma appears, and #pragma decimals 6 does not skip it either, it just rewrites to a different number.As of v3.211.0 that rewrite costs ~9 ns for a bare %f (mac, clang 16), down from ~59 ns — essentially all of the old cost was one snprintf call emitting four characters. An explicit precision (%.6f) skips the rewrite entirely by contract and costs only the format scan, ~5 ns. Re-run the measurement with cx tests/bench/decimals_bench.cx --run > /dev/null.
Is there a maximum? No — and there deliberately never was one enforced (v3.212.0). Whatever C's %.*f accepts, CX accepts: #pragma decimals 40 gives forty decimals, correctly, however long the result runs. The header used to advertise a 0..17 clamp via a cx_set_print_decimals setter; the setter had zero callers — emit assigns the global directly — so the range was documented and never enforced, and the dead function is now deleted rather than revived. What made a clamp look necessary was a fixed 64-byte buffer in the float-to-string path, which returned a length it did not have (str(1.0e300) = 305 characters). That buffer is gone: cx_str_from_float allocates exactly what the number needs, so there is nothing left to clamp.
Per-CALL precision: strf(v, d) (v3.212.0). This pragma is file-global and last-wins, which is the right default and the wrong tool when one program prints money at 2 decimals and an angle at 6. strf(price, 2) sets the count for that call only; strf(v) with no second argument still means the pragma. A NEGATIVE d is C's, deliberately — C reads a negative %.*f precision as "omitted", i.e. six decimals — because where numbers are concerned CX stays as close to C as it can. See Example 108 and cxtest 731.
#pragma floattolerance N — RESOLVED 2026-05-24What it does in CX: epsilon for == comparison on float operands.
Native behavior: C's == on double is bit-exact.
Resolution shipped: emit_pragma_floattolerance() reads gPragmas("floattolerance") (default "0.0001"). AST_EQ / AST_NE with float operand(s) emit bi_fabs(a-b) < tolerance (or >= for !=) instead of bit-exact == / !=. Operand types known via emit_infer_type which was already in place from string-concat dispatch.
Verified: no failing test exercises this yet, but the emit shape compiles and matches CX semantics for the float-equality cases.
No test currently exercises this — was a defensive fix done alongside the other two for completeness.
#pragma appname "Name" — LIVEApplication name. Used as the window title when a graphics screen is opened, and available to the banner. Read by the compiler (it is in the -P key set).
#pragma console on/offNative binaries are always console-capable (we always link in stdio). No-op is correct.
#pragma include, #pragma define, #pragma undefThe preprocessor's own directives, not configuration keys: preproc.c acts on them as it reads the file, so they never reach the pragma store and are not looked up by name. They are live, and they are the one group on this page that a -P flag cannot express.
#pragma onerrordefault off — the batteries-included error record (v3.308.0)Every failure writes a structured record — code, message, file, line and the CX FUNCTION it happened in — to the side channel ($CX_SIDE_CHANNEL), with no registration of any kind. Nothing is written unless a channel is configured, so a program's stdout and stderr are byte-for-byte what they have always been; under the screen the session names one and every program it runs reports its failures structurally without being changed.
off turns that record off. It does NOT touch a handler you registered with onerror(&h) — the pragma governs the default, and registration overrides. Lowers to a single cx_onerror_default(0) in the prologue, emitted only when the pragma is present, so an untouched program is byte-identical.
#pragma checks on — the runtime value guards (aliases check, checkbounds, onerror)Checks are off by default on both backends; #pragma checks on enables them on both; a program behaves identically compiled native or to the register VM either way. That is the whole rule (ruled 2026-08-17), and it is why this pragma has no 🟡 row: there is no "handled on one backend" state it can be in.
Off is C's bargain — nothing is spent asking, and the lenient answer stands. On, each guard becomes a named error at the point of the access:
| with checks on | what it catches | code |
|---|---|---|
| a field access through a null struct pointer | p->x where p is null — instead of dereferencing it | CX-E5024 native, CX-E5021/CX-E5022 on the VM, all three carrying null pointer dereference |
| a grown-array index out of range | the lenient 0 read / dropped write becomes fatal | CX-E5023 |
| access through a deleted container (v3.284.0) | a read or write reaching a handle containerDelete released — instead of the element type's zero and a dropped write | CX-E5044 |
The container line is the newest and the reason it exists is worth one sentence: by default that access is defined, not undefined — a read answers 0 / 0.00 / "" and a write drops, the same on both backends — so the pragma is not adding safety to something random, it is asking to be TOLD about something the language already answers quietly. See §containerDelete in the Language Reference.
Decided at compile time, so a program built without it carries no guard at all: the register VM's bytecode for a checks-off program is byte-for-byte what it would be if the feature did not exist.
#pragma gc default=N max=N min=N (his ruling 2026-09-09)Two numbers a program may set, and it needs neither in the ordinary case. The GC arena every compiled program allocates from starts at default= and grows automatically by doubling up to max=. His words: "2 pragmas. 1) default , 2) max (default 4G)" and "we auto increase unless there is a cap in #pragma".
| Pragma | Sets | Default |
|---|---|---|
#pragma gc default=N | the arena's STARTING size; growth from here is automatic | 64K |
#pragma gc max=N | the CEILING — reaching it is a LOUD refusal naming this pragma and printing the number | 4G |
#pragma gc min=N | allocation granularity; must be a multiple of 16 | 16 |
Sizes accept a K/M/G suffix. realloc= is default= under its prior spelling and still works.
REACHING THE CEILING IS NEVER SILENT, and that is the point of the entry. Until 3.3.015.0 the ceiling was 1 GB and the allocator refused by returning zero and saying nothing — so a json document too large to hold came back reporting valid = 1 with nothing in it, and the program printed 0 warnings and exited 0. cx_limits.h's own policy block had required the opposite since Fold A ("a clear pragma cap exceeded error, NOT silent truncation or a segfault"). A capacity refusal now says the document is WELL-FORMED, prints the ceiling and the bytes in use, and names this pragma — and it is told apart from a syntax error, so a good document is never accused of being corrupt.
Only a CEILING names a pragma. If the machine itself is out of memory, or a single allocation is larger than one arena chunk can describe, the message says so instead: pointing a user at a knob that cannot help is worse than saying nothing.
#pragma MaxX N (2026-06-05)Per the no-fixed-array policy (every growable structure: seed → cx_realloc doubling → cap → error, never silent truncation, never unbounded), each runtime growable array has a #pragma-overridable ceiling. Defaults are generous — a normal program never reaches them; the cap exists so a runaway or malicious input fails fast with a clear message instead of exhausting memory.
emit_c emits one cx_limit_set("MaxX", N) call into main() (after cx_rt_init) for each present pragma. Defaults live in runtime/cx_limits.h; the grow helper is cx_grow_capped() in runtime/cx_limits.c.
| Pragma | Caps | Default |
|---|---|---|
#pragma MaxAiOps N | ops in one AI function's bytecode (cxasm.c / cx_ai_native.c) | 1048576 |
#pragma MaxAiStrings N | string-pool entries per AI function | 65536 |
#pragma MaxAiFuncs N | registered AI functions | 65536 |
#pragma MaxAiLabels N | labels in one AI bytecode parse | 65536 |
#pragma MaxNamedSlots N | #pragma named slot table + native named-var (gVT) table | 1048576 |
#pragma MaxAiCache N | AI response-cache entries | 65536 |
#pragma MaxCodeScratch N | peek/poke bytecode scratch PCs + codeswap markers + pending asm | 16777216 |
#pragma MaxOwned N | CISC-VM owned container instances | 4096 |
#pragma MaxAiPrompt N | AI prompt space — the system prompt (ai_set_system) and a job's prompt (ai_call_async); both grow to what is set and refuse past this | 65536 |
#pragma MaxCellEdit N | the grid's cell edit box — how long a value may be typed into one editable cell; the buffer grows to what is typed and refuses past this, by name, rather than dropping the keystroke | 65536 |
#pragma MaxSortNdx N | sortndx element capacity ceiling | 16777216 |
#pragma MaxSortNdxTombstones N | deleted sortndx entries tolerated before the index auto-compacts | 64 |
#pragma MaxGameTimers N | the game timer pool (timercreate) | 256 |
#pragma MaxGameObjects N | the game-object pool (goload) | 128 |
#pragma MaxGameOverrides N | per-object runtime-override slots (goset*) | 16 |
#pragma MaxBlobs N | the blob handle table (embed() / asset() / goload file reads) | 512 |
#pragma MaxGfxFonts N | loaded fonts (loadfont) — the table grows from 32 and refuses past this | 4096 |
#pragma MaxGfxModels N | 3D models (loadmodel and the GenMesh* creators) — grows from 32 | 8192 |
#pragma MaxGfxShaders N | shaders — grows from 16 | 1024 |
#pragma MaxGfxCameras N | 3D cameras — grows from 4, which was the smallest limit in the graphics runtime | 256 |
#pragma MaxGfxLights N | lights — this one can only LOWER: the vendored lighting shader's array is four, so the ceiling is the shader's and the pragma restricts rather than permits | 4 |
This table is the WHOLE set, and a pin holds it that way. The caps are one row each in CX_LIMIT_ROWS (runtime/cx_limits.h) -- and since BUGS-139 item 2 that one list is the single source of FOUR readers, not one: g_limit_rows[], the long g_max_* definitions and their externs (runtime/cx_limits.c and the header), the native emitter's key list (compiler/emit_c.c) and the register VM loader's (runtime/cx_risc_asm.c). Before the fold each of those was a hand-kept copy, so a cap added to some of them and not others worked on one backend and did nothing on the other, in silence -- and cx_limits.c's own comment said adding a cap was "one row ... nothing else to wire here", which was four acts short. tests/bugs/Z_seed_literals.ps1 check (6) is what holds this, and the pin this paragraph used to name -- Z_pragma_caps_documented.ps1 -- is ARCHIVED, so the sentence was true of nothing. (6) asks three things of the DEFINITION rather than of a reader: that CX_LIMIT_ROWS exists at all, that no reader keeps a literal copy of a key, and that every key in it is named in the table above. It sits beside check (5), which keeps a growable's SEED from leaving cx_limits.h under a private name -- the identical escape one indirection along.
TWO CAPS ARE OUTSIDE THAT LIST AND THIS TABLE, and (6) names them rather than pattern-dodging them: #pragma MaxOwnedStrings and #pragma MaxFormat do not travel through cx_limit_set at all -- each calls its own setter (cx_vm_set_max_owned, cx_set_sprintf_fmt_max) -- so neither has a row, a variable or a default to fold, and each is still hand-kept in BOTH backends. (6) requires both branches to stay present in both, and refuses a THIRD cap arriving that way. Filed under BUGS-139 item 2. Six caps had gone undocumented before it existed (MaxSortNdx, MaxSortNdxTombstones, MaxGameTimers, MaxGameObjects, MaxGameOverrides, MaxBlobs), because adding a row to the array and adding a row to this table were two separate acts of memory. Adding a cap now fails the battery until this table names it.
Compile-time-only caps on compiler-side growable arrays (no source-observable effect, so no pragma) live in compiler/cxc_limits.h: CXC_MAX_FOREIGNS, CXC_MAX_FOREIGN_PARAMS.
Doc created 2026-05-23 alongside the C-port spike + transpiler bring-up.