Skip to content

Internal (non-exported) func_N emitted as GLOBAL symbols → multiple-definition collision when co-linking two dissolved .o's #656

Description

@avrabe

Summary

synth compile --relocatable --all-exports emits internal, non-exported functions as GLOBAL ELF symbols named func_<index> (by wasm function index). Two independently-dissolved objects therefore both define func_2, func_3, … as T, and co-linking them fails with multiple definition of 'func_N'. Internal functions have no reason to be global — emitting them STB_LOCAL (or per-module-prefixing) would let independently-dissolved objects co-link directly.

Repro (synth 0.33.1, --target cortex-m3 --all-exports --relocatable)

Two gust thin-seam drivers, each dissolved to its own .o (gpio-thin, spi-thin):

$ arm-none-eabi-nm gpio-thin-cm3.o | grep func_
00000000 T func_2
00000038 T func_3
00000110 T func_4
00000148 T func_5
0000017c T func_6
$ arm-none-eabi-nm spi-thin-cm3.o | grep func_
... T func_2 .. T func_7      # same global names, different bodies

$ arm-none-eabi-ld -r -o merged.o gpio-thin-cm3.o spi-thin-cm3.o
arm-none-eabi-ld: spi-thin-cm3.o: in function `spi_abort':
(.text+0x0): multiple definition of `func_2'; gpio-thin-cm3.o:(.text+0x0): first defined here
... (func_3, func_4, func_5, func_6 likewise)

The func_N are the module-internal helpers (not in the wasm export section, not imports). Only the intended exports (gpio_configure, spi_begin, …) and import-call relocations should be global; the internal helpers should be file-local.

Impact

Blocks composing multiple independently-dissolved components/drivers into one relocatable node by direct linking. Downstream (gust REQ-DRV-BREADTH-001: a GPIO+timer+SPI+UART multi-driver node) must currently meld-fuse the modules into one wasm before dissolve, or accept one .o per node. Any consumer linking ≥2 synth objects hits this.

Suggested fix

Emit non-exported functions with STB_LOCAL binding (they're internal to the object). Exports and import-call targets stay STB_GLOBAL. Alternatively, prefix internal symbols per-module (<module>__func_N). The local-binding option is the minimal correct change and needs no naming scheme.

Kill-criterion

After the fix: arm-none-eabi-nm <obj> | grep 'func_' shows lowercase t (local) for internal helpers, and arm-none-eabi-ld -r a.o b.o of two distinct dissolved objects links with 0 multiple-definition errors.

Filed from the gust driver-breadth work (pulseengine/gale). Happy to share both .os or a minimal 2-export repro crate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions