diff --git a/include/v8-debug.h b/include/v8-debug.h index 0d0ee739c0..50314501e3 100644 --- a/include/v8-debug.h +++ b/include/v8-debug.h @@ -276,6 +276,14 @@ class V8_EXPORT Debug { */ static MaybeLocal GetInternalProperties(Isolate* isolate, Local value); + + /** + * Defines if the ES2015 tail call elimination feature is enabled or not. + * The change of this flag triggers deoptimization of all functions that + * contain calls at tail position. + */ + static bool IsTailCallEliminationEnabled(Isolate* isolate); + static void SetTailCallEliminationEnabled(Isolate* isolate, bool enabled); }; diff --git a/src/api.cc b/src/api.cc index dea23a44f3..38c22db645 100644 --- a/src/api.cc +++ b/src/api.cc @@ -8089,6 +8089,15 @@ void Debug::SetLiveEditEnabled(Isolate* isolate, bool enable) { internal_isolate->debug()->set_live_edit_enabled(enable); } +bool Debug::IsTailCallEliminationEnabled(Isolate* isolate) { + i::Isolate* internal_isolate = reinterpret_cast(isolate); + return internal_isolate->is_tail_call_elimination_enabled(); +} + +void Debug::SetTailCallEliminationEnabled(Isolate* isolate, bool enabled) { + i::Isolate* internal_isolate = reinterpret_cast(isolate); + internal_isolate->SetTailCallEliminationEnabled(enabled); +} MaybeLocal Debug::GetInternalProperties(Isolate* v8_isolate, Local value) { diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index 284edfa0ab..2249cbcb3f 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -927,7 +927,13 @@ void BytecodeGraphBuilder::BuildCall(TailCallMode tail_call_mode) { void BytecodeGraphBuilder::VisitCall() { BuildCall(TailCallMode::kDisallow); } -void BytecodeGraphBuilder::VisitTailCall() { BuildCall(TailCallMode::kAllow); } +void BytecodeGraphBuilder::VisitTailCall() { + TailCallMode tail_call_mode = + bytecode_array_->GetIsolate()->is_tail_call_elimination_enabled() + ? TailCallMode::kAllow + : TailCallMode::kDisallow; + BuildCall(tail_call_mode); +} void BytecodeGraphBuilder::VisitCallJSRuntime() { FrameStateBeforeAndAfter states(this); diff --git a/src/isolate.cc b/src/isolate.cc index f2f02d3a4c..44e5ec0658 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -2885,6 +2885,14 @@ std::string Isolate::GetTurboCfgFileName() { } } +void Isolate::SetTailCallEliminationEnabled(bool enabled) { + if (is_tail_call_elimination_enabled_ == enabled) return; + is_tail_call_elimination_enabled_ = enabled; + // TODO(ishell): Introduce DependencyGroup::kTailCallChangedGroup to + // deoptimize only those functions that are affected by the change of this + // flag. + internal::Deoptimizer::DeoptimizeAll(this); +} // Heap::detached_contexts tracks detached contexts as pairs // (number of GC since the context was detached, the context). diff --git a/src/isolate.h b/src/isolate.h index 1ebaa28a76..a91ec93187 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -1096,9 +1096,7 @@ class Isolate { bool is_tail_call_elimination_enabled() const { return is_tail_call_elimination_enabled_; } - void set_tail_call_elimination_enabled(bool enabled) { - is_tail_call_elimination_enabled_ = enabled; - } + void SetTailCallEliminationEnabled(bool enabled); void AddDetachedContext(Handle context); void CheckDetachedContextsAfterGC(); diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc index e3063d8b1f..22f74807fb 100644 --- a/src/parsing/parser.cc +++ b/src/parsing/parser.cc @@ -784,7 +784,8 @@ Parser::Parser(ParseInfo* info) DCHECK(!info->script().is_null() || info->source_stream() != NULL); set_allow_lazy(info->allow_lazy_parsing()); set_allow_natives(FLAG_allow_natives_syntax || info->is_native()); - set_allow_tailcalls(FLAG_harmony_tailcalls && !info->is_native()); + set_allow_tailcalls(FLAG_harmony_tailcalls && !info->is_native() && + info->isolate()->is_tail_call_elimination_enabled()); set_allow_harmony_sloppy(FLAG_harmony_sloppy); set_allow_harmony_sloppy_function(FLAG_harmony_sloppy_function); set_allow_harmony_sloppy_let(FLAG_harmony_sloppy_let); diff --git a/test/cctest/test-debug.cc b/test/cctest/test-debug.cc index c1cfc4ac79..253842d152 100644 --- a/test/cctest/test-debug.cc +++ b/test/cctest/test-debug.cc @@ -8070,3 +8070,60 @@ TEST(BreakLocationIterator) { DisableDebugger(isolate); } + +TEST(DisableTailCallElimination) { + i::FLAG_allow_natives_syntax = true; + i::FLAG_harmony_tailcalls = true; + // TODO(ishell, 4698): Investigate why TurboFan in --always-opt mode makes + // stack[2].getFunctionName() return null. + i::FLAG_turbo_inlining = false; + + DebugLocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::HandleScope scope(isolate); + CHECK(v8::Debug::IsTailCallEliminationEnabled(isolate)); + + CompileRun( + "'use strict'; \n" + "Error.prepareStackTrace = (error,stack) => { \n" + " error.strace = stack; \n" + " return error.message + \"\\n at \" + stack.join(\"\\n at \"); \n" + "} \n" + " \n" + "function getCaller() { \n" + " var e = new Error(); \n" + " e.stack; // prepare stack trace \n" + " var stack = e.strace; \n" + " %GlobalPrint('caller: '); \n" + " %GlobalPrint(stack[2].getFunctionName()); \n" + " %GlobalPrint('\\n'); \n" + " return stack[2].getFunctionName(); \n" + "} \n" + "function f() { \n" + " var caller = getCaller(); \n" + " if (caller === 'g') return 1; \n" + " if (caller === 'h') return 2; \n" + " return 0; \n" + "} \n" + "function g() { \n" + " return f(); \n" + "} \n" + "function h() { \n" + " var result = g(); \n" + " return result; \n" + "} \n" + "%NeverOptimizeFunction(getCaller); \n" + "%NeverOptimizeFunction(f); \n" + "%NeverOptimizeFunction(h); \n" + ""); + ExpectInt32("h();", 2); + ExpectInt32("h(); %OptimizeFunctionOnNextCall(g); h();", 2); + v8::Debug::SetTailCallEliminationEnabled(isolate, false); + CHECK(!v8::Debug::IsTailCallEliminationEnabled(isolate)); + ExpectInt32("h();", 1); + ExpectInt32("h(); %OptimizeFunctionOnNextCall(g); h();", 1); + v8::Debug::SetTailCallEliminationEnabled(isolate, true); + CHECK(v8::Debug::IsTailCallEliminationEnabled(isolate)); + ExpectInt32("h();", 2); + ExpectInt32("h(); %OptimizeFunctionOnNextCall(g); h();", 2); +}