6366f3347b
The GC already treats some embedded object pointers in Code as weak, based on Code::IsWeakObject. If one of those embedded objects ends up unmarked during a full mark-collect GC, then the Code is marked for lazy deoptimization and the embedded objects are cleared. However, many of those same objects are often held strongly by the deoptimization literal array for the Code, which causes memory leaks. This change updates the deoptimization literals array to store those objects weakly. Any Code currently executing on the stack might need those deoptimization literals in order to deoptimize, so the deoptimization literal array is marked strongly in that case. Design document: https://docs.google.com/document/d/1gFRBYCeqz9Mysx8CVYQkldBbk3AZLo8UX0DMLZV_7qw/edit?usp=sharing Bug: v8:4578 Change-Id: I02e86683c59371e9f88ecf523750c9c6afebdb39 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3160299 Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Seth Brenith <seth.brenith@microsoft.com> Cr-Commit-Position: refs/heads/main@{#77805}
96 lines
2.8 KiB
JavaScript
96 lines
2.8 KiB
JavaScript
// Copyright 2021 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: --allow-natives-syntax --expose-gc --opt
|
|
// Flags: --no-stress-opt --no-always-opt --no-assert-types
|
|
|
|
// This weak ref is for checking whether the closure-allocated object o got
|
|
// collected as it should.
|
|
var weak;
|
|
|
|
// Return a function which refers to a variable from its enclosing function.
|
|
function makeFn() {
|
|
var o = {};
|
|
weak = new WeakRef(o);
|
|
o.num = 0;
|
|
return () => { return ++o.num; };
|
|
}
|
|
|
|
// Simple wrapper function which will cause inlining.
|
|
function g(f) {
|
|
f();
|
|
}
|
|
|
|
// Optimize g so that it inlines a specific function instance created by makeFn,
|
|
// and then drop all user-visible references to that function instance.
|
|
(function () {
|
|
var fn = makeFn();
|
|
%PrepareFunctionForOptimization(g);
|
|
%PrepareFunctionForOptimization(fn);
|
|
g(fn);
|
|
%OptimizeFunctionOnNextCall(g);
|
|
g(fn);
|
|
})();
|
|
|
|
// WeakRefs created during the current microtask are strong, so defer the rest.
|
|
setTimeout(() => {
|
|
// Since the function inlined by g no longer exists, it should deopt and
|
|
// release the inner function.
|
|
gc();
|
|
|
|
// Check that the memory leak described in v8:4578 no longer happens.
|
|
assertEquals(undefined, weak.deref());
|
|
|
|
// Next, let's check the opposite case: if an optimized function's Code is
|
|
// currently running at the time of gc, then it must keep its deoptimization
|
|
// data alive.
|
|
|
|
// Another simple wrapper function, but this one causes a GC internally.
|
|
function h(f) {
|
|
gcAndCheckState(); // Non-inlined call
|
|
f(); // Inlined call
|
|
}
|
|
|
|
var doCheck = false;
|
|
function gcAndCheckState() {
|
|
if (!doCheck) return;
|
|
|
|
// It's possible that h has already deoptimized by this point if
|
|
// --stress-incremental-marking caused additional gc cycles.
|
|
var optimized = isOptimized(h);
|
|
|
|
gc();
|
|
|
|
// If the optimized code for h is still on the stack, then it must not clear
|
|
// out its own deoptimization data. Eventually h will deopt due to the wrong
|
|
// function being passed, but only after this function has returned.
|
|
if (optimized) {
|
|
assertNotEquals(undefined, weak.deref());
|
|
} else {
|
|
assertEquals(undefined, weak.deref());
|
|
}
|
|
}
|
|
|
|
// Don't inline gcAndCheckState, to avoid the possibility that its content
|
|
// could cause h to deoptimize.
|
|
%NeverOptimizeFunction(gcAndCheckState);
|
|
|
|
// Optimize h to inline a specific function instance, and then drop all
|
|
// user-visible references to that inlined function.
|
|
(function () {
|
|
var fn = makeFn();
|
|
%PrepareFunctionForOptimization(h);
|
|
%PrepareFunctionForOptimization(fn);
|
|
h(fn);
|
|
%OptimizeFunctionOnNextCall(h);
|
|
h(fn);
|
|
})();
|
|
|
|
// Defer again so the WeakRef can expire.
|
|
setTimeout(() => {
|
|
doCheck = true;
|
|
h(() => {});
|
|
}, 0);
|
|
}, 0);
|