Skip to content

Free Pascal codegen notes

YadeWira edited this page Oct 18, 2026 · 1 revision

Free Pascal codegen notes

FPC 3.2.2, x86-64. Each point was measured on sylz's hot loops; check a routine with fpc -O3 -al and count its (%rsp) references.

Register allocation decides the speed

FPC gives each local one location for the whole routine (no live-range splitting), so what matters is the routine's total number of variables, the locals of inlined callees included. When they don't fit, it can spill exactly the pointer on the critical path: in the first decoder ip and op lived on the stack (830 MB/s); keeping them in registers gave 2031 MB/s.

  • Any call in a routine leaves its variables only the 5 callee-saved registers (rbx, r12–r15). One Move in a hot loop is enough to push the rest to the stack. Move the exact, slow path into its own routine.
  • Passing a local by var pins it to the stack, even when the callee is inline (1834 → 2210 MB/s once removed).
  • Records larger than a register always live in memory. That is a tool: put values used only in comparisons (loop limits) in a record, and the registers go to the pointers on the critical path (2210 → 2458 MB/s).
  • try/finally and managed locals (dynamic arrays) add an fpc_setjmp exception frame, and the compressor loop kept nearly every local on the stack with one. Allocate in a wrapper, run the loop on raw pointers.
  • The goto version of the level-1 compressor kept more locals on the stack (127 vs 104 references) and ran at 226 vs 273 MB/s.
  • -O4, -OoUSERBP and -OoREGVAR changed nothing in these loops.

Arithmetic

  • UInt32 * UInt32 is evaluated in 64 bits on x86-64: truncate with UInt32(...) before shifting a multiplicative hash, or the high bits leak into the index.
  • Pascal is case-insensitive: a local mflimit hides a constant MFLIMIT.

Where it ends

After all of the above, the Pascal decoder ran at 72–76% of the C LZ4 decoder on identical streams, and the level-1 compressor at about 55% of lz4's speed for the same table size. The x86-64 assembler closes most of that: +25% decode, +51% compress.

Clone this wiki locally