-
Notifications
You must be signed in to change notification settings - Fork 375
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
fix(gnovm): assign to deref pointers #1919
Conversation
This reverts commit e34a322.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1919 +/- ##
===========================================
+ Coverage 48.25% 58.73% +10.47%
===========================================
Files 408 436 +28
Lines 62338 68638 +6300
===========================================
+ Hits 30081 40314 +10233
+ Misses 29749 25313 -4436
- Partials 2508 3011 +503
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
// The star expression is on the LHS, so evaluate the expression as | ||
// a reference. This ensures the value that is pushed is a pointer to | ||
// the pointer value represented by the lx.X expression. This will be | ||
// helpful if the underlying pointer value dos not have a base; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does not have a base
looking at this now. |
See #2255 for fix.
In the above example there are two Blocks; the global, and the inner func block. The line
|
Closing in favor of #2255 |
This is a complete solution, alternative to #1919, and (I think) closes #1326. It creates a new container for "baseless" (floating) values constructed via `new(xxx)` or `&struct{}`, which currently do not have a base containing object for that value, and are currently represented as PointerValues with .Base set to nil. The containing object is like a Block but minimal -- it only contains one Value, and has no Source or Parent. The modifications to realm.go allow for proper ref-counting so that even when there are multiple references to the baseless value, and even when the value is primitive, gc and ref-counting works (since the containing HeapItemValue is ref-counted). PointerValue.Base should now never be nil. See also #1919 (comment) for why the previous solution doesn't work. A better optimization than the one mentioned in the comment above, is to always store the HeapItemValue along with the Value, since the Value's refcount should always be 1. This is left for the future, after first checking that this invariant is true. --------- Co-authored-by: deelawn <dboltz03@gmail.com>
This is a complete solution, alternative to gnolang#1919, and (I think) closes gnolang#1326. It creates a new container for "baseless" (floating) values constructed via `new(xxx)` or `&struct{}`, which currently do not have a base containing object for that value, and are currently represented as PointerValues with .Base set to nil. The containing object is like a Block but minimal -- it only contains one Value, and has no Source or Parent. The modifications to realm.go allow for proper ref-counting so that even when there are multiple references to the baseless value, and even when the value is primitive, gc and ref-counting works (since the containing HeapItemValue is ref-counted). PointerValue.Base should now never be nil. See also gnolang#1919 (comment) for why the previous solution doesn't work. A better optimization than the one mentioned in the comment above, is to always store the HeapItemValue along with the Value, since the Value's refcount should always be 1. This is left for the future, after first checking that this invariant is true. --------- Co-authored-by: deelawn <dboltz03@gmail.com>
Closes #1326.
Some context about the problem this solves
This PR solves an issue where values don't get persisted after assignments to pointer values, where the LHS of the assign statement is a star expression. For example, this statement could cause an issue:
*i = 7
. The only time there is an issue with this kind of assignment is when the pointer was previously assigned an address usingnew
or a composite literal -- so the above example of the assignment toi
would only be an issue ifi
was initialized byi = new(int)
. Or some composite value might be initialized with something likes = &S{}
.Now that the cases where this can be an issue have been laid out, let's look at the underlying cause of the issue. Here is the definition of a pointer value:
In the case outlined above with pointer assignment using
new
or a composite literal, theBase
of the pointer's underlying value isnil
. Let's look at another example:After this package is created and
Assign
is called, this will be the composition of bothintPtr
andconcreteIntPtr
:Notice that the pointer that points to a concrete value has a base and the pointer that points to a value created dynamically using
new
does not have a base. This is fine, but causes an issue with persisting a value after assignment.Keeping that in mind, let's next look at how star expressions are resolved to values. Here is the star expression definition:
So when we see a star expression, we will evaluate the expression the star expression is dereferencing; the evaluation op will push this value to the machine stack.
gno/gnovm/pkg/gnolang/machine.go
Lines 1905 to 1908 in b2f12a9
All (I think?) assignment statements will call the machine's
PopAsPointer
method to retrieve a pointer of the LHS value. In our case with the star expression on the LHS, this will be the value pushed after the evaluation above. The value is popped here:gno/gnovm/pkg/gnolang/machine.go
Lines 1932 to 1934 in b2f12a9
This value that is popped is the value of the "inner"
PointerValue
(see values ofintPtr
andconcreteIntPtr
after assignment above). Therefore, if the LHS pointer value was assigned usingnew
or a composite literal, theBase
will benil
. This is the problem and reason why assignments with these properties won't end up persisting any values. As an example, consider the standard assignment statement*i = 5
, setting one value equal to another. Assignment pops the pointer to the LHS value and sends it and the RHS value toAssign2
:gno/gnovm/pkg/gnolang/op_assign.go
Lines 27 to 46 in b2f12a9
The problem is that
Assign2
requires a realm and a target pointer value with a non-nil base in order to do the assignment and mark the values to be persisted:gno/gnovm/pkg/gnolang/values.go
Lines 279 to 284 in b2f12a9
In our case, the pointer value,
pv
, has no base defined for reasons outlined earlier. This means that, whilepv.TV.Assign
is still called to do the assignment,DidUpdate
is never called.DidUpdate
needs to be called so that the parent object (base) is marked as dirty so the realm finalization step knows to check its descendent objects for other dirty and new values to persist.The Solution
What are the criteria for this problem to occur?
new
or a composite literalWe can check the first criterion in the preprocessor. When transcribing a star expression, set a flag
IsLHS
if this transcription step was called from a LHS assignment step.Next, when calling
PushForPointer
as part of an assign statement evaluation, check if the star expression is on the LHS. If it is, we want to evaluate a reference to the expression. This means that the value pushed to the stack will not be the "inner pointer", but the full pointer value (see the value ofintPtr
above). This is important because now, during assignment, we have the outer pointer'sBase
value at our disposal.During the assignment operation,
PopAsPointer
is called; this will pop the reference value that was just evaluated. At this point, the pointer value popped actually refers tointPtr
rather than*intPtr
. So it checks if this star expression is on the LHS. If it is, it knows that the value needs to be dereferenced. But first it does one more check -- if the base of the dereferenced pointer is nil, assign it the value of the base of the parent pointer. This ensures that the value will be persisted after assignment.Contributors' checklist...
BREAKING CHANGE: xxx
message was included in the description