summaryrefslogtreecommitdiff
path: root/src/s2wasm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/s2wasm.h')
-rw-r--r--src/s2wasm.h1460
1 files changed, 0 insertions, 1460 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