Skip to content

perl: run on the default/unset locale without a startup panic (kd-dvph)#822

Open
brandonpayton wants to merge 3 commits into
gascity/kd-1mr/kd-gtxa-errno-constantsfrom
gascity/kd-1mr/kd-dvph-perl-default-locale-startup-panic-composite-lc-all-unpar
Open

perl: run on the default/unset locale without a startup panic (kd-dvph)#822
brandonpayton wants to merge 3 commits into
gascity/kd-1mr/kd-gtxa-errno-constantsfrom
gascity/kd-1mr/kd-dvph-perl-default-locale-startup-panic-composite-lc-all-unpar

Conversation

@brandonpayton

Copy link
Copy Markdown
Member

Purpose

Perl on Kandelo aborted at interpreter startup for any locale except literally LC_ALL=C, so perl -e 'print 2+3' panicked (exit 29) with the default/unset locale or any UTF-8 locale. This makes Perl usable on Kandelo with a normal environment. Fixes kd-dvph (blocks the homebrew-all umbrella kd-1mr).

Root cause

Kandelo's libc is musl, whose setlocale(LC_ALL, "") returns a positional, ;-separated composite of the per-category locales in category order (CTYPE;NUMERIC;TIME;COLLATE;MONETARY;MESSAGES) when categories differ — e.g. with no locale env it returns C.UTF-8;C;C;C;C;C. This is POSIX-legal (the LC_ALL return string is unspecified and need only round-trip through setlocale).

perl-cross cross-configured the target with glibc's assumption (d_perl_lc_all_uses_name_value_pairs=define), so perl compiled out its positional parser and parsed musl's first field C.UTF-8 as a glibc name=value pair. No =locale.c panic "needs an '=' to split name=value" → exit 29.

This is a perl↔musl compatibility-boundary issue in the port config, not a platform gap: musl's format is POSIX-conformant, and forcing musl to emit glibc's format would change libc-wide behavior for every setlocale caller.

Fix

build-perl.sh patches the target config.h (the primary perl.wasm config; the build-time miniperl keeps xconfig.h) to perl's positional LC_ALL mode — the mechanism perl already ships for *BSD:

  • #undef PERL_LC_ALL_USES_NAME_VALUE_PAIRS
  • #define PERL_LC_ALL_SEPARATOR ";"
  • #define PERL_LC_ALL_CATEGORY_POSITIONS_INIT { LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES } (musl's emit order)

get_category_index() maps each position to perl's internal index; a compile-time STATIC_ASSERT guards the count. build.toml revision 1 → 2 (output bytes change).

Verification (both kernel hosts)

Node (runCentralizedProgram + NodePlatformIO) — 8 pass / 0 fail / 1 skip; browser (headless Chromium via BrowserKernel) — 2 pass / 0 fail. perl -e now runs with an unset locale, LC_ALL=C, LC_ALL=C.UTF-8, LANG=C.UTF-8, LANG=en_US.UTF-8 (the browser default), and a disparate composite — all exit-29 panics before. Built-in ${^UTF8LOCALE} confirms per-category parsing routes CTYPE correctly (not just non-panicking).

New regression smokes: packages/registry/perl/demo/locale-smoke.ts (Node) and locale-browser-smoke.ts (browser). Docs: a porting-guide troubleshooting note on the musl-positional vs glibc-name=value LC_ALL format.

Package-recipe-only change (build script + demo + docs); no kernel/host/musl/ABI code, so the 5-suite full gate is not triggered.

Coordination

Overlaps build-perl.sh with the active perl-runtime work in #821 (kd-k7zy); this patch is orthogonal (startup locale scan vs module-loading) and small. Merge sequencing to be coordinated.

🤖 Generated with Claude Code

The Perl bottle shipped only perl.wasm with no core-module runtime, and the
interpreter crashed on any real module load, so the port only ever passed a
trivial arithmetic smoke. Make Perl-on-Kandelo usable: File::Spec, POSIX, Cwd,
XSLoader, Config and the pure-perl core all load and run. Fixes kd-k7zy (the
kd-p3hr follow-up where File::Spec failed on missing generated XSLoader.pm).

Three root causes, all in the build recipe:
- Generated runtime files were never produced/packaged: `make perl` stops
  before perl-cross's nonxs/extensions targets that generate lib/XSLoader.pm and
  stage the core tree, and the target Config had an empty osname so MakeMaker's
  probes failed. Fix: -Dosname=linux + build with `make -k`, then package
  lib/perl5/5.40.3 as a new perl-runtime.zip output.
