← board

Copy cannot compile on the frozen-string path

Repro

$ printf "program t; var s: string[16]; begin s := Copy('abcdef',1,3); end.\n" > /tmp/w.pas
$ ./compiler/pascal26 -uPXX_MANAGED_STRING /tmp/w.pas /tmp/w.bin
pascal26:291: error: undefined variable (PXXIoErrorHook)
  in: ./compiler/../lib/rtl/textfile.pas
  near: LastIOResult := 0 ; if PXXIoErrorHook >>> <> nil then
pascal26:291: error: PXXIoErrorHook is not a procedure or function, so it cannot be called

lib/rtl/textfile.pas:291 is if PXXIoErrorHook <> nil then PXXIoErrorHook();. The only declaration in the tree is compiler/builtin/builtinheap.pas:611 (PXXIoErrorHook: TPXXDivZeroProc, the 4th of the hook family, unguarded), and lib/rtl/sysutils.pas:5867 installs it. The frozen build reaches the reference without reaching the declaration.

Scope, measured — not "Copy under some conditions"

source -d (default) -u (frozen)
WriteLn('hi') ok ok
s := 'ab'; WriteLn(s) ok ok
test/hello.pas ok ok
s := Copy('abcdef',1,3) ok FAIL
WriteLn(Copy('abcdef',1,3)) ok FAIL
ParamStr(0)[1] ok FAIL (a different error — see below)

Any mention of Copy is enough; it does not need a WriteLn, a variable, or a non-literal argument.

PXX_MANAGED_STRING IS ON BY DEFAULT — verified with an {$ifdef} probe: no flag and -d both print MANAGED, -u prints FROZEN. So the broken half is the one you have to ask for, which is why this has not bitten anyone. It is still a real half: Makefile:134 is FROZEN_PXXFLAGS := -uPXX_MANAGED_STRING and the self-compile bench at Makefile:427/433 uses it — on test/hello.pas, the one source above that still works.

The flag mechanism is not the bug. Control: -uSOMETHING_NOBODY_DEFINES compiles the same file fine.

Pre-existing, not a regression. stable_linux_amd64/default/stable_pinned (pin v404) fails identically, same line, same message.

A second failure on the same path, probably a different cause

ParamStr(0)[1] under -u gives:

error: compiler error: call to a runtime stub that was never emitted
(code offset 0 is the ELF entry point). A frontend driver is missing its
stub-emission call for the current flags/target.

Also identical on the pin. Recorded here rather than split off because both are "the frozen path is missing something the managed path gets", and whoever opens one should look at the other before deciding they are two tickets. If they turn out to have different causes, split then.

How this was found

Incidentally, while landing the soft-keyword conversion of the sys*/ParamStr intrinsics (bug-p-nine-intrinsic-spellings-are-hard-keywords-so-they-cannot-be- user-names) — the new test compiled under -d and refused under -u, and the pin control showed neither was mine.

Worth one line for the next reader: -u UNDEFINES. I spent two probe rounds reading -uPXX_MANAGED_STRING as "managed mode on" and had a wrong ticket half drafted about managed strings. FROZEN_PXXFLAGS := -uPXX_MANAGED_STRING at Makefile:134 is the line that says which way round it is, and the {$ifdef} probe is the two-second way to stop guessing.