Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8336492: Regression in lambda serialization #20349

Closed
wants to merge 20 commits into from

Conversation

mcimadamore
Copy link
Contributor

@mcimadamore mcimadamore commented Jul 26, 2024

This PR consolidates the code for dealing with local captures in both Lower and LambdaToMethod. It does so by adding a new tree scanner, namely CaptureScanner, which is then subclassed by Lower.FreeVarCollector and also by the new LambdaToMethod.LambdaCaptureScanner.

The main idea behind the new visitor is that we can compute the set of (most) captured locals w/o relying on complex state from Lower, and make the logic general enough to work on any tree. This can be done by keeping track of all local variable declarations occurring in a given subtree T. Then, if T contains a reference to a local variable that has not been seen, we can assume that this variable is a captured variable. This simple logic works rather well, and doesn't rely on checking e.g. that the accessed variable has the same owner of the method that owns a local class (which then caused other artifacts such as Lower::ownerToCopyFreeVarsFrom, now removed).

The bigger rewrite is the one in LambdaToMethod. That class featured a custom visitor called LambdaAnalyzerPreprocessor (now removed) which maintains a stack of frames encountered during a visit, and tries to establish which references to local variables needs to be captured by the lambda, as well as whether this needs to be referenced by the lambda. Thanks to the new CaptureScanner visitor, all this logic can be achieved in a very small subclass of CaptureScanner, namely LambdaCaptureScanner.

This removes the need to keep track of frames, and other ancillary data structures. LambdaTranslationContext is now significantly simpler, and its most important field is a Map<VarSymbol, VarSymbol> which maps logical lambda symbols to translated ones (in the generated lambda method). We no longer need to maintain different maps for different kinds of captured symbols.

The code for patching identifiers in a lambda expression also became a lot simpler: we just check if we have pending lambda translation context and, if so, we ask if the identifier symbol is contained in the translation map. Otherwise, we leave the identifier as is.

Fixing the issue referenced by this PR is now very simple: inside LambdaCaptureScanner, we just need to make sure that any identifier referring to a captured local field generated by Lower is re-captured, and the rest just works. To make the code more robust I've added a new variable flag to denote synthetic captured fields generated by Lower.

Compatibility

This PR changes the set of fields that are added to local/anonymous classes. Consider the following case:

class Outer {
    void m(int x) {
        class Local extends Serializable {
            void print() {
                System.out.println(x);
            }
        }
    }
}

This used to generate the following bytecode for Local:

class Outer$1Local {
  final int val$x;
    descriptor: I
  final Outer this$0;
    descriptor: LOuter;
  Outer$1Local();
    descriptor: (LOuter;I)V

  void print();
    descriptor: ()V
}

With this patch, we only generate this:

class Outer$1Local implements java.io.Serializable {
  final int val$x;
    descriptor: I
  Outer$1Local();
    descriptor: (LOuter;I)V

  void print();
    descriptor: ()V
}

