summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2020-07-30 11:41:43 -0700
committerGitHub <noreply@github.com>2020-07-30 11:41:43 -0700
commitf8cd0ad7e2aa389ac04c5978ed9d654bdcf1e22f (patch)
tree0108600b45278469bd29f7b18a843afddb271563 /src
parent4253dae801b7f5526c28c4bbd8cda1d32067344d (diff)
downloadbinaryen-f8cd0ad7e2aa389ac04c5978ed9d654bdcf1e22f.tar.gz
binaryen-f8cd0ad7e2aa389ac04c5978ed9d654bdcf1e22f.tar.bz2
binaryen-f8cd0ad7e2aa389ac04c5978ed9d654bdcf1e22f.zip
wasm2js: Add an "Export" scope for name resolution (#2998)
Previously we used "Top" for both exports and the top level (which has functions and globals). The warning about name collisions there was meant only for exports (where if a name collides and so it must be renamed, means that there will be an externally-visible oddness for the user). But it applied to functions too, which could be annoying, and was not dangerous (at worst, it might be confusing when reading the emitted JS and seeing NAME_1, NAME_2, but there is no effect on execution or on exports). To fix this, add a new Export name scope. This separates function names from export names. However, it runs into another issue which is that when checking for a name conflict we had a big set of all the names in all the scopes. That is, FOO would only ever be used in one scope, period, and other appearances of that Name in wasm would get a suffix. As a result, if an exported function FOO has the name foo, we'd export it as FOO but name the function FOO_1 which is annoying. To fix that, keep sets of all names in each scope. When mangling a name we can then only care about the relevant scope, EXCEPT for local names, which must also not conflict with function names. That is, this would be bad: function foo(bar) { var bar = 0; } function bar() { .. It's not ok to call a parameter "bar" if there is a function by that name (well, it could be if it isn't called in that scope). So when mangling the Local scope, also check the Top one as well. The test output changes are due to non-overlapping scopes, specifically Local and Label. It's fine to have foo : while(1) { var foo = 5; } Those "foo"s do not conflict. Fixes emscripten-core/emscripten#11743
Diffstat (limited to 'src')
-rw-r--r--src/wasm2js.h77
1 files changed, 44 insertions, 33 deletions
diff --git a/src/wasm2js.h b/src/wasm2js.h
index 33f70a4fc..8d233eb95 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -85,7 +85,13 @@ IString stringToIString(std::string str) { return IString(str.c_str(), false); }
// Used when taking a wasm name and generating a JS identifier. Each scope here
// is used to ensure that all names have a unique name but the same wasm name
// within a scope always resolves to the same symbol.
+//
+// Export: Export names
+// Top: The main scope which contains functions and globals
+// Local: Local variables in a function.
+// Label: Label identifiers in a function
enum class NameScope {
+ Export,
Top,
Local,
Label,
@@ -181,11 +187,15 @@ public:
// First up check our cached of mangled names to avoid doing extra work
// below
- auto& mangledScope = mangledNames[(int)scope];
- auto it = mangledScope.find(name.c_str());
- if (it != mangledScope.end()) {
+ auto& map = wasmNameToMangledName[(int)scope];
+ auto it = map.find(name.c_str());
+ if (it != map.end()) {
return it->second;
}
+ // The mangled names in our scope.
+ auto& scopeMangledNames = mangledNames[(int)scope];
+ // In some cases (see below) we need to also check the Top scope.
+ auto& topMangledNames = mangledNames[int(NameScope::Top)];
// This is the first time we've seen the `name` and `scope` pair. Generate a
// globally unique name based on `name` and then register that in our cache
@@ -203,28 +213,30 @@ public:
}
auto mangled = asmangle(out.str());
ret = stringToIString(mangled);
- if (!allMangledNames.count(ret)) {
- break;
+ if (scopeMangledNames.count(ret)) {
+ // When export names collide things may be confusing, as this is
+ // observable externally by the person using the JS. Report a warning.
+ if (scope == NameScope::Export) {
+ std::cerr << "wasm2js: warning: export names colliding: " << mangled
+ << '\n';
+ }
+ continue;
}
-
- // In the global scope that's how you refer to actual function exports, so
- // it's a bug currently if they're not globally unique. This should
- // probably be fixed via a different namespace for exports or something
- // like that.
- // XXX This is not actually a valid check atm, since functions are not in
- // the global-most scope, but rather in the "asmFunc" scope which is
- // inside it. Also, for emscripten style glue, we emit the exports as
- // a return, so there is no name placed into the scope. For these
- // reasons, just warn here, don't error.
- if (scope == NameScope::Top) {
- std::cerr << "wasm2js: warning: global scope may be colliding with "
- "other scope: "
- << mangled << '\n';
+ // The Local scope is special: a Local name must not collide with a Top
+ // name, as they are in a single namespace in JS and can conflict:
+ //
+ // function foo(bar) {
+ // var bar = 0;
+ // }
+ // function bar() { ..
+ if (scope == NameScope::Local && topMangledNames.count(ret)) {
+ continue;
}
+ // We found a good name, use it.
+ scopeMangledNames.insert(ret);
+ map[name.c_str()] = ret;
+ return ret;
}
- allMangledNames.insert(ret);
- mangledScope[name.c_str()] = ret;
- return ret;
}
private:
@@ -238,8 +250,10 @@ private:
// Mangled names cache by interned names.
// Utilizes the usually reused underlying cstring's pointer as the key.
- std::unordered_map<const char*, IString> mangledNames[(int)NameScope::Max];
- std::unordered_set<IString> allMangledNames;
+ std::unordered_map<const char*, IString>
+ wasmNameToMangledName[(int)NameScope::Max];
+ // Set of all mangled names in each scope.
+ std::unordered_set<IString> mangledNames[(int)NameScope::Max];
// If a function is callable from outside, we'll need to cast the inputs
// and our return value. Otherwise, internally, casts are only needed
@@ -383,16 +397,13 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
ModuleUtils::iterImportedGlobals(
*wasm, [&](Global* import) { addGlobalImport(asmFunc[3], import); });
- // make sure exports get their expected names
- for (auto& e : wasm->exports) {
- if (e->kind == ExternalKind::Function) {
- fromName(e->name, NameScope::Top);
- }
- }
+ // Note the names of functions. We need to do this here as when generating
+ // mangled local names we need them not to conflict with these (see fromName)
+ // so we can't wait until we parse each function to note its name.
for (auto& f : wasm->functions) {
fromName(f->name, NameScope::Top);
}
- fromName(WASM_FETCH_HIGH_BITS, NameScope::Top);
+
// globals
bool generateFetchHighBits = false;
ModuleUtils::iterDefinedGlobals(*wasm, [&](Global* global) {
@@ -589,7 +600,7 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
if (export_->kind == ExternalKind::Function) {
ValueBuilder::appendToObjectWithQuotes(
exports,
- fromName(export_->name, NameScope::Top),
+ fromName(export_->name, NameScope::Export),
ValueBuilder::makeName(fromName(export_->value, NameScope::Top)));
}
if (export_->kind == ExternalKind::Memory) {
@@ -615,7 +626,7 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
IString("prototype")));
ValueBuilder::appendToCall(memory, descs);
ValueBuilder::appendToObjectWithQuotes(
- exports, fromName(export_->name, NameScope::Top), memory);
+ exports, fromName(export_->name, NameScope::Export), memory);
}
}
if (wasm->memory.exists) {