Skip to content

Canonical-ABI glue survives the dissolve: componentizing a scalar-only driver doubles .text, and cabi_realloc (x2) is never removed #303

Description

@avrabe

Measured while turning gale's thin-seam drivers into real components (gale REQ-DRV-COMPONENT-001). Reporting because it changes whether that migration is affordable at all on a 32 KB part.

The numbers — same driver, same toolchain (loom 1.2.0 + synth 0.52.0, cortex-m3, --all-exports --relocatable)

shape .text
raw #[no_mangle] exports, env.mmio_* imports (today) 502 B
component: import gust:hal/mmio, export gust:hal/gpio, canonical exports only 1032 B
component, dual-exporting raw + canonical (transitional) 1196 B
dual-export after loom optimize --passes inline 1202 B

So componentizing roughly doubles a tiny driver, and loom does not reduce it.

What is in the extra bytes

$ arm-none-eabi-nm gpio.o | grep ' T '
T cabi_realloc
T cabi_realloc_wit_bindgen_0_52_0
T gust:hal/gpio@0.1.0#clear
T gust:hal/gpio@0.1.0#configure
T gust:hal/gpio@0.1.0#read
T gust:hal/gpio@0.1.0#set
T gust:hal/gpio@0.1.0#toggle

Two observations:

  1. cabi_realloc is emitted twice and is unreachable. The whole interface is scalar — every function is func(u32, u32[, u32]) -> [u32]. Nothing is lifted or lowered that could allocate. Neither realloc can ever be called on this component, yet both survive --passes inline.
  2. The canonical wrappers roughly double the function bodies. Discounting the ~104 B of realloc, five canonical wrappers cost ~928 B against ~502 B for the same five functions exported raw.

Why this matters for the dissolve specifically

In a hosted runtime the canonical ABI is load-bearing. In this pipeline it is not: the component is fused and lowered ahead of time, and the native consumer calls the functions directly — nothing ever performs a canonical lift/lower at runtime. The glue is build-time scaffolding that survives into the shipped image.

The interface here is the cheapest possible case (all scalars). If it costs 2x, the cost on richer interfaces is worth knowing before eight drivers each pay it.

The ask

Is there a pass — or could there be — that recognises canonical-ABI export glue as dead once the component has been fused for ahead-of-time lowering, and drops it along with an unreachable cabi_realloc? Even just DCE'ing a provably-uncallable cabi_realloc on scalar-only interfaces would be a clear win.

Happy to provide the fixture: gpio-thin is a single public crate, five scalar functions, and both shapes are one cargo build apart. Same offer as the synth#882 fixtures, which turned out to catch more than was reported.

Repro

git clone https://github.com/pulseengine/gale
cd gale/benches/gust/drivers/gpio-thin      # branch: feat/gpio-thin-component
cargo build --release --target wasm32-unknown-unknown
loom optimize target/wasm32-unknown-unknown/release/gust_gpio_thin.wasm --passes inline -o /tmp/g.wasm
synth compile /tmp/g.wasm --target cortex-m3 --all-exports --relocatable -o /tmp/g.o
arm-none-eabi-size /tmp/g.o

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