Commit 3deb8218 authored by Thibaud Michaud's avatar Thibaud Michaud Committed by V8 LUCI CQ

[wasm][fuzzer] Allow Liftoff as a reference tier

Add an option to use Liftoff instead of the interpreter as the reference
tier for fuzzing. The tier to use is chosen based on the input data
before generating the module. This way, the module can use features
depending on what is available in the reference tier, and we still get a
chance to find correctness issues that would only be detected by the
interpreter.

R=clemensb@chromium.org

Bug: v8:11856
Change-Id: I2e9878345355a37caec5fdb338dda42a84e8e63a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3008645
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75633}
parent 72af112c
......@@ -10,6 +10,8 @@
#include "src/execution/isolate.h"
#include "src/objects/objects-inl.h"
#include "src/utils/ostreams.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module-builder.h"
......@@ -26,8 +28,61 @@ namespace internal {
namespace wasm {
namespace fuzzer {
// Compile a baseline module. We pass a pointer to a max step counter and a
// nondeterminsm flag that are updated during execution by Liftoff.
Handle<WasmModuleObject> CompileReferenceModule(Zone* zone, Isolate* isolate,
ModuleWireBytes wire_bytes,
ErrorThrower* thrower,
int32_t* max_steps,
int32_t* nondeterminism) {
// Create the native module.
std::shared_ptr<NativeModule> native_module;
constexpr bool kNoVerifyFunctions = false;
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(isolate);
ModuleResult module_res = DecodeWasmModule(
enabled_features, wire_bytes.start(), wire_bytes.end(),
kNoVerifyFunctions, ModuleOrigin::kWasmOrigin, isolate->counters(),
isolate->metrics_recorder(), v8::metrics::Recorder::ContextId::Empty(),
DecodingMethod::kSync, GetWasmEngine()->allocator());
CHECK(module_res.ok());
std::shared_ptr<WasmModule> module = module_res.value();
CHECK_NOT_NULL(module);
native_module =
GetWasmEngine()->NewNativeModule(isolate, enabled_features, module, 0);
native_module->SetWireBytes(
base::OwnedVector<uint8_t>::Of(wire_bytes.module_bytes()));
// Compile all functions with Liftoff.
WasmCodeRefScope code_ref_scope;
auto env = native_module->CreateCompilationEnv();
for (size_t i = module->num_imported_functions; i < module->functions.size();
++i) {
auto& func = module->functions[i];
base::Vector<const uint8_t> func_code = wire_bytes.GetFunctionBytes(&func);
WasmFeatures unused_detected_features;
FunctionBody func_body(func.sig, func.code.offset(), func_code.begin(),
func_code.end());
auto result = ExecuteLiftoffCompilation(
&env, func_body, func.func_index, kForDebugging, isolate->counters(),
&unused_detected_features, {}, nullptr, 0, max_steps, nondeterminism);
native_module->PublishCode(
native_module->AddCompiledCode(std::move(result)));
}
// Create the module object.
constexpr base::Vector<const char> kNoSourceUrl;
Handle<Script> script =
GetWasmEngine()->GetOrCreateScript(isolate, native_module, kNoSourceUrl);
Handle<FixedArray> export_wrappers = isolate->factory()->NewFixedArray(
static_cast<int>(module->num_exported_functions));
return WasmModuleObject::New(isolate, std::move(native_module), script,
export_wrappers);
}
void InterpretAndExecuteModule(i::Isolate* isolate,
Handle<WasmModuleObject> module_object) {
Handle<WasmModuleObject> module_object,
Handle<WasmModuleObject> module_ref,
int32_t* max_steps, int32_t* nondeterminism) {
// We do not instantiate the module if there is a start function, because a
// start function can contain an infinite loop which we cannot handle.
if (module_object->module()->start_function_index >= 0) return;
......@@ -55,50 +110,70 @@ void InterpretAndExecuteModule(i::Isolate* isolate,
return;
}
base::OwnedVector<WasmValue> arguments =
testing::MakeDefaultInterpreterArguments(isolate, main_function->sig());
// Now interpret.
testing::WasmInterpretationResult interpreter_result =
testing::InterpretWasmModule(isolate, instance,
main_function->function_index(),
arguments.begin());
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
// terminating code. With possible non-determinism we cannot guarantee that
// the generated code will not go into an infinite loop and cause a timeout in
// Clusterfuzz. Therefore we do not execute the generated code if the result
// may be non-deterministic.
if (interpreter_result.possible_nondeterminism()) return;
// Try to instantiate and execute the module_object.
{
ErrorThrower thrower(isolate, "Second Instantiation");
// We instantiated before, so the second instantiation must also succeed:
CHECK(GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_object, {},
{}) // no imports & memory
.ToHandle(&instance));
}
base::OwnedVector<Handle<Object>> compiled_args =
testing::MakeDefaultArguments(isolate, main_function->sig());
bool exception_ref = false;
bool exception = false;
int32_t result_compiled = testing::CallWasmFunctionForTesting(
int32_t result_ref = 0;
int32_t result = 0;
auto interpreter_result = testing::WasmInterpretationResult::Failed();
if (module_ref.is_null()) {
base::OwnedVector<WasmValue> arguments =
testing::MakeDefaultInterpreterArguments(isolate, main_function->sig());
// Now interpret.
testing::WasmInterpretationResult interpreter_result =
testing::InterpretWasmModule(isolate, instance,
main_function->function_index(),
arguments.begin());
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
// terminating code. With possible non-determinism we cannot guarantee that
// the generated code will not go into an infinite loop and cause a timeout
// in Clusterfuzz. Therefore we do not execute the generated code if the
// result may be non-deterministic.
if (interpreter_result.possible_nondeterminism()) return;
if (interpreter_result.finished()) {
result_ref = interpreter_result.result();
} else {
DCHECK(interpreter_result.trapped());
exception_ref = true;
}
} else {
Handle<WasmInstanceObject> instance_ref;
{
ErrorThrower thrower(isolate, "WebAssembly Instantiation");
DCHECK(GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_ref, {},
{}) // no imports & memory
.ToHandle(&instance_ref));
}
result_ref = testing::CallWasmFunctionForTesting(
isolate, instance_ref, "main", static_cast<int>(compiled_args.size()),
compiled_args.begin(), &exception_ref);
// Reached max steps, do not try to execute the test module as it might
// never terminate.
if (*max_steps == 0) return;
// If there is nondeterminism, we cannot guarantee the behavior of the test
// module, and in particular it may not terminate.
if (*nondeterminism != 0) return;
}
result = testing::CallWasmFunctionForTesting(
isolate, instance, "main", static_cast<int>(compiled_args.size()),
compiled_args.begin(), &exception);
if (interpreter_result.trapped() != exception) {
if (exception_ref != exception) {
const char* exception_text[] = {"no exception", "exception"};
FATAL("interpreter: %s; compiled: %s",
exception_text[interpreter_result.trapped()],
FATAL("expected: %s; got: %s", exception_text[interpreter_result.trapped()],
exception_text[exception]);
}
if (interpreter_result.finished()) {
CHECK_EQ(interpreter_result.result(), result_compiled);
if (!exception) {
CHECK_EQ(result_ref, result);
}
}
......@@ -423,7 +498,18 @@ void WasmExecutionFuzzer::FuzzWasmModule(base::Vector<const uint8_t> data,
// compiled with Turbofan and which one with Liftoff.
uint8_t tier_mask = data.empty() ? 0 : data[0];
if (!data.empty()) data += 1;
// Build the bitmask to control which functions should be compiled for
// debugging.
uint8_t debug_mask = data.empty() ? 0 : data[0];
if (!data.empty()) data += 1;
// Control whether Liftoff or the interpreter will be used as the reference
// tier.
// TODO(thibaudm): Port nondeterminism detection to arm.
#if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_X86)
bool liftoff_as_reference = data.empty() ? false : data[0] % 2;
#else
bool liftoff_as_reference = false;
#endif
if (!data.empty()) data += 1;
if (!GenerateModule(i_isolate, &zone, data, &buffer)) {
return;
......@@ -461,7 +547,16 @@ void WasmExecutionFuzzer::FuzzWasmModule(base::Vector<const uint8_t> data,
if (!compiles) return;
InterpretAndExecuteModule(i_isolate, compiled_module.ToHandleChecked());
int32_t max_steps = 16 * 1024;
int32_t nondeterminism = false;
Handle<WasmModuleObject> module_ref;
if (liftoff_as_reference) {
module_ref = CompileReferenceModule(&zone, i_isolate, wire_bytes,
&interpreter_thrower, &max_steps,
&nondeterminism);
}
InterpretAndExecuteModule(i_isolate, compiled_module.ToHandleChecked(),
module_ref, &max_steps, &nondeterminism);
}
} // namespace fuzzer
......
......@@ -23,8 +23,10 @@ namespace fuzzer {
// possible. If the interpretation finishes within kMaxSteps steps,
// module_object is instantiated again and the compiled "main" function is
// executed.
void InterpretAndExecuteModule(Isolate* isolate,
Handle<WasmModuleObject> module_object);
void InterpretAndExecuteModule(
Isolate* isolate, Handle<WasmModuleObject> module_object,
Handle<WasmModuleObject> module_ref = Handle<WasmModuleObject>::null(),
int32_t* max_steps = nullptr, int32_t* nondeterminism = nullptr);
void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
bool compiles);
......
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