summaryrefslogtreecommitdiff
path: root/src/wasm
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2021-01-15 18:13:05 +0000
committerGitHub <noreply@github.com>2021-01-15 10:13:05 -0800
commite2dc4c338476aedff8d95c7549ab0f39d1aaf6d1 (patch)
treefa6fc4184c5ee762ccdc861a1f1c52188a7a3dc5 /src/wasm
parentd808e900b8b8a0bc99d77fd38e94426df73c3afa (diff)
downloadbinaryen-e2dc4c338476aedff8d95c7549ab0f39d1aaf6d1.tar.gz
binaryen-e2dc4c338476aedff8d95c7549ab0f39d1aaf6d1.tar.bz2
binaryen-e2dc4c338476aedff8d95c7549ab0f39d1aaf6d1.zip
[GC] Read and lower Let instructions (#3485)
For now we don't support non-nullability, and can therefore lower a let into simpler things. That is, (let $x = ... ;; ) => (block $x = ... ;; ) This lets us handle wasm binaries with let, so that we can optimize them (with the current downside of losing non-nullability). This is still not trivial to do, sadly, because the indexing of lets is somewhat odd in the binary. A let modifies the indexes of other things declared before it, which means that index "0" means different things at different times. And this is trickier for us because we add more locals as needed for tuples and stacky code. So this PR makes us track the absolute local indexes from which each let started to allocate its locals. The binary testcase was created from this wat using wasp: (module (type $vector (array (field (mut f64)))) (func $main (local $x i32) (local $y i32) (drop (local.get $x)) ;; 0 is the index appearing in the binary ;; first let (array.new_with_rtt $vector (f64.const 3.14159) (i32.const 1) (rtt.canon $vector) ) (let (local $v (ref $vector)) (drop (local.get $v)) ;; 0 (drop (local.get $x)) ;; 1 ;; another one, nested (array.new_with_rtt $vector (f64.const 1234) (i32.const 2) (rtt.canon $vector) ) (let (local $w (ref $vector)) (drop (local.get $v)) ;; 1 (drop (local.get $w)) ;; 0 (drop (local.get $x)) ;; 2 ) ) ;; another one, later (array.new_with_rtt $vector (f64.const 2.1828) (i32.const 3) (rtt.canon $vector) ) (let (local $v (ref $vector)) (drop (local.get $v)) ;; 0 (drop (local.get $x)) ;; 1 ) (drop (local.get $x)) ;; 0 ) )
Diffstat (limited to 'src/wasm')
-rw-r--r--src/wasm/wasm-binary.cpp90
1 files changed, 78 insertions, 12 deletions
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 9b4794997..bf2e4afef 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1833,15 +1833,9 @@ void WasmBinaryBuilder::readFunctions() {
readNextDebugLocation();
BYN_TRACE("reading " << i << std::endl);
- size_t numLocalTypes = getU32LEB();
- for (size_t t = 0; t < numLocalTypes; t++) {
- auto num = getU32LEB();
- auto type = getConcreteType();
- while (num > 0) {
- func->vars.push_back(type);
- num--;
- }
- }
+
+ readVars();
+
std::swap(func->prologLocation, debugLocation);
{
// process the function body
@@ -1854,6 +1848,7 @@ void WasmBinaryBuilder::readFunctions() {
assert(breakStack.empty());
assert(expressionStack.empty());
assert(controlFlowStack.empty());
+ assert(letStack.empty());
assert(depth == 0);
func->body = getBlockOrSingleton(func->sig.results);
assert(depth == 0);
@@ -1863,6 +1858,7 @@ void WasmBinaryBuilder::readFunctions() {
throwError("stack not empty on function exit");
}
assert(controlFlowStack.empty());
+ assert(letStack.empty());
if (pos != endOfFunction) {
throwError("binary offset at function exit not at expected location");
}
@@ -1875,6 +1871,18 @@ void WasmBinaryBuilder::readFunctions() {
BYN_TRACE(" end function bodies\n");
}
+void WasmBinaryBuilder::readVars() {
+ size_t numLocalTypes = getU32LEB();
+ for (size_t t = 0; t < numLocalTypes; t++) {
+ auto num = getU32LEB();
+ auto type = getConcreteType();
+ while (num > 0) {
+ currFunction->vars.push_back(type);
+ num--;
+ }
+ }
+}
+
void WasmBinaryBuilder::readExports() {
BYN_TRACE("== readExports\n");
size_t num = getU32LEB();
@@ -2886,6 +2894,10 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
visitCallRef(call);
break;
}
+ case BinaryConsts::Let: {
+ visitLet((curr = allocator.alloc<Block>())->cast<Block>());
+ break;
+ }
case BinaryConsts::AtomicPrefix: {
code = static_cast<uint8_t>(getU32LEB());
if (maybeVisitLoad(curr, code, /*isAtomic=*/true)) {
@@ -3056,6 +3068,36 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
return BinaryConsts::ASTNodes(code);
}
+Index WasmBinaryBuilder::getAbsoluteLocalIndex(Index index) {
+ // Wasm binaries put each let at the bottom of the index space, which may be
+ // good for binary size as often the uses of the let variables are close to
+ // the let itself. However, in Binaryen IR we just have a simple flat index
+ // space of absolute values, which we add to as we parse, and we depend on
+ // later optimizations to reorder locals for size.
+ //
+ // For example, if we have $x, then we add a let with $y, the binary would map
+ // 0 => y, 1 => x, while in Binaryen IR $x always stays at 0, and $y is added
+ // at 1.
+ //
+ // Compute the relative index in the let we were added. We start by looking at
+ // the last let added, and if we belong to it, we are already relative to it.
+ // We will continue relativizing as we go down, til we find our let.
+ int64_t relative = index;
+ for (auto i = int64_t(letStack.size()) - 1; i >= 0; i--) {
+ auto& info = letStack[i];
+ int64_t currNum = info.num;
+ // There were |currNum| let items added in this let. Check if we were one of
+ // them.
+ if (relative < currNum) {
+ return info.absoluteStart + relative;
+ }
+ relative -= currNum;
+ }
+ // We were not a let, but a normal var from the beginning. In that case, after
+ // we subtracted the let items, we have the proper absolute index.
+ return relative;
+}
+
void WasmBinaryBuilder::startControlFlow(Expression* curr) {
if (DWARF && currFunction) {
controlFlowStack.push_back(curr);
@@ -3322,9 +3364,8 @@ void WasmBinaryBuilder::visitCallIndirect(CallIndirect* curr) {
void WasmBinaryBuilder::visitLocalGet(LocalGet* curr) {
BYN_TRACE("zz node: LocalGet " << pos << std::endl);
- ;
requireFunctionContext("local.get");
- curr->index = getU32LEB();
+ curr->index = getAbsoluteLocalIndex(getU32LEB());
if (curr->index >= currFunction->getNumLocals()) {
throwError("bad local.get index");
}
@@ -3335,7 +3376,7 @@ void WasmBinaryBuilder::visitLocalGet(LocalGet* curr) {
void WasmBinaryBuilder::visitLocalSet(LocalSet* curr, uint8_t code) {
BYN_TRACE("zz node: Set|LocalTee\n");
requireFunctionContext("local.set outside of function");
- curr->index = getU32LEB();
+ curr->index = getAbsoluteLocalIndex(getU32LEB());
if (curr->index >= currFunction->getNumLocals()) {
throwError("bad local.set index");
}
@@ -5669,6 +5710,31 @@ void WasmBinaryBuilder::visitCallRef(CallRef* curr) {
curr->finalize(sig.results);
}
+void WasmBinaryBuilder::visitLet(Block* curr) {
+ // A let is lowered into a block that contains the value, and we allocate
+ // locals as needed, which works as we remove non-nullability.
+
+ startControlFlow(curr);
+ // Get the output type.
+ curr->type = getType();
+ // Get the new local types. First, get the absolute index from which we will
+ // start to allocate them.
+ Index absoluteStart = currFunction->vars.size();
+ readVars();
+ Index numNewVars = currFunction->vars.size() - absoluteStart;
+ // Assign the values into locals.
+ Builder builder(wasm);
+ for (Index i = 0; i < numNewVars; i++) {
+ auto* value = popNonVoidExpression();
+ curr->list.push_back(builder.makeLocalSet(absoluteStart + i, value));
+ }
+ // Read the body, with adjusted local indexes.
+ letStack.emplace_back(LetData{numNewVars, absoluteStart});
+ curr->list.push_back(getBlockOrSingleton(curr->type));
+ letStack.pop_back();
+ curr->finalize(curr->type);
+}
+
bool WasmBinaryBuilder::maybeVisitI31New(Expression*& out, uint32_t code) {
if (code != BinaryConsts::I31New) {
return false;