Skip to content

Commit

Permalink
Require unique names in binaryen IR (WebAssembly#746)
Browse files Browse the repository at this point in the history
  • Loading branch information
kripken authored Oct 7, 2016
1 parent eb95826 commit caf0a3d
Show file tree
Hide file tree
Showing 34 changed files with 3,843 additions and 3,740 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ Binaryen's internal IR is an AST, designed to be
* **Flexible and fast** for optimization.
* **As close as possible to WebAssembly** so it is simple and fast to convert it to and from WebAssembly.

Binaryen IR is essentially a subset of WebAssembly, [everything but some stack machine specific things that are cumbersome to represent in an AST](https://github.com/WebAssembly/binaryen/issues/663). (In particular, that means that if you use Binaryen to load WebAssembly that contains such stack machine specific code, it might be transformed a little, and when emitted you will not necessarily get the same thing you started out with.)
The differences between Binaryen IR and WebAssembly are:

* Binaryen IR [is an AST](https://github.com/WebAssembly/binaryen/issues/663). This differs from the WebAssembly binary format which is a stack machine.
* Binaryen IR requires the names of blocks and loops to be unique. This differs from the WebAssembly s-expression format which allows duplicate names (and depends on scoping to disambiguate).

As a result, you might notice that round-trip conversions (wasm => Binaryen IR => wasm) change code a little in some corner cases.

## Tools

Expand Down
57 changes: 37 additions & 20 deletions src/asm2wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "asm_v_wasm.h"
#include "passes/passes.h"
#include "pass.h"
#include "parsing.h"
#include "ast_utils.h"
#include "wasm-builder.h"
#include "wasm-emscripten.h"
Expand Down Expand Up @@ -1106,17 +1107,14 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
Ref params = ast[2];
Ref body = ast[3];

unsigned nextId = 0;
auto getNextId = [&nextId](std::string prefix) {
return IString((prefix + '$' + std::to_string(nextId++)).c_str(), false);
};
UniqueNameMapper nameMapper;

// given an asm.js label, returns the wasm label for breaks or continues
auto getBreakLabelName = [](IString label) {
return IString((std::string("label$break$") + label.str).c_str(), false);
return Name(std::string("label$break$") + label.str);
};
auto getContinueLabelName = [](IString label) {
return IString((std::string("label$continue$") + label.str).c_str(), false);
return Name(std::string("label$continue$") + label.str);
};

IStringSet functionVariables; // params or vars
Expand Down Expand Up @@ -1705,13 +1703,14 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
} else if (what == BLOCK) {
Name name;
if (parentLabel.is()) {
name = getBreakLabelName(parentLabel);
name = nameMapper.pushLabelName(getBreakLabelName(parentLabel));
parentLabel = IString();
breakStack.push_back(name);
}
auto ret = processStatements(ast[1], 0);
if (name.is()) {
breakStack.pop_back();
nameMapper.popLabelName(name);
Block* block = ret->dynCast<Block>();
if (block && block->name.isNull()) {
block->name = name;
Expand All @@ -1727,12 +1726,12 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
} else if (what == BREAK) {
auto ret = allocator.alloc<Break>();
assert(breakStack.size() > 0);
ret->name = !!ast[1] ? getBreakLabelName(ast[1]->getIString()) : breakStack.back();
ret->name = !!ast[1] ? nameMapper.sourceToUnique(getBreakLabelName(ast[1]->getIString())) : breakStack.back();
return ret;
} else if (what == CONTINUE) {
auto ret = allocator.alloc<Break>();
assert(continueStack.size() > 0);
ret->name = !!ast[1] ? getContinueLabelName(ast[1]->getIString()) : continueStack.back();
ret->name = !!ast[1] ? nameMapper.sourceToUnique(getContinueLabelName(ast[1]->getIString())) : continueStack.back();
return ret;
} else if (what == WHILE) {
bool forever = ast[1][0] == NUM && ast[1][1]->getInteger() == 1;
Expand All @@ -1743,9 +1742,11 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
in = getContinueLabelName(parentLabel);
parentLabel = IString();
} else {
out = getNextId("while-out");
in = getNextId("while-in");
out = "while-out";
in = "while-in";
}
out = nameMapper.pushLabelName(out);
in = nameMapper.pushLabelName(in);
ret->name = in;
breakStack.push_back(out);
continueStack.push_back(in);
Expand Down Expand Up @@ -1774,6 +1775,8 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->finalize();
continueStack.pop_back();
breakStack.pop_back();
nameMapper.popLabelName(in);
nameMapper.popLabelName(out);
return ret;
} else if (what == DO) {
if (ast[1][0] == NUM && ast[1][1]->getNumber() == 0) {
Expand All @@ -1783,14 +1786,17 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
stop = getBreakLabelName(parentLabel);
parentLabel = IString();
} else {
stop = getNextId("do-once");
stop = "do-once";
}
IString more = getNextId("unlikely-continue");
stop = nameMapper.pushLabelName(stop);
Name more = nameMapper.pushLabelName("unlikely-continue");
breakStack.push_back(stop);
continueStack.push_back(more);
auto child = process(ast[2]);
continueStack.pop_back();
breakStack.pop_back();
nameMapper.popLabelName(more);
nameMapper.popLabelName(stop);
// if we never continued, we don't need a loop
BreakSeeker breakSeeker(more);
breakSeeker.walk(child);
Expand Down Expand Up @@ -1819,15 +1825,19 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
in = getContinueLabelName(parentLabel);
parentLabel = IString();
} else {
out = getNextId("do-out");
in = getNextId("do-in");
out = "do-out";
in = "do-in";
}
out = nameMapper.pushLabelName(out);
in = nameMapper.pushLabelName(in);
loop->name = in;
breakStack.push_back(out);
continueStack.push_back(in);
loop->body = process(ast[2]);
continueStack.pop_back();
breakStack.pop_back();
nameMapper.popLabelName(in);
nameMapper.popLabelName(out);
Break *continuer = allocator.alloc<Break>();
continuer->name = in;
continuer->condition = process(ast[1]);
Expand All @@ -1847,9 +1857,11 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
in = getContinueLabelName(parentLabel);
parentLabel = IString();
} else {
out = getNextId("for-out");
in = getNextId("for-in");
out = "for-out";
in = "for-in";
}
out = nameMapper.pushLabelName(out);
in = nameMapper.pushLabelName(in);
ret->name = in;
breakStack.push_back(out);
continueStack.push_back(in);
Expand All @@ -1873,6 +1885,8 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->finalize();
continueStack.pop_back();
breakStack.pop_back();
nameMapper.popLabelName(in);
nameMapper.popLabelName(out);
Block *outer = allocator.alloc<Block>();
// add an outer block for the init as well
outer->list.push_back(process(finit));
Expand Down Expand Up @@ -1957,8 +1971,9 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
name = getBreakLabelName(parentLabel);
parentLabel = IString();
} else {
name = getNextId("switch");
name = "switch";
}
name = nameMapper.pushLabelName(name);
breakStack.push_back(name);

auto br = allocator.alloc<Switch>();
Expand Down Expand Up @@ -2009,14 +2024,14 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
auto case_ = processStatements(body, 0);
Name name;
if (condition->isNull()) {
name = br->default_ = getNextId("switch-default");
name = br->default_ = nameMapper.pushLabelName("switch-default");
} else {
auto index = getLiteral(condition).getInteger();
assert(index >= min);
index -= min;
assert(index >= 0);
uint64_t index_s = index;
name = getNextId("switch-case");
name = nameMapper.pushLabelName("switch-case");
if (br->targets.size() <= index_s) {
br->targets.resize(index_s + 1);
}
Expand All @@ -2028,6 +2043,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
next->list.push_back(case_);
next->finalize();
top = next;
nameMapper.popLabelName(name);
}

// the outermost block can be branched to to exit the whole switch
Expand All @@ -2042,6 +2058,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
}

