From e48ad6aa2a83c430a1fa7efbb33e29c51a319997 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 14:48:31 -0700 Subject: [PATCH 01/19] refactor --- src/support/process.h | 209 ++++++++++++++++++++++++++++++++++++++ src/tools/wasm-reduce.cpp | 187 ++-------------------------------- 2 files changed, 219 insertions(+), 177 deletions(-) create mode 100644 src/support/process.h diff --git a/src/support/process.h b/src/support/process.h new file mode 100644 index 00000000000..f117f0bd635 --- /dev/null +++ b/src/support/process.h @@ -0,0 +1,209 @@ +/* + * 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; + + int code; + std::string output; + double time; + + ProgramResult(size_t timeout = -1) : timeout(timeout) {} + ProgramResult(std::string command, size_t timeout) : timeout(timeout) { + 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 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/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", From 0437021971f23288d03fa3883588d0dce99580d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 14:50:56 -0700 Subject: [PATCH 02/19] wip --- src/passes/CMakeLists.txt | 1 + src/passes/LLVM.cpp | 56 +++++++++++++++++++++++++++++++++++++++ src/passes/pass.cpp | 3 +++ src/passes/passes.h | 1 + 4 files changed, 61 insertions(+) create mode 100644 src/passes/LLVM.cpp 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..218a85c4662 --- /dev/null +++ b/src/passes/LLVM.cpp @@ -0,0 +1,56 @@ +/* + * 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. + */ + +// +// Write the module to binary, and load it from there. This is useful in +// testing to check for the effects of roundtripping in a single wasm-opt +// parameter. +// + +#include "ir/module-utils.h" +#include "pass.h" +#include "wasm-binary.h" +#include "wasm.h" + +namespace wasm { + +struct LLVMOpt : public Pass { + void run(PassRunner* runner, Module* module) override { + BufferWithRandomAccess buffer; + // Save features, which would not otherwise make it through a round trip if + // the target features section has been stripped. + auto features = module->features; + // Write, clear, and read the module + WasmBinaryWriter(module, buffer).write(); + ModuleUtils::clearModule(*module); + auto input = buffer.getAsChars(); + WasmBinaryBuilder parser(*module, input); + parser.setDWARF(runner->options.debugInfo); + try { + parser.read(); + } catch (ParseException& p) { + p.dump(std::cerr); + std::cerr << '\n'; + Fatal() << "error in parsing wasm binary"; + } + // 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..74188513876 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -165,6 +165,9 @@ 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..773e0c3c4c7 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -61,6 +61,7 @@ Pass* createLocalCSEPass(); Pass* createLogExecutionPass(); Pass* createInstrumentLocalsPass(); Pass* createInstrumentMemoryPass(); +Pass* createLLLVMOptPass(); Pass* createLoopInvariantCodeMotionPass(); Pass* createMemory64LoweringPass(); Pass* createMemoryPackingPass(); From 46c6a2b1c749f7e89836a8ebbd63bd8200a294a5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 15:05:13 -0700 Subject: [PATCH 03/19] work --- src/passes/LLVM.cpp | 8 ++++++-- src/passes/passes.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 218a85c4662..a53b9adc9b6 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -23,6 +23,7 @@ #include "ir/module-utils.h" #include "pass.h" #include "wasm-binary.h" +#include "wasm-io.h" #include "wasm.h" namespace wasm { @@ -33,12 +34,15 @@ struct LLVMOpt : public Pass { // Save features, which would not otherwise make it through a round trip if // the target features section has been stripped. auto features = module->features; - // Write, clear, and read the module + // Write the module to a temp file. WasmBinaryWriter(module, buffer).write(); + FILE* temp = std::tmpfile(); + std::fwrite(buffer.data(), buffer.size(), 1, temp); + std::fclose(temp); + // Clear the module in preparation for reading, and read it. ModuleUtils::clearModule(*module); auto input = buffer.getAsChars(); WasmBinaryBuilder parser(*module, input); - parser.setDWARF(runner->options.debugInfo); try { parser.read(); } catch (ParseException& p) { diff --git a/src/passes/passes.h b/src/passes/passes.h index 773e0c3c4c7..db1d9a6057b 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -61,7 +61,7 @@ Pass* createLocalCSEPass(); Pass* createLogExecutionPass(); Pass* createInstrumentLocalsPass(); Pass* createInstrumentMemoryPass(); -Pass* createLLLVMOptPass(); +Pass* createLLVMOptPass(); Pass* createLoopInvariantCodeMotionPass(); Pass* createMemory64LoweringPass(); Pass* createMemoryPackingPass(); From 11e5e980d5b270167c6c3c6c0478606fc0f3bb1d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 15:23:55 -0700 Subject: [PATCH 04/19] builds --- src/passes/LLVM.cpp | 35 ++++++++++++++++++++--------------- src/support/process.h | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index a53b9adc9b6..ebe5704518d 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -22,7 +22,7 @@ #include "ir/module-utils.h" #include "pass.h" -#include "wasm-binary.h" +#include "support/process.h" #include "wasm-io.h" #include "wasm.h" @@ -30,26 +30,31 @@ namespace wasm { struct LLVMOpt : public Pass { void run(PassRunner* runner, Module* module) override { - BufferWithRandomAccess buffer; + 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, BASENAME.3.wasm"); // Save features, which would not otherwise make it through a round trip if // the target features section has been stripped. auto features = module->features; // Write the module to a temp file. - WasmBinaryWriter(module, buffer).write(); - FILE* temp = std::tmpfile(); - std::fwrite(buffer.data(), buffer.size(), 1, temp); - std::fclose(temp); + std::string tempWasmA = base + ".1.wasm"; + ModuleWriter().writeBinary(*module, tempWasmA); + // Compile the wasm to C. + std::string tempC = base + ".2.c"; + if (ProgramResult("emcc " + tempWasmA + " -o " + tempC + " -s WASM2C").failed()) { + Fatal() << "LLVMOpt: failed to convert to C"; + } + // Compile the C to wasm. + // TODO: optimize! + std::string tempWasmB = base + ".3.c"; + if (ProgramResult("emcc " + tempC + " -o " + tempWasmB).failed()) { + Fatal() << "LLVMOpt: failed to convert to wasm"; + } // Clear the module in preparation for reading, and read it. ModuleUtils::clearModule(*module); - auto input = buffer.getAsChars(); - WasmBinaryBuilder parser(*module, input); - try { - parser.read(); - } catch (ParseException& p) { - p.dump(std::cerr); - std::cerr << '\n'; - Fatal() << "error in parsing wasm binary"; - } + ModuleReader().readBinary(tempWasmB, *module); // Reapply features module->features = features; } diff --git a/src/support/process.h b/src/support/process.h index f117f0bd635..40dbbf06701 100644 --- a/src/support/process.h +++ b/src/support/process.h @@ -66,7 +66,7 @@ struct ProgramResult { double time; ProgramResult(size_t timeout = -1) : timeout(timeout) {} - ProgramResult(std::string command, size_t timeout) : timeout(timeout) { + ProgramResult(std::string command, size_t timeout = -1) : timeout(timeout) { getFromExecution(command); } From feff13a47b3bf0763dded453a144b966c25fe3f8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 15:40:17 -0700 Subject: [PATCH 05/19] work --- src/passes/LLVM.cpp | 19 +++++++++++++------ src/support/process.h | 12 +++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index ebe5704518d..0c5fdb55adf 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -34,22 +34,29 @@ struct LLVMOpt : public Pass { "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, BASENAME.3.wasm"); + " we create (BASENAME.1.wasm, BASENAME.2.c, etc."); // Save features, which would not otherwise make it through a round trip if // the target features section has been stripped. auto features = module->features; // Write the module to a temp file. std::string tempWasmA = base + ".1.wasm"; ModuleWriter().writeBinary(*module, tempWasmA); - // Compile the wasm to C. - std::string tempC = base + ".2.c"; - if (ProgramResult("emcc " + tempWasmA + " -o " + tempC + " -s WASM2C").failed()) { + // 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::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"; } // Compile the C to wasm. // TODO: optimize! - std::string tempWasmB = base + ".3.c"; - if (ProgramResult("emcc " + tempC + " -o " + tempWasmB).failed()) { + std::string tempWasmC = base + ".3.wasm"; + ProgramResult c2wasm("emcc " + tempC + " -o " + tempWasmC); + if (c2wasm.failed()) { + c2wasm.dump(std::cout); Fatal() << "LLVMOpt: failed to convert to wasm"; } // Clear the module in preparation for reading, and read it. diff --git a/src/support/process.h b/src/support/process.h index 40dbbf06701..955a70270af 100644 --- a/src/support/process.h +++ b/src/support/process.h @@ -61,6 +61,8 @@ namespace wasm { struct ProgramResult { size_t timeout; + std::string command; + int code; std::string output; double time; @@ -71,7 +73,9 @@ struct ProgramResult { } #ifdef _WIN32 - void getFromExecution(std::string command) { + void getFromExecution(std::string command_) { + command = command_; + Timer timer; timer.start(); SECURITY_ATTRIBUTES saAttr; @@ -158,7 +162,9 @@ struct ProgramResult { #else // POSIX // runs the command and notes the output // TODO: also stderr, not just stdout? - void getFromExecution(std::string command) { + void getFromExecution(std::string command_) { + command = command_; + Timer timer; timer.start(); // do this using just core stdio.h and stdlib.h, for portability @@ -189,7 +195,7 @@ struct ProgramResult { bool failed() { return code != 0; } void dump(std::ostream& o) { - o << "[ProgramResult] code: " << code << " stdout: \n" + o << "[ProgramResult " << command << "]\ncode: " << code << ", stdout: \n" << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; } }; From c3372ff9be25a6af92462f352d438c20734f9c25 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 16:56:56 -0700 Subject: [PATCH 06/19] works? --- src/passes/LLVM.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 0c5fdb55adf..5f0399d46e4 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -23,6 +23,7 @@ #include "ir/module-utils.h" #include "pass.h" #include "support/process.h" +#include "wasm-builder.h" #include "wasm-io.h" #include "wasm.h" @@ -35,9 +36,22 @@ struct LLVMOpt : public Pass { "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."); + // Save features, which would not otherwise make it through a round trip if // the target features section has been stripped. auto features = module->features; + + Builder builder(*module); + // Ensure the memory is exported, which wasm2c requires. + if (!module->getExportOrNull("memory")) { + module->addExport(builder.makeExport("memory", "0", ExternalKind::Memory)); + } + // Ensure a _start is exported, which wasm2c requires. + if (!module->getExportOrNull("_start")) { + module->addFunction(builder.makeFunction("start", { Type::none, Type::none }, {}, builder.makeNop())); + module->addExport(builder.makeExport("_start", "start", ExternalKind::Function)); + } + // Write the module to a temp file. std::string tempWasmA = base + ".1.wasm"; ModuleWriter().writeBinary(*module, tempWasmA); From c72a209e6ce827f1bf062e8b586418ed30efca16 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 17:00:06 -0700 Subject: [PATCH 07/19] works! --- src/passes/LLVM.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 5f0399d46e4..0f0a314f9c8 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -42,6 +42,9 @@ struct LLVMOpt : public Pass { auto features = module->features; Builder builder(*module); + + // Ensure there is a memory. + module->memory.exists = true; // Ensure the memory is exported, which wasm2c requires. if (!module->getExportOrNull("memory")) { module->addExport(builder.makeExport("memory", "0", ExternalKind::Memory)); From 4937d3c6a85bee65445a844d6d162a05eb2269a8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 17:14:52 -0700 Subject: [PATCH 08/19] break --- src/passes/LLVM.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 0f0a314f9c8..982b27831ec 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -41,6 +41,13 @@ struct LLVMOpt : public Pass { // 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::vector originalExports; + for (auto& e : module->exports) { + originalExports.push_back(*e); + } + Builder builder(*module); // Ensure there is a memory. @@ -69,16 +76,25 @@ struct LLVMOpt : public Pass { Fatal() << "LLVMOpt: failed to convert to C"; } // Compile the C to wasm. - // TODO: optimize! std::string tempWasmC = base + ".3.wasm"; - ProgramResult c2wasm("emcc " + tempC + " -o " + tempWasmC); + std::string cmd = "emcc " + tempC + " -o " + tempWasmC + " -O1 -s EXPORTED_FUNCTIONS="; + bool first = true; + for (auto e : originalExports) { + if (first) { + first = false; + } else { + cmd += ','; + } + cmd += std::string("_w2c_") + e.name.str; + } + 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); - ModuleReader().readBinary(tempWasmB, *module); + ModuleReader().readBinary(tempWasmC, *module); // Reapply features module->features = features; } From 50b10781c1187547629bd2a34e8954e84ccf5626 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 17:31:36 -0700 Subject: [PATCH 09/19] work --- src/passes/LLVM.cpp | 1 + src/support/string.h | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 982b27831ec..6e2394214ff 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -23,6 +23,7 @@ #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" diff --git a/src/support/string.h b/src/support/string.h index 120835b80d3..9e6fdfa60be 100644 --- a/src/support/string.h +++ b/src/support/string.h @@ -120,6 +120,14 @@ 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 From 81d27dd858c48a5a69e9b90e5030273e398ef4ad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 17:50:07 -0700 Subject: [PATCH 10/19] fix --- src/passes/LLVM.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 6e2394214ff..9cf8c718ac7 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -44,9 +44,9 @@ struct LLVMOpt : public Pass { // Note the original exports, which are the only things we will want to have // exported at the end. - std::vector originalExports; + std::set originalExports; for (auto& e : module->exports) { - originalExports.push_back(*e); + originalExports.insert(e->name); } Builder builder(*module); @@ -76,6 +76,14 @@ struct LLVMOpt : public Pass { wasm2c.dump(std::cout); Fatal() << "LLVMOpt: failed to convert to C"; } + // Modify the C. + 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::string tempWasmC = base + ".3.wasm"; std::string cmd = "emcc " + tempC + " -o " + tempWasmC + " -O1 -s EXPORTED_FUNCTIONS="; @@ -86,7 +94,7 @@ struct LLVMOpt : public Pass { } else { cmd += ','; } - cmd += std::string("_w2c_") + e.name.str; + cmd += std::string("_w2c_") + e.str; } ProgramResult c2wasm(cmd); if (c2wasm.failed()) { @@ -96,6 +104,20 @@ struct LLVMOpt : public Pass { // Clear the module in preparation for reading, and read it. ModuleUtils::clearModule(*module); ModuleReader().readBinary(tempWasmC, *module); + // Filter out any new exports + 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}" + return !e->name.startsWith("w2c_"); + }), + module->exports.end()); + // Do a cleanup (we may optimize anyhow, though?) + { + PassRunner postRunner(runner); + postRunner.add("remove-unused-module-elements"); + postRunner.setIsNested(true); + postRunner.run(); + } // Reapply features module->features = features; } From cfe2a85cfd5708b83f49b6899de9d455043ab1d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 17:50:50 -0700 Subject: [PATCH 11/19] work --- src/passes/LLVM.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 9cf8c718ac7..474252c21c8 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -111,6 +111,7 @@ struct LLVMOpt : public Pass { return !e->name.startsWith("w2c_"); }), module->exports.end()); + // But, the table... :( // Do a cleanup (we may optimize anyhow, though?) { PassRunner postRunner(runner); From 5afbd2b389e5b7c18bf0cc4f5fc94dc1c40ee41f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 May 2021 17:51:16 -0700 Subject: [PATCH 12/19] format --- src/passes/LLVM.cpp | 32 +++++++++++++++++++++----------- src/passes/pass.cpp | 4 +--- src/support/process.h | 3 +-- src/support/string.h | 5 +++-- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 474252c21c8..16d4ab19a9b 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -55,12 +55,15 @@ struct LLVMOpt : public Pass { module->memory.exists = true; // Ensure the memory is exported, which wasm2c requires. if (!module->getExportOrNull("memory")) { - module->addExport(builder.makeExport("memory", "0", ExternalKind::Memory)); + module->addExport( + builder.makeExport("memory", "0", ExternalKind::Memory)); } // Ensure a _start is exported, which wasm2c requires. if (!module->getExportOrNull("_start")) { - module->addFunction(builder.makeFunction("start", { Type::none, Type::none }, {}, builder.makeNop())); - module->addExport(builder.makeExport("_start", "start", ExternalKind::Function)); + module->addFunction(builder.makeFunction( + "start", {Type::none, Type::none}, {}, builder.makeNop())); + module->addExport( + builder.makeExport("_start", "start", ExternalKind::Function)); } // Write the module to a temp file. @@ -71,7 +74,9 @@ struct LLVMOpt : public Pass { // only do the wasm2c bit. 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"); + 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"; @@ -86,7 +91,8 @@ struct LLVMOpt : public Pass { Output(tempC, Flags::Text).getStream() << cCode; // Compile the C to wasm. std::string tempWasmC = base + ".3.wasm"; - std::string cmd = "emcc " + tempC + " -o " + tempWasmC + " -O1 -s EXPORTED_FUNCTIONS="; + std::string cmd = + "emcc " + tempC + " -o " + tempWasmC + " -O1 -s EXPORTED_FUNCTIONS="; bool first = true; for (auto e : originalExports) { if (first) { @@ -105,12 +111,16 @@ struct LLVMOpt : public Pass { ModuleUtils::clearModule(*module); ModuleReader().readBinary(tempWasmC, *module); // Filter out any new exports - 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}" - return !e->name.startsWith("w2c_"); - }), - module->exports.end()); + 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}" + return !e->name.startsWith("w2c_"); + }), + module->exports.end()); // But, the table... :( // Do a cleanup (we may optimize anyhow, though?) { diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 74188513876..d38ce3b0fb5 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -165,9 +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("llvm-opt", "run the LLVM optimizer (!)", createLLVMOptPass); registerPass("local-cse", "common subexpression elimination inside basic blocks", createLocalCSEPass); diff --git a/src/support/process.h b/src/support/process.h index 955a70270af..029f0af9fbe 100644 --- a/src/support/process.h +++ b/src/support/process.h @@ -21,8 +21,8 @@ #ifndef wasm_support_process_h #define wasm_support_process_h -#include #include +#include #include "support/timing.h" @@ -212,4 +212,3 @@ inline std::ostream& operator<<(std::ostream& o, wasm::ProgramResult& result) { } // namespace std #endif // wasm_support_process_h - diff --git a/src/support/string.h b/src/support/string.h index 9e6fdfa60be..bee8b0b7aa0 100644 --- a/src/support/string.h +++ b/src/support/string.h @@ -120,9 +120,10 @@ 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) { +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) { + while ((start = str.find(from, start)) != std::string::npos) { str.replace(start, from.length(), to); start += to.length(); } From 4c4950905375175e45f6e971667814829ce2802d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 May 2021 13:11:33 -0700 Subject: [PATCH 13/19] works --- src/passes/LLVM.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 16d4ab19a9b..db1424bdca5 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -38,6 +38,8 @@ struct LLVMOpt : public Pass { " 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; @@ -67,11 +69,13 @@ struct LLVMOpt : public Pass { } // 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( @@ -82,6 +86,7 @@ struct LLVMOpt : public Pass { 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 ", ""); @@ -90,6 +95,7 @@ struct LLVMOpt : public Pass { 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="; @@ -109,6 +115,7 @@ struct LLVMOpt : public Pass { } // 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 module->exports.erase(std::remove_if(module->exports.begin(), @@ -121,9 +128,16 @@ struct LLVMOpt : public Pass { return !e->name.startsWith("w2c_"); }), module->exports.end()); - // But, the table... :( + // 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); From bdc2a597ae2a9e98fd4bde270dddd46459480d02 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 May 2021 13:16:36 -0700 Subject: [PATCH 14/19] fix --- src/passes/LLVM.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index db1424bdca5..bf3486c7a35 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -117,7 +117,7 @@ struct LLVMOpt : public Pass { ModuleUtils::clearModule(*module); std::cout << "[LLVMOpt] reading wasm...\n"; ModuleReader().readBinary(tempWasmC, *module); - // Filter out any new exports + // 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) { @@ -125,7 +125,11 @@ struct LLVMOpt : public Pass { // all those that are not one of our // original exports, whose name is // now "w2c_${ORIGINAL_NAME}" - return !e->name.startsWith("w2c_"); + if (!e->name.startsWith("w2c_")) { + return true; + } + e->name = e->name.str + 4; + return false; }), module->exports.end()); // Remove the table: the "native" table contains things the new sandboxing From de7a09c4489f8021b2990a557827e5084fbedfe4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 May 2021 13:21:25 -0700 Subject: [PATCH 15/19] test --- test/passes/llvm.passes | 1 + test/passes/llvm.txt | 14 ++++++++++++++ test/passes/llvm.wast | 8 ++++++++ 3 files changed, 23 insertions(+) create mode 100644 test/passes/llvm.passes create mode 100644 test/passes/llvm.txt create mode 100644 test/passes/llvm.wast 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..5c052e23652 --- /dev/null +++ b/test/passes/llvm.txt @@ -0,0 +1,14 @@ +[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))) + (export "foo" (func $2)) + (func $2 (result i32) + (i32.const 42) + ) +) diff --git a/test/passes/llvm.wast b/test/passes/llvm.wast new file mode 100644 index 00000000000..3fd31bc9aaa --- /dev/null +++ b/test/passes/llvm.wast @@ -0,0 +1,8 @@ +(module + (func "foo" (result i32) + (i32.add + (i32.const 41) + (i32.const 1) + ) + ) +) From 6716ad7442cc533ebe5056fccd7e9cf8509d839e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 May 2021 15:47:03 -0700 Subject: [PATCH 16/19] clean --- src/passes/LLVM.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index bf3486c7a35..d4cde708228 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -51,21 +51,24 @@ struct LLVMOpt : public Pass { originalExports.insert(e->name); } + // 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, which wasm2c requires. + // 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 requires. + // Ensure a _start is exported, which wasm2c expects. if (!module->getExportOrNull("_start")) { module->addFunction(builder.makeFunction( - "start", {Type::none, Type::none}, {}, builder.makeNop())); + "byn$llvm-start", {Type::none, Type::none}, {}, builder.makeNop())); module->addExport( - builder.makeExport("_start", "start", ExternalKind::Function)); + builder.makeExport("_start", "byn$llvm-start", ExternalKind::Function)); } // Write the module to a temp file. From bc17bcb4e226529a103fc8317cc0eb13534ee91f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 May 2021 16:08:20 -0700 Subject: [PATCH 17/19] start func --- src/passes/LLVM.cpp | 42 +++++++++++++++++++++++++++++++++--------- test/passes/llvm.txt | 12 ++++++++++++ test/passes/llvm.wast | 7 +++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index d4cde708228..80c4a148145 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -50,6 +50,13 @@ struct LLVMOpt : public Pass { 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 @@ -65,10 +72,18 @@ struct LLVMOpt : public Pass { } // Ensure a _start is exported, which wasm2c expects. if (!module->getExportOrNull("_start")) { + Name name("byn_llvm_runtime_start"); module->addFunction(builder.makeFunction( - "byn$llvm-start", {Type::none, Type::none}, {}, builder.makeNop())); + name, {Type::none, Type::none}, {}, builder.makeNop())); module->addExport( - builder.makeExport("_start", "byn$llvm-start", ExternalKind::Function)); + 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. @@ -103,14 +118,18 @@ struct LLVMOpt : public Pass { std::string cmd = "emcc " + tempC + " -o " + tempWasmC + " -O1 -s EXPORTED_FUNCTIONS="; bool first = true; - for (auto e : originalExports) { - if (first) { - first = false; - } else { - cmd += ','; + auto addExports = [&](const std::set& names) { + for (auto e : names) { + if (first) { + first = false; + } else { + cmd += ','; + } + cmd += std::string("_w2c_") + e.str; } - cmd += std::string("_w2c_") + e.str; - } + }; + addExports(originalExports); + addExports(usedExports); ProgramResult c2wasm(cmd); if (c2wasm.failed()) { c2wasm.dump(std::cout); @@ -135,6 +154,11 @@ struct LLVMOpt : public Pass { 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. diff --git a/test/passes/llvm.txt b/test/passes/llvm.txt index 5c052e23652..f5dc878b407 100644 --- a/test/passes/llvm.txt +++ b/test/passes/llvm.txt @@ -7,8 +7,20 @@ [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 index 3fd31bc9aaa..93769ba38e4 100644 --- a/test/passes/llvm.wast +++ b/test/passes/llvm.wast @@ -1,8 +1,15 @@ (module + (global $global (mut i32) (i32.const 1)) + (start $startey) (func "foo" (result i32) (i32.add (i32.const 41) (i32.const 1) ) ) + (func $startey + (global.set $global + (i32.const 2) + ) + ) ) From cdd932d17cbfc2e1875d17b8522b40f4e40667de Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 21 May 2021 09:14:21 -0700 Subject: [PATCH 18/19] test experiment with imports. they become indirect calls... --- test/passes/llvm.wast | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/passes/llvm.wast b/test/passes/llvm.wast index 93769ba38e4..6f57a01db23 100644 --- a/test/passes/llvm.wast +++ b/test/passes/llvm.wast @@ -1,15 +1,23 @@ (module + (import "env" "importedf" (func $imported-func)) (global $global (mut i32) (i32.const 1)) (start $startey) - (func "foo" (result i32) + (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) ) ) From d71ee2f1a8154327761b18ef578cdc3b189de578 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 5 May 2025 15:20:27 -0700 Subject: [PATCH 19/19] comment --- src/passes/LLVM.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/passes/LLVM.cpp b/src/passes/LLVM.cpp index 80c4a148145..9d4af15d331 100644 --- a/src/passes/LLVM.cpp +++ b/src/passes/LLVM.cpp @@ -15,9 +15,7 @@ */ // -// Write the module to binary, and load it from there. This is useful in -// testing to check for the effects of roundtripping in a single wasm-opt -// parameter. +// Run LLVM to optimize the wasm. // #include "ir/module-utils.h"