[lld][WebAssembly] Do not coalesce segments with differing flags in -r
Project: llvm/llvm-project | Language: C++ | Concepts: data-structures, error-handling | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/219606
The problem
When linking WebAssembly object files in relocatable mode (-r), the linker (wasm-ld) would combine ("coalesce") data segments if they had the same name, even if their flags differed. This caused bugs: for example, segments containing string literals with and without the STRINGS flag (which marks mergeable string tables) would be merged, causing the linker to treat all data as mergeable strings. This led to corruption, such as splitting strings at embedded nulls or preventing unused data from being discarded when RETAIN flags were incorrectly inherited.
Why it's non-trivial
A naive fix would just avoid merging segments with different flags, but some flags (like RETAIN) are meant to be accumulated, while others (like STRINGS) should keep segments separate. The solution must correctly distinguish which flags should affect coalescing and which should not, and must do so only in relocatable mode, preserving correct semantics for both final and intermediate linking.
The approach
1. Change the segment map key to include flags (except RETAIN) in relocatable mode:
- llvm::SmallDenseMap<StringRef, OutputSegment *> segmentMap;
+ using SegmentKey = std::pair<StringRef, uint32_t>;
+ llvm::SmallDenseMap<SegmentKey, OutputSegment *> segmentMap;
Before:
- The segment map was keyed only by the segment name.
After:
- The key is now a pair of (name, flags), so segments with the same name but different flags are kept separate.
2. Define a function to compute the correct key:
+ auto getSegmentKey = [&](StringRef name, uint32_t flags) {
+ return SegmentKey(name, ctx.arg.relocatable
+ ? (flags & ~WASM_SEG_FLAG_RETAIN)
+ : 0);
+ };
- In relocatable mode, the key includes all flags except
RETAIN(which is safe to accumulate). - In non-relocatable mode, only the name is used (flags are ignored, so segments are merged as before).
3. Use the new key when creating or looking up output segments:
- if (!segmentMap.contains(name))
- segmentMap[name] = createOutputSegment(name);
- s = segmentMap[name];
+ auto key = getSegmentKey(name, segment->flags);
+ if (!segmentMap.contains(key))
+ segmentMap[key] = createOutputSegment(name);
+ s = segmentMap[key];
- Wherever the code previously used the segment name to look up or create an output segment, it now uses the computed key.
4. Add a regression test:
A new test file (lld/test/wasm/relocatable-strings-conflict.s) verifies that segments with the same name but different STRINGS flags are not coalesced in relocatable output, and that the final binary is not corrupted.
From the review thread
No notable review discussion on this PR.
Takeaways
- When coalescing data structures by name, always consider whether additional attributes (like flags) should be part of the key to avoid semantic bugs.
- Some flags are accumulative and safe to merge, while others require strict separation; understand the semantics of each flag before designing your merge logic.
- Always add regression tests for subtle linker or code generation bugs, especially when fixing issues that only appear in multi-stage build pipelines.