// 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(isolate)->LowMemoryNotification(); } } // namespace TEST(CallCounter) { { // This test assumes use of the generic wrapper. FlagScope 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(&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 instance = CompileAndInstantiateForTesting( isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end())); MaybeHandle maybe_export = testing::GetExportedFunction(isolate, instance.ToHandleChecked(), "main"); Handle 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 params[2] = {Handle(Smi::FromInt(6), isolate), Handle(Smi::FromInt(7), isolate)}; static const int32_t kExpectedValue = 42; Handle receiver = isolate->factory()->undefined_value(); MaybeHandle maybe_result = Execution::Call(isolate, main_export, receiver, 2, params); Handle 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 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(&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 instance = CompileAndInstantiateForTesting( isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end())); // Get the exported function. MaybeHandle maybe_export = testing::GetExportedFunction(isolate, instance.ToHandleChecked(), "main"); Handle 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(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 params[1] = { Handle(Smi::FromInt(expected_value), isolate)}; Handle receiver = isolate->factory()->undefined_value(); MaybeHandle maybe_result = Execution::Call(isolate, main_export, receiver, 1, params); Handle 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 params[1] = { Handle(Smi::FromInt(expected_value), isolate)}; Handle receiver = isolate->factory()->undefined_value(); MaybeHandle maybe_result = Execution::Call(isolate, main_export, receiver, 1, params); Handle 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