// Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "src/v8.h" #include "src/api.h" #include "src/base/platform/platform.h" #include "src/compilation-cache.h" #include "src/debug/debug.h" #include "src/deoptimizer.h" #include "src/isolate.h" #include "src/objects-inl.h" #include "test/cctest/cctest.h" using ::v8::base::OS; using ::v8::internal::Deoptimizer; using ::v8::internal::EmbeddedVector; using ::v8::internal::Handle; using ::v8::internal::JSFunction; // Size of temp buffer for formatting small strings. #define SMALL_STRING_BUFFER_SIZE 80 // Utility class to set the following runtime flags when constructed and return // to their default state when destroyed: // --allow-natives-syntax --always-opt --noturbo-inlining class AlwaysOptimizeAllowNativesSyntaxNoInlining { public: AlwaysOptimizeAllowNativesSyntaxNoInlining() : always_opt_(i::FLAG_always_opt), allow_natives_syntax_(i::FLAG_allow_natives_syntax), turbo_inlining_(i::FLAG_turbo_inlining) { i::FLAG_always_opt = true; i::FLAG_allow_natives_syntax = true; i::FLAG_turbo_inlining = false; } ~AlwaysOptimizeAllowNativesSyntaxNoInlining() { i::FLAG_always_opt = always_opt_; i::FLAG_allow_natives_syntax = allow_natives_syntax_; i::FLAG_turbo_inlining = turbo_inlining_; } private: bool always_opt_; bool allow_natives_syntax_; bool turbo_inlining_; }; // Utility class to set the following runtime flags when constructed and return // to their default state when destroyed: // --allow-natives-syntax --noturbo-inlining class AllowNativesSyntaxNoInlining { public: AllowNativesSyntaxNoInlining() : allow_natives_syntax_(i::FLAG_allow_natives_syntax), turbo_inlining_(i::FLAG_turbo_inlining) { i::FLAG_allow_natives_syntax = true; i::FLAG_turbo_inlining = false; } ~AllowNativesSyntaxNoInlining() { i::FLAG_allow_natives_syntax = allow_natives_syntax_; i::FLAG_turbo_inlining = turbo_inlining_; } private: bool allow_natives_syntax_; bool turbo_inlining_; }; // Abort any ongoing incremental marking to make sure that all weak global // handle callbacks are processed. static void NonIncrementalGC(i::Isolate* isolate) { isolate->heap()->CollectAllGarbage(i::Heap::kFinalizeIncrementalMarkingMask, i::GarbageCollectionReason::kTesting); } static Handle GetJSFunction(v8::Local context, const char* property_name) { v8::Local fun = v8::Local::Cast( context->Global()->Get(context, v8_str(property_name)).ToLocalChecked()); return i::Handle::cast(v8::Utils::OpenHandle(*fun)); } TEST(DeoptimizeSimple) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); // Test lazy deoptimization of a simple function. { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "function h() { %DeoptimizeFunction(f); }" "function g() { count++; h(); }" "function f() { g(); };" "f();"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); // Test lazy deoptimization of a simple function. Call the function after the // deoptimization while it is still activated further down the stack. { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "function g() { count++; %DeoptimizeFunction(f); f(false); }" "function f(x) { if (x) { g(); } else { return } };" "f(true);"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); } TEST(DeoptimizeSimpleWithArguments) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); // Test lazy deoptimization of a simple function with some arguments. { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "function h(x) { %DeoptimizeFunction(f); }" "function g(x, y) { count++; h(x); }" "function f(x, y, z) { g(1,x); y+z; };" "f(1, \"2\", false);"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); // Test lazy deoptimization of a simple function with some arguments. Call the // function after the deoptimization while it is still activated further down // the stack. { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "function g(x, y) { count++; %DeoptimizeFunction(f); f(false, 1, y); }" "function f(x, y, z) { if (x) { g(x, y); } else { return y + z; } };" "f(true, 1, \"2\");"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); } TEST(DeoptimizeSimpleNested) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); // Test lazy deoptimization of a simple function. Have a nested function call // do the deoptimization. { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "var result = 0;" "function h(x, y, z) { return x + y + z; }" "function g(z) { count++; %DeoptimizeFunction(f); return z;}" "function f(x,y,z) { return h(x, y, g(z)); };" "result = f(1, 2, 3);"); NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(6, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); } } TEST(DeoptimizeRecursive) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); { // Test lazy deoptimization of a simple function called recursively. Call // the function recursively a number of times before deoptimizing it. AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "var calls = 0;" "function g() { count++; %DeoptimizeFunction(f); }" "function f(x) { calls++; if (x > 0) { f(x - 1); } else { g(); } };" "f(10);"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(11, env->Global() ->Get(env.local(), v8_str("calls")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); v8::Local fun = v8::Local::Cast( env->Global() ->Get(env.local(), v8_str(CcTest::isolate(), "f")) .ToLocalChecked()); CHECK(!fun.IsEmpty()); } TEST(DeoptimizeMultiple) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "var result = 0;" "function g() { count++;" " %DeoptimizeFunction(f1);" " %DeoptimizeFunction(f2);" " %DeoptimizeFunction(f3);" " %DeoptimizeFunction(f4);}" "function f4(x) { g(); };" "function f3(x, y, z) { f4(); return x + y + z; };" "function f2(x, y) { return x + f3(y + 1, y + 1, y + 1) + y; };" "function f1(x) { return f2(x + 1, x + 1) + x; };" "result = f1(1);"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(14, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); } TEST(DeoptimizeConstructor) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "function g() { count++;" " %DeoptimizeFunction(f); }" "function f() { g(); };" "result = new f() instanceof f;"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK(env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->IsTrue()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "var result = 0;" "function g() { count++;" " %DeoptimizeFunction(f); }" "function f(x, y) { this.x = x; g(); this.y = y; };" "result = new f(1, 2);" "result = result.x + result.y;"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(3, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); } TEST(DeoptimizeConstructorMultiple) { ManualGCScope manual_gc_scope; LocalContext env; v8::HandleScope scope(env->GetIsolate()); { AlwaysOptimizeAllowNativesSyntaxNoInlining options; CompileRun( "var count = 0;" "var result = 0;" "function g() { count++;" " %DeoptimizeFunction(f1);" " %DeoptimizeFunction(f2);" " %DeoptimizeFunction(f3);" " %DeoptimizeFunction(f4);}" "function f4(x) { this.result = x; g(); };" "function f3(x, y, z) { this.result = new f4(x + y + z).result; };" "function f2(x, y) {" " this.result = x + new f3(y + 1, y + 1, y + 1).result + y; };" "function f1(x) { this.result = new f2(x + 1, x + 1).result + x; };" "result = new f1(1).result;"); } NonIncrementalGC(CcTest::i_isolate()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(14, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(CcTest::i_isolate())); } UNINITIALIZED_TEST(DeoptimizeBinaryOperationADDString) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; AllowNativesSyntaxNoInlining options; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); const char* f_source = "function f(x, y) { return x + y; };"; { // Compile function f and collect to type feedback to insert binary op // stub call in the optimized code. i::FLAG_prepare_always_opt = true; CompileRun( "var count = 0;" "var result = 0;" "var deopt = false;" "function X() { };" "X.prototype.toString = function () {" " if (deopt) { count++; %DeoptimizeFunction(f); } return 'an X'" "};"); CompileRun(f_source); CompileRun( "for (var i = 0; i < 5; i++) {" " f('a+', new X());" "};"); // Compile an optimized version of f. i::FLAG_always_opt = true; CompileRun(f_source); CompileRun("f('a+', new X());"); CHECK(!i_isolate->use_optimizer() || GetJSFunction(env.local(), "f")->IsOptimized()); // Call f and force deoptimization while processing the binary operation. CompileRun( "deopt = true;" "var result = f('a+', new X());"); } NonIncrementalGC(i_isolate); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); v8::Local result = env->Global()->Get(env.local(), v8_str("result")).ToLocalChecked(); CHECK(result->IsString()); v8::String::Utf8Value utf8(isolate, result); CHECK_EQ(0, strcmp("a+an X", *utf8)); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } static void CompileConstructorWithDeoptimizingValueOf() { CompileRun("var count = 0;" "var result = 0;" "var deopt = false;" "function X() { };" "X.prototype.valueOf = function () {" " if (deopt) { count++; %DeoptimizeFunction(f); } return 8" "};"); } static void TestDeoptimizeBinaryOpHelper(LocalContext* env, const char* binary_op) { i::Isolate* i_isolate = reinterpret_cast((*env)->GetIsolate()); EmbeddedVector f_source_buffer; SNPrintF(f_source_buffer, "function f(x, y) { return x %s y; };", binary_op); char* f_source = f_source_buffer.start(); AllowNativesSyntaxNoInlining options; // Compile function f and collect to type feedback to insert binary op stub // call in the optimized code. i::FLAG_prepare_always_opt = true; CompileConstructorWithDeoptimizingValueOf(); CompileRun(f_source); CompileRun("for (var i = 0; i < 5; i++) {" " f(8, new X());" "};"); // Compile an optimized version of f. i::FLAG_always_opt = true; CompileRun(f_source); CompileRun("f(7, new X());"); CHECK(!i_isolate->use_optimizer() || GetJSFunction((*env).local(), "f")->IsOptimized()); // Call f and force deoptimization while processing the binary operation. CompileRun("deopt = true;" "var result = f(7, new X());"); NonIncrementalGC(i_isolate); CHECK(!GetJSFunction((*env).local(), "f")->IsOptimized()); } UNINITIALIZED_TEST(DeoptimizeBinaryOperationADD) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); TestDeoptimizeBinaryOpHelper(&env, "+"); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(15, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeBinaryOperationSUB) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); TestDeoptimizeBinaryOpHelper(&env, "-"); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(-1, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeBinaryOperationMUL) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); TestDeoptimizeBinaryOpHelper(&env, "*"); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(56, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeBinaryOperationDIV) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); TestDeoptimizeBinaryOpHelper(&env, "/"); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeBinaryOperationMOD) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); TestDeoptimizeBinaryOpHelper(&env, "%"); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(7, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeCompare) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); const char* f_source = "function f(x, y) { return x < y; };"; { AllowNativesSyntaxNoInlining options; // Compile function f and collect to type feedback to insert compare ic // call in the optimized code. i::FLAG_prepare_always_opt = true; CompileRun( "var count = 0;" "var result = 0;" "var deopt = false;" "function X() { };" "X.prototype.toString = function () {" " if (deopt) { count++; %DeoptimizeFunction(f); } return 'b'" "};"); CompileRun(f_source); CompileRun( "for (var i = 0; i < 5; i++) {" " f('a', new X());" "};"); // Compile an optimized version of f. i::FLAG_always_opt = true; CompileRun(f_source); CompileRun("f('a', new X());"); CHECK(!i_isolate->use_optimizer() || GetJSFunction(env.local(), "f")->IsOptimized()); // Call f and force deoptimization while processing the comparison. CompileRun( "deopt = true;" "var result = f('a', new X());"); } NonIncrementalGC(i_isolate); CHECK(!GetJSFunction(env.local(), "f")->IsOptimized()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(true, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->BooleanValue(env.local()) .FromJust()); CHECK_EQ(0, Deoptimizer::GetDeoptimizedCodeCount(i_isolate)); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeLoadICStoreIC) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); // Functions to generate load/store/keyed load/keyed store IC calls. const char* f1_source = "function f1(x) { return x.y; };"; const char* g1_source = "function g1(x) { x.y = 1; };"; const char* f2_source = "function f2(x, y) { return x[y]; };"; const char* g2_source = "function g2(x, y) { x[y] = 1; };"; { AllowNativesSyntaxNoInlining options; // Compile functions and collect to type feedback to insert ic // calls in the optimized code. i::FLAG_prepare_always_opt = true; CompileRun( "var count = 0;" "var result = 0;" "var deopt = false;" "function X() { };" "X.prototype.__defineGetter__('y', function () {" " if (deopt) { count++; %DeoptimizeFunction(f1); };" " return 13;" "});" "X.prototype.__defineSetter__('y', function () {" " if (deopt) { count++; %DeoptimizeFunction(g1); };" "});" "X.prototype.__defineGetter__('z', function () {" " if (deopt) { count++; %DeoptimizeFunction(f2); };" " return 13;" "});" "X.prototype.__defineSetter__('z', function () {" " if (deopt) { count++; %DeoptimizeFunction(g2); };" "});"); CompileRun(f1_source); CompileRun(g1_source); CompileRun(f2_source); CompileRun(g2_source); CompileRun( "for (var i = 0; i < 5; i++) {" " f1(new X());" " g1(new X());" " f2(new X(), 'z');" " g2(new X(), 'z');" "};"); // Compile an optimized version of the functions. i::FLAG_always_opt = true; CompileRun(f1_source); CompileRun(g1_source); CompileRun(f2_source); CompileRun(g2_source); CompileRun("f1(new X());"); CompileRun("g1(new X());"); CompileRun("f2(new X(), 'z');"); CompileRun("g2(new X(), 'z');"); if (i_isolate->use_optimizer()) { CHECK(GetJSFunction(env.local(), "f1")->IsOptimized()); CHECK(GetJSFunction(env.local(), "g1")->IsOptimized()); CHECK(GetJSFunction(env.local(), "f2")->IsOptimized()); CHECK(GetJSFunction(env.local(), "g2")->IsOptimized()); } // Call functions and force deoptimization while processing the ics. CompileRun( "deopt = true;" "var result = f1(new X());" "g1(new X());" "f2(new X(), 'z');" "g2(new X(), 'z');"); } NonIncrementalGC(i_isolate); CHECK(!GetJSFunction(env.local(), "f1")->IsOptimized()); CHECK(!GetJSFunction(env.local(), "g1")->IsOptimized()); CHECK(!GetJSFunction(env.local(), "f2")->IsOptimized()); CHECK(!GetJSFunction(env.local(), "g2")->IsOptimized()); CHECK_EQ(4, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(13, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } isolate->Exit(); isolate->Dispose(); } UNINITIALIZED_TEST(DeoptimizeLoadICStoreICNested) { ManualGCScope manual_gc_scope; i::FLAG_concurrent_recompilation = false; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); i::Isolate* i_isolate = reinterpret_cast(isolate); isolate->Enter(); { LocalContext env(isolate); v8::HandleScope scope(env->GetIsolate()); // Functions to generate load/store/keyed load/keyed store IC calls. const char* f1_source = "function f1(x) { return x.y; };"; const char* g1_source = "function g1(x) { x.y = 1; };"; const char* f2_source = "function f2(x, y) { return x[y]; };"; const char* g2_source = "function g2(x, y) { x[y] = 1; };"; { AllowNativesSyntaxNoInlining options; // Compile functions and collect to type feedback to insert ic // calls in the optimized code. i::FLAG_prepare_always_opt = true; CompileRun( "var count = 0;" "var result = 0;" "var deopt = false;" "function X() { };" "X.prototype.__defineGetter__('y', function () {" " g1(this);" " return 13;" "});" "X.prototype.__defineSetter__('y', function () {" " f2(this, 'z');" "});" "X.prototype.__defineGetter__('z', function () {" " g2(this, 'z');" "});" "X.prototype.__defineSetter__('z', function () {" " if (deopt) {" " count++;" " %DeoptimizeFunction(f1);" " %DeoptimizeFunction(g1);" " %DeoptimizeFunction(f2);" " %DeoptimizeFunction(g2); };" "});"); CompileRun(f1_source); CompileRun(g1_source); CompileRun(f2_source); CompileRun(g2_source); CompileRun( "for (var i = 0; i < 5; i++) {" " f1(new X());" " g1(new X());" " f2(new X(), 'z');" " g2(new X(), 'z');" "};"); // Compile an optimized version of the functions. i::FLAG_always_opt = true; CompileRun(f1_source); CompileRun(g1_source); CompileRun(f2_source); CompileRun(g2_source); CompileRun("f1(new X());"); CompileRun("g1(new X());"); CompileRun("f2(new X(), 'z');"); CompileRun("g2(new X(), 'z');"); if (i_isolate->use_optimizer()) { CHECK(GetJSFunction(env.local(), "f1")->IsOptimized()); CHECK(GetJSFunction(env.local(), "g1")->IsOptimized()); CHECK(GetJSFunction(env.local(), "f2")->IsOptimized()); CHECK(GetJSFunction(env.local(), "g2")->IsOptimized()); } // Call functions and force deoptimization while processing the ics. CompileRun( "deopt = true;" "var result = f1(new X());"); } NonIncrementalGC(i_isolate); CHECK(!GetJSFunction(env.local(), "f1")->IsOptimized()); CHECK(!GetJSFunction(env.local(), "g1")->IsOptimized()); CHECK(!GetJSFunction(env.local(), "f2")->IsOptimized()); CHECK(!GetJSFunction(env.local(), "g2")->IsOptimized()); CHECK_EQ(1, env->Global() ->Get(env.local(), v8_str("count")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(13, env->Global() ->Get(env.local(), v8_str("result")) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } isolate->Exit(); isolate->Dispose(); }