diff --git a/docs/design/coreclr/botr/clr-abi.md b/docs/design/coreclr/botr/clr-abi.md index e90af6c523de95..0539c9bad99792 100644 --- a/docs/design/coreclr/botr/clr-abi.md +++ b/docs/design/coreclr/botr/clr-abi.md @@ -385,7 +385,7 @@ When a funclet finishes execution, and the VM returns execution to the function Any register value changes made in the funclet are lost. If a funclet wants to make a variable change known to the main function (or the funclet that contains the "try" region), that variable change needs to be made to the shared main function stack frame. This not a fundamental limitation. If necessary, the runtime can be updated to preserve non-volatile register changes made in funclets. -Funclets are not required to preserve non-volatile registers. +Funclets are not required to preserve non-volatile registers that are saved by the main method body. # EH Info, GC Info, and Hot & Cold Splitting diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S index 67b05bc9ab77d4..8be19fcc8086ca 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S @@ -464,6 +464,21 @@ NESTED_ENTRY RhpCallFinallyFunclet, _TEXT, NoHandler ALTERNATE_ENTRY RhpCallFinallyFunclet2 + mov rsi, [rsp + locArg1] // rsi <- regdisplay + + mov rax, [rsi + OFFSETOF__REGDISPLAY__pRbx] + mov [rax] , rbx + mov rax, [rsi + OFFSETOF__REGDISPLAY__pRbp] + mov [rax] , rbp + mov rax, [rsi + OFFSETOF__REGDISPLAY__pR12] + mov [rax] , r12 + mov rax, [rsi + OFFSETOF__REGDISPLAY__pR13] + mov [rax] , r13 + mov rax, [rsi + OFFSETOF__REGDISPLAY__pR14] + mov [rax] , r14 + mov rax, [rsi + OFFSETOF__REGDISPLAY__pR15] + mov [rax] , r15 + mov rax, [rsp + locThread] // rax <- Thread* lock or dword ptr [rax + OFFSETOF__Thread__m_ThreadStateFlags], TSF_DoNotTriggerGc diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm index ae147ffee8e9bd..054bf2256bfcdb 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm @@ -617,6 +617,27 @@ NESTED_ENTRY RhpCallFinallyFunclet, _TEXT ALTERNATE_ENTRY RhpCallFinallyFunclet2 + mov rdx, [rsp + rsp_offsetof_arguments + 8h] ;; rdx <- regdisplay + + mov rax, [rdx + OFFSETOF__REGDISPLAY__pRbx] + mov [rax] , rbx + mov rax, [rdx + OFFSETOF__REGDISPLAY__pRbp] + mov [rax] , rbp + mov rax, [rdx + OFFSETOF__REGDISPLAY__pRsi] + mov [rax] , rsi + mov rax, [rdx + OFFSETOF__REGDISPLAY__pRdi] + mov [rax] , rdi + mov rax, [rdx + OFFSETOF__REGDISPLAY__pR12] + mov [rax] , r12 + mov rax, [rdx + OFFSETOF__REGDISPLAY__pR13] + mov [rax] , r13 + mov rax, [rdx + OFFSETOF__REGDISPLAY__pR14] + mov [rax] , r14 + mov rax, [rdx + OFFSETOF__REGDISPLAY__pR15] + mov [rax] , r15 + + ;; XMM6-15 do not need copy-back into REGDISPLAY (no GC adjustment required). + mov rax, [rsp + rsp_offsetof_thread] ;; rax <- Thread* lock or dword ptr [rax + OFFSETOF__Thread__m_ThreadStateFlags], TSF_DoNotTriggerGc diff --git a/src/tests/Regressions/coreclr/GitHub_129010/Test129010.csproj b/src/tests/Regressions/coreclr/GitHub_129010/Test129010.csproj new file mode 100644 index 00000000000000..917fdfe044e22e --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_129010/Test129010.csproj @@ -0,0 +1,11 @@ + + + 1 + + + + + + + + diff --git a/src/tests/Regressions/coreclr/GitHub_129010/test129010.cs b/src/tests/Regressions/coreclr/GitHub_129010/test129010.cs new file mode 100644 index 00000000000000..10fe1165e3d19c --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_129010/test129010.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class Program +{ + static Exception ex = new Exception(); + static byte[] data = null; + static int count = 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Alloc() + { + data = new byte[65536]; + if (count % 16 == 0) + { + // Force compacting GC + GC.Collect(2, GCCollectionMode.Forced, true, true); + } + count++; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Test() + { + try + { + throw ex; + } + catch (Exception) + { + throw; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static object Dummy(object a) + { + return a; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Finally(object a) + { + // This puts object a into the first nonvolatile register + object b = Dummy(a); + Alloc(); + Dummy(b); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Test2(object a) + { + try + { + try + { + Test(); + } + finally + { + Finally(a); + } + } + catch (Exception) + { + } + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static object Foo(int x) + { + return x.ToString(); + } + + static int sum = 0; + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Verify(object a) + { + sum += ((string)a).Length; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Test3() + { + // This puts object a into the first nonvolatile register + object a = Foo(5); + Test2(a); + Verify(a); + } + + [Fact] + public static void TestEntryPoint() + { + for (int i = 0; i < 100; i++) + { + Test3(); + } + } +}