diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/s2wasm.h | 1460 | ||||
-rw-r--r-- | src/tools/s2wasm.cpp | 281 | ||||
-rw-r--r-- | src/tools/wasm-emscripten-finalize.cpp | 1 | ||||
-rw-r--r-- | src/wasm-emscripten.h | 8 | ||||
-rw-r--r-- | src/wasm-linker.cpp | 417 | ||||
-rw-r--r-- | src/wasm-linker.h | 342 | ||||
-rw-r--r-- | src/wasm/wasm-emscripten.cpp | 38 |
7 files changed, 12 insertions, 2535 deletions
diff --git a/src/s2wasm.h b/src/s2wasm.h deleted file mode 100644 index 3cb4c7139..000000000 --- a/src/s2wasm.h +++ /dev/null @@ -1,1460 +0,0 @@ -/* - * Copyright 2015 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// .s to WebAssembly translator. -// - -#ifndef wasm_s2wasm_h -#define wasm_s2wasm_h - -#include <limits.h> - -#include "wasm.h" -#include "parsing.h" -#include "pass.h" -#include "asm_v_wasm.h" -#include "wasm-builder.h" -#include "wasm-linker.h" - -namespace wasm { - -// -// S2WasmBuilder - parses a .s file into WebAssembly -// - -class S2WasmBuilder { - const char* inputStart; - const char* s; - bool debug; - Module* wasm; - MixedArena* allocator; - LinkerObject* linkerObj; - std::unique_ptr<LinkerObject::SymbolInfo> symbolInfo; - std::unordered_map<uint32_t, uint32_t> fileIndexMap; - - public: - S2WasmBuilder(const char* input, bool debug) - : inputStart(input), - s(input), - debug(debug), - wasm(nullptr), - allocator(nullptr), - linkerObj(nullptr) - {} - - void build(LinkerObject *obj) { - // If getSymbolInfo has not already been called, populate the symbol - // info now. - if (!symbolInfo) symbolInfo.reset(getSymbolInfo()); - linkerObj = obj; - wasm = &obj->wasm; - allocator = &wasm->allocator; - - s = inputStart; - process(); - } - - // getSymbolInfo scans the .s file to determine what symbols it defines - // and references. - LinkerObject::SymbolInfo* getSymbolInfo() { - if (!symbolInfo) { - symbolInfo = make_unique<LinkerObject::SymbolInfo>(); - scan(symbolInfo.get()); - } - return symbolInfo.get(); - } - - private: - // utilities - - void skipWhitespace() { - while (1) { - while (*s && isspace(*s)) s++; - if (*s != '#') break; - while (*s != '\n') s++; - } - } - - void skipToEOL() { - s = strchr(s, '\n'); - assert(s); - } - - bool skipComma() { - skipWhitespace(); - if (*s != ',') return false; - s++; - skipWhitespace(); - return true; - } - - bool skipEqual() { - skipWhitespace(); - if (*s != '=') return false; - s++; - skipWhitespace(); - return true; - } - - #define abort_on(why) { \ - dump(why ":"); \ - abort(); \ - } - - bool peek(const char *pattern) { - return strncmp(s, pattern, strlen(pattern)) == 0; - } - - // match and skip the pattern, if matched - bool match(const char *pattern) { - size_t size = strlen(pattern); - if (strncmp(s, pattern, size) == 0) { - s += size; - skipWhitespace(); - return true; - } - return false; - } - - void mustMatch(const char *pattern) { - bool matched = match(pattern); - if (!matched) { - std::cerr << "<< " << pattern << " >>\n"; - abort_on("bad mustMatch"); - } - } - - void dump(const char *text) { - std::cerr << "[[" << text << "]]:\n==========\n"; - for (size_t i = 0; i < 60; i++) { - if (!s[i]) break; - std::cerr << s[i]; - } - std::cerr << "\n==========\n"; - } - - void unget(Name str) { - s -= strlen(str.str); - } - - Name getStr() { - std::string str; // TODO: optimize this and the other get* methods - while (*s && !isspace(*s)) { - str += *s; - s++; - } - return cashew::IString(str.c_str(), false); - } - - void skipToSep() { - while (*s && !isspace(*s) && *s != ',' && *s != '(' && *s != ')' && *s != ':' && *s != '+' && *s != '-') { - s++; - } - } - - Name getStrToSep() { - std::string str; - while (*s && !isspace(*s) && *s != ',' && *s != '(' && *s != ')' && *s != ':' && *s != '+' && *s != '-' && *s != '=') { - str += *s; - s++; - } - return cashew::IString(str.c_str(), false); - } - - Name getStrToColon() { - std::string str; - while (*s && !isspace(*s) && *s != ':') { - str += *s; - s++; - } - return cashew::IString(str.c_str(), false); - } - - // get an int - int32_t getInt() { - const char* loc = s; - uint32_t value = 0; - bool neg = false; - if (*loc == '-') { - neg = true; - loc++; - } - while (isdigit(*loc)) { - uint32_t digit = *loc - '0'; - if (value > std::numeric_limits<uint32_t>::max() / 10) { - abort_on("uint32_t overflow"); - } - value *= 10; - if (value > std::numeric_limits<uint32_t>::max() - digit) { - abort_on("uint32_t overflow"); - } - value += digit; - loc++; - } - if (neg) { - uint32_t positive_int_min = - (uint32_t) - (1 + std::numeric_limits<int32_t>::min()) + (uint32_t)1; - if (value > positive_int_min) { - abort_on("negative int32_t overflow"); - } - s = loc; - return -value; - } - s = loc; - return value; - } - - // get an int from an arbitrary string, with our full error handling - int32_t getInt(const char *from) { - const char *before = s; - s = from; - auto ret = getInt(); - s = before; - return ret; - } - - // gets a constant, which may be a relocation for later. - // returns whether this is a relocation - // TODO: Clean up this and the way relocs are created from parsed objects - LinkerObject::Relocation* getRelocatableConst(uint32_t* target) { - if (isdigit(*s) || *s == '-') { - int32_t val = getInt(); - memcpy(target, &val, sizeof(val)); - return nullptr; - } - - // a global constant, we need to fix it up later - Name name = getStrToSep(); - LinkerObject::Relocation::Kind kind = isFunctionName(name) ? - LinkerObject::Relocation::kFunction : - LinkerObject::Relocation::kData; - int offset = 0; - if (*s == '+') { - s++; - offset = getInt(); - } else if (*s == '-') { - s++; - offset = -getInt(); - } - return new LinkerObject::Relocation( - kind, target, fixEmLongjmp(cleanFunction(name)), offset); - } - - Expression* relocationToGetGlobal(LinkerObject::Relocation* relocation) { - if (!relocation) { - return nullptr; - } - - auto name = relocation->symbol; - auto g = allocator->alloc<GetGlobal>(); - g->name = name; - g->type = i32; - - // Optimization: store any nonnegative addends in their natural place. - // Only do this for positive addends because load/store offsets cannot be - // negative. - if (relocation->addend >= 0) { - *relocation->data = relocation->addend; - return g; - } - - auto c = allocator->alloc<Const>(); - c->type = i32; - c->value = Literal(relocation->addend); - - auto add = allocator->alloc<Binary>(); - add->type = i32; - add->op = AddInt32; - add->left = c; - add->right = g; - return add; - } - Expression* getRelocatableExpression(uint32_t* target) { - auto relocation = std::unique_ptr<LinkerObject::Relocation>(getRelocatableConst(target)); - if (!relocation) { - return nullptr; - } - if (linkerObj->isObjectImplemented(relocation->symbol)) { - linkerObj->addRelocation(relocation.release()); - return nullptr; - } - return relocationToGetGlobal(relocation.get()); - } - - int64_t getInt64() { - const char* loc = s; - uint64_t value = 0; - bool neg = false; - if (*loc == '-') { - neg = true; - loc++; - } - while (isdigit(*loc)) { - uint64_t digit = *loc - '0'; - if (value > std::numeric_limits<uint64_t>::max() / 10) { - abort_on("uint64_t overflow"); - } - value *= 10; - if (value > std::numeric_limits<uint64_t>::max() - digit) { - abort_on("uint64_t overflow"); - } - value += digit; - loc++; - } - if (neg) { - uint64_t positive_int_min = - (uint64_t) - (1 + std::numeric_limits<int64_t>::min()) + (uint64_t)1; - if (value > positive_int_min) { - abort_on("negative int64_t overflow"); - } - s = loc; - return -value; - } - s = loc; - return value; - } - - Name getSeparated(char separator) { - skipWhitespace(); - std::string str; - while (*s && *s != separator && *s != '\n') { - str += *s; - s++; - } - skipWhitespace(); - return cashew::IString(str.c_str(), false); - } - Name getCommaSeparated() { return getSeparated(','); } - Name getAtSeparated() { return getSeparated('@'); } - - Name getAssign() { - skipWhitespace(); - if (*s != '$') return Name(); - const char *before = s; - s++; - std::string str; - while (*s && *s != '=' && *s != '\n' && *s != ',') { - str += *s; - s++; - } - if (*s != '=') { // not an assign - s = before; - return Name(); - } - s++; - skipComma(); - return cashew::IString(str.c_str(), false); - } - - std::vector<char> getQuoted() { - assert(*s == '"'); - s++; - std::vector<char> str; - while (*s && *s != '\"') { - if (s[0] == '\\') { - switch (s[1]) { - case 'n': str.push_back('\n'); s += 2; continue; - case 'r': str.push_back('\r'); s += 2; continue; - case 't': str.push_back('\t'); s += 2; continue; - case 'f': str.push_back('\f'); s += 2; continue; - case 'b': str.push_back('\b'); s += 2; continue; - case '\\': str.push_back('\\'); s += 2; continue; - case '"': str.push_back('"'); s += 2; continue; - default: { - if (isdigit(s[1])) { - int code = (s[1] - '0')*8*8 + (s[2] - '0')*8 + (s[3] - '0'); - str.push_back(char(code)); - s += 4; - continue; - } else abort_on("getQuoted-escape"); - } - } - } - str.push_back(*s); - s++; - } - s++; - skipWhitespace(); - return str; - } - - Type tryGetType() { - if (match("i32")) return i32; - if (match("i64")) return i64; - if (match("f32")) return f32; - if (match("f64")) return f64; - return none; - } - - Type tryGetTypeWithoutNewline() { - const char* saved = s; - Type type = tryGetType(); - if (type != none && strchr(saved, '\n') > s) { - s = saved; - type = none; - } - return type; - } - - Type getType() { - Type t = tryGetType(); - if (t != none) { - return t; - } - abort_on("getType"); - } - - // The LLVM backend emits function names as name@FUNCTION. - bool isFunctionName(Name name) { - return !!strstr(name.str, "@FUNCTION"); - } - - // Drop the @ and after it. - Name cleanFunction(Name name) { - if (!strchr(name.str, '@')) return name; - char *temp = strdup(name.str); - *strchr(temp, '@') = 0; - Name ret = cashew::IString(temp, false); - free(temp); - return ret; - } - - // processors - - void scan(LinkerObject::SymbolInfo* info) { - s = inputStart; - while (*s) { - skipWhitespace(); - - // add function definitions and aliases - if (match(".type")) { - Name name = getCommaSeparated(); - skipComma(); - if (!match("@function")) continue; - if (match(".hidden")) mustMatch(name.str); - if (match(".set")) { // function aliases - // syntax: .set alias, original@FUNCTION - Name name = getCommaSeparated(); - skipComma(); - Name alias = getAtSeparated(); - mustMatch("@FUNCTION"); - auto ret = info->aliasedSymbols.insert({name, LinkerObject::SymbolAlias(alias, LinkerObject::Relocation::kFunction, 0)}); - if (!ret.second) std::cerr << "Unsupported data alias redefinition: " << name << ", skipping...\n"; - continue; - } - - mustMatch(name.str); - if (match(":")) { - info->implementedFunctions.insert(name); - } else { - abort_on("unknown directive"); - } - } else if (match(".import_global")) { - Name name = getStr(); - info->importedObjects.insert(name); - s = strchr(s, '\n'); - } else if (match(".set")) { // data aliases - // syntax: .set alias, original - Name lhs = getCommaSeparated(); - skipComma(); - Name rhs = getStrToSep(); - assert(!isFunctionName(rhs)); - Offset offset = 0; - if (*s == '+') { - s++; - offset = getInt(); - } - - // check if the rhs is already an alias - const auto alias = symbolInfo->aliasedSymbols.find(rhs); - if (alias != symbolInfo->aliasedSymbols.end() && alias->second.kind == LinkerObject::Relocation::kData) { - offset += alias->second.offset; - rhs = alias->second.symbol; - } - - // add the new alias - auto ret = symbolInfo->aliasedSymbols.insert({lhs, LinkerObject::SymbolAlias(rhs, - LinkerObject::Relocation::kData, offset)}); - if (!ret.second) std::cerr << "Unsupported function alias redefinition: " << lhs << ", skipping...\n"; - } else { - s = strchr(s, '\n'); - if (!s) - break; - } - } - } - - void process() { - while (*s) { - skipWhitespace(); - if (debug) dump("process"); - if (!*s) break; - if (*s != '.') skipObjectAlias(false); - s++; - if (match("text")) parseText(); - else if (match("type")) parseType(); - else if (match("weak") || match("hidden") || match("protected") || match("internal")) getStr(); // contents are in the content that follows - else if (match("imports")) skipImports(); - else if (match("data")) {} - else if (match("ident")) skipToEOL(); - else if (match("section")) parseToplevelSection(); - else if (match("file")) parseFile(); - else if (match("align") || match("p2align")) skipToEOL(); - else if (match("import_global")) { - skipToEOL(); - skipWhitespace(); - if (match(".size")) { - skipToEOL(); - } - } - else if (match("globl")) parseGlobl(); - else if (match("functype")) parseFuncType(); - else skipObjectAlias(true); - } - } - - void skipObjectAlias(bool prefix) { - if (debug) dump("object_alias"); - mustMatch("set"); - - Name lhs = getCommaSeparated(); - WASM_UNUSED(lhs); - skipComma(); - Name rhs = getStr(); - WASM_UNUSED(rhs); - skipWhitespace(); - - // if no size attribute (e.g. weak symbol), skip - if (!match(".size")) return; - - mustMatch(lhs.str); - mustMatch(","); - Name size = getStr(); - WASM_UNUSED(size); - skipWhitespace(); - } - - void parseToplevelSection() { - auto section = getCommaSeparated(); - // Skipping .debug_ sections - if (!strncmp(section.c_str(), ".debug_", strlen(".debug_"))) { - const char *next = strstr(s, ".section"); - s = !next ? s + strlen(s) : next; - return; - } - // Initializers are anything in a section whose name begins with .init_array - if (!strncmp(section.c_str(), ".init_array", strlen(".init_array") - 1)) { - parseInitializer(); - return; - } - s = strchr(s, '\n'); - } - - void parseInitializer() { - // Ignore the rest of the .section line - skipToEOL(); - skipWhitespace(); - // The section may start with .p2align - if (match(".p2align")) { - skipToEOL(); - skipWhitespace(); - } - mustMatch(".int32"); - do { - linkerObj->addInitializerFunction(cleanFunction(getStr())); - skipWhitespace(); - } while (match(".int32")); - } - - void parseText() { - while (*s) { - skipWhitespace(); - if (!*s) break; - if (*s != '.') break; - s++; - if (parseVersionMin()); - else if (match("file")) parseFile(); - else if (match("globl")) parseGlobl(); - else if (match("type")) parseType(); - else { - s--; - break; - } - } - } - - void parseFile() { - if (debug) dump("file"); - size_t fileId = 0; - if (*s != '"') { - fileId = getInt(); - skipWhitespace(); - } - auto filename = getQuoted(); - uint32_t index = wasm->debugInfoFileNames.size(); - wasm->debugInfoFileNames.push_back(std::string(filename.begin(), filename.end())); - fileIndexMap[fileId] = index; - } - - void parseGlobl() { - linkerObj->addGlobal(getStr()); - skipWhitespace(); - } - - void parseFuncType() { - auto decl = make_unique<FunctionType>(); - Name name = getCommaSeparated(); - skipComma(); - if(match("void")) { - decl->result = none; - } else { - decl->result = getType(); - } - while (*s && skipComma()) decl->params.push_back(getType()); - std::string sig = getSig(decl.get()); - decl->name = "FUNCSIG$" + sig; - - FunctionType *ty = wasm->getFunctionTypeOrNull(decl->name); - if (!ty) { - // The wasm module takes ownership of the FunctionType if we insert it. - // Otherwise it's already in the module and ours is freed. - ty = decl.release(); - wasm->addFunctionType(ty); - } - linkerObj->addExternType(name, ty); - } - - bool parseVersionMin() { - if (match("watchos_version_min") || match("tvos_version_min") || match("ios_version_min") || match("macosx_version_min")) { - s = strchr(s, '\n'); - skipWhitespace(); - return true; - } else - return false; - } - - void parseFunction() { - if (debug) dump("func"); - if (match(".set")) { // alias - // syntax: .set alias, original@FUNCTION - getCommaSeparated(); - skipComma(); - getAtSeparated(); - mustMatch("@FUNCTION"); - return; - } - - Name name = getStrToSep(); - mustMatch(":"); - - Function::DebugLocation debugLocation = { 0, 0, 0 }; - bool useDebugLocation = false; - auto recordLoc = [&]() { - if (debug) dump("loc"); - size_t fileId = getInt(); - skipWhitespace(); - uint32_t row = getInt(); - skipWhitespace(); - uint32_t column = getInt(); - auto iter = fileIndexMap.find(fileId); - if (iter == fileIndexMap.end()) { - abort_on("idx"); - } - useDebugLocation = true; - debugLocation = { iter->second, row, column }; - s = strchr(s, '\n'); - }; - auto recordLabel = [&]() { - if (debug) dump("label"); - Name label = getStrToSep(); - // TODO: track and create map of labels and their ranges for our AST - WASM_UNUSED(label); - s = strchr(s, '\n'); - }; - - unsigned nextId = 0; - auto getNextId = [&nextId]() { - return cashew::IString(std::to_string(nextId++).c_str(), false); - }; - wasm::Builder builder(*wasm); - std::vector<NameType> params; - Type resultType = none; - std::vector<NameType> vars; - - std::map<Name, Type> localTypes; - // params and result - while (1) { - if (match(".param")) { - while (1) { - Name name = getNextId(); - Type type = getType(); - params.emplace_back(name, type); - localTypes[name] = type; - skipWhitespace(); - if (!match(",")) break; - } - } else if (match(".result")) { - resultType = getType(); - } else if (match(".indidx")) { - int64_t indirectIndex = getInt64(); - skipWhitespace(); - if (indirectIndex < 0) { - abort_on("indidx"); - } - linkerObj->addIndirectIndex(name, indirectIndex); - } else if (match(".local")) { - while (1) { - Name name = getNextId(); - Type type = getType(); - vars.emplace_back(name, type); - localTypes[name] = type; - skipWhitespace(); - if (!match(",")) break; - } - } else if (match(".file")) { - parseFile(); - skipWhitespace(); - } else if (match(".loc")) { - recordLoc(); - skipWhitespace(); - } else if (peek(".Lfunc_begin")) { - recordLabel(); - skipWhitespace(); - } else break; - } - Function* func = builder.makeFunction(name, std::move(params), resultType, std::move(vars)); - - // parse body - func->body = allocator->alloc<Block>(); - std::vector<Expression*> bstack; - auto addToBlock = [&](Expression* curr) { - if (useDebugLocation) { - func->debugLocations[curr] = debugLocation; - } - Expression* last = bstack.back(); - if (last->is<Loop>()) { - last = last->cast<Loop>()->body; - } - last->cast<Block>()->list.push_back(curr); - }; - bstack.push_back(func->body); - std::vector<Expression*> estack; - auto push = [&](Expression* curr) { - //std::cerr << "push " << curr << '\n'; - estack.push_back(curr); - }; - auto pop = [&]() { - assert(!estack.empty()); - Expression* ret = estack.back(); - assert(ret); - estack.pop_back(); - //std::cerr << "pop " << ret << '\n'; - return ret; - }; - auto getNumInputs = [&]() { - int ret = 1; - const char *t = s; - while (*t != '\n') { - if (*t == ',') ret++; - t++; - } - return ret; - }; - auto getInputs = [&](int num) { - // we may have $pop, $0, $pop, $1 etc., which are getlocals - // interleaved with stack pops, and the stack pops must be done in - // *reverse* order, i.e., that input should turn into - // lastpop, getlocal(0), firstpop, getlocal(1) - std::vector<Expression*> inputs; // TODO: optimize (if .s format doesn't change) - inputs.resize(num); - for (int i = 0; i < num; i++) { - if (match("$pop")) { - skipToSep(); - inputs[i] = nullptr; - } else if (*s == '$') { - s++; - auto curr = allocator->alloc<GetLocal>(); - curr->index = func->getLocalIndex(getStrToSep()); - curr->type = func->getLocalType(curr->index); - inputs[i] = curr; - } else { - abort_on("bad input register"); - } - if (*s == ')') s++; // tolerate 0(argument) syntax, where we started at the 'a' - if (*s == ':') { // tolerate :attribute=value syntax (see getAttributes) - s++; - skipToSep(); - } - if (i < num - 1) skipComma(); - } - for (int i = num-1; i >= 0; i--) { - if (inputs[i] == nullptr) inputs[i] = pop(); - } - return inputs; - }; - auto getInput = [&]() { - return getInputs(1)[0]; - }; - auto setOutput = [&](Expression* curr, Name assign) { - if (assign.isNull() || assign.str[0] == 'd') { // drop - auto* add = curr; - if (isConcreteType(curr->type)) { - add = builder.makeDrop(curr); - } - addToBlock(add); - } else if (assign.str[0] == 'p') { // push - push(curr); - } else { // set to a local - auto set = allocator->alloc<SetLocal>(); - set->index = func->getLocalIndex(assign); - set->value = curr; - set->type = curr->type; - set->setTee(false); - addToBlock(set); - } - }; - auto getAttributes = [&](int num) { - const char *before = s; - std::vector<const char*> attributes; // TODO: optimize (if .s format doesn't change) - attributes.resize(num); - for (int i = 0; i < num; i++) { - skipToSep(); - if (*s == ')') s++; // tolerate 0(argument) syntax, where we started at the 'a' - if (*s == ':') { - attributes[i] = s + 1; - } else { - attributes[i] = nullptr; - } - if (i < num - 1) skipComma(); - } - s = before; - return attributes; - }; - // - auto makeBinary = [&](BinaryOp op, Type type) { - Name assign = getAssign(); - skipComma(); - auto curr = allocator->alloc<Binary>(); - curr->op = op; - auto inputs = getInputs(2); - curr->left = inputs[0]; - curr->right = inputs[1]; - curr->finalize(); - assert(curr->type == type); - setOutput(curr, assign); - }; - auto makeUnary = [&](UnaryOp op, Type type) { - Name assign = getAssign(); - skipComma(); - auto curr = allocator->alloc<Unary>(); - curr->op = op; - curr->value = getInput(); - curr->type = type; - curr->finalize(); - setOutput(curr, assign); - }; - auto makeHost = [&](HostOp op) { - Name assign = getAssign(); - auto curr = allocator->alloc<Host>(); - curr->op = op; - curr->finalize(); - setOutput(curr, assign); - }; - auto makeHost1 = [&](HostOp op) { - Name assign = getAssign(); - auto curr = allocator->alloc<Host>(); - curr->op = op; - curr->operands.push_back(getInput()); - curr->finalize(); - setOutput(curr, assign); - }; - auto useRelocationExpression = [&](Expression *expr, Expression *reloc) { - if (!reloc) { - return expr; - } - // Optimization: if the given expr is (i32.const 0), ignore it - if (expr->_id == Expression::ConstId && - ((Const*)expr)->value.getInteger() == 0) { - return reloc; - } - - // Otherwise, need to add relocation expr to given expr - auto add = allocator->alloc<Binary>(); - add->type = i32; - add->op = AddInt32; - add->left = expr; - add->right = reloc; - return (Expression*)add; - }; - auto makeLoad = [&](Type type) { - skipComma(); - auto curr = allocator->alloc<Load>(); - curr->isAtomic = false; - curr->type = type; - int32_t bytes = getInt() / CHAR_BIT; - curr->bytes = bytes > 0 ? bytes : getTypeSize(type); - curr->signed_ = match("_s"); - match("_u"); - Name assign = getAssign(); - auto relocation = getRelocatableExpression(&curr->offset.addr); - mustMatch("("); - auto attributes = getAttributes(1); - curr->ptr = useRelocationExpression(getInput(), relocation); - curr->align = curr->bytes; - if (attributes[0]) { - assert(strncmp(attributes[0], "p2align=", 8) == 0); - curr->align = Address(1) << getInt(attributes[0] + 8); - } - setOutput(curr, assign); - }; - auto makeStore = [&](Type type) { - auto curr = allocator->alloc<Store>(); - curr->isAtomic = false; - curr->valueType = type; - s += strlen("store"); - if(!isspace(*s)) { - curr->bytes = getInt() / CHAR_BIT; - } else { - curr->bytes = getTypeSize(type); - } - skipWhitespace(); - auto relocation = getRelocatableExpression(&curr->offset.addr); - mustMatch("("); - auto attributes = getAttributes(2); - auto inputs = getInputs(2); - curr->ptr = useRelocationExpression(inputs[0], relocation); - curr->align = curr->bytes; - if (attributes[0]) { - assert(strncmp(attributes[0], "p2align=", 8) == 0); - curr->align = Address(1) << getInt(attributes[0] + 8); - } - curr->value = inputs[1]; - curr->finalize(); - addToBlock(curr); - }; - auto makeSelect = [&](Type type) { - Name assign = getAssign(); - skipComma(); - auto curr = allocator->alloc<Select>(); - auto inputs = getInputs(3); - curr->ifTrue = inputs[0]; - curr->ifFalse = inputs[1]; - curr->condition = inputs[2]; - assert(curr->condition->type == i32); - curr->type = type; - setOutput(curr, assign); - }; - auto makeCall = [&](Type type) { - if (match("_indirect")) { - // indirect call - Name assign = getAssign(); - int num = getNumInputs(); - auto inputs = getInputs(num); - auto* target = *(inputs.end() - 1); - std::vector<Expression*> operands(inputs.begin(), inputs.end() - 1); - auto* funcType = ensureFunctionType(getSig(type, operands), wasm); - assert(type == funcType->result); - auto* indirect = builder.makeCallIndirect(funcType, target, std::move(operands)); - setOutput(indirect, assign); - } else { - // non-indirect call - Name assign = getAssign(); - Name rawTarget = cleanFunction(getCommaSeparated()); - Call* curr = allocator->alloc<Call>(); - curr->type = type; - skipWhitespace(); - if (*s == ',') { - skipComma(); - int num = getNumInputs(); - for (Expression* input : getInputs(num)) { - curr->operands.push_back(input); - } - } - Name target = linkerObj->resolveAlias(rawTarget, LinkerObject::Relocation::kFunction); - curr->target = target; - if (!linkerObj->isFunctionImplemented(target)) { - linkerObj->addUndefinedFunctionCall(curr); - } - setOutput(curr, assign); - } - }; - #define BINARY_INT_OR_FLOAT(op) (type == i32 ? BinaryOp::op##Int32 : (type == i64 ? BinaryOp::op##Int64 : (type == f32 ? BinaryOp::op##Float32 : BinaryOp::op##Float64))) - #define BINARY_INT(op) (type == i32 ? BinaryOp::op##Int32 : BinaryOp::op##Int64) - #define BINARY_FLOAT(op) (type == f32 ? BinaryOp::op##Float32 : BinaryOp::op##Float64) - auto handleTyped = [&](Type type) { - switch (*s) { - case 'a': { - if (match("add")) makeBinary(BINARY_INT_OR_FLOAT(Add), type); - else if (match("and")) makeBinary(BINARY_INT(And), type); - else if (match("abs")) makeUnary(type == f32 ? UnaryOp::AbsFloat32 : UnaryOp::AbsFloat64, type); - else abort_on("type.a"); - break; - } - case 'c': { - if (match("const")) { - Name assign = getAssign(); - if (type == i32) { - // may be a relocation - auto curr = allocator->alloc<Const>(); - curr->type = curr->value.type = i32; - auto relocation = getRelocatableExpression((uint32_t*)curr->value.geti32Ptr()); - auto expr = useRelocationExpression(curr, relocation); - setOutput(expr, assign); - } else { - cashew::IString str = getStr(); - setOutput(parseConst(str, type, *allocator), assign); - } - } - else if (match("call")) makeCall(type); - else if (match("convert_s/i32")) makeUnary(type == f32 ? UnaryOp::ConvertSInt32ToFloat32 : UnaryOp::ConvertSInt32ToFloat64, type); - else if (match("convert_u/i32")) makeUnary(type == f32 ? UnaryOp::ConvertUInt32ToFloat32 : UnaryOp::ConvertUInt32ToFloat64, type); - else if (match("convert_s/i64")) makeUnary(type == f32 ? UnaryOp::ConvertSInt64ToFloat32 : UnaryOp::ConvertSInt64ToFloat64, type); - else if (match("convert_u/i64")) makeUnary(type == f32 ? UnaryOp::ConvertUInt64ToFloat32 : UnaryOp::ConvertUInt64ToFloat64, type); - else if (match("clz")) makeUnary(type == i32 ? UnaryOp::ClzInt32 : UnaryOp::ClzInt64, type); - else if (match("ctz")) makeUnary(type == i32 ? UnaryOp::CtzInt32 : UnaryOp::CtzInt64, type); - else if (match("copysign")) makeBinary(BINARY_FLOAT(CopySign), type); - else if (match("ceil")) makeUnary(type == f32 ? UnaryOp::CeilFloat32 : UnaryOp::CeilFloat64, type); - else abort_on("type.c"); - break; - } - case 'd': { - if (match("demote/f64")) makeUnary(UnaryOp::DemoteFloat64, type); - else if (match("div_s")) makeBinary(BINARY_INT(DivS), type); - else if (match("div_u")) makeBinary(BINARY_INT(DivU), type); - else if (match("div")) makeBinary(BINARY_FLOAT(Div), type); - else abort_on("type.g"); - break; - } - case 'e': { - if (match("eqz")) makeUnary(type == i32 ? UnaryOp::EqZInt32 : UnaryOp::EqZInt64, type); - else if (match("eq")) makeBinary(BINARY_INT_OR_FLOAT(Eq), i32); - else if (match("extend_s/i32")) makeUnary(UnaryOp::ExtendSInt32, type); - else if (match("extend_u/i32")) makeUnary(UnaryOp::ExtendUInt32, type); - else abort_on("type.e"); - break; - } - case 'f': { - if (match("floor")) makeUnary(type == f32 ? UnaryOp::FloorFloat32 : UnaryOp::FloorFloat64, type); - else abort_on("type.e"); - break; - } - case 'g': { - if (match("gt_s")) makeBinary(BINARY_INT(GtS), i32); - else if (match("gt_u")) makeBinary(BINARY_INT(GtU), i32); - else if (match("ge_s")) makeBinary(BINARY_INT(GeS), i32); - else if (match("ge_u")) makeBinary(BINARY_INT(GeU), i32); - else if (match("gt")) makeBinary(BINARY_FLOAT(Gt), i32); - else if (match("ge")) makeBinary(BINARY_FLOAT(Ge), i32); - else abort_on("type.g"); - break; - } - case 'l': { - if (match("lt_s")) makeBinary(BINARY_INT(LtS), i32); - else if (match("lt_u")) makeBinary(BINARY_INT(LtU), i32); - else if (match("le_s")) makeBinary(BINARY_INT(LeS), i32); - else if (match("le_u")) makeBinary(BINARY_INT(LeU), i32); - else if (match("load")) makeLoad(type); - else if (match("lt")) makeBinary(BINARY_FLOAT(Lt), i32); - else if (match("le")) makeBinary(BINARY_FLOAT(Le), i32); - else abort_on("type.g"); - break; - } - case 'm': { - if (match("mul")) makeBinary(BINARY_INT_OR_FLOAT(Mul), type); - else if (match("min")) makeBinary(BINARY_FLOAT(Min), type); - else if (match("max")) makeBinary(BINARY_FLOAT(Max), type); - else abort_on("type.m"); - break; - } - case 'n': { - if (match("neg")) makeUnary(type == f32 ? UnaryOp::NegFloat32 : UnaryOp::NegFloat64, type); - else if (match("nearest")) makeUnary(type == f32 ? UnaryOp::NearestFloat32 : UnaryOp::NearestFloat64, type); - else if (match("ne")) makeBinary(BINARY_INT_OR_FLOAT(Ne), i32); - else abort_on("type.n"); - break; - } - case 'o': { - if (match("or")) makeBinary(BINARY_INT(Or), type); - else abort_on("type.o"); - break; - } - case 'p': { - if (match("promote/f32")) makeUnary(UnaryOp::PromoteFloat32, type); - else if (match("popcnt")) makeUnary(type == i32 ? UnaryOp::PopcntInt32 : UnaryOp::PopcntInt64, type); - else abort_on("type.p"); - break; - } - case 'r': { - if (match("rem_s")) makeBinary(BINARY_INT(RemS), type); - else if (match("rem_u")) makeBinary(BINARY_INT(RemU), type); - else if (match("reinterpret/i32")) makeUnary(UnaryOp::ReinterpretInt32, type); - else if (match("reinterpret/i64")) makeUnary(UnaryOp::ReinterpretInt64, type); - else if (match("reinterpret/f32")) makeUnary(UnaryOp::ReinterpretFloat32, type); - else if (match("reinterpret/f64")) makeUnary(UnaryOp::ReinterpretFloat64, type); - else if (match("rotl")) makeBinary(BINARY_INT(RotL), type); - else if (match("rotr")) makeBinary(BINARY_INT(RotR), type); - else abort_on("type.r"); - break; - } - case 's': { - if (match("shr_s")) makeBinary(BINARY_INT(ShrS), type); - else if (match("shr_u")) makeBinary(BINARY_INT(ShrU), type); - else if (match("shl")) makeBinary(BINARY_INT(Shl), type); - else if (match("sub")) makeBinary(BINARY_INT_OR_FLOAT(Sub), type); - else if (peek("store")) makeStore(type); - else if (match("select")) makeSelect(type); - else if (match("sqrt")) makeUnary(type == f32 ? UnaryOp::SqrtFloat32 : UnaryOp::SqrtFloat64, type); - else abort_on("type.s"); - break; - } - case 't': { - if (match("trunc_s/f32")) makeUnary(type == i32 ? UnaryOp::TruncSFloat32ToInt32 : UnaryOp::TruncSFloat32ToInt64, type); - else if (match("trunc_u/f32")) makeUnary(type == i32 ? UnaryOp::TruncUFloat32ToInt32 : UnaryOp::TruncUFloat32ToInt64, type); - else if (match("trunc_s/f64")) makeUnary(type == i32 ? UnaryOp::TruncSFloat64ToInt32 : UnaryOp::TruncSFloat64ToInt64, type); - else if (match("trunc_u/f64")) makeUnary(type == i32 ? UnaryOp::TruncUFloat64ToInt32 : UnaryOp::TruncUFloat64ToInt64, type); - else if (match("trunc")) makeUnary(type == f32 ? UnaryOp::TruncFloat32 : UnaryOp::TruncFloat64, type); - else abort_on("type.t"); - break; - } - case 'w': { - if (match("wrap/i64")) makeUnary(UnaryOp::WrapInt64, type); - else abort_on("type.w"); - break; - } - case 'x': { - if (match("xor")) makeBinary(BINARY_INT(Xor), type); - else abort_on("type.x"); - break; - } - default: abort_on("type.?"); - } - }; - // labels - size_t nextLabel = 0; - auto getNextLabel = [&nextLabel]() { - return cashew::IString(("label$" + std::to_string(nextLabel++)).c_str(), false); - }; - auto getBranchLabel = [&](uint32_t offset) { - assert(offset < bstack.size()); - Expression* target = bstack[bstack.size() - 1 - offset]; - if (target->is<Block>()) { - return target->cast<Block>()->name; - } else { - return target->cast<Loop>()->name; - } - }; - // main loop - while (1) { - skipWhitespace(); - if (debug) dump("main function loop"); - if (match("i32.")) { - handleTyped(i32); - } else if (match("i64.")) { - handleTyped(i64); - } else if (match("f32.")) { - handleTyped(f32); - } else if (match("f64.")) { - handleTyped(f64); - } else if (match("block")) { - Type blockType = tryGetTypeWithoutNewline(); - auto curr = allocator->alloc<Block>(); - curr->type = blockType; - curr->name = getNextLabel(); - addToBlock(curr); - bstack.push_back(curr); - } else if (match("end_block")) { - auto* block = bstack.back()->cast<Block>(); - block->finalize(block->type); - if (isConcreteType(block->type) && block->list.size() == 0) { - // empty blocks that return a value are not valid, fix that up - block->list.push_back(allocator->alloc<Unreachable>()); - block->finalize(); - } - bstack.pop_back(); - } else if (peek(".LBB")) { - // FIXME legacy tests: it can be leftover from "loop" or "block", but it can be a label too - auto p = s; - while (*p && *p != ':' && *p != '#' && *p != '\n') p++; - if (*p == ':') { // it's a label - recordLabel(); - } else s = strchr(s, '\n'); - } else if (match("loop")) { - Type loopType = tryGetTypeWithoutNewline(); - auto curr = allocator->alloc<Loop>(); - addToBlock(curr); - curr->type = loopType; - curr->name = getNextLabel(); - auto implicitBlock = allocator->alloc<Block>(); - curr->body = implicitBlock; - implicitBlock->type = loopType; - bstack.push_back(curr); - } else if (match("end_loop")) { - auto* loop = bstack.back()->cast<Loop>(); - bstack.pop_back(); - loop->body->cast<Block>()->finalize(); - loop->finalize(loop->type); - } else if (match("br_table")) { - auto curr = allocator->alloc<Switch>(); - curr->condition = getInput(); - while (skipComma()) { - curr->targets.push_back(getBranchLabel(getInt())); - } - assert(curr->targets.size() > 0); - curr->default_ = curr->targets.back(); - curr->targets.pop_back(); - addToBlock(curr); - } else if (match("br")) { - auto curr = allocator->alloc<Break>(); - bool hasCondition = false; - if (*s == '_') { - mustMatch("_if"); - hasCondition = true; - } - curr->name = getBranchLabel(getInt()); - if (hasCondition) { - skipComma(); - curr->condition = getInput(); - } - curr->finalize(); - addToBlock(curr); - } else if (match("call")) { - makeCall(none); - } else if (match("copy_local")) { - Name assign = getAssign(); - skipComma(); - setOutput(getInput(), assign); - } else if (match("tee_local")) { - Name assign = getAssign(); - skipComma(); - auto curr = allocator->alloc<SetLocal>(); - curr->index = func->getLocalIndex(getAssign()); - skipComma(); - curr->value = getInput(); - curr->setTee(true); - setOutput(curr, assign); - } else if (match("return")) { - addToBlock(builder.makeReturn(*s == '$' ? getInput() : nullptr)); - } else if (match("unreachable")) { - addToBlock(allocator->alloc<Unreachable>()); - } else if (match("current_memory")) { - makeHost(CurrentMemory); - } else if (match("grow_memory")) { - makeHost1(GrowMemory); - } else if (peek(".Lfunc_end")) { - // TODO fix handwritten tests to have .endfunc - recordLabel(); - // skip the next line, which has a .size we can ignore - s = strstr(s, ".size"); - s = strchr(s, '\n'); - break; // the function is done - } else if (match(".endfunc")) { - skipWhitespace(); - // getting all labels at the end of function - while (peek(".L") && strchr(s, ':') < strchr(s, '\n')) { - recordLabel(); - skipWhitespace(); - } - // skip the next line, which has a .size we can ignore - s = strstr(s, ".size"); - s = strchr(s, '\n'); - break; // the function is done - } else if (match(".file")) { - parseFile(); - } else if (match(".loc")) { - recordLoc(); - } else if (peek(".L") && strchr(s, ':') < strchr(s, '\n')) { - recordLabel(); - } else { - abort_on("function element"); - } - } - if (!estack.empty()) { - addToBlock(estack.back()); - estack.pop_back(); - } - // finishing touches - bstack.back()->cast<Block>()->finalize(); - bstack.pop_back(); // remove the base block for the function body - assert(bstack.empty()); - assert(estack.empty()); - func->body->cast<Block>()->finalize(); - wasm->addFunction(func); - } - - void parseType() { - if (debug) dump("type"); - Name name = getStrToSep(); - skipComma(); - if (match("@function")) { - if (match(".hidden")) mustMatch(name.str); - return parseFunction(); - } else if (match("@object")) { - return parseObject(name); - } - abort_on("parseType"); - } - - void parseObject(Name name) { - if (debug) std::cerr << "parseObject " << name << '\n'; - if (match(".data") || match(".bss")) { - } else if (match(".section")) { - s = strchr(s, '\n'); - } else if (match(".lcomm")) { - parseLcomm(name); - return; - } - skipWhitespace(); - Address align = 4; // XXX default? - if (match(".globl")) { - mustMatch(name.str); - skipWhitespace(); - } - if (match(".align") || match(".p2align")) { - align = getInt(); - skipWhitespace(); - } - align = (Address)1 << align; // convert from power to actual bytes - if (match(".lcomm")) { - parseLcomm(name, align); - return; - } - mustMatch(name.str); - mustMatch(":"); - std::vector<char> raw; - bool zero = true; - std::vector<std::pair<LinkerObject::Relocation*, Address>> currRelocations; // [relocation, offset in raw] - while (1) { - skipWhitespace(); - if (match(".asci")) { - bool z; - if (match("i")) { - z = false; - } else { - mustMatch("z"); - z = true; - } - auto quoted = getQuoted(); - raw.insert(raw.end(), quoted.begin(), quoted.end()); - if (z) raw.push_back(0); - zero = false; - } else if (match(".zero") || match(".skip")) { - Address size = getInt(); - if (size <= 0) { - abort_on(".zero with zero or negative size"); - } - unsigned char value = 0; - if (skipComma()) { - value = getInt(); - if (value != 0) zero = false; - } - for (Address i = 0, e = size; i < e; ++i) { - raw.push_back(value); - } - } else if (match(".int8")) { - Address size = raw.size(); - raw.resize(size + 1); - (*(int8_t*)(&raw[size])) = getInt(); - zero = false; - } else if (match(".int16")) { - Address size = raw.size(); - raw.resize(size + 2); - int16_t val = getInt(); - memcpy(&raw[size], &val, sizeof(val)); - zero = false; - } else if (match(".int32")) { - Address size = raw.size(); - raw.resize(size + 4); - auto relocation = getRelocatableConst((uint32_t*)&raw[size]); // just the size, as we may reallocate; we must fix this later, if it's a relocation - if (relocation) { - if (!linkerObj->isObjectImplemented(relocation->symbol)) { - abort_on("s2wasm is currently unable to model imported globals in data segment initializers"); - } - linkerObj->addRelocation(relocation); - currRelocations.emplace_back(relocation, size); - } - zero = false; - } else if (match(".int64")) { - Address size = raw.size(); - raw.resize(size + 8); - int64_t val = getInt64(); - memcpy(&raw[size], &val, sizeof(val)); - zero = false; - } else { - break; - } - } - skipWhitespace(); - Address size = raw.size(); - if (match(".size")) { - mustMatch(name.str); - mustMatch(","); - Address seenSize = atoi(getStr().str); // TODO: optimize - assert(seenSize >= size); - while (raw.size() < seenSize) { - raw.push_back(0); - } - size = seenSize; - } - // raw is now finalized, prepare relocations - for (auto& curr : currRelocations) { - auto* r = curr.first; - auto i = curr.second; - r->data = (uint32_t*)&raw[i]; - } - // assign the address, add to memory - linkerObj->addStatic(size, align, name); - if (!zero) { - linkerObj->addSegment(name, raw); - } - } - - void parseLcomm(Name name, Address align=1) { - mustMatch(name.str); - skipComma(); - Address size = getInt(); - Address localAlign = 1; - if (*s == ',') { - skipComma(); - localAlign = Address(1) << getInt(); - } - linkerObj->addStatic(size, std::max(align, localAlign), name); - } - - void skipImports() { - while (1) { - if (match(".import")) { - s = strchr(s, '\n'); - skipWhitespace(); - continue; - } - break; - } - } - - // This version only converts emscripten_longjmp_jmpbuf and does not deal - // with invoke wrappers. This is used when we only have a function name as - // relocatable constant. - static Name fixEmLongjmp(const Name &name) { - if (name == "emscripten_longjmp_jmpbuf") - return "emscripten_longjmp"; - return name; - } -}; - -} // namespace wasm - -#endif // wasm_s2wasm_h diff --git a/src/tools/s2wasm.cpp b/src/tools/s2wasm.cpp deleted file mode 100644 index dc6b1f38f..000000000 --- a/src/tools/s2wasm.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2015 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// s2wasm console tool -// - -#include <exception> - -#include "ir/trapping.h" -#include "support/colors.h" -#include "support/command-line.h" -#include "support/file.h" -#include "s2wasm.h" -#include "wasm-emscripten.h" -#include "wasm-io.h" -#include "wasm-linker.h" -#include "wasm-printing.h" -#include "wasm-validator.h" - -using namespace cashew; -using namespace wasm; - -int main(int argc, const char *argv[]) { - bool ignoreUnknownSymbols = false; - bool generateEmscriptenGlue = false; - bool allowMemoryGrowth = false; - bool importMemory = false; - bool emitBinary = false; - bool debugInfo = false; - std::string startFunction; - std::string sourceMapFilename; - std::string sourceMapUrl; - std::string symbolMap; - std::vector<std::string> archiveLibraries; - TrapMode trapMode = TrapMode::Allow; - unsigned numReservedFunctionPointers = 0; - Options options("s2wasm", "Link .s file into .wast"); - options.extra["validate"] = "wasm"; - options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--ignore-unknown", "", "Ignore unknown symbols", - Options::Arguments::Zero, - [&ignoreUnknownSymbols](Options *, const std::string& ) { - ignoreUnknownSymbols = true; - }) - .add("--start", "", "Generate the start method (default: main)", - Options::Arguments::Optional, - [&startFunction](Options *, const std::string& argument) { - startFunction = argument.size() ? argument : "main"; - }) - .add("--global-base", "", "Where to start to place globals", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["global-base"] = argument; - }) - .add("--allocate-stack", "-s", "Size of the user stack in linear memory", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["stack-allocation"] = argument; - }) - .add("--initial-memory", "-i", "Initial size of the linear memory", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["initial-memory"] = argument; - }) - .add("--max-memory", "-m", "Maximum size of the linear memory", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["max-memory"] = argument; - }) - .add("--allow-memory-growth", "", "Allow linear memory to grow at runtime", - Options::Arguments::Zero, - [&allowMemoryGrowth](Options *, const std::string& ) { - allowMemoryGrowth = true; - }) - .add("--trap-mode", "", - "Strategy for handling potentially trapping instructions. Valid " - "values are \"allow\", \"js\", and \"clamp\"", - Options::Arguments::One, - [&trapMode](Options *o, const std::string& argument) { - try { - trapMode = trapModeFromString(argument); - } catch (std::invalid_argument& e) { - std::cerr << "Error: " << e.what() << "\n"; - exit(EXIT_FAILURE); - } - }) - .add("--emscripten-glue", "-e", "Generate emscripten glue", - Options::Arguments::Zero, - [&generateEmscriptenGlue](Options *, const std::string& ) { - generateEmscriptenGlue = true; - }) - .add("--import-memory", "", "Import the linear memory instead of exporting it", - Options::Arguments::Zero, - [&importMemory](Options *, const std::string& ) { - importMemory = true; - }) - .add("--library", "-l", "Add archive library", - Options::Arguments::N, - [&archiveLibraries](Options *o, const std::string& argument) { - archiveLibraries.push_back(argument); - }) - .add("--validate", "-v", "Control validation of the output module", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - if (argument != "web" && argument != "none" && argument != "wasm") { - std::cerr << "Valid arguments for --validate flag are 'wasm', 'web' and 'none'.\n"; - exit(1); - } - o->extra["validate"] = argument; - }) - .add("--emscripten-reserved-function-pointers", "", - "Number of reserved function pointers for emscripten addFunction " - "support", - Options::Arguments::One, - [&numReservedFunctionPointers](Options *o, - const std::string &argument) { - numReservedFunctionPointers = std::stoi(argument); - }) - .add("--emit-binary", "", - "Emit binary instead of text for the output file", - Options::Arguments::Zero, - [&emitBinary](Options *, const std::string &) { - emitBinary = true; - }) - .add("--debuginfo", "-g", - "Emit names section in wasm binary (or full debuginfo in wast)", - Options::Arguments::Zero, - [&debugInfo](Options *, const std::string &) { - debugInfo = true; - }) - .add("--source-map", "-sm", - "Emit source map (if using binary output) to the specified file", - Options::Arguments::One, - [&sourceMapFilename](Options *, const std::string& argument) { - sourceMapFilename = argument; - }) - .add("--source-map-url", "-su", - "Use specified string as source map URL", - Options::Arguments::One, - [&sourceMapUrl](Options *, const std::string& argument) { - sourceMapUrl = argument; - }) - .add("--symbolmap", "-s", - "Emit a symbol map (indexes => names)", - Options::Arguments::One, - [&symbolMap](Options *, const std::string& argument) { - symbolMap = argument; - }) - .add_positional("INFILE", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["infile"] = argument; - }); - options.parse(argc, argv); - - if (options.extra["output"].size() == 0) { - // when no output file is specified, we emit text to stdout - emitBinary = false; - } - - if (allowMemoryGrowth && !generateEmscriptenGlue) { - Fatal() << "Error: adding memory growth code without Emscripten glue. " - "This doesn't do anything.\n"; - } - - auto debugFlag = options.debug ? Flags::Debug : Flags::Release; - auto input(read_file<std::string>(options.extra["infile"], Flags::Text, debugFlag)); - - if (options.debug) std::cerr << "Parsing and wasming..." << std::endl; - uint64_t globalBase = options.extra.find("global-base") != options.extra.end() - ? std::stoull(options.extra["global-base"]) - : 0; - uint64_t stackAllocation = - options.extra.find("stack-allocation") != options.extra.end() - ? std::stoull(options.extra["stack-allocation"]) - : 0; - uint64_t initialMem = - options.extra.find("initial-memory") != options.extra.end() - ? std::stoull(options.extra["initial-memory"]) - : 0; - uint64_t maxMem = - options.extra.find("max-memory") != options.extra.end() - ? std::stoull(options.extra["max-memory"]) - : 0; - if (options.debug) std::cerr << "Global base " << globalBase << '\n'; - - Linker linker(globalBase, stackAllocation, initialMem, maxMem, - importMemory || generateEmscriptenGlue, ignoreUnknownSymbols, startFunction, - options.debug); - - S2WasmBuilder mainbuilder(input.c_str(), options.debug); - linker.linkObject(mainbuilder); - - if (trapMode != TrapMode::Allow) { - Module* wasm = &(linker.getOutput().wasm); - PassRunner runner(wasm); - addTrapModePass(runner, trapMode); - runner.run(); - } - - for (const auto& m : archiveLibraries) { - auto archiveFile(read_file<std::vector<char>>(m, Flags::Binary, debugFlag)); - bool error; - Archive lib(archiveFile, error); - if (error) Fatal() << "Error opening archive " << m << "\n"; - linker.linkArchive(lib); - } - - linker.layout(); - - std::string metadata; - Module& wasm = linker.getOutput().wasm; - if (generateEmscriptenGlue) { - if (options.debug) { - std::cerr << "Emscripten gluing..." << std::endl; - WasmPrinter::printModule(&wasm, std::cerr); - } - metadata = emscriptenGlue( - wasm, - allowMemoryGrowth, - linker.getStackPointerAddress(), - linker.getStaticBump(), - linker.getOutput().getInitializerFunctions(), - numReservedFunctionPointers); - } - - if (options.extra["validate"] != "none") { - if (options.debug) std::cerr << "Validating..." << std::endl; - if (!wasm::WasmValidator().validate(wasm, - WasmValidator::Globally | (options.extra["validate"] == "web" ? WasmValidator::Web : 0))) { - WasmPrinter::printModule(&wasm); - Fatal() << "Error: linked module is not valid.\n"; - } - } - - if (options.debug) std::cerr << "Printing..." << std::endl; - auto outputDebugFlag = options.debug ? Flags::Debug : Flags::Release; - auto outputBinaryFlag = emitBinary ? Flags::Binary : Flags::Text; - Output output(options.extra["output"], outputBinaryFlag, outputDebugFlag); - - ModuleWriter writer; - writer.setDebug(options.debug); - writer.setDebugInfo(debugInfo); - writer.setSymbolMap(symbolMap); - writer.setBinary(emitBinary); - if (emitBinary) { - writer.setSourceMapFilename(sourceMapFilename); - writer.setSourceMapUrl(sourceMapUrl); - } - writer.write(wasm, output); - - if (generateEmscriptenGlue) { - if (emitBinary) { - std::cout << metadata; - } else { - output << ";; METADATA: " << metadata; - } - } - - if (options.debug) std::cerr << "Done." << std::endl; - return 0; -} diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index f69cc6429..4207983ee 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -28,7 +28,6 @@ #include "wasm-binary.h" #include "wasm-emscripten.h" #include "wasm-io.h" -#include "wasm-linker.h" #include "wasm-printing.h" #include "wasm-validator.h" diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h index a0a87664b..21bd21bea 100644 --- a/src/wasm-emscripten.h +++ b/src/wasm-emscripten.h @@ -67,14 +67,6 @@ private: void generateStackRestoreFunction(); }; -std::string emscriptenGlue( - Module& wasm, - bool allowMemoryGrowth, - Address stackPointer, - Address staticBump, - std::vector<Name> const& initializerFunctions, - unsigned numReservedFunctionPointers); - } // namespace wasm #endif // wasm_wasm_emscripten_h diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp deleted file mode 100644 index df51d85c6..000000000 --- a/src/wasm-linker.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright 2016 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "wasm-linker.h" -#include "asm_v_wasm.h" -#include "ir/utils.h" -#include "s2wasm.h" -#include "support/utilities.h" -#include "wasm-builder.h" -#include "wasm-emscripten.h" -#include "wasm-printing.h" - -using namespace wasm; - -// Name of the dummy function to prevent erroneous nullptr comparisons. -static constexpr const char* dummyFunction = "__wasm_nullptr"; -static constexpr const char* stackPointer = "__stack_pointer"; - -void Linker::placeStackPointer(Address stackAllocation) { - // ensure this is the first allocation - assert(nextStatic == globalBase || nextStatic == 1); - const Address pointerSize = 4; - // Unconditionally allocate space for the stack pointer. Emscripten - // allocates the stack itself, and initializes the stack pointer itself. - out.addStatic(pointerSize, pointerSize, stackPointer); - if (stackAllocation) { - // If we are allocating the stack, set up a relocation to initialize the - // stack pointer to point to one past-the-end of the stack allocation. - std::vector<char> raw; - raw.resize(pointerSize); - auto relocation = new LinkerObject::Relocation( - LinkerObject::Relocation::kData, (uint32_t*)&raw[0], ".stack", stackAllocation); - out.addRelocation(relocation); - assert(out.wasm.memory.segments.empty()); - out.addSegment(stackPointer, raw); - } -} - -void Linker::ensureFunctionImport(Name target, std::string signature) { - if (!out.wasm.getImportOrNull(target)) { - auto import = new Import; - import->name = import->base = target; - import->module = ENV; - import->functionType = ensureFunctionType(signature, &out.wasm)->name; - import->kind = ExternalKind::Function; - out.wasm.addImport(import); - } -} - -void Linker::ensureObjectImport(Name target) { - if (!out.wasm.getImportOrNull(target)) { - auto import = new Import; - import->name = import->base = target; - import->module = ENV; - import->kind = ExternalKind::Global; - import->globalType = i32; - out.wasm.addImport(import); - } -} - -void Linker::layout() { - // Convert calls to undefined functions to call_imports - for (const auto& f : out.undefinedFunctionCalls) { - Name target = f.first; - if (!out.symbolInfo.undefinedFunctions.count(target)) continue; - // Create an import for the target if necessary. - ensureFunctionImport(target, getSig(*f.second.begin())); - // Change each call. The target is the same since it's still the name. - // Delete and re-allocate the Expression as CallImport to avoid undefined - // behavior. - for (auto* call : f.second) { - auto type = call->type; - ExpressionList operands(out.wasm.allocator); - operands.swap(call->operands); - auto target = call->target; - CallImport* newCall = ExpressionManipulator::convert<Call, CallImport>(call, out.wasm.allocator); - newCall->type = type; - newCall->operands.swap(operands); - newCall->target = target; - } - } - - // Allocate all user statics - for (const auto& obj : out.staticObjects) { - allocateStatic(obj.allocSize, obj.alignment, obj.name); - } - - // Update the segments with their addresses now that they have been allocated. - for (const auto& seg : out.segments) { - Address address = staticAddresses[seg.first]; - out.wasm.memory.segments[seg.second].offset = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(address))); - segmentsByAddress[address] = seg.second; - } - - // Place the stack after the user's static data, to keep those addresses - // small. - if (stackAllocation) allocateStatic(stackAllocation, 16, ".stack"); - - // The minimum initial memory size is the amount of static variables we have - // allocated. Round it up to a page, and update the page-increment versions - // of initial and max - Address initialMem = roundUpToPageSize(nextStatic); - if (userInitialMemory) { - if (initialMem > userInitialMemory) { - Fatal() << "Specified initial memory size " << userInitialMemory << - " is smaller than required size " << initialMem; - } - out.wasm.memory.initial = userInitialMemory / Memory::kPageSize; - } else { - out.wasm.memory.initial = initialMem / Memory::kPageSize; - } - out.wasm.memory.exists = true; - - if (userMaxMemory) out.wasm.memory.max = userMaxMemory / Memory::kPageSize; - - if (importMemory) { - auto memoryImport = make_unique<Import>(); - memoryImport->name = MEMORY; - memoryImport->module = ENV; - memoryImport->base = MEMORY; - memoryImport->kind = ExternalKind::Memory; - out.wasm.memory.imported = true; - out.wasm.addImport(memoryImport.release()); - } else { - auto memoryExport = make_unique<Export>(); - memoryExport->name = MEMORY; - memoryExport->value = Name::fromInt(0); - memoryExport->kind = ExternalKind::Memory; - out.wasm.addExport(memoryExport.release()); - } - - // Add imports for any imported objects - for (const auto& obj : out.symbolInfo.importedObjects) { - ensureObjectImport(obj); - } - - // XXX For now, export all functions marked .globl. - for (Name name : out.globls) exportFunction(out.wasm, name, false); - for (Name name : out.initializerFunctions) exportFunction(out.wasm, name, true); - - // Pad the indirect function table with a dummy function - makeDummyFunction(); - // Always create a table, even if it's empty. - out.wasm.table.exists = true; - - // Pre-assign the function indexes - for (auto& pair : out.indirectIndexes) { - if (functionIndexes.count(pair.first) != 0 || - functionNames.count(pair.second) != 0) { - Fatal() << "Function " << pair.first << " already has an index " << - functionIndexes[pair.first] << " while setting index " << pair.second; - } - if (debug) { - std::cerr << "pre-assigned function index: " << pair.first << ": " - << pair.second << '\n'; - } - functionIndexes[pair.first] = pair.second; - functionNames[pair.second] = pair.first; - } - - // Emit the pre-assigned function names in sorted order - for (const auto& P : functionNames) { - ensureTableSegment(); - getTableDataRef().push_back(P.second); - } - - for (auto& relocation : out.relocations) { - // TODO: Handle weak symbols properly, instead of always taking the weak definition. - auto *alias = out.getAlias(relocation->symbol, relocation->kind); - Name name = relocation->symbol; - - if (debug) std::cerr << "fix relocation " << name << '\n'; - - if (alias) { - name = alias->symbol; - relocation->addend += alias->offset; - } - - if (relocation->kind == LinkerObject::Relocation::kData) { - const auto& symbolAddress = staticAddresses.find(name); - if (symbolAddress == staticAddresses.end()) Fatal() << "Unknown relocation: " << name << '\n'; - *(relocation->data) = symbolAddress->second + relocation->addend; - if (debug) std::cerr << " ==> " << *(relocation->data) << '\n'; - } else { - // function address - if (!out.wasm.getFunctionOrNull(name)) { - if (FunctionType* f = out.getExternType(name)) { - // Address of an imported function is taken, but imports do not have addresses in wasm. - // Generate a thunk to forward to the call_import. - Function* thunk = getImportThunk(name, f); - *(relocation->data) = getFunctionIndex(thunk->name) + relocation->addend; - } else { - std::cerr << "Unknown symbol: " << name << '\n'; - if (!ignoreUnknownSymbols) Fatal() << "undefined reference\n"; - *(relocation->data) = 0; - } - } else { - *(relocation->data) = getFunctionIndex(name) + relocation->addend; - } - } - } - out.relocations.clear(); - - if (!!startFunction) { - if (out.symbolInfo.implementedFunctions.count(startFunction) == 0) { - Fatal() << "Unknown start function: `" << startFunction << "`\n"; - } - const auto *target = out.wasm.getFunction(startFunction); - Name start("_start"); - if (out.symbolInfo.implementedFunctions.count(start) != 0) { - Fatal() << "Start function already present: `" << start << "`\n"; - } - auto* func = new Function; - func->name = start; - out.wasm.addFunction(func); - out.wasm.addStart(start); - Builder builder(out.wasm); - auto* block = builder.makeBlock(); - func->body = block; - { - // Create the call, matching its parameters. - // TODO allow calling with non-default values. - std::vector<Expression*> args; - Index paramNum = 0; - for (Type type : target->params) { - Name name = Name::fromInt(paramNum++); - Builder::addVar(func, name, type); - auto* param = builder.makeGetLocal(func->getLocalIndex(name), type); - args.push_back(param); - } - auto* call = builder.makeCall(startFunction, args, target->result); - Expression* e = call; - if (target->result != none) e = builder.makeDrop(call); - block->list.push_back(e); - block->finalize(); - } - } - - // ensure an explicit function type for indirect call targets - for (auto& segment : out.wasm.table.segments) { - for (auto& name : segment.data) { - auto* func = out.wasm.getFunction(name); - func->type = ensureFunctionType(getSig(func), &out.wasm)->name; - } - } - - // Export malloc/realloc/free/memalign whenever availble. JavsScript version of - // malloc has some issues and it cannot be called once _sbrk() is called, and - // JS glue code does not have realloc(). TODO This should get the list of - // exported functions from emcc.py - it has EXPORTED_FUNCTION metadata to keep - // track of this. Get the list of exported functions using a command-line - // argument from emcc.py and export all of them. - for (auto function : {"malloc", "free", "realloc", "memalign"}) { - if (out.symbolInfo.implementedFunctions.count(function)) { - exportFunction(out.wasm, function, true); - } - } - - // finalize function table - unsigned int tableSize = getTableData().size(); - if (tableSize > 0) { - out.wasm.table.initial = out.wasm.table.max = tableSize; - } -} - -bool Linker::linkObject(S2WasmBuilder& builder) { - LinkerObject::SymbolInfo *newSymbols = builder.getSymbolInfo(); - // check for multiple definitions - for (const Name& symbol : newSymbols->implementedFunctions) { - if (out.symbolInfo.implementedFunctions.count(symbol)) { - // TODO: Figure out error handling for library-style pieces - // TODO: give LinkerObjects (or builders) names for better errors. - std::cerr << "Error: multiple definition of symbol " << symbol << "\n"; - return false; - } - } - // Allow duplicate aliases only if they refer to the same name. For now we - // do not expect aliases in compiler-rt files. - // TODO: figure out what the semantics of merging aliases should be. - for (const auto& alias : newSymbols->aliasedSymbols) { - if (out.symbolInfo.aliasedSymbols.count(alias.first) && - (out.symbolInfo.aliasedSymbols.at(alias.first).symbol != alias.second.symbol || - out.symbolInfo.aliasedSymbols.at(alias.first).kind != alias.second.kind)) { - std::cerr << "Error: conflicting definitions for alias " - << alias.first.c_str() << "of type " << alias.second.kind << "\n"; - return false; - } - } - out.symbolInfo.merge(*newSymbols); - builder.build(&out); - return true; -} - -bool Linker::linkArchive(Archive& archive) { - bool selected; - do { - selected = false; - for (auto child = archive.child_begin(), end = archive.child_end(); - child != end; ++child) { - Archive::SubBuffer memberBuf = child->getBuffer(); - // S2WasmBuilder expects its input to be NUL-terminated. Archive members - // are - // not NUL-terminated. So we have to copy the contents out before parsing. - std::vector<char> memberString(memberBuf.len + 1); - memcpy(memberString.data(), memberBuf.data, memberBuf.len); - memberString[memberBuf.len] = '\0'; - S2WasmBuilder memberBuilder(memberString.data(), false); - auto* memberSymbols = memberBuilder.getSymbolInfo(); - for (const Name& symbol : memberSymbols->implementedFunctions) { - if (out.symbolInfo.undefinedFunctions.count(symbol)) { - if (!linkObject(memberBuilder)) return false; - selected = true; - break; - } - } - } - // If we selected an archive member, it may depend on another archive member - // so continue to make passes over the members until no more are added. - } while (selected); - return true; -} - -Address Linker::getStaticBump() const { - return nextStatic - globalBase; -} - -void Linker::ensureTableSegment() { - if (out.wasm.table.segments.size() == 0) { - auto emptySegment = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))); - out.wasm.table.segments.emplace_back(emptySegment); - } -} - -std::vector<Name>& Linker::getTableDataRef() { - assert(out.wasm.table.segments.size() == 1); - return out.wasm.table.segments[0].data; -} - -std::vector<Name> Linker::getTableData() { - if (out.wasm.table.segments.size() > 0) { - return getTableDataRef(); - } - return {}; -} - -Index Linker::getFunctionIndex(Name name) { - if (!functionIndexes.count(name)) { - ensureTableSegment(); - functionIndexes[name] = getTableData().size(); - getTableDataRef().push_back(name); - if (debug) { - std::cerr << "function index: " << name << ": " - << functionIndexes[name] << '\n'; - } - } - return functionIndexes[name]; -} - -void Linker::makeDummyFunction() { - bool create = false; - // Check if there are address-taken functions - for (auto& relocation : out.relocations) { - if (relocation->kind == LinkerObject::Relocation::kFunction) { - create = true; - break; - } - } - - if (!create) return; - wasm::Builder wasmBuilder(out.wasm); - Expression *unreachable = wasmBuilder.makeUnreachable(); - Function *dummy = wasmBuilder.makeFunction( - Name(dummyFunction), - std::vector<Type>{}, - Type::none, - std::vector<Type>{}, - unreachable - ); - out.wasm.addFunction(dummy); - getFunctionIndex(dummy->name); -} - -Function* Linker::getImportThunk(Name name, const FunctionType* funcType) { - Name thunkName = std::string("__importThunk_") + name.c_str(); - if (Function* thunk = out.wasm.getFunctionOrNull(thunkName)) return thunk; - ensureFunctionImport(name, getSig(funcType)); - wasm::Builder wasmBuilder(out.wasm); - std::vector<NameType> params; - Index p = 0; - for (const auto& ty : funcType->params) params.emplace_back(std::to_string(p++), ty); - Function *f = wasmBuilder.makeFunction(thunkName, std::move(params), funcType->result, {}); - std::vector<Expression*> args; - for (Index i = 0; i < funcType->params.size(); ++i) { - args.push_back(wasmBuilder.makeGetLocal(i, funcType->params[i])); - } - Expression* call = wasmBuilder.makeCallImport(name, args, funcType->result); - f->body = call; - out.wasm.addFunction(f); - return f; -} - -Address Linker::getStackPointerAddress() const { - return Address(staticAddresses.at(stackPointer)); -} diff --git a/src/wasm-linker.h b/src/wasm-linker.h deleted file mode 100644 index 89f1cb718..000000000 --- a/src/wasm-linker.h +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright 2016 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// (very) basic linking functionality for s2wasm. -// Performs some of the tasks that will eventually be done by a real linker. -// Currently can allocate static variables and the stack, lay out memory -// and initial segment contents, and process relocations. (In particular, there -// is no merging of multiple modules). Currently this is only inteded to turn -// a .s file produced by LLVM into a usable wast file. - -#ifndef wasm_wasm_linker_h -#define wasm_wasm_linker_h - -#include "support/archive.h" -#include "support/name.h" -#include "support/utilities.h" -#include "wasm.h" - -namespace wasm { - -class S2WasmBuilder; - -inline void exportFunction(Module& wasm, Name name, bool must_export) { - if (!wasm.getFunctionOrNull(name)) { - assert(!must_export); - return; - } - if (wasm.getExportOrNull(name)) return; // Already exported - auto exp = new Export; - exp->name = exp->value = name; - exp->kind = ExternalKind::Function; - wasm.addExport(exp); -} - -// An "object file" for linking. Contains a wasm module, plus the associated -// information needed for linking/layout. -class LinkerObject { - public: - struct Relocation { - enum Kind { kData, kFunction }; - Kind kind; // Whether the symbol refers to data or a function. - // Instead of section offsets as relocation targets, for now this is just - // a pointer to the memory to rewrite. - uint32_t* data; - Name symbol; // Like the symbol index in ELF r_info field - int addend; // Like the ELF r_addend field - Relocation(Kind kind, uint32_t* data, Name symbol, int addend) : - kind(kind), data(data), symbol(symbol), addend(addend) {} - }; - struct SymbolAlias { - Name symbol; - Relocation::Kind kind; - Offset offset; - SymbolAlias(Name symbol, Relocation::Kind kind, Offset offset) : - symbol(symbol), kind(kind), offset(offset) {} - }; - // Information about symbols - struct SymbolInfo { - std::unordered_set<cashew::IString> implementedFunctions; - std::unordered_set<cashew::IString> undefinedFunctions; - std::unordered_set<cashew::IString> importedObjects; - // TODO: it's not clear that this really belongs here. - std::unordered_map<cashew::IString, SymbolAlias> aliasedSymbols; - - // For now, do not support weak symbols or anything special. Just directly - // merge the functions together, and remove any newly-defined functions - // from undefinedFunction - void merge(SymbolInfo& other) { - for (const auto& func : other.implementedFunctions) { - undefinedFunctions.erase(func); - } - implementedFunctions.insert(other.implementedFunctions.begin(), - other.implementedFunctions.end()); - importedObjects.insert(other.importedObjects.begin(), - other.importedObjects.end()); - aliasedSymbols.insert(other.aliasedSymbols.begin(), - other.aliasedSymbols.end()); - } - }; - - LinkerObject() {} - - // Allocate a static object - void addStatic(Address allocSize, Address alignment, Name name) { - staticObjects.emplace_back(allocSize, alignment, name); - } - - void addGlobal(Name name) { - globls.push_back(name); - } - - // This takes ownership of the added Relocation - void addRelocation(Relocation* relocation) { - relocations.emplace_back(relocation); - } - - bool isFunctionImplemented(Name name) { - return symbolInfo.implementedFunctions.count(name) != 0; - } - - // An object is considered implemented if it is not imported - bool isObjectImplemented(Name name) { - return symbolInfo.importedObjects.count(name) == 0; - } - - // If name is an alias, return what it points to. Otherwise return name. - Name resolveAlias(Name name, Relocation::Kind kind) { - auto aliased = symbolInfo.aliasedSymbols.find(name); - if (aliased != symbolInfo.aliasedSymbols.end() && aliased->second.kind == kind) return aliased->second.symbol; - return name; - } - - SymbolAlias *getAlias(Name name, Relocation::Kind kind) { - auto aliased = symbolInfo.aliasedSymbols.find(name); - if (aliased != symbolInfo.aliasedSymbols.end() && aliased->second.kind == kind) return &aliased->second; - return nullptr; - } - - // Add an initializer segment for the named static variable. - void addSegment(Name name, const char* data, Address size) { - segments[name] = wasm.memory.segments.size(); - wasm.memory.segments.emplace_back(wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))), data, size); - } - - void addSegment(Name name, std::vector<char>& data) { - segments[name] = wasm.memory.segments.size(); - wasm.memory.segments.emplace_back(wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))), data); - } - - void addInitializerFunction(Name name) { - initializerFunctions.emplace_back(name); - assert(symbolInfo.implementedFunctions.count(name)); - } - - void addUndefinedFunctionCall(Call* call) { - symbolInfo.undefinedFunctions.insert(call->target); - undefinedFunctionCalls[call->target].push_back(call); - } - - void addExternType(Name name, FunctionType* ty) { - externTypesMap[name] = ty; - } - FunctionType* getExternType(Name name) { - auto f = externTypesMap.find(name); - if (f == externTypesMap.end()) return nullptr; - return f->second; - } - - void addIndirectIndex(Name name, Address index) { - assert(!indirectIndexes.count(name)); - indirectIndexes[name] = index; - } - - bool isEmpty() { - return wasm.functions.empty(); - } - - std::vector<Name> const& getInitializerFunctions() const { - return initializerFunctions; - } - - friend class Linker; - - Module wasm; - - private: - struct StaticObject { - Address allocSize; - Address alignment; - Name name; - StaticObject(Address allocSize, Address alignment, Name name) : - allocSize(allocSize), alignment(alignment), name(name) {} - }; - - std::vector<Name> globls; - - std::vector<StaticObject> staticObjects; - std::vector<std::unique_ptr<Relocation>> relocations; - - SymbolInfo symbolInfo; - - using CallList = std::vector<Call*>; - std::map<Name, CallList> undefinedFunctionCalls; - - // Types of functions which are declared but not defined. - std::unordered_map<cashew::IString, FunctionType*> externTypesMap; - - std::map<Name, Address> segments; // name => segment index (in wasm module) - - // preassigned indexes for functions called indirectly - std::map<Name, Address> indirectIndexes; - - std::vector<Name> initializerFunctions; - - LinkerObject(const LinkerObject&) = delete; - LinkerObject& operator=(const LinkerObject&) = delete; - -}; - -// Class which performs some linker-like functionality; namely taking an object -// file with relocations, laying out the linear memory and segments, and -// applying the relocations, resulting in an executable wasm module. -class Linker { - public: - Linker(Address globalBase, Address stackAllocation, Address userInitialMemory, - Address userMaxMemory, bool importMemory, bool ignoreUnknownSymbols, - Name startFunction, bool debug) - : ignoreUnknownSymbols(ignoreUnknownSymbols), - startFunction(startFunction), - globalBase(globalBase), - nextStatic(globalBase), - userInitialMemory(userInitialMemory), - userMaxMemory(userMaxMemory), - importMemory(importMemory), - stackAllocation(stackAllocation), - debug(debug) { - if (userMaxMemory && userMaxMemory < userInitialMemory) { - Fatal() << "Specified max memory " << userMaxMemory << - " is < specified initial memory " << userInitialMemory; - } - if (roundUpToPageSize(userMaxMemory) != userMaxMemory) { - Fatal() << "Specified max memory " << userMaxMemory << - " is not a multiple of 64k"; - } - if (roundUpToPageSize(userInitialMemory) != userInitialMemory) { - Fatal() << "Specified initial memory " << userInitialMemory << - " is not a multiple of 64k"; - } - - // Don't allow anything to be allocated at address 0 - if (globalBase == 0) nextStatic = 1; - - // Place the stack pointer at the bottom of the linear memory, to keep its - // address small (and thus with a small encoding). - placeStackPointer(stackAllocation); - // Allocate __dso_handle. For asm.js, emscripten provides this in JS, but - // wasm modules can't import data objects. Its value is 0 for the main - // executable, which is all we have with static linking. In the future this - // can go in a crtbegin or similar file. - out.addStatic(4, 4, "__dso_handle"); - } - - // Return a reference to the LinkerObject for the main executable. If empty, - // it can be passed to an S2WasmBuilder and constructed. - LinkerObject& getOutput() { return out; } - - // Allocate the user stack, set up the initial memory size of the module, lay - // out the linear memory, process the relocations, and set up the indirect - // function table. - void layout(); - - // Add an object to the link by constructing it in-place with a builder. - // Returns false if an error occurred. - bool linkObject(S2WasmBuilder& builder); - - // Add an archive to the link. Any objects in the archive that satisfy a - // currently-undefined reference will be added to the link. - // Returns false if an error occurred. - bool linkArchive(Archive& archive); - - // Returns the address of the stack pointer. - Address getStackPointerAddress() const; - - // Returns the total size of all static allocations. - Address getStaticBump() const; - - private: - // Allocate a static variable and return its address in linear memory - Address allocateStatic(Address allocSize, Address alignment, Name name) { - Address address = alignAddr(nextStatic, alignment); - staticAddresses[name] = address; - nextStatic = address + allocSize; - return address; - } - - // Allocate space for a stack pointer and (if stackAllocation > 0) set up a - // relocation for it to point to the top of the stack. - void placeStackPointer(Address stackAllocation); - - void ensureFunctionImport(Name target, std::string signature); - void ensureObjectImport(Name target); - - // Makes sure the table has a single segment, with offset 0, - // to which we can add content. - void ensureTableSegment(); - - std::vector<Name>& getTableDataRef(); - std::vector<Name> getTableData(); - - // Retrieves (and assigns) an entry index in the indirect function table for - // a given function. - Index getFunctionIndex(Name name); - - // Adds a dummy function in the indirect table at slot 0 to prevent NULL - // pointer miscomparisons. - void makeDummyFunction(); - - static Address roundUpToPageSize(Address size) { - return (size + Memory::kPageSize - 1) & Memory::kPageMask; - } - - Function* getImportThunk(Name name, const FunctionType* t); - - // The output module (linked executable) - LinkerObject out; - - bool ignoreUnknownSymbols; - Name startFunction; - - // where globals can start to be statically allocated, i.e., the data segment - Address globalBase; - Address nextStatic; // location of next static allocation - Address userInitialMemory; // Initial memory size (in bytes) specified by the user. - Address userMaxMemory; // Max memory size (in bytes) specified by the user. - //(after linking, this is rounded and set on the wasm object in pages) - bool importMemory; // Whether the memory should be imported instead of - // defined. - Address stackAllocation; - bool debug; - - std::unordered_map<cashew::IString, int32_t> staticAddresses; // name => address - std::unordered_map<Address, Address> segmentsByAddress; // address => segment index - std::unordered_map<cashew::IString, Address> functionIndexes; - std::map<Address, cashew::IString> functionNames; -}; - -} - -#endif // wasm_wasm_linker_h diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index 9236fd24c..c70069e76 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -22,7 +22,6 @@ #include "asmjs/shared-constants.h" #include "shared-constants.h" #include "wasm-builder.h" -#include "wasm-linker.h" #include "wasm-traversal.h" #include "wasm.h" @@ -159,6 +158,18 @@ static bool hasI64ResultOrParam(FunctionType* ft) { return false; } +inline void exportFunction(Module& wasm, Name name, bool must_export) { + if (!wasm.getFunctionOrNull(name)) { + assert(!must_export); + return; + } + if (wasm.getExportOrNull(name)) return; // Already exported + auto exp = new Export; + exp->name = exp->value = name; + exp->kind = ExternalKind::Function; + wasm.addExport(exp); +} + void EmscriptenGlueGenerator::generateDynCallThunks() { std::unordered_set<std::string> sigs; Builder builder(wasm); @@ -768,29 +779,4 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( return meta.str(); } -std::string emscriptenGlue( - Module& wasm, - bool allowMemoryGrowth, - Address stackPointer, - Address staticBump, - std::vector<Name> const& initializerFunctions, - unsigned numReservedFunctionPointers) { - EmscriptenGlueGenerator generator(wasm, stackPointer); - generator.fixInvokeFunctionNames(); - generator.generateRuntimeFunctions(); - - if (allowMemoryGrowth) { - generator.generateMemoryGrowthFunction(); - } - - generator.generateDynCallThunks(); - - if (numReservedFunctionPointers) { - generator.generateJSCallThunks(numReservedFunctionPointers); - } - - return generator.generateEmscriptenMetadata(staticBump, initializerFunctions, - numReservedFunctionPointers); -} - } // namespace wasm |