// Copyright 2019 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. // Serializer tests don't make sense in lite mode, as it doesn't gather // IC feedback. #ifndef V8_LITE_MODE #include "test/cctest/compiler/serializer-tester.h" #include "src/api/api-inl.h" #include "src/codegen/optimized-compilation-info.h" #include "src/compiler/serializer-for-background-compilation.h" #include "src/compiler/zone-stats.h" #include "src/zone/zone.h" namespace v8 { namespace internal { namespace compiler { SerializerTester::SerializerTester(const char* source) : canonical_(main_isolate()) { // The tests only make sense in the context of concurrent compilation. FLAG_concurrent_inlining = true; // The tests don't make sense when optimizations are turned off. FLAG_opt = true; // We need the IC to feed it to the serializer. FLAG_use_ic = true; // We need manual control over when a given function is optimized. FLAG_always_opt = false; // We need allocation of executable memory for the compilation. FLAG_jitless = false; FLAG_allow_natives_syntax = true; FlagList::EnforceFlagImplications(); std::string function_string = "(function() { "; function_string += source; function_string += " })();"; Handle function = Handle::cast(v8::Utils::OpenHandle( *v8::Local::Cast(CompileRun(function_string.c_str())))); uint32_t flags = i::OptimizedCompilationInfo::kInlining | i::OptimizedCompilationInfo::kFunctionContextSpecializing | i::OptimizedCompilationInfo::kLoopPeeling | i::OptimizedCompilationInfo::kBailoutOnUninitialized | i::OptimizedCompilationInfo::kAllocationFolding | i::OptimizedCompilationInfo::kSplitting | i::OptimizedCompilationInfo::kAnalyzeEnvironmentLiveness; Optimize(function, main_zone(), main_isolate(), flags, &broker_); // Update handle to the corresponding serialized Handle in the broker. function = broker_->FindCanonicalPersistentHandleForTesting(*function); function_ = MakeRef(broker(), function); } TEST(SerializeEmptyFunction) { SerializerTester tester( "function f() {}; %EnsureFeedbackVectorForFunction(f); return f;"); JSFunctionRef function = tester.function(); CHECK(tester.broker()->IsSerializedForCompilation( function.shared(), function.feedback_vector())); } // This helper function allows for testing whether an inlinee candidate // was properly serialized. It expects that the top-level function (that is // run through the SerializerTester) will return its inlinee candidate. void CheckForSerializedInlinee(const char* source, int argc = 0, Handle argv[] = {}) { SerializerTester tester(source); JSFunctionRef f = tester.function(); CHECK(tester.broker()->IsSerializedForCompilation(f.shared(), f.feedback_vector())); MaybeHandle g_obj = Execution::Call( tester.isolate(), tester.function().object(), tester.isolate()->factory()->undefined_value(), argc, argv); Handle g; CHECK(g_obj.ToHandle(&g)); CHECK_WITH_MSG( g->IsJSFunction(), "The return value of the outer function must be a function too"); Handle g_func = Handle::cast(g); // Look up corresponding serialized Handles in the broker. Handle sfi( tester.broker() ->FindCanonicalPersistentHandleForTesting( g_func->shared())); SharedFunctionInfoRef g_sfi = MakeRef(tester.broker(), sfi); Handle fv( tester.broker()->FindCanonicalPersistentHandleForTesting( g_func->feedback_vector())); FeedbackVectorRef g_fv = MakeRef(tester.broker(), fv); CHECK(tester.broker()->IsSerializedForCompilation(g_sfi, g_fv)); } TEST(SerializeInlinedClosure) { CheckForSerializedInlinee( "function f() {" " function g(){ return g; }" " %EnsureFeedbackVectorForFunction(g);" " return g();" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeInlinedFunction) { CheckForSerializedInlinee( "function g() {};" "%EnsureFeedbackVectorForFunction(g);" "function f() {" " g(); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeCallUndefinedReceiver) { CheckForSerializedInlinee( "function g(a,b,c) {};" "%EnsureFeedbackVectorForFunction(g);" "function f() {" " g(1,2,3); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeCallUndefinedReceiver2) { CheckForSerializedInlinee( "function g(a,b) {};" "%EnsureFeedbackVectorForFunction(g);" "function f() {" " g(1,2); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeCallProperty) { CheckForSerializedInlinee( "let obj = {" " g: function g(a,b,c) {}" "};" "%EnsureFeedbackVectorForFunction(obj.g);" "function f() {" " obj.g(1,2,3); return obj.g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeCallProperty2) { CheckForSerializedInlinee( "let obj = {" " g: function g(a,b) {}" "};" "%EnsureFeedbackVectorForFunction(obj.g);" "function f() {" " obj.g(1,2); return obj.g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeCallAnyReceiver) { CheckForSerializedInlinee( "let obj = {" " g: function g() {}" "};" "%EnsureFeedbackVectorForFunction(obj.g);" "function f() {" " with(obj) {" " g(); return g;" " };" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeCallWithSpread) { CheckForSerializedInlinee( "function g(args) {};" "%EnsureFeedbackVectorForFunction(g);" "const arr = [1,2,3];" "function f() {" " g(...arr); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } // The following test causes the CallIC of `g` to turn megamorphic, // thus allowing us to test if we forward arguments hints (`callee` in this // example) and correctly serialize the inlining candidate `j`. TEST(SerializeCallArguments) { CheckForSerializedInlinee( "function g(callee) { callee(); };" "function h() {};" "function i() {};" "%EnsureFeedbackVectorForFunction(g);" "g(h); g(i);" "function f() {" " function j() {};" " g(j);" " return j;" "};" "%EnsureFeedbackVectorForFunction(f);" "var j = f();" "%EnsureFeedbackVectorForFunction(j);" "f(); return f;"); } TEST(SerializeConstruct) { CheckForSerializedInlinee( "function g() {};" "%EnsureFeedbackVectorForFunction(g);" "function f() {" " new g(); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeConstructWithSpread) { CheckForSerializedInlinee( "function g(a, b, c) {};" "%EnsureFeedbackVectorForFunction(g);" "const arr = [1, 2];" "function f() {" " new g(0, ...arr); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeConstructSuper) { CheckForSerializedInlinee( "class A {};" "class B extends A { constructor() { super(); } };" "%EnsureFeedbackVectorForFunction(A);" "%EnsureFeedbackVectorForFunction(B);" "function f() {" " new B(); return A;" "};" "%EnsureFeedbackVectorForFunction(f);" "f(); return f;"); } TEST(SerializeConditionalJump) { CheckForSerializedInlinee( "function g(callee) { callee(); };" "function h() {};" "function i() {};" "%EnsureFeedbackVectorForFunction(g);" "let a = true;" "g(h); g(i);" "function f() {" " function q() {};" " if (a) g(q);" " return q;" "};" "%EnsureFeedbackVectorForFunction(f);" "var q = f();" "%EnsureFeedbackVectorForFunction(q);" "f(); return f;"); } TEST(SerializeUnconditionalJump) { CheckForSerializedInlinee( "function g(callee) { callee(); };" "function h() {};" "function i() {};" "%EnsureFeedbackVectorForFunction(g);" "%EnsureFeedbackVectorForFunction(h);" "%EnsureFeedbackVectorForFunction(i);" "let a = false;" "g(h); g(i);" "function f() {" " function p() {};" " function q() {};" " if (a) q();" " else g(p);" " return p;" "};" "%EnsureFeedbackVectorForFunction(f);" "var p = f();" "%EnsureFeedbackVectorForFunction(p);" "f(); return f;"); } TEST(MergeJumpTargetEnvironment) { CheckForSerializedInlinee( "function f() {" " let g;" " while (true) {" " if (g === undefined) {g = ()=>1; break;} else {g = ()=>2; break};" " };" " g(); return g;" "};" "%EnsureFeedbackVectorForFunction(f);" "%EnsureFeedbackVectorForFunction(f());" "f(); return f;"); // Two calls to f to make g() megamorhpic. } TEST(BoundFunctionTarget) { CheckForSerializedInlinee( "function apply(foo, arg) { return foo(arg); };" "%EnsureFeedbackVectorForFunction(apply);" "function test() {" " const lambda = (a) => a;" " %EnsureFeedbackVectorForFunction(lambda);" " let bound = apply.bind(null, lambda).bind(null, 42);" " %TurbofanStaticAssert(bound() == 42); return apply;" "};" "%EnsureFeedbackVectorForFunction(test);" "test(); return test;"); } TEST(BoundFunctionArguments) { CheckForSerializedInlinee( "function apply(foo, arg) { return foo(arg); };" "%EnsureFeedbackVectorForFunction(apply);" "function test() {" " const lambda = (a) => a;" " %EnsureFeedbackVectorForFunction(lambda);" " let bound = apply.bind(null, lambda).bind(null, 42);" " %TurbofanStaticAssert(bound() == 42); return lambda;" "};" "%EnsureFeedbackVectorForFunction(test);" "test(); return test;"); } TEST(ArrowFunctionInlined) { // The loop is to ensure there is a feedback vector for the arrow function // {b}. CheckForSerializedInlinee( "function foo() {" " let b = x => x * x;" " let a = [1, 2, 3].map(b);" " return b;" "}" "%EnsureFeedbackVectorForFunction(foo);" "for (let i = 0; i < 100; ++i) foo();" "return foo;"); } TEST(BoundFunctionResult) { CheckForSerializedInlinee( "function id(x) { return x }" "function foo() { id.bind(undefined, 42)(); return id; }" "%PrepareFunctionForOptimization(foo);" "%PrepareFunctionForOptimization(id);" "foo();" "foo();" "%OptimizeFunctionOnNextCall(foo);" "foo(); return foo;"); } TEST(MultipleFunctionCalls) { CheckForSerializedInlinee( "function inc(x) { return ++x; }" "function dec(x) { return --x; }" "function apply(f, x) { return f(x); }" "function foo() { apply(inc, 42); apply(dec, 42); return dec; }" "%PrepareFunctionForOptimization(inc);" "%PrepareFunctionForOptimization(dec);" "%PrepareFunctionForOptimization(apply);" "%PrepareFunctionForOptimization(foo);" "foo();" "foo();" "%OptimizeFunctionOnNextCall(foo);" "foo(); return foo;"); } } // namespace compiler } // namespace internal } // namespace v8 #endif // V8_LITE_MODE