Commit 57b14b06 authored by ahaas's avatar ahaas Committed by Commit bot

[wasm] Track in the interpreter if a NaN could have been produced.

The wasm specification does not fully specify the binary representation
of NaN: the sign bit can be non-deterministic. The wasm-code fuzzer
found a test case where the wasm interpreter and the compiled code
produce a different sign bit for a NaN, and as a consequence they
produce different results.

With this CL the interpreter tracks whether it executed an instruction
which can produce a NaN, which are div and sqrt instructions. The
fuzzer uses this information and compares the result of the interpreter
with the result of the compiled code only if there was no instruction
which could have produced a NaN.

R=titzer@chromium.org

TEST=cctest/test-run-wasm-interpreter/TestMayProduceNaN
BUG=chromium:657481

Review-Url: https://chromiumcodereview.appspot.com/2438603003
Cr-Commit-Position: refs/heads/master@{#40474}
parent 2282dd09
......@@ -62,8 +62,6 @@ namespace wasm {
V(I64GtS, int64_t, >) \
V(I64GeS, int64_t, >=) \
V(F32Add, float, +) \
V(F32Mul, float, *) \
V(F32Div, float, /) \
V(F32Eq, float, ==) \
V(F32Ne, float, !=) \
V(F32Lt, float, <) \
......@@ -71,8 +69,6 @@ namespace wasm {
V(F32Gt, float, >) \
V(F32Ge, float, >=) \
V(F64Add, double, +) \
V(F64Mul, double, *) \
V(F64Div, double, /) \
V(F64Eq, double, ==) \
V(F64Ne, double, !=) \
V(F64Lt, double, <) \
......@@ -80,6 +76,12 @@ namespace wasm {
V(F64Gt, double, >) \
V(F64Ge, double, >=)
#define FOREACH_SIMPLE_BINOP_NAN(V) \
V(F32Mul, float, *) \
V(F64Mul, double, *) \
V(F32Div, float, /) \
V(F64Div, double, /)
#define FOREACH_OTHER_BINOP(V) \
V(I32DivS, int32_t) \
V(I32DivU, uint32_t) \
......@@ -127,14 +129,12 @@ namespace wasm {
V(F32Floor, float) \
V(F32Trunc, float) \
V(F32NearestInt, float) \
V(F32Sqrt, float) \
V(F64Abs, double) \
V(F64Neg, double) \
V(F64Ceil, double) \
V(F64Floor, double) \
V(F64Trunc, double) \
V(F64NearestInt, double) \
V(F64Sqrt, double) \
V(I32SConvertF32, float) \
V(I32SConvertF64, double) \
V(I32UConvertF32, float) \
......@@ -165,6 +165,10 @@ namespace wasm {
V(I32AsmjsSConvertF64, double) \
V(I32AsmjsUConvertF64, double)
#define FOREACH_OTHER_UNOP_NAN(V) \
V(F32Sqrt, float) \
V(F64Sqrt, double)
static inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) {
if (b == 0) {
*trap = kTrapDivByZero;
......@@ -460,7 +464,8 @@ static inline float ExecuteF32NearestInt(float a, TrapReason* trap) {
}
static inline float ExecuteF32Sqrt(float a, TrapReason* trap) {
return sqrtf(a);
float result = sqrtf(a);
return result;
}
static inline double ExecuteF64Abs(double a, TrapReason* trap) {
......@@ -975,7 +980,8 @@ class ThreadImpl : public WasmInterpreter::Thread {
blocks_(zone),
state_(WasmInterpreter::STOPPED),
break_pc_(kInvalidPc),
trap_reason_(kTrapCount) {}
trap_reason_(kTrapCount),
possible_nondeterminism_(false) {}
virtual ~ThreadImpl() {}
......@@ -1030,6 +1036,7 @@ class ThreadImpl : public WasmInterpreter::Thread {
frames_.clear();
state_ = WasmInterpreter::STOPPED;
trap_reason_ = kTrapCount;
possible_nondeterminism_ = false;
}
virtual int GetFrameCount() { return static_cast<int>(frames_.size()); }
......@@ -1053,6 +1060,8 @@ class ThreadImpl : public WasmInterpreter::Thread {
virtual pc_t GetBreakpointPc() { return break_pc_; }
virtual bool PossibleNondeterminism() { return possible_nondeterminism_; }
bool Terminated() {
return state_ == WasmInterpreter::TRAPPED ||
state_ == WasmInterpreter::FINISHED;
......@@ -1087,6 +1096,7 @@ class ThreadImpl : public WasmInterpreter::Thread {
WasmInterpreter::State state_;
pc_t break_pc_;
TrapReason trap_reason_;
bool possible_nondeterminism_;
CodeMap* codemap() { return codemap_; }
WasmInstance* instance() { return instance_; }
......@@ -1602,6 +1612,19 @@ class ThreadImpl : public WasmInterpreter::Thread {
FOREACH_SIMPLE_BINOP(EXECUTE_SIMPLE_BINOP)
#undef EXECUTE_SIMPLE_BINOP
#define EXECUTE_SIMPLE_BINOP_NAN(name, ctype, op) \
case kExpr##name: { \
WasmVal rval = Pop(); \
WasmVal lval = Pop(); \
ctype result = lval.to<ctype>() op rval.to<ctype>(); \
possible_nondeterminism_ |= std::isnan(result); \
WasmVal result_val(result); \
Push(pc, result_val); \
break; \
}
FOREACH_SIMPLE_BINOP_NAN(EXECUTE_SIMPLE_BINOP_NAN)
#undef EXECUTE_SIMPLE_BINOP_NAN
#define EXECUTE_OTHER_BINOP(name, ctype) \
case kExpr##name: { \
TrapReason trap = kTrapCount; \
......@@ -1627,6 +1650,20 @@ class ThreadImpl : public WasmInterpreter::Thread {
FOREACH_OTHER_UNOP(EXECUTE_OTHER_UNOP)
#undef EXECUTE_OTHER_UNOP
#define EXECUTE_OTHER_UNOP_NAN(name, ctype) \
case kExpr##name: { \
TrapReason trap = kTrapCount; \
volatile ctype val = Pop().to<ctype>(); \
ctype result = Execute##name(val, &trap); \
possible_nondeterminism_ |= std::isnan(result); \
WasmVal result_val(result); \
if (trap != kTrapCount) return DoTrap(trap, pc); \
Push(pc, result_val); \
break; \
}
FOREACH_OTHER_UNOP_NAN(EXECUTE_OTHER_UNOP_NAN)
#undef EXECUTE_OTHER_UNOP_NAN
default:
V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s",
code->start[pc], OpcodeName(code->start[pc]));
......
......@@ -125,6 +125,10 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
virtual const WasmFrame* GetFrame(int index) = 0;
virtual WasmFrame* GetMutableFrame(int index) = 0;
virtual WasmVal GetReturnValue(int index = 0) = 0;
// 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.
virtual bool PossibleNondeterminism() = 0;
// Thread-specific breakpoints.
bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled);
......
......@@ -333,6 +333,72 @@ TEST(GrowMemoryInvalidSize) {
}
}
TEST(TestPossibleNondeterminism) {
{
// F32Div may produced NaN
TestingModule module(kExecuteInterpreted);
WasmRunner<float> r(&module, MachineType::Float32(),
MachineType::Float32());
BUILD(r, WASM_F32_DIV(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
r.Call(1048575.5f, 2.5f);
CHECK(!r.possible_nondeterminism());
r.Call(0.0f, 0.0f);
CHECK(r.possible_nondeterminism());
}
{
// F32Sqrt may produced NaN
TestingModule module(kExecuteInterpreted);
WasmRunner<float> r(&module, MachineType::Float32());
BUILD(r, WASM_F32_SQRT(WASM_GET_LOCAL(0)));
r.Call(16.0f);
CHECK(!r.possible_nondeterminism());
r.Call(-1048575.5f);
CHECK(r.possible_nondeterminism());
}
{
// F32Mul may produced NaN
TestingModule module(kExecuteInterpreted);
WasmRunner<float> r(&module, MachineType::Float32(),
MachineType::Float32());
BUILD(r, WASM_F32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
r.Call(1048575.5f, 2.5f);
CHECK(!r.possible_nondeterminism());
r.Call(std::numeric_limits<float>::infinity(), 0.0f);
CHECK(r.possible_nondeterminism());
}
{
// F64Div may produced NaN
TestingModule module(kExecuteInterpreted);
WasmRunner<double> r(&module, MachineType::Float64(),
MachineType::Float64());
BUILD(r, WASM_F64_DIV(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
r.Call(1048575.5, 2.5);
CHECK(!r.possible_nondeterminism());
r.Call(0.0, 0.0);
CHECK(r.possible_nondeterminism());
}
{
// F64Sqrt may produced NaN
TestingModule module(kExecuteInterpreted);
WasmRunner<double> r(&module, MachineType::Float64());
BUILD(r, WASM_F64_SQRT(WASM_GET_LOCAL(0)));
r.Call(1048575.5);
CHECK(!r.possible_nondeterminism());
r.Call(-1048575.5);
CHECK(r.possible_nondeterminism());
}
{
// F64Mul may produced NaN
TestingModule module(kExecuteInterpreted);
WasmRunner<double> r(&module, MachineType::Float64(),
MachineType::Float64());
BUILD(r, WASM_F64_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
r.Call(1048575.5, 2.5);
CHECK(!r.possible_nondeterminism());
r.Call(std::numeric_limits<double>::infinity(), 0.0);
CHECK(r.possible_nondeterminism());
}
}
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -626,7 +626,8 @@ class WasmRunner {
compiled_(false),
signature_(MachineTypeForC<ReturnType>() == MachineType::None() ? 0 : 1,
GetParameterCount(p0, p1, p2, p3), storage_),
compiler_(&signature_, module) {
compiler_(&signature_, module),
possible_nondeterminism_(false) {
DCHECK(module);
InitSigStorage(p0, p1, p2, p3);
}
......@@ -736,6 +737,7 @@ class WasmRunner {
thread->PushFrame(compiler_.function_, args.start());
if (thread->Run() == WasmInterpreter::FINISHED) {
WasmVal val = thread->GetReturnValue();
possible_nondeterminism_ |= thread->PossibleNondeterminism();
return val.to<ReturnType>();
} else if (thread->state() == WasmInterpreter::TRAPPED) {
// TODO(titzer): return the correct trap code
......@@ -752,6 +754,7 @@ class WasmRunner {
WasmFunction* function() { return compiler_.function_; }
WasmInterpreter* interpreter() { return compiler_.interpreter_; }
bool possible_nondeterminism() { return possible_nondeterminism_; }
protected:
v8::internal::AccountingAllocator allocator_;
......@@ -761,6 +764,7 @@ class WasmRunner {
FunctionSig signature_;
WasmFunctionCompiler compiler_;
WasmFunctionWrapper<ReturnType> wrapper_;
bool possible_nondeterminism_;
bool interpret() { return compiler_.execution_mode_ == kExecuteInterpreted; }
......
......@@ -114,7 +114,7 @@ int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
const WasmModule* module, int function_index,
WasmVal* args) {
WasmVal* args, bool* possible_nondeterminism) {
CHECK(module != nullptr);
Zone zone(isolate->allocator(), ZONE_NAME);
......@@ -165,6 +165,7 @@ int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
if (instance.mem_start) {
free(instance.mem_start);
}
*possible_nondeterminism = thread->PossibleNondeterminism();
if (interpreter_result == WasmInterpreter::FINISHED) {
WasmVal val = thread->GetReturnValue();
return val.to<int32_t>();
......
......@@ -45,7 +45,7 @@ int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
// should not have any imports or exports
int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
const WasmModule* module, int function_index,
WasmVal* args);
WasmVal* args, bool* may_produced_nan);
// Compiles WasmModule bytes and return an instance of the compiled module.
const Handle<JSObject> CompileInstantiateWasmModuleForTesting(
......
......@@ -63,10 +63,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return 0;
}
int32_t result_interpreted;
bool possible_nondeterminism = false;
{
WasmVal args[] = {WasmVal(1), WasmVal(2), WasmVal(3)};
result_interpreted = testing::InterpretWasmModule(
i_isolate, &interpreter_thrower, module.get(), 0, args);
i_isolate, &interpreter_thrower, module.get(), 0, args,
&possible_nondeterminism);
}
ErrorThrower compiler_thrower(i_isolate, "Compiler");
......@@ -93,7 +95,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
CHECK(i_isolate->has_pending_exception());
i_isolate->clear_pending_exception();
} else {
if (result_interpreted != result_compiled) {
// The WebAssembly spec allows the sign bit of NaN to be non-deterministic.
// This sign bit may cause result_interpreted to be different than
// result_compiled. Therefore we do not check the equality of the results
// if the execution may have produced a NaN at some point.
if (!possible_nondeterminism && (result_interpreted != result_compiled)) {
V8_Fatal(__FILE__, __LINE__, "WasmCodeFuzzerHash=%x",
v8::internal::StringHasher::HashSequentialString(
data, static_cast<int>(size), WASM_CODE_FUZZER_HASH_SEED));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment