Summary
Since commit fdeb32e ("big endian support", 2026-05-05), platforms/pico cannot link. main_dict lost its const qualifier, which moves the 397,720-byte embedded dictionary out of .rodata (flash) and into .data (SRAM). The RP2040 only has 264 KB of SRAM, so the link fails unconditionally:
arm-none-eabi/bin/ld: DECtalkMini.elf.elf section `.data' will not fit in region `RAM'
arm-none-eabi/bin/ld: address 0x2006a6e8 of DECtalkMini.elf.elf section `.heap' is not within region `RAM'
arm-none-eabi/bin/ld: section .stack1_dummy VMA [20040000,200407ff] overlaps section .data VMA [200000c0,20067707]
arm-none-eabi/bin/ld: region `RAM' overflowed by 173800 bytes
collect2: error: ld returned 1 exit status
This is still present on dectalk-develop HEAD (7f8f8b9). It affects any NO_FILESYSTEM target; I've only tested the Pico, but platforms/gba compiles the same path and has even less RAM, so it is presumably affected too.
Reproduction
export PICO_SDK_PATH=... PICO_EXTRAS_PATH=...
cd platforms/pico
cmake -S . -B build -DPICO_BOARD=pico -DCMAKE_BUILD_TYPE=Release
cmake --build build
Environment: pico-sdk 2.3.0, pico-extras, arm-none-eabi-gcc 13.2.1, PICO_BOARD=pico.
Root cause
fdeb32e made two related changes:
src/maindict.c:2 — const unsigned char main_dict[] → unsigned char main_dict[]
src/loaddict.c — the extern declaration lost const, and an in-place byte-swap loop was added (now init_dictionary() at src/loaddict.c:386):
b = (S32*)&main_dict[8];
entries = get_long_int(main_dict);
for(i = 0; i < entries; i++) {
b[i] = SWAP_32_LITTLE(b[i]);
}
The underlying goal is legitimate: the dictionary index is stored little-endian, so a big-endian host needs it swapped. But doing it in place forces the whole 389 KB blob to be writable.
The unfortunate part is that on little-endian targets this achieves nothing. include/port.h:95-103:
#ifndef __BIG_ENDIAN__
...
#define SWAP_32_LITTLE(x) (x)
So on every little-endian embedded target the loop rewrites each word with its own value — at a cost of 389 KB of RAM that the device does not have.
Worth noting src/epsonapi.c:61 still declares it the original way, so the tree is currently inconsistent about the qualifier:
extern const unsigned char main_dict[];
Suggested fix
Restore const on the definition and both extern declarations, and make the swap conditional so it never runs (and never needs a writable dictionary) on little-endian:
#ifdef __BIG_ENDIAN__
/* index is stored little-endian; needs swapping on a big-endian host */
...
#endif
That restores the embedded targets. It does leave big-endian NO_FILESYSTEM builds needing a different approach, since they can't mutate a flash-resident blob either — byte-swapping on read, or building a small RAM-resident copy of just the index (entries * sizeof(S32) bytes, rather than the entire dictionary), would work for both.
I'm happy to open a PR if that direction seems right.
Why this went unnoticed
.github/workflows/build.yml builds Windows 32-bit, Windows 64-bit, AppImage, and Android. All four load the dictionary from a file, so none of them compile the NO_FILESYSTEM branch in loaddict.c — and 389 KB of .data is harmless on a desktop anyway. There is no CI coverage for the Pico or GBA ports, so a change to shared code broke them silently about three months ago. A link-only CI job for platforms/pico would catch this class of regression cheaply.
Summary
Since commit fdeb32e ("big endian support", 2026-05-05),
platforms/picocannot link.main_dictlost itsconstqualifier, which moves the 397,720-byte embedded dictionary out of.rodata(flash) and into.data(SRAM). The RP2040 only has 264 KB of SRAM, so the link fails unconditionally:This is still present on
dectalk-developHEAD (7f8f8b9). It affects anyNO_FILESYSTEMtarget; I've only tested the Pico, butplatforms/gbacompiles the same path and has even less RAM, so it is presumably affected too.Reproduction
Environment: pico-sdk 2.3.0, pico-extras, arm-none-eabi-gcc 13.2.1,
PICO_BOARD=pico.Root cause
fdeb32e made two related changes:
src/maindict.c:2—const unsigned char main_dict[]→unsigned char main_dict[]src/loaddict.c— theexterndeclaration lostconst, and an in-place byte-swap loop was added (nowinit_dictionary()atsrc/loaddict.c:386):The underlying goal is legitimate: the dictionary index is stored little-endian, so a big-endian host needs it swapped. But doing it in place forces the whole 389 KB blob to be writable.
The unfortunate part is that on little-endian targets this achieves nothing.
include/port.h:95-103:So on every little-endian embedded target the loop rewrites each word with its own value — at a cost of 389 KB of RAM that the device does not have.
Worth noting
src/epsonapi.c:61still declares it the original way, so the tree is currently inconsistent about the qualifier:Suggested fix
Restore
conston the definition and bothexterndeclarations, and make the swap conditional so it never runs (and never needs a writable dictionary) on little-endian:That restores the embedded targets. It does leave big-endian
NO_FILESYSTEMbuilds needing a different approach, since they can't mutate a flash-resident blob either — byte-swapping on read, or building a small RAM-resident copy of just the index (entries * sizeof(S32)bytes, rather than the entire dictionary), would work for both.I'm happy to open a PR if that direction seems right.
Why this went unnoticed
.github/workflows/build.ymlbuilds Windows 32-bit, Windows 64-bit, AppImage, and Android. All four load the dictionary from a file, so none of them compile theNO_FILESYSTEMbranch inloaddict.c— and 389 KB of.datais harmless on a desktop anyway. There is no CI coverage for the Pico or GBA ports, so a change to shared code broke them silently about three months ago. A link-only CI job forplatforms/picowould catch this class of regression cheaply.