Command line

The installed wrapper is normally called as:

pxx [options] source [output]

From a checkout, use:

./pxx [options] source [output]

The wrapper created by install.sh calls the pinned compiler and adds bundled library roots. The underlying compiler executable is still named compiler/pascal26.

Source and output

The frontend is selected by the source file's extension: .pas/.pp for Pascal, .c for C, .npy or .py for Nil Python, and .asm for the assembly-source frontend.

With an output path, PXX writes the executable there and emits a matching map file. Without an output path, it derives one from the source name and refuses to overwrite the source.

An output path ending in .o also selects object-output mode, the same as passing --emit-obj. An output path ending in .so selects shared-library mode, the same as passing --shared.

Information flags

These answer a question about the toolchain itself and need no source file. Each exits 0.

Flag Answers
--help, -h usage, plus the options worth remembering
--version generation, the frontends built in, the host
--where, --config every path the compiler resolves, and which tier it came from
--list-targets the --target= values, and which of them run on this host
--list-libraries the units this binary can actually find, grouped by directory
--doctor what this box can do — cross-run, ESP, gdb, FPC seed, gcc

--version names the compiler generation — the same number a {$IF PXX_VERSION >= n} directive tests:

pxx (pascal26) — self-hosting Pascal-dialect compiler
  generation:  26   (the value {$IF PXX_VERSION >= n} tests)
  frontends:   pascal c nilpy rust zig ada basic fortran algol erlang lolcode whitespace
  host arch:   x86-64 linux

That frontend list is every frontend compiled into the binary, and they sit at very different stages. The ones these docs cover in depth are Pascal, C and Nil Python; the rest are a real BASIC frontend, two experimental ones, and six skeleton probes, and their presence in the list is not a support claim. See Other frontends for which is which.

--where — the one to reach for first

When a build cannot find a unit or a header, --where is the answer, because it prints the paths from the code that resolves them rather than from a rule written down separately. It marks each root that does not exist [MISSING], and ends with the tier order in force:

Library roots (as ParseUsesUnit resolves them):
  /opt/pxx/lib/rtl/   [RTL]
  /opt/pxx/lib/pcl/   [PCL]
  ...

Pascal unit search roots, in order (-Fu goes in FRONT of these):
  /opt/pxx/../lib/rtl/platform/posix/   [MISSING]
  /opt/pxx/lib/rtl/platform/posix/

Tier order: -Fu/-I  >  PXX_HOME/PXX_LIBPATH  >  pxx.cfg  >  exe-dir defaults.

--config is an exact alias — same output, same exit — for when the question is phrased as "which pxx.cfg is in effect?" rather than "where is the RTL?". (Deliberately not called "byte-identical": in these docs that phrase is reserved for two specific technical claims, and reusing it for two flags that print the same thing would blur a distinction worth keeping. The two claims are not the same claim — in the self-host fixedpoint it is the binary that is identical, to our own previous output; in the gcc-oracle corpora it is a compiled program's output that is identical, to the output of the same program built with gcc. PXX does not emit gcc's machine code. See Status.)

One subtlety worth knowing: -Fu and -I given before --where on the command line appear in its output, and ones given after it do not — --where answers where it is read.

--doctor — capability, not correctness

--doctor reports what this box can do with this binary: native compilation, whether the RTL, builtin units and C headers were found, which cross targets can actually be run here through qemu, whether an ESP-IDF and Espressif toolchain are present, and whether an FPC seed, gdb and gcc are around for compiler development. Every no row names what to install.

Nothing in it is fatal. Compiling and running a native program needs none of the optional rows; each no costs exactly the one capability its row names.

Options

