[testing] Prevent heuristics from triggering optimization in tests
This CL adds a mechanism that prevents the RuntimeProfiler from triggering optimization of a function after %PrepareFunctionForOptimization has been called. This is useful to prevent flakiness in tests, as sometimes a function that already got deoptimized would receive a new code object from a concurrent compile that was triggered by a heuristic just in the right moment for the assertUnoptimized test to fail. For example, the following was happening: PrepareFunctionForOptimization [marking `testAdd` for optimized recompilation, reason: small function] [concurrently compiling method `testAdd` using TurboFan] [manually marking `testAdd` for non-concurrent optimization] [synchonously compiling method `testAdd` using TurboFan] [synchonously optimizing `testAdd` produced code object 0xAAAA - took 1.638 ms] Runtime_GetOptimizationStatus OPTIMIZED `testAdd` (code object 0xAAAA) DeoptimizeFunction `testAdd` with Code Object 0xAAAA [concurrently optimizing `testAdd` produced code object 0xBBBB - took 3.377 ms] Runtime_GetOptimizationStatus OPTIMIZED `testAdd` (code object 0xBBBB) Bug: v8:9563 Change-Id: Ia4c846aba95281589317d43b82383e70fe0a35f5 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1763546 Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Commit-Queue: Sigurd Schneider <sigurds@chromium.org> Cr-Commit-Position: refs/heads/master@{#63343}
This commit is contained in:
parent
6fa5de0b8e
commit
6d9b7988f7
@ -4,6 +4,7 @@
|
||||
|
||||
#include "src/codegen/pending-optimization-table.h"
|
||||
|
||||
#include "src/base/flags.h"
|
||||
#include "src/execution/isolate-inl.h"
|
||||
#include "src/heap/heap-inl.h"
|
||||
#include "src/objects/hash-table.h"
|
||||
@ -12,12 +13,24 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
enum class FunctionStatus { kPrepareForOptimize, kMarkForOptimize };
|
||||
enum class FunctionStatus : int {
|
||||
kPrepareForOptimize = 1 << 0,
|
||||
kMarkForOptimize = 1 << 1,
|
||||
kAllowHeuristicOptimization = 1 << 2,
|
||||
};
|
||||
|
||||
using FunctionStatusFlags = base::Flags<FunctionStatus>;
|
||||
|
||||
void PendingOptimizationTable::PreparedForOptimization(
|
||||
Isolate* isolate, Handle<JSFunction> function) {
|
||||
Isolate* isolate, Handle<JSFunction> function,
|
||||
bool allow_heuristic_optimization) {
|
||||
DCHECK(FLAG_testing_d8_test_runner);
|
||||
|
||||
FunctionStatusFlags status = FunctionStatus::kPrepareForOptimize;
|
||||
if (allow_heuristic_optimization) {
|
||||
status |= FunctionStatus::kAllowHeuristicOptimization;
|
||||
}
|
||||
|
||||
Handle<ObjectHashTable> table =
|
||||
isolate->heap()->pending_optimize_for_test_bytecode().IsUndefined()
|
||||
? ObjectHashTable::New(isolate, 1)
|
||||
@ -26,15 +39,33 @@ void PendingOptimizationTable::PreparedForOptimization(
|
||||
isolate);
|
||||
Handle<Tuple2> tuple = isolate->factory()->NewTuple2(
|
||||
handle(function->shared().GetBytecodeArray(), isolate),
|
||||
handle(
|
||||
Smi::FromInt(static_cast<int>(FunctionStatus::kPrepareForOptimize)),
|
||||
isolate),
|
||||
AllocationType::kYoung);
|
||||
handle(Smi::FromInt(status), isolate), AllocationType::kYoung);
|
||||
table =
|
||||
ObjectHashTable::Put(table, handle(function->shared(), isolate), tuple);
|
||||
isolate->heap()->SetPendingOptimizeForTestBytecode(*table);
|
||||
}
|
||||
|
||||
bool PendingOptimizationTable::IsHeuristicOptimizationAllowed(
|
||||
Isolate* isolate, JSFunction function) {
|
||||
DCHECK(FLAG_testing_d8_test_runner);
|
||||
|
||||
Handle<Object> table =
|
||||
handle(isolate->heap()->pending_optimize_for_test_bytecode(), isolate);
|
||||
Handle<Object> entry =
|
||||
table->IsUndefined()
|
||||
? handle(ReadOnlyRoots(isolate).the_hole_value(), isolate)
|
||||
: handle(Handle<ObjectHashTable>::cast(table)->Lookup(
|
||||
handle(function.shared(), isolate)),
|
||||
isolate);
|
||||
if (entry->IsTheHole()) {
|
||||
return true;
|
||||
}
|
||||
DCHECK(entry->IsTuple2());
|
||||
DCHECK(Handle<Tuple2>::cast(entry)->value2().IsSmi());
|
||||
FunctionStatusFlags status(Smi::ToInt(Handle<Tuple2>::cast(entry)->value2()));
|
||||
return status & FunctionStatus::kAllowHeuristicOptimization;
|
||||
}
|
||||
|
||||
void PendingOptimizationTable::MarkedForOptimization(
|
||||
Isolate* isolate, Handle<JSFunction> function) {
|
||||
DCHECK(FLAG_testing_d8_test_runner);
|
||||
@ -58,8 +89,11 @@ void PendingOptimizationTable::MarkedForOptimization(
|
||||
}
|
||||
|
||||
DCHECK(entry->IsTuple2());
|
||||
Handle<Tuple2>::cast(entry)->set_value2(
|
||||
Smi::FromInt(static_cast<int>(FunctionStatus::kMarkForOptimize)));
|
||||
DCHECK(Handle<Tuple2>::cast(entry)->value2().IsSmi());
|
||||
FunctionStatusFlags status(Smi::ToInt(Handle<Tuple2>::cast(entry)->value2()));
|
||||
status = status.without(FunctionStatus::kPrepareForOptimize) |
|
||||
FunctionStatus::kMarkForOptimize;
|
||||
Handle<Tuple2>::cast(entry)->set_value2(Smi::FromInt(status));
|
||||
table = ObjectHashTable::Put(Handle<ObjectHashTable>::cast(table),
|
||||
handle(function->shared(), isolate), entry);
|
||||
isolate->heap()->SetPendingOptimizeForTestBytecode(*table);
|
||||
|
@ -21,7 +21,8 @@ class PendingOptimizationTable {
|
||||
// strongly in pending optimization table preventing the bytecode to be
|
||||
// flushed.
|
||||
static void PreparedForOptimization(Isolate* isolate,
|
||||
Handle<JSFunction> function);
|
||||
Handle<JSFunction> function,
|
||||
bool allow_heuristic_optimization);
|
||||
|
||||
// This function should be called when the function is marked for optimization
|
||||
// via the intrinsics. This will update the state of the bytecode array in the
|
||||
@ -36,6 +37,12 @@ class PendingOptimizationTable {
|
||||
// then this function removes the entry from pending optimization table.
|
||||
static void FunctionWasOptimized(Isolate* isolate,
|
||||
Handle<JSFunction> function);
|
||||
|
||||
// This function returns whether a heuristic is allowed to trigger
|
||||
// optimization the function. This mechanism is used in tests to prevent
|
||||
// heuristics from interfering with manually triggered optimization.
|
||||
static bool IsHeuristicOptimizationAllowed(Isolate* isolate,
|
||||
JSFunction function);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "src/codegen/assembler.h"
|
||||
#include "src/codegen/compilation-cache.h"
|
||||
#include "src/codegen/compiler.h"
|
||||
#include "src/codegen/pending-optimization-table.h"
|
||||
#include "src/execution/execution.h"
|
||||
#include "src/execution/frames-inl.h"
|
||||
#include "src/handles/global-handles.h"
|
||||
@ -119,6 +120,17 @@ void RuntimeProfiler::MaybeOptimize(JSFunction function,
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (FLAG_testing_d8_test_runner) {
|
||||
if (!PendingOptimizationTable::IsHeuristicOptimizationAllowed(isolate_,
|
||||
function)) {
|
||||
if (FLAG_trace_opt_verbose) {
|
||||
PrintF("[function ");
|
||||
function.PrintName();
|
||||
PrintF(" has been marked manually for optimization]\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAG_always_osr) {
|
||||
AttemptOnStackReplacement(frame, AbstractCode::kMaxLoopNestingMarker);
|
||||
|
@ -349,12 +349,24 @@ RUNTIME_FUNCTION(Runtime_EnsureFeedbackVectorForFunction) {
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_PrepareFunctionForOptimization) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
DCHECK(args.length() == 1 || args.length() == 2);
|
||||
if (!args[0].IsJSFunction()) {
|
||||
return ReadOnlyRoots(isolate).undefined_value();
|
||||
}
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
|
||||
|
||||
bool allow_heuristic_optimization = false;
|
||||
if (args.length() == 2) {
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, sync_object, 1);
|
||||
if (!sync_object->IsString())
|
||||
return ReadOnlyRoots(isolate).undefined_value();
|
||||
Handle<String> sync = Handle<String>::cast(sync_object);
|
||||
if (sync->IsOneByteEqualTo(
|
||||
StaticCharVector("allow heuristic optimization"))) {
|
||||
allow_heuristic_optimization = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!EnsureFeedbackVector(function)) {
|
||||
return ReadOnlyRoots(isolate).undefined_value();
|
||||
}
|
||||
@ -375,7 +387,8 @@ RUNTIME_FUNCTION(Runtime_PrepareFunctionForOptimization) {
|
||||
// Hold onto the bytecode array between marking and optimization to ensure
|
||||
// it's not flushed.
|
||||
if (FLAG_testing_d8_test_runner) {
|
||||
PendingOptimizationTable::PreparedForOptimization(isolate, function);
|
||||
PendingOptimizationTable::PreparedForOptimization(
|
||||
isolate, function, allow_heuristic_optimization);
|
||||
}
|
||||
|
||||
return ReadOnlyRoots(isolate).undefined_value();
|
||||
|
@ -502,7 +502,7 @@ namespace internal {
|
||||
F(NotifyContextDisposed, 0, 1) \
|
||||
F(OptimizeFunctionOnNextCall, -1, 1) \
|
||||
F(OptimizeOsr, -1, 1) \
|
||||
F(PrepareFunctionForOptimization, 1, 1) \
|
||||
F(PrepareFunctionForOptimization, -1, 1) \
|
||||
F(PrintWithNameForAssert, 2, 1) \
|
||||
F(RedirectToWasmInterpreter, 2, 1) \
|
||||
F(RunningInSimulator, 0, 1) \
|
||||
|
@ -12,7 +12,7 @@ function f() {
|
||||
return s;
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(f);
|
||||
%PrepareFunctionForOptimization(f, "allow heuristic optimization");
|
||||
f();
|
||||
f();
|
||||
f();
|
||||
|
Loading…
Reference in New Issue
Block a user