|
| 1 | +// Custom WARP (wasm-ecosystem/wasm-compiler) host for the cross-runtime |
| 2 | +// benchmark. Unlike WARP's stock `vb_bench` (which links no imports and times an |
| 3 | +// export externally), this host links the `env` functions the json-as bench lib |
| 4 | +// needs - performance.now / Date.now / console.log / writeFile / abort - so WARP |
| 5 | +// runs the *real* bench() loop and self-measures exactly like every other |
| 6 | +// runtime, emitting the same `__AS_BENCH_JSON__` result lines. |
| 7 | +// |
| 8 | +// WARP has no WASI and - by design ("no recursions", static execution context) - |
| 9 | +// cannot re-enter the module from inside a host import, so readFile (which would |
| 10 | +// have to call the wasm __new allocator) is impossible. The WARP bench build |
| 11 | +// therefore embeds its payload instead of reading a file; the measured |
| 12 | +// deserialize/serialize work is identical. WARP also destabilizes under one long |
| 13 | +// single-shot allocation loop, so run-bench.runtimes.sh builds with BENCH_FRAMES |
| 14 | +// (the bench lib splits the timed run into small GC-separated frames). |
| 15 | +// |
| 16 | +// Build (links the static libs from a WARP build tree, see run-bench.runtimes.sh): |
| 17 | +// g++ -std=gnu++14 -O2 -DJIT_TARGET_X86_64 -DINTERRUPTION_REQUEST=0 \ |
| 18 | +// -DEAGER_ALLOCATION=1 -I"$WARP_SRC" warp_host.cpp \ |
| 19 | +// -Wl,--start-group <libWasmModule|libcompiler|libruntime|libutils|lib_core_common>.a \ |
| 20 | +// -Wl,--end-group -lpthread -o warp_host |
| 21 | +// |
| 22 | +// Usage: warp_host <module.wasm> |
| 23 | +// Prints whatever the bench writes (progress + __AS_BENCH_JSON__<path>\t<json>). |
| 24 | +#include <chrono> |
| 25 | +#include <cstdint> |
| 26 | +#include <cstdio> |
| 27 | +#include <cstdlib> |
| 28 | +#include <cstring> |
| 29 | +#include <string> |
| 30 | +#include <vector> |
| 31 | + |
| 32 | +#include "src/WasmModule/WasmModule.hpp" |
| 33 | +#include "src/core/common/NativeSymbol.hpp" |
| 34 | +#include "src/core/common/function_traits.hpp" |
| 35 | +#include "src/utils/STDCompilerLogger.hpp" |
| 36 | + |
| 37 | +using namespace vb; |
| 38 | +using Clock = std::chrono::high_resolution_clock; |
| 39 | + |
| 40 | +namespace { |
| 41 | +WasmModule *g_module = nullptr; |
| 42 | +Clock::time_point g_epoch; |
| 43 | + |
| 44 | +// Reads an AssemblyScript string (UTF-16LE, byte-length stored as the u32 at |
| 45 | +// ptr-4) out of linear memory and returns it as UTF-8. |
| 46 | +std::string liftString(uint32_t ptr) { |
| 47 | + if (ptr == 0U || g_module == nullptr) return std::string(); |
| 48 | + uint8_t const *lenField = g_module->getLinearMemoryRegion(ptr - 4U, 4U); |
| 49 | + uint32_t byteLen = 0U; |
| 50 | + std::memcpy(&byteLen, lenField, 4U); |
| 51 | + if (byteLen == 0U) return std::string(); |
| 52 | + uint8_t const *data = g_module->getLinearMemoryRegion(ptr, byteLen); |
| 53 | + std::string out; |
| 54 | + out.reserve(byteLen / 2U); |
| 55 | + for (uint32_t i = 0U; i + 1U < byteLen; i += 2U) { |
| 56 | + uint32_t cu = static_cast<uint32_t>(data[i]) | (static_cast<uint32_t>(data[i + 1U]) << 8); |
| 57 | + // Minimal UTF-16 -> UTF-8 (the bench's strings are ASCII JSON + log text; |
| 58 | + // surrogate pairs are passed through per-unit, which is fine for output). |
| 59 | + if (cu < 0x80U) { |
| 60 | + out.push_back(static_cast<char>(cu)); |
| 61 | + } else if (cu < 0x800U) { |
| 62 | + out.push_back(static_cast<char>(0xC0U | (cu >> 6))); |
| 63 | + out.push_back(static_cast<char>(0x80U | (cu & 0x3FU))); |
| 64 | + } else { |
| 65 | + out.push_back(static_cast<char>(0xE0U | (cu >> 12))); |
| 66 | + out.push_back(static_cast<char>(0x80U | ((cu >> 6) & 0x3FU))); |
| 67 | + out.push_back(static_cast<char>(0x80U | (cu & 0x3FU))); |
| 68 | + } |
| 69 | + } |
| 70 | + return out; |
| 71 | +} |
| 72 | + |
| 73 | +// --- env imports the bench lib calls (none re-enter the module) ------------- |
| 74 | +double host_performance_now(void *) noexcept { |
| 75 | + return std::chrono::duration<double, std::milli>(Clock::now() - g_epoch).count(); |
| 76 | +} |
| 77 | +double host_date_now(void *) noexcept { |
| 78 | + return std::chrono::duration<double, std::milli>(Clock::now().time_since_epoch()).count(); |
| 79 | +} |
| 80 | +void host_console_log(uint32_t ptr, void *) noexcept { printf("%s\n", liftString(ptr).c_str()); } |
| 81 | + |
| 82 | +// dumpToFile() calls writeFile(path, json). The env build's path is |
| 83 | +// ./build/logs/as/<mode>/<suite>.<type>.as.json; re-emit it as an |
| 84 | +// __AS_BENCH_JSON__ line so run-bench.runtimes.sh routes it to runtimes/warp/. |
| 85 | +void host_write_file(uint32_t namePtr, uint32_t dataPtr, void *) noexcept { |
| 86 | + printf("__AS_BENCH_JSON__%s\t%s\n", liftString(namePtr).c_str(), liftString(dataPtr).c_str()); |
| 87 | +} |
| 88 | +void host_abort(uint32_t msg, uint32_t file, uint32_t line, uint32_t col, void *) noexcept { |
| 89 | + printf("abort: %s in %s:%u:%u\n", liftString(msg).c_str(), liftString(file).c_str(), line, col); |
| 90 | + std::exit(1); |
| 91 | +} |
| 92 | + |
| 93 | +std::vector<uint8_t> loadFile(char const *path) { |
| 94 | + FILE *f = fopen(path, "rb"); |
| 95 | + if (f == nullptr) { |
| 96 | + fprintf(stderr, "warp_host: cannot open %s\n", path); |
| 97 | + std::exit(1); |
| 98 | + } |
| 99 | + fseek(f, 0, SEEK_END); |
| 100 | + long n = ftell(f); |
| 101 | + rewind(f); |
| 102 | + std::vector<uint8_t> buf(static_cast<size_t>(n)); |
| 103 | + size_t rd = fread(buf.data(), 1U, buf.size(), f); |
| 104 | + (void)rd; |
| 105 | + fclose(f); |
| 106 | + return buf; |
| 107 | +} |
| 108 | +} // namespace |
| 109 | + |
| 110 | +int main(int argc, char **argv) { |
| 111 | + if (argc < 2) { |
| 112 | + fprintf(stderr, "usage: warp_host <module.wasm>\n"); |
| 113 | + return 1; |
| 114 | + } |
| 115 | + std::vector<uint8_t> bytecode = loadFile(argv[1]); |
| 116 | + g_epoch = Clock::now(); |
| 117 | + |
| 118 | + WasmModule::initEnvironment(&malloc, &realloc, &free); |
| 119 | + STDCompilerLogger logger{}; |
| 120 | + WasmModule module(UINT64_MAX, logger, false, nullptr, 0U); |
| 121 | + g_module = &module; |
| 122 | + |
| 123 | + // V1 imports (statically linked at compile time -> pass to compile(), and an |
| 124 | + // EMPTY span to initFromCompiledBinary, which rejects STATIC symbols). |
| 125 | + auto imports = make_array( |
| 126 | + STATIC_LINK("env", "performance.now", host_performance_now), |
| 127 | + STATIC_LINK("env", "Date.now", host_date_now), |
| 128 | + STATIC_LINK("env", "console.log", host_console_log), |
| 129 | + STATIC_LINK("env", "writeFile", host_write_file), |
| 130 | + STATIC_LINK("env", "abort", host_abort)); |
| 131 | + Span<NativeSymbol const> importSpan(imports.data(), imports.size()); |
| 132 | + |
| 133 | + try { |
| 134 | + WasmModule::CompileResult compiled{module.compile( |
| 135 | + Span<uint8_t const>(bytecode.data(), static_cast<uint32_t>(bytecode.size())), importSpan)}; |
| 136 | + module.initFromCompiledBinary(compiled.getModule().span(), Span<NativeSymbol const>(), Span<uint8_t const>()); |
| 137 | + module.start(nullptr); // runs the bench (all work happens in the start section) |
| 138 | + } catch (std::exception const &e) { |
| 139 | + fprintf(stderr, "warp_host: %s\n", e.what()); |
| 140 | + return 1; |
| 141 | + } |
| 142 | + return 0; |
| 143 | +} |
0 commit comments