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

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

daniellansun opened a new pull request, #3283:
URL: https://github.com/apache/groovy/pull/3283

   Backport of https://github.com/apache/groovy/pull/3280 to `GROOVY_6_0_X`.
   
   Follow-up to https://github.com/apache/groovy/pull/3226 (already on this 
branch).
   
   ## Review follow-up
   
   The array-store duplicate lived on `BinaryExpressionMultiTypeDispatcher` as 
`duplicateTopBeneath(operandStack, operandsBelow)`. The only caller passed `2`. 
That helper now sits on `OperandStack` next to `dup` and `swap`, as 
`dupUnderTwo()`.
   
   An array store has already cast the index to `int`, so the two operands 
under the value are one word each (array reference, int index). The copy is 
`DUP_X2`, or `DUP2_X2` when the value is a `long` or a `double`. `DUP_X1` and 
`DUP2_X1` are not part of this operation.
   
   ## What still uses a temporary
   
   A plain array or `putAt` statement evaluates receiver, index and right-hand 
side as the store arguments. No temporary is allocated.
   
   When the assignment expression's value is required:
   
   - The array-store fast path duplicates the value under those two operands 
before the component cast. The store consumes the original. The expression 
value is the pre-cast right-hand side, not the value returned by `putAt`.
   - A `putAt` call cannot share that duplicate. The classic call site pushes a 
`CallSite` under the receiver and the index, three words below the value, and 
there is no `DUP_X3`. Inserting the copy only under the receiver and the index 
would make it the `invokeinterface` receiver. Invokedynamic has no call-site 
operand, but the copy would have to be emitted inside that backend after the 
arguments are on the stack. Both backends keep one temporary across the call 
and clear a reference slot immediately afterwards.
   
   Compound `+=`, Elvis `?=` / `??=`, and postfix `a[i]++` evaluate the 
receiver and the index once because each is used for the read and the write. 
The operation result is also stored, because `putAt` consumes it and the 
expression value is the operation result. Those slots are required. Reference 
slots are cleared once the value has been reloaded. `removeVar` only forgets 
the slot; it does not clear it.
   
   ## Guarantees
   
   - Evaluation order for a plain subscript assignment stays receiver, then 
index, then right-hand side. A safe subscript still evaluates the right-hand 
side before the null check and skips the index when the receiver is null.
   - The value of an assignment expression remains the right-hand side (or the 
compound-operation result), not the value returned by `putAt`.
   - A reference temporary that subscript assignment still needs does not stay 
reachable for the rest of the enclosing method.
   
   ## Tests
   
   - `bugs.Groovy10657` covers statement and expression `putAt`, array 
assignment, Elvis subscript, compound `+=`, and postfix `a[i]++`, including 
that a large right-hand side or receiver can be collected while the frame is 
still live.
   - `StaticCompilationTest` keeps the `@CompileStatic` store sequences 
(`intArraySet`, `objectArraySet`, `DUP_X2`, `DUP2_X2`).
   - `BinaryOperationsTest` checks the same store sequences without 
`@CompileStatic` on the classic backend, and the invokedynamic `putAt` shape: a 
statement allocates no right-hand slot, and an expression clears the one 
temporary it keeps.




> 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