v8/test/cctest/wasm/test-run-wasm-wrappers.cc
Vicky Kontoura 26310718e4 [wasm] Replace generic js-to-wasm wrapper when threshold is reached
This CL adds a basic tiering strategy for the js-to-wasm wrappers.
When applicable, calls to exported WebAssembly functions are initially
handled through the generic js-to-wasm wrapper. If these calls
through the generic wrapper reach a constant threshold, the specific
(per-signature) wrapper is compiled synchronously for the function
and the generic wrapper is replaced.

Bug: v8:10982
Change-Id: I65e706daffb5cb6e723ce2f7b785f7ecb7b2fa7b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2461243
Commit-Queue: Vicky Kontoura <vkont@google.com>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70503}
2020-10-14 13:21:59 +00:00

185 lines
7.3 KiB
C++

// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_wrappers {
using testing::CompileAndInstantiateForTesting;
#ifdef V8_TARGET_ARCH_X64
namespace {
void Cleanup() {
// By sending a low memory notifications, we will try hard to collect all
// garbage and will therefore also invoke all weak callbacks of actually
// unreachable persistent handles.
Isolate* isolate = CcTest::InitIsolateOnce();
reinterpret_cast<v8::Isolate*>(isolate)->LowMemoryNotification();
}
} // namespace
TEST(CallCounter) {
{
// This test assumes use of the generic wrapper.
FlagScope<bool> use_wasm_generic_wrapper(&FLAG_wasm_generic_wrapper, true);
TestSignatures sigs;
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Define the Wasm function.
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_ii());
f->builder()->AddExport(CStrVector("main"), f);
byte code[] = {WASM_I32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)),
WASM_END};
f->EmitCode(code, sizeof(code));
// Compile module.
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeHandle<WasmInstanceObject> instance = CompileAndInstantiateForTesting(
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
MaybeHandle<WasmExportedFunction> maybe_export =
testing::GetExportedFunction(isolate, instance.ToHandleChecked(),
"main");
Handle<WasmExportedFunction> main_export = maybe_export.ToHandleChecked();
// Check that the counter has initially a value of 0.
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
0);
// Call the exported Wasm function and get the result.
Handle<Object> params[2] = {Handle<Object>(Smi::FromInt(6), isolate),
Handle<Object>(Smi::FromInt(7), isolate)};
static const int32_t kExpectedValue = 42;
Handle<Object> receiver = isolate->factory()->undefined_value();
MaybeHandle<Object> maybe_result =
Execution::Call(isolate, main_export, receiver, 2, params);
Handle<Object> result = maybe_result.ToHandleChecked();
// Check that the counter has now a value of 1.
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
1);
CHECK(result->IsSmi() && Smi::ToInt(*result) == kExpectedValue);
}
Cleanup();
}
TEST(WrapperReplacement) {
{
// This test assumes use of the generic wrapper.
FlagScope<bool> use_wasm_generic_wrapper(&FLAG_wasm_generic_wrapper, true);
TestSignatures sigs;
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Define the Wasm function.
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
f->builder()->AddExport(CStrVector("main"), f);
byte code[] = {WASM_RETURN1(WASM_GET_LOCAL(0)), WASM_END};
f->EmitCode(code, sizeof(code));
// Compile module.
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeHandle<WasmInstanceObject> instance = CompileAndInstantiateForTesting(
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
// Get the exported function.
MaybeHandle<WasmExportedFunction> maybe_export =
testing::GetExportedFunction(isolate, instance.ToHandleChecked(),
"main");
Handle<WasmExportedFunction> main_export = maybe_export.ToHandleChecked();
// Check that the counter has initially a value of 0.
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
0);
CHECK_GT(kGenericWrapperThreshold, 0);
// Call the exported Wasm function as many times as required to reach the
// threshold for compiling the specific wrapper.
const int threshold = static_cast<int>(kGenericWrapperThreshold);
for (int i = 1; i < threshold; ++i) {
// Verify that the wrapper to be used is still the generic one.
Code wrapper =
main_export->shared().wasm_exported_function_data().wrapper_code();
CHECK(wrapper.is_builtin() &&
wrapper.builtin_index() == Builtins::kGenericJSToWasmWrapper);
// Call the function.
int32_t expected_value = i;
Handle<Object> params[1] = {
Handle<Object>(Smi::FromInt(expected_value), isolate)};
Handle<Object> receiver = isolate->factory()->undefined_value();
MaybeHandle<Object> maybe_result =
Execution::Call(isolate, main_export, receiver, 1, params);
Handle<Object> result = maybe_result.ToHandleChecked();
// Verify that the counter has now a value of i and the return value is
// correct.
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
i);
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
}
// Get the wrapper-code object before making the call that will kick off the
// wrapper replacement.
Code wrapper_before_call =
main_export->shared().wasm_exported_function_data().wrapper_code();
// Verify that the wrapper before the call is the generic wrapper.
CHECK(wrapper_before_call.is_builtin() &&
wrapper_before_call.builtin_index() ==
Builtins::kGenericJSToWasmWrapper);
// Call the exported Wasm function one more time to kick off the wrapper
// replacement.
int32_t expected_value = 42;
Handle<Object> params[1] = {
Handle<Object>(Smi::FromInt(expected_value), isolate)};
Handle<Object> receiver = isolate->factory()->undefined_value();
MaybeHandle<Object> maybe_result =
Execution::Call(isolate, main_export, receiver, 1, params);
Handle<Object> result = maybe_result.ToHandleChecked();
// Check that the counter has the threshold value and the result is correct.
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
kGenericWrapperThreshold);
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
// Verify that the wrapper-code object has changed.
Code wrapper_after_call =
main_export->shared().wasm_exported_function_data().wrapper_code();
CHECK_NE(wrapper_after_call, wrapper_before_call);
// Verify that the wrapper is now a specific one.
CHECK(wrapper_after_call.kind() == CodeKind::JS_TO_WASM_FUNCTION);
}
Cleanup();
}
#endif
} // namespace test_run_wasm_wrappers
} // namespace wasm
} // namespace internal
} // namespace v8