在 V8 中,什么是惰性反优化,它是如何发生的?

In V8, what is lazy deoptimization, and how does it happen?

根据V8源码和turbofan资料,有一种反优化叫做lazy deoptimization,描述如下(v8/src/common/globals.h):

Lazy: the code has been marked as dependent on some assumption which is checked elsewhere and can trigger deoptimization the next time the code is executed.

然而,当观察'v8/test/mjsunit/compiler/deopt-lazy-shape-mutation.js'与d8的执行时,我发现返回时立即发生去优化来自函数 change_o。我想这是因为执行 change_o 操作 o.

的形状,破坏了对 fo 的映射依赖
> d8/d8 --trace-deopt --allow-natives-syntax test/deopt-lazy-shape-mutation.js
[marking dependent code 0x3d7d00044001 (0x3d7d08293535 <SharedFunctionInfo f>) (opt id 0) for deoptimization, reason: code dependencies]
[bailout (kind: deopt-lazy, reason: (unknown)): begin. deoptimizing 0x3d7d08293779 <JSFunction f (sfi = 0x3d7d08293535)>, opt id 0, node id 20, bytecode offset 4, deopt exit 0, FP to SP delta 32, caller SP 0x7ffdaa56ff68, pc 0x3d7d00044111]

我的问题是:

  1. 惰性反优化到底是什么? 在上面的示例中,是否可以理解 fchange_o 返回后立即取消优化的原因是 change_o 标志着 f 的某些假设具有遭到入侵?

  2. 惰性反优化是如何发生的?eager deoptimization 的情况下,我看到有名为 Deoptimize* 的节点明确表示立即去优化条件,并使用 call 和条件跳转,例如 jnzja 等。但是,我无法弄清楚 lazy deoptimization 如何进入执行流程。是否有一些主管监视 call-ret 操作,并在 callee 损害 caller 的依赖性时触发去优化?

(此处为 V8 开发人员。)

  1. What exactly is lazy deoptimization?

这是一个函数的“预定”反优化,当前在堆栈上有一个或多个激活,但不是当前正在执行的函数(它将拥有最顶层的堆栈帧,并将执行“急切的反优化”如果必须的话)。取消优化意味着必须重写堆栈帧的内容,这对于任何非最顶层堆栈帧来说都非常困难,因此此类函数被标记为取消优化,并且一旦控制 return 对它们进行取消优化(即当它们成为最顶层的堆栈框架时)。

请注意,同一个函数可以急切地(针对其当前正在执行的激活)和惰性地(针对堆栈中更下方的任何其他激活)进行去优化。

In the example above, is it ok to understand the reason why f was deoptimized as soon as it is returned from change_o is that change_o marks that some assumption of f has been compromised?

是的。 change_o 使之前优化 f 时所做的假设无效。 (f 的任何后续优化都不会做出相同的假设。)

  1. How does lazy deoptimization occur?

堆栈上的return地址被重写,因此不是恢复执行原始代码,而是开始反优化序列。如果您想深入了解详细信息,请参阅 deoptimizer.cc 中的 class ActivationsFinder