英文:
Where is the description of Constant Folding in the Java Language Specification, Java SE 11 Edition (JLS SE 11)?
问题
至我所知,Java通过编译时的常量折叠来处理常量变量§4.12.4。我已尽力查找,但无法在JLS中找到其描述。是否有人可以告诉我在哪里找到Java 11的官方关于常量折叠过程的描述?
英文:
As far as I know, Java deals with constant variables §4.12.4 by constant folding in compile time. I've tried my best, but I couldn't find its description from JLS. Could anybody tell me where I could find official description of the constant folding process for Java 11?
答案1
得分: 3
以下是翻译好的部分:
- 规范不使用术语“常量折叠”。
- 它对“常量表达式”的定义如下:
- 一个“常量表达式”是一个表示基本类型或
String
值的表达式,不会突然中断,并且仅由以下内容组成: - 常量类型为
String
的表达式始终被“内部化”,以共享唯一的实例,使用方法String.intern
。 - 即使常量表达式出现在非常量表达式不被视为FP-strict的上下文中,也始终将其视为FP-strict(§15.4)。
- 一个“常量表达式”是一个表示基本类型或
- 常量表达式用作
switch
语句(§14.11)中的case
标签,并且在赋值上下文(§5.2)和类或接口的初始化(§12.4.2)中具有特殊意义。它们还可能控制while
、do
或for
语句正常完成的能力(§14.21),以及具有数值操作数的条件运算符? :
的类型。
最后一部分已经指出了常量表达式的预计算在哪些情况下是强制性的。在涉及到case
标签时,编译器必须报告重复值,因此必须在编译时计算这些值。在计算循环时,必须计算常量布尔表达式以确定代码的可达性。
同样,初始化器需要预先计算以确定其正确性。例如,short s = 'a' * 2;
是一个正确的声明,但short s = Short.MAX_VALUE + 1;
则不是。
常量表达式的一个众所周知的用例是常量变量的初始化器。在读取常量变量时,将使用常量值而不是读取变量,与Q&A “JLS是否要求内联最终的String常量?”进行比较。
但这并不意味着“常量折叠”是强制性的。理论上,符合规范的实现仍然可以在变量在每个使用变量的地方按变量初始化器中编写的常量表达式的方式进行计算。在实践中,字节码格式导致了常量折叠行为。字节码中用于记录常量变量的值的ConstantValue
属性只能容纳预先计算的值。当针对已编译的类文件进行编译时,编译器无法访问常量变量的原始表达式。它只能使用预先计算的值。
同样,编译switch
指令通常使用tableswitch
或lookupswitch
指令执行,都需要为case
标签预先计算的int
值。编译器必须采取巨大的措施来实施不同的策略。
此外,注解值的编译格式只能容纳预先计算的表达式。
英文:
The specification does not use the term Constant Folding.
It has the definition of Constant Expressions
> A constant expression is an expression denoting a value of primitive type or a String
that does not complete abruptly and is composed using only the following:
>
> […]
>
> Constant expressions of type String
are always "interned" so as to share unique instances, using the method String.intern
.
>
> A constant expression is always treated as FP-strict (§15.4), even if it occurs in a context where a non-constant expression would not be considered to be FP-strict.
>
> Constant expressions are used as case
labels in switch
statements (§14.11) and have a special significance in assignment contexts (§5.2) and the initialization of a class or interface (§12.4.2). They may also govern the ability of a while
, do
, or for
statement to complete normally (§14.21), and the type of a conditional operator ? :
with numeric operands.
The last part does already point out where precalculation of constant expressions is mandatory. When it comes to case
labels, the compiler is required to report duplicates, hence, it must calculate the values at compile-time. When calculating loops, it must calculate constant boolean expressions to determine code reachability.
Likewise, initializers need precalculation to determine the correctness. E.g. short s = 'a' * 2;
is a correct declaration, but short s = Short.MAX_VALUE + 1;
is not.
A well known use case of constant expressions is the initializer of constant variables. When reading a constant variable, the constant value will be used instead of reading the variable, compare with the Q&A “Does the JLS require inlining of final String constants?”
But this does not imply that “constant folding” is mandatory. In theory, a conforming implementation still could perform the calculation of the constant expression as written in the variable initializer at every place where the variable is used. In practice, the bytecode format leads to a constant folding behavior. The ConstantValue
attribute which is used to record the value of a constant variable in bytecode can only hold a precalculated value. When compiling against an already compiled class file, the original expression of a constant variable is not available to the compiler. It can only use the precalculated value.
Likewise, compiling a switch
instruction is normally done using either, the tableswitch
or the lookupswitch
instruction, both requiring precalculated int
values for the case
labels. A compiler would have to go great length to implement a different strategy.
Also, the compiled format for annotation values can only hold precalculated expressions.
答案2
得分: 2
The Java Language Specification(JLS)定义了语言的语义;常量折叠是编译器优化,不会改变Java程序的行为,因此在JLS中没有指定,也不需要。只要编译后的程序按照JLS的规定执行即可,Java的实现可以选择是否执行常量折叠,或者在某些情况下执行而在其他情况下不执行。
尽管如此,JLS确实以一种允许在更多情况下进行常量折叠而不改变程序行为的方式来定义语言语义。最相关的段落可能是你在§14.2.4中引用的:
常量变量是用常量表达式(§15.28)初始化的原始类型或String类型的final变量。一个变量是否为常量变量可能会涉及到类初始化(§12.4.1)、二进制兼容性(§13.1)、可达性(§14.21)和明确赋值(§16.1.1)等方面的影响。
关于类初始化、二进制兼容性、可达性和明确赋值的引用部分明确定义了常量变量的语义,与其他变量有所不同;具体而言,它们定义的行为与执行常量折叠的编译器所期望的行为相符。这使得在实现规范时可以进行优化而不会过于限制优化的方式。
英文:
The Java Language Specification defines the semantics of the language; constant folding is a compiler optimisation which does not change the behaviour of a Java program, so it is not specified in the JLS, and does not need to be. It is allowed for an implementation of Java not to do it, or to do it in some circumstances but not others, so long as the compiled program does what the JLS says it should.
That said, the JLS does define the language semantics in such a way as to allow constant folding in more cases without changing the behaviour of the program. The most relevant paragraph is what you presumably refer to in §14.2.4:
> A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1), reachability (§14.21), and definite assignment (§16.1.1).
The referenced sections about class initialisation, binary compatibility, reachability and definite assignment specifically define the semantics of constant variables differently to other variables; specifically, the behaviours they define are the behaviours you would expect from a compiler which does fold constants. This allows those implementing the specification to do the optimisation without overly constraining how they do it.
答案3
得分: 2
以下是已翻译的内容:
"字段的处理与问题中链接的页面相关联:https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1
对于常量变量(§4.12.4)的字段引用必须在编译时解析为常量变量初始化器所表示的值 V。
如果该字段是静态的,那么在二进制文件的代码中不应存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须始终表现为已初始化(§12.4.2);字段的默认初始值(如果与 V 不同)永远不应被观察到。
如果该字段是非静态的,那么在二进制文件的代码中除了包含该字段的类之外,不应存在对该字段的引用。(它将是一个类而不是接口,因为接口只能有静态字段。)该类应该在实例创建期间将字段的值设置为 V(§12.5)。"
英文:
The process for fields is linked from the page linked in the question: https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1
> A reference to a field that is a constant variable (§4.12.4) must be
> resolved at compile time to the value V denoted by the constant
> variable's initializer.
>
> If such a field is static, then no reference to the field should be
> present in the code in a binary file, including the class or interface
> which declared the field. Such a field must always appear to have been
> initialized (§12.4.2); the default initial value for the field (if
> different than V) must never be observed.
>
> If such a field is non-static, then no reference to the field should
> be present in the code in a binary file, except in the class
> containing the field. (It will be a class rather than an interface,
> since an interface has only static fields.) The class should have code
> to set the field's value to V during instance creation (§12.5).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论