Option Effect
--target=ARCH Select x86_64, i386, aarch64, arm32, riscv32, or xtensa.
--xtensa-abi=call0|windowed Select the Xtensa call ABI.
--xtensa-cpu=lx6 Use the older ESP32 LX6 software divide/mod profile.
--xtensa-fpu Use Xtensa hardware single-precision float operations where supported.
--esp-profile=bare Select the bare-metal ESP platform profile for riscv32 or xtensa.
--emit-obj Emit a relocatable object (.o) instead of a linked executable. x86-64, i386, riscv32 and xtensa write a general object — code, data, bss, relocations, exported symbols — for a compiled source; .asm sources produce objects on x86-64 only. The export surface is the C-convention routines, so in practice the source must be Pascal or C — no other frontend has a spelling that marks a routine cdecl, and an object that would define no linkable symbol is refused rather than written. aarch64 and arm32 have no object writer. On x86-64 and i386 the export surface is the C-convention routines; an x86-64 object is position-independent and links with no extra flags, an i386 one needs -no-pie — see Linking a pxx object into another program. Same as an output path ending in .o.
--shared Emit an ET_DYN shared library (.so) instead of an executable. x86-64 only. Works for compiled sources (Pascal or C — the same C-convention export surface as --emit-obj, and no other frontend can spell it) as well as the .asm frontend; the export surface is the C-convention routines, as for --emit-obj, and the library both links with ld and loads with dlopen — see Linking a pxx object into another program. Same as an output path ending in .so.
-S Also write <output>.s, a best-effort x86-64 disassembly text dump of the emitted code. Additive — the normal output (executable, --emit-obj, or --shared) still happens. x86-64 only.
-g Emit DWARF debug information.
--debug Print compiler tracing diagnostics.
--dump-ir Print lowered IR while still emitting output.
--dump-rtti Print generated RTTI tables while still emitting output.
-dNAME Define a conditional compilation symbol.
-uNAME Undefine a conditional compilation symbol, except PXX.
-FuDIR Add a search root: a Pascal unit directory, and the directory a Nil Python import searches for a third-party package (point it at the package's parent). See Nil Python.
-IDIR Add a C include directory and a Pascal unit search root.
-Mobjfpc Accept the Object Pascal compatibility mode marker.
--threadsafe Use atomic refcounts for managed strings and arrays. On x86-64, i386, aarch64, and arm32 only.
--no-auto-var Disable auto-typed variable declarations.
--no-lazy-var Disable inline/lazy variable declarations.
--system-libs Disable the Magic Link auto-pull mechanism and link C dependencies dynamically.
--system-libs=stems Granular opt-out: dynamically link listed comma-separated C libraries (e.g. m,pthread), keeping the rest magic-linked.
-nostdinc / --nostdinc Disable adding default C header search directories.

Strictness and dialect

PXX is lax by default and turns FPC-parity checks on individually. See the compiler modes page for the whole lax → --strict → granular → --mimic-fpc model; each flag below is documented there in context. The directive column names the in-source directive with the same effect where one exists.

Option Effect Directive
--strict FPC-parity strictness umbrella (currently the routine-visibility check below). {$STRICT ON}
--strict-fpc The FPC-parity umbrella: --strict-case, --strict-operator, --strict-visibility and --require-forward together — the checks that match FPC and are proven against the real FPC corpora. Also switches two rules that change a value rather than a diagnostic: FPC shift widths, and VariantChar. --strict-overload is deliberately not included; see modes. {$STRICT_FPC ON}
--require-forward A routine must be defined above its call, forward;-declared, in an interface section, or be a class method — no whole-source pre-scan. First check under --strict. {$STRICT ON}
--strict-overload Require explicit overload; on overloaded routines. {$STRICT_OVERLOAD ON}
--permissive-overload Relax the overload marker requirement (the default). {$STRICT_OVERLOAD OFF}
--strict-overload-width Among integer overloads, pick the narrowest that fits, as FPC does, instead of the default dialect's widening. Changes which body a call binds to, so it is deliberately not in --strict-fpc; see modes, which carries the value table. — command line only
--strict-operator FPC-parity rejection of = / <> on class operands (lax default allows them). {$STRICT_OPERATOR ON}
--strict-case FPC-parity case-label diagnostics: inverted ranges, duplicate/overlapping labels. {$STRICT_CASE ON}
--strict-visibility Enforce private / protected / strict member access (lax default parses the markers but grants access anywhere). {$STRICT_VISIBILITY ON}
--lax-decl-order Opt out of declare-before-use gating for forward-visible globals (strict/FPC-parity is the default). {$DECLORDER OFF}
--auto-locals Assignment to an undeclared name declares a routine-local inferred-type var instead of erroring. Off by default (masks typos). {$IMPLICITVARS ON}
--mimic-fpc FPC-compatibility preset: the curated FPC define set plus --require-forward, {$I+}, and --strict-visibility. See FPC compatibility. {$MIMIC FPC}

Runtime and codegen

Option Effect
-O0-O3 Optimization level. -O2 is the proven default; -O3 carries newer, still-promoting passes. -g implies -O0 unless an -O level is given explicitly.
--no-default-rtl Do not pull the default standard-unit surface (textfile + builtin). Used by the compiler self-build.
--no-div-check Opt out of the integer div/mod pre-divide zero check (default on: divide by zero raises a clean runtime error rather than a raw SIGFPE).
--no-nil-check Opt out of all emitted nil checks, including those a source turned on with {$NILCHECKS ON}. Call sites are checked by default (a method on a nil instance, or a call through a nil procvar, method pointer or interface); bare p^ derefs are not. A checked site raises a catchable EAccessViolation with SysUtils, else Runtime error 216. See directives.
--no-signals Opt out of the default signal runtime (graceful SIGINT/SIGTERM dispatch + SetSignalHandler). PC targets only.
--fpc-float-errors Emulate FPC's float error behaviour: unmask invalid / zero-divide / overflow at entry (the set FPC itself unmasks) and report a trap as FPC's runtime error — 208 float division by zero, 205 overflow, 207 invalid. Off by default; see below. x86-64 only, and it needs the signal runtime (so not with --no-signals).
--no-unhandled-handler Do not install the default unhandled-exception handler.
--no-strict-ir Opt out of the self-host IR guard (the hard error on any unlowered IR node). For an in-development frontend only.
--strict-ir Accepted no-op: the IR guard is the default now. Kept so existing invocations keep working.
--map / --no-map Force the map file next to the output on or off. A map is written by default when an output path is given.
--no-shims Refuse every mimic_<module> substitution — an import must resolve to a real unit of that name or fail. Turns "compiled without compatibility shims" from a claim into a checked property. Nil Python only; see shims.
--max-stack-frame=N Set the oversized-stack-frame warning threshold in bytes (=0 disables it).
--werror / -Werror Promote any warning to a fatal error.
--xtensa-soft-divide / --xtensa-cpu=lx6 Route div/mod through software helpers (ESP32 classic LX6, no hardware divide).

--experimental-ir-codegen is accepted as a deprecated no-op (IR is the only backend).

Float errors are quiet by default

The default is the part a Pascal reader will not expect, and it is a decision rather than an omission: PXX leaves the float exceptions masked, so 1.0/0.0 evaluates to +Inf, 0.0/0.0 to NaN, and the program keeps running. FPC unmasks at startup and aborts; PXX does not. The reasoning is that measurement and streaming data with out-of-bounds inputs is better served by Inf/NaN propagating through a computation than by an abort partway into it.

--fpc-float-errors is how a program asks for FPC's behaviour instead. The same program compiled with and without it:

$ pxx --fpc-float-errors trap.pas trap && ./trap div
Runtime error 208 (division by zero)          # exit status 208

$ pxx trap.pas trap && ./trap div
no trap, r= Inf                               # exit status 0

Overflow (1e308 * 10) reports 205 and invalid (0.0/0.0) reports 207 under the flag. Underflow and inexact stay masked, exactly as they do under FPC, so runtime error 206 is decoded but never armed by the flag alone.

The flag is program-wide and set at compile time. To change the mask for one region at runtime, use GetExceptionMask / SetExceptionMask from the math unit, which take and return an FPC-compatible TFPUExceptionMask.

Diagnostics and internal flags

The --warn-* flags are opt-in diagnostics you can run against ordinary source; each is off by default and none changes what is compiled. The dump and measure flags below them serve compiler development and self-inspection — use those only when directed.

Option Effect
--dump-cpp Dump the intermediate C++-ish form.
--proc-map Dump the procedure map.
--measure-inline / --measure-regcall Emit inline / register-call instrumentation.
--warn-missed-fold Warn on constant-fold opportunities the optimizer missed.
--warn-self-result Warn when a parameterless function's bare own name is read as its Result.
--warn-uses-leak Warn whenever a name resolves through a unit not reachable by the non-transitive uses rule. Read-only measurement — resolution itself is unchanged.
--warn-ignored-directives Report a routine directive that is accepted but cannot be honored here, naming the reason (cdecl, register, iram off the ESP targets, stackful, reintroduce, and inline when the routine cannot be inlined). Diagnostic only — codegen is unchanged. See routine directives.

Search paths

The wrapper created by install.sh already passes the bundled lib/ roots. Use -Fu for project-local units:

./pxx -Fusrc -Fulib/more app.pas app

Search roots are checked in flag order before the default library roots. That lets a project override or add units deliberately without changing the checkout.

The full order — flags, then environment, then pxx.cfg, then the defaults guessed from the binary's location — is printed by pxx --where (see Information flags above), which reads it from the resolver itself. Prefer running it over trusting a copy of the rule.

Use -I for C headers. It also feeds the Pascal unit search path, which is useful for generated bindings that sit next to the imported header:

./pxx -Iinclude main.pas main

Environment and pxx.cfg

Three environment variables and one optional config file sit between the command-line flags and the defaults the compiler guesses from its own location. Run pxx --where to see which of them are actually in effect — it prints all of them, set or unset.

Variable Effect
PXX_HOME=<root> Install root. Its lib/ and compiler/builtin/ replace the roots guessed from the binary's own directory.
PXX_LIBPATH=a:b Extra Pascal unit roots, inserted after -Fu and before the defaults.
PXX_CONFIG=<file> Use this config file instead of searching for one.

PXX_HOME is what makes an unpacked tarball work from any directory, and it is honoured all-or-nothing: the exe-dir guesses are not kept underneath it as a fallback. Point it at the wrong root and the RTL is simply not found —

error: uses: unit source not found: platform_backend

— which is --where territory, and every root will be marked [MISSING].

The config file

If PXX_CONFIG is unset, the compiler takes the first of these that exists: ./pxx.cfg, ~/.config/pxx/pxx.cfg, <exe dir>/pxx.cfg. Three directives are understood today:

home      /opt/pxx
unitpath  /opt/units
incpath   /opt/inc

--where echoes the file it chose and the directives it read from it, so a config that is not taking effect is one command away from explaining itself. Per-library define and mode manifests are not implemented.

Examples

./pxx --where
./pxx --doctor
./pxx hello.pas hello
./pxx -g hello.pas hello
./pxx --target=aarch64 hello.pas hello.a64
./pxx -dDEBUG hello.pas hello
./pxx -Fusrc -Iinclude app.pas app
./pxx --target=riscv32 --esp-profile=bare main.pas main.o

Next