breakStack.pop_back();
nameMapper.popLabelName(name);

return top;
}
Expand Down
5 changes: 4 additions & 1 deletion src/ast_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@

namespace wasm {

// Finds if there are breaks targeting a name. Note that since names are
// unique in our IR, we just need to look for the name, and do not need
// to analyze scoping.
struct BreakSeeker : public PostWalker<BreakSeeker, Visitor<BreakSeeker>> {
Name target; // look for this one XXX looking by name may fall prey to duplicate names
Name target;
Index found;
WasmType valueType;

Expand Down
6 changes: 5 additions & 1 deletion src/cfg/Relooper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <string>

#include "ast_utils.h"
#include "parsing.h"

namespace CFG {

Expand Down Expand Up @@ -939,7 +940,10 @@ void Relooper::Calculate(Block *Entry) {

wasm::Expression* Relooper::Render(RelooperBuilder& Builder) {
assert(Root);
return Root->Render(Builder, false);
auto* ret = Root->Render(Builder, false);
// we may use the same name for more than one block in HandleFollowupMultiples
wasm::UniqueNameMapper::uniquify(ret);
return ret;
}

#ifdef RELOOPER_DEBUG
Expand Down
85 changes: 85 additions & 0 deletions src/parsing.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,91 @@ struct ParseException {
}
};

// Helper for parsers that may not have unique label names. This transforms
// the names into unique ones, as required by Binaryen IR.
struct UniqueNameMapper {
std::vector<Name> labelStack;
std::map<Name, std::vector<Name>> labelMappings; // name in source => stack of uniquified names
std::map<Name, Name> reverseLabelMapping; // uniquified name => name in source

Index otherIndex = 0;

Name getPrefixedName(Name prefix) {
if (reverseLabelMapping.find(prefix) == reverseLabelMapping.end()) return prefix;
// make sure to return a unique name not already on the stack
while (1) {
Name ret = Name(prefix.str + std::to_string(otherIndex++));
if (reverseLabelMapping.find(ret) == reverseLabelMapping.end()) return ret;
}
}

// receives a source name. generates a unique name, pushes it, and returns it
Name pushLabelName(Name sName) {
Name name = getPrefixedName(sName);
labelStack.push_back(name);
labelMappings[sName].push_back(name);
reverseLabelMapping[name] = sName;
return name;
}

void popLabelName(Name name) {
assert(labelStack.back() == name);
labelStack.pop_back();
labelMappings[reverseLabelMapping[name]].pop_back();
}

Name sourceToUnique(Name sName) {
return labelMappings.at(sName).back();
}

Name uniqueToSource(Name name) {
return reverseLabelMapping.at(name);
}

void clear() {
labelStack.clear();
labelMappings.clear();
reverseLabelMapping.clear();
}

// Given an expression, ensures all names are unique
static void uniquify(Expression* curr) {
struct Walker : public ControlFlowWalker<Walker, Visitor<Walker>> {
UniqueNameMapper mapper;

static void doPreVisitControlFlow(Walker* self, Expression** currp) {
auto* curr = *currp;
if (auto* block = curr->dynCast<Block>()) {
if (block->name.is()) block->name = self->mapper.pushLabelName(block->name);
} else if (auto* loop = curr->dynCast<Loop>()) {
if (loop->name.is()) loop->name = self->mapper.pushLabelName(loop->name);
}
}
static void doPostVisitControlFlow(Walker* self, Expression** currp) {
auto* curr = *currp;
if (auto* block = curr->dynCast<Block>()) {
if (block->name.is()) self->mapper.popLabelName(block->name);
} else if (auto* loop = curr->dynCast<Loop>()) {
if (loop->name.is()) self->mapper.popLabelName(loop->name);
}
}

void visitBreak(Break *curr) {
curr->name = mapper.sourceToUnique(curr->name);
}
void visitSwitch(Switch *curr) {
for (auto& target : curr->targets) {
target = mapper.sourceToUnique(target);
}
curr->default_ = mapper.sourceToUnique(curr->default_);
}
};

Walker walker;
walker.walk(curr);
}
};

} // namespace wasm

#endif // wasm_parsing_h
Loading

0 comments on commit caf0a3d

Please sign in to comment.