That is, the field for this$0 is omitted (as that's not used). This affects the serialized form of this local class, as there's now one less field in the serialized form. While this change is desirable (it brings behavior in sync with lambda expressions, and avoiding capture of enclosing instance might be beneficial for GC reachability), we need to make sure this is ok.

Also, it is possible that the order in which local capture fields appear in classes/constructor parameters of local/anon classes changes slightly, due the fact we're using a different visitor.

Another, minor issue in this rewrite is that now generated methods for non-serializable lambdas can have different names from before. In the old code, a shared counter for all lambdas in a class was used. As the new code shares the same logic for checking name clashing as the one used in serializable lambdas, we now increment the suffix after lambda$foo$ if needed. So if two lambdas are defined in two methods foo and bar, we now get lambda$foo$0 and lambda$foo$bar$0 (before, one of them would have had the index set to 1). While this affects some tests, which name we generate for synthetic lambda methods is a private contract, so I didn't think that preserving 100% compatibility was relevant here.

@cushon perhaps you could help assessing the compatibility impact of this change (as you seem to have a codebase which rely on serialization of local classes/lambdas) ?


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Change requires CSR request JDK-8337558 to be approved
  • Commit message must refer to an issue

Issues

  • JDK-8336492: Regression in lambda serialization (Bug - P3)
  • JDK-8337558: Regression in lambda serialization (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/20349/head:pull/20349
$ git checkout pull/20349

Update a local copy of the PR:
$ git checkout pull/20349
$ git pull https://git.openjdk.org/jdk.git pull/20349/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 20349

View PR using the GUI difftool:
$ git pr show -t 20349

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/20349.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 26, 2024

👋 Welcome back mcimadamore! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jul 26, 2024

@mcimadamore This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8336492: Regression in lambda serialization

Reviewed-by: vromero

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 324 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk
Copy link

openjdk bot commented Jul 26, 2024

@mcimadamore The following label will be automatically applied to this pull request:

  • compiler

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@@ -2337,7 +2243,7 @@ private boolean shouldEmitOuterThis(ClassSymbol sym) {
// Enclosing instance field is used
return true;
}
if (rs.isSerializable(sym.type)) {
if (sym.owner.kind != MTH && rs.isSerializable(sym.type)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, this change serialized form for local/anon classes, by omitting capture of enclosing this where possible. (see discussion on serialization compatibility in the PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: consistency with the lambda case is kind of nice, but if this turns out to be problematic, we can omit this change from this PR, and always capture this$0 in case of a serializable local/anon class.

@mcimadamore mcimadamore marked this pull request as ready for review July 26, 2024 13:26
@mcimadamore
Copy link
Contributor Author

/csr needed

@openjdk openjdk bot added rfr Pull request is ready for review csr Pull request needs approved CSR before integration labels Jul 26, 2024
@openjdk
Copy link

openjdk bot commented Jul 26, 2024

@mcimadamore has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@mcimadamore please create a CSR request for issue JDK-8336492 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@mlbridge
Copy link

mlbridge bot commented Jul 26, 2024

@archiecobbs
Copy link
Contributor

Refactoring Awesomeness Ratio (not counting unit tests) = 1.58. 👍

…tured parameter matches the name of the captured local (skipping any intermediate synthetic local capture in local classes)
@cushon
Copy link
Contributor

cushon commented Jul 26, 2024

This looks exciting!

@cushon perhaps you could help assessing the compatibility impact of this change (as you seem to have a codebase which rely on serialization of local classes/lambdas) ?

I'd be happy to. I have started a round of testing at 76a972b, it'll take about a day to have results.

One caveat is that Google's use of serialization generally doesn't rely on persisting the serialized form, and we had disabled the check for serialization in shouldEmitOuterThis so the enclosing instance could be skipped even for serializable classes. That has been fine for us, but I don't know how well that generalizes. There's some previous discussion about the compatibility impact of omitting this references in JDK-8277965 and linked issues.

Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great simplification, some comments for your consideration

* and to compute the name of the synthetic lambda method (which depends
* on _where_ the lambda capture occurs).
*/
record CaptureSiteInfo(Symbol owner, boolean inStaticInit, VarSymbol pendingVar) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor suggestion, add another constructor with only the first two arguments as there are two constructor invocations where the last argument is null

}

/**
* @return Name of the enclosing method to be folded into synthetic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably the comment needs to also mention that it can return new or static no only the method name

public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
this.make = make;
this.attrEnv = env;
this.context = null;
this.contextMap = new HashMap<>();
cdef = analyzer.analyzeAndPreprocessClass((JCClassDecl) cdef);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@@ -338,22 +401,15 @@ public void visitLambda(JCLambda tree) {
owner::setTypeAttributes,
sym::setTypeAttributes);

apportionTypeAnnotations(tree,
owner::getInitTypeAttributes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure that this is semantically the same as before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure that this is semantically the same as before

Yeah, this is a bit tricky. Basically, the issue with type annotations is that they are encoded in different places depending on the case. E.g. a local variable inside a static/instance initializer has the type annotation recorded in the class symbol (!!). There's also different methods to fetch type annotations, depending on whether it was a normal type annotation, or one in a static/instance initializer.

The general idea is that we try to figure out the symbol that "owns" the type annotation while visiting, and then move all lambda annotations from that owner onto the new lambda method symbol.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be better after this push

// Lambda methods are private synthetic.
// Inherit ACC_STRICT from the enclosing method, or, for clinit,
// from the class.
long flags = SYNTHETIC | LAMBDA_METHOD |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side: I wonder if we could use the same bits for flags: LAMBDA_METHOD and RECORD and safe a bit position for other flags

@@ -1731,17 +1339,15 @@ final class ReferenceTranslationContext extends TranslationContext<JCMemberRefer
enum LambdaSymbolKind {
PARAM, // original to translated lambda parameters
LOCAL_VAR, // original to translated lambda locals
CAPTURED_VAR, // variables in enclosing scope to translated synthetic parameters
CAPTURED_THIS; // class symbols to translated synthetic parameters (for captured member access)
CAPTURED_VAR; // variables in enclosing scope to translated synthetic parameters

boolean propagateAnnotations() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: there is only one use of this method and the method can be rewritten with a ternary operator and probably inlined in its only client

Copy link
Contributor Author

@mcimadamore mcimadamore Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed here

/**
* Simple subclass modelling the translation context of a method reference.
*/
final class ReferenceTranslationContext extends TranslationContext<JCMemberReference> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor, we can probably remove this class it is not adding any behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed here

ret = new VarSymbol((sym.flags() & FINAL) | PARAMETER, sym.name, types.erasure(sym.type), translatedSym);
ret.pos = sym.pos;
// Set ret.data. Same as case LOCAL_VAR above.
if (sym.isExceptionParameter()) {
Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a change from your patch but, do we really need to set this exception parameter thing for a method param? Also this could be a TypeMetadata. Also, there is no test affected if this if with its block is removed so either unnecessary, me thinks, or we need more tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure, but eyeballing the code, I see that sym.isExceptionParameter is used in the backend (e.g. Code) so I'd suspect that a failure in doing this would lead to issues when generating bytecode, which I suspect is why this code was added in the first place. But, while that seems useful for a local variable declared inside a lambda, I don't see how a lambda parameter can itself be an exception parameter...

@@ -289,9 +336,18 @@ public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMake
@Override
public void visitClassDef(JCClassDecl tree) {
KlassInfo prevKlassInfo = kInfo;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

future improvement? we have class info, lambda context, capture site info, should we probably gather them all in an environment like structure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes and no. First, note that capture site info is gone.
As for lambda context and klass info, we use them in different ways: the lambda context is set when we're in a lambda. The klass info is set when we are inside a klass. We create many lambda contexts for each klass info. We could of course move the lambda context field from the visitor to klass info, but I'm not sure how much that buys.

@cushon
Copy link
Contributor

cushon commented Jul 27, 2024

Some initial testing shows a regression in type annotation handling with this change.

Given an example like the following, the type annotation in the lambda is now being incorrectly propagated to the constructor. A RuntimeInvisibleTypeAnnotations attribute for the annotation is emitted on both the synthetic lambda method and the constructor. I noticed this because the length for the RuntimeInvisibleTypeAnnotations is longer than the constructor, and both ASM and javap report an error for the class file.

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

class T {
  @Target(ElementType.TYPE_USE)
  @interface A {}

  Runnable r =
      () -> {
        List<@A Object> xs = new ArrayList<>();
        xs.add(1);
        xs.add(2);
        xs.add(3);
        xs.add(5);
        xs.add(6);
        xs.add(7);
        xs.add(8);
      };

  T() {}
}

javap reports an error because the type annotation's length is longer than the constructor:

$ javap -c T
Compiled from "T.java"
class T {
  java.lang.Runnable r;

  T();
    Code:
Error: error at or after byte 0
Error: Fatal error: Bytecode offset out of range; bci=89, codeLength=14

Using the JDK 11 javap shows

  T();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: invokedynamic #7,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
        10: putfield      #11                 // Field r:Ljava/lang/Runnable;
        13: return
      LineNumberTable:
        line 22: 0
        line 10: 4
        line 22: 13
      RuntimeInvisibleTypeAnnotations:
        0: #35(): LOCAL_VARIABLE, {start_pc=8, length=81, index=0}, location=[TYPE_ARGUMENT(0)]
          T$A

@mcimadamore
Copy link
Contributor Author

Some initial testing shows a regression in type annotation handling with this change.

Thanks, I'll take a look

@mcimadamore
Copy link
Contributor Author

Some initial testing shows a regression in type annotation handling with this change.

Thanks, I'll take a look

Ugh. This is quite ugly. The type annotation is seen, and correctly moved by LambdaToMethod. Unfortunately, the annotation seems to be added in two places: in the local variable, and then in the synthetic constructor of T. We move the former, but not the latter.

@mcimadamore
Copy link
Contributor Author

After trying different approaches, I realized that the cleanest (by far) solution is to simply capture the lambda owner symbol during Attr, instead of trying to reconstruct this owner using various (wrong) heuristics. When doing this, I realized that there were several issues which predated my refactoring - that is, the logic in TypeAnnotations relies on the BLOCK flag to be set on static/instance initializer owner method symbols - but sometimes this flag was missing (this is now fixed). Also, the EnclosingMethodAttribute sometimes reported <clinit> being the method owner of a local class. This is deliberately against the JVMS, which state:

In particular, method_index must be zero if the current class was immediately enclosed in source code by an instance initializer, static initializer, instance variable initializer, or class variable initializer. (The first two concern both local classes and anonymous classes, while the last two concern anonymous classes declared on the right hand side of a field assignment.)

We even had a test which enforced the wrong behavior (!!).

Last, the static/instance initializer symbol used to have a null type. This is now fixed, as we can always rely on the BLOCK flag instead (which is, again, cleaner).

As a result, CaptureSiteInfo is now gone.

Symbol fakeOwner =
new MethodSymbol(tree.flags | BLOCK |
env.info.scope.owner.flags() & STRICTFP, names.empty, null,
env.info.scope.owner.flags() & STRICTFP, names.empty, fakeOwnerType,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the type was left null here, which caused some crashes in LambdaToMethod. There was really no reason for this to be null, except that ClassWriter looks at type being null as a test to see if owner is a static/instance init block. But we can use BLOCK for that (as TypeAnnotations does)

@@ -3572,7 +3573,7 @@ public Env<AttrContext> lambdaEnv(JCLambda that, Env<AttrContext> env) {
if (clinit == null) {
Type clinitType = new MethodType(List.nil(),
syms.voidType, List.nil(), syms.methodClass);
clinit = new MethodSymbol(STATIC | SYNTHETIC | PRIVATE,
clinit = new MethodSymbol(BLOCK | STATIC | SYNTHETIC | PRIVATE,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added missing BLOCK, so that we're now consistent with what the rest of the code (not only LambdaToMethod) expects.

@@ -329,7 +329,7 @@ protected int writeEnclosingMethodAttribute(Name attributeName, ClassSymbol c) {
int alenIdx = writeAttr(attributeName);
ClassSymbol enclClass = c.owner.enclClass();
MethodSymbol enclMethod =
(c.owner.type == null // local to init block
((c.owner.flags() & BLOCK) != 0 // local to init block
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new test, which looks at the BLOCK flag

@openjdk openjdk bot added the rfr Pull request is ready for review label Jul 31, 2024
Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

latest changes look good

@cushon
Copy link
Contributor

cushon commented Aug 1, 2024

I did another round of testing with the latest version of the PR, everything still looks good to me.

  • I am no longer seeing any serialization compatibility impact.
  • I'm still seeing some impact from changes to lambda names. The impact is minor, and all of the occurrences I saw were in tests. I don't have any concerns about proceeding with that part.

@mcimadamore
Copy link
Contributor Author

I did another round of testing with the latest version of the PR, everything still looks good to me.

* I am no longer seeing any serialization compatibility impact.

* I'm still seeing some impact from changes to lambda names. The impact is minor, and all of the occurrences I saw were in tests. I don't have any concerns about proceeding with that part.

Thanks for confirming. I think this is in good shape, and we can now proceed.

Copy link

@ExE-Boss ExE-Boss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using SequencedSet would allow to skip the call to com.sun.tools.javac.util.List​::reverse() on the result of CaptureScanner​::analyzeCaptures().

*/
private final ListBuffer<VarSymbol> fvs = new ListBuffer<>();
private final Set<VarSymbol> fvs = new LinkedHashSet<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private final Set<VarSymbol> fvs = new LinkedHashSet<>();
private final SequencedSet<VarSymbol> fvs = new LinkedHashSet<>();

@@ -96,6 +93,6 @@ public void visitVarDef(JCTree.JCVariableDecl tree) {
*/
List<Symbol.VarSymbol> analyzeCaptures() {
scan(tree);
return fvs.toList();
return List.from(fvs);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return List.from(fvs);
return List.from(fvs.reversed());

@@ -314,7 +314,7 @@ List<VarSymbol> freevars(ClassSymbol c) {
return fvs;
}
FreeVarCollector collector = new FreeVarCollector(classDef(c));
fvs = collector.analyzeCaptures();
fvs = collector.analyzeCaptures().reverse();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fvs = collector.analyzeCaptures().reverse();
fvs = collector.analyzeCaptures();


import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import java.util.Set;
import java.util.Set;
import java.util.SequencedSet;

@openjdk openjdk bot added ready Pull request is ready to be integrated and removed csr Pull request needs approved CSR before integration labels Aug 7, 2024
@mcimadamore
Copy link
Contributor Author

Using SequencedSet would allow to skip the call to com.sun.tools.javac.util.List​::reverse() on the result of CaptureScanner​::analyzeCaptures().

Not sure about that. Note that Lower wants the order of capture to be reversed compared to the "logical" order, but LambdaToMethod does not. To me, it is cleaner to put the reverse call close to the code that has "special" requirements, which in this case happens to be Lower (which is why the code ended up with that shape).

@ExE-Boss
Copy link

Using SequencedSet would allow to skip the call to com.sun.tools.javac.util.List​::reverse() on the result of CaptureScanner​::analyzeCaptures().

Not sure about that. Note that Lower wants the order of capture to be reversed compared to the "logical" order, but LambdaToMethod does not. To me, it is cleaner to put the reverse call close to the code that has "special" requirements, which in this case happens to be Lower (which is why the code ended up with that shape).

Well, at least typing the field as a SequencedSet would signal that the iteration order is important.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Aug 14, 2024
@mcimadamore
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Aug 14, 2024

@mcimadamore This pull request has not yet been marked as ready for integration.

@bridgekeeper
Copy link

bridgekeeper bot commented Sep 11, 2024

@mcimadamore This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@mcimadamore
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Sep 13, 2024

@mcimadamore This pull request has not yet been marked as ready for integration.

Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Sep 13, 2024
@mcimadamore
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Sep 13, 2024

Going to push as commit 8a4ea09.
Since your change was applied there have been 324 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Sep 13, 2024
@openjdk openjdk bot closed this Sep 13, 2024
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Sep 13, 2024
@openjdk
Copy link

openjdk bot commented Sep 13, 2024

@mcimadamore Pushed as commit 8a4ea09.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler [email protected] integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

5 participants