WebAssembly / WebAssembly/wabt
Custom section (before …)/(after …) placement is parsed but ignored
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 8.1k
- Forks
- 827
- Avg merge
- 4d 6h
- Merged PRs (30d)
- 18
Description
wat2wasm accepts the (before …) / (after …) placement clause on a (@custom …) annotation and then ignores it, so the section is always written at the end regardless of what was asked for.
Reproducing
(module
(@custom "x" (before type) "\01\02")
(type (;0;) (func))
(func))
$ wat2wasm --enable-annotations demo.wat -o demo.wasm
$ wasm-objdump -h demo.wasm
Sections:
Type start=0x0000000a end=0x0000000e (size=0x00000004) count: 1
Function start=0x00000010 end=0x00000012 (size=0x00000002) count: 1
Code start=0x00000014 end=0x00000018 (size=0x00000004) count: 1
Custom start=0x0000001a end=0x0000001e (size=0x00000004) "x"
The section asked to go before the type section is written last. Assembling the same module three ways — (before type), (after code), and with no clause at all — gives three byte-identical binaries, which is the clearest statement of the problem: the clause has no effect whatsoever.
Version is 1.0.41 (git~1.0.41-58-gda988ca2).
Why
WastParser::ParseCustomSectionAnnotation parses the clause, checks the section name is one it recognises, and then drops the tokens without recording anything:
switch (Peek()) {
case TokenType::Function:
case TokenType::Type:
...
case TokenType::Start: {
DropToken();
break;
}
Custom in include/wabt/ir.h has no field to hold a placement, and BinaryWriter writes every custom section from one loop after the data section, so there is nowhere for the information to go and nothing that would act on it.
How I ran into it
I was round-tripping the spec testsuite — wast2json on each of the 147 .wast files, then every module it emits through wasm2wat and back through wat2wasm, comparing binaries. 1598 modules round-trip byte for byte. Nearly all of the differences that remain are legitimate normalisation: binary-leb128.wast and friends deliberately use padded LEB128 encodings, and empty elem and data sections get dropped. custom.wast was the exception — its custom sections come back in different positions, which led me here.
Fixing it
I am happy to implement this, but there is a question in it that seemed worth asking before writing code.
Making wat2wasm honour the clause is self-contained: a placement field on Custom, the parser storing it instead of discarding it, and the binary writer consulting it. BeginKnownSection and EndSection are single funnels for every known section, so the writer side needs two hooks rather than a change at each of the thirteen call sites.
Preserving placement through a disassembly round trip is the larger half, since the binary reader would have to record where each custom section appeared and the wat writer would have to emit the clause. That raises the question I would rather not answer unilaterally: should wasm2wat then emit a placement clause for every custom section, which changes its output for everyone, or only when the position is not the default one?
Happy to do whichever you prefer, or just the wat2wasm half if you would rather keep the two separate.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with WastParser::ParseCustomSectionAnnotation and the Custom definition in include/wabt/ir.h, then trace BinaryWriter through BeginKnownSection and EndSection. Compare the existing behavior with custom.wast and the reported before/after examples. Done means the chosen scope is documented and placement clauses affect the emitted section order, with round-trip behavior handled consistently if included.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, wasm
- Domain
- compilers, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 56/100