diff options
author | Alon Zakai <alonzakai@gmail.com> | 2016-09-21 19:33:11 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-21 19:33:11 -0700 |
commit | 8be82627c6a8cbded0dab67ad1f31906a54ba78c (patch) | |
tree | b7b14f1899ffe39a7562007474a58948685146c8 /src | |
parent | 7292ef9c863a0766c697cc0a77516447ff652820 (diff) | |
parent | 740e36eab98d679387fea60cd642591a69ce809f (diff) | |
download | binaryen-8be82627c6a8cbded0dab67ad1f31906a54ba78c.tar.gz binaryen-8be82627c6a8cbded0dab67ad1f31906a54ba78c.tar.bz2 binaryen-8be82627c6a8cbded0dab67ad1f31906a54ba78c.zip |
Merge pull request #703 from WebAssembly/spec-update
Spec update - get us passing the 0xc spec tests (minus stacky stuff)
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 12 | ||||
-rw-r--r-- | src/asm_v_wasm.h | 10 | ||||
-rw-r--r-- | src/binaryen-c.cpp | 1 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 23 | ||||
-rw-r--r-- | src/passes/Print.cpp | 55 | ||||
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 7 | ||||
-rw-r--r-- | src/shared-constants.h | 5 | ||||
-rw-r--r-- | src/shell-interface.h | 22 | ||||
-rw-r--r-- | src/tools/wasm-shell.cpp | 116 | ||||
-rw-r--r-- | src/wasm-binary.h | 147 | ||||
-rw-r--r-- | src/wasm-builder.h | 6 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 36 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 486 | ||||
-rw-r--r-- | src/wasm-validator.h | 13 | ||||
-rw-r--r-- | src/wasm.cpp | 5 | ||||
-rw-r--r-- | src/wasm.h | 54 |
16 files changed, 700 insertions, 298 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index bce67a582..1c528d7f3 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -177,6 +177,7 @@ private: else if (type == f64) value = Literal(double(0)); else WASM_UNREACHABLE(); global->init = wasm.allocator.alloc<Const>()->set(value); + global->mutable_ = true; wasm.addGlobal(global); } @@ -502,9 +503,20 @@ void Asm2WasmBuilder::processAsm(Ref ast) { type = WasmType::f64; } if (type != WasmType::none) { + // we need imported globals to be mutable, but wasm doesn't support that yet, so we must + // import an immutable and create a mutable global initialized to its value + import->name = Name(std::string(import->name.str) + "$asm2wasm$import"); import->kind = Import::Global; import->globalType = type; mappedGlobals.emplace(name, type); + { + auto global = new Global(); + global->name = name; + global->type = type; + global->init = builder.makeGetGlobal(import->name, type); + global->mutable_ = true; + wasm.addGlobal(global); + } } else { import->kind = Import::Function; } diff --git a/src/asm_v_wasm.h b/src/asm_v_wasm.h index 7a4a0be31..53881861c 100644 --- a/src/asm_v_wasm.h +++ b/src/asm_v_wasm.h @@ -54,6 +54,16 @@ std::string getSig(WasmType result, const ListType& operands) { return ret; } +template<typename ListType> +std::string getSigFromStructs(WasmType result, const ListType& operands) { + std::string ret; + ret += getSig(result); + for (auto operand : operands) { + ret += getSig(operand.type); + } + return ret; +} + WasmType sigToWasmType(char sig); FunctionType* sigToFunctionType(std::string sig); diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 3ce24bdbd..14fc4680d 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -128,6 +128,7 @@ BinaryenFunctionTypeRef BinaryenAddFunctionType(BinaryenModuleRef module, const auto* wasm = (Module*)module; auto* ret = new FunctionType; if (name) ret->name = name; + else ret->name = Name::fromInt(wasm->functionTypes.size()); ret->result = WasmType(result); for (BinaryenIndex i = 0; i < numParams; i++) { ret->params.push_back(WasmType(paramTypes[i])); diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 669a19b89..48639a341 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -55,15 +55,20 @@ struct PatternDatabase { input = strdup( #include "OptimizeInstructions.wast.processed" ); - SExpressionParser parser(input); - Element& root = *parser.root; - SExpressionWasmBuilder builder(wasm, *root[0]); - // parse module form - auto* func = wasm.getFunction("patterns"); - auto* body = func->body->cast<Block>(); - for (auto* item : body->list) { - auto* pair = item->cast<Block>(); - patternMap[pair->list[0]->_id].emplace_back(pair->list[0], pair->list[1]); + try { + SExpressionParser parser(input); + Element& root = *parser.root; + SExpressionWasmBuilder builder(wasm, *root[0]); + // parse module form + auto* func = wasm.getFunction("patterns"); + auto* body = func->body->cast<Block>(); + for (auto* item : body->list) { + auto* pair = item->cast<Block>(); + patternMap[pair->list[0]->_id].emplace_back(pair->list[0], pair->list[1]); + } + } catch (ParseException& p) { + p.dump(std::cerr); + Fatal() << "error in parsing wasm binary"; } } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 43ddc954c..363147b98 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -109,6 +109,9 @@ struct PrintSExpression : public Visitor<PrintSExpression> { o << ' '; printName(curr->name); } + if (isConcreteWasmType(curr->type)) { + o << ' ' << printWasmType(curr->type); + } incIndent(); if (curr->list.size() > 0 && curr->list[0]->is<Block>()) { // recurse into the first element @@ -224,7 +227,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> { printCallBody(curr); } void visitCallImport(CallImport *curr) { - printOpening(o, "call_import "); + printOpening(o, "call "); printCallBody(curr); } void visitCallIndirect(CallIndirect *curr) { @@ -537,8 +540,8 @@ struct PrintSExpression : public Visitor<PrintSExpression> { printText(o, curr->base.str) << ' '; switch (curr->kind) { case Export::Function: if (curr->functionType) visitFunctionType(curr->functionType, &curr->name); break; - case Export::Table: o << "(table " << curr->name << ")"; break; - case Export::Memory: o << "(memory " << curr->name << ")"; break; + case Export::Table: printTableHeader(&currModule->table); break; + case Export::Memory: printMemoryHeader(&currModule->memory); break; case Export::Global: o << "(global " << curr->name << ' ' << printWasmType(curr->globalType) << ")"; break; default: WASM_UNREACHABLE(); } @@ -560,7 +563,11 @@ struct PrintSExpression : public Visitor<PrintSExpression> { void visitGlobal(Global *curr) { printOpening(o, "global "); printName(curr->name) << ' '; - o << printWasmType(curr->type) << ' '; + if (curr->mutable_) { + o << "(mut " << printWasmType(curr->type) << ") "; + } else { + o << printWasmType(curr->type) << ' '; + } visit(curr->init); o << ')'; } @@ -599,11 +606,26 @@ struct PrintSExpression : public Visitor<PrintSExpression> { } decIndent(); } - void visitTable(Table *curr) { + void printTableHeader(Table* curr) { printOpening(o, "table") << ' '; o << curr->initial; if (curr->max && curr->max != Table::kMaxSize) o << ' ' << curr->max; - o << " anyfunc)\n"; + o << " anyfunc)"; + } + void visitTable(Table *curr) { + // if table wasn't imported, declare it + bool found = false; + for (auto& import : currModule->imports) { + if (import->kind == Import::Table) { + found = true; + break; + } + } + if (!found) { + doIndent(o, indent); + printTableHeader(curr); + o << '\n'; + } doIndent(o, indent); for (auto& segment : curr->segments) { printOpening(o, "elem ", true); @@ -615,11 +637,26 @@ struct PrintSExpression : public Visitor<PrintSExpression> { o << ')'; } } - void visitMemory(Memory* curr) { + void printMemoryHeader(Memory* curr) { printOpening(o, "memory") << ' '; o << curr->initial; if (curr->max && curr->max != Memory::kMaxSize) o << ' ' << curr->max; - o << ")\n"; + o << ")"; + } + void visitMemory(Memory* curr) { + // if memory wasn't imported, declare it + bool found = false; + for (auto& import : currModule->imports) { + if (import->kind == Import::Memory) { + found = true; + break; + } + } + if (!found) { + doIndent(o, indent); + printMemoryHeader(curr); + o << '\n'; + } for (auto segment : curr->segments) { doIndent(o, indent); printOpening(o, "data ", true); @@ -652,7 +689,6 @@ struct PrintSExpression : public Visitor<PrintSExpression> { currModule = curr; printOpening(o, "module", true); incIndent(); - doIndent(o, indent); visitMemory(&curr->memory); if (curr->start.is()) { doIndent(o, indent); @@ -682,7 +718,6 @@ struct PrintSExpression : public Visitor<PrintSExpression> { o << maybeNewLine; } if (curr->table.segments.size() > 0 || curr->table.initial > 0 || curr->table.max != Table::kMaxSize) { - doIndent(o, indent); visitTable(&curr->table); o << maybeNewLine; } diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index fa8ab2f6c..15482de25 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -146,11 +146,10 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R // if without an else. try to reduce if (condition) br => br_if (condition) Break* br = curr->ifTrue->dynCast<Break>(); if (br && !br->condition) { // TODO: if there is a condition, join them - // if the br has a value, then if => br_if means we always execute the value, and also the order is value,condition vs condition,value if (canTurnIfIntoBrIf(curr->condition, br->value)) { br->condition = curr->condition; br->finalize(); - replaceCurrent(br); + replaceCurrent(Builder(*getModule()).dropIfConcretelyTyped(br)); anotherCycle = true; } } @@ -407,18 +406,18 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R auto* ifTrueBreak = iff->ifTrue->dynCast<Break>(); if (ifTrueBreak && !ifTrueBreak->condition && canTurnIfIntoBrIf(iff->condition, ifTrueBreak->value)) { // we are an if-else where the ifTrue is a break without a condition, so we can do this - list[i] = ifTrueBreak; ifTrueBreak->condition = iff->condition; ifTrueBreak->finalize(); + list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifTrueBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifFalse); continue; } // otherwise, perhaps we can flip the if auto* ifFalseBreak = iff->ifFalse->dynCast<Break>(); if (ifFalseBreak && !ifFalseBreak->condition && canTurnIfIntoBrIf(iff->condition, ifFalseBreak->value)) { - list[i] = ifFalseBreak; ifFalseBreak->condition = Builder(*getModule()).makeUnary(EqZInt32, iff->condition); ifFalseBreak->finalize(); + list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifFalseBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifTrue); continue; } diff --git a/src/shared-constants.h b/src/shared-constants.h index f0148cb67..923ccc7de 100644 --- a/src/shared-constants.h +++ b/src/shared-constants.h @@ -47,12 +47,9 @@ extern Name GROW_WASM_MEMORY, BR, ANYFUNC, FAKE_RETURN, - ASSERT_RETURN, - ASSERT_TRAP, - ASSERT_INVALID, + MUT, SPECTEST, PRINT, - INVOKE, EXIT; } // namespace wasm diff --git a/src/shell-interface.h b/src/shell-interface.h index ee9ff166a..b9a90131b 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -111,7 +111,24 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { } } - void importGlobals(std::map<Name, Literal>& globals, Module& wasm) override {} + void importGlobals(std::map<Name, Literal>& globals, Module& wasm) override { + // add spectest globals + for (auto& import : wasm.imports) { + if (import->kind == Import::Global && import->module == SPECTEST && import->base == GLOBAL) { + switch (import->globalType) { + case i32: globals[import->name] = Literal(int32_t(666)); break; + case i64: globals[import->name] = Literal(int64_t(666)); break; + case f32: globals[import->name] = Literal(float(666.6)); break; + case f64: globals[import->name] = Literal(double(666.6)); break; + default: WASM_UNREACHABLE(); + } + } else if (import->kind == Import::Memory && import->module == SPECTEST && import->base == MEMORY) { + // imported memory has initial 1 and max 2 + wasm.memory.initial = 1; + wasm.memory.max = 2; + } + } + } Literal callImport(Import *import, LiteralList& arguments) override { if (import->module == SPECTEST && import->base == PRINT) { @@ -130,7 +147,8 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { Literal callTable(Index index, LiteralList& arguments, WasmType result, ModuleInstance& instance) override { if (index >= table.size()) trap("callTable overflow"); - auto* func = instance.wasm.getFunction(table[index]); + auto* func = instance.wasm.checkFunction(table[index]); + if (!func) trap("uninitialized table element"); if (func->params.size() != arguments.size()) trap("callIndirect: bad # of arguments"); for (size_t i = 0; i < func->params.size(); i++) { if (func->params[i] != arguments[i].type) { diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index a95ced87f..670f5783d 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -34,26 +34,55 @@ using namespace cashew; using namespace wasm; +Name ASSERT_RETURN("assert_return"), + ASSERT_TRAP("assert_trap"), + ASSERT_INVALID("assert_invalid"), + ASSERT_MALFORMED("assert_malformed"), + ASSERT_UNLINKABLE("assert_unlinkable"), + INVOKE("invoke"), + GET("get"); + +// Modules named in the file + +std::map<Name, std::unique_ptr<Module>> modules; +std::map<Name, std::unique_ptr<SExpressionWasmBuilder>> builders; +std::map<Name, std::unique_ptr<ShellExternalInterface>> interfaces; +std::map<Name, std::unique_ptr<ModuleInstance>> instances; + // -// An invocation into a module +// An operation on a module // -struct Invocation { +struct Operation { ModuleInstance* instance; - IString name; + Name operation; + Name name; LiteralList arguments; - Invocation(Element& invoke, ModuleInstance* instance, SExpressionWasmBuilder& builder) : instance(instance) { - assert(invoke[0]->str() == INVOKE); - name = invoke[1]->str(); - for (size_t j = 2; j < invoke.size(); j++) { - Expression* argument = builder.parseExpression(*invoke[j]); + Operation(Element& element, ModuleInstance* instanceInit, SExpressionWasmBuilder& builder) : instance(instanceInit) { + operation = element[0]->str(); + Index i = 1; + if (element.size() >= 3 && element[2]->isStr()) { + // module also specified + Name moduleName = element[i++]->str(); + instance = instances[moduleName].get(); + } + name = element[i++]->str(); + for (size_t j = i; j < element.size(); j++) { + Expression* argument = builder.parseExpression(*element[j]); arguments.push_back(argument->dynCast<Const>()->value); } } - Literal invoke() { - return instance->callExport(name, arguments); + Literal operate() { + if (operation == INVOKE) { + return instance->callExport(name, arguments); + } else if (operation == GET) { + return instance->getExport(name); + } else { + Fatal() << "unknown operation: " << operation << '\n'; + WASM_UNREACHABLE(); + } } }; @@ -70,15 +99,17 @@ static void verify_result(Literal a, Literal b) { } } -static void run_asserts(size_t* i, bool* checked, Module* wasm, +static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, Element* root, - std::unique_ptr<SExpressionWasmBuilder>* builder, + SExpressionWasmBuilder* builder, Name entry) { - std::unique_ptr<ShellExternalInterface> interface; - std::unique_ptr<ModuleInstance> instance; + ModuleInstance* instance = nullptr; if (wasm) { - interface = wasm::make_unique<ShellExternalInterface>(); // prefix make_unique to work around visual studio bugs - instance = wasm::make_unique<ModuleInstance>(*wasm, interface.get()); + auto tempInterface = wasm::make_unique<ShellExternalInterface>(); // prefix make_unique to work around visual studio bugs + auto tempInstance = wasm::make_unique<ModuleInstance>(*wasm, tempInterface.get()); + interfaces[moduleName].swap(tempInterface); + instances[moduleName].swap(tempInstance); + instance = instances[moduleName].get(); if (entry.is()) { Function* function = wasm->getFunction(entry); if (!function) { @@ -109,7 +140,7 @@ static void run_asserts(size_t* i, bool* checked, Module* wasm, Colors::green(std::cerr); std::cerr << " [line: " << curr.line << "]\n"; Colors::normal(std::cerr); - if (id == ASSERT_INVALID) { + if (id == ASSERT_INVALID || id == ASSERT_MALFORMED || id == ASSERT_UNLINKABLE) { // a module invalidity test Module wasm; bool invalid = false; @@ -125,6 +156,33 @@ static void run_asserts(size_t* i, bool* checked, Module* wasm, // maybe parsed ok, but otherwise incorrect invalid = !WasmValidator().validate(wasm); } + if (!invalid && id == ASSERT_UNLINKABLE) { + // validate "instantiating" the mdoule + for (auto& import : wasm.imports) { + if (import->module == SPECTEST && import->base == PRINT) { + if (import->kind != Import::Function) { + std::cerr << "spectest.print should be a function, but is " << import->kind << '\n'; + invalid = true; + break; + } + } else { + std::cerr << "unknown import: " << import->module << '.' << import->base << '\n'; + invalid = true; + break; + } + } + for (auto& segment : wasm.table.segments) { + for (auto name : segment.data) { + // spec tests consider it illegal to use spectest.print in a table + if (auto* import = wasm.checkImport(name)) { + if (import->module == SPECTEST && import->base == PRINT) { + std::cerr << "cannot put spectest.print in table\n"; + invalid = true; + } + } + } + } + } if (!invalid) { Colors::red(std::cerr); std::cerr << "[should have been invalid]\n"; @@ -134,23 +192,23 @@ static void run_asserts(size_t* i, bool* checked, Module* wasm, } } else if (id == INVOKE) { assert(wasm); - Invocation invocation(curr, instance.get(), *builder->get()); - invocation.invoke(); + Operation operation(curr, instance, *builder); + operation.operate(); } else if (wasm) { // if no wasm, we skipped the module // an invoke test bool trapped = false; WASM_UNUSED(trapped); Literal result; try { - Invocation invocation(*curr[1], instance.get(), *builder->get()); - result = invocation.invoke(); + Operation operation(*curr[1], instance, *builder); + result = operation.operate(); } catch (const TrapException&) { trapped = true; } if (id == ASSERT_RETURN) { assert(!trapped); if (curr.size() >= 3) { - Literal expected = builder->get() + Literal expected = builder ->parseExpression(*curr[2]) ->dynCast<Const>() ->value; @@ -229,14 +287,16 @@ int main(int argc, const char* argv[]) { Colors::green(std::cerr); std::cerr << "BUILDING MODULE [line: " << curr.line << "]\n"; Colors::normal(std::cerr); - Module wasm; - std::unique_ptr<SExpressionWasmBuilder> builder; - builder = wasm::make_unique<SExpressionWasmBuilder>(wasm, *root[i]); + auto module = wasm::make_unique<Module>(); + Name moduleName; + auto builder = wasm::make_unique<SExpressionWasmBuilder>(*module, *root[i], &moduleName); + builders[moduleName].swap(builder); + modules[moduleName].swap(module); i++; - assert(WasmValidator().validate(wasm)); - run_asserts(&i, &checked, &wasm, &root, &builder, entry); + assert(WasmValidator().validate(*modules[moduleName])); + run_asserts(moduleName, &i, &checked, modules[moduleName].get(), &root, builders[moduleName].get(), entry); } else { - run_asserts(&i, &checked, nullptr, &root, nullptr, entry); + run_asserts(Name(), &i, &checked, nullptr, &root, nullptr, entry); } } } catch (ParseException& p) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 561a310b0..bbfde5b21 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -411,7 +411,6 @@ enum ASTNodes { SetLocal = 0x15, CallFunction = 0x16, CallIndirect = 0x17, - CallImport = 0x18, TeeLocal = 0x19, GetGlobal = 0x1a, SetGlobal = 0x1b, @@ -653,7 +652,7 @@ public: if (debug) std::cerr << "write one at" << o.size() << std::endl; size_t sizePos = writeU32LEBPlaceholder(); size_t start = o.size(); - Function* function = wasm->getFunction(i); + Function* function = wasm->functions[i].get(); mappedLocals.clear(); numLocalsByType.clear(); if (debug) std::cerr << "writing" << function->name << std::endl; @@ -727,27 +726,21 @@ public: } finishSection(start); } - - std::map<Name, uint32_t> mappedImports; // name of the Import => index - uint32_t getImportIndex(Name name) { - if (!mappedImports.size()) { - // Create name => index mapping. - for (size_t i = 0; i < wasm->imports.size(); i++) { - assert(mappedImports.count(wasm->imports[i]->name) == 0); - mappedImports[wasm->imports[i]->name] = i; - } - } - assert(mappedImports.count(name)); - return mappedImports[name]; - } - std::map<Name, uint32_t> mappedFunctions; // name of the Function => index + std::map<Name, Index> mappedFunctions; // name of the Function => index. first imports, then internals uint32_t getFunctionIndex(Name name) { if (!mappedFunctions.size()) { // Create name => index mapping. + for (auto& import : wasm->imports) { + if (import->kind != Import::Function) continue; + assert(mappedFunctions.count(import->name) == 0); + auto index = mappedFunctions.size(); + mappedFunctions[import->name] = index; + } for (size_t i = 0; i < wasm->functions.size(); i++) { assert(mappedFunctions.count(wasm->functions[i]->name) == 0); - mappedFunctions[wasm->functions[i]->name] = i; + auto index = mappedFunctions.size(); + mappedFunctions[wasm->functions[i]->name] = index; } } assert(mappedFunctions.count(name)); @@ -957,7 +950,7 @@ public: for (auto* operand : curr->operands) { recurse(operand); } - o << int8_t(BinaryConsts::CallImport) << U32LEB(curr->operands.size()) << U32LEB(getImportIndex(curr->target)); + o << int8_t(BinaryConsts::CallFunction) << U32LEB(curr->operands.size()) << U32LEB(getFunctionIndex(curr->target)); } void visitCallIndirect(CallIndirect *curr) { if (debug) std::cerr << "zz node: CallIndirect" << std::endl; @@ -1300,8 +1293,13 @@ public: else if (match(BinaryConsts::Section::FunctionSignatures)) readFunctionSignatures(); else if (match(BinaryConsts::Section::Functions)) readFunctions(); else if (match(BinaryConsts::Section::ExportTable)) readExports(); - else if (match(BinaryConsts::Section::Globals)) readGlobals(); - else if (match(BinaryConsts::Section::DataSegments)) readDataSegments(); + else if (match(BinaryConsts::Section::Globals)) { + readGlobals(); + // imports can read global imports, so we run getGlobalName and create the mapping + // but after we read globals, we need to add the internal globals too, so do that here + mappedGlobals.clear(); // wipe the mapping + getGlobalName(0); // force rebuild + } else if (match(BinaryConsts::Section::DataSegments)) readDataSegments(); else if (match(BinaryConsts::Section::FunctionTable)) readFunctionTable(); else if (match(BinaryConsts::Section::Names)) readNames(); else { @@ -1497,10 +1495,25 @@ public: assert(numResults == 1); curr->result = getWasmType(); } + curr->name = Name::fromInt(wasm.functionTypes.size()); wasm.addFunctionType(curr); } } + std::vector<Name> functionImportIndexes; // index in function index space => name of function import + + // gets a name in the combined function import+defined function space + Name getFunctionIndexName(Index i) { + if (i < functionImportIndexes.size()) { + auto* import = wasm.getImport(functionImportIndexes[i]); + assert(import->kind == Import::Function); + return import->name; + } else { + i -= functionImportIndexes.size(); + return wasm.functions.at(i)->name; + } + } + void readImports() { if (debug) std::cerr << "== readImports" << std::endl; size_t num = getU32LEB(); @@ -1511,16 +1524,17 @@ public: curr->name = Name(std::string("import$") + std::to_string(i)); curr->kind = (Import::Kind)getU32LEB(); switch (curr->kind) { - case Export::Function: { + case Import::Function: { auto index = getU32LEB(); assert(index < wasm.functionTypes.size()); - curr->functionType = wasm.getFunctionType(index); + curr->functionType = wasm.functionTypes[index].get(); assert(curr->functionType->name.is()); + functionImportIndexes.push_back(curr->name); break; } - case Export::Table: break; - case Export::Memory: break; - case Export::Global: curr->globalType = getWasmType(); break; + case Import::Table: break; + case Import::Memory: break; + case Import::Global: curr->globalType = getWasmType(); break; default: WASM_UNREACHABLE(); } curr->module = getInlineString(); @@ -1529,7 +1543,7 @@ public: } } - std::vector<FunctionType*> functionTypes; + std::vector<FunctionType*> functionTypes; // types of defined functions void readFunctionSignatures() { if (debug) std::cerr << "== readFunctionSignatures" << std::endl; @@ -1538,7 +1552,7 @@ public: for (size_t i = 0; i < num; i++) { if (debug) std::cerr << "read one" << std::endl; auto index = getU32LEB(); - functionTypes.push_back(wasm.getFunctionType(index)); + functionTypes.push_back(wasm.functionTypes[index].get()); } } @@ -1551,9 +1565,9 @@ public: // We read functions before we know their names, so we need to backpatch the names later std::vector<Function*> functions; // we store functions here before wasm.addFunction after we know their names - std::map<size_t, std::vector<Call*>> functionCalls; // at index i we have all calls to i + std::map<Index, std::vector<Call*>> functionCalls; // at index i we have all calls to the defined function i Function* currFunction = nullptr; - size_t endOfFunction; + Index endOfFunction = -1; // before we see a function (like global init expressions), there is no end of function to check void readFunctions() { if (debug) std::cerr << "== readFunctions" << std::endl; @@ -1611,7 +1625,7 @@ public: } } - std::map<Export*, size_t> exportIndexes; + std::map<Export*, Index> exportIndexes; void readExports() { if (debug) std::cerr << "== readExports" << std::endl; @@ -1645,6 +1659,8 @@ public: auto curr = new Global; curr->type = getWasmType(); curr->init = readExpression(); + curr->mutable_ = true; // TODO + curr->name = Name("global$" + std::to_string(wasm.globals.size())); wasm.addGlobal(curr); } } @@ -1698,14 +1714,17 @@ public: } // now that we have names for each function, apply things - if (startIndex != static_cast<Index>(-1) && startIndex < wasm.functions.size()) { - wasm.start = wasm.functions[startIndex]->name; + if (startIndex != static_cast<Index>(-1)) { + wasm.start = getFunctionIndexName(startIndex); } for (auto& iter : exportIndexes) { Export* curr = iter.first; switch (curr->kind) { - case Export::Function: curr->value = wasm.functions[iter.second]->name; break; + case Export::Function: { + curr->value = getFunctionIndexName(iter.second); + break; + } case Export::Table: curr->value = Name::fromInt(0); break; case Export::Memory: curr->value = Name::fromInt(0); break; case Export::Global: curr->value = getGlobalName(iter.second); break; @@ -1726,7 +1745,7 @@ public: auto i = pair.first; auto& indexes = pair.second; for (auto j : indexes) { - wasm.table.segments[i].data.push_back(wasm.functions[j]->name); + wasm.table.segments[i].data.push_back(getFunctionIndexName(j)); } } } @@ -1795,8 +1814,7 @@ public: case BinaryConsts::Br: case BinaryConsts::BrIf: visitBreak((curr = allocator.alloc<Break>())->cast<Break>(), code); break; // code distinguishes br from br_if case BinaryConsts::TableSwitch: visitSwitch((curr = allocator.alloc<Switch>())->cast<Switch>()); break; - case BinaryConsts::CallFunction: visitCall((curr = allocator.alloc<Call>())->cast<Call>()); break; - case BinaryConsts::CallImport: visitCallImport((curr = allocator.alloc<CallImport>())->cast<CallImport>()); break; + case BinaryConsts::CallFunction: curr = visitCall(); break; // we don't know if it's a call or call_import yet case BinaryConsts::CallIndirect: visitCallIndirect((curr = allocator.alloc<CallIndirect>())->cast<CallIndirect>()); break; case BinaryConsts::GetLocal: visitGetLocal((curr = allocator.alloc<GetLocal>())->cast<GetLocal>()); break; case BinaryConsts::TeeLocal: @@ -1938,44 +1956,51 @@ public: } curr->default_ = getBreakName(getInt32()); } - void visitCall(Call *curr) { - if (debug) std::cerr << "zz node: Call" << std::endl; - auto arity = getU32LEB(); - WASM_UNUSED(arity); - auto index = getU32LEB(); - assert(index < functionTypes.size()); - auto type = functionTypes[index]; + + template<typename T> + void fillCall(T* call, FunctionType* type, Index arity) { + assert(type); auto num = type->params.size(); assert(num == arity); - curr->operands.resize(num); + call->operands.resize(num); for (size_t i = 0; i < num; i++) { - curr->operands[num - i - 1] = popExpression(); + call->operands[num - i - 1] = popExpression(); } - curr->type = type->result; - functionCalls[index].push_back(curr); + call->type = type->result; } - void visitCallImport(CallImport *curr) { - if (debug) std::cerr << "zz node: CallImport" << std::endl; + + Expression* visitCall() { + if (debug) std::cerr << "zz node: Call" << std::endl; auto arity = getU32LEB(); WASM_UNUSED(arity); - auto import = wasm.getImport(getU32LEB()); - curr->target = import->name; - auto type = import->functionType; - assert(type); - auto num = type->params.size(); - assert(num == arity); - if (debug) std::cerr << "zz node: CallImport " << curr->target << " with type " << type->name << " and " << num << " params\n"; - curr->operands.resize(num); - for (size_t i = 0; i < num; i++) { - curr->operands[num - i - 1] = popExpression(); + auto index = getU32LEB(); + FunctionType* type; + Expression* ret; + if (index < functionImportIndexes.size()) { + // this is a call of an imported function + auto* call = allocator.alloc<CallImport>(); + auto* import = wasm.getImport(functionImportIndexes[index]); + call->target = import->name; + type = import->functionType; + fillCall(call, type, arity); + ret = call; + } else { + // this is a call of a defined function + auto* call = allocator.alloc<Call>(); + auto adjustedIndex = index - functionImportIndexes.size(); + assert(adjustedIndex < functionTypes.size()); + type = functionTypes[adjustedIndex]; + fillCall(call, type, arity); + functionCalls[adjustedIndex].push_back(call); // we don't know function names yet + ret = call; } - curr->type = type->result; + return ret; } void visitCallIndirect(CallIndirect *curr) { if (debug) std::cerr << "zz node: CallIndirect" << std::endl; auto arity = getU32LEB(); WASM_UNUSED(arity); - auto* fullType = wasm.getFunctionType(getU32LEB()); + auto* fullType = wasm.functionTypes.at(getU32LEB()).get(); curr->fullType = fullType->name; auto num = fullType->params.size(); assert(num == arity); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index f9b2e73f6..546d72391 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -339,6 +339,12 @@ public: input->finalize(); return ret; } + + // Drop an expression if it has a concrete type + Expression* dropIfConcretelyTyped(Expression* curr) { + if (!isConcreteWasmType(curr->type)) return curr; + return makeDrop(curr); + } }; } // namespace wasm diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 292d6a521..942ba1ab6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -51,14 +51,14 @@ class Flow { public: Flow() {} Flow(Literal value) : value(value) {} - Flow(IString breakTo) : breakTo(breakTo) {} + Flow(Name breakTo) : breakTo(breakTo) {} Literal value; - IString breakTo; // if non-null, a break is going on + Name breakTo; // if non-null, a break is going on bool breaking() { return breakTo.is(); } - void clearIf(IString target) { + void clearIf(Name target) { if (breakTo == target) { breakTo.clear(); } @@ -155,18 +155,19 @@ public: Flow visitBreak(Break *curr) { NOTE_ENTER("Break"); bool condition = true; - Flow flow(curr->name); + Flow flow; if (curr->value) { flow = visit(curr->value); if (flow.breaking()) return flow; - flow.breakTo = curr->name; } if (curr->condition) { Flow conditionFlow = visit(curr->condition); if (conditionFlow.breaking()) return conditionFlow; condition = conditionFlow.value.getInteger() != 0; + if (!condition) return flow; } - return condition ? flow : Flow(); + flow.breakTo = curr->name; + return flow; } Flow visitSwitch(Switch *curr) { NOTE_ENTER("Switch"); @@ -553,12 +554,23 @@ public: } } + // call an exported function Literal callExport(Name name, LiteralList& arguments) { Export *export_ = wasm.checkExport(name); if (!export_) externalInterface->trap("callExport not found"); return callFunction(export_->value, arguments); } + // get an exported global + Literal getExport(Name name) { + Export *export_ = wasm.checkExport(name); + if (!export_) externalInterface->trap("getExport external not found"); + Name internalName = export_->value; + auto iter = globals.find(internalName); + if (iter == globals.end()) externalInterface->trap("getExport internal not found"); + return iter->second; + } + std::string printFunctionStack() { std::string ret = "/== (binaryen interpreter stack trace)\n"; for (int i = int(functionStack.size()) - 1; i >= 0; i--) { @@ -577,7 +589,7 @@ private: std::vector<Name> functionStack; // Call a function, starting an invocation. - Literal callFunction(IString name, LiteralList& arguments) { + Literal callFunction(Name name, LiteralList& arguments) { // if the last call ended in a jump up the stack, it might have left stuff for us to clean up here callDepth = 0; functionStack.clear(); @@ -586,7 +598,7 @@ private: public: // Internal function call. Must be public so that callTable implementations can use it (refactor?) - Literal callFunctionInternal(IString name, LiteralList& arguments) { + Literal callFunctionInternal(Name name, LiteralList& arguments) { class FunctionScope { public: @@ -695,6 +707,7 @@ public: auto name = curr->name; NOTE_EVAL1(name); NOTE_EVAL1(instance.globals[name]); + assert(instance.globals.find(name) != instance.globals.end()); return instance.globals[name]; } Flow visitSetGlobal(SetGlobal *curr) { @@ -744,7 +757,7 @@ public: return Literal(int32_t(ret)); } case HasFeature: { - IString id = curr->nameOperand; + Name id = curr->nameOperand; if (id == WASM) return Literal(1); return Literal((int32_t)0); } @@ -775,7 +788,10 @@ public: assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); // cannot still be breaking, it means we missed our stop Literal ret = flow.value; if (function->result == none) ret = Literal(); - assert(function->result == ret.type); + if (function->result != ret.type) { + std::cerr << "calling " << function->name << " resulted in " << ret << " but the function type is " << function->result << '\n'; + abort(); + } callDepth = previousCallDepth; // may decrease more than one, if we jumped up the stack // if we jumped up the stack, we also need to pop higher frames while (functionStack.size() > previousFunctionStackSize) { diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 9abdea744..bf2ab7739 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -62,6 +62,8 @@ class Element { bool dollared_; bool quoted_; + #define element_assert(condition) assert((condition) ? true : (std::cerr << "on: " << *this << '\n' && 0)); + public: Element(MixedArena& allocator) : isList_(true), list_(allocator), line(-1), col(-1) {} @@ -80,7 +82,7 @@ public: } Element* operator[](unsigned i) { - if (i >= list().size()) throw ParseException("expected more elements in list", line, col); + if (i >= list().size()) element_assert(0 && "expected more elements in list"); return list()[i]; } @@ -91,12 +93,12 @@ public: // string methods IString str() { - assert(!isList_); + element_assert(!isList_); return str_; } const char* c_str() { - assert(!isList_); + element_assert(!isList_); return str_.str; } @@ -130,8 +132,12 @@ public: void dump() { std::cout << "dumping " << this << " : " << *this << ".\n"; } + + #undef element_assert }; +#define element_assert(condition, element) assert((condition) ? true : (std::cerr << "on: " << element << " at " << element.line << ":" << element.col << '\n' && 0)); + // // Generic S-Expression parsing into lists // @@ -212,6 +218,10 @@ private: if (depth == 0) { break; } + } else if (input[0] == '\n') { + line++; + lineStart = input; + input++; } else { input++; } @@ -265,19 +275,27 @@ class SExpressionWasmBuilder { Module& wasm; MixedArena& allocator; std::vector<Name> functionNames; + std::vector<Name> functionTypeNames; + std::vector<Name> globalNames; int functionCounter; - int importCounter; int globalCounter; std::map<Name, WasmType> functionTypes; // we need to know function return types before we parse their contents public: // Assumes control of and modifies the input. - SExpressionWasmBuilder(Module& wasm, Element& module) : wasm(wasm), allocator(wasm.allocator), importCounter(0), globalCounter(0) { + SExpressionWasmBuilder(Module& wasm, Element& module, Name* moduleName = nullptr) : wasm(wasm), allocator(wasm.allocator), globalCounter(0) { assert(module[0]->str() == MODULE); - if (module.size() > 1 && module[1]->isStr()) { + if (module.size() == 1) return; + Index i = 1; + if (module[i]->dollared()) { + if (moduleName) { + *moduleName = module[i]->str(); + } + i++; + } + if (i < module.size() && module[i]->isStr()) { // these s-expressions contain a binary module, actually std::vector<char> data; - size_t i = 1; while (i < module.size()) { auto str = module[i++]->c_str(); if (auto size = strlen(str)) { @@ -288,14 +306,19 @@ public: binaryBuilder.read(); return; } + Index implementedFunctions = 0; functionCounter = 0; - for (unsigned i = 1; i < module.size(); i++) { - preParseFunctionType(*module[i]); - preParseImports(*module[i]); + for (unsigned j = i; j < module.size(); j++) { + auto& s = *module[j]; + preParseFunctionType(s); + preParseImports(s); + if (s[0]->str() == FUNC && !isImport(s)) { + implementedFunctions++; + } } - functionCounter = 0; - for (unsigned i = 1; i < module.size(); i++) { - parseModuleElement(*module[i]); + functionCounter -= implementedFunctions; // we go through the functions again, now parsing them, and the counter begins from where imports ended + for (unsigned j = i; j < module.size(); j++) { + parseModuleElement(*module[j]); } } @@ -325,8 +348,8 @@ private: if (curr.size() > 2) throw ParseException("invalid result arity", curr.line, curr.col); functionTypes[name] = stringToWasmType(curr[1]->str()); } else if (id == TYPE) { - Name typeName = curr[1]->str(); - if (!wasm.checkFunctionType(typeName)) throw ParseException("unknown function", curr.line, curr.col); + Name typeName = getFunctionTypeName(*curr[1]); + if (!wasm.checkFunctionType(typeName)) throw ParseException("unknown function type", curr.line, curr.col); type = wasm.getFunctionType(typeName); functionTypes[name] = type->result; } else if (id == PARAM && curr.size() > 1) { @@ -356,17 +379,35 @@ private: } } if (need) { + functionType->name = Name::fromInt(wasm.functionTypes.size()); + functionTypeNames.push_back(functionType->name); wasm.addFunctionType(functionType.release()); } } } + bool isImport(Element& curr) { + for (Index i = 0; i < curr.size(); i++) { + auto& x = *curr[i]; + if (x.isList() && x.size() > 0 && x[0]->isStr() && x[0]->str() == IMPORT) return true; + } + return false; + } + void preParseImports(Element& curr) { IString id = curr[0]->str(); if (id == IMPORT) parseImport(curr); + if (isImport(curr)) { + if (id == FUNC) parseFunction(curr, true /* preParseImport */); + else if (id == GLOBAL) parseGlobal(curr, true /* preParseImport */); + else if (id == TABLE) parseTable(curr, true /* preParseImport */); + else if (id == MEMORY) parseMemory(curr, true /* preParseImport */); + else throw ParseException("fancy import we don't support yet", curr.line, curr.col); + } } void parseModuleElement(Element& curr) { + if (isImport(curr)) return; // already done IString id = curr[0]->str(); if (id == START) return parseStart(curr); if (id == FUNC) return parseFunction(curr); @@ -404,11 +445,33 @@ private: } else { // index size_t offset = atoi(s.str().c_str()); - if (offset >= functionNames.size()) throw ParseException("unknown function"); + if (offset >= functionNames.size()) throw ParseException("unknown function in getFunctionName"); return functionNames[offset]; } } + Name getFunctionTypeName(Element& s) { + if (s.dollared()) { + return s.str(); + } else { + // index + size_t offset = atoi(s.str().c_str()); + if (offset >= functionTypeNames.size()) throw ParseException("unknown function type in getFunctionTypeName"); + return functionTypeNames[offset]; + } + } + + Name getGlobalName(Element& s) { + if (s.dollared()) { + return s.str(); + } else { + // index + size_t offset = atoi(s.str().c_str()); + if (offset >= globalNames.size()) throw ParseException("unknown global in getGlobalName"); + return globalNames[offset]; + } + } + void parseStart(Element& s) { wasm.addStart(getFunctionName(*s[1])); } @@ -435,25 +498,39 @@ private: i++; } } +#if 0 + if (exportName.is() && !name.is()) { + name = exportName; // useful for debugging + } +#endif return i; } - void parseFunction(Element& s) { + void parseFunction(Element& s, bool preParseImport = false) { size_t i = 1; Name name, exportName; i = parseFunctionNames(s, name, exportName); - if (!name.is()) { - // unnamed, use an index - name = Name::fromInt(functionCounter); + if (!preParseImport) { + if (!name.is()) { + // unnamed, use an index + name = Name::fromInt(functionCounter); + } + functionCounter++; + } else { + // just preparsing, functionCounter was incremented by preParseFunctionType + if (!name.is()) { + // unnamed, use an index + name = functionNames[functionCounter - 1]; + } } if (exportName.is()) { auto ex = make_unique<Export>(); ex->name = exportName; ex->value = name; ex->kind = Export::Function; + if (wasm.checkExport(ex->name)) throw ParseException("duplicate export", s.line, s.col); wasm.addExport(ex.release()); } - functionCounter++; Expression* body = nullptr; localIndex = 0; otherIndex = 0; @@ -464,6 +541,7 @@ private: WasmType result = none; Name type; Block* autoBlock = nullptr; // we may need to add a block for the very top level + Name importModule, importBase; auto makeFunction = [&]() { currFunction = std::unique_ptr<Function>(Builder(wasm).makeFunction( name, @@ -511,9 +589,9 @@ private: if (curr.size() > 2) throw ParseException("invalid result arity", curr.line, curr.col); result = stringToWasmType(curr[1]->str()); } else if (id == TYPE) { - Name name = curr[1]->str(); + Name name = getFunctionTypeName(*curr[1]); type = name; - if (!wasm.checkFunctionType(name)) throw ParseException("unknown function"); + if (!wasm.checkFunctionType(name)) throw ParseException("unknown function type"); FunctionType* type = wasm.getFunctionType(name); result = type->result; for (size_t j = 0; j < type->params.size(); j++) { @@ -522,6 +600,9 @@ private: typeParams.emplace_back(name, currType); currLocalTypes[name] = currType; } + } else if (id == IMPORT) { + importModule = curr[1]->str(); + importBase = curr[2]->str(); } else { // body if (typeParams.size() > 0 && params.size() == 0) { @@ -537,6 +618,34 @@ private: } } } + // see https://github.com/WebAssembly/spec/pull/301 + if (type.isNull()) { + // if no function type name provided, then we generated one + std::unique_ptr<FunctionType> functionType = std::unique_ptr<FunctionType>(sigToFunctionType(getSigFromStructs(result, params))); + for (auto& existing : wasm.functionTypes) { + if (existing->structuralComparison(*functionType)) { + type = existing->name; + break; + } + } + if (!type.is()) throw ParseException("no function type [internal error?]", s.line, s.col); + } + if (importModule.is()) { + // this is an import, actually + assert(preParseImport); + std::unique_ptr<Import> im = make_unique<Import>(); + im->name = name; + im->module = importModule; + im->base = importBase; + im->kind = Import::Function; + im->functionType = wasm.getFunctionType(type); + wasm.addImport(im.release()); + assert(!currFunction); + currLocalTypes.clear(); + labelStack.clear(); + return; + } + assert(!preParseImport); if (brokeToAutoBlock) { ensureAutoBlock(); autoBlock->name = FAKE_RETURN; @@ -549,21 +658,8 @@ private: body = allocator.alloc<Nop>(); } if (currFunction->result != result) throw ParseException("bad func declaration", s.line, s.col); - // see https://github.com/WebAssembly/spec/pull/301 - if (type.isNull()) { - // if no function type name provided, then we generated one - std::unique_ptr<FunctionType> functionType = std::unique_ptr<FunctionType>(sigToFunctionType(getSig(currFunction.get()))); - for (auto& existing : wasm.functionTypes) { - if (existing->structuralComparison(*functionType)) { - type = existing->name; - break; - } - } - if (!type.is()) throw ParseException("no function type [internal error?]", s.line, s.col); - } currFunction->body = body; currFunction->type = type; - wasm.addFunction(currFunction.release()); currLocalTypes.clear(); labelStack.clear(); @@ -586,6 +682,10 @@ private: abort(); } + bool isWasmType(IString str) { + return stringToWasmType(str, true) != none; + } + public: Expression* parseExpression(Element* s) { return parseExpression(*s); @@ -594,7 +694,7 @@ public: #define abort_on(str) { throw ParseException(std::string("abort_on ") + str); } Expression* parseExpression(Element& s) { - if (!s.isList()) throw ParseException("invalid node for parseExpression, needed list", s.line, s.col); + element_assert(s.isList(), s); IString id = s[0]->str(); const char *str = id.str; const char *dot = strchr(str, '.'); @@ -954,7 +1054,7 @@ private: Expression* makeGetGlobal(Element& s) { auto ret = allocator.alloc<GetGlobal>(); - ret->name = s[1]->str(); + ret->name = getGlobalName(*s[1]); auto* global = wasm.checkGlobal(ret->name); if (global) { ret->type = global->type; @@ -970,7 +1070,8 @@ private: Expression* makeSetGlobal(Element& s) { auto ret = allocator.alloc<SetGlobal>(); - ret->name = s[1]->str(); + ret->name = getGlobalName(*s[1]); + if (wasm.checkGlobal(ret->name) && !wasm.checkGlobal(ret->name)->mutable_) throw ParseException("set_global of immutable", s.line, s.col); ret->value = parseExpression(s[2]); return ret; } @@ -985,13 +1086,23 @@ private: auto& s = *sp; size_t i = 1; if (i < s.size() && s[i]->isStr()) { - curr->name = s[i]->str(); - i++; + // could be a name or a type + if (s[i]->dollared() || stringToWasmType(s[i]->str(), true /* allowError */) == none) { + curr->name = s[i]->str(); + i++; + } else { + curr->name = getPrefixedName("block"); + } } else { curr->name = getPrefixedName("block"); } labelStack.push_back(curr->name); - if (i >= s.size()) break; // labeled empty block + if (i >= s.size()) break; // empty block + if (s[i]->isStr()) { + // block signature + i++; // TODO: parse the signature + if (i >= s.size()) break; // empty block + } auto& first = *s[i]; if (first[0]->str() == BLOCK) { // recurse @@ -1008,7 +1119,7 @@ private: auto& s = *sp; size_t i = 1; if (i < s.size()) { - if (s[i]->isStr()) { + while (i < s.size() && s[i]->isStr()) { i++; } if (t < int(stack.size()) - 1) { @@ -1126,40 +1237,33 @@ private: Expression* makeIf(Element& s) { auto ret = allocator.alloc<If>(); - ret->condition = parseExpression(s[1]); - - // ifTrue and ifFalse may get implicit blocks - auto handle = [&](const char* title, Element& s) { - Name name = getPrefixedName(title); - bool explicitThenElse = false; - if (s[0]->str() == THEN || s[0]->str() == ELSE) { - explicitThenElse = true; - if (s[1]->isStr() && s[1]->dollared()) { - name = s[1]->str(); - } - } - labelStack.push_back(name); - auto* ret = parseExpression(&s); - labelStack.pop_back(); - if (explicitThenElse) { - ret->dynCast<Block>()->name = name; - } else { - // add a block if we must - if (BreakSeeker::has(ret, name)) { - auto* block = allocator.alloc<Block>(); - block->name = name; - block->list.push_back(ret); - block->finalize(); - ret = block; - } - } - return ret; - }; - - ret->ifTrue = handle("if-true", *s[2]); - if (s.size() == 4) { - ret->ifFalse = handle("if-else", *s[3]); - ret->finalize(); + Index i = 1; + Name label; + if (s[i]->dollared()) { + // the if is labeled + label = s[i++]->str(); + } else { + label = getPrefixedName("if"); + } + labelStack.push_back(label); + if (s[i]->isStr()) { + // if type, TODO: parse? + i++; + } + ret->condition = parseExpression(s[i++]); + ret->ifTrue = parseExpression(*s[i++]); + if (i < s.size()) { + ret->ifFalse = parseExpression(*s[i++]); + } + ret->finalize(); + labelStack.pop_back(); + // create a break target if we must + if (BreakSeeker::has(ret, label)) { + auto* block = allocator.alloc<Block>(); + block->name = label; + block->list.push_back(ret); + block->finalize(); + return block; } return ret; } @@ -1182,16 +1286,20 @@ private: auto ret = allocator.alloc<Loop>(); size_t i = 1; Name out; - if (s.size() > i + 1 && s[i]->isStr() && s[i + 1]->isStr()) { // out can only be named if both are + if (s.size() > i + 1 && s[i]->dollared() && s[i + 1]->dollared()) { // out can only be named if both are out = s[i]->str(); i++; } - if (s.size() > i && s[i]->isStr()) { + if (s.size() > i && s[i]->dollared()) { ret->name = s[i]->str(); i++; } else { ret->name = getPrefixedName("loop-in"); } + if (i < s.size() && s[i]->isStr()) { + // block signature + i++; // TODO: parse the signature + } labelStack.push_back(ret->name); ret->body = makeMaybeBlock(s, i); labelStack.pop_back(); @@ -1207,8 +1315,18 @@ private: } Expression* makeCall(Element& s) { + auto target = getFunctionName(*s[1]); + auto* import = wasm.checkImport(target); + if (import && import->kind == Import::Function) { + auto ret = allocator.alloc<CallImport>(); + ret->target = target; + Import* import = wasm.getImport(ret->target); + ret->type = import->functionType->result; + parseCallOperands(s, 2, s.size(), ret); + return ret; + } auto ret = allocator.alloc<Call>(); - ret->target = s[1]->str(); + ret->target = target; ret->type = functionTypes[ret->target]; parseCallOperands(s, 2, s.size(), ret); return ret; @@ -1224,6 +1342,7 @@ private: } Expression* makeCallIndirect(Element& s) { + if (!seenTable) throw ParseException("no table"); auto ret = allocator.alloc<CallIndirect>(); IString type = s[1]->str(); auto* fullType = wasm.checkFunctionType(type); @@ -1346,12 +1465,14 @@ private: bool hasMemory = false; - void parseMemory(Element& s) { + void parseMemory(Element& s, bool preParseImport = false) { + if (hasMemory) throw ParseException("too many memories"); hasMemory = true; Index i = 1; if (s[i]->dollared()) { wasm.memory.name = s[i++]->str(); } + Name importModule, importBase; if (s[i]->isList()) { auto& inner = *s[i]; if (inner[0]->str() == EXPORT) { @@ -1359,12 +1480,17 @@ private: ex->name = inner[1]->str(); ex->value = wasm.memory.name; ex->kind = Export::Memory; + if (wasm.checkExport(ex->name)) throw ParseException("duplicate export", s.line, s.col); wasm.addExport(ex.release()); i++; + } else if (inner[0]->str() == IMPORT) { + importModule = inner[1]->str(); + importBase = inner[2]->str(); + i++; } else { assert(inner.size() > 0 ? inner[0]->str() != IMPORT : true); // (memory (data ..)) format - parseData(*s[i]); + parseInnerData(*s[i]); wasm.memory.initial = wasm.memory.segments[0].data.size(); return; } @@ -1404,13 +1530,15 @@ private: void parseData(Element& s) { if (!hasMemory) throw ParseException("data but no memory"); Index i = 1; - Expression* offset; - if (i < s.size() && s[i]->isList()) { - // there is an init expression - offset = parseExpression(s[i++]); - } else { - offset = allocator.alloc<Const>()->set(Literal(int32_t(0))); + if (!s[i]->isList()) { + // the memory is named + i++; } + auto* offset = parseExpression(s[i++]); + parseInnerData(s, i, offset); + } + + void parseInnerData(Element& s, Index i = 1, Expression* offset = nullptr) { std::vector<char> data; while (i < s.size()) { const char *input = s[i++]->c_str(); @@ -1418,6 +1546,9 @@ private: stringToBinary(input, size, data); } } + if (!offset) { + offset = allocator.alloc<Const>()->set(Literal(int32_t(0))); + } wasm.memory.segments.emplace_back(offset, data.data(), data.size()); } @@ -1426,33 +1557,29 @@ private: ex->name = s[1]->str(); if (s[2]->isList()) { auto& inner = *s[2]; + ex->value = inner[1]->str(); if (inner[0]->str() == FUNC) { - ex->value = inner[1]->str(); ex->kind = Export::Function; } else if (inner[0]->str() == MEMORY) { if (!hasMemory) throw ParseException("memory exported but no memory"); - ex->value = Name::fromInt(0); ex->kind = Export::Memory; } else if (inner[0]->str() == TABLE) { - ex->value = Name::fromInt(0); ex->kind = Export::Table; } else if (inner[0]->str() == GLOBAL) { - ex->value = inner[1]->str(); - ex->kind = Export::Table; + ex->kind = Export::Global; + if (wasm.checkGlobal(ex->value) && wasm.getGlobal(ex->value)->mutable_) throw ParseException("cannot export a mutable global", s.line, s.col); } else { WASM_UNREACHABLE(); } } else if (!s[2]->dollared() && !std::isdigit(s[2]->str()[0])) { + ex->value = s[3]->str(); if (s[2]->str() == MEMORY) { if (!hasMemory) throw ParseException("memory exported but no memory"); - ex->value = Name::fromInt(0); ex->kind = Export::Memory; } else if (s[2]->str() == TABLE) { - ex->value = Name::fromInt(0); ex->kind = Export::Table; } else if (s[2]->str() == GLOBAL) { - ex->value = s[3]->str(); - ex->kind = Export::Table; + ex->kind = Export::Global; } else { WASM_UNREACHABLE(); } @@ -1461,6 +1588,7 @@ private: ex->value = s[2]->str(); ex->kind = Export::Function; } + if (wasm.checkExport(ex->name)) throw ParseException("duplicate export", s.line, s.col); wasm.addExport(ex.release()); } @@ -1473,22 +1601,39 @@ private: im->kind = Import::Function; } else if ((*s[3])[0]->str() == MEMORY) { im->kind = Import::Memory; + if (hasMemory) throw ParseException("too many memories"); + hasMemory = true; } else if ((*s[3])[0]->str() == TABLE) { im->kind = Import::Table; + if (seenTable) throw ParseException("more than one table"); + seenTable = true; } else if ((*s[3])[0]->str() == GLOBAL) { im->kind = Import::Global; } else { newStyle = false; // either (param..) or (result..) } } + Index newStyleInner = 1; if (s.size() > 3 && s[3]->isStr()) { im->name = s[i++]->str(); - } else if (newStyle) { - im->name = (*s[3])[1]->str(); - } else { - im->name = Name::fromInt(importCounter); + } else if (newStyle && newStyleInner < s[3]->size() && (*s[3])[newStyleInner]->dollared()) { + im->name = (*s[3])[newStyleInner++]->str(); + } + if (!im->name.is()) { + if (im->kind == Import::Function) { + im->name = Name("import$function$" + std::to_string(functionCounter++)); + functionNames.push_back(im->name); + } else if (im->kind == Import::Global) { + im->name = Name("import$global" + std::to_string(globalCounter++)); + globalNames.push_back(im->name); + } else if (im->kind == Import::Memory) { + im->name = Name("import$memory$" + std::to_string(0)); + } else if (im->kind == Import::Table) { + im->name = Name("import$table$" + std::to_string(0)); + } else { + WASM_UNREACHABLE(); + } } - importCounter++; if (!s[i]->quoted()) { if (s[i]->str() == MEMORY) { im->kind = Import::Memory; @@ -1508,7 +1653,7 @@ private: im->base = s[i++]->str(); // parse internals Element& inner = newStyle ? *s[3] : s; - Index j = newStyle ? 2 : i; + Index j = newStyle ? newStyleInner : i; if (im->kind == Import::Function) { std::unique_ptr<FunctionType> type = make_unique<FunctionType>(); if (inner.size() > j) { @@ -1535,51 +1680,115 @@ private: } im->functionType = ensureFunctionType(getSig(type.get()), &wasm); } else if (im->kind == Import::Global) { - im->globalType = stringToWasmType(inner[j]->str()); + if (inner[j]->isStr()) { + im->globalType = stringToWasmType(inner[j]->str()); + } else { + auto& inner2 = *inner[j]; + assert(inner2[0]->str() == MUT); + im->globalType = stringToWasmType(inner2[1]->str()); + throw ParseException("cannot import a mutable global", s.line, s.col); + } + } else if (im->kind == Import::Table) { + if (j < inner.size() - 1) { + wasm.table.initial = atoi(inner[j++]->c_str()); + } + if (j < inner.size() - 1) { + wasm.table.max = atoi(inner[j++]->c_str()); + } else { + wasm.table.max = wasm.table.initial; + } + // ends with the table element type + } else if (im->kind == Import::Memory) { + if (j < inner.size()) { + wasm.memory.initial = atoi(inner[j++]->c_str()); + } + if (j < inner.size()) { + wasm.memory.max = atoi(inner[j++]->c_str()); + } else { + wasm.memory.max = wasm.memory.initial; + } } wasm.addImport(im.release()); } - void parseGlobal(Element& s) { + void parseGlobal(Element& s, bool preParseImport = false) { std::unique_ptr<Global> global = make_unique<Global>(); size_t i = 1; - if (s[i]->dollared()) { + if (s[i]->dollared() && !(s[i]->isStr() && isWasmType(s[i]->str()))) { global->name = s[i++]->str(); } else { global->name = Name::fromInt(globalCounter); } globalCounter++; - if (s[i]->isList()) { + globalNames.push_back(global->name); + bool mutable_ = false; + WasmType type = none; + bool exported = false; + Name importModule, importBase; + while (i < s.size() && s[i]->isList()) { auto& inner = *s[i]; if (inner[0]->str() == EXPORT) { auto ex = make_unique<Export>(); ex->name = inner[1]->str(); ex->value = global->name; ex->kind = Export::Global; + if (wasm.checkExport(ex->name)) throw ParseException("duplicate export", s.line, s.col); wasm.addExport(ex.release()); + exported = true; + i++; + } else if (inner[0]->str() == IMPORT) { + importModule = inner[1]->str(); + importBase = inner[2]->str(); + i++; + } else if (inner[0]->str() == MUT) { + mutable_ = true; + type = stringToWasmType(inner[1]->str()); i++; } else { - WASM_UNREACHABLE(); + break; } } - global->type = stringToWasmType(s[i++]->str()); - global->init = parseExpression(s[i++]); + if (exported && mutable_) throw ParseException("cannot export a mutable global", s.line, s.col); + if (type == none) { + type = stringToWasmType(s[i++]->str()); + } + if (importModule.is()) { + // this is an import, actually + assert(preParseImport); + if (mutable_) throw ParseException("cannot import a mutable global", s.line, s.col); + std::unique_ptr<Import> im = make_unique<Import>(); + im->name = global->name; + im->module = importModule; + im->base = importBase; + im->kind = Import::Global; + im->globalType = type; + wasm.addImport(im.release()); + return; + } + assert(!preParseImport); + global->type = type; + if (i < s.size()) { + global->init = parseExpression(s[i++]); + } else { + throw ParseException("global without init", s.line, s.col); + } + global->mutable_ = mutable_; assert(i == s.size()); wasm.addGlobal(global.release()); } bool seenTable = false; - void parseTable(Element& s) { + void parseTable(Element& s, bool preParseImport = false) { + if (seenTable) throw ParseException("more than one table"); seenTable = true; Index i = 1; if (i == s.size()) return; // empty table in old notation -#if 0 // TODO: new table notation if (s[i]->dollared()) { wasm.table.name = s[i++]->str(); } -#endif if (i == s.size()) return; + Name importModule, importBase; if (s[i]->isList()) { auto& inner = *s[i]; if (inner[0]->str() == EXPORT) { @@ -1587,8 +1796,13 @@ private: ex->name = inner[1]->str(); ex->value = wasm.table.name; ex->kind = Export::Table; + if (wasm.checkExport(ex->name)) throw ParseException("duplicate export", s.line, s.col); wasm.addExport(ex.release()); i++; + } else if (inner[0]->str() == IMPORT) { + importModule = inner[1]->str(); + importBase = inner[2]->str(); + i++; } else { WASM_UNREACHABLE(); } @@ -1597,30 +1811,48 @@ private: if (!s[i]->dollared()) { if (s[i]->str() == ANYFUNC) { // (table type (elem ..)) - parseElem(*s[i + 1]); - wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + parseInnerElem(*s[i + 1]); + if (wasm.table.segments.size() > 0) { + wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + } else { + wasm.table.initial = wasm.table.max = 0; + } return; } // first element isn't dollared, and isn't anyfunc. this could be old syntax for (table 0 1) which means function 0 and 1, or it could be (table initial max? type), look for type if (s[s.size() - 1]->str() == ANYFUNC) { // (table initial max? type) - wasm.table.initial = atoi(s[i]->c_str()); - wasm.table.max = atoi(s[i + 1]->c_str()); + if (i < s.size() - 1) { + wasm.table.initial = atoi(s[i++]->c_str()); + } + if (i < s.size() - 1) { + wasm.table.max = atoi(s[i++]->c_str()); + } return; } } // old notation (table func1 func2 ..) - parseElem(s, i); - wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + parseInnerElem(s, i); + if (wasm.table.segments.size() > 0) { + wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + } else { + wasm.table.initial = wasm.table.max = 0; + } } - void parseElem(Element& s, Index i = 1) { + void parseElem(Element& s) { + Index i = 1; + if (!s[i]->isList()) { + // the table is named + i++; + } + auto* offset = parseExpression(s[i++]); + parseInnerElem(s, i, offset); + } + + void parseInnerElem(Element& s, Index i = 1, Expression* offset = nullptr) { if (!seenTable) throw ParseException("elem without table", s.line, s.col); - Expression* offset; - if (s[i]->isList()) { - // there is an init expression - offset = parseExpression(s[i++]); - } else { + if (!offset) { offset = allocator.alloc<Const>()->set(Literal(int32_t(0))); } Table::Segment segment(offset); @@ -1650,6 +1882,10 @@ private: type->result = stringToWasmType(curr[1]->str()); } } + if (!type->name.is()) { + type->name = Name::fromInt(wasm.functionTypes.size()); + } + functionTypeNames.push_back(type->name); wasm.addFunctionType(type.release()); } }; diff --git a/src/wasm-validator.h b/src/wasm-validator.h index e23221337..988d1104d 100644 --- a/src/wasm-validator.h +++ b/src/wasm-validator.h @@ -179,7 +179,6 @@ public: } } void visitCallIndirect(CallIndirect *curr) { - shouldBeTrue(getModule()->table.segments.size() > 0, curr, "no table"); auto* type = getModule()->checkFunctionType(curr->fullType); if (!shouldBeTrue(!!type, curr, "call_indirect type must exist")) return; shouldBeEqualOrFirstIsUnreachable(curr->target->type, i32, curr, "indirect call target must be an i32"); @@ -335,7 +334,7 @@ public: } void visitGlobal(Global* curr) { - shouldBeTrue(curr->init->is<Const>(), curr->name, "global init must be valid"); + shouldBeTrue(curr->init->is<Const>() || curr->init->is<GetGlobal>(), curr->name, "global init must be valid"); shouldBeEqual(curr->type, curr->init->type, nullptr, "global init must have correct type"); } @@ -394,7 +393,15 @@ public: break; } } - shouldBeTrue(found, name, "module exports must be found"); + shouldBeTrue(found, name, "module function exports must be found"); + } else if (exp->kind == Export::Global) { + shouldBeTrue(curr->checkGlobal(name), name, "module global exports must be found"); + } else if (exp->kind == Export::Table) { + shouldBeTrue(name == Name("0") || name == curr->table.name, name, "module table exports must be found"); + } else if (exp->kind == Export::Memory) { + shouldBeTrue(name == Name("0") || name == curr->memory.name, name, "module memory exports must be found"); + } else { + WASM_UNREACHABLE(); } Name exportName = exp->name; shouldBeFalse(exportNames.count(exportName) > 0, exportName, "module exports must be unique"); diff --git a/src/wasm.cpp b/src/wasm.cpp index f6486eb50..a910575f0 100644 --- a/src/wasm.cpp +++ b/src/wasm.cpp @@ -72,12 +72,9 @@ Name GROW_WASM_MEMORY("__growWasmMemory"), BR("br"), ANYFUNC("anyfunc"), FAKE_RETURN("fake_return_waka123"), - ASSERT_RETURN("assert_return"), - ASSERT_TRAP("assert_trap"), - ASSERT_INVALID("assert_invalid"), + MUT("mut"), SPECTEST("spectest"), PRINT("print"), - INVOKE("invoke"), EXIT("exit"); // core AST type checking diff --git a/src/wasm.h b/src/wasm.h index 4d5cf4d70..53b4e2938 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1027,7 +1027,11 @@ public: void finalize() { if (condition) { - type = none; + if (value) { + type = value->type; + } else { + type = none; + } } else { type = unreachable; } @@ -1519,6 +1523,7 @@ public: Name name; WasmType type; Expression* init; + bool mutable_; }; class Module { @@ -1547,12 +1552,6 @@ private: public: Module() {}; - FunctionType* getFunctionType(Index i) { assert(i < functionTypes.size()); return functionTypes[i].get(); } - Import* getImport(Index i) { assert(i < imports.size()); return imports[i].get(); } - Export* getExport(Index i) { assert(i < exports.size()); return exports[i].get(); } - Function* getFunction(Index i) { assert(i < functions.size()); return functions[i].get(); } - Global* getGlobal(Index i) { assert(i < globals.size()); return globals[i].get(); } - FunctionType* getFunctionType(Name name) { assert(functionTypesMap.count(name)); return functionTypesMap[name]; } Import* getImport(Name name) { assert(importsMap.count(name)); return importsMap[name]; } Export* getExport(Name name) { assert(exportsMap.count(name)); return exportsMap[name]; } @@ -1565,56 +1564,35 @@ public: Function* checkFunction(Name name) { if (!functionsMap.count(name)) return nullptr; return functionsMap[name]; } Global* checkGlobal(Name name) { if (!globalsMap.count(name)) return nullptr; return globalsMap[name]; } - FunctionType* checkFunctionType(Index i) { if (i >= functionTypes.size()) return nullptr; return functionTypes[i].get(); } - Import* checkImport(Index i) { if (i >= imports.size()) return nullptr; return imports[i].get(); } - Export* checkExport(Index i) { if (i >= exports.size()) return nullptr; return exports[i].get(); } - Function* checkFunction(Index i) { if (i >= functions.size()) return nullptr; return functions[i].get(); } - Global* checkGlobal(Index i) { if (i >= globals.size()) return nullptr; return globals[i].get(); } - void addFunctionType(FunctionType* curr) { - Name numericName = Name::fromInt(functionTypes.size()); // TODO: remove all these, assert on names already existing, do numeric stuff in wasm-s-parser etc. - if (curr->name.isNull()) { - curr->name = numericName; - } + assert(curr->name.is()); functionTypes.push_back(std::unique_ptr<FunctionType>(curr)); + assert(functionTypesMap.find(curr->name) == functionTypesMap.end()); functionTypesMap[curr->name] = curr; - functionTypesMap[numericName] = curr; } void addImport(Import* curr) { - Name numericName = Name::fromInt(imports.size()); - if (curr->name.isNull()) { - curr->name = numericName; - } + assert(curr->name.is()); imports.push_back(std::unique_ptr<Import>(curr)); + assert(importsMap.find(curr->name) == importsMap.end()); importsMap[curr->name] = curr; - importsMap[numericName] = curr; } void addExport(Export* curr) { - Name numericName = Name::fromInt(exports.size()); - if (curr->name.isNull()) { - curr->name = numericName; - } + assert(curr->name.is()); exports.push_back(std::unique_ptr<Export>(curr)); + assert(exportsMap.find(curr->name) == exportsMap.end()); exportsMap[curr->name] = curr; - exportsMap[numericName] = curr; } void addFunction(Function* curr) { - Name numericName = Name::fromInt(functions.size()); - if (curr->name.isNull()) { - curr->name = numericName; - } + assert(curr->name.is()); functions.push_back(std::unique_ptr<Function>(curr)); + assert(functionsMap.find(curr->name) == functionsMap.end()); functionsMap[curr->name] = curr; - functionsMap[numericName] = curr; } void addGlobal(Global* curr) { - Name numericName = Name::fromInt(globals.size()); - if (curr->name.isNull()) { - curr->name = numericName; - } + assert(curr->name.is()); globals.push_back(std::unique_ptr<Global>(curr)); + assert(globalsMap.find(curr->name) == globalsMap.end()); globalsMap[curr->name] = curr; - globalsMap[numericName] = curr; } void addStart(const Name &s) { |