From 9137cb4e88ace1a79cdff59a9b77342588118887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 10 Nov 2023 18:34:51 +0900 Subject: [PATCH] Disallow inlining Main (#94449) When we compile managed code, `Main` is not the actual spot where execution of managed code starts. Instead it's the `StartupCodeMain` method that the compiler generates. This method is responsible for initializing the managed environment, calling `Main` and tearing down the environment. If `Main` is short enough, sometimes it gets inlined into `StartupCodeMain` this has bad impact on diagnostics (don't see `Main` in stack traces, can't set breakpoints). Pretend it was marked `NoInlining`. --- .../StartupCode/StartupCodeMainMethod.cs | 22 +++++++++++++++++++ .../JitInterface/CorInfoImpl.RyuJit.cs | 9 ++++++++ src/coreclr/vm/jitinterface.cpp | 5 ++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs index c86852973e527..3dcbb4f27cc09 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs @@ -254,6 +254,25 @@ public override MethodSignature Signature } } + public override bool IsNoOptimization + { + get + { + // Mark as no optimization so that Main doesn't get inlined + // into this method. We want Main to be visible in stack traces. + return true; + } + } + + public override bool IsNoInlining + { + get + { + // Mark NoInlining so that IsNoOptimization is guaranteed to kick in. + return true; + } + } + public override MethodIL EmitIL() { ILEmitter emit = new ILEmitter(); @@ -268,6 +287,9 @@ public override MethodIL EmitIL() if (Context.Target.IsWindows) codeStream.MarkDebuggerStepInPoint(); + // This would be tail call eligible but we don't do tail calls + // if the method is marked NoInlining and we just did it above. + codeStream.Emit(ILOpcode.tail); codeStream.Emit(ILOpcode.call, emit.NewToken(WrappedMethod)); codeStream.Emit(ILOpcode.ret); diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 460dbb744d934..eb988b0b604f8 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -13,6 +13,7 @@ using ILCompiler; using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem.Ecma; #if SUPPORT_JIT using MethodCodeNode = Internal.Runtime.JitSupport.JitMethodCodeNode; @@ -799,6 +800,14 @@ private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUC { MethodDesc caller = HandleToObject(callerHnd); + if (caller.OwningType is EcmaType ecmaOwningType + && ecmaOwningType.EcmaModule.EntryPoint == caller) + { + // Do not tailcall from the application entrypoint. + // We want Main to be visible in stack traces. + result = false; + } + if (caller.IsNoInlining) { // Do not tailcall from methods that are marked as noinline (people often use no-inline diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 9471239e25a87..1a844f7da4db0 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8226,9 +8226,8 @@ bool CEEInfo::canTailCall (CORINFO_METHOD_HANDLE hCaller, { mdMethodDef callerToken = pCaller->GetMemberDef(); - // We don't want to tailcall the entrypoint for an application; JIT64 will sometimes - // do this for simple entrypoints and it results in a rather confusing debugging - // experience. + // Do not tailcall from the application entrypoint. + // We want Main to be visible in stack traces. if (callerToken == pCaller->GetModule()->GetEntryPointToken()) { result = false;