diff --git a/src/shell-interface.h b/src/shell-interface.h index b4dee08d662..1e68c6955ae 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -105,8 +105,8 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { ModuleRunner* getImportInstance(Importable* import) { auto it = linkedInstances.find(import->module); if (it == linkedInstances.end()) { - Fatal() << "importGlobals: unknown import: " << import->module.str << "." - << import->base.str; + Fatal() << "getImportInstance: unknown import: " << import->module.str + << "." << import->base.str; } return it->second.get(); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 5d2864cd9ca..0afdff0cde8 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -55,8 +55,11 @@ struct LoggingExternalInterface : public ShellExternalInterface { ModuleRunner* instance = nullptr; public: - LoggingExternalInterface(Loggings& loggings, Module& wasm) - : loggings(loggings), wasm(wasm) { + LoggingExternalInterface( + Loggings& loggings, + Module& wasm, + std::map> linkedInstances_ = {}) + : ShellExternalInterface(linkedInstances_), loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { exportedTable = *exp->getInternalName(); @@ -185,7 +188,11 @@ struct LoggingExternalInterface : public ShellExternalInterface { } else if (import->base == "getTempRet0") { return {Literal(state.tempRet0)}; } + } else if (linkedInstances.count(import->module)) { + // This is from a recognized module. + return getImportInstance(import)->callExport(import->base, arguments); } + // Anything else, we ignore. std::cerr << "[LoggingExternalInterface ignoring an unknown import " << import->module << " . " << import->base << '\n'; return {}; @@ -279,35 +286,24 @@ struct ExecutionResults { // If set, we should ignore this and not compare it to anything. bool ignore = false; - // get results of execution - void get(Module& wasm) { - LoggingExternalInterface interface(loggings, wasm); + // Get results of executing a module. Optionally, provide a second module to + // link with it (like fuzz_shell's second module). + void get(Module& wasm, Module* second = nullptr) { try { - ModuleRunner instance(wasm, &interface); - // This is not an optimization: we want to execute anything, even relaxed - // SIMD instructions. - instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); - instance.instantiate(); - interface.setModuleRunner(&instance); - // execute all exported methods (that are therefore preserved through - // opts) - for (auto& exp : wasm.exports) { - if (exp->kind != ExternalKind::Function) { - continue; - } - std::cout << "[fuzz-exec] calling " << exp->name << "\n"; - auto* func = wasm.getFunction(*exp->getInternalName()); - FunctionResult ret = run(func, wasm, instance); - results[exp->name] = ret; - if (auto* values = std::get_if(&ret)) { - // ignore the result if we hit an unreachable and returned no value - if (values->size() > 0) { - std::cout << "[fuzz-exec] note result: " << exp->name << " => "; - for (auto value : *values) { - printValue(value); - } - } - } + // Run the first module. + LoggingExternalInterface interface(loggings, wasm); + auto instance = std::make_shared(wasm, &interface); + runModule(wasm, *instance, interface); + + if (second) { + // Link and run the second module. + std::map> linkedInstances; + linkedInstances["primary"] = instance; + LoggingExternalInterface secondInterface( + loggings, *second, linkedInstances); + auto secondInstance = std::make_shared( + *second, &secondInterface, linkedInstances); + runModule(*second, *secondInstance, secondInterface); } } catch (const TrapException&) { // May throw in instance creation (init of offsets). @@ -319,6 +315,36 @@ struct ExecutionResults { } } + void runModule(Module& wasm, + ModuleRunner& instance, + LoggingExternalInterface& interface) { + // This is not an optimization: we want to execute anything, even relaxed + // SIMD instructions. + instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); + instance.instantiate(); + interface.setModuleRunner(&instance); + // execute all exported methods (that are therefore preserved through + // opts) + for (auto& exp : wasm.exports) { + if (exp->kind != ExternalKind::Function) { + continue; + } + std::cout << "[fuzz-exec] calling " << exp->name << "\n"; + auto* func = wasm.getFunction(*exp->getInternalName()); + FunctionResult ret = run(func, wasm, instance); + results[exp->name] = ret; + if (auto* values = std::get_if(&ret)) { + // ignore the result if we hit an unreachable and returned no value + if (values->size() > 0) { + std::cout << "[fuzz-exec] note result: " << exp->name << " => "; + for (auto value : *values) { + printValue(value); + } + } + } + } + } + void printValue(Literal value) { // Unwrap an externalized GC value to get the actual value, but not strings, // which are normally a subtype of ext. diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 5edd2a15b83..97bb835cd9d 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -79,6 +79,7 @@ int main(int argc, const char* argv[]) { bool converge = false; bool fuzzExecBefore = false; bool fuzzExecAfter = false; + std::string fuzzExecSecond; std::string extraFuzzCommand; bool translateToFuzz = false; std::string initialFuzz; @@ -148,6 +149,15 @@ For more on how to optimize effectively, see [&](Options* o, const std::string& arguments) { fuzzExecBefore = fuzzExecAfter = true; }) + .add("--fuzz-exec-second", + "", + "A second module to link with the first, for fuzz-exec-before (only " + "before, as optimizations are not applied to it)", + WasmOptOption, + Options::Arguments::One, + [&](Options* o, const std::string& arguments) { + fuzzExecSecond = arguments; + }) .add("--extra-fuzz-command", "-efc", "An extra command to run on the output before and after optimizing. " @@ -345,7 +355,16 @@ For more on how to optimize effectively, see ExecutionResults results; if (fuzzExecBefore) { - results.get(wasm); + if (fuzzExecSecond.empty()) { + results.get(wasm); + } else { + // Add the second module. + Module second; + second.features = wasm.features; + ModuleReader().read(fuzzExecSecond, second); + + results.get(wasm, &second); + } } if (emitSpecWrapper.size() > 0) { diff --git a/test/lit/exec/second.wast b/test/lit/exec/second.wast new file mode 100644 index 00000000000..f94b1d82d76 --- /dev/null +++ b/test/lit/exec/second.wast @@ -0,0 +1,17 @@ + +;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s + +(module + (func $first (export "first") (result i32) + (i32.const 42) + ) +) + +;; Test that --fuzz-exec-second will run a second wasm file that is +;; provided, and call its exports as well as the first module's. + +;; CHECK: [fuzz-exec] calling first +;; CHECK-NEXT: [fuzz-exec] note result: first => 42 +;; CHECK: [fuzz-exec] calling second +;; CHECK-NEXT: [fuzz-exec] note result: second => 1337 + diff --git a/test/lit/exec/second.wast.second b/test/lit/exec/second.wast.second new file mode 100644 index 00000000000..de36a5f14ca --- /dev/null +++ b/test/lit/exec/second.wast.second @@ -0,0 +1,12 @@ +(module + (import "primary" "first" (func $first-func (result i32))) + + (func $second (export "second") (result i32) + ;; Test we can call the first module, linked as "primary." + (i32.add + (call $first-func) + (i32.const 1295) + ) + ) +) + diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 3ee35a69bb2..9258c6ffedc 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -38,6 +38,11 @@ ;; CHECK-NEXT: after optimization, helping ;; CHECK-NEXT: fuzzing find bugs ;; CHECK-NEXT: +;; CHECK-NEXT: --fuzz-exec-second A second module to link with the +;; CHECK-NEXT: first, for fuzz-exec-before +;; CHECK-NEXT: (only before, as optimizations +;; CHECK-NEXT: are not applied to it) +;; CHECK-NEXT: ;; CHECK-NEXT: --extra-fuzz-command,-efc An extra command to run on the ;; CHECK-NEXT: output before and after ;; CHECK-NEXT: optimizing. The output is