[Compile] Avoid flushing code that's marked for optimization in tests.
Bytecode flushing can make tests using assertOptimized flaky if the bytecode is flushed between marking and optimization. It can also be flaky if the feedback vector is collected before optimization. To prevent this, a new %PrepareForOptimization runtime-test function is added that hold onto the bytecode strongly until it is optimized after being explicitly marked for optimization by %OptimizeFunctionOnNextCall. BUG=v8:8801,v8:8395 Change-Id: Idbd962a3a2044b915903f9c5e92d1789942b5b41 Reviewed-on: https://chromium-review.googlesource.com/c/1463525 Commit-Queue: Ross McIlroy <rmcilroy@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Cr-Commit-Position: refs/heads/master@{#59914}
This commit is contained in:
parent
087727d1fc
commit
2cb8a6e349
@ -22,7 +22,7 @@
|
|||||||
#include "src/debug/liveedit.h"
|
#include "src/debug/liveedit.h"
|
||||||
#include "src/frames-inl.h"
|
#include "src/frames-inl.h"
|
||||||
#include "src/globals.h"
|
#include "src/globals.h"
|
||||||
#include "src/heap/heap.h"
|
#include "src/heap/heap-inl.h"
|
||||||
#include "src/interpreter/interpreter.h"
|
#include "src/interpreter/interpreter.h"
|
||||||
#include "src/isolate-inl.h"
|
#include "src/isolate-inl.h"
|
||||||
#include "src/log-inl.h"
|
#include "src/log-inl.h"
|
||||||
@ -758,6 +758,15 @@ MaybeHandle<Code> GetOptimizedCode(Handle<JSFunction> function,
|
|||||||
return MaybeHandle<Code>();
|
return MaybeHandle<Code>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If code was pending optimization for testing, delete remove the strong root
|
||||||
|
// that was preventing the bytecode from being flushed between marking and
|
||||||
|
// optimization.
|
||||||
|
if (isolate->heap()->pending_optimize_for_test_bytecode() ==
|
||||||
|
shared->GetBytecodeArray()) {
|
||||||
|
isolate->heap()->SetPendingOptimizeForTestBytecode(
|
||||||
|
ReadOnlyRoots(isolate).undefined_value());
|
||||||
|
}
|
||||||
|
|
||||||
Handle<Code> cached_code;
|
Handle<Code> cached_code;
|
||||||
if (GetCodeFromOptimizedCodeCache(function, osr_offset)
|
if (GetCodeFromOptimizedCodeCache(function, osr_offset)
|
||||||
.ToHandle(&cached_code)) {
|
.ToHandle(&cached_code)) {
|
||||||
|
@ -119,6 +119,11 @@ void Heap::SetMessageListeners(TemplateList value) {
|
|||||||
roots_table()[RootIndex::kMessageListeners] = value->ptr();
|
roots_table()[RootIndex::kMessageListeners] = value->ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Heap::SetPendingOptimizeForTestBytecode(Object bytecode) {
|
||||||
|
DCHECK(bytecode->IsBytecodeArray() || bytecode->IsUndefined(isolate()));
|
||||||
|
roots_table()[RootIndex::kPendingOptimizeForTestBytecode] = bytecode->ptr();
|
||||||
|
}
|
||||||
|
|
||||||
PagedSpace* Heap::paged_space(int idx) {
|
PagedSpace* Heap::paged_space(int idx) {
|
||||||
DCHECK_NE(idx, LO_SPACE);
|
DCHECK_NE(idx, LO_SPACE);
|
||||||
DCHECK_NE(idx, NEW_SPACE);
|
DCHECK_NE(idx, NEW_SPACE);
|
||||||
|
@ -660,6 +660,7 @@ class Heap {
|
|||||||
V8_INLINE void SetRootStringTable(StringTable value);
|
V8_INLINE void SetRootStringTable(StringTable value);
|
||||||
V8_INLINE void SetRootNoScriptSharedFunctionInfos(Object value);
|
V8_INLINE void SetRootNoScriptSharedFunctionInfos(Object value);
|
||||||
V8_INLINE void SetMessageListeners(TemplateList value);
|
V8_INLINE void SetMessageListeners(TemplateList value);
|
||||||
|
V8_INLINE void SetPendingOptimizeForTestBytecode(Object bytecode);
|
||||||
|
|
||||||
// Set the stack limit in the roots table. Some architectures generate
|
// Set the stack limit in the roots table. Some architectures generate
|
||||||
// code that looks here, because it is faster than loading from the static
|
// code that looks here, because it is faster than loading from the static
|
||||||
|
@ -792,6 +792,7 @@ void Heap::CreateInitialObjects() {
|
|||||||
set_retaining_path_targets(roots.empty_weak_array_list());
|
set_retaining_path_targets(roots.empty_weak_array_list());
|
||||||
|
|
||||||
set_feedback_vectors_for_profiling_tools(roots.undefined_value());
|
set_feedback_vectors_for_profiling_tools(roots.undefined_value());
|
||||||
|
set_pending_optimize_for_test_bytecode(roots.undefined_value());
|
||||||
|
|
||||||
set_script_list(roots.empty_weak_array_list());
|
set_script_list(roots.empty_weak_array_list());
|
||||||
|
|
||||||
|
@ -290,7 +290,8 @@ class RootVisitor;
|
|||||||
/* KeepDuringJob set for JS WeakRefs */ \
|
/* KeepDuringJob set for JS WeakRefs */ \
|
||||||
V(HeapObject, weak_refs_keep_during_job, WeakRefsKeepDuringJob) \
|
V(HeapObject, weak_refs_keep_during_job, WeakRefsKeepDuringJob) \
|
||||||
V(HeapObject, interpreter_entry_trampoline_for_profiling, \
|
V(HeapObject, interpreter_entry_trampoline_for_profiling, \
|
||||||
InterpreterEntryTrampolineForProfiling)
|
InterpreterEntryTrampolineForProfiling) \
|
||||||
|
V(Object, pending_optimize_for_test_bytecode, PendingOptimizeForTestBytecode)
|
||||||
|
|
||||||
// Entries in this list are limited to Smis and are not visited during GC.
|
// Entries in this list are limited to Smis and are not visited during GC.
|
||||||
#define SMI_ROOT_LIST(V) \
|
#define SMI_ROOT_LIST(V) \
|
||||||
|
@ -296,6 +296,60 @@ RUNTIME_FUNCTION(Runtime_OptimizeFunctionOnNextCall) {
|
|||||||
return ReadOnlyRoots(isolate).undefined_value();
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RUNTIME_FUNCTION(Runtime_PrepareFunctionForOptimization) {
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
DCHECK_EQ(1, args.length());
|
||||||
|
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
|
||||||
|
|
||||||
|
// Only one function should be prepared for optimization at a time
|
||||||
|
CHECK(isolate->heap()->pending_optimize_for_test_bytecode()->IsUndefined());
|
||||||
|
|
||||||
|
// Check function allows lazy compilation.
|
||||||
|
if (!function->shared()->allows_lazy_compilation()) {
|
||||||
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If function isn't compiled, compile it now.
|
||||||
|
IsCompiledScope is_compiled_scope(function->shared()->is_compiled_scope());
|
||||||
|
if (!is_compiled_scope.is_compiled() &&
|
||||||
|
!Compiler::Compile(function, Compiler::CLEAR_EXCEPTION,
|
||||||
|
&is_compiled_scope)) {
|
||||||
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure function has a feedback vector to hold type feedback for
|
||||||
|
// optimization.
|
||||||
|
JSFunction::EnsureFeedbackVector(function);
|
||||||
|
|
||||||
|
// If optimization is disabled for the function, return without making it
|
||||||
|
// pending optimize for test.
|
||||||
|
if (function->shared()->optimization_disabled() &&
|
||||||
|
function->shared()->disable_optimization_reason() ==
|
||||||
|
BailoutReason::kNeverOptimize) {
|
||||||
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the function is already optimized, return without making it pending
|
||||||
|
// optimize for test.
|
||||||
|
if (function->IsOptimized() || function->shared()->HasAsmWasmData()) {
|
||||||
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the function has optimized code, ensure that we check for it and then
|
||||||
|
// return without making it pending optimize for test.
|
||||||
|
if (function->HasOptimizedCode()) {
|
||||||
|
DCHECK(function->ChecksOptimizationMarker());
|
||||||
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold onto the bytecode array between marking and optimization to ensure
|
||||||
|
// it's not flushed.
|
||||||
|
isolate->heap()->SetPendingOptimizeForTestBytecode(
|
||||||
|
function->shared()->GetBytecodeArray());
|
||||||
|
|
||||||
|
return ReadOnlyRoots(isolate).undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
RUNTIME_FUNCTION(Runtime_OptimizeOsr) {
|
RUNTIME_FUNCTION(Runtime_OptimizeOsr) {
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
DCHECK(args.length() == 0 || args.length() == 1);
|
DCHECK(args.length() == 0 || args.length() == 1);
|
||||||
|
@ -498,6 +498,7 @@ namespace internal {
|
|||||||
F(NotifyContextDisposed, 0, 1) \
|
F(NotifyContextDisposed, 0, 1) \
|
||||||
F(OptimizeFunctionOnNextCall, -1, 1) \
|
F(OptimizeFunctionOnNextCall, -1, 1) \
|
||||||
F(OptimizeOsr, -1, 1) \
|
F(OptimizeOsr, -1, 1) \
|
||||||
|
F(PrepareFunctionForOptimization, 1, 1) \
|
||||||
F(PrintWithNameForAssert, 2, 1) \
|
F(PrintWithNameForAssert, 2, 1) \
|
||||||
F(RedirectToWasmInterpreter, 2, 1) \
|
F(RedirectToWasmInterpreter, 2, 1) \
|
||||||
F(RunningInSimulator, 0, 1) \
|
F(RunningInSimulator, 0, 1) \
|
||||||
|
@ -14,6 +14,7 @@ my_array_proto.__proto__ = [].__proto__;
|
|||||||
function push_wrapper_2(array, value) {
|
function push_wrapper_2(array, value) {
|
||||||
array.push(value);
|
array.push(value);
|
||||||
}
|
}
|
||||||
|
%PrepareFunctionForOptimization(push_wrapper_2);
|
||||||
array = [];
|
array = [];
|
||||||
array.__proto__ = my_array_proto;
|
array.__proto__ = my_array_proto;
|
||||||
push_wrapper_2(array, 66);
|
push_wrapper_2(array, 66);
|
||||||
|
@ -7,10 +7,12 @@
|
|||||||
(function test() {
|
(function test() {
|
||||||
function foo(a) { a.push(a.length = 2); }
|
function foo(a) { a.push(a.length = 2); }
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
@ -19,10 +21,12 @@
|
|||||||
(function testElementTypeCheckSmi() {
|
(function testElementTypeCheckSmi() {
|
||||||
function foo(a) { a.push('a'); }
|
function foo(a) { a.push('a'); }
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([1]);
|
foo([1]);
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
@ -31,10 +35,12 @@
|
|||||||
(function testElementTypeCheckDouble() {
|
(function testElementTypeCheckDouble() {
|
||||||
function foo(a) { a.push('a'); }
|
function foo(a) { a.push('a'); }
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo([0.3413312]);
|
foo([0.3413312]);
|
||||||
foo([0.3413312]);
|
foo([0.3413312]);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([0.3413312]);
|
foo([0.3413312]);
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([0.3413312]);
|
foo([0.3413312]);
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
@ -44,10 +50,12 @@
|
|||||||
%NeverOptimizeFunction(bar);
|
%NeverOptimizeFunction(bar);
|
||||||
function foo(a) { a.push(bar(a)); }
|
function foo(a) { a.push(bar(a)); }
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo(["1"]);
|
foo(["1"]);
|
||||||
foo(["1"]);
|
foo(["1"]);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo(["1"]);
|
foo(["1"]);
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo(["1"]);
|
foo(["1"]);
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
@ -56,10 +64,12 @@
|
|||||||
(function test() {
|
(function test() {
|
||||||
function foo(a) { a.push(a.length = 2); }
|
function foo(a) { a.push(a.length = 2); }
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo([0.34234]);
|
foo([0.34234]);
|
||||||
foo([0.34234]);
|
foo([0.34234]);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([0.34234]);
|
foo([0.34234]);
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo([0.34234]);
|
foo([0.34234]);
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
@ -70,10 +80,12 @@
|
|||||||
|
|
||||||
function foo(a) { a.push(1); }
|
function foo(a) { a.push(1); }
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo(new Array(N));
|
foo(new Array(N));
|
||||||
foo(new Array(N));
|
foo(new Array(N));
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo(new Array(N));
|
foo(new Array(N));
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo(new Array(N));
|
foo(new Array(N));
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
@ -90,11 +102,18 @@
|
|||||||
}
|
}
|
||||||
function foo(a) { a.push(0.23441233123); }
|
function foo(a) { a.push(0.23441233123); }
|
||||||
|
|
||||||
|
|
||||||
// 1. Optimize foo to handle fast mode arrays.
|
// 1. Optimize foo to handle fast mode arrays.
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo(mkArray(kFastModeLength));
|
foo(mkArray(kFastModeLength));
|
||||||
foo(mkArray(kFastModeLength));
|
foo(mkArray(kFastModeLength));
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
foo(mkArray(kFastModeLength));
|
foo(mkArray(kFastModeLength));
|
||||||
|
assertOptimized(foo);
|
||||||
|
|
||||||
|
// Prepare foo to be re-optimized, ensuring it's bytecode / feedback vector
|
||||||
|
// doesn't get flushed after deoptimization.
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
|
|
||||||
// 2. Given a slow mode array, foo will deopt.
|
// 2. Given a slow mode array, foo will deopt.
|
||||||
foo(mkArray(kSlowModeLength));
|
foo(mkArray(kSlowModeLength));
|
||||||
|
15
test/mjsunit/compiler/dont-flush-code-marked-for-opt.js
Normal file
15
test/mjsunit/compiler/dont-flush-code-marked-for-opt.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Flags: --opt --allow-natives-syntax --expose-gc --stress-flush-bytecode
|
||||||
|
|
||||||
|
function foo(a) {}
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
|
foo();
|
||||||
|
foo();
|
||||||
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
gc();
|
||||||
|
foo();
|
||||||
|
assertOptimized(foo);
|
@ -10,6 +10,7 @@
|
|||||||
return String.fromCodePoint(x);
|
return String.fromCodePoint(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
assertEquals("\u0000", foo(0));
|
assertEquals("\u0000", foo(0));
|
||||||
assertEquals("\u0000", foo(-0));
|
assertEquals("\u0000", foo(-0));
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
@ -17,6 +18,10 @@
|
|||||||
assertEquals("\u0000", foo(-0));
|
assertEquals("\u0000", foo(-0));
|
||||||
assertOptimized(foo);
|
assertOptimized(foo);
|
||||||
|
|
||||||
|
// Prepare foo to be re-optimized, ensuring it's bytecode / feedback vector
|
||||||
|
// doesn't get flushed after deoptimization.
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
|
|
||||||
// Now passing anything outside the valid code point
|
// Now passing anything outside the valid code point
|
||||||
// range should invalidate the optimized code.
|
// range should invalidate the optimized code.
|
||||||
assertThrows(_ => foo(-1));
|
assertThrows(_ => foo(-1));
|
||||||
|
@ -232,9 +232,6 @@
|
|||||||
|
|
||||||
# BUG(v8:8169)
|
# BUG(v8:8169)
|
||||||
'external-backing-store-gc': [SKIP],
|
'external-backing-store-gc': [SKIP],
|
||||||
|
|
||||||
# https://crbug.com/v8/8781
|
|
||||||
'compiler/string-from-code-point': [PASS, FAIL],
|
|
||||||
}], # ALWAYS
|
}], # ALWAYS
|
||||||
|
|
||||||
['novfp3 == True', {
|
['novfp3 == True', {
|
||||||
@ -306,11 +303,6 @@
|
|||||||
# BUG(v8:4237)
|
# BUG(v8:4237)
|
||||||
'regress/regress-3976': [SKIP],
|
'regress/regress-3976': [SKIP],
|
||||||
|
|
||||||
# BUG(v8:8801): Bytecode flushing interacts poorly with assertOptimized
|
|
||||||
'regress/regress-7254': [SKIP],
|
|
||||||
'array-push5': [SKIP],
|
|
||||||
'compiler/deopt-array-push': [SKIP],
|
|
||||||
|
|
||||||
# Slow tests.
|
# Slow tests.
|
||||||
'array-constructor': [PASS, SLOW],
|
'array-constructor': [PASS, SLOW],
|
||||||
'json': [PASS, SLOW],
|
'json': [PASS, SLOW],
|
||||||
@ -421,6 +413,7 @@
|
|||||||
'compiler/deopt-inlined-from-call': [SKIP],
|
'compiler/deopt-inlined-from-call': [SKIP],
|
||||||
'compiler/deopt-numberoroddball-binop': [SKIP],
|
'compiler/deopt-numberoroddball-binop': [SKIP],
|
||||||
'compiler/deopt-string-outofbounds': [SKIP],
|
'compiler/deopt-string-outofbounds': [SKIP],
|
||||||
|
'compiler/dont-flush-code-marked-for-opt': [SKIP],
|
||||||
'compiler/increment-typefeedback': [SKIP],
|
'compiler/increment-typefeedback': [SKIP],
|
||||||
'compiler/inlined-array-pop-opt': [SKIP],
|
'compiler/inlined-array-pop-opt': [SKIP],
|
||||||
'compiler/inlined-call': [SKIP],
|
'compiler/inlined-call': [SKIP],
|
||||||
|
@ -9,6 +9,7 @@ function foo(a) {
|
|||||||
a[1] = "";
|
a[1] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%PrepareFunctionForOptimization(foo);
|
||||||
foo([0,0].map(x => x));
|
foo([0,0].map(x => x));
|
||||||
foo([0,0].map(x => x));
|
foo([0,0].map(x => x));
|
||||||
%OptimizeFunctionOnNextCall(foo);
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
Loading…
Reference in New Issue
Block a user