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

Fix allocation of empty array in the frozen heap for collectible types #100444

Conversation

xoofx
Copy link
Member

@xoofx xoofx commented Mar 29, 2024

Fixes #100437

An empty array should not be allocated on a frozen heap if its type is coming from a collectible assembly.

Might be difficult to bring a proper test (e.g is there an API to check if an object is instantiated in a frozen heap?). If it is required, guidance appreciated.

This fix should be backported to net8.0 as well.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 29, 2024
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 29, 2024
@xoofx
Copy link
Member Author

xoofx commented Mar 29, 2024

My colleague that has been investigating this is also suggesting adding an assert in FrozenObjectHeapManager::TryAllocateObject to check that a type cannot be collectible.

_ASSERT(type != nullptr);
_ASSERT(FOH_COMMIT_SIZE >= MIN_OBJECT_SIZE);

Thoughts?

I can add it as part of this PR.

@jkotas
Copy link
Member

jkotas commented Mar 29, 2024

adding an assert

Sounds good to me.

We should also add a test that hits it.

@jkotas
Copy link
Member

jkotas commented Mar 29, 2024

We should also add a test that hits it.

Let me know if you need help with the test.

@xoofx
Copy link
Member Author

xoofx commented Mar 29, 2024

Let me know if you need help with the test.

Yes, please, a starting place would be helpful! 😅

@JulieLeeMSFT JulieLeeMSFT added this to the 9.0.0 milestone Mar 29, 2024
@cshung
Copy link
Member

cshung commented Mar 29, 2024

is there an API to check if an object is instantiated in a frozen heap

Yes, IGCHeap::IsInFrozenSegment should do.

virtual bool IsInFrozenSegment(Object *object) PURE_VIRTUAL

@xoofx
Copy link
Member Author

xoofx commented Mar 30, 2024

Yes, IGCHeap::IsInFrozenSegment should do.

Thanks! I meant from C# as I'm not familiar how I will be able to make tests only from C++.

@jkotas
Copy link
Member

jkotas commented Mar 30, 2024

is there an API to check if an object is instantiated in a frozen heap

We prefer to test observable behavior like that the program does not crash. You do not need an API that checks if an object is instantiated in a frozen heap for that.

@jkotas
Copy link
Member

jkotas commented Mar 30, 2024

Yes, please, a starting place would be helpful!

This will crash with high probability due to this bug:

using System.Runtime.Loader;

public class Program
{
    static void Main()
    {
        WeakReference[] wrs = new WeakReference[10];
        for (int i = 0; i < wrs.Length; i++)
        {
            var alc = new MyAssemblyLoadContext();
            var a = alc.LoadFromAssemblyPath(typeof(Program).Assembly.Location);
            wrs[i] = (WeakReference)a.GetType("Program").GetMethod("Work").Invoke(null, null);
            GC.Collect();
        }

        foreach (var wr in wrs)
        {
            Console.WriteLine(wr.Target);
        }
    }

    public static WeakReference Work()
    {
        return new WeakReference(Array.Empty<Program>());
    }
}

class MyAssemblyLoadContext : AssemblyLoadContext
{
    public MyAssemblyLoadContext()
        : base(isCollectible: true)
    {
    }
}

@xoofx
Copy link
Member Author

xoofx commented Mar 30, 2024

We prefer to test observable behavior like that the program does not crash. You do not need an API that checks if an object is instantiated in a frozen heap for that.
This will crash with high probability due to this bug:

Makes sense! Where should I add such a test? In src\tests\GC\Scenarios but I see also that there is a src\tests\GC\API\Frozen?

@jkotas
Copy link
Member

jkotas commented Mar 30, 2024

As I was writing the test, I have realized that there is an opposite problem too: The frozen object allocated in shared generic static constructor may end up leaking:

class MyG<T>
{
     // This will be allocated on frozen heap, but it is going to leak if T is collectible
    static object s = new object();
}

I am not sure what's the best way to fix this leak. We can either detect and reject these problematic patterns as canidates for frozen heap allocation, or we need to pass the containing generic type to the MayBeFrozen allocation helper. cc @EgorBo

@jkotas
Copy link
Member

jkotas commented Mar 30, 2024

Where should I add such a test?

I would add it under src\tests\JIT\Regression\JitBlue. This is a codegen related problem, so the regression test for it belongs under JIT tests.

@xoofx
Copy link
Member Author

xoofx commented Mar 30, 2024

Added the test with commit 1189eec but I don't know how to run it. I tried .\build.cmd clr+libs+libs.tests -rc checked -lc release -test but doesn't look that it ran it.

@EgorBo
Copy link
Member

EgorBo commented Mar 30, 2024

As I was writing the test, I have realized that there is an opposite problem too: The frozen object allocated in shared generic static constructor may end up leaking:

class MyG<T>
{
     // This will be allocated on frozen heap, but it is going to leak if T is collectible
    static object s = new object();
}

I am not sure what's the best way to fix this leak. We can either detect and reject these problematic patterns as canidates for frozen heap allocation, or we need to pass the containing generic type to the MayBeFrozen allocation helper. cc @EgorBo

Good point! I propose this patch here:

-    if ((fi.fieldFlags & flagsToCheck) == flagsToCheck)
+    if (((fi.fieldFlags & flagsToCheck) == flagsToCheck) &&
+       ((info.compCompHnd->getClassAttribs(info.compClassHnd) & CORINFO_FLG_SHAREDINST) == 0))

