Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions src/wasm/wasm-stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2568,25 +2568,45 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() {
for (Index i = 0; i < func->getNumParams(); i++) {
mappedLocals[std::make_pair(i, 0)] = i;
}

// Normally we map all locals of the same type into a range of adjacent
// addresses, which is more compact. However, if we need to keep DWARF valid,
// do not do any reordering at all - instead, do a trivial mapping that
// keeps everything unmoved.
//
// Unless we have run DWARF-invalidating passes, all locals added during the
// process that are not in DWARF info (tuple locals, tuple scratch locals,
// locals to resolve stacky format, ..) have been all tacked on to the
// existing locals and happen at the end, so as long as we print the local
// types in order, we don't invalidate original local DWARF info here.
if (DWARF) {
FindAll<TupleExtract> extracts(func->body);
if (!extracts.list.empty()) {
Fatal() << "DWARF + multivalue is not yet complete";
Index mappedIndex = func->getVarIndexBase();
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
size_t size = func->getLocalType(i).size();
for (Index j = 0; j < size; j++) {
mappedLocals[std::make_pair(i, j)] = mappedIndex + j;
}
mappedIndex += size;
}
Index varStart = func->getVarIndexBase();
Index varEnd = varStart + func->getNumVars();
o << U32LEB(func->getNumVars());
for (Index i = varStart; i < varEnd; i++) {
mappedLocals[std::make_pair(i, 0)] = i;
countScratchLocals();

size_t numBinaryLocals =
mappedIndex - func->getVarIndexBase() + scratchLocals.size();
o << U32LEB(numBinaryLocals);
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
for (const auto& type : func->getLocalType(i)) {
o << U32LEB(1);
parent.writeType(type);
}
}
for (auto& [type, _] : scratchLocals) {
o << U32LEB(1);
parent.writeType(func->getLocalType(i));
parent.writeType(type);
scratchLocals[type] = mappedIndex++;
}
return;
}

for (auto type : func->vars) {
for (const auto& t : type) {
noteLocalType(t);
Expand Down
106 changes: 106 additions & 0 deletions test/lit/binary/dwarf-multivalue.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
;; Test that we handle multivalue + DWARF correctly. When we need to preserve
;; DWARF info, we don't do any local reordering and all newly added locals
;; during parsing/writing are added at the end of the local list.

;; Generated from this c file with the following command:
;; $ emcc -g -Xclang -target-abi -Xclang experimental-mv dwarf-multivalue.c -o dwarf-multivalue.wasm
;;
;; struct MyStruct {
;; int a;
;; float b;
;; };
;;
;; struct MyStruct foo() {
;; struct MyStruct ret = {.a = 3, .b = 3.5};
;; return ret;
;; }
;;
;; void test() {
;; struct MyStruct s = foo();
;; }
;;
;; int main() {
;; test();
;; return 0;
;; }

;; The original wasm file's $test function's locals are as follows:
;; (func $test
;; (local $0 i32)
;; (local $1 i32)
;; (local $2 i32)
;; (local $3 i32)
;; (local $4 f32)
;; (local $5 i32)
;; (local $6 i32)
;; (local $7 i32)
;; (local $8 f32)
;; (local $9 i32)
;; (local $10 f32)

;; If we parse this wasm file into Binaryen IR, two locals are added in the
;; process. Here $11 is added for tuple parsing and $12 is added for stacky IR
;; resolving during binary reading process.
;; RUN: wasm-dis %s.wasm -o - | filecheck %s --check-prefix=ORIG
;; ORIG: (func $test
;; ORIG-NEXT: (local $0 i32)
;; ORIG-NEXT: (local $1 i32)
;; ORIG-NEXT: (local $2 i32)
;; ORIG-NEXT: (local $3 i32)
;; ORIG-NEXT: (local $4 f32)
;; ORIG-NEXT: (local $5 i32)
;; ORIG-NEXT: (local $6 i32)
;; ORIG-NEXT: (local $7 i32)
;; ORIG-NEXT: (local $8 f32)
;; ORIG-NEXT: (local $9 i32)
;; ORIG-NEXT: (local $10 f32)
;; ORIG-NEXT: (local $11 (tuple i32 f32))
;; ORIG-NEXT: (local $12 i32)

;; If we write this IR into binary, even if this cannot be displayed in the wast
;; format, the local order of $test will look like this, because we don't
;; reorder locals:
;; (func $test
;; (local $0 i32)
;; (local $1 i32)
;; (local $2 i32)
;; (local $3 i32)
;; (local $4 f32)
;; (local $5 i32)
;; (local $6 i32)
;; (local $7 i32)
;; (local $8 f32)
;; (local $9 i32)
;; (local $10 f32)
;; (local $11 i32) ;; Previous (local $11 (tuple i32 f32))'s first element
;; (local $12 f32) ;; Previous (local $11 (tuple i32 f32))'s second element
;; (local $13 i32) ;; Previous (local $12 i32)
;; (local $14 f32) ;; scratch local for f32

;; We parse this binary again into Binaryen IR, roundtripping the original
;; binary. Here until (local $14) is the same with the previous list, and $15 is
;; is added for tuple parsing and $16 is added for stacky IR resolving during
;; binary reading process.
;; RUN: wasm-opt -all -g --roundtrip %s.wasm -S -o - | filecheck %s --check-prefix=ROUNDTRIP
;; ROUNDTRIP: (func $test
;; ROUNDTRIP-NEXT: (local $0 i32)
;; ROUNDTRIP-NEXT: (local $1 i32)
;; ROUNDTRIP-NEXT: (local $2 i32)
;; ROUNDTRIP-NEXT: (local $3 i32)
;; ROUNDTRIP-NEXT: (local $4 f32)
;; ROUNDTRIP-NEXT: (local $5 i32)
;; ROUNDTRIP-NEXT: (local $6 i32)
;; ROUNDTRIP-NEXT: (local $7 i32)
;; ROUNDTRIP-NEXT: (local $8 f32)
;; ROUNDTRIP-NEXT: (local $9 i32)
;; ROUNDTRIP-NEXT: (local $10 f32)
;; ROUNDTRIP-NEXT: (local $11 i32)
;; ROUNDTRIP-NEXT: (local $12 f32)
;; ROUNDTRIP-NEXT: (local $13 i32)
;; ROUNDTRIP-NEXT: (local $14 f32)
;; ROUNDTRIP-NEXT: (local $15 (tuple i32 f32))
;; ROUNDTRIP-NEXT: (local $16 i32)

;; We can see that we don't reorder the locals during the process and the
;; original list of locals, local $0~$10, is untouched, to NOT invalidate DWARF
;; info.
Binary file added test/lit/binary/dwarf-multivalue.test.wasm
Binary file not shown.