Skip to content

Commit 1cf4354

Browse files
Add a test
1 parent d964353 commit 1cf4354

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,8 @@ private static unsafe int Main(string[] args)
430430

431431
LSSATests.Run();
432432

433+
FunctionalLSSATests.Run();
434+
433435
TestThreadStaticAlignment();
434436

435437
TestLiveInFirstBlockForTracked(new object());

src/tests/nativeaot/SmokeTests/HelloWasm/LSSATests.cs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
67

78
namespace System.Runtime.JitTesting;
89

@@ -630,9 +631,83 @@ private static void YetAnotherSafePoint() { }
630631

631632
// Roslyn likes to eliminate locals we write. Force it to abstain with this function.
632633
private static void ForceILLocal(void* x) { }
634+
}
635+
636+
public unsafe class FunctionalLSSATests
637+
{
638+
private static bool s_failed;
639+
private static GCHandle s_chkObjWeakHandle;
640+
641+
// These tests, unlike the ones above, test the generated code indirectly, by creating objects and checking they're
642+
// kept alive as expected. They are ideally suited for testing correctness, where we need something to be visible
643+
// to the GC but do not care particularly about how precisely that is achieved.
644+
public static void Run()
645+
{
646+
s_chkObjWeakHandle = GCHandle.Alloc(null, GCHandleType.Weak);
647+
648+
Program.StartTest("FunctionalLSSATests");
649+
650+
SpilledCandidateSlotInvalidated();
651+
if (!FunctionalTestSucceeded(nameof(SpilledCandidateSlotInvalidated)))
652+
return;
653+
654+
Program.PassTest();
655+
}
656+
657+
private static bool FunctionalTestSucceeded(string name)
658+
{
659+
if (s_failed)
660+
{
661+
Program.FailTest($" {name} failed");
662+
return false;
663+
}
664+
return true;
665+
}
666+
667+
[MethodImpl(MethodImplOptions.NoInlining)]
668+
private static ref int SpilledCandidateSlotInvalidated()
669+
{
670+
// Here we are testing that 'derivedRef' keeps 'chkObj' alive as expected,
671+
// despite the fact 'chkObj' is spilled at the point 'derivedRef' is formed.
672+
ClassWithField chkObj = NewCheckedObject();
673+
LogicalSafePoint(chkObj, chkObj);
674+
LogicalSafePoint(chkObj, chkObj);
675+
676+
ref int derivedRef = ref chkObj.Field;
677+
chkObj = GetNullOpaquely(); // Invalidate the pin.
678+
LogicalSafePoint(chkObj, chkObj);
679+
LogicalSafePoint(chkObj, chkObj);
680+
ValidateCheckedObjectIsAlive();
681+
682+
return ref derivedRef;
683+
}
633684

634-
class ClassWithField
685+
[MethodImpl(MethodImplOptions.NoInlining)]
686+
private static ClassWithField NewCheckedObject()
635687
{
636-
public int Field;
688+
ClassWithField obj = new();
689+
s_chkObjWeakHandle.Target = obj;
690+
return obj;
637691
}
692+
693+
[MethodImpl(MethodImplOptions.NoInlining)]
694+
private static void ValidateCheckedObjectIsAlive()
695+
{
696+
GC.Collect();
697+
if (s_chkObjWeakHandle.Target is null)
698+
{
699+
s_failed = true;
700+
}
701+
}
702+
703+
[MethodImpl(MethodImplOptions.NoInlining)]
704+
private static void LogicalSafePoint(object x = null, object y = null) { }
705+
706+
[MethodImpl(MethodImplOptions.NoInlining)]
707+
private static ClassWithField GetNullOpaquely() => null;
708+
}
709+
710+
class ClassWithField
711+
{
712+
public int Field;
638713
}

0 commit comments

Comments
 (0)