RFR: 8294982: Implementation of Classfile API [v20]
Maurizio Cimadamore
mcimadamore at openjdk.org
Thu Feb 16 15:03:58 UTC 2023
On Thu, 16 Feb 2023 10:41:33 GMT, Adam Sotona <asotona at openjdk.org> wrote:
>> This is root pull request with Classfile API implementation, tests and benchmarks initial drop into JDK.
>>
>> Following pull requests consolidating JDK class files parsing, generating, and transforming ([JDK-8294957](https://bugs.openjdk.org/browse/JDK-8294957)) will chain to this one.
>>
>> Classfile API development is tracked at:
>> https://github.com/openjdk/jdk-sandbox/tree/classfile-api-branch
>>
>> Development branch of consolidated JDK class files parsing, generating, and transforming is at:
>> https://github.com/openjdk/jdk-sandbox/tree/classfile-api-dev-branch
>>
>> Classfile API [JEP](https://bugs.openjdk.org/browse/JDK-8280389) and [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openjdk/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/java.base/jdk/internal/classfile/package-summary.html) is also available.
>>
>> Please take you time to review this non-trivial JDK addition.
>>
>> Thank you,
>> Adam
>
> Adam Sotona has updated the pull request incrementally with one additional commit since the last revision:
>
> added 4-byte Unicode text to Utf8EntryTest
src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPoolBuilder.java line 75:
> 73: static ConstantPoolBuilder of(ClassModel classModel) {
> 74: ClassReader reader = (ClassReader) classModel.constantPool();
> 75: return reader.optionValue(Classfile.Option.Key.CP_SHARING)
Question: the fact that, when transforming, the new constant pool is the same as the old one, plus some "appended" entries at the end, is a pretty crucial property to ensure that existing code that is not transformed (e.g. unknown attributes) remains legal after transformation. But it seems that, if the `CP_SHARING` option is not passed, we build a brand new constant pool, in which case there's no guarantee that old indices will still point to the same position. Doesn't that lead to issue with unknown attributes?
I guess my question is: shouldn't the semantics properties/guarantees of a classfile transform be specified regardless of the options being passed in? E.g. it's ok if using different options ends up with different performance model, but I'm a bit worried if options can affect correctness of the transform?
src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java line 38:
> 36: private final List<Attribute<?>> attributes = new ArrayList<>();
> 37:
> 38: public AttributeHolder() {
default constructor
src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java line 54:
> 52: */
> 53: public class BytecodeHelpers {
> 54: // public static Map<ConstantDesc, Opcode> constantsToOpcodes = new HashMap<>(16);
Should this be removed?
src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java line 56:
> 54: // public static Map<ConstantDesc, Opcode> constantsToOpcodes = new HashMap<>(16);
> 55:
> 56: public BytecodeHelpers() {
Should this also be removed (same as default constructor) ?
src/java.base/share/classes/jdk/internal/classfile/impl/ChainedFieldBuilder.java line 48:
> 46: this.consumer = consumer;
> 47: FieldBuilder b = downstream;
> 48: while (b instanceof ChainedFieldBuilder cb)
I'm probably missing something - but if `b` is another chained builder, instead of using a loop, can't we just skip to its `terminal` field? Also, the `downstream` field seems unused. (same considerations apply for `ChainedMethodBuilder` and `ChainedClassBuilder`).
I note that `NonTerminalCodeBuilder` seems to be already doing what I suggest here (e.g. skip to `terminal`).
src/java.base/share/classes/jdk/internal/classfile/impl/ConcreteEntry.java line 58:
> 56: import jdk.internal.classfile.jdktypes.PackageDesc;
> 57:
> 58: public abstract sealed class ConcreteEntry {
Why the name `concrete` ? Am I missing something (e.g. existence of "non-concrete" pool entries?)
src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java line 649:
> 647: if (parentMap == null)
> 648: parentMap = new IdentityHashMap<>();
> 649: int[] table = parentMap.computeIfAbsent(parent, new Function<CodeAttribute, int[]>() {
Can use a lambda here?
src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java line 658:
> 656: mruParent = parent;
> 657: mruParentTable = table;
> 658: return mruParentTable[lab.getContextInfo()] - 1;
Am I correct that this code can misbehave e.g. if `computeIfAbsent` ends up creating a brand new `table` array - in which case, all array elements are set to `0` - meaning we end up returning `-1`. Is that what we want?
src/java.base/share/classes/jdk/internal/classfile/impl/InstructionData.java line 47:
> 45: * InstructionData
> 46: */
> 47: public class InstructionData {
As CodeImpl seems to be the only client of this, I wonder if we could move the static cache in there?
src/java.base/share/classes/jdk/internal/classfile/impl/LabelImpl.java line 65:
> 63:
> 64: public int getContextInfo() {
> 65: return contextInfo;
This seems to be the BCI - should we change names to reflect that? (`contextInfo` seems very vague)
src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java line 51:
> 49: * TransformImpl
> 50: */
> 51: public class TransformImpl {
Understanding check: a transform implementation is made up of two runnable (start/end handlers) and a consumer. The consumer can be used e.g. to iterate and transform all the contents of a compound element (e.g. all the instruction in a code attribute).
User-defined transforms can be "resolved" that is, we can "bind" them to a specific builder, which effectively turns them into consumers (and into a concrete transform implementation that can be used).
When two transforms T1 and T2 are composed (so that T1 runs before T2), we need to implement the `resolve` method of the resulting transform so that:
* We resolve T2 against the provided builder - this gives us a "downstream" consumer;
* We create a new "chained" builder which wraps the provided builder, and whose `with` method calls the downstream consumer;
* We then resolve T1 against the chained builder, and return the resulting consumer
This means that when we call `accept` on the resulting consumer, we will transform (using T1) and call `with` (passing the transformed element) on the chained builder, which will end up calling `accept` on the downstream consumer, which will apply T2 and finally add the resulting element to the provided builder.
Correct? (Phew)
-------------
PR: https://git.openjdk.org/jdk/pull/10982
More information about the build-dev
mailing list