[ 
https://issues.apache.org/jira/browse/GROOVY-10657?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18140818#comment-18140818
 ] 

ASF GitHub Bot commented on GROOVY-10657:
-----------------------------------------

blackdrag commented on code in PR #3226:
URL: https://github.com/apache/groovy/pull/3226#discussion_r4506990204


##########
src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionMultiTypeDispatcher.java:
##########
@@ -433,21 +440,57 @@ protected void assignToArray(final Expression orig, final 
Expression receiver, f
 
             // load rhs
             rhsValueLoader.visit(asmGenerator);
+            // The store consumes the value. Duplicate it under the array 
reference
+            // and the int index first, so the assignment expression can yield 
the
+            // original value without a local variable. The copy is taken 
before the
+            // component cast, which applies to the consumed value only.
+            if 
(!Boolean.TRUE.equals(orig.getNodeMetaData(AsmClassGenerator.ELIDE_EXPRESSION_VALUE)))
 {
+                duplicateTopBeneath(operandStack, 2);
+            }
             operandStack.doGroovyCast(arrayType.getComponentType());
 
             // store value in array
             bew.arraySet(false);
 
-            // update operand stack
+            // update operand stack; a duplicated value stays as the 
expression result
             operandStack.remove(3);
-
-            if 
(!Boolean.TRUE.equals(orig.getNodeMetaData(AsmClassGenerator.ELIDE_EXPRESSION_VALUE)))
-                rhsValueLoader.visit(asmGenerator); // re-load result value
         } else {
             super.assignToArray(orig, receiver, index, rhsValueLoader, safe);
         }
     }
 
+    /**
+     * Duplicates the top operand beneath {@code operandsBelow} tracked 
operands.
+     * An array store has an array reference and an int index below the value,
+     * two words in total; the value is one word, or two for long and double.
+     */
+    private void duplicateTopBeneath(final OperandStack operandStack, final 
int operandsBelow) {
+        ClassNode top = operandStack.getTopOperand();
+        operandStack.remove(1);
+        ClassNode[] below = new ClassNode[operandsBelow];
+        int wordsBelow = 0;
+        for (int i = operandsBelow - 1; i >= 0; i -= 1) {
+            below[i] = operandStack.getTopOperand();
+            wordsBelow += isTwoWord(below[i]) ? 2 : 1;
+            operandStack.remove(1);
+        }
+        int wordsTop = isTwoWord(top) ? 2 : 1;
+        int opcode;
+        if (wordsTop == 1 && wordsBelow == 1) opcode = DUP_X1;
+        else if (wordsTop == 1 && wordsBelow == 2) opcode = DUP_X2;
+        else if (wordsTop == 2 && wordsBelow == 1) opcode = DUP2_X1;
+        else if (wordsTop == 2 && wordsBelow == 2) opcode = DUP2_X2;

Review Comment:
   this should be in OperandStack (see dup and swap)



##########
src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompilationTest.groovy:
##########
@@ -261,21 +262,73 @@ final class StaticCompilationTest extends 
AbstractBytecodeTestCase {
                 'L0',
                 'LINENUMBER 3',
                 'ALOAD 1',
-                'ASTORE 2',
                 'ICONST_0',
-                'ISTORE 3',
                 'ICONST_1',
-                'ISTORE 4',
-                'ALOAD 2',
-                'ILOAD 3',
-                'ILOAD 4',
                 'INVOKESTATIC 
org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V',
                 'L1',
                 'LINENUMBER 4',
                 'RETURN'
             ])
     }
 