the logic to detect candidates for NonGC allocators is quite trivial here, I have a prototype for a more advanced version, but it's not ready yet.

@EgorBo
Copy link
Member

EgorBo commented Mar 30, 2024

Good point! I propose this patch here:

Ah, it's a bit too conservative. Basically, with this patch we'll never optimize Array.Empty<string>() into a nongc handle. I presume a proper way is to pass type of current class as an argument in frozen allocator.. but that is not a trivial change so probably for now we should take the conservative patch?

@xoofx xoofx force-pushed the fix-frozen-empty-array-alloc-for-collectible-array-type branch from 6050688 to 81578a1 Compare March 30, 2024 17:06
@jkotas
Copy link
Member

jkotas commented Mar 30, 2024

Ah, it's a bit too conservative. Basically, with this patch we'll never optimize Array.Empty() into a nongc handle. I presume a proper way is to pass type of current class as an argument in frozen allocator.. but that is not a trivial change so probably for now we should take the conservative patch?

Yes, the conservative fix can be backport candidate. The full fix would be hard to backport.

@xoofx Would you like to include it in this PR? (The test for this case can have similar structure - it should use weak handle to verify that the object is gone after the collectible assembly is unloaded.)

@xoofx xoofx force-pushed the fix-frozen-empty-array-alloc-for-collectible-array-type branch from 81578a1 to a9abd1d Compare March 30, 2024 17:58
@jkotas
Copy link
Member

jkotas commented Mar 31, 2024

I have pushed test update that hits all interesting cases.

src/tests/issues.targets Outdated Show resolved Hide resolved
@EgorBo
Copy link
Member

EgorBo commented Apr 1, 2024

I also usually struggle finding the right syntax for issues.props 😐

@jkotas jkotas merged commit 59d9749 into dotnet:main Apr 2, 2024
116 checks passed
@jkotas
Copy link
Member

jkotas commented Apr 2, 2024

/backport to release/8.0-staging

Copy link
Contributor

github-actions bot commented Apr 2, 2024

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/8517805878

Copy link
Contributor

github-actions bot commented Apr 2, 2024

@jkotas backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch

Applying: Fix allocation of empty array in the frozen heap for collectible types (#100437)
Using index info to reconstruct a base tree...
M	src/coreclr/vm/frozenobjectheap.cpp
M	src/coreclr/vm/gchelpers.cpp
Falling back to patching base and 3-way merge...
Auto-merging src/coreclr/vm/gchelpers.cpp
CONFLICT (content): Merge conflict in src/coreclr/vm/gchelpers.cpp
Auto-merging src/coreclr/vm/frozenobjectheap.cpp
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 Fix allocation of empty array in the frozen heap for collectible types (#100437)
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

Copy link
Contributor

github-actions bot commented Apr 2, 2024

@jkotas an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

jkotas added a commit that referenced this pull request Apr 2, 2024
#100444)

* Fix allocation of empty array in the frozen heap for collectible types (#100437)

* Remove Optimize from csproj

* Add test for generic with static

* Apply suggestions from code review

* Better test

* Disable tests on Mono

---------

Co-authored-by: Jan Kotas <[email protected]>
@jkotas
Copy link
Member

jkotas commented Apr 2, 2024

I have opened #100510 on re-enabling the optimization in shared generic with proper lifetime checks.

#100509 is the backport to .NET 8.

@xoofx Thank you for your help with getting this bug found and fixed!

@xoofx
Copy link
Member Author

xoofx commented Apr 2, 2024

@xoofx Thank you for your help with getting this bug found and fixed!

Most of the hard work investigating the crash from our side came from @alexey-zakharov ☺️

We are super glad that a quick fix was found, as we are heavily relying on collectible ALC and that bug haunted several crashes on our CI. Thanks a lot for helping with it!

alexey-zakharov pushed a commit to Unity-Technologies/runtime that referenced this pull request Apr 2, 2024
dotnet#100444)

* Fix allocation of empty array in the frozen heap for collectible types (dotnet#100437)

* Remove Optimize from csproj

* Add test for generic with static

* Apply suggestions from code review

* Better test

* Disable tests on Mono

---------

Co-authored-by: Jan Kotas <[email protected]>
(cherry picked from commit 78f7707)
jkotas added a commit that referenced this pull request Apr 4, 2024
#100444) (#100509)

* Fix allocation of empty array in the frozen heap for collectible types (#100437)

* Remove Optimize from csproj

* Add test for generic with static

* Apply suggestions from code review

* Better test

* Disable tests on Mono

---------

Co-authored-by: Alexandre Mutel <[email protected]>
@xoofx xoofx deleted the fix-frozen-empty-array-alloc-for-collectible-array-type branch April 11, 2024 09:52
matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
dotnet#100444)

* Fix allocation of empty array in the frozen heap for collectible types (dotnet#100437)

* Remove Optimize from csproj

* Add test for generic with static

* Apply suggestions from code review

* Better test

* Disable tests on Mono

---------

Co-authored-by: Jan Kotas <[email protected]>
@github-actions github-actions bot locked and limited conversation to collaborators May 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Empty array allocated on the Frozen Heap for a Collectible type?
6 participants