跳到主要内容

JEP 165:编译器控制

QWen Max 中英对照

概述

这个 JEP 提出了一种改进的方式来控制 JVM 编译器。它启用了运行时可管理的、依赖于方法的编译器标志。(在编译期间不可变。)

目标

  • 对 JVM 编译器(C1 和 C2)进行细粒度和方法上下文相关的控制
  • 能够在运行时更改 JVM 编译器的控制选项
  • 无性能下降

动机

方法上下文依赖的编译过程控制是一个强大的工具,可以用来编写小型独立的 JVM 编译器测试,这些测试可以在不重启整个 JVM 的情况下运行。它对于创建 JVM 编译器中 bug 的解决方法也非常有用。对编译器选项的良好封装也是一种良好的实践。

描述

指令

所有控制 JVM 编译器的选项将被汇总到一组选项中。带有值的一组选项被称为编译器指令,它是指如何进行编译的指示。指令会与一个方法匹配器一起提供给虚拟机,该匹配器决定指令适用于哪些方法。在运行时可以同时激活多个指令,但只有一个指令会被应用于特定的编译。指令可以在运行时添加和移除。

指令格式

指令文件具有指定的标准化且人类可读的文件格式。指令文件可以通过命令行和诊断命令加载。一个指令文件包含一个或多个已定义的指令。一条指令包含一个方法模式以及若干带有值的选项。指令的顺序非常重要。compilerBroker 将应用第一个模式与编译匹配的指令。

