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

[Neo VM] adopt light gc from neogo #3581

Open
wants to merge 36 commits into
base: HF_Echidna
Choose a base branch
from
Open

Conversation

Jim8y
Copy link
Contributor

@Jim8y Jim8y commented Nov 15, 2024

Description

This pr tries to adopt the light GC solution from neogo.

It:

  • remove reference counter management from stackitem, do it in stack.
  • adopt neogo gc solution.

Fixes # (issue)

Type of change

  • Optimization (the change is only an optimization)
  • Style (the change is only a code style for better maintenance or standard purpose)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Test Configuration:

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

@Jim8y
Copy link
Contributor Author

Jim8y commented Nov 15, 2024

A few unit test was updated to adapt light GC. Need help from neogo to review this pr, @roman-khimov @AnnaShaleva.

Copy link
Contributor

@roman-khimov roman-khimov left a comment

Choose a reason for hiding this comment

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

  1. It should be a part of Echidna, we can't easily throw away the old RC.
  2. Probably the best way to test is to keep both RCs in the code and run through testnet/mainnet tx set, comparing values after every instruction. My expectation is they will be the same (at least if don't have any circular references in contract code).

src/Neo.VM/ReferenceCounterV2.cs Outdated Show resolved Hide resolved
src/Neo.VM/JumpTable/JumpTable.Compound.cs Outdated Show resolved Hide resolved
if (item is CompoundType compoundType)
{
// Increment the item's stack references by the specified count.
compoundType.StackReferences--;
Copy link
Member

Choose a reason for hiding this comment

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

In circular references this counter will become negative

Copy link
Contributor Author

@Jim8y Jim8y Nov 17, 2024

Choose a reason for hiding this comment

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

for stack item A, B:

  1. A->C, B->D => ref.A = 1, ref.B = 1, ref.C = 1, ref.D = 1

  2. C->D => ref.C = 1, ref.D = 2.

  3. D->C => ref.C = 2, ref.D = 2

when we try to remvoe C from A, we will have:

  1. C--; => ref(C) == 1

5.then, remve D from B, we will have:

D--; => ref.D = 1, // from here the operation related to D and C will be over, the RC will never be decreased anymore.

  1. but if we revert step 5, and try to remove C from D, we will have:

C--; => ref.C =0,

causing D--; ref.D = 1.

  1. following setp 6, remove D from B,

D--; ref.D =0, but since C is removed from D in step 6, no further operation will be taken

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it can become negative, rather it will have some positive count that is not exactly true (like 1 in RC with no elements on the stack), but this is a safe behavior.

@shargon
Copy link
Member

shargon commented Nov 16, 2024

For me this change seems good, but the test changed to fit this version, so if we want to bee 100% safe we will need a HF and be able to execute with the previous version

@Jim8y
Copy link
Contributor Author

Jim8y commented Nov 17, 2024

For me this change seems good, but the test changed to fit this version, so if we want to bee 100% safe we will need a HF and be able to execute with the previous version

should be the same if properly implementated, some ut was updated cause gc no longer work the same way, for instance, in this pr, you dont have to worry about adding a compound type whose subitems are not RC counted to an array, the GC will do it,,, so the test for adding RC uncounted compound type will no longer work.

@Jim8y Jim8y changed the base branch from master to HF_Echidna November 17, 2024 15:46
@Jim8y Jim8y mentioned this pull request Nov 17, 2024
9 tasks
@Jim8y Jim8y marked this pull request as draft November 17, 2024 15:53
src/Neo/SmartContract/ApplicationEngine.cs Outdated Show resolved Hide resolved
src/Neo.VM/Types/CompoundType.cs Outdated Show resolved Hide resolved
@Jim8y
Copy link
Contributor Author

Jim8y commented Nov 21, 2024

more tests. running on mainnet and testnet.

{
if (!map.TryGetValue(key, out var value1))
{
engine.ReferenceCounter.AddStackReference(key);
Copy link
Member

Choose a reason for hiding this comment

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

the key is added multiple times? @roman-khimov

Copy link
Member

@cschuchardt88 cschuchardt88 Nov 21, 2024

Choose a reason for hiding this comment

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

its dictionary?

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 key is added if it does not exsit in the dictionary, when the value is removed from the dic, the key is removed as well. So no value also means no key.

Copy link
Member

Choose a reason for hiding this comment

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

But if i add twice the key is only one and is counted as two

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if you add twice, the key wont be changed, key will only be added once, then it will only be removing the value, and add the value.

The complete logic here is:

key exists:

     remove old value ref, add new value ref

key does not exist:
add key ref, add new value ref.

Copy link
Member

Choose a reason for hiding this comment

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

The key don't change in the map, but it's called twice to engine.ReferenceCounter.AddStackReference(key);

break;
case Map map:
map.Remove(key);
var old = map.RemoveKey(key);
if (old != null && engine.ReferenceCounter.Version == RCVersion.V2)
Copy link
Member

Choose a reason for hiding this comment

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

This is checked that can return false in neo-go? @roman-khimov

Comment on lines +240 to +242
Array array = new(new StackItem[] { 1, 2, 3, 4 });
engine.CurrentContext.EvaluationStack.Push(array);
Assert.AreEqual(array.Count + 1, engine.ReferenceCounter.Count);
Copy link
Member

Choose a reason for hiding this comment

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

We should try to avoid the UT changes, in this case, what's wrong exactly?

Copy link
Contributor Author

@Jim8y Jim8y Nov 21, 2024

Choose a reason for hiding this comment

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

this is because of how we process reference counter for compound type, previously we can test the RC without the engine, cause stackitem itself will process the RC, but in the new version, only executionstack will process the stack, as a result, we must push the item to the stack to get a RC count.

That is why it was 4 in the past, as the array is not in the stack. 4 = Array.subitems.
While new version its 5 (4+1), cause we have to push the array itself to the stack. 5 = Array.subitems+Array

@shargon
Copy link
Member

shargon commented Nov 21, 2024

I already made my optimizations/review, just we should ensure that it works with tons of UT and checking all the UT that was not working with the old logic


if (item is CompoundType compoundType)
{
// Increment the item's stack references by the specified count.
Copy link
Contributor

Choose a reason for hiding this comment

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

Increment -> Decrease

/// <inheritdoc/>
public void RemoveStackReference(StackItem item)
{
// Increment the reference count by the specified count.
Copy link
Contributor

Choose a reason for hiding this comment

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

Increment -> Decrease

_referencesCount += count;

if (_referencesCount > _limits.MaxStackSize)
throw new IndexOutOfRangeException("Circular reference detected, execution stopped.");
Copy link
Contributor

@Hecate2 Hecate2 Nov 25, 2024

Choose a reason for hiding this comment

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

Not the best exception message. It can be simply caused by too many objects

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants