void PostOptimizeAssignment(JSBinaryOperatorExpression boe, JSExpression rhs) { var rhsCopy = rhs as JSStructCopyExpression; if (rhsCopy == null) { return; } var expr = rhsCopy.Struct; var rre = expr as JSResultReferenceExpression; if (rre != null) { expr = rre.Referent; } var invocation = expr as JSInvocationExpression; if (invocation == null) { return; } var invokeMethod = invocation.JSMethod; if (invokeMethod == null) { return; } var invocationSecondPass = GetSecondPass(invokeMethod); if (invocationSecondPass == null) { return; } bool eliminateCopy = false; string eliminateReason = null; if (invocationSecondPass.ResultIsNew) { // If this expression is the return value of a function invocation, we can eliminate struct // copies if the return value is a 'new' expression. eliminateCopy = true; eliminateReason = "Result is new"; } else if (invocationSecondPass.ResultVariable != null) { // We can also eliminate a return value copy if the return value is one of the function's // arguments, and we are sure that argument does not escape (other than through the return // statement, that is). var parameters = invokeMethod.Method.Parameters; int parameterIndex = -1; for (var i = 0; i < parameters.Length; i++) { if (parameters[i].Name != invocationSecondPass.ResultVariable) { continue; } parameterIndex = i; break; } // We found a single parameter that acts as the result of this function call. if (parameterIndex >= 0) { GenericParameter relevantParameter; var innerValue = invocation.Arguments[parameterIndex]; // Identify any local variables that are a dependency of the result parameter. var localVariableDependencies = StaticAnalyzer.ExtractInvolvedVariables( innerValue, (n) => // If a variable is inside a copy expression we can ignore it as a dependency. // The copy ensures that the dependency is resolved at the time of invocation. (n is JSStructCopyExpression) || // If a variable is inside an invocation expression we can ignore it as a dependency. // Dependency resolution would have occurred for the invocation already. (n is JSInvocationExpression) ); // Was the result parameter already copied when invoking the function? // If so, we can eliminate the copy of the result because all dependencies have been resolved. // Note that this is separate from the variable dependency extraction above - // if an invocation has multiple variable dependencies, the above merely narrows them down // while this detects cases where there are effectively *no* dependencies of any kind. // This also detects cases where the argument is the result of an invocation or property access // and the result was copied. var isAlreadyCopied = innerValue is JSStructCopyExpression; var dependenciesModified = localVariableDependencies.Any(iv => SecondPass.IsVariableModified(iv.Name)); var copyNeededForTarget = IsCopyNeededForAssignmentTarget(boe.Left); var escapes = invocationSecondPass.DoesVariableEscape(invocationSecondPass.ResultVariable, false); var modified = invocationSecondPass.IsVariableModified(invocationSecondPass.ResultVariable); var traceMessage = (Action)(() => { if (TracePostOptimizeDecisions) { Console.WriteLine( "< {0}: {1} > escapes:{2} modified:{3} copyNeededForTarget({4}):{5} inputsModified:{6}", parameters[parameterIndex].Name, innerValue, escapes, modified, boe.Left, copyNeededForTarget, dependenciesModified ); } }); if (isAlreadyCopied) { eliminateCopy = true; eliminateReason = "Result was already copied as part of argument list"; } else if (!escapes && !dependenciesModified) { if (!copyNeededForTarget) { eliminateCopy = true; eliminateReason = "Target does not require a copy and other criteria are met"; } else if (!modified) { eliminateCopy = true; eliminateReason = "Input not modified, doesn't escape, no dependency changes"; } traceMessage(); } else { traceMessage(); } } } if (eliminateCopy) { if (TracePostOptimizedCopies) { Console.WriteLine("Post-optimized assignment {0} because {1}", boe, eliminateReason); } boe.ReplaceChild(rhsCopy, rhsCopy.Struct); } }
public void VisitNode(JSBinaryOperatorExpression boe) { if (boe.Operator != JSOperator.Assignment) { base.VisitNode(boe); return; } bool doPostOptimization = false; GenericParameter relevantParameter; if (IsCopyNeeded(boe.Right, out relevantParameter)) { var rightVars = new HashSet <JSVariable>(StaticAnalyzer.ExtractInvolvedVariables(boe.Right)); // Even if the assignment target is never modified, if the assignment *source* // gets modified, we need to make a copy here, because the target is probably // being used as a back-up copy. var rightVarsModified = (rightVars.Any((rv) => SecondPass.IsVariableModified(rv.Name))); var rightVarsAreReferences = rightVars.Any((rv) => rv.IsReference); if ( ( rightVarsModified || IsCopyNeededForAssignmentTarget(boe.Left) || rightVarsAreReferences ) && !IsCopyAlwaysUnnecessaryForAssignmentTarget(boe.Left) ) { if (TraceInsertedCopies) { Console.WriteLine(String.Format("struct copy introduced for assignment {0} = {1}", boe.Left, boe.Right)); } doPostOptimization = true; boe.Right = MakeCopyForExpression(boe.Right, relevantParameter); } else { if (TraceElidedCopies) { Console.WriteLine(String.Format("struct copy elided for assignment {0} = {1}", boe.Left, boe.Right)); } } } else { if (TraceElidedCopies && TypeUtil.IsStruct(boe.Right.GetActualType(TypeSystem))) { Console.WriteLine(String.Format("no copy needed for assignment {0} = {1}", boe.Left, boe.Right)); } } VisitChildren(boe); if (doPostOptimization) { PostOptimizeAssignment(boe, boe.Right); } }