- The interpreter miscompiled and panicked on any weak-ref use: -O2 without
  -fno-strict-aliasing miscompiles perl's weak-ref code into a magic_killbackrefs
  panic. The flag was on HOSTCFLAGS only; add it to the target -Dccflags.
- XS core modules could not load: usedl=define builds .so's loaded via dlopen,
  which Kandelo wasm lacks. -Uusedl links XS into perl.wasm with a boot table so
  XSLoader::load resolves them without dlopen. perl-cross ignores -Dnoextensions,
  so the static set is curated by editing Makefile.config's fullpath_static_ext
  (drop ext/re, which duplicates core regcomp symbols, and external-lib/threads
  exts). Also work around a perl-cross bug where the static-ext recipe never
  runs pm_to_blib: after make -k, stage each curated static ext's .pm.

build.toml revision 1 -> 2 (output bytes change). package.toml declares the
perl-runtime output. Formula/perl.rb installs perl.wasm + the runtime and tests
File::Spec/POSIX/XSLoader. demo/runtime-smoke.ts is a Node kernel-host smoke.

Verified via Node runCentralizedProgram against the packaged perl-runtime.zip
(LC_ALL=C): File::Spec catfile/rel2abs, Cwd, POSIX, Fcntl, List::Util,
Data::Dumper, XSLoader, Config all load (PERL_RUNTIME_SMOKE_PASS). Known
boundaries filed: kd-dvph (default-locale startup panic), kd-gtxa (Errno errno
extraction), kd-14n8 (broaden static XS / dlopen). Browser bottle tracks kd-yuef.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@brandonpayton brandonpayton force-pushed the gascity/kd-1mr/kd-dvph-perl-default-locale-startup-panic-composite-lc-all-unpar branch from f0bf4f8 to 92022d4 Compare July 1, 2026 12:44
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Phase B-1 matrix build status — pr-822-staging

ABI v16. 67 built, 0 failed, 67 total.

Package Arch Status Sha
libcurl wasm32 built b01be826
libcxx wasm32 built bd2dd5a6
libcxx wasm64 built 926cfc96
libpng wasm32 built 25e56ef0
libxml2 wasm32 built eb77d16a
libxml2 wasm64 built 9743c3c9
openssl wasm32 built 296793ae
openssl wasm64 built b53a16d9
sqlite wasm32 built 93b98c80
sqlite wasm64 built 7079a1ac
zlib wasm32 built 4543740c
zlib wasm64 built 4ccf5221
bc wasm32 built 0c54e287
bzip2 wasm32 built 614536d6
coreutils wasm32 built e1a33298
curl wasm32 built ecbc3967
dash wasm32 built 4f2caf8b
diffutils wasm32 built 34f174f8
dinit wasm32 built 97a7849b
fbdoom wasm32 built 3799bb5e
file wasm32 built 535dd689
findutils wasm32 built 4b727dbf
gawk wasm32 built 92e5184d
git wasm32 built 42b4d1cb
grep wasm32 built fd4d79fa
gzip wasm32 built 8f54d8ac
kandelo-sdk wasm32 built ba15af22
kernel wasm32 built 917959d2
less wasm32 built 7f3184f8
lsof wasm32 built 4dc5ae5b
m4 wasm32 built 5fbbe8b3
make wasm32 built 57a6d854
mariadb wasm32 built 9ed4aa94
mariadb wasm64 built eaf29411
modeset wasm32 built d9f12284
msmtpd wasm32 built 23172a3a
nano wasm32 built 763f56f8
ncurses wasm32 built a7862ee0
netcat wasm32 built 57bdd0cd
nginx wasm32 built 933bdcd3
php wasm32 built e047ea3c
posix-utils-lite wasm32 built 2aec933c
sed wasm32 built 9d958a03
spidermonkey wasm32 built ef0ac7d5
tar wasm32 built 050e7cb3
tcl wasm32 built ee9ae67a
unzip wasm32 built b176c19f
userspace wasm32 built a9e013a2
vim wasm32 built 8194ff88
wget wasm32 built 378dca44
xz wasm32 built 763b854c
zip wasm32 built 1c834314
zstd wasm32 built bcd693e5
bash wasm32 built d435c352
mariadb-test wasm32 built 74e71062
mariadb-vfs wasm32 built 85427981
mariadb-vfs wasm64 built 6c3ac528
nethack wasm32 built 5b7fc658
node wasm32 built cdad27ac
spidermonkey-node wasm32 built 6d8dc738
vim-browser-bundle wasm32 built ef862c6f
nethack-browser-bundle wasm32 built d4992e6b
rootfs wasm32 built ae3df667
shell wasm32 built 61c3bb81
lamp wasm32 built f5445d80
node-vfs wasm32 built 75f6e69a
wordpress wasm32 built 1c1bcff5

Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.

