diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 2b8790a4fae..d915c0b3fdf 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -36,6 +36,7 @@ set(passes_SOURCES InstrumentMemory.cpp LegalizeJSInterface.cpp LimitSegments.cpp + LLVM.cpp LocalCSE.cpp LogExecution.cpp LoopInvariantCodeMotion.cpp diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp new file mode 100644 index 00000000000..9d4af15d331 --- /dev/null +++ b/src/passes/LLVM.cpp @@ -0,0 +1,182 @@ +/* + * Copyright 2021 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. + */ + +// +// Run LLVM to optimize the wasm. +// + +#include "ir/module-utils.h" +#include "pass.h" +#include "support/process.h" +#include "support/string.h" +#include "wasm-builder.h" +#include "wasm-io.h" +#include "wasm.h" + +namespace wasm { + +struct LLVMOpt : public Pass { + void run(PassRunner* runner, Module* module) override { + std::string base = runner->options.getArgument( + "llvm-opt", + "LLVMOpt usage: wasm-opt --llvm-opt=BASENAME\n" + " BASENAME will be used as the base name for the temporary files that\n" + " we create (BASENAME.1.wasm, BASENAME.2.c, etc."); + + std::cout << "[LLVMOpt] preprocessing...\n"; + + // Save features, which would not otherwise make it through a round trip if + // the target features section has been stripped. + auto features = module->features; + + // Note the original exports, which are the only things we will want to have + // exported at the end. + std::set originalExports; + for (auto& e : module->exports) { + originalExports.insert(e->name); + } + // We will add additional exports, some of which we can forget about + // imediately, but others we need to keep around until almost the very end + // because we need to use them. + // For example, we add an export for the start function if there is one, and + // we find it in the output wasm and use it to find the start function. We + // can then remove the export + std::set usedExports; + + // Add exports for more things that we need, either for the wasm2c runtime, + // or to make it easy for us to find what we need afterwards. (We will + // remove all non-original exports at the end anyhow.) + Builder builder(*module); + + // Ensure there is a memory. + module->memory.exists = true; + // Ensure the memory is exported, as the wasm2c runtime uses it + if (!module->getExportOrNull("memory")) { + module->addExport( + builder.makeExport("memory", "0", ExternalKind::Memory)); + } + // Ensure a _start is exported, which wasm2c expects. + if (!module->getExportOrNull("_start")) { + Name name("byn_llvm_runtime_start"); + module->addFunction(builder.makeFunction( + name, {Type::none, Type::none}, {}, builder.makeNop())); + module->addExport( + builder.makeExport("_start", name, ExternalKind::Function)); + } + // Export the wasm start function, if there is one. + Name wasmStartExport("byn_llvm_wasm_start"); + if (module->start.is()) { + module->addExport( + builder.makeExport(wasmStartExport, module->start, ExternalKind::Function)); + usedExports.insert(wasmStartExport); + } + + // Write the module to a temp file. + std::cout << "[LLVMOpt] writing wasm...\n"; + std::string tempWasmA = base + ".1.wasm"; + ModuleWriter().writeBinary(*module, tempWasmA); + // Compile the wasm to C, which we do by "compiling" it to wasm + wasm2c. By + // running emcc with --post-link, the input wasm file is left as is, and we + // only do the wasm2c bit. + std::cout << "[LLVMOpt] wasm => C...\n"; + std::string tempWasmB = base + ".2.wasm"; + std::string tempC = tempWasmB + ".c"; + ProgramResult wasm2c( + "emcc " + tempWasmA + " -o " + tempWasmB + + " -s WASM2C --post-link -s ASSERTIONS=0 -s ERROR_ON_UNDEFINED_SYMBOLS=0"); + if (wasm2c.failed()) { + wasm2c.dump(std::cout); + Fatal() << "LLVMOpt: failed to convert to C"; + } + // Modify the C. + std::cout << "[LLVMOpt] rewrite C...\n"; + auto cCode(read_file(tempC, Flags::Text)); + // Remove "static" as we want direct access to the wasm exports actually. + String::replaceAll(cCode, "static ", ""); + // Remove extra sandboxing: we are compiling back to wasm! + String::replaceAll(cCode, "FUNC_PROLOGUE;", ""); + String::replaceAll(cCode, "FUNC_EPILOGUE;", ""); + Output(tempC, Flags::Text).getStream() << cCode; + // Compile the C to wasm. + std::cout << "[LLVMOpt] C => LLVM optimizer => wasm...\n"; + std::string tempWasmC = base + ".3.wasm"; + std::string cmd = + "emcc " + tempC + " -o " + tempWasmC + " -O1 -s EXPORTED_FUNCTIONS="; + bool first = true; + auto addExports = [&](const std::set& names) { + for (auto e : names) { + if (first) { + first = false; + } else { + cmd += ','; + } + cmd += std::string("_w2c_") + e.str; + } + }; + addExports(originalExports); + addExports(usedExports); + ProgramResult c2wasm(cmd); + if (c2wasm.failed()) { + c2wasm.dump(std::cout); + Fatal() << "LLVMOpt: failed to convert to wasm"; + } + // Clear the module in preparation for reading, and read it. + ModuleUtils::clearModule(*module); + std::cout << "[LLVMOpt] reading wasm...\n"; + ModuleReader().readBinary(tempWasmC, *module); + // Filter out any new exports, and rename the existing ones. + module->exports.erase(std::remove_if(module->exports.begin(), + module->exports.end(), + [&](const std::unique_ptr& e) { + // The exports we want to erase are + // all those that are not one of our + // original exports, whose name is + // now "w2c_${ORIGINAL_NAME}" + if (!e->name.startsWith("w2c_")) { + return true; + } + e->name = e->name.str + 4; + return false; + }), + module->exports.end()); + module->updateMaps(); + // Find the important things we exported so that we could find them later. + if (auto* e = module->getExportOrNull(wasmStartExport)) { + module->start = e->value; + } + // Remove the table: the "native" table contains things the new sandboxing + // layer in C added and needs. We want to look into the wasm in that + // sandbox. + // TODO: find the sandboxed table items (export them first) + module->tables.clear(); + module->elementSegments.clear(); + module->updateMaps(); + // Do a cleanup (we may optimize anyhow, though?) + { + std::cout << "[LLVMOpt] postprocessing...\n"; + PassRunner postRunner(runner); + postRunner.add("remove-unused-module-elements"); + postRunner.setIsNested(true); + postRunner.run(); + } + // Reapply features + module->features = features; + } +}; + +Pass* createLLVMOptPass() { return new LLVMOpt(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 89b3672729b..d38ce3b0fb5 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -165,6 +165,7 @@ void PassRegistry::registerPasses() { "legalizes i64 types on the import/export boundary in a minimal " "manner, only on things only JS will call", createLegalizeJSInterfaceMinimallyPass); + registerPass("llvm-opt", "run the LLVM optimizer (!)", createLLVMOptPass); registerPass("local-cse", "common subexpression elimination inside basic blocks", createLocalCSEPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 4cc8040a4e0..db1d9a6057b 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -61,6 +61,7 @@ Pass* createLocalCSEPass(); Pass* createLogExecutionPass(); Pass* createInstrumentLocalsPass(); Pass* createInstrumentMemoryPass(); +Pass* createLLVMOptPass(); Pass* createLoopInvariantCodeMotionPass(); Pass* createMemory64LoweringPass(); Pass* createMemoryPackingPass(); diff --git a/src/support/process.h b/src/support/process.h new file mode 100644 index 00000000000..029f0af9fbe --- /dev/null +++ b/src/support/process.h @@ -0,0 +1,214 @@ +/* + * Copyright 2021 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. + */ + +// +// Process helpers. +// + +#ifndef wasm_support_process_h +#define wasm_support_process_h + +#include +#include + +#include "support/timing.h" + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +// Create a string with last error message +std::string GetLastErrorStdStr() { + DWORD error = GetLastError(); + if (error) { + LPVOID lpMsgBuf; + DWORD bufLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf, + 0, + NULL); + if (bufLen) { + LPCSTR lpMsgStr = (LPCSTR)lpMsgBuf; + std::string result(lpMsgStr, lpMsgStr + bufLen); + LocalFree(lpMsgBuf); + return result; + } + } + return std::string(); +} +#endif + +namespace wasm { + +struct ProgramResult { + size_t timeout; + + std::string command; + + int code; + std::string output; + double time; + + ProgramResult(size_t timeout = -1) : timeout(timeout) {} + ProgramResult(std::string command, size_t timeout = -1) : timeout(timeout) { + getFromExecution(command); + } + +#ifdef _WIN32 + void getFromExecution(std::string command_) { + command = command_; + + Timer timer; + timer.start(); + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + HANDLE hChildStd_OUT_Rd; + HANDLE hChildStd_OUT_Wr; + + if ( + // Create a pipe for the child process's STDOUT. + !CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0) || + // Ensure the read handle to the pipe for STDOUT is not inherited. + !SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { + Fatal() << "CreatePipe \"" << command + << "\" failed: " << GetLastErrorStdStr() << ".\n"; + } + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.hStdError = hChildStd_OUT_Wr; + si.hStdOutput = hChildStd_OUT_Wr; + si.dwFlags |= STARTF_USESTDHANDLES; + ZeroMemory(&pi, sizeof(pi)); + + // Start the child process. + if (!CreateProcess(NULL, // No module name (use command line) + (LPSTR)command.c_str(), // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + TRUE, // Set handle inheritance to TRUE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi) // Pointer to PROCESS_INFORMATION structure + ) { + Fatal() << "CreateProcess \"" << command + << "\" failed: " << GetLastErrorStdStr() << ".\n"; + } + + // Wait until child process exits. + DWORD retVal = WaitForSingleObject(pi.hProcess, timeout * 1000); + if (retVal == WAIT_TIMEOUT) { + printf("Command timeout: %s", command.c_str()); + TerminateProcess(pi.hProcess, -1); + } + DWORD dwordExitCode; + if (!GetExitCodeProcess(pi.hProcess, &dwordExitCode)) { + Fatal() << "GetExitCodeProcess failed: " << GetLastErrorStdStr() << ".\n"; + } + code = (int)dwordExitCode; + + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + // Read output from the child process's pipe for STDOUT + // Stop when there is no more data. + { + const int BUFSIZE = 4096; + DWORD dwRead, dwTotal, dwTotalRead = 0; + CHAR chBuf[BUFSIZE]; + BOOL bSuccess = FALSE; + + PeekNamedPipe(hChildStd_OUT_Rd, NULL, 0, NULL, &dwTotal, NULL); + while (dwTotalRead < dwTotal) { + bSuccess = + ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE - 1, &dwRead, NULL); + if (!bSuccess || dwRead == 0) + break; + chBuf[dwRead] = 0; + dwTotalRead += dwRead; + output.append(chBuf); + } + } + timer.stop(); + time = timer.getTotal(); + } +#else // POSIX + // runs the command and notes the output + // TODO: also stderr, not just stdout? + void getFromExecution(std::string command_) { + command = command_; + + Timer timer; + timer.start(); + // do this using just core stdio.h and stdlib.h, for portability + // sadly this requires two invokes + code = system(("timeout " + std::to_string(timeout) + "s " + command + + " > /dev/null 2> /dev/null") + .c_str()); + const int MAX_BUFFER = 1024; + char buffer[MAX_BUFFER]; + FILE* stream = popen( + ("timeout " + std::to_string(timeout) + "s " + command + " 2> /dev/null") + .c_str(), + "r"); + while (fgets(buffer, MAX_BUFFER, stream) != NULL) { + output.append(buffer); + } + pclose(stream); + timer.stop(); + time = timer.getTotal() / 2; + } +#endif // _WIN32 + + bool operator==(ProgramResult& other) { + return code == other.code && output == other.output; + } + bool operator!=(ProgramResult& other) { return !(*this == other); } + + bool failed() { return code != 0; } + + void dump(std::ostream& o) { + o << "[ProgramResult " << command << "]\ncode: " << code << ", stdout: \n" + << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; + } +}; + +} // namespace wasm + +namespace std { + +inline std::ostream& operator<<(std::ostream& o, wasm::ProgramResult& result) { + result.dump(o); + return o; +} + +} // namespace std + +#endif // wasm_support_process_h diff --git a/src/support/string.h b/src/support/string.h index 120835b80d3..bee8b0b7aa0 100644 --- a/src/support/string.h +++ b/src/support/string.h @@ -120,6 +120,15 @@ inline bool isNumber(const std::string& str) { return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); } +inline void +replaceAll(std::string& str, const std::string& from, const std::string& to) { + size_t start = 0; + while ((start = str.find(from, start)) != std::string::npos) { + str.replace(start, from.length(), to); + start += to.length(); + } +} + } // namespace String } // namespace wasm diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 48435fac0d3..e9bc3d29f20 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -36,39 +36,12 @@ #include "support/command-line.h" #include "support/file.h" #include "support/path.h" +#include "support/process.h" #include "support/timing.h" #include "wasm-builder.h" #include "wasm-io.h" #include "wasm-validator.h" -#ifdef _WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -// Create a string with last error message -std::string GetLastErrorStdStr() { - DWORD error = GetLastError(); - if (error) { - LPVOID lpMsgBuf; - DWORD bufLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&lpMsgBuf, - 0, - NULL); - if (bufLen) { - LPCSTR lpMsgStr = (LPCSTR)lpMsgBuf; - std::string result(lpMsgStr, lpMsgStr + bufLen); - LocalFree(lpMsgBuf); - return result; - } - } - return std::string(); -} -#endif + using namespace wasm; // A timeout on every execution of the command. @@ -78,148 +51,8 @@ static size_t timeout = 2; // default of enabling all features should work in most cases. static std::string extraFlags = "-all"; -struct ProgramResult { - int code; - std::string output; - double time; - - ProgramResult() = default; - ProgramResult(std::string command) { getFromExecution(command); } - -#ifdef _WIN32 - void getFromExecution(std::string command) { - Timer timer; - timer.start(); - SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - HANDLE hChildStd_OUT_Rd; - HANDLE hChildStd_OUT_Wr; - - if ( - // Create a pipe for the child process's STDOUT. - !CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0) || - // Ensure the read handle to the pipe for STDOUT is not inherited. - !SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { - Fatal() << "CreatePipe \"" << command - << "\" failed: " << GetLastErrorStdStr() << ".\n"; - } - - STARTUPINFO si; - PROCESS_INFORMATION pi; - - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - si.hStdError = hChildStd_OUT_Wr; - si.hStdOutput = hChildStd_OUT_Wr; - si.dwFlags |= STARTF_USESTDHANDLES; - ZeroMemory(&pi, sizeof(pi)); - - // Start the child process. - if (!CreateProcess(NULL, // No module name (use command line) - (LPSTR)command.c_str(), // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - TRUE, // Set handle inheritance to TRUE - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi) // Pointer to PROCESS_INFORMATION structure - ) { - Fatal() << "CreateProcess \"" << command - << "\" failed: " << GetLastErrorStdStr() << ".\n"; - } - - // Wait until child process exits. - DWORD retVal = WaitForSingleObject(pi.hProcess, timeout * 1000); - if (retVal == WAIT_TIMEOUT) { - printf("Command timeout: %s", command.c_str()); - TerminateProcess(pi.hProcess, -1); - } - DWORD dwordExitCode; - if (!GetExitCodeProcess(pi.hProcess, &dwordExitCode)) { - Fatal() << "GetExitCodeProcess failed: " << GetLastErrorStdStr() << ".\n"; - } - code = (int)dwordExitCode; - - // Close process and thread handles. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - // Read output from the child process's pipe for STDOUT - // Stop when there is no more data. - { - const int BUFSIZE = 4096; - DWORD dwRead, dwTotal, dwTotalRead = 0; - CHAR chBuf[BUFSIZE]; - BOOL bSuccess = FALSE; - - PeekNamedPipe(hChildStd_OUT_Rd, NULL, 0, NULL, &dwTotal, NULL); - while (dwTotalRead < dwTotal) { - bSuccess = - ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE - 1, &dwRead, NULL); - if (!bSuccess || dwRead == 0) - break; - chBuf[dwRead] = 0; - dwTotalRead += dwRead; - output.append(chBuf); - } - } - timer.stop(); - time = timer.getTotal(); - } -#else // POSIX - // runs the command and notes the output - // TODO: also stderr, not just stdout? - void getFromExecution(std::string command) { - Timer timer; - timer.start(); - // do this using just core stdio.h and stdlib.h, for portability - // sadly this requires two invokes - code = system(("timeout " + std::to_string(timeout) + "s " + command + - " > /dev/null 2> /dev/null") - .c_str()); - const int MAX_BUFFER = 1024; - char buffer[MAX_BUFFER]; - FILE* stream = popen( - ("timeout " + std::to_string(timeout) + "s " + command + " 2> /dev/null") - .c_str(), - "r"); - while (fgets(buffer, MAX_BUFFER, stream) != NULL) { - output.append(buffer); - } - pclose(stream); - timer.stop(); - time = timer.getTotal() / 2; - } -#endif // _WIN32 - - bool operator==(ProgramResult& other) { - return code == other.code && output == other.output; - } - bool operator!=(ProgramResult& other) { return !(*this == other); } - - bool failed() { return code != 0; } - - void dump(std::ostream& o) { - o << "[ProgramResult] code: " << code << " stdout: \n" - << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; - } -}; - -namespace std { - -inline std::ostream& operator<<(std::ostream& o, ProgramResult& result) { - result.dump(o); - return o; -} - -} // namespace std - -ProgramResult expected; +// The expected outcome. +static ProgramResult expected; // Removing functions is extremely beneficial and efficient. We aggressively // try to remove functions, unless we've seen they can't be removed, in which @@ -296,12 +129,12 @@ struct Reducer if (verbose) { std::cerr << "| trying pass command: " << currCommand << "\n"; } - if (!ProgramResult(currCommand).failed()) { + if (!ProgramResult(currCommand, timeout).failed()) { auto newSize = file_size(test); if (newSize < oldSize) { // the pass didn't fail, and the size looks smaller, so promising // see if it is still has the property we are preserving - if (ProgramResult(command) == expected) { + if (ProgramResult(command, timeout) == expected) { std::cerr << "| command \"" << currCommand << "\" succeeded, reduced size to " << newSize << '\n'; copy_file(test, working); @@ -1273,14 +1106,14 @@ int main(int argc, const char* argv[]) { std::ofstream dst(test, std::ios::binary); dst << "waka waka\n"; } - ProgramResult resultOnInvalid(command); + ProgramResult resultOnInvalid(command, timeout); if (resultOnInvalid == expected) { // Try it on a valid input. Module emptyModule; ModuleWriter writer; writer.setBinary(true); writer.write(emptyModule, test); - ProgramResult resultOnValid(command); + ProgramResult resultOnValid(command, timeout); if (resultOnValid == expected) { Fatal() << "running the command on the given input gives the same result as " @@ -1300,11 +1133,11 @@ int main(int argc, const char* argv[]) { if (!binary) { cmd += " -S "; } - ProgramResult readWrite(cmd); + ProgramResult readWrite(cmd, timeout); if (readWrite.failed()) { stopIfNotForced("failed to read and write the binary", readWrite); } else { - ProgramResult result(command); + ProgramResult result(command, timeout); if (result != expected) { stopIfNotForced("running command on the canonicalized module should " "give the same results", diff --git a/test/passes/llvm.passes b/test/passes/llvm.passes new file mode 100644 index 00000000000..1b301176f47 --- /dev/null +++ b/test/passes/llvm.passes @@ -0,0 +1 @@ +llvm-opt=foo diff --git a/test/passes/llvm.txt b/test/passes/llvm.txt new file mode 100644 index 00000000000..f5dc878b407 --- /dev/null +++ b/test/passes/llvm.txt @@ -0,0 +1,26 @@ +[LLVMOpt] preprocessing... +[LLVMOpt] writing wasm... +[LLVMOpt] wasm => C... +[LLVMOpt] rewrite C... +[LLVMOpt] C => LLVM optimizer => wasm... +[LLVMOpt] reading wasm... +[LLVMOpt] postprocessing... +(module + (type $none_=>_i32 (func (result i32))) + (type $none_=>_none (func)) + (memory $0 256 256) + (data (i32.const 1024) "-+ 0X0x\00(null)\00[wasm trap %d, halting]\n\00\00\00\00\00\00\00\11\00\n\00\11\11\11\00\00\00\00\05\00\00\00\00\00\00\t\00\00\00\00\0b\00\00\00\00\00\00\00\00\11\00\0f\n\11\11\11\03\n\07\00\01\00\t\0b\0b\00\00\t\06\0b\00\00\0b\00\06\11\00\00\00\11\11\11\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0b\00\00\00\00\00\00\00\00\11\00\n\n\11\11\11\00\n\00\00\02\00\t\0b\00\00\00\t\00\0b\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\0c\00\00\00\00\t\0c\00\00\00\00\00\0c\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0e\00\00\00\00\00\00\00\00\00\00\00\0d\00\00\00\04\0d\00\00\00\00\t\0e\00\00\00\00\00\0e\00\00\0e\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\00\00\00\00\0f\00\00\00\00\0f\00\00\00\00\t\10\00\00\00\00\00\10\00\00\10\00\00\12\00\00\00\12\12\12\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\12\00\00\00\12\12\12\00\00\00\00\00\00\t\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\n\00\00\00\00\n\00\00\00\00\t\0b\00\00\00\00\00\0b\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\0c\00\00\00\00\t\0c\00\00\00\00\00\0c\00\00\0c\00\000123456789ABCDEF\18\06\00\00") + (data (i32.const 1560) "\05\00\00\00\00\00\00\00\00\00\00\00\08\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\t\00\00\00\n\00\00\00\98\18\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\18\06\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\c0\1c\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\f0\1eP\00") + (export "foo" (func $2)) + (export "byn_llvm_wasm_start" (func $3)) + (start $3) + (func $2 (result i32) + (i32.const 42) + ) + (func $3 + (i32.store offset=1960 + (i32.const 0) + (i32.const 2) + ) + ) +) diff --git a/test/passes/llvm.wast b/test/passes/llvm.wast new file mode 100644 index 00000000000..6f57a01db23 --- /dev/null +++ b/test/passes/llvm.wast @@ -0,0 +1,23 @@ +(module + (import "env" "importedf" (func $imported-func)) + (global $global (mut i32) (i32.const 1)) + (start $startey) + (export "foo" (func $foobar)) + (export "ex_of_im" (func $wrapped_imported_func)) + (func $foobar (result i32) + (i32.add + (i32.const 41) + (i32.const 1) + ) + ) + (func $startey + ;; TODO: otpimize globals + (global.set $global + (i32.const 2) + ) + (call $imported-func) + ) + (func $wrapped_imported_func + (call $imported-func) + ) +)