Skip to content

NO_FILESYSTEM targets can't link since fdeb32e9: main_dict lost const, putting the 389 KB dictionary in .data #40

Description

@lambdamikel

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:

  1. src/maindict.c:2const unsigned char main_dict[]unsigned char main_dict[]
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions