|
3 | 3 |
|
4 | 4 | using System;
|
5 | 5 | using System.Runtime.CompilerServices;
|
| 6 | +using System.Runtime.InteropServices; |
6 | 7 |
|
7 | 8 | namespace System.Runtime.JitTesting;
|
8 | 9 |
|
@@ -630,9 +631,83 @@ private static void YetAnotherSafePoint() { }
|
630 | 631 |
|
631 | 632 | // Roslyn likes to eliminate locals we write. Force it to abstain with this function.
|
632 | 633 | 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 | + } |
633 | 684 |
|
634 |
| - class ClassWithField |
| 685 | + [MethodImpl(MethodImplOptions.NoInlining)] |
| 686 | + private static ClassWithField NewCheckedObject() |
635 | 687 | {
|
636 |
| - public int Field; |
| 688 | + ClassWithField obj = new(); |
| 689 | + s_chkObjWeakHandle.Target = obj; |
| 690 | + return obj; |
637 | 691 | }
|
| 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; |
638 | 713 | }
|
0 commit comments