From 1f183000813733f68c27d0860c82f787ca038695 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 21 May 2026 19:27:20 +0200 Subject: [PATCH] fix(cli): warn when meld fuse uses the multi-memory default (#172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `meld fuse` defaults to `--memory multi`, which produces a core module with one linear memory per input component. The canonical optimise → AOT chain then rejects it: `wasm-opt` needs `--enable-multimemory`, and `synth --cortex-m` has no single flat address space for multiple memories. A user on the happy path (`meld fuse a b -o fused.wasm`) hit that wall silently at the very next tool. This is #172 options 2 + 3 (warn + document): - `meld fuse` now prints a warning whenever `--memory multi` is in effect — including the default — naming the downstream implication and pointing at `--memory shared --address-rebase`, the combination that flows through `wasm-opt` → `synth` cleanly. - The `--memory` `--help` text documents the same. #172 option 1 (flip the default to `shared`) is deliberately NOT done here. Flipping the core memory default is high-blast-radius, and `shared` is not an unambiguously safe default: it is currently labelled "legacy mode" and documented as broken when any component uses `memory.grow`. The default flip is a separate decision and is left to the issue owner; this PR makes the current default non-surprising in the meantime. Tests: `test_cli_memory_default_is_multi` pins the default so a future flip is a deliberate edit; `test_cli_memory_shared_parses`. 7/7 meld-cli tests pass. Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 17 ++++++++++++++++ meld-cli/src/main.rs | 48 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 563453b..8ae12ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- **`meld fuse` warns when the `multi` memory default is used** + (issue #172, `meld-cli`). The default `--memory multi` produces a + multi-memory core module that `wasm-opt` rejects without + `--enable-multimemory` and that has no single-address-space (MCU) + lowering for `synth`. A user on the happy path + (`meld fuse a b -o fused.wasm`) previously hit that wall silently + at the next tool. `meld fuse` now prints a warning naming the + downstream implication and pointing at + `--memory shared --address-rebase`, and the `--memory` help text + documents it. The *default itself* is intentionally left at + `multi` — flipping it is a high-blast-radius change, and `shared` + carries its own caveat (broken under `memory.grow`, currently + labelled "legacy mode"), so the default flip is deferred as a + separate decision (#172 option 1) rather than made here. + ## [0.9.0] — 2026-05-20 ### Fixed diff --git a/meld-cli/src/main.rs b/meld-cli/src/main.rs index b59f55f..813c5cb 100644 --- a/meld-cli/src/main.rs +++ b/meld-cli/src/main.rs @@ -50,7 +50,12 @@ enum Commands { #[arg(short, long, default_value = "fused.wasm")] output: String, - /// Memory strategy: "multi" (default) or "shared" + /// Memory strategy. "multi" (default) keeps one linear memory + /// per input component — the fused module then needs + /// `wasm-opt --enable-multimemory` and has no single-address- + /// space (MCU) lowering. "shared" merges into one memory; pair + /// it with --address-rebase for a module the wasm-opt → synth + /// chain accepts directly. See issue #172. #[arg(long, default_value = "multi")] memory: String, @@ -214,7 +219,21 @@ fn fuse_command( // Parse memory strategy let memory_strategy = match memory.as_str() { - "multi" => MemoryStrategy::MultiMemory, + "multi" => { + // #172: the `multi` default produces a multi-memory module + // that wasm-opt rejects without --enable-multimemory and + // that has no MCU (single-address-space) lowering. Warn so + // a user on the happy path is not surprised at the next + // tool in the chain. + eprintln!( + "warning: --memory multi produced a multi-memory module. \ + wasm-opt needs --enable-multimemory to consume it, and \ + it has no single-address-space (MCU) lowering. For a \ + fused module the wasm-opt → synth chain accepts \ + directly, re-run with `--memory shared --address-rebase`." + ); + MemoryStrategy::MultiMemory + } "shared" => { println!("Using shared memory (legacy mode)"); MemoryStrategy::SharedMemory @@ -601,6 +620,31 @@ mod tests { } } + #[test] + fn test_cli_memory_default_is_multi() { + // #172: the `--memory` default is `multi`. Pin it so a future + // change to the default is a deliberate edit to this test + // (flipping it is a high-blast-radius decision — see #172). + let cli = Cli::try_parse_from(["meld", "fuse", "a.wasm", "-o", "out.wasm"]) + .expect("fuse args parse"); + match cli.command { + Some(Commands::Fuse { memory, .. }) => assert_eq!(memory, "multi"), + _ => panic!("Expected Fuse command"), + } + } + + #[test] + fn test_cli_memory_shared_parses() { + let cli = Cli::try_parse_from([ + "meld", "fuse", "a.wasm", "-o", "out.wasm", "--memory", "shared", + ]) + .expect("fuse args parse"); + match cli.command { + Some(Commands::Fuse { memory, .. }) => assert_eq!(memory, "shared"), + _ => panic!("Expected Fuse command"), + } + } + #[test] fn test_write_import_map() { // Build a minimal core module with two function imports