Write init expressions folded so they can be parsed back - #2830
Conversation
WriteInitExpr wrapped the whole expression list in a single pair of
parentheses and then wrote the instructions unfolded. That is fine for
the usual single-instruction init expression, but an extended constant
expression has several instructions, and the result cannot be parsed:
(global (;1;) (mut i32) (i32.const 44
i32.const 3
i32.sub))
wasm2wat produced this for a module using the extended-const proposal, so
its output no longer assembled. Globals, and data and elem offsets, were
all affected, and --fold-exprs made no difference because init
expressions never went through the folded writer.
Write them with WriteFoldedExprList instead, which emits a single folded
expression that is valid in all of those positions:
(global (;1;) (mut i32) (i32.sub
(i32.const 44)
(i32.const 3)))
Single-instruction init expressions are unchanged.
run-roundtrip.py did not accept --enable-extended-const, which is why
this was never covered; add the flag along with a roundtrip test.
sbc100
left a comment
There was a problem hiding this comment.
LGTM, but is there no way to use the flat (non-folded) form with extended const expressions? i.e. do we need to force one form over the other here or can we support both?
|
Yes, flat is possible, but not uniformly, and that's what pushed me towards folding. Globals take a bare instruction sequence, so So keeping the flat form means teaching the writer to emit Happy to do it the other way if you'd rather the writer respect |
|
This approach seems like a good improvement as is. |
wasm2watproduces output it can't read back for modules that use the extended-const proposal.An init expression with more than one instruction comes out like this:
which fails to assemble:
WriteInitExpropens a single pair of parentheses around the whole expression list and then writes the instructions unfolded. For the ordinary one-instruction case that gives(i32.const 45)and is fine, but with several instructions the leading(reads as the start of a folded expression and everything after it is a syntax error. Globals, data offsets and elem offsets are all affected.--fold-exprsdoesn't help, because init expressions never went through the folded writer at all.This writes them with
WriteFoldedExprListinstead, which emits one folded expression, valid in every position an init expression can appear:Single-instruction init expressions are unaffected — those still print as
(i32.const 45), so existing expectations don't move.How I found it
I ran every
.txtundertest/through wat2wasm → wasm2wat → wat2wasm with--enable-alland compared the two binaries. 1102 modules round-tripped byte-identically and three failed to re-assemble, all for this reason:test/dump/extended-const.txt,test/dump/invalid-data-segment-offset.txtandtest/parse/module/bad-global-invalid-expr.txt. After the change all 1105 round-trip and the binaries match.test/dump/extended-const.txtalready covers this module, but it's an objdump test, so nothing ever fed the disassembly back to the assembler.Testing
Added
test/roundtrip/extended-const.txt, covering a global, a data offset and an elem offset with extended constant expressions. It fails without the writer change with the parse error above, and passes with it.run-roundtrip.pyhad no--enable-extended-const, which is the reason the roundtrip suite couldn't have caught this in the first place, so I added the flag and passed it to both tools alongside the existing ones.test/roundtripgoes from 92 to 93 passing, and the unit tests are unchanged at 135.One caveat on my local runs: this is Windows, and a lot of the wider suite can't execute here — Application Control blocks the freshly built tools for several directories, and the wasm2c tests need a
cl.exeI don't have.roundtrip,desugarandtypecheckrun clean, and I checked that no expected output anywhere intest/contains the old unparseable shape, but I'd rather flag that than imply I ran everything.