CX+AI

CX Pragma Reference

Every pragma and what it changes

GENERATED from DOCS/reference/manuals/CX_Pragmas.html by tests/forge.cx — edit the source, not this page.

C-port: #pragma handling TODO

โš  THIS IS A 2026-05 PORTING TODO, NOT A PRAGMA REFERENCE โ€” and it is published as CX_Pragmas.html, which is wrong. It tracks which PB-era pragmas the then-new C emitter still ignored. Several names below (BuildJIT, DumpASM, EvalStack, MaxAi*, RunThreaded, pack, ftoi, โ€ฆ) are not read by any code path in the current compiler โ€” measured 2026-07-23 while building the -P key validator (v3.179.28), which is derived from the actual cxc_preproc_get_pragma call sites. The live list is what cx -P <bogus> prints: it names the key you got wrong and lists every key the compiler reads, exactly spelled (matching is case-sensitive โ€” pragma lookup is strcmp). Trust that over this file until a real pragma reference replaces it.

The Phase 1 transpiler (cx_emit_c-v02.pbi) currently ignores all #pragma directives โ€” emitted C runs with C-default semantics regardless of what the source pragma'd for. Most pragmas are harmless to ignore (they targeted the PB VM's runtime knobs); a few have real observable divergence that this doc tracks.

Pragmas are stored in CXLang::gPragmas() map by the prescan ( cx_prescan-v02.pbi), key = pragma name, value = string value. The transpiler has access to it at emit time -- each fix below is "read gPragmas(<name>), adjust emit accordingly."

For the canonical architecture see ARCHITECTURE_C_PORT.md.


Status legend


Pragmas with observable divergence (RESOLVED 2026-05-24)

๐ŸŸข #pragma decimals N โ€” RESOLVED 2026-05-24

