diff options
author | Alon Zakai <azakai@google.com> | 2020-07-30 11:41:43 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-30 11:41:43 -0700 |
commit | f8cd0ad7e2aa389ac04c5978ed9d654bdcf1e22f (patch) | |
tree | 0108600b45278469bd29f7b18a843afddb271563 /src | |
parent | 4253dae801b7f5526c28c4bbd8cda1d32067344d (diff) | |
download | binaryen-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.h | 77 |
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) { |