Symptom
$ cat > c.bas <<'X'
DIM s = "abc"
DIM t = "def"
PRINT s + t
X
$ pascal26 c.bas c
pascal26:3: 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.
Reproduced on pinned as well as HEAD, so it is not a regression from the
shim-gating fix in
[[bug-a-a-unit-free-basic-program-calls-a-helper-it-never-emits]] — that fix
made a different stub family (PXXStrFromLit) honest, and this one has the
same cause one family over.
The cause, and why it is not simply "emit the shims"
AnsiStrConcatAddr is 0 because EmitAnsiStringRuntime did not run. It cannot
usefully run for a unit-free .bas program: the shims are nine pushes around
EmitCallProc(FindProc('PXXStrConcat')), and PXXStrConcat's body ships in
builtinheap, which BASIC pulls through exactly one door — USES. Emitting
them anyway is what produced the PXXStrFromLit hole, where the call survived
the whole compile as an unresolved placeholder and silently did not happen.
So this needs a decision, not a patch, and it is the same one that ticket deferred:
- Pull
builtinheapwhen a.basprogram does managed-string work. Honest and matches every other driver (ParseUsesUnitAmbient('builtinheap')is what Pascal, C and NilPy do). Costs the unit:10 PRINT "hello"is 559 bytes today andtest_basic_comprehensive.bas, which pulls it, is 103 KB. Gating on "does this source concatenate" keeps the small programs small. - Give BASIC frozen-string concatenation that does not reach the managed
path at all. BASIC has no
A$variables and itsDIM s = "..."is atyString, so the managed path may be entirely gratuitous here.
Option 2 was measured on 2026-08-24 and does not exist: a Pascal program
doing ShortString + ShortString pulls builtinheap too and comes out at 63,760
bytes. There is no frozen-string concat path that skips the unit, in any
frontend. That turns this into a size-vs-capability call rather than a coding
question, and it is now blocked on
[[decide-how-much-string-machinery-the-basic-frontend-gets]], which carries
the three options, the measurements and a recommendation.
Checked 2026-08-24: NOT the same defect as the one-char-string bug
This ticket used to say "see
[[bug-a-basic-prints-a-string-variable-as-its-character-code]] ... settle that
one first; it may turn out this is the same defect wearing a second hat."
It was settled, and it is not. With one-character initialisers now correctly
typed as strings, PRINT s + t still fails identically for "abc" + "def" and
for "a" + "b". Independent bug, unchanged scope.
...and it has a THIRD sibling: string COMPARISON, on cross targets only
Found the same day, by the same fix. IF a = "x" in a unit-free .bas program
used to pass by accident — the variable was a tyInteger holding 120, so it was
an integer compare. Now that it is a real string compare it needs PXXStrEq,
and on aarch64 and arm32 that is
pascal26:22: error: compiler error: PXXStrEq not found
x86-64 and i386 compile it — they have an inline path — which is why this is
recorded here rather than filed separately: it is the same root (a
managed-string helper whose body ships only with builtinheap) reached through a
third operator, and it should be fixed by the same decision. Whichever option
above is chosen must cover +, = and <>, and the cross rows are what prove
it — the native tier alone would call two of the three fixed.
Gate when it is done
The three existing .bas tests unchanged on x86-64 / i386 / aarch64 / arm32,
plus this program compiling AND printing abcdef, plus
test/test_basic_unit_free_string_literal.bas staying small (it asserts the
unit-free path, and pulling builtinheap unconditionally would defeat it without
failing it).