Compiler directives
Directives are {$...} comments the compiler reads while lexing. This page
lists the directives PXX recognizes. Anything not listed here is accepted as an
inert comment (so FPC sources with unsupported directives still compile), which
means a misspelled directive is silently ignored rather than flagged.
Where a directive has an equivalent command-line flag, it is named in the table. The strictness switches are explained in context on the compiler modes page.
Conditional compilation
| Directive | Effect |
|---|---|
{$DEFINE name} |
Define a conditional symbol (optionally {$DEFINE name := value}). |
{$UNDEF name} |
Undefine a conditional symbol. |
{$CLAIM name} |
Define a conditional symbol for the whole compilation, not just the current unit. Set-once: {$UNDEF} cannot take it back. |
{$IFDEF name} |
Compile the branch if name is defined. |
{$IFNDEF name} |
Compile the branch if name is not defined. |
{$IF expr} |
Compile the branch if expr is true (see below). |
{$IFOPT x} |
Compile-option test. Option switches are not modelled, so the branch is always taken as false. |
{$ELSEIF expr} |
else-branch guarded by expr, only if no prior branch was taken. |
{$ELSE} |
Compile the branch if no prior branch in the chain was taken. |
{$ENDIF} / {$IFEND} |
Close the conditional. |
{$DEFINE} is scoped to the unit that writes it, as in FPC — a unit's define
never reaches the program or any other unit. {$CLAIM} is the deliberate
exception, for the claim-and-skip pattern: two units can supply the same
capability, and whichever is compiled first claims the name so the second
compiles itself out.
{$IFNDEF PXX_HAS_THE_THING}
{$CLAIM PXX_HAS_THE_THING}
{ …this unit provides it… }
{$ENDIF}
A claim is order-dependent by construction, and that is the point — it is
#ifndef GUARD / #define GUARD from C, at a spelling that says so. It is
not retroactive: only sources scanned after the claim see it. pxx reads a
source file whole before parsing it, so every conditional in the main program is
resolved before its uses clause compiles anything — a program that wants the
name must claim it itself, above its own uses.
{$IF} / {$ELSEIF} expressions support defined(NAME), declared(NAME),
integer literals and defined integer symbols, the comparison operators, !
(not), and parentheses. A bare non-zero integer is true ({$IF 1} / {$IF 0}).
Float literals are rejected.
Includes and resources
| Directive | Effect | Flag |
|---|---|---|
{$I file} / {$INCLUDE file} |
Include another source file. Search roots come from -Fi / -I. (A leading +/- selects the IO-check switch below instead.) |
-Fi, -I |
{$R file} / {$R name file} |
Queue an embedded resource. {$R *.lfm} resolves the wildcard to the current unit; a .res file is a no-op. (A leading +/- selects the range-check switch below.) |
Check switches
Short toggles use a trailing + (on) or - (off); the long forms take
ON / OFF.
| Directive | Effect | Default | Flag |
|---|---|---|---|
{$I+} / {$I-} · {$IOCHECKS ON|OFF} |
IO-result checking after file operations. | On | |
{$R+} / {$R-} · {$RANGECHECKS ON|OFF} |
Range checking. | Off | |
{$Q+} / {$Q-} · {$OVERFLOWCHECKS ON|OFF} |
Integer overflow checking. | Off | |
{$NILCHECKS ON|OFF} |
Nil-pointer checking. | tri-state — see below | --no-nil-check |
{$NILCHECKS} is tri-state
The Default column cannot hold one answer, because there is not one. The compiler distinguishes on, off, and the author said nothing — and the two kinds of site resolve that third state in opposite directions:
| Site | Said nothing | {$NILCHECKS ON} |
{$NILCHECKS OFF} |
|---|---|---|---|
| Calls — a method on a nil instance, a call through a nil procvar, method pointer, or interface | checked | checked | unchecked |
Bare derefs — p^, p^.f, p^[i] |
unchecked | checked | unchecked |
So {$NILCHECKS ON} adds the deref checks and {$NILCHECKS OFF} removes
the call checks; each is a no-op for the other class. --no-nil-check is the
master off for both, and it outranks the directive — a source that says
{$NILCHECKS ON} still gets no checks under that flag.
The split follows the cost. A call check is about 2% and replaces a fault that lands frames away from the call, or on a target with no signal runtime does not land at all. A deref check is a test inside whatever loop the deref sits in — measured at +6% on a loop doing nothing else — and on PC targets the MMU already reports it at exactly the right instruction.
What a checked site does when it fires
It calls PXXNilRef, which prints Runtime error 216 (nil reference) and
halts. With SysUtils in the program it instead raises a catchable
EAccessViolation, and that is the point of the feature — a raw memory fault
can never offer it, because try..except does not run for one.
The same program shows both halves. Unchecked, the except never runs:
program uncatchnil;
uses sysutils;
var p: ^Integer;
begin
p := nil;
try
WriteLn(p^);
except
on E: Exception do WriteLn('caught: ', E.ClassName);
end;
WriteLn('still running');
end.
Segmentation fault (core dumped)
Add {$NILCHECKS ON} after the uses clause and the identical program prints:
caught: EAccessViolation
still running
A call on a nil instance is already checked without the directive, and carries
the reason in its message — EAccessViolation: Access violation (nil reference).
Strictness and dialect switches
Each takes ON / OFF and has a matching command-line flag — see
compiler modes.
| Directive | Effect | Default | Flag |
|---|---|---|---|
{$STRICT ON|OFF} |
FPC-parity strictness umbrella (routine visibility). | Off | --strict |
{$STRICT_OVERLOAD ON|OFF} |
Require explicit overload;. |
Off | --strict-overload |
{$STRICT_OPERATOR ON|OFF} |
Reject = / <> on class operands. |
Off | --strict-operator |
{$STRICT_CASE ON|OFF} |
Inverted-range / duplicate case-label diagnostics. |
Off | --strict-case |
{$STRICT_VISIBILITY ON|OFF} |
Enforce member visibility. | Off | --strict-visibility |
{$STRICT_FPC ON|OFF} |
FPC-parity umbrella: case + operator + visibility + require-forward (not overload), plus two semantic rules — FPC shift widths and Variant→Char. |
Off | --strict-fpc |
{$DECLORDER ON|OFF} |
Declare-before-use for forward-visible globals. OFF = lax. |
On | --lax-decl-order (opt-out) |
{$IMPLICITVARS ON|OFF} |
Undeclared assignment declares an inferred-type local. | Off | --auto-locals |
{$MIMIC FPC} |
FPC-compatibility preset: {$STRICT_FPC} + FPC defines + {$I+}. |
— | --mimic-fpc |
Layout, comments, and dialect
| Directive | Effect | Default |
|---|---|---|
{$MODE name} |
Dialect marker. objfpc fpc tp delphi delphiunicode pxx accepted; iso / extendedpascal warn; macpas and unrecognised names are errors. Only delphi changes behavior (@-optional procedural value, and no nested comments); the rest are inert. See modes. |
(no marker) |
{$PACKRECORDS N} / {$ALIGN N} |
Record field alignment: normal/default or 1/2/4/8/16. |
8 |
{$SCOPEDENUMS ON|OFF} |
Enum members live only under the type scope (TEnum.member). |
Off |
{$NESTEDCOMMENTS ON|OFF} |
Allow nested same-type comments. | On |
{$CSTYLECOMMENTS ON|OFF} |
Accept /* … */ comments. |
Off |
{$CASESENSITIVE ON|OFF} |
Case-sensitive identifiers. | Off |
{$LAZYCASING ON|OFF} |
Relaxed identifier casing. | Off |
{$INTERFACES COM|CORBA} |
Interface model: COM (refcounted) or CORBA (unmanaged). | COM |
{$ASMMODE intel|att} |
Inline-asm syntax. Intel only; att is an error. |
intel |
Runtime knobs and messages
| Directive | Effect | Flag |
|---|---|---|
{$THREADSAFE ON|OFF} |
Atomic managed-refcount runtime (x86-64/i386/aarch64/arm32; on i386/aarch64/arm32 use the --threadsafe flag instead). |
--threadsafe |
{$FASTDOUBLES ON|OFF} |
Compute Double via the hardware single FPU where present (lossy; xtensa only, no-op elsewhere). |
|
{$MAXSTACKFRAME n} / {$MAXSTACKFRAME OFF} |
Oversized-stack-frame warning threshold in bytes. | --max-stack-frame= |
{$WARNING text} |
Emit a compile-time warning. | |
{$MESSAGE text} |
Emit a compile-time message. | |
{$ERROR text} |
Emit a compile-time error and stop. |
The per-thread variable area
threadvar storage comes out of a fixed area carved into every thread's block,
3072 bytes by default. It costs that much of BSS and of each thread's stack
whether or not the program declares a single threadvar, so it is settable per
compile — as a define, because a define needs no new option:
| Define | threadvar area |
Block size (__pxxTlsBlockSize) |
|---|---|---|
-dPXX_TLS_USER_0 |
0 | 1152 |
-dPXX_TLS_USER_1K |
1024 | 2176 |
-dPXX_TLS_USER_2K |
2048 | 3200 |
| (default) | 3072 | 4224 |
-dPXX_TLS_USER_4K |
4096 | 5248 |
-dPXX_TLS_USER_8K |
8192 | 9344 |
-dPXX_TLS_USER_16K |
16384 | 17536 |
Measured on an x86-64 hello, BSS at each rung: 35,324 / 36,348 / 37,372 /
38,396 / 39,420 / 43,516 / 51,708 — exactly the area's own difference each time.
-dPXX_TLS_USER_0 is the right setting for a program with no threadvar, which
is most of them.
It is a cap, not a budget. A program whose threadvars do not fit is
refused at compile time, with a diagnostic naming the rung to raise to; it is
never quietly given storage the reserved block does not contain. That is what
makes the setting safe in both directions, and it is why the size must be fixed
on the command line rather than grown as declarations are parsed: the block is
reserved, and __pxxTlsBlockSize folds to a literal, before the first line of
the program is read.
The RTL follows the setting without being rebuilt — lib/rtl/palthread.pas
reads the real block size through __pxxTlsBlockSize rather than restating it.