// Copyright 2021 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 #include "include/v8-exception.h" #include "include/v8-local-handle.h" #include "include/v8-primitive.h" #include "include/v8-value.h" #include "src/wasm/wasm-module-builder.h" #include "test/cctest/cctest.h" #include "test/cctest/test-api.h" #include "test/common/node-observer-tester.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-macro-gen.h" namespace v8 { namespace internal { namespace wasm { static const int kDeoptLoopCount = 1e3; // Validates the type of the result returned by a test function. template bool CheckType(v8::Local result) { return result->IsNumber(); } template <> bool CheckType(v8::Local result) { return result->IsUndefined(); } template <> bool CheckType(v8::Local result) { return result->IsInt32(); } template <> bool CheckType(v8::Local result) { return result->IsBigInt(); } template <> bool CheckType>(v8::Local result) { return result->IsBigInt(); } template <> bool CheckType>(v8::Local result) { return result->IsString(); } template <> bool CheckType(v8::Local result) { return result->IsNull(); } static TestSignatures sigs; struct ExportedFunction { std::string name; const FunctionSig* signature; std::vector locals; std::vector code; bool DoesSignatureContainI64() const { for (auto type : signature->all()) { if (type == wasm::kWasmI64) return true; } return false; } }; #define WASM_CODE(...) __VA_ARGS__ #define DECLARE_EXPORTED_FUNCTION(name, sig, code) \ static ExportedFunction k_##name = {#name, sig, {}, code}; #define DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(name, sig, locals, code) \ static ExportedFunction k_##name = {#name, sig, locals, code}; DECLARE_EXPORTED_FUNCTION(nop, sigs.v_v(), WASM_CODE({WASM_NOP})) DECLARE_EXPORTED_FUNCTION(unreachable, sigs.v_v(), WASM_CODE({WASM_UNREACHABLE})) DECLARE_EXPORTED_FUNCTION(i32_square, sigs.i_i(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(0), kExprI32Mul})) DECLARE_EXPORTED_FUNCTION(i64_square, sigs.l_l(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(0), kExprI64Mul})) DECLARE_EXPORTED_FUNCTION(externref_null_id, sigs.a_a(), WASM_CODE({WASM_LOCAL_GET(0)})) static constexpr ValueType extern_extern_types[] = {kWasmExternRef.AsNonNull(), kWasmExternRef.AsNonNull()}; static constexpr FunctionSig sig_extern_extern(1, 1, extern_extern_types); DECLARE_EXPORTED_FUNCTION(externref_id, &sig_extern_extern, WASM_CODE({WASM_LOCAL_GET(0)})) DECLARE_EXPORTED_FUNCTION(f32_square, sigs.f_f(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(0), kExprF32Mul})) DECLARE_EXPORTED_FUNCTION(f64_square, sigs.d_d(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(0), kExprF64Mul})) DECLARE_EXPORTED_FUNCTION(void_square, sigs.v_i(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(0), kExprI32Mul, kExprDrop})) DECLARE_EXPORTED_FUNCTION(add, sigs.i_ii(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), kExprI32Add})) DECLARE_EXPORTED_FUNCTION(i64_add, sigs.l_ll(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), kExprI64Add})) DECLARE_EXPORTED_FUNCTION(sum3, sigs.i_iii(), WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), WASM_LOCAL_GET(2), kExprI32Add, kExprI32Add})) DECLARE_EXPORTED_FUNCTION(no_args, sigs.i_v(), WASM_CODE({WASM_I32V(42)})) DECLARE_EXPORTED_FUNCTION(load_i32, sigs.i_i(), WASM_CODE({WASM_LOAD_MEM(MachineType::Int32(), WASM_LOCAL_GET(0))})) DECLARE_EXPORTED_FUNCTION(load_i64, sigs.l_l(), WASM_CODE({WASM_I64_SCONVERT_I32(WASM_LOAD_MEM( MachineType::Int32(), WASM_I32_CONVERT_I64(WASM_LOCAL_GET(0))))})) DECLARE_EXPORTED_FUNCTION(load_f32, sigs.f_f(), WASM_CODE({WASM_F32_SCONVERT_I32(WASM_LOAD_MEM( MachineType::Int32(), WASM_I32_SCONVERT_F32(WASM_LOCAL_GET(0))))})) DECLARE_EXPORTED_FUNCTION(load_f64, sigs.d_d(), WASM_CODE({WASM_F64_SCONVERT_I32(WASM_LOAD_MEM( MachineType::Int32(), WASM_I32_SCONVERT_F64(WASM_LOCAL_GET(0))))})) DECLARE_EXPORTED_FUNCTION(store_i32, sigs.v_ii(), WASM_CODE({WASM_STORE_MEM(MachineType::Int32(), WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))})) // int32_t test(int32_t v0, int32_t v1, int32_t v2, int32_t v3, int32_t v4, // int32_t v5, int32_t v6, int32_t v7, int32_t v8, int32_t v9) { // return v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9; // } static const ValueType kIntTypes11[11] = { kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32}; static FunctionSig i_iiiiiiiiii(1, 10, kIntTypes11); DECLARE_EXPORTED_FUNCTION( sum10, &i_iiiiiiiiii, WASM_CODE({WASM_LOCAL_GET(0), WASM_LOCAL_GET(1), WASM_LOCAL_GET(2), WASM_LOCAL_GET(3), WASM_LOCAL_GET(4), WASM_LOCAL_GET(5), WASM_LOCAL_GET(6), WASM_LOCAL_GET(7), WASM_LOCAL_GET(8), WASM_LOCAL_GET(9), kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add})) // double test(int32_t i32, int64_t i64, float f32, double f64) { // return i32 + i64 + f32 + f64; // } static const ValueType kMixedTypes5[5] = {kWasmF64, kWasmI32, kWasmI64, kWasmF32, kWasmF64}; static FunctionSig d_ilfd(1, 4, kMixedTypes5); DECLARE_EXPORTED_FUNCTION( sum_mixed, &d_ilfd, WASM_CODE({WASM_LOCAL_GET(2), kExprF64ConvertF32, WASM_LOCAL_GET(3), kExprF64Add, WASM_LOCAL_GET(0), kExprF64UConvertI32, kExprF64Add, WASM_LOCAL_GET(1), kExprF64UConvertI64, kExprF64Add})) // float f32_square_deopt(float f32) { // static int count = 0; // if (++count == kDeoptLoopCount) { // callback(f32); // } // return f32 * f32; // } DECLARE_EXPORTED_FUNCTION_WITH_LOCALS( f32_square_deopt, sigs.f_f(), {kWasmI32}, WASM_CODE( {WASM_STORE_MEM( MachineType::Int32(), WASM_I32V(1024), WASM_LOCAL_TEE(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(1024)), WASM_ONE))), WASM_BLOCK( WASM_BR_IF(0, WASM_I32_NE(WASM_LOCAL_GET(1), WASM_I32V(kDeoptLoopCount))), WASM_CALL_FUNCTION(0, WASM_F64_CONVERT_F32(WASM_LOCAL_GET(0)))), WASM_F32_MUL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(0))})) // double f64_square_deopt(double f64) { // static int count = 0; // if (++count == kDeoptLoopCount) { // callback(f64); // } // return f64 * f64; // } DECLARE_EXPORTED_FUNCTION_WITH_LOCALS( f64_square_deopt, sigs.d_d(), {kWasmI32}, WASM_CODE( {WASM_STORE_MEM( MachineType::Int32(), WASM_I32V(1028), WASM_LOCAL_TEE(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(1028)), WASM_ONE))), WASM_BLOCK(WASM_BR_IF(0, WASM_I32_NE(WASM_LOCAL_GET(1), WASM_I32V(kDeoptLoopCount))), WASM_CALL_FUNCTION(0, WASM_LOCAL_GET(0))), WASM_F64_MUL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(0))})) // int32_t i32_square_deopt(int32_t i32) { // static int count = 0; // if (++count == kDeoptLoopCount) { // callback(i32); // } // return i32 * i32; // } DECLARE_EXPORTED_FUNCTION_WITH_LOCALS( i32_square_deopt, sigs.i_i(), {kWasmI32}, WASM_CODE( {WASM_STORE_MEM( MachineType::Int32(), WASM_I32V(1032), WASM_LOCAL_TEE(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(1032)), WASM_ONE))), WASM_BLOCK( WASM_BR_IF(0, WASM_I32_NE(WASM_LOCAL_GET(1), WASM_I32V(kDeoptLoopCount))), WASM_CALL_FUNCTION(0, WASM_F64_SCONVERT_I32(WASM_LOCAL_GET(0)))), WASM_I32_MUL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(0))})) // int64_t i64_square_deopt(int64_t i64) { // static int count = 0; // if (++count == kDeoptLoopCount) { // callback(i64); // } // return i64 * i64; // } DECLARE_EXPORTED_FUNCTION_WITH_LOCALS( i64_square_deopt, sigs.l_l(), {kWasmI32}, WASM_CODE( {WASM_STORE_MEM( MachineType::Int32(), WASM_I32V(1036), WASM_LOCAL_TEE(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(1036)), WASM_ONE))), WASM_BLOCK( WASM_BR_IF(0, WASM_I32_NE(WASM_LOCAL_GET(1), WASM_I32V(kDeoptLoopCount))), WASM_CALL_FUNCTION(0, WASM_F64_SCONVERT_I64(WASM_LOCAL_GET(0)))), WASM_I64_MUL(WASM_LOCAL_GET(0), WASM_LOCAL_GET(0))})) // void void_square_deopt(int32_t i32) { // static int count = 0; // if (++count == kDeoptLoopCount) { // callback(i32); // } // } DECLARE_EXPORTED_FUNCTION_WITH_LOCALS( void_square_deopt, sigs.v_i(), {kWasmI32}, WASM_CODE( {WASM_STORE_MEM( MachineType::Int32(), WASM_I32V(1040), WASM_LOCAL_TEE(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(1040)), WASM_ONE))), WASM_BLOCK( WASM_BR_IF(0, WASM_I32_NE(WASM_LOCAL_GET(1), WASM_I32V(kDeoptLoopCount))), WASM_CALL_FUNCTION(0, WASM_F64_SCONVERT_I32(WASM_LOCAL_GET(0))))})) enum TestMode { kJSToWasmInliningDisabled, kJSToWasmInliningEnabled }; class FastJSWasmCallTester { public: FastJSWasmCallTester() : allocator_(), zone_(&allocator_, ZONE_NAME), builder_(zone_.New(&zone_)), old_budget_(i::v8_flags.invocation_count_for_turbofan) { i::v8_flags.experimental_wasm_typed_funcref = true; i::v8_flags.experimental_wasm_typed_funcref = true; i::v8_flags.allow_natives_syntax = true; i::v8_flags.turbo_inline_js_wasm_calls = true; i::v8_flags.stress_background_compile = false; i::v8_flags.concurrent_osr = false; // Seems to mess with %ObserveNode. i::v8_flags.invocation_count_for_turbofan = 20; } ~FastJSWasmCallTester() { i::v8_flags.invocation_count_for_turbofan = old_budget_; } void DeclareCallback(const char* name, FunctionSig* signature, const char* module) { builder_->AddImport(base::CStrVector(name), signature, base::CStrVector(module)); } void AddExportedFunction(const ExportedFunction& exported_func) { WasmFunctionBuilder* func = builder_->AddFunction(exported_func.signature); for (auto& wasm_type : exported_func.locals) func->AddLocal(wasm_type); func->EmitCode(exported_func.code.data(), static_cast(exported_func.code.size())); func->Emit(kExprEnd); builder_->AddExport(base::CStrVector(exported_func.name.c_str()), kExternalFunction, func->func_index()); // JS-to-Wasm inlining is disabled when targeting 32 bits if the Wasm // function signature contains an I64. #if defined(V8_TARGET_ARCH_32_BIT) if (exported_func.DoesSignatureContainI64()) { test_mode_ = kJSToWasmInliningDisabled; } #endif } // Executes a test function that returns a value of type T. template void CallAndCheckWasmFunction(const std::string& exported_function_name, const std::vector>& args, const T& expected_result, bool test_lazy_deopt = false) { LocalContext env; v8::Local result_value = DoCallAndCheckWasmFunction( env, exported_function_name, args, test_lazy_deopt); CHECK(CheckType(result_value)); if constexpr (std::is_convertible_v) { CHECK_EQ(result_value, expected_result); } else { T result = ConvertJSValue::Get(result_value, env.local()).ToChecked(); CHECK_EQ(result, expected_result); } } // Executes a test function that returns NaN. void CallAndCheckWasmFunctionNaN( const std::string& exported_function_name, const std::vector>& args, bool test_lazy_deopt = false) { LocalContext env; v8::Local result_value = DoCallAndCheckWasmFunction( env, exported_function_name, args, test_lazy_deopt); CHECK(CheckType(result_value)); double result = ConvertJSValue::Get(result_value, env.local()).ToChecked(); CHECK(std::isnan(result)); } // Executes a test function that returns a BigInt. void CallAndCheckWasmFunctionBigInt( const std::string& exported_function_name, const std::vector>& args, const v8::Local expected_result, bool test_lazy_deopt = false) { LocalContext env; v8::Local result_value = DoCallAndCheckWasmFunction( env, exported_function_name, args, test_lazy_deopt); CHECK(CheckType>(result_value)); auto result = ConvertJSValue::Get(result_value, env.local()).ToChecked(); CHECK_EQ(result->Int64Value(), expected_result->Int64Value()); } // Executes a test function that returns void. void CallAndCheckWasmFunction(const std::string& exported_function_name, const std::vector>& args, bool test_lazy_deopt = false) { LocalContext env; v8::Local result_value = DoCallAndCheckWasmFunction( env, exported_function_name, args, test_lazy_deopt); CHECK(test_lazy_deopt ? result_value->IsNumber() /* NaN */ : result_value->IsUndefined()); } // Executes a test function that triggers eager deoptimization. template T CallAndCheckWasmFunctionWithEagerDeopt( const std::string& exported_function_name, const std::string& arg, const T& expected_result, const std::string& deopt_arg) { LocalContext env; v8::Isolate* isolate = CcTest::isolate(); v8::TryCatch try_catch(isolate); std::string js_code = "const importObj = {" " env: {" " callback : function(num) {}" " }" "};" "let buf = new Uint8Array(" + WasmModuleAsJSArray() + ");" "let module = new WebAssembly.Module(buf);" "let instance = new WebAssembly.Instance(module, importObj);" "function test(value) {" " return %ObserveNode(instance.exports." + exported_function_name + "(value));" "}" "%PrepareFunctionForOptimization(test);" "test(" + arg + ");" "%OptimizeFunctionOnNextCall(test);" "test(" + arg + ");"; v8::Local result_value = CompileRunWithJSWasmCallNodeObserver(js_code.c_str()); CHECK(CheckType(result_value)); T result = ConvertJSValue::Get(result_value, env.local()).ToChecked(); CHECK_EQ(result, expected_result); std::string deopt_code = "test(" + deopt_arg + ");"; result_value = CompileRun(deopt_code.c_str()); CHECK(CheckType(result_value)); return ConvertJSValue::Get(result_value, env.local()).ToChecked(); } // Executes a test function that throws an exception. void CallAndCheckExceptionCaught(const std::string& exported_function_name, const v8::Local arg) { LocalContext env; CHECK((*env)->Global()->Set(env.local(), v8_str("arg"), arg).FromJust()); v8::Isolate* isolate = CcTest::isolate(); v8::TryCatch try_catch(isolate); std::string js_code = "const importObj = {" " env: {" " callback : function(num) {}" " }" "};" "let buf = new Uint8Array(" + WasmModuleAsJSArray() + ");" "let module = new WebAssembly.Module(buf);" "let instance = new WebAssembly.Instance(module, importObj);" "let " + exported_function_name + " = instance.exports." + exported_function_name + ";" "function test() {" " return %ObserveNode(" + exported_function_name + "(arg));" "}" "%PrepareFunctionForOptimization(test);" "test();"; CompileRun(js_code.c_str()); CHECK(try_catch.HasCaught()); try_catch.Reset(); CompileRunWithJSWasmCallNodeObserver( "%OptimizeFunctionOnNextCall(test); test();"); CHECK(try_catch.HasCaught()); } // Executes a test function with a try/catch. void CallAndCheckWithTryCatch(const std::string& exported_function_name, const v8::Local arg) { LocalContext env; CHECK((*env)->Global()->Set(env.local(), v8_str("arg"), arg).FromJust()); std::string js_code = "const importObj = {" " env: {" " callback : function(num) {}" " }" "};" "let buf = new Uint8Array(" + WasmModuleAsJSArray() + ");" "let module = new WebAssembly.Module(buf);" "let instance = new WebAssembly.Instance(module, importObj);" "let " + exported_function_name + " = instance.exports." + exported_function_name + ";" "function test() {" " try {" " return %ObserveNode(" + exported_function_name + "(arg));" " } catch (e) {" " return 0;" " }" "}" "%PrepareFunctionForOptimization(test);" "test();"; v8::Local result_value_interpreted = CompileRun(js_code.c_str()); CHECK(CheckType(result_value_interpreted)); auto result_interpreted = ConvertJSValue::Get(result_value_interpreted, env.local()) .ToChecked(); v8::Local result_value_compiled = CompileRun( "%OptimizeFunctionOnNextCall(test);" "test();"); CHECK(CheckType(result_value_compiled)); auto result_compiled = ConvertJSValue::Get(result_value_compiled, env.local()) .ToChecked(); CHECK_EQ(result_interpreted, result_compiled); } // Executes a test function with a try/catch calling a Wasm function returning // void. void CallAndCheckWithTryCatch_void( const std::string& exported_function_name, const std::vector>& args) { LocalContext env; for (size_t i = 0; i < args.size(); i++) { CHECK((*env) ->Global() ->Set(env.local(), v8_str(("arg" + std::to_string(i)).c_str()), args[i]) .FromJust()); } std::string js_args = ArgsToString(args.size()); std::string js_code = "const importObj = {" " env: {" " callback : function(num) {}" " }" "};" "let buf = new Uint8Array(" + WasmModuleAsJSArray() + ");" "let module = new WebAssembly.Module(buf);" "let instance = new WebAssembly.Instance(module, importObj);" "let " + exported_function_name + " = instance.exports." + exported_function_name + ";" "function test() {" " try {" " %ObserveNode(" + exported_function_name + "(" + js_args + "));" " return 1;" " } catch (e) {" " return 0;" " }" "}" "%PrepareFunctionForOptimization(test);" "test();"; v8::Local result_value_interpreted = CompileRun(js_code.c_str()); CHECK(CheckType(result_value_interpreted)); auto result_interpreted = ConvertJSValue::Get(result_value_interpreted, env.local()) .ToChecked(); v8::Local result_value_compiled = CompileRun( "%OptimizeFunctionOnNextCall(test);" "test();"); CHECK(CheckType(result_value_compiled)); auto result_compiled = ConvertJSValue::Get(result_value_compiled, env.local()) .ToChecked(); CHECK_EQ(result_interpreted, result_compiled); } private: // Convert the code of a Wasm module into a string that represents the content // of a JavaScript Uint8Array, that can be loaded with // WebAssembly.Module(buf). std::string WasmModuleAsJSArray() { ZoneBuffer buffer(&zone_); builder_->WriteTo(&buffer); std::stringstream string_stream; string_stream << "["; auto it = buffer.begin(); if (it != buffer.end()) { string_stream << "0x" << std::setfill('0') << std::setw(2) << std::hex << static_cast(*it++); } while (it != buffer.end()) { string_stream << ", 0x" << std::setfill('0') << std::setw(2) << std::hex << static_cast(*it++); } string_stream << "]"; return string_stream.str(); } v8::Local DoCallAndCheckWasmFunction( LocalContext& env, const std::string& exported_function_name, const std::vector>& args, bool test_lazy_deopt = false) { for (size_t i = 0; i < args.size(); i++) { CHECK((*env) ->Global() ->Set(env.local(), v8_str(("arg" + std::to_string(i)).c_str()), args[i]) .FromJust()); } std::string js_code = test_lazy_deopt ? GetJSTestCodeWithLazyDeopt(env, WasmModuleAsJSArray(), exported_function_name, args.size()) : GetJSTestCode(WasmModuleAsJSArray(), exported_function_name, args.size()); return CompileRunWithJSWasmCallNodeObserver(js_code); } v8::Local CompileRunWithJSWasmCallNodeObserver( const std::string& js_code) { // Note: Make sure to not capture stack locations (e.g. `this`) here since // these lambdas are executed on another thread. const auto test_mode = test_mode_; compiler::ModificationObserver js_wasm_call_observer( [](const compiler::Node* node) { CHECK_EQ(compiler::IrOpcode::kJSCall, node->opcode()); }, [test_mode](const compiler::Node* node, const compiler::ObservableNodeState& old_state) -> compiler::NodeObserver::Observation { if (old_state.opcode() != node->opcode()) { CHECK_EQ(compiler::IrOpcode::kJSCall, old_state.opcode()); // JS-to-Wasm inlining is disabled when targeting 32 bits if the // Wasm function signature contains an I64. CHECK_EQ(test_mode == kJSToWasmInliningEnabled ? compiler::IrOpcode::kJSWasmCall : compiler::IrOpcode::kCall, node->opcode()); return compiler::NodeObserver::Observation::kStop; } return compiler::NodeObserver::Observation::kContinue; }); { compiler::ObserveNodeScope scope(CcTest::i_isolate(), &js_wasm_call_observer); return CompileRun(js_code.c_str()); } } // Format the JS test code that loads and instantiates a Wasm module and // calls a Wasm exported function, making sure that it is compiled by // TurboFan: // // function test() {" // let result = exported_func(arg0, arg1, ..., argN-1); // return result;" // } std::string GetJSTestCode(const std::string& wasm_module, const std::string& wasm_exported_function_name, size_t arity) { std::string js_args = ArgsToString(arity); return "const importObj = {" " env: { callback : function(num) {} }" "};" "let buf = new Uint8Array(" + wasm_module + ");" "let module = new WebAssembly.Module(buf);" "let instance = new WebAssembly.Instance(module, importObj);" "let " + wasm_exported_function_name + " = instance.exports." + wasm_exported_function_name + ";" "function test() {" " let result = %ObserveNode(" + wasm_exported_function_name + "(" + js_args + "));" " return result;" "}" "%PrepareFunctionForOptimization(test);" "test(" + js_args + ");" "%OptimizeFunctionOnNextCall(test);" "test(" + js_args + ");"; } // Format the JS test code that loads and instantiates a Wasm module and // calls a Wasm exported function in a loop, and it's compiled with TurboFan: // // var b = 0;" // var n = 0;" // function test() {" // let result = 0; // for(var i = 0; i < 1e5; i++) { // result = exported_func(arg0 + b) + n; // } // return result;" // } // // Here the Wasm function calls back into a JavaScript function that modifies // the values of 'b' and 'n', triggering the lazy deoptimization of the 'test' // function. std::string GetJSTestCodeWithLazyDeopt( LocalContext& env, const std::string& wasm_module, const std::string& wasm_exported_function_name, size_t arity) { DCHECK_LE(arity, 1); bool bigint_arg = false; if (arity == 1) { v8::Local arg0 = (*env)->Global()->Get(env.local(), v8_str("arg0")).ToLocalChecked(); bigint_arg = arg0->IsBigInt(); } std::string js_args = ArgsToString(arity); std::string code = "const importObj = {" " env: {" " callback : function(num) {" " n = 1; b = 1;" " }" " }" "};" "let buf = new Uint8Array(" + wasm_module + ");" "let module = new WebAssembly.Module(buf);" "let instance = new WebAssembly.Instance(module, importObj);" "let " + wasm_exported_function_name + " = instance.exports." + wasm_exported_function_name + ";" "var b = 0;" "var n = 0;" "function test(" + js_args + ") {" " var result = 0;" " for (let i = 0; i < " + std::to_string(kDeoptLoopCount) + " + 5; i++) {"; code += bigint_arg ? " result = %ObserveNode(" + wasm_exported_function_name + "(" + js_args + " + BigInt(b))) + BigInt(n);" : " result = %ObserveNode(" + wasm_exported_function_name + "(" + js_args + " + b)) + n;"; code += " }" " return result;" "}" "%PrepareFunctionForOptimization(test);" "test(" + js_args + ");" "%OptimizeFunctionOnNextCall(test);" "test(" + js_args + ");"; return code; } // Format a string that represents the set of arguments passed to a test // function, in the form 'arg0, arg1, ..., argN-1'. // The value of these args is set by GetJSTestCodeWithLazyDeopt. std::string ArgsToString(size_t arity) { std::stringstream string_stream; for (size_t i = 0; i < arity; i++) { if (i > 0) string_stream << ", "; string_stream << "arg" << i; } return string_stream.str(); } AccountingAllocator allocator_; Zone zone_; WasmModuleBuilder* builder_; TestMode test_mode_ = kJSToWasmInliningEnabled; int old_budget_; }; TEST(TestFastJSWasmCall_Nop) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_nop); tester.CallAndCheckWasmFunction("nop", {}); } TEST(TestFastJSWasmCall_I32Arg) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_i32_square); tester.CallAndCheckWasmFunction("i32_square", {v8_num(42)}, 42 * 42); } TEST(TestFastJSWasmCall_I32ArgNotSmi) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_add); tester.CallAndCheckWasmFunction( "add", {v8_num(0x7fffffff), v8_int(1)}, 0x80000000); } TEST(TestFastJSWasmCall_F32Arg) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_f32_square); tester.CallAndCheckWasmFunction("f32_square", {v8_num(42.0)}, 42.0 * 42.0); } TEST(TestFastJSWasmCall_F64Arg) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_f64_square); tester.CallAndCheckWasmFunction("f64_square", {v8_num(42.0)}, 42.0 * 42.0); } TEST(TestFastJSWasmCall_I64Arg) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_i64_square); tester.CallAndCheckWasmFunctionBigInt("i64_square", {v8_bigint(1234567890ll)}, v8_bigint(1234567890ll * 1234567890ll)); } TEST(TestFastJSWasmCall_I64NegativeResult) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_i64_add); tester.CallAndCheckWasmFunctionBigInt( "i64_add", {v8_bigint(1ll), v8_bigint(-2ll)}, v8_bigint(-1ll)); } TEST(TestFastJSWasmCall_ExternrefNullArg) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_externref_null_id); Local v8_null = v8::Null(CcTest::isolate()); tester.CallAndCheckWasmFunction("externref_null_id", {v8_null}, nullptr); tester.CallAndCheckWasmFunction("externref_null_id", {v8_num(42)}, 42); tester.CallAndCheckWasmFunctionBigInt("externref_null_id", {v8_bigint(42)}, v8_bigint(42)); auto str = v8_str("test"); tester.CallAndCheckWasmFunction("externref_null_id", {str}, str); } TEST(TestFastJSWasmCall_ExternrefArg) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_externref_id); tester.CallAndCheckWasmFunction("externref_id", {v8_num(42)}, 42); tester.CallAndCheckWasmFunctionBigInt("externref_id", {v8_bigint(42)}, v8_bigint(42)); auto str = v8_str("test"); tester.CallAndCheckWasmFunction("externref_id", {str}, str); } TEST(TestFastJSWasmCall_MultipleArgs) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_sum10); tester.CallAndCheckWasmFunction( "sum10", {v8_num(1), v8_num(2), v8_num(3), v8_num(4), v8_num(5), v8_num(6), v8_num(7), v8_num(8), v8_num(9), v8_num(10)}, 55); } TEST(TestFastJSWasmCall_MixedArgs) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_sum_mixed); tester.CallAndCheckWasmFunction( "sum_mixed", {v8_num(1), v8_bigint(0x80000000), v8_num(42.0), v8_num(.5)}, 1 + 0x80000000 + 42 + .5); } TEST(TestFastJSWasmCall_MistypedArgs) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_i32_square); tester.CallAndCheckWasmFunction("i32_square", {v8_str("test")}, 0); } TEST(TestFastJSWasmCall_MixedMistypedArgs) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_sum_mixed); tester.CallAndCheckWasmFunctionNaN( "sum_mixed", {v8_str("alpha"), v8_bigint(0x80000000), v8_str("beta"), v8_str("gamma")}); } TEST(TestFastJSWasmCall_NoArgs) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_no_args); tester.CallAndCheckWasmFunction("no_args", {}, 42); } TEST(TestFastJSWasmCall_NoReturnTypes) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_void_square); tester.CallAndCheckWasmFunction("void_square", {v8_num(42)}); } TEST(TestFastJSWasmCall_MismatchedArity) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_sum3); tester.CallAndCheckWasmFunction("sum3", {v8_num(1), v8_num(2)}, 3); tester.CallAndCheckWasmFunction( "sum3", {v8_num(1), v8_num(2), v8_num(3), v8_num(4), v8_num(5), v8_num(6)}, 6); tester.CallAndCheckWasmFunction("sum3", {}, 0); } // Lazy deoptimization tests TEST(TestFastJSWasmCall_LazyDeopt_I32Result) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.DeclareCallback("callback", sigs.v_d(), "env"); tester.AddExportedFunction(k_i32_square_deopt); tester.CallAndCheckWasmFunction("i32_square_deopt", {v8_num(42)}, 43 * 43 + 1, true); } TEST(TestFastJSWasmCall_LazyDeopt_I64Result) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.DeclareCallback("callback", sigs.v_d(), "env"); tester.AddExportedFunction(k_i64_square_deopt); tester.CallAndCheckWasmFunctionBigInt("i64_square_deopt", {v8_bigint(42)}, v8_bigint(43 * 43 + 1), true); // This test would fail if the result was converted into a HeapNumber through // a double, losing precision. tester.CallAndCheckWasmFunctionBigInt( "i64_square_deopt", {v8_bigint(1234567890ll)}, v8_bigint(1524157877488187882ll), // (1234567890 + 1)*(1234567890 + 1)+1 true); } TEST(TestFastJSWasmCall_LazyDeopt_F32Result) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.DeclareCallback("callback", sigs.v_d(), "env"); tester.AddExportedFunction(k_f32_square_deopt); tester.CallAndCheckWasmFunction("f32_square_deopt", {v8_num(42.0)}, 43 * 43 + 1, true); } TEST(TestFastJSWasmCall_LazyDeopt_F64Result) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.DeclareCallback("callback", sigs.v_d(), "env"); tester.AddExportedFunction(k_f64_square_deopt); tester.CallAndCheckWasmFunction("f64_square_deopt", {v8_num(42.0)}, 43 * 43 + 1, true); } TEST(TestFastJSWasmCall_LazyDeopt_VoidResult) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.DeclareCallback("callback", sigs.v_d(), "env"); tester.AddExportedFunction(k_void_square_deopt); tester.CallAndCheckWasmFunction("void_square_deopt", {v8_num(42.0)}, true); } // Eager deoptimization tests TEST(TestFastJSWasmCall_EagerDeopt) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_f32_square); float result_after_deopt = tester.CallAndCheckWasmFunctionWithEagerDeopt( "f32_square", "42", 42.0 * 42.0, "{x:1,y:2}"); CHECK(std::isnan(result_after_deopt)); } // Exception handling tests TEST(TestFastJSWasmCall_Unreachable) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_unreachable); tester.CallAndCheckWithTryCatch_void("unreachable", {}); } TEST(TestFastJSWasmCall_Trap_i32) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_load_i32); tester.CallAndCheckWithTryCatch("load_i32", {v8_int(0x7fffffff)}); } TEST(TestFastJSWasmCall_Trap_i64) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_load_i64); tester.CallAndCheckWithTryCatch("load_i64", {v8_bigint(0x7fffffff)}); } TEST(TestFastJSWasmCall_Trap_f32) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_load_f32); tester.CallAndCheckWithTryCatch("load_f32", {v8_num(0x7fffffff)}); } TEST(TestFastJSWasmCall_Trap_f64) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_load_f64); tester.CallAndCheckWithTryCatch("load_f64", {v8_num(0x7fffffff)}); } TEST(TestFastJSWasmCall_Trap_void) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_store_i32); tester.CallAndCheckWithTryCatch_void("store_i32", {v8_int(0x7fffffff), v8_int(42)}); } // BigInt TEST(TestFastJSWasmCall_I64ArgExpectsBigInt) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_i64_square); tester.CallAndCheckExceptionCaught("i64_square", v8_int(42)); } TEST(TestFastJSWasmCall_F32ArgDoesntExpectBigInt) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_f32_square); tester.CallAndCheckExceptionCaught("f32_square", v8_bigint(42ll)); } TEST(TestFastJSWasmCall_F64ArgDoesntExpectBigInt) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_f64_square); tester.CallAndCheckExceptionCaught("f64_square", v8_bigint(42ll)); } TEST(TestFastJSWasmCall_I32ArgDoesntExpectBigInt) { v8::HandleScope scope(CcTest::isolate()); FastJSWasmCallTester tester; tester.AddExportedFunction(k_i32_square); tester.CallAndCheckExceptionCaught("i32_square", v8_bigint(42ll)); } } // namespace wasm } // namespace internal } // namespace v8