← board

The System math and thread surfaces are not ambient in units

FPC declares sqrt / ln / exp / sin / cos / arctan / pi and the low-level thread API (BeginThread, TThreadID, …) in the System unit, so portable source calls them with no uses line at all. pxx keeps them in real units — math (618 lines of correctly-rounded numerics) and palthreadobj (a slot registry, a mutex and TThread) — that have no business being duplicated into builtin, and instead pulls the unit in ambiently when a token scan sees one of the names.

That scan lived only in ParseProgram, and it sees only the program's own tokens. So the identical call compiled in a program and did not compile in a unit.

Repro

unit uamb;
interface
function F(x: Double): Double;
implementation
function F(x: Double): Double;
begin
  F := sqrt(x) + ln(x) + exp(0.0) + arctan(0.0) + sin(0.0) + cos(0.0);
end;
end.
pascal26:8: error: undefined variable (sqrt)
pascal26:8: error: undefined variable (ln)
pascal26:8: error: undefined variable (exp)
pascal26:8: error: undefined variable (arctan)
pascal26:8: error: undefined variable (sin)
pascal26:8: error: undefined variable (cos)

FPC compiles it and prints 5.3863.

This is the third instance of one shapetextfile (fixed as bug-textfile-primitives-not-ambient-in-units) and the TObject root methods were the first two, and both were patched into the same unit-level scan loop. Per devdocs/dev/root-cause-over-microfix.md, "two mechanisms is a smell, three is a design flaw": the design flaw is that the ambient-pull rule is written twice, once for programs and once for units, and every new System-unit surface has to be added to both. Noted below.

Fix

compiler/pasparser_proc.inc, ParseUsesUnitBody — the existing unit-level token scan (~4300, the one already carrying unitNeedsText and unitNeedsRootMeth) gains unitNeedsMath and unitNeedsThreads arms, with pull sites beside the existing textfile pull. Triggers are copied exactly from the program-level scan, comments and all:

Self-pull is already guarded. A unit is entered into CompiledUnits at pasparser_proc.inc:3368, before the token scan at ~4300, so math scanning its own sqrt calls re-enters ParseUsesUnitBody('math'), hits the already-compiled guard and exits. No new guard was needed; measured, not assumed.

Outcome — FIXED, 2026-08-27

Adjacent, deliberately NOT built (flagged, per scope discipline)

  1. {$threadsafe on} written inside a UNIT does not work. The directive is processed when the unit is lexed, which is after ParseProgram decided whether to emit the thread runtime stubs, so the pull succeeds and the link then fails with call to a runtime stub that was never emitted. The flag form (--threadsafe) is fine, and is what the test uses. This is a driver/stub-emission ordering bug, not an ambient-pull bug, and wants its own ticket if anyone hits it.
  2. The two scans should be one routine. ParseProgram's scan (pasparser_prog.inc ~640-710) and ParseUsesUnitBody's (~4300) now carry four duplicated name lists between them. Folding them into a single ScanAmbientSurfaces(fromTok, toTok) is the root-cause fix, but the two differ in where they may act (the program mints root-method rows at the end of pass 1, a unit must do it before its bodies parse), so it is a real refactor and not a quick-gate-only change. Filed as a smell here rather than done half-way.

Log