What 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:

  1. Writing the pragma is free. It is not a runtime switch โ€” emit puts a single cx_print_decimals = N; at the top of main and nothing else changes.
  2. NOT writing it is not free, and never was. The rewrite that honours it (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 ftoi "truncate" / "round" โ€” RESOLVED 2026-05-24

What it does in CX: controls (int)float cast โ€” round (default) or truncate.

Native behavior: C's (int)x always truncates.

Resolution shipped: emit_pragma_ftoi_truncate() reads gPragmas("ftoi") (strips surrounding quotes, defaults to round). AST_CAST to int from a float operand emits ((cx_int)bi_round(x)) when rounding, ((cx_int)(x)) when truncating.

Verified: (int)3.7 = 4 (matches CX) instead of 3.


๐ŸŸข #pragma floattolerance N โ€” RESOLVED 2026-05-24

What 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.


Pragmas that are no-ops today but should be honored (Phase 1.x to Phase 2)

๐ŸŸก #pragma EvalStack N (default 256)

CX intent: sizes the VM eval stack. Bigger = deeper expression nesting allowed; smaller = catches runaway recursion earlier.

Native: native code uses the C call stack, no eval stack. Irrelevant for the bulk transpiled code.

2026-06-04 UPDATE โ€” permanently a no-op. VM #1 (user-OCX / CISC) and VM #2 (AI mini-VM) were both retired when CX moved to a single register VM (see DOCS/design/UNIFIED_REGISTER_ISA.md). The register VM uses register slots, not an eval stack, so EvalStack has no applicable target in the active runtime. This pragma is a safe no-op and will remain so unless a future stack-based VM tier is added.


๐ŸŸก #pragma autoquit N

CX intent: auto-exit after N seconds (lets test harnesses bound runaway loops without manual kill).

Native: native console programs just return from main(); no auto-exit timer.

Fix path: emit a cx_autoquit(N) call in main() that spawns a background thread sleeping N sec then calling exit(0). Tiny runtime addition (~30 LOC in cx_runtime.c).


๐ŸŸก #pragma RunThreaded on/off

CX intent: PB-VM threading toggle.

Native: the C-port runtime is always threaded by design (per architecture decision 7 + CLAUDE.md rule 21). RunThreaded on is the only meaningful state. Emit a warning if off is set; treat as no-op.

Fix path: print warning at emit time if gPragmas("RunThreaded") = "off"; no code emit change.


๐ŸŸก #pragma DefaultFPS N

CX intent: raylib game frame rate.

Native: raylib still has SetTargetFPS. The transpiler should emit a SetTargetFPS(N) at program start if any raylib calls are present.

Fix path (Phase 2 when raylib bindings land): detect raylib use in the program, emit SetTargetFPS(gPragmas("DefaultFPS")) after cx_rt_init.


๐ŸŸก #pragma ThreadKillWait N

CX intent: ms to wait for threads to drain during shutdown.

Native: the shutdown protocol (architecture ยง5) already has a configurable timeout (default 5 sec). Map this pragma to set cx_set_shutdown_timeout(N) in cx_rt_init.

Fix path: ~5 LOC in main() emit.


๐ŸŸก #pragma appname "Name"

CX intent: window title, executable manifest name.

Native: for console programs, harmless. For GUI programs (raylib), the title is set by InitWindow(w, h, title) โ€” transpiler should default the title from this pragma when raylib's init is emitted.

Fix path (Phase 2): when emitting InitWindow, use gPragmas("appname") as the title if no explicit string is passed.


๐ŸŸก #pragma version on / #pragma banner on

CX intent: print the CX version banner / app banner on startup.

Native: if banner is on, emit a printf("<appname> v<version>\n") at the top of main(). Trivial.

Fix path: read both pragmas, emit conditional banner before user code in main().


Pragmas correctly handled (or correctly no-op)

๐ŸŸข #pragma console on/off

Native binaries are always console-capable (we always link in stdio). No-op is correct.

๐ŸŸข #pragma DumpASM on

CX dev tool; native has no in-process ASM dump. The existing -a / --asm CLI flag still works for the .ocx listing. No-op for native.

๐ŸŸข #pragma pack(...)

Already documented as ignored by today's prescan (with warning). Native struct layout is determined by gcc/clang; pack pragmas would need our own struct-packing pass. Phase 3+ if anyone asks.

๐ŸŸข #pragma include, #pragma define, #pragma undef

Handled by prescan before AST construction; transpiler never sees them.


#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 onwhat it catchescode
a field access through a null struct pointerp->x where p is null โ€” instead of dereferencing itCX-E5024 native, CX-E5021/CX-E5022 on the VM, all three carrying null pointer dereference
a grown-array index out of rangethe lenient 0 read / dropped write becomes fatalCX-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 writeCX-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.


Pragmas changing meaning in Phase 2

#pragma BuildJIT yes/no/auto โ†’ #pragma EmitMode native/ocx/auto

The existing BuildJIT pragma controlled today's JIT auto-marking. In the C-port architecture, the analogous pragma controls default emit mode for functions in the file:

Per-function _ocx / _native modifiers override the file-level setting.

Maintains backward compat: existing #pragma BuildJIT in a file can be auto-translated to the equivalent EmitMode value during the transition.


Implementation priority โ€” DONE for ๐Ÿ”ด (2026-05-24)

All three observable-divergence pragmas were resolved in a single turn (~80 LOC across helpers + binop dispatch). ๐ŸŸก pragmas remain for when their cxtests demand them.


Capacity caps โ€” #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.

PragmaCapsDefault
#pragma MaxAiOps Nops in one AI function's bytecode (cxasm.c / cx_ai_native.c)1048576
#pragma MaxAiStrings Nstring-pool entries per AI function65536
#pragma MaxAiFuncs Nregistered AI functions65536
#pragma MaxAiLabels Nlabels in one AI bytecode parse65536
#pragma MaxNamedSlots N#pragma named slot table + native named-var (gVT) table1048576
#pragma MaxAiCache NAI response-cache entries65536
#pragma MaxCodeScratch Npeek/poke bytecode scratch PCs + codeswap markers + pending asm16777216
#pragma MaxOwned NCISC-VM owned container instances4096

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.