+    // GROOVY-10657: a reference right-hand side must not be kept in a local
+    @Test
+    void testSubscriptAssignmentReference() {
+        assert compile(method: 'm', '''@groovy.transform.CompileStatic
+            void m(Object[] a, Object v) {
+                a[0] = v
+            }
+        ''').hasStrictSequence([
+                'L0',
+                'LINENUMBER 3',
+                'ALOAD 1',
+                'ICONST_0',
+                'ALOAD 2',
+                'INVOKESTATIC 
org/codehaus/groovy/runtime/BytecodeInterface8.objectArraySet 
([Ljava/lang/Object;ILjava/lang/Object;)V',
+                'L1',
+                'LINENUMBER 4',
+                'RETURN'
+            ])
+    }
+
+    // GROOVY-10657: the assignment value is the right-hand side, duplicated 
under the store operands
+    @Test
+    void testSubscriptAssignmentValue() {
+        assert compile(method: 'm', '''@groovy.transform.CompileStatic
+            Object m(Object[] a, Object v) {
+                return (a[0] = v)
+            }
+        ''').hasStrictSequence([
+                'L0',
+                'LINENUMBER 3',
+                'ALOAD 1',
+                'ICONST_0',
+                'ALOAD 2',
+                'DUP_X2',
+                'INVOKESTATIC 
org/codehaus/groovy/runtime/BytecodeInterface8.objectArraySet 
([Ljava/lang/Object;ILjava/lang/Object;)V',
+                'ARETURN'
+            ])
+    }
+
+    // GROOVY-10657: a category-2 value uses DUP2_X2 so the copy keeps both 
words
+    @Test
+    void testSubscriptAssignmentLongValue() {

Review Comment:
   actually most of these test should be not only testing static compilation, 
but also have that kind of sequence without static compilation. 



##########
src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionMultiTypeDispatcher.java:
##########
@@ -433,21 +440,57 @@ protected void assignToArray(final Expression orig, final 
Expression receiver, f
 
             // load rhs
             rhsValueLoader.visit(asmGenerator);
+            // The store consumes the value. Duplicate it under the array 
reference
+            // and the int index first, so the assignment expression can yield 
the
+            // original value without a local variable. The copy is taken 
before the
+            // component cast, which applies to the consumed value only.
+            if 
(!Boolean.TRUE.equals(orig.getNodeMetaData(AsmClassGenerator.ELIDE_EXPRESSION_VALUE)))
 {
+                duplicateTopBeneath(operandStack, 2);
+            }
             operandStack.doGroovyCast(arrayType.getComponentType());
 
             // store value in array
             bew.arraySet(false);
 
-            // update operand stack
+            // update operand stack; a duplicated value stays as the 
expression result
             operandStack.remove(3);
-
-            if 
(!Boolean.TRUE.equals(orig.getNodeMetaData(AsmClassGenerator.ELIDE_EXPRESSION_VALUE)))
-                rhsValueLoader.visit(asmGenerator); // re-load result value
         } else {
             super.assignToArray(orig, receiver, index, rhsValueLoader, safe);
         }
     }
 
+    /**
+     * Duplicates the top operand beneath {@code operandsBelow} tracked 
operands.
+     * An array store has an array reference and an int index below the value,
+     * two words in total; the value is one word, or two for long and double.
+     */
+    private void duplicateTopBeneath(final OperandStack operandStack, final 
int operandsBelow) {

Review Comment:
   The only value for operandsBelow seems to be 2, why do we need this to work 
for another case?





> Generated code for overloaded assignment operator prevents garbage collection
> -----------------------------------------------------------------------------
>
>                 Key: GROOVY-10657
>                 URL: https://issues.apache.org/jira/browse/GROOVY-10657
>             Project: Groovy
>          Issue Type: Bug
>          Components: Compiler
>    Affects Versions: 4.0.3
>            Reporter: Jonathan Baxter
>            Assignee: Daniel Sun
>            Priority: Major
>             Fix For: 7.0.0-alpha-1, 6.0.1
>
>
> The groovy code 
> {code:java}
> obj["lhs"] = "rhs"{code}
> ostensibly converts to 
> {code:java}
> obj.putAt("lhs","rhs")
> {code}
> if {{obj}} has the appropriate {{putAt}} method.
> However , in the process of tracking down a memory leak, I discovered that 
> assignment is not exactly equivalent to invocation of the {{putAt}} method. 
> Specifically, the code generated by the compiler is equivalent to creating an 
> additional temporary variable to hold the rhs of the assignment, and then 
> invoking the {{putAt}} method with that temporary variable as its second 
> argument:
> {code:java}
> String string = "rhs"
> obj.putAt("lhs",string){code}
> The problem with this is that since {{"rhs"}} now has a live reference 
> ({{{}string{}}}), it is not eligible for garbage collection. 
> In my particular use case, {{"rhs"}} is a very large object that I would like 
> to be automatically garbage collected after the assignment is executed, but 
> before the script has finished. But that means I have to use the underlying 
> {{putAt}} method rather than the assignment operator, which rather negates 
> the benefits of overloading the assignment operator.
> One potential fix would be to simply null out any temporary variables 
> constructed by the compiler, so that 
> {code:java}
> obj["lhs"] = "rhs"
> {code}
>  becomes
> {code:java}
> String string = "rhs" 
> obj.putAt("lhs",string) 
> string = null <----- "rhs" is now eligible for garbage-collection{code}



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to