JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

JIT: fix overly aggressive tail recursive call loop marking - #33517

Merged
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking
Mar 12, 2020
Merged

JIT: fix overly aggressive tail recursive call loop marking#33517
AndyAyersMS merged 3 commits into
dotnet:masterfrom
AndyAyersMS:FixRecursiveTailCallLoopMarking

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.

Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.

If there is a tail recursive call site, the jit will mark all blocks
from method entry to the block with the call as "in a loop," anticipating
a tail recursive call to loop optimization.
Because of some confusing naming we were doing this even for recursive calls
that were not tail calls. Upshot is that blocks were marked as being in loops
when they weren't, and (among other things) this made the inliner more
aggressive for calls in those blocks.
@Dotnet-GitSync-BotDotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 12, 2020
@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL.

This fixes an issue I ran into while working on on stack replacement, see these review comments.

I'll mark the one semantic change with a review comment; the remainder is renaming and compressing some dumps via JITDUMP.

Jit-diffs shows reduction in code size. At first glance most of this is because the jit is doing fewer inlines as fewer blocks in recursive methods are considered to be "in a loop."

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for x64 default jit
Summary of Code Size diffs:
(Lower is better)
Total bytes of diff: -33810 (-0.07% of base)
diff is an improvement.
Top file regressions (bytes):
57 : Newtonsoft.Json.Bson.dasm (0.06% of base)
5 : xunit.assert.dasm (0.00% of base)
5 : xunit.core.dasm (0.01% of base)
Top file improvements (bytes):
-13569 : System.Private.Xml.dasm (-0.38% of base)
-6637 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.12% of base)
-3632 : Microsoft.CodeAnalysis.CSharp.dasm (-0.08% of base)
-2019 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.07% of base)
-1218 : System.Reflection.Metadata.dasm (-0.28% of base)
-1195 : System.Configuration.ConfigurationManager.dasm (-0.34% of base)
-1017 : System.Private.CoreLib.dasm (-0.02% of base)
-882 : Microsoft.CodeAnalysis.dasm (-0.05% of base)
-861 : Newtonsoft.Json.dasm (-0.10% of base)
-610 : System.Data.Common.dasm (-0.04% of base)
-515 : System.Collections.Immutable.dasm (-0.05% of base)
-361 : System.Reflection.MetadataLoadContext.dasm (-0.20% of base)
-256 : System.Runtime.Numerics.dasm (-0.35% of base)
-219 : System.Private.DataContractSerialization.dasm (-0.03% of base)
-134 : Microsoft.VisualBasic.Core.dasm (-0.03% of base)
-116 : System.Linq.Expressions.dasm (-0.02% of base)
-109 : System.Diagnostics.PerformanceCounter.dasm (-0.13% of base)
-87 : System.Diagnostics.Process.dasm (-0.10% of base)
-80 : System.IO.FileSystem.dasm (-0.08% of base)
-56 : System.Drawing.Common.dasm (-0.02% of base)
32 total files with Code Size differences (29 improved, 3 regressed), 199 unchanged.
Top method regressions (bytes):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (bytes):
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-844 (-29.57% of base) : System.Private.Xml.dasm - SchemaCollectionPreprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-838 (-29.65% of base) : System.Private.Xml.dasm - Preprocessor:PreprocessSimpleType(XmlSchemaSimpleType,bool):this
-835 (-21.90% of base) : System.Private.Xml.dasm - Compiler:CompileElement(XmlSchemaElement):this
-833 (-13.60% of base) : System.Private.CoreLib.dasm - DateTimeParse:ParseByFormat(byref,byref,byref,DateTimeFormatInfo,byref):bool
-826 (-22.77% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileElement(XmlSchemaElement):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-639 (-6.31% of base) : System.Configuration.ConfigurationManager.dasm - ConfigurationElement:DeserializeElement(XmlReader,bool):this
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-448 (-19.67% of base) : System.Private.Xml.dasm - SchemaCollectionCompiler:CompileSimpleType(XmlSchemaSimpleType):this
-439 (-16.28% of base) : Newtonsoft.Json.dasm - JsonSchemaGenerator:GenerateInternal(Type,int,bool):JsonSchema:this
-436 (-18.96% of base) : System.Private.Xml.dasm - Compiler:CompileSimpleType(XmlSchemaSimpleType):this
-432 (-20.11% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyExpression(BoundExpression,DiagnosticBag):BoundExpression:this
-374 (-11.77% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VBSemanticModel:GetSemanticSymbols(BoundNodeSummary,Binder,int,byref,byref):ImmutableArray`1:this
-366 (-25.33% of base) : Microsoft.CodeAnalysis.CSharp.dasm - SyntaxEquivalence:AreEquivalentRecursive(GreenNode,GreenNode,Func`2,bool):bool
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
Top method regressions (percentages):
62 (27.56% of base) : Microsoft.CodeAnalysis.dasm - SyntaxWalker:Visit(SyntaxNode):this
21 ( 6.84% of base) : System.Private.Xml.dasm - XmlILVisitor:CachesResult(QilNode):bool:this
12 ( 6.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - TypeCompilationState:CallsInitializeComponent(MethodSymbol,HashSet`1):bool:this
40 ( 5.52% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - AsyncMethodToClassRewriter:TypeNeedsClearing(TypeSymbol):bool:this
39 ( 5.24% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:GetFirstDirective(Func`2):DirectiveTriviaSyntax:this
23 ( 4.45% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LambdaRewriter:FramePointer(VisualBasicSyntaxNode,NamedTypeSymbol):BoundExpression:this
13 ( 4.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:IsEmptyStructType(TypeSymbol):bool:this
37 ( 3.52% of base) : Newtonsoft.Json.dasm - JsonSchemaModelBuilder:BuildNodeModel(JsonSchemaNode):JsonSchemaModel:this
5 ( 3.45% of base) : System.IO.Packaging.dasm - CompatibilityScope:ShouldProcessContent(String,String):bool:this
46 ( 2.70% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - VisualBasicSyntaxNode:FindTriviaByOffset(SyntaxNode,int,Func`2):SyntaxTrivia
43 ( 2.40% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
43 ( 1.93% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:WriteTokenInternal(BsonToken):this
5 ( 1.86% of base) : Microsoft.CodeAnalysis.dasm - MetadataWriter:GetImportScopeIndex(IImportScope,Dictionary`2):int:this
7 ( 1.81% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - LocalRewriter:LocalOrFieldNeedsToBeCleanedUp(TypeSymbol):bool:this
14 ( 1.19% of base) : Newtonsoft.Json.Bson.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
14 ( 1.19% of base) : Newtonsoft.Json.dasm - BsonBinaryWriter:CalculateSize(BsonToken):int:this
5 ( 0.67% of base) : xunit.assert.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.core.dasm - ArgumentFormatter:FormatTypeName(Type):String
5 ( 0.67% of base) : xunit.execution.dotnet.dasm - ArgumentFormatter:FormatTypeName(Type):String
3 ( 0.28% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitAllElementInitializersRecursive(ArrayTypeSymbol,ArrayBuilder`1,bool):this
Top method improvements (percentages):
-573 (-60.38% of base) : Microsoft.CodeAnalysis.dasm - AnalyzerFileReference:GetFullyQualifiedTypeName(TypeDefinition,PEModule):String
-1226 (-53.80% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Binder:ReclassifyInvocationExpressionAsStatement(BoundExpression,DiagnosticBag):BoundExpression:this
-1251 (-50.10% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - DataFlowPass:MakeSlotsForExpression(BoundExpression):SlotCollection:this
-659 (-45.73% of base) : Microsoft.CodeAnalysis.CSharp.dasm - CodeGenerator:EmitStore(BoundAssignmentOperator):this
-361 (-45.12% of base) : System.Reflection.MetadataLoadContext.dasm - EcmaToStringHelpers:ToTypeString(TypeDefinitionHandle,MetadataReader):String
-465 (-43.26% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - SymbolDisplayVisitor:VisitNamespace(INamespaceSymbol,String):this
-710 (-40.85% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - CodeGenerator:EmitStore(BoundExpression):this
-56 (-37.84% of base) : System.Drawing.Common.dasm - GraphicsContext:Dispose(bool):this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():ubyte:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():short:this
-239 (-35.78% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():int:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathPatternParser:ParseRelativePathPattern():QilNode:this
-239 (-35.46% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():long:this
-48 (-35.29% of base) : Microsoft.CodeAnalysis.CSharp.dasm - DirectiveParser:ParseLogicalNot():ExpressionSyntax:this
-116 (-35.05% of base) : System.Linq.Expressions.dasm - Expression:GetUserDefinedBinaryOperator(int,Type,Type,String):MethodInfo
-605 (-34.97% of base) : Microsoft.CodeAnalysis.CSharp.dasm - LocalRewriter:MakeConversion(CSharpSyntaxNode,BoundExpression,Conversion,TypeSymbol,bool,bool,ConstantValue):BoundExpression:this
-245 (-34.70% of base) : System.Private.Xml.dasm - XPathParser`1:ParseRelativeLocationPath():double:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):ubyte:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):short:this
-235 (-31.84% of base) : System.Private.Xml.dasm - XPathParser`1:ParseSubExpr(int):int:this
238 total methods with Code Size differences (218 improved, 20 regressed), 240241 unchanged.


// A tail recursive call is a potential loop from the current block to the start of the method.
if (canTailCall && gtIsRecursiveCall(methHnd))
if ((tailCallFlags != 0) && canTailCall && gtIsRecursiveCall(methHnd))

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix -- canTailCall is set to true at the top of a method, and so might still be true when we reach this point for calls that are not tail calls. So we need to also check if this call is a tail call.

Comment threadsrc/coreclr/src/jit/importer.cpp Outdated

@CarolEidtCarolEidt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I gather you've sampled to diffs to ensure that they are as expected?

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

I gather you've sampled to diffs to ensure that they are as expected?

Wasn't sure what to expect. Checked what I hope is a representative sample; all of the diffs I saw were from less inlining, and quite often, from less inlining on exception paths. Here's an example from System.Private.Xml:

 mov rcx, 0xD1FFAB1E
call CORINFO_HELP_NEWSFAST
mov rdi, rax
- mov rcx, rsi- xor rdx, rdx- call XPathException:CreateMessage(String,ref):String- mov dword ptr [rdi+112], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+16]- mov rdx, rax- call CORINFO_HELP_ASSIGN_REF- xor rdx, rdx- mov gword ptr [rdi+32], rdx- mov dword ptr [rdi+116], 0xD1FFAB1E- mov dword ptr [rdi+116], 0xD1FFAB1E- lea rcx, bword ptr [rdi+120]+ mov rcx, rdi
mov rdx, rsi
- call CORINFO_HELP_ASSIGN_REF- xor rcx, rcx- mov gword ptr [rdi+128], rcx+ xor r8, r8+ xor r9, r9+ call XPathException:.ctor(String,ref,Exception):this
mov rcx, rdi
call CORINFO_HELP_THROW
int3
-; Total bytes of code 779, prolog size 10, PerfScore 155.04, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this+; Total bytes of code 632, prolog size 10, PerfScore 133.46, (MethodHash=14ab57cd) for method QueryBuilder:ProcessNode(AstNode,int,byref):Query:this

In some of the "regression" cases, eg SyntaxWalker:Visit(SyntaxNode):thisXmlILVisitor:CachesResult(QilNode):bool:this, the extra inlines lead to less code overall because the inlines enabled struct promotion. So there's perhaps a missing bit of inline heuristic that should look for enhanced promotion opportunities, but these are tricky to get right (success often requires a coordinated set of inlines).

@erozenfeld

Copy link
Copy Markdown
Contributor

I would expect some diffs to show eliminated zero-inits since we rely on BBF_BACKWARD_JUMP when deciding whether we can eliminate explicit zero initializations.

@erozenfeld

Copy link
Copy Markdown
Contributor

We can make this check even less aggressive if we take into account other conditions that currently block tail recursion --> loop optimization:

if (opts.compTailCallLoopOpt && canFastTailCall && gtIsRecursiveCall(call) && !lvaReportParamTypeArg() &&
!lvaKeepAliveAndReportThis() && !call->IsVirtual() && !hasStructParam && !varTypeIsStruct(call->TypeGet()))
{
fastTailCallToLoop = true;
}

printf("\n");
}
#endif
JITDUMP("\nGTF_CALL_M_EXPLICIT_TAILCALL set for call [%06u]\n", dspTreeID(call));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dspTreeID() really should be named getTreeID()

@briansullbriansull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Thanks for finding and fixing this

@AndyAyersMS

AndyAyersMS commented Mar 12, 2020

Copy link
Copy Markdown
MemberAuthor

I would expect some diffs to show eliminated zero-init

Was looking for diffs like this, but haven't spotted any. Will sample a few more. I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing. We have one for prolog size but it's not quite the same, and I think is broken.

We can make this check even less aggressive if we take into account other conditions

Sure, perhaps in a follow-on PR?

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

@erozenfeld

Copy link
Copy Markdown
Contributor

I've also been thinking we need a jit-diff metric for prolog zeroing, which might make it easier to see this sort of thing.

In this case the zero-init diffs will be in the main body, not in the prolog. This change should only affect elimination of explicit zero-inits.

I'm also a bit worried that we are setting BBF_BACKARD_JUMP in the middle of importation -- if we change this flag for an already imported block then we potentially have a latent bug in the imported code for that block. I suspect all the semantically important cases are handled by the pre-importation values, and the while-importing changes that add BBF_BACKARD_JUMP` only impact heuristics, but it would be good to be sure (perhaps, use two separate bits?).

Yes, I think we might have this problem at the place where we call fgVarNeedsExplicitZeroInit in importer.cpp (github doesn't let me create a link since the file is too long). If we add BBF_BACKWARD_JUMP to that block after we've made the decision to skip explicit zero-init and later turn the call into a loop, we may get a problem. This is somewhat mitigated by the code in fgMorphRecursiveFastTailCallIntoLoop (below) but it looks like we still have a hole. It certainly is not a problem for C# code but we can probably get a test case with il.

// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
// for all non-parameter IL locals as well as temp structs with GC fields.
// Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
continue;
}
#endif// FEATURE_FIXED_OUT_ARGS
if (!varDsc->lvIsParam)
{
var_types lclType = varDsc->TypeGet();
bool isUserLocal = (varNum < info.compLocalsCount);
bool structWithGCFields = ((lclType == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr());
if (isUserLocal || structWithGCFields)
{
GenTree* lcl = gtNewLclvNode(varNum, lclType);
GenTree* init = nullptr;
if (varTypeIsStruct(lclType))
{
constbool isVolatile = false;
constbool isCopyBlock = false;
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), isVolatile, isCopyBlock);
init = fgMorphInitBlock(init);
}
else
{
GenTree* zero = gtNewZeroConNode(genActualType(lclType));
init = gtNewAssignNode(lcl, zero);
}
Statement* initStmt = gtNewStmt(init, callILOffset);
fgInsertStmtBefore(block, lastStmt, initStmt);
}
}
}
}

Sure, perhaps in a follow-on PR?

Sounds good.

@AndyAyersMS
AndyAyersMS merged commit 84cf6a1 into dotnet:masterMar 12, 2020
@AndyAyersMS
AndyAyersMS deleted the FixRecursiveTailCallLoopMarking branch March 12, 2020 22:55
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Mar 12, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@AndyAyersMS@erozenfeld@lpereira@jashook@CarolEidt@briansull@Dotnet-GitSync-Bot