ext/Errno/Errno_pm.PL discovers the E* errno constants by preprocessing
`#include <errno.h>` and scanning cpp output for `# <line> "file"` linemarkers.
perl-cross defines cpp/cpprun/cppstdin as "$cc -E -P"; -P suppresses those
linemarkers, so on the wasm cross target the scan found no headers, collected
no constants, and Errno_pm.PL died "No error definitions found" -- Errno.pm was
never generated/staged and `use Errno` failed. The constants are plain
`#define E* <int>` in the sysroot (musl arch/generic bits/errno.h).

build-perl.sh now patches get_files() to fall back to the sysroot errno headers
when linemarker discovery yields nothing, adds Errno.pm to the fail-loud
runtime post-check, and bumps the package revision (perl-runtime.zip now ships
Errno.pm; perl.wasm is byte-unchanged). Adds Node + browser use-Errno smokes.

Stacked on #821 (kd-k7zy). Verified: Node errno-smoke 16/16 (Errno.pm with 134
musl-valued constants + %! tie); browser smoke harness committed (skips where
the browser bundle is absent; browser acceptance tracked by kd-yuef).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
musl's setlocale(LC_ALL,"") returns a positional ';'-separated composite
(e.g. "C.UTF-8;C;C;C;C;C") when the categories differ, but perl-cross
configured the wasm target with glibc's name=value assumption
(PERL_LC_ALL_USES_NAME_VALUE_PAIRS). perl parsed musl's first field
"C.UTF-8" as name=value, found no '=', and aborted at interpreter startup
(exit 29) for every locale except literally LC_ALL=C -- making perl unusable
with the default or any UTF-8 locale on Kandelo.

build-perl.sh now patches the target config.h to perl's positional LC_ALL
mode with musl's category order (CTYPE;NUMERIC;TIME;COLLATE;MONETARY;MESSAGES),
the mechanism perl already ships for *BSD; get_category_index() maps each
position to its internal index and a STATIC_ASSERT guards the count. Host
miniperl (xconfig.h) is left matching the build machine's libc. build.toml
revision 1->2 (output bytes change). Adds a Node + browser locale smoke and a
porting-guide troubleshooting note.

Verified on both kernel hosts: perl -e runs with an unset locale, LC_ALL=C,
LC_ALL=C.UTF-8, LANG=C.UTF-8, LANG=en_US.UTF-8, and a disparate composite
(all exit-29 panics before); built-in ${^UTF8LOCALE} confirms per-category
parsing routes CTYPE correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@brandonpayton brandonpayton changed the base branch from main to gascity/kd-1mr/kd-gtxa-errno-constants July 2, 2026 02:42
@brandonpayton brandonpayton force-pushed the gascity/kd-1mr/kd-dvph-perl-default-locale-startup-panic-composite-lc-all-unpar branch from 92022d4 to 392e9c0 Compare July 2, 2026 02:42
@brandonpayton

Copy link
Copy Markdown
Member Author

Re-stacked as part of the perl convoy. Base is now gascity/kd-1mr/kd-gtxa-errno-constants (#827, Errno), which is itself stacked on #821 (kd-k7zy runtime). Linear stack: #821#827#822.

This PR's own change is the 5 locale files only: build-perl.sh (target config.h musl positional LC_ALL patch), build.toml (revision → 4), docs/porting-guide.md (locale gotcha), and the two locale smoke demos. Per GitHub's compare API the range #827...#822 is 1 commit / 5 files (ahead 1, behind 0).

Note: the 'Files changed' tab may briefly show the whole stack (~10 files) — that's a base-change cache lag (the head was pushed just before the base was retargeted); it recomputes on its own and will retarget to main when #827 merges. Merge order: #821, then #827, then this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant