[wasm][fuzzer] Check result of compiled code vs interpreter

The plain "wasm fuzzer" (which takes the fuzzer input as the wasm wire
bytes) was already running both the interpreter and compiled code, but
it did not compare the results of both.
This CL fixes this by reusing some logic that was already present in the
fuzzers based on the {WasmCompileFuzzer} class.

R=ahaas@chromium.org

Bug: chromium:1113681, chromium:1112099
Change-Id: I9d407f66dfcba0eec90f050630b028edd5fae1d1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2339624
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69310}
This commit is contained in:
Clemens Backes 2020-08-10 16:20:01 +02:00 committed by Commit Bot
parent d16f404cba
commit 242df3a2ed
5 changed files with 78 additions and 63 deletions

View File

@ -1182,9 +1182,9 @@ class WasmInterpreterInternals {
TrapReason GetTrapReason() { return trap_reason_; }
bool PossibleNondeterminism() { return possible_nondeterminism_; }
bool PossibleNondeterminism() const { return possible_nondeterminism_; }
uint64_t NumInterpretedCalls() { return num_interpreted_calls_; }
uint64_t NumInterpretedCalls() const { return num_interpreted_calls_; }
CodeMap* codemap() { return &codemap_; }
@ -3856,7 +3856,9 @@ WasmInterpreter::WasmInterpreter(Isolate* isolate, const WasmModule* module,
// used in the {unique_ptr} in the header.
WasmInterpreter::~WasmInterpreter() = default;
WasmInterpreter::State WasmInterpreter::state() { return internals_->state(); }
WasmInterpreter::State WasmInterpreter::state() const {
return internals_->state();
}
void WasmInterpreter::InitFrame(const WasmFunction* function, WasmValue* args) {
internals_->InitFrame(function, args);
@ -3870,19 +3872,19 @@ void WasmInterpreter::Pause() { internals_->Pause(); }
void WasmInterpreter::Reset() { internals_->Reset(); }
WasmValue WasmInterpreter::GetReturnValue(int index) {
WasmValue WasmInterpreter::GetReturnValue(int index) const {
return internals_->GetReturnValue(index);
}
TrapReason WasmInterpreter::GetTrapReason() {
TrapReason WasmInterpreter::GetTrapReason() const {
return internals_->GetTrapReason();
}
bool WasmInterpreter::PossibleNondeterminism() {
bool WasmInterpreter::PossibleNondeterminism() const {
return internals_->PossibleNondeterminism();
}
uint64_t WasmInterpreter::NumInterpretedCalls() {
uint64_t WasmInterpreter::NumInterpretedCalls() const {
return internals_->NumInterpretedCalls();
}

View File

@ -66,7 +66,7 @@ class WasmInterpreter {
//==========================================================================
// Execution controls.
//==========================================================================
State state();
State state() const;
void InitFrame(const WasmFunction* function, WasmValue* args);
// Pass -1 as num_steps to run till completion, pause or breakpoint.
State Run(int num_steps = -1);
@ -75,16 +75,16 @@ class WasmInterpreter {
void Reset();
// Stack inspection and modification.
WasmValue GetReturnValue(int index = 0);
TrapReason GetTrapReason();
WasmValue GetReturnValue(int index = 0) const;
TrapReason GetTrapReason() const;
// Returns true if the thread executed an instruction which may produce
// nondeterministic results, e.g. float div, float sqrt, and float mul,
// where the sign bit of a NaN is nondeterministic.
bool PossibleNondeterminism();
bool PossibleNondeterminism() const;
// Returns the number of calls / function frames executed on this thread.
uint64_t NumInterpretedCalls();
uint64_t NumInterpretedCalls() const;
//==========================================================================
// Testing functionality.

View File

@ -41,16 +41,39 @@ MaybeHandle<WasmInstanceObject> CompileAndInstantiateForTesting(
isolate, thrower, module.ToHandleChecked(), {}, {});
}
bool InterpretWasmModuleForTesting(Isolate* isolate,
Handle<WasmInstanceObject> instance,
size_t argc, WasmValue* args) {
WasmInterpretationResult GetInterpretationResult(
Isolate* isolate, const WasmInterpreter& interpreter,
WasmInterpreter::State interpreter_result) {
bool stack_overflow = isolate->has_pending_exception();
isolate->clear_pending_exception();
if (stack_overflow) return WasmInterpretationResult::Failed();
if (interpreter.state() == WasmInterpreter::TRAPPED) {
return WasmInterpretationResult::Trapped(
interpreter.PossibleNondeterminism());
}
if (interpreter_result == WasmInterpreter::FINISHED) {
return WasmInterpretationResult::Finished(
interpreter.GetReturnValue().to<int32_t>(),
interpreter.PossibleNondeterminism());
}
// The interpreter did not finish within the limited number of steps, so it
// might execute an infinite loop or infinite recursion. Return "failed"
// status in that case.
return WasmInterpretationResult::Failed();
}
WasmInterpretationResult InterpretWasmModuleForTesting(
Isolate* isolate, Handle<WasmInstanceObject> instance, size_t argc,
WasmValue* args) {
HandleScope handle_scope(isolate); // Avoid leaking handles.
WasmCodeRefScope code_ref_scope;
MaybeHandle<WasmExportedFunction> maybe_function =
GetExportedFunction(isolate, instance, "main");
Handle<WasmExportedFunction> function;
if (!maybe_function.ToHandle(&function)) {
return false;
if (!GetExportedFunction(isolate, instance, "main").ToHandle(&function)) {
return WasmInterpretationResult::Failed();
}
int function_index = function->function_index();
const FunctionSig* signature =
@ -106,13 +129,7 @@ bool InterpretWasmModuleForTesting(Isolate* isolate,
arguments.get());
WasmInterpreter::State interpreter_result = interpreter.Run(kMaxNumSteps);
if (isolate->has_pending_exception()) {
// Stack overflow during interpretation.
isolate->clear_pending_exception();
return false;
}
return interpreter_result != WasmInterpreter::PAUSED;
return GetInterpretationResult(isolate, interpreter, interpreter_result);
}
int32_t RunWasmModuleForTesting(Isolate* isolate,
@ -152,23 +169,7 @@ WasmInterpretationResult InterpretWasmModule(
interpreter.InitFrame(&instance->module()->functions[function_index], args);
WasmInterpreter::State interpreter_result = interpreter.Run(kMaxNumSteps);
bool stack_overflow = isolate->has_pending_exception();
isolate->clear_pending_exception();
if (stack_overflow) return WasmInterpretationResult::Stopped();
if (interpreter.state() == WasmInterpreter::TRAPPED) {
return WasmInterpretationResult::Trapped(
interpreter.PossibleNondeterminism());
}
if (interpreter_result == WasmInterpreter::FINISHED) {
return WasmInterpretationResult::Finished(
interpreter.GetReturnValue().to<int32_t>(),
interpreter.PossibleNondeterminism());
}
return WasmInterpretationResult::Stopped();
return GetInterpretationResult(isolate, interpreter, interpreter_result);
}
MaybeHandle<WasmExportedFunction> GetExportedFunction(

View File

@ -37,14 +37,6 @@ int32_t CallWasmFunctionForTesting(Isolate* isolate,
ErrorThrower* thrower, const char* name,
int argc, Handle<Object> argv[]);
// Interprets the exported wasm function "main". Returns false if it was not
// possible to execute the function (e.g. because it does not exist), or if the
// interpretation does not finish after kMaxNumSteps. Otherwise returns true.
// The arguments array is extended with default values if necessary.
bool InterpretWasmModuleForTesting(Isolate* isolate,
Handle<WasmInstanceObject> instance,
size_t argc, WasmValue* args);
// Decode, verify, and run the function labeled "main" in the
// given encoded module. The module should have no imports.
int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
@ -61,7 +53,7 @@ MaybeHandle<WasmInstanceObject> CompileAndInstantiateForTesting(
class WasmInterpretationResult {
public:
static WasmInterpretationResult Stopped() { return {kStopped, 0, false}; }
static WasmInterpretationResult Failed() { return {kFailed, 0, false}; }
static WasmInterpretationResult Trapped(bool possible_nondeterminism) {
return {kTrapped, 0, possible_nondeterminism};
}
@ -70,7 +62,10 @@ class WasmInterpretationResult {
return {kFinished, result, possible_nondeterminism};
}
bool stopped() const { return status_ == kStopped; }
// {failed()} captures different reasons: The module was invalid, no function
// to call was found in the module, the function did not termine within a
// limited number of steps, or a stack overflow happened.
bool failed() const { return status_ == kFailed; }
bool trapped() const { return status_ == kTrapped; }
bool finished() const { return status_ == kFinished; }
@ -82,7 +77,7 @@ class WasmInterpretationResult {
bool possible_nondeterminism() const { return possible_nondeterminism_; }
private:
enum Status { kFinished, kTrapped, kStopped };
enum Status { kFinished, kTrapped, kFailed };
const Status status_;
const int32_t result_;
@ -102,6 +97,14 @@ WasmInterpretationResult InterpretWasmModule(
Isolate* isolate, Handle<WasmInstanceObject> instance,
int32_t function_index, WasmValue* args);
// Interprets the exported wasm function "main". Returns a "failed" result if it
// was not possible to execute the function (e.g. because it does not exist), or
// if the interpretation does not finish after kMaxNumSteps. The arguments array
// is extended with default values if necessary.
WasmInterpretationResult InterpretWasmModuleForTesting(
Isolate* isolate, Handle<WasmInstanceObject> instance, size_t argc,
WasmValue* args);
// Runs the module instance with arguments.
int32_t RunWasmModuleForTesting(Isolate* isolate,
Handle<WasmInstanceObject> instance, int argc,

View File

@ -46,10 +46,9 @@ void InterpretAndExecuteModule(i::Isolate* isolate,
thrower.Reset(); // Ignore errors.
return;
}
if (!testing::InterpretWasmModuleForTesting(isolate, instance, 0, nullptr)) {
isolate->clear_pending_exception();
return;
}
testing::WasmInterpretationResult interpreter_result =
testing::InterpretWasmModuleForTesting(isolate, instance, 0, nullptr);
if (interpreter_result.failed()) return;
// Try to instantiate and execute the module_object.
maybe_instance = isolate->wasm_engine()->SyncInstantiate(
@ -61,10 +60,21 @@ void InterpretAndExecuteModule(i::Isolate* isolate,
thrower.Reset(); // Ignore errors.
return;
}
if (testing::RunWasmModuleForTesting(isolate, instance, 0, nullptr) < 0) {
isolate->clear_pending_exception();
return;
int32_t result_compiled =
testing::RunWasmModuleForTesting(isolate, instance, 0, nullptr);
if (interpreter_result.trapped() != isolate->has_pending_exception()) {
const char* exception_text[] = {"no exception", "exception"};
FATAL("interpreter: %s; compiled: %s",
exception_text[interpreter_result.trapped()],
exception_text[isolate->has_pending_exception()]);
}
if (interpreter_result.finished()) {
CHECK_EQ(interpreter_result.result(), result_compiled);
}
// Cleanup any pending exception.
isolate->clear_pending_exception();
}
namespace {
@ -320,7 +330,6 @@ void WasmExecutionFuzzer::FuzzWasmModule(Vector<const uint8_t> data,
ErrorThrower interpreter_thrower(i_isolate, "Interpreter");
ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());
// Compile with Turbofan here. Liftoff will be tested later.
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
MaybeHandle<WasmModuleObject> compiled_module;
{
@ -361,7 +370,7 @@ void WasmExecutionFuzzer::FuzzWasmModule(Vector<const uint8_t> data,
// Do not execute the generated code if the interpreter did not finished after
// a bounded number of steps.
if (interpreter_result.stopped()) return;
if (interpreter_result.failed()) return;
// The WebAssembly spec allows the sign bit of NaN to be non-deterministic.
// This sign bit can make the difference between an infinite loop and