summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/s2wasm.h1460
-rw-r--r--src/tools/s2wasm.cpp281
-rw-r--r--src/tools/wasm-emscripten-finalize.cpp1
-rw-r--r--src/wasm-emscripten.h8
-rw-r--r--src/wasm-linker.cpp417
-rw-r--r--src/wasm-linker.h342
-rw-r--r--src/wasm/wasm-emscripten.cpp38
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