← board

B an FPC-compatible Signal() unit over SetSignalHandler

What already exists (nothing to build in the compiler)

The work

A unit exporting FPC's shape:

type
  SignalHandler = procedure(sig: Longint);
  TSignalHandler = SignalHandler;
const
  SIG_DFL = 0; SIG_IGN = 1; SIG_ERR = -1;
function Signal(signum: Longint; handler: SignalHandler): SignalHandler;

The implementation is a table plus one trampoline, and needs no new compiler support:

var Handlers: array[1..64] of SignalHandler;
procedure Trampoline;
begin
  if Assigned(Handlers[__pxxSigNum]) then Handlers[__pxxSigNum](__pxxSigNum);
end;

Signal stores into Handlers[signum], calls SetSignalHandler(signum, @Trampoline) once per signal, and returns the previous entry — FPC's contract.

Also worth exporting, since corpus code spells them either way: fpSignal (the BaseUnix name) and the SIGxxx number constants, which the existing signal tests currently spell as bare integers.

Decide while writing it, do not guess

Gate

Track B's: build with $(PXX_STABLE), make lib-test / demos. A test that registers one handler for two different signals and asserts each got its own number is the one that bites — a single-signal test passes even with the trampoline hard-wired.

Resolved 2026-08-28 (Track B, frankB)

lib/rtl/signals.pas — table plus one trampoline, as the ticket specified, with test/lib_signals_fpc.pas in lib-test. All three "decide, do not guess" items were settled by measurement rather than by picking the safe-looking option, and two of the three came out better than the ticket budgeted for.

SIG_IGN — honest, and no Track A ticket needed

The ticket's contingency was: if the runtime cannot express ignore, make Signal(x, SIG_IGN) an explicit error, and if fixing that needs a compiler change, file it as Track A. It does not need one. PalIgnoreSignal is already exported from lib/rtl/platform.pas and installs sa_handler=SIG_IGN through rt_sigaction (platform_backend.pas:392). So ignore is a real ignore, and SIG_DFL separately maps to SetSignalHandler(sig, nil).

Same shape as the rest of tonight: the capability existed and the ticket's premise was a claim about a search, not about the runtime.

cdecl on a procedure type — accepted

Measured before relying on it, per the ticket. procedure(sig: Longint); cdecl; compiles, and a call through a variable of that type reaches the handler with the right argument. It is meaningless here — pxx is libc-free and calls the trampoline with its own ABI — but corpus source spells it, so the declaration matches what real code writes.

Typed-constant casts (SIG_DFL = SignalHandler(0)) and procedure-variable comparison both work too, so FPC's exact spelling of the constants carries over.

x86-64 only — refuses loudly, does not compile empty

{$ifndef CPUX86_64} + {$error ...}. The define is CPUX86_64 (measured; not CPU_X86_64). The important half is what the {$else} does NOT do: compiling to an empty unit would satisfy a uses clause and provide no dispositions at all, which is the "paper over" the ticket forbids. Negative-controlled by building the unit with the guard inverted — the intended message appears and the compile fails.

Found while doing this: {$FATAL} is silently ignored — the frontend dispatches warning/message/error and no-ops everything else, so a guard that means "stop, unsupported configuration" compiles clean and yields a binary that should not exist. Filed as [[bug-p-fatal-directive-is-silently-ignored]] (Track P). {$ERROR} works and is what this unit uses.

The two negative controls

Both target the ticket's own stated risk, that a single-signal test passes against a hard-wired trampoline:

The sentinel is unreachable on failure (SIGNALS FAIL, halt 1) and the Makefile compares the whole output, so the FAIL lines survive as the diagnostic.

Log