指令文件格式将是 JSON 的一个子集,并带有一些补充。该格式在以下方面与 JSON 有所不同:

  • 仅支持与命令行选项兼容的数字类型——整数和双精度浮点数。
  • 允许使用注释——以 "//" 开头的行
  • 在数组和对象中允许额外的尾随逗号(,
  • 转义字符可能不被允许(待定)
  • 选项名称为字符串,但可以选择性加引号

该文件可以使用 JVM 规范支持的所有 UTF-8 字符。这些字符是为文件格式保留的:

{ - curly brace open
} - curly brace end
[ - square brace open
] - square brace end
" - quoutes
: - colon
, - comma

指令示例 1

[    // Start if array of directives
{ // Start of directive block
// Excactly one match with one or several patterns
// The array is not required with a single pattern
match: ["java*::*", "oracle*::*"],
// A directives block that only apply to a single compiler
c1: {
// A bool option. Extra trailing comma should not cause a parse error
PrintAssembly:true,
},
// Another compiler block
c2: {
// force inline patters prepended with +, prevent with -
inline: ["+vm*::*","-*::*" ]
},
// Options outside compiler block apply to all compilers
BreakAtExecute: true // enable break in compiled code
BreakAtCompile: true // enable break in compiler
},
{ // start of another directives block
// match ant method whose class end with 'Concurrent'
match: ["*Concurrent::*"],
c2: {
// disable compilation
Exclude:true,
}
// with the c1 directive unspecified the options remains default.
}
]

指令示例 2

[   
{
// pattern to match against class+method+signature
// leading and trailing wildcard (*) allowed
match: "apa.Dingo::*",

// override defaults for specified compiler
// the innermost option has the highest priority
c1: {
//override c1 presets
PrintInlining: false // Example - this option may not exist
}

c2: {
// control inlining of method
// + force inline, - dont inline
inline : [ "+java.util::*", "-com.sun::*"],
}

// directives outside a specific preset applies to all compilers
inline : [ "+java.util::*", "-com.sun::*"],
PrintAssembly: true
},
{
// matching several patterns require an array
match: ["steve::*","alex::*"]

c2: {
Enable: false, // Ignore this directive for c2.
BreakAtExecute: true // This will not be applied since Enable is false above
}

// applies to all compilers
// + force inline, - dont inline
inline : [ "+java.util::*", "-com.sun::*"],
PrintInlining: true
},
]

指令选项列表

第一个实现包含以下选项。所有选项之前都已在 CompileCommand 选项命令中使用过。更多选项将会被添加。

常用标志:

  • Enable, bool
  • Exclude, bool
  • BreakAtExecute, bool
  • BreakAtCompile, bool
  • Log, bool
  • PrintAssembly, bool
  • PrintInlining, bool
  • PrintNMethods, bool
  • ReplayInline, bool
  • DumpReplay, bool
  • DumpInline, bool
  • CompilerDirectivesIgnoreCompileCommands, bool
  • Inline, ccstr[]

仅 C2:BlockLayoutByFrequencybool PrintOptoAssemblybool PrintIntrinsicsbool raceOptoPipeliningbool TraceOptoOutputbool TraceSpillingbool Vectorizebool VectorizeDebugbool CloneMapDebugbool IGVPrintLevelintx MaxNodeLimitintx DisableIntrinsicsccstr

inline:<one pattern or an array of string patterns>
The pattern is a string that matches a method name in the same way as directives are matched.
A '+' prepended to the pattern signifies that methods matching should be force-inlined.
A '-' that it should be prevented from inlining.
The command from the first pattern that matches is used.
Example1: inline:["+java.lang.*::*", -"sun*::*"]
Example2: inline:"+java.lang.*::*"

指令模式

在 "match" 和 "inline" 选项中使用的方法模式具有以下形式:Class.method(signature)

类(Class)包括用 / 分隔的包名。类和方法可以通过前导和尾随的 * 进行通配,或者替换为 *。如果省略签名,则默认为 *

以下是有效的模式:“java.lang.String::indexOf” “java/lang/String.indexOf” “.lang.String::indexOf(I)” “java/lang/String.(I)” “java/lang/String.()” “.” “::” “java.lang.::

指令解析器

指令解析器负责解析指令文件,并将信息添加到虚拟机的内部格式中。

如果在命令行中指定了格式错误的指令文件,虚拟机将打印错误信息并退出。如果通过诊断命令添加了格式错误的指令文件,它将被忽略,并且会打印出适当的警告信息。

解析器将验证所有选项是否有效。依赖于平台的选项将在不支持它们的平台上打印警告信息。这样做的理由是,同一个指令文件应该可以在任何部署的平台上使用。

未指定的选项将使用默认值。如果指定了命令行选项,那将成为默认值。方法模式的默认值是“.”(匹配所有方法)。

编译器代理

编译器代理(compilerBroker)有一个包含所有已应用指令的指令堆栈。底部的指令是默认设置,永远不能被移除。当加载带有额外指令的文件时,这些指令会以相反的顺序添加,文件中的第一条指令最终会位于堆栈的顶部。这是一个可用性特性。

当一个方法被提交编译时,编译器代理(compilerBroker)会选择第一个匹配的指令并将其传递给编译器。编译器代理和编译器会忽略那些可能导致错误代码的选项(例如,在不支持某硬件指令的平台上强制使用该指令),并且会发出适当的警告。指令选项与普通的命令行标志有相同的限制 —— 例如,强制内联只有在中间表示(IR)不会变得过大的情况下才会被尊重。

命令行界面

可以在命令行中添加指令文件。如果标记错误(正常的命令行解析)、文件缺失或文件内容格式不正确,虚拟机将退出并返回错误信息。

-XX:CompilerDirectivesFile=<file>

诊断命令接口

以下是将与编译器控制一起使用的诊断命令:

jcmd <pid> Compiler.add_directives <file>   
Add additional directives from the file. The new directives will be added on top of the old, with the first directive in the file ending up on the top of the directives stack.

jcmd <pid> Compiler.list_directives
List all directives on the directives stack from top to bottom.

jcmd <pid> Compiler.clear_directives
Clear the directives stack

jcmd <pid> Compiler.remove_directives
Remove the top element from the directives stack

CompileCommand 与向后兼容性

CompilerControl 应该在所有使用场景中取代 CompileCommand。CompileCommand 将为了向后兼容而保留,目标是尽可能保持其行为一致。

可以应用四个层次的控制。编译器控制(Compiler Control)将拥有最高优先级,并会覆盖任何其他标志或命令。其次是编译命令(CompileCommand),再次是任何命令行标志,最后是默认标志值。如果同时使用编译器控制和编译命令,编译器控制会认为编译命令正在覆盖默认值。

如果同时使用 CompileCommand 和编译器指令,JVM 应该打印一条警告信息。

方法模式

编译器控制将使用与 CompileCommand 相同的方法模式格式。该模式由三部分组成:包和类名、方法名以及签名。这三部分中的任何一部分都可以通过前导或尾随的 * 进行通配。任何部分的默认值均为 *。

示例:

java/example/Test.split

由三个部分组成

java/example/Test + split + (Ljava/lang/String;)Ljava/lang/String;

风险与假设

编译器选项的数量繁多,这使得我们最初只能专注于其中的一个子集。我们将重点关注一个子集,然后从那里逐步扩展。

依赖

  • 诊断命令 - 已就位
  • 使用完整的 JDK - 已就位

影响

  • 文档:标志和 API
  • CCC:对于指令格式、JVM 编译器标志更改和 API,需要提交 CCC 请求。
  • 性能:标准回归测试