← board

{$packrecords c} is refused

Found 2026-08-21 while adding the arm arm of [[feature-mimic-fpc-compiler-define-profile]]:

$ pascal26 --target=arm32 --mimic-fpc-compiler p.pas
pascal26:1: error: invalid packrecords value: c

FPC 3.2.2's fpcdefs.inc opens its arm branch with {$packrecords c}, so the arm profile cannot get past the include even though the define derivation itself is correct.

What it means

{$packrecords N} sets record field alignment to N bytes. c is not a number: it means "use the alignment rules this platform's C compiler uses" — which is not always the same as any single N, because C's rule is per-field natural alignment capped by the ABI. It is the directive every FPC binding to a C library uses, so this is not arm-specific or FPC-compiler-specific surface.

Scope, and the honest version of it

pxx's record layout for extern "C" interop already has to answer this question — lib/crtl and the C frontend share the IR's record layout — so the rule likely exists and the gap is the directive spelling. Confirm that before scoping: if pxx's default record layout already IS C-compatible on the hosted targets, {$packrecords c} is close to a no-op there and the fix is accepting the value rather than implementing a mode. If it is not, accepting the directive silently would be a silent wrong layout, which is worse than the current refusal — in that case it stays refused until the mode exists.

That fork is why this is filed rather than fixed: the cheap version and the correct version are the same edit only if the premise holds.

Gate

{$packrecords c} accepted with the layout it promises (verified against a gcc oracle on a struct with mixed field widths, which tools/gcc_diff_probe.sh already does), and the arm arm of --mimic-fpc-compiler reaching past {$i fpcdefs.inc}.

Fixed 2026-09-04 (frankS, Track P)

The premise held, and it was measured rather than assumed — which is what this ticket said to check before scoping, since the cheap fix and the correct fix are the same edit only if it does.

PackRecordsVal is read in exactly one place (pasparser_decl.inc: if fAlign > TokPackRecords[TokPos-1] then fAlign := TokPackRecords[TokPos-1]), as a CEILING on a field alignment that is already TypeFieldAlign — and TypeFieldAlign IS the platform C rule, i386's 4-byte cap on an 8-byte scalar included, itself measured against gcc when bug-a-an-8-byte-scalar-is-over-aligned-inside-a-struct-on-i386 was fixed. TypeAlign never answers above 8.

So c is a NO-CAP, not a mode: it means "do not cap, the rule underneath is already C's". Implemented as PackRecordsVal := 16, not as a synonym for the default 8 — identical today, and still correct the day a 16-aligned type appears, where the synonym would silently be wrong.

Verified against gcc on both targets, offsets and sizeof, not just sizeof (test-packrecords-c-gcc-oracle, struct { char a; double b; short c; int d; char e; }):

             gcc            pxx {$packrecords c}
  x86-64     0 8 16 20 24 32   0 8 16 20 24 32
  i386       0 4 12 16 20 24   0 4 12 16 20 24

The two target rows must differ from each other as well as match their oracle, so a run that measured the wrong target mismatches instead of passing quietly.

The {$packrecords 1} row in the same file is the positive control and it is the whole test. pxx's DEFAULT layout already agrees with gcc, so line 1 would match even if {$packrecords c} were accepted and discarded — which is exactly how the rest of this directive family used to fail. Only a row whose answer must differ (0 1 9 11 15 16) separates "the directive worked" from "the directive was thrown away". Negative-controlled: pointing both rows at c makes the gate go RED with that reason, not green.

Second gate condition met. pascal26 --target=arm32 --mimic-fpc-compiler now gets past {$i fpcdefs.inc} (FPC 3.2.2's, line 48 is the {$packrecords c} that blocked it). The include now reports two further gaps that were silent before — {$H-} and {$PACKENUM 1}, both in fpcdefs.inc's first nine lines — filed as feature-p-packenum-and-h-minus-for-the-fpc-compiler-corpus. Those are the arm profile's NEXT wall, not this one.

Log