RFR: 8341782: Allow lambda capture of basic for() loop variables as with enhanced for()

Archie Cobbs acobbs at openjdk.org
Wed Oct 9 15:41:00 UTC 2024


On Wed, 9 Oct 2024 09:13:59 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

>> This PR changes the compiler to allow basic `for()` loop iteration variables to be captured by lambdas in the loop body, even when their "effectively final" status is invalidated by modifications in the header of the loop.
>> 
>> This allows code like this to compile:
>> 
>> for (int i = 1; i <= 3; i++) {
>>     Runnable r = () -> System.out.println(i);
>> }
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java line 2733:
> 
>> 2731:                 try {
>> 2732:                     scan(tree.body);
>> 2733:                     for (int adr = forLoopVarMin; adr < forLoopVarMax; adr++) {
> 
> Question: since we already have a dedicated flag for loop variables - could we set the flag on _all_ loop variables (before the body), and then remove it in `letInit` (same as we do for effectively final flag) ? This way, at the end of the loop, the variables that still have the flag set will be the ones that have not been reassigned, correct?

Yes - that could be a simpler way to do it. It would allow for the elimination of the `reassignedInForLoopBodyPrev` bit set, which is currently serving the same purpose (but in reverse). However, there would be two new concerns:
* We would need a new flag that says "we are currently in the for() loop body" and not, for example, in the step, where we should leave the flag alone.
* We would also need to ensure we don't get confused with nested `for()` loops. So the aforementioned flag can't just be a boolean (because otherwise which `for()` loop are we talking about?). For example, a symbol address range instead.

That would end up looking something like this... WDYT?

index 29ab8435ada..aa908902059 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
@@ -2146,6 +2148,12 @@ public class AssignAnalyzer extends BaseAnalyzer {
          */
         WriteableScope unrefdResources;
 
+        /** In a basic for() loop body, this is the address range of the loop variables.
+         *  If we see a reassignment to any of these variables, we clear the variable's
+         *  FOR_LOOP_BODY_MAY_CAPTURE flag (where "reassigned" means "assigned when not DU").
+         */
+        AddressRange forLoopVarsInBody;
+
         /** Modified when processing a loop body the second time for DU analysis. */
         FlowKind flowKind = FlowKind.NORMAL;
 
@@ -2231,6 +2239,11 @@ void newVar(JCVariableDecl varDecl) {
          */
         void letInit(DiagnosticPosition pos, VarSymbol sym) {
             if (sym.adr >= firstadr && trackable(sym)) {
+
+                // In basic for() loop bodies, variables that are reassigned can no longer be captured
+                if (forLoopVarsInBody != null && forLoopVarsInBody.test(sym.adr) && !uninits.isMember(sym.adr))
+                    sym.flags_field &= ~FOR_LOOP_BODY_MAY_CAPTURE;
+
                 if ((sym.flags() & EFFECTIVELY_FINAL) != 0) {
                     if (!uninits.isMember(sym.adr)) {
                         //assignment targeting an effectively final variable
@@ -2691,6 +2704,7 @@ public void visitForLoop(JCForLoop tree) {
             flowKind = FlowKind.NORMAL;
             int nextadrPrev = nextadr;
             scan(tree.init);
+            final AddressRange loopVars = new AddressRange(nextadrPrev, nextadr);
             final Bits initsSkip = new Bits(true);
             final Bits uninitsSkip = new Bits(true);
             pendingExits = new ListBuffer<>();
@@ -2712,7 +2726,16 @@ public void visitForLoop(JCForLoop tree) {
                     uninitsSkip.assign(uninits);
                     uninitsSkip.inclRange(firstadr, nextadr);
                 }
-                scan(tree.body);
+                final AddressRange forLoopVarsInBodyPrev = forLoopVarsInBody;
+                forLoopVarsInBody = loopVars;
+                try {
+                    loopVars.stream()
+                      .mapToObj(adr -> vardecls[adr].sym)
+                      .forEach(sym -> sym.flags_field |= FOR_LOOP_BODY_MAY_CAPTURE);
+                    scan(tree.body);
+                } finally {
+                    forLoopVarsInBody = forLoopVarsInBodyPrev;
+                }
                 resolveContinues(tree);
                 scan(tree.step);
                 if (log.nerrors != prevErrors ||
@@ -3270,6 +3293,20 @@ public void analyzeTree(Env<?> env, JCTree tree, TreeMaker make) {
         }
     }
 
+    /** A contiguous range of symbol addresses.
+     */
+    record AddressRange(int min, int max) implements IntPredicate {
+
+        @Override
+        public boolean test(int adr) {
+            return adr >= this.min() && adr < this.max();
+        }
+
+        public IntStream stream() {
+            return IntStream.range(this.min(), this.max());
+        }
+    }
+
     /**
      * This pass implements the last step of the dataflow analysis, namely
      * the effectively-final analysis check. This checks that every local variable

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/21415#discussion_r1793750314


More information about the compiler-dev mailing list