public void GetMethodInfo(MethodKey key, out Parameter[] parameters, out Local[] locals, out ILVariable[] decLocals) { parameters = null; locals = null; decLocals = null; foreach (var textView in MainWindow.Instance.AllTextViews) { if (parameters != null && decLocals != null) break; var cm = textView.CodeMappings; if (cm == null) continue; MemberMapping mapping; if (!cm.TryGetValue(key, out mapping)) continue; var method = mapping.MethodDefinition; if (mapping.LocalVariables != null && method.Body != null) { locals = method.Body.Variables.ToArray(); decLocals = new ILVariable[method.Body.Variables.Count]; foreach (var v in mapping.LocalVariables) { if (v.IsGenerated) continue; if (v.OriginalVariable == null) continue; if ((uint)v.OriginalVariable.Index >= decLocals.Length) continue; decLocals[v.OriginalVariable.Index] = v; } } parameters = method.Parameters.ToArray(); } }
void AssignNameToVariable(ILVariable varDef, IEnumerable<ILExpression> allExpressions) { string proposedName = null; foreach (ILExpression expr in allExpressions) { if (expr.Operand != varDef) continue; if (expr.Code == ILCode.Stloc) { proposedName = GetNameFromExpression(expr.Arguments.Single()); } if (proposedName != null) break; } if (proposedName == null) proposedName = GetNameByType(varDef.Type); if (!typeNames.ContainsKey(proposedName)) { typeNames.Add(proposedName, 0); } int count = ++typeNames[proposedName]; if (count > 1) { varDef.Name = proposedName + count.ToString(); } else { varDef.Name = proposedName; } }
public bool Get (ILVariable localVariable, out DynamicCallSiteInfo info) { FieldReference storageField; if (Aliases.TryGetValue(localVariable.Name, out storageField)) return Get(storageField, out info); info = null; return false; }
bool HasSingleLoad(ILVariable v) { int loads = 0; foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { if (expr.Operand == v) { if (expr.Code == ILCode.Ldloc) loads++; else if (expr.Code != ILCode.Stloc) return false; } } return loads == 1; }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> static bool? FindLoadInNext(ILExpression expr, ILVariable v, out ILExpression parent, out int pos) { parent = null; pos = 0; if (expr == null) return false; for (int i = 0; i < expr.Arguments.Count; i++) { ILExpression arg = expr.Arguments[i]; if (arg.Code == ILCode.Ldloc && arg.Operand == v) { parent = expr; pos = i; return true; } bool? r = FindLoadInNext(arg, v, out parent, out pos); if (r != null) return r; } return IsWithoutSideEffects(expr.Code) ? (bool?)null : false; }
/// <summary> /// Gets whether 'expressionBeingMoved' can be inlined into 'expr'. /// </summary> public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved) { ILExpression parent; int pos; return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true; }
bool IsPartOfInitializer(InstructionCollection <ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType, Dictionary <ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables)
/// finally BlockContainer { /// Block IL_0012(incoming: 1) { /// if (comp(ldloc obj != ldnull)) Block IL_001a { /// callvirt Dispose(obj) /// } /// leave IL_0012(nop) /// } /// } bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, in string disposeMethodFullName = "System.IDisposable.Dispose",
public ReplaceDelegateTargetVisitor(ILInstruction target, ILVariable thisVariable) { this.target = target; this.thisVariable = thisVariable; }
string GenerateNameForVariable(ILVariable variable, ILBlock methodBody) { string proposedName = null; if (variable.Type == context.CurrentType.Module.TypeSystem.Int32) { // test whether the variable might be a loop counter bool isLoopCounter = false; foreach (ILWhileLoop loop in methodBody.GetSelfAndChildrenRecursive <ILWhileLoop>()) { ILExpression expr = loop.Condition; while (expr != null && expr.Code == ILCode.LogicNot) { expr = expr.Arguments[0]; } if (expr != null) { switch (expr.Code) { case ILCode.Clt: case ILCode.Clt_Un: case ILCode.Cgt: case ILCode.Cgt_Un: case ILCode.Cle: case ILCode.Cle_Un: case ILCode.Cge: case ILCode.Cge_Un: ILVariable loadVar; if (expr.Arguments[0].Match(ILCode.Ldloc, out loadVar) && loadVar == variable) { isLoopCounter = true; } break; } } } if (isLoopCounter) { // For loop variables, use i,j,k,l,m,n for (char c = 'i'; c <= maxLoopVariableName; c++) { if (!typeNames.ContainsKey(c.ToString())) { proposedName = c.ToString(); break; } } } } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForStores = (from expr in methodBody.GetSelfAndChildrenRecursive <ILExpression>() where expr.Code == ILCode.Stloc && expr.Operand == variable select GetNameFromExpression(expr.Arguments.Single()) ).Except(fieldNamesInCurrentType).ToList(); if (proposedNameForStores.Count == 1) { proposedName = proposedNameForStores[0]; } } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForLoads = (from expr in methodBody.GetSelfAndChildrenRecursive <ILExpression>() from i in Enumerable.Range(0, expr.Arguments.Count) let arg = expr.Arguments[i] where arg.Code == ILCode.Ldloc && arg.Operand == variable select GetNameForArgument(expr, i) ).Except(fieldNamesInCurrentType).ToList(); if (proposedNameForLoads.Count == 1) { proposedName = proposedNameForLoads[0]; } } if (string.IsNullOrEmpty(proposedName)) { proposedName = GetNameByType(variable.Type); } // remove any numbers from the proposed name int number; proposedName = SplitName(proposedName, out number); if (!typeNames.ContainsKey(proposedName)) { typeNames.Add(proposedName, 0); } int count = ++typeNames[proposedName]; if (count > 1) { return(proposedName + count.ToString()); } else { return(proposedName); } }
static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection) { // Argument is of the form 'getter(getter(...(v)))' // Unpack it into a list of getters: List<ILExpression> getters = new List<ILExpression>(); while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.Ldfld) { getters.Add(argument); if (argument.Arguments.Count != 1) return false; argument = argument.Arguments[0]; } // Ensure that the final argument is 'v' if (!argument.MatchLdloc(v)) return false; // Now compare the getters with those that are currently active on the initializer stack: int i; for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) { ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer ILExpression g2 = getters[getters.Count - i]; // matching getter from argument if (g1.Operand != g2.Operand) { // operands differ, so we abort the comparison break; } } // Remove all initializers from the stack that were not matched with one from the argument: initializerStack.RemoveRange(i, initializerStack.Count - i); // Now create new initializers for the remaining arguments: for (; i <= getters.Count; i++) { ILExpression g = getters[getters.Count - i]; MemberReference mr = (MemberReference)g.Operand; TypeReference returnType; if (mr is FieldReference) returnType = TypeAnalysis.GetFieldType((FieldReference)mr); else returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr); ILExpression nestedInitializer = new ILExpression( IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, null, g); // add new initializer to its parent: ILExpression parentInitializer = initializerStack[initializerStack.Count - 1]; if (parentInitializer.Code == ILCode.InitCollection) { // can't add children to collection initializer if (parentInitializer.Arguments.Count == 1) { // convert empty collection initializer to object initializer parentInitializer.Code = ILCode.InitObject; } else { return false; } } parentInitializer.Arguments.Add(nestedInitializer); initializerStack.Add(nestedInitializer); } ILExpression lastInitializer = initializerStack[initializerStack.Count - 1]; if (isCollection) { return lastInitializer.Code == ILCode.InitCollection; } else { if (lastInitializer.Code == ILCode.InitCollection) { if (lastInitializer.Arguments.Count == 1) { // convert empty collection initializer to object initializer lastInitializer.Code = ILCode.InitObject; return true; } else { return false; } } else { return true; } } }
protected JSExpression Translate_Ldloca(ILExpression node, ILVariable variable) { return JSReferenceExpression.New( Translate_Ldloc(node, variable) ); }
/// <summary> /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="loadInst">The load instruction (a descendant within 'next')</param> /// <param name="v">The variable being inlined.</param> static bool IsGeneratedValueTypeTemporary(ILInstruction next, LdLoca loadInst, ILVariable v, ILInstruction inlinedExpression) { Debug.Assert(loadInst.Variable == v); // Inlining a value type variable is allowed only if the resulting code will maintain the semantics // that the method is operating on a copy. // Thus, we have to ensure we're operating on an r-value. // Additionally, we cannot inline in cases where the C# compiler prohibits the direct use // of the rvalue (e.g. M(ref (MyStruct)obj); is invalid). return(IsUsedAsThisPointerInCall(loadInst) && !IsLValue(inlinedExpression)); }
public bool IsPotentiallyUsedUninitialized(ILVariable v) { Debug.Assert(v.Function == scope); return(variablesWithUninitializedUsage[v.IndexInFunction]); }
private bool MatchThreeValuedLogicConditionPattern(ILInstruction condition, out ILVariable nullable1, out ILVariable nullable2) { // Try to match: nullable1.GetValueOrDefault() || (!nullable2.GetValueOrDefault() && !nullable1.HasValue) nullable1 = null; nullable2 = null; if (!condition.MatchLogicOr(out var lhs, out var rhs)) { return(false); } if (!MatchGetValueOrDefault(lhs, out nullable1)) { return(false); } if (!NullableType.GetUnderlyingType(nullable1.Type).IsKnownType(KnownTypeCode.Boolean)) { return(false); } if (!rhs.MatchLogicAnd(out lhs, out rhs)) { return(false); } if (!lhs.MatchLogicNot(out var arg)) { return(false); } if (!MatchGetValueOrDefault(arg, out nullable2)) { return(false); } if (!NullableType.GetUnderlyingType(nullable2.Type).IsKnownType(KnownTypeCode.Boolean)) { return(false); } if (!rhs.MatchLogicNot(out arg)) { return(false); } return(MatchHasValueCall(arg, nullable1)); }
void EnsureVariableNameIsAvailable(AstNode currentNode, string name) { int pos = currentlyUsedVariableNames.IndexOf(name); if (pos < 0) { // name is still available return; } // Naming conflict. Let's rename the existing variable so that the field keeps the name from metadata. NameVariables nv = new NameVariables(); // Add currently used variable and parameter names foreach (string nameInUse in currentlyUsedVariableNames) { nv.AddExistingName(nameInUse); } // variables declared in child nodes of this block foreach (VariableInitializer vi in currentNode.Descendants.OfType <VariableInitializer>()) { nv.AddExistingName(vi.Name); } // parameters in child lambdas foreach (ParameterDeclaration pd in currentNode.Descendants.OfType <ParameterDeclaration>()) { nv.AddExistingName(pd.Name); } string newName = nv.GetAlternativeName(name); currentlyUsedVariableNames[pos] = newName; // find top-most block AstNode topMostBlock = currentNode.Ancestors.OfType <BlockStatement>().LastOrDefault() ?? currentNode; // rename identifiers foreach (IdentifierExpression ident in topMostBlock.Descendants.OfType <IdentifierExpression>()) { if (ident.Identifier == name) { ident.Identifier = newName; ILVariable v = ident.Annotation <ILVariable>(); if (v != null) { v.Name = newName; } } } // rename variable declarations foreach (VariableInitializer vi in topMostBlock.Descendants.OfType <VariableInitializer>()) { if (vi.Name == name) { vi.Name = newName; ILVariable v = vi.Annotation <ILVariable>(); if (v != null) { v.Name = newName; } } } }
public override object VisitBlockStatement(BlockStatement blockStatement, object data) { int numberOfVariablesOutsideBlock = currentlyUsedVariableNames.Count; base.VisitBlockStatement(blockStatement, data); foreach (ExpressionStatement stmt in blockStatement.Statements.OfType <ExpressionStatement>().ToArray()) { Match displayClassAssignmentMatch = displayClassAssignmentPattern.Match(stmt); if (!displayClassAssignmentMatch.Success) { continue; } ILVariable variable = displayClassAssignmentMatch.Get <AstNode>("variable").Single().Annotation <ILVariable>(); if (variable == null) { continue; } TypeDefinition type = variable.Type.ResolveWithinSameModule(); if (!IsPotentialClosure(context, type)) { continue; } if (displayClassAssignmentMatch.Get <AstType>("type").Single().Annotation <TypeReference>().ResolveWithinSameModule() != type) { continue; } // Looks like we found a display class creation. Now let's verify that the variable is used only for field accesses: bool ok = true; foreach (var identExpr in blockStatement.Descendants.OfType <IdentifierExpression>()) { if (identExpr.Identifier == variable.Name && identExpr != displayClassAssignmentMatch.Get("variable").Single()) { if (!(identExpr.Parent is MemberReferenceExpression && identExpr.Parent.Annotation <FieldReference>() != null)) { ok = false; } } } if (!ok) { continue; } Dictionary <FieldReference, AstNode> dict = new Dictionary <FieldReference, AstNode>(); // Delete the variable declaration statement: VariableDeclarationStatement displayClassVarDecl = PatternStatementTransform.FindVariableDeclaration(stmt, variable.Name); if (displayClassVarDecl != null) { displayClassVarDecl.Remove(); } // Delete the assignment statement: AstNode cur = stmt.NextSibling; stmt.Remove(); // Delete any following statements as long as they assign parameters to the display class BlockStatement rootBlock = blockStatement.Ancestors.OfType <BlockStatement>().LastOrDefault() ?? blockStatement; List <ILVariable> parameterOccurrances = rootBlock.Descendants.OfType <IdentifierExpression>() .Select(n => n.Annotation <ILVariable>()).Where(p => p != null && p.IsParameter).ToList(); AstNode next; for (; cur != null; cur = next) { next = cur.NextSibling; // Test for the pattern: // "variableName.MemberName = right;" ExpressionStatement closureFieldAssignmentPattern = new ExpressionStatement( new AssignmentExpression( new NamedNode("left", new MemberReferenceExpression { Target = new IdentifierExpression(variable.Name), MemberName = Pattern.AnyString }), new AnyNode("right") ) ); Match m = closureFieldAssignmentPattern.Match(cur); if (m.Success) { FieldDefinition fieldDef = m.Get <MemberReferenceExpression>("left").Single().Annotation <FieldReference>().ResolveWithinSameModule(); AstNode right = m.Get <AstNode>("right").Single(); bool isParameter = false; bool isDisplayClassParentPointerAssignment = false; if (right is ThisReferenceExpression) { isParameter = true; } else if (right is IdentifierExpression) { // handle parameters only if the whole method contains no other occurrence except for 'right' ILVariable v = right.Annotation <ILVariable>(); isParameter = v.IsParameter && parameterOccurrances.Count(c => c == v) == 1; if (!isParameter && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) { // parent display class within the same method // (closure2.localsX = closure1;) isDisplayClassParentPointerAssignment = true; } } else if (right is MemberReferenceExpression) { // copy of parent display class reference from an outer lambda // closure2.localsX = this.localsY MemberReferenceExpression mre = m.Get <MemberReferenceExpression>("right").Single(); do { // descend into the targets of the mre as long as the field types are closures FieldDefinition fieldDef2 = mre.Annotation <FieldReference>().ResolveWithinSameModule(); if (fieldDef2 == null || !IsPotentialClosure(context, fieldDef2.FieldType.ResolveWithinSameModule())) { break; } // if we finally get to a this reference, it's copying a display class parent pointer if (mre.Target is ThisReferenceExpression) { isDisplayClassParentPointerAssignment = true; } mre = mre.Target as MemberReferenceExpression; } while (mre != null); } if (isParameter || isDisplayClassParentPointerAssignment) { dict[fieldDef] = right; cur.Remove(); } else { break; } } else { break; } } // Now create variables for all fields of the display class (except for those that we already handled as parameters) List <Tuple <AstType, ILVariable> > variablesToDeclare = new List <Tuple <AstType, ILVariable> >(); foreach (FieldDefinition field in type.Fields) { if (field.IsStatic) { continue; // skip static fields } if (dict.ContainsKey(field)) // skip field if it already was handled as parameter { continue; } string capturedVariableName = field.Name; if (capturedVariableName.StartsWith("$VB$Local_", StringComparison.Ordinal) && capturedVariableName.Length > 10) { capturedVariableName = capturedVariableName.Substring(10); } EnsureVariableNameIsAvailable(blockStatement, capturedVariableName); currentlyUsedVariableNames.Add(capturedVariableName); ILVariable ilVar = new ILVariable { IsGenerated = true, Name = capturedVariableName, Type = field.FieldType, }; variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), ilVar)); dict[field] = new IdentifierExpression(capturedVariableName).WithAnnotation(ilVar); } // Now figure out where the closure was accessed and use the simpler replacement expression there: foreach (var identExpr in blockStatement.Descendants.OfType <IdentifierExpression>()) { if (identExpr.Identifier == variable.Name) { MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent; AstNode replacement; if (dict.TryGetValue(mre.Annotation <FieldReference>().ResolveWithinSameModule(), out replacement)) { mre.ReplaceWith(replacement.Clone()); } } } // Now insert the variable declarations (we can do this after the replacements only so that the scope detection works): Statement insertionPoint = blockStatement.Statements.FirstOrDefault(); foreach (var tuple in variablesToDeclare) { var newVarDecl = new VariableDeclarationStatement(tuple.Item1, tuple.Item2.Name); newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation()); newVarDecl.Variables.Single().AddAnnotation(tuple.Item2); blockStatement.Statements.InsertBefore(insertionPoint, newVarDecl); } } currentlyUsedVariableNames.RemoveRange(numberOfVariablesOutsideBlock, currentlyUsedVariableNames.Count - numberOfVariablesOutsideBlock); return(null); }
public static JSClosureVariable New(ILVariable variable, MethodReference function) { return(new JSClosureVariable(variable.Name, variable.Type, function)); }
/// <summary> /// Determines whether a variable was merged with other variables. /// </summary> public bool WasMerged(ILVariable variable) { VariableToDeclare v = variableDict[variable]; return(v.InvolvedInCollision || v.RemovedDueToCollision); }
/// <summary> /// Runs a very simple form of copy propagation. /// Copy propagation is used in two cases: /// 1) assignments from arguments to local variables /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg), /// then we can replace the variable with the argument. /// 2) assignments of address-loading instructions to local variables /// </summary> public void CopyPropagation(List<ILNode> newList) { var newListTemp = newList; method.GetSelfAndChildrenRecursive<ILNode>(newList); bool recalc = false; foreach (var node1 in newList) { var block = node1 as ILBlock; if (block == null) continue; for (int i = 0; i < block.Body.Count; i++) { ILVariable v; ILExpression copiedExpr; if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr) && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 && CanPerformCopyPropagation(copiedExpr, v)) { // un-inline the arguments of the ldArg instruction ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count]; for (int j = 0; j < uninlinedArgs.Length; j++) { uninlinedArgs[j] = new ILVariable { GeneratedByDecompiler = true, Name = v.Name + "_cp_" + j }; block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j])); recalc = true; } // perform copy propagation: foreach (var node2 in newListTemp) { var expr = node2 as ILExpression; if (expr == null) continue; if (expr.Code == ILCode.Ldloc && expr.Operand == v) { expr.Code = copiedExpr.Code; expr.Operand = copiedExpr.Operand; for (int j = 0; j < uninlinedArgs.Length; j++) { expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j])); } } } if (context.CalculateILRanges) { Utils.AddILRanges(block, block.Body, i, block.Body[i].ILRanges); Utils.AddILRanges(block, block.Body, i, copiedExpr.ILRanges); } block.Body.RemoveAt(i); if (uninlinedArgs.Length > 0) { // if we un-inlined stuff; we need to update the usage counters AnalyzeMethod(); } InlineInto(block, block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i] i -= uninlinedArgs.Length + 1; if (recalc) { recalc = false; newListTemp = method.GetSelfAndChildrenRecursive<ILNode>(newListTemp == newList ? (newListTemp = list_ILNode) : newListTemp); } } } } }
/// <summary> /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable. /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="loadInst">The load within 'next'</param> /// <param name="inlinedExpression">The expression being inlined</param> static bool NonAggressiveInlineInto(ILInstruction next, ILInstruction loadInst, ILInstruction inlinedExpression, ILVariable v) { Debug.Assert(loadInst.IsDescendantOf(next)); // decide based on the source expression being inlined switch (inlinedExpression.OpCode) { case OpCode.DefaultValue: case OpCode.StObj: case OpCode.CompoundAssignmentInstruction: case OpCode.Await: return(true); case OpCode.LdLoc: if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null) { // Roslyn likes to put the result of fetching a state machine field into a temporary variable, // so inline more aggressively in such cases. return(true); } break; } var parent = loadInst.Parent; if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _)) { // inline into nullable ctor call in lifted operator parent = parent.Parent; } if (parent is ILiftableInstruction liftable && liftable.IsLifted) { return(true); // inline into lifted operators } if (parent is NullCoalescingInstruction && NullableType.IsNullable(v.Type)) { return(true); // inline nullables into ?? operator } // decide based on the target into which we are inlining switch (next.OpCode) { case OpCode.Leave: return(parent == next); case OpCode.IfInstruction: while (parent.MatchLogicNot(out _)) { parent = parent.Parent; } return(parent == next); case OpCode.BlockContainer: if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst) { next = switchInst; goto case OpCode.SwitchInstruction; } else { return(false); } case OpCode.SwitchInstruction: return(parent == next || (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next)); default: return(false); } }
/// <summary> /// Gets whether 'expressionBeingMoved' can be inlined into 'expr'. /// </summary> public static bool CanInlineInto(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved) { ILInstruction loadInst; return(FindLoadInNext(expr, v, expressionBeingMoved, out loadInst) == true); }
protected JSVariable DeclareVariable(ILVariable variable, MethodReference function) { return DeclareVariable(JSVariable.New(variable, function)); }
bool DoTransform(Block body, int pos) { ILInstruction inst = body.Instructions[pos]; // Match stloc(v, newobj) if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot)) { IType instType; switch (initInst) { case NewObj newObjInst: if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor && !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { // on statement level (no other expressions on IL stack), // prefer to keep local variables (but not stack slots), // unless we are in a constructor (where inlining object initializers might be critical // for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression. return(false); } // Do not try to transform display class usages or delegate construction. // DelegateConstruction transform cannot deal with this. if (DelegateConstruction.IsSimpleDisplayClass(newObjInst.Method.DeclaringType)) { return(false); } if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst)) { return(false); } instType = newObjInst.Method.DeclaringType; break; case DefaultValue defaultVal: if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor) { // on statement level (no other expressions on IL stack), // prefer to keep local variables (but not stack slots), // unless we are in a constructor (where inlining object initializers might be critical // for the base ctor call) return(false); } instType = defaultVal.Type; break; default: return(false); } int initializerItemsCount = 0; var blockKind = BlockKind.CollectionInitializer; possibleIndexVariables = new Dictionary <ILVariable, (int Index, ILInstruction Value)>(); currentPath = new List <AccessPathElement>(); isCollection = false; pathStack = new Stack <HashSet <AccessPathElement> >(); pathStack.Push(new HashSet <AccessPathElement>()); // Detect initializer type by scanning the following statements // each must be a callvirt with ldloc v as first argument // if the method is a setter we're dealing with an object initializer // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer while (pos + initializerItemsCount + 1 < body.Instructions.Count && IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind)) { initializerItemsCount++; } // Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable // directly after the possible initializer. if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v)) { return(false); } // Calculate the correct number of statements inside the initializer: // All index variables that were used in the initializer have Index set to -1. // We fetch the first unused variable from the list and remove all instructions after its // first usage (i.e. the init store) from the initializer. var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index); if (index != null) { initializerItemsCount = index.Value - pos - 1; } // The initializer would be empty, there's nothing to do here. if (initializerItemsCount <= 0) { return(false); } context.Step("CollectionOrObjectInitializer", inst); // Create a new block and final slot (initializer target variable) var initializerBlock = new Block(blockKind); ILVariable finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); initializerBlock.FinalInstruction = new LdLoc(finalSlot); initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone())); // Move all instructions to the initializer block. for (int i = 1; i <= initializerItemsCount; i++) { switch (body.Instructions[i + pos]) { case CallInstruction call: if (!(call is CallVirt || call is Call)) { continue; } var newCall = call; var newTarget = newCall.Arguments[0]; foreach (var load in newTarget.Descendants.OfType <IInstructionWithVariableOperand>()) { if ((load is LdLoc || load is LdLoca) && load.Variable == v) { load.Variable = finalSlot; } } initializerBlock.Instructions.Add(newCall); break; case StObj stObj: var newStObj = stObj; foreach (var load in newStObj.Target.Descendants.OfType <IInstructionWithVariableOperand>()) { if ((load is LdLoc || load is LdLoca) && load.Variable == v) { load.Variable = finalSlot; } } initializerBlock.Instructions.Add(newStObj); break; case StLoc stLoc: var newStLoc = stLoc; initializerBlock.Instructions.Add(newStLoc); break; } } initInst.ReplaceWith(initializerBlock); body.Instructions.RemoveRange(pos + 1, initializerItemsCount); ILInlining.InlineIfPossible(body, pos, context); } return(true); }
static bool Capture(ref ILVariable pmvar, ILVariable v) { if (pmvar != null) return pmvar == v; pmvar = v; return true; }
/// <summary> /// Initializes the state range logic: /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) /// </summary> void InitStateRanges(ILNode entryPoint) { ranges = new DefaultDictionary<ILNode, StateRange>(n => new StateRange()); ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); rangeAnalysisStateVariable = null; }
List<ILNode> ConvertToAst(List<ByteCode> body) { List<ILNode> ast = new List<ILNode>(); // Convert stack-based IL code to ILAst tree foreach(ByteCode byteCode in body) { OpCode opCode = byteCode.OpCode; object operand = byteCode.Operand; MethodBodyRocks.ExpandMacro(ref opCode, ref operand, methodDef.Body); ILExpression expr = new ILExpression(opCode, operand); expr.ILRanges.Add(new ILRange() { From = byteCode.Offset, To = byteCode.EndOffset }); // Label for this instruction if (byteCode.Label != null) { ast.Add(byteCode.Label); } // Reference arguments using temporary variables int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { StackSlot slot = byteCode.StackBefore[i]; if (slot.PushedBy != null) { ILExpression ldExpr = new ILExpression(OpCodes.Ldloc, slot.LoadFrom); expr.Arguments.Add(ldExpr); } else { ILExpression ldExpr = new ILExpression(OpCodes.Ldloc, new ILVariable() { Name = "ex", IsGenerated = true }); expr.Arguments.Add(ldExpr); } } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new ILExpression(OpCodes.Stloc, byteCode.StoreTo[0], expr)); } else { ILVariable tmpVar = new ILVariable() { Name = "expr_" + byteCode.Offset.ToString("X2"), IsGenerated = true }; ast.Add(new ILExpression(OpCodes.Stloc, tmpVar, expr)); foreach(ILVariable storeTo in byteCode.StoreTo) { ast.Add(new ILExpression(OpCodes.Stloc, storeTo, new ILExpression(OpCodes.Ldloc, tmpVar))); } } } // Try to in-line stloc / ldloc pairs for(int i = 0; i < ast.Count - 1; i++) { if (i < 0) continue; ILExpression currExpr = ast[i] as ILExpression; ILExpression nextExpr = ast[i + 1] as ILExpression; if (currExpr != null && nextExpr != null && currExpr.OpCode.Code == Code.Stloc) { // If the next expression is generated stloc, look inside if (nextExpr.OpCode.Code == Code.Stloc && ((ILVariable)nextExpr.Operand).IsGenerated) { nextExpr = nextExpr.Arguments[0]; } // Find the use of the 'expr' for(int j = 0; j < nextExpr.Arguments.Count; j++) { ILExpression arg = nextExpr.Arguments[j]; // We are moving the expression evaluation past the other aguments. // It is ok to pass ldloc because the expression can not contain stloc and thus the ldcoc will still return the same value if (arg.OpCode.Code == Code.Ldloc) { bool canInline; allowInline.TryGetValue((ILVariable)arg.Operand, out canInline); if (arg.Operand == currExpr.Operand && canInline) { // Assigne the ranges for optimized away instrustions somewhere currExpr.Arguments[0].ILRanges.AddRange(currExpr.ILRanges); currExpr.Arguments[0].ILRanges.AddRange(nextExpr.Arguments[j].ILRanges); ast.RemoveAt(i); nextExpr.Arguments[j] = currExpr.Arguments[0]; // Inline the stloc body i -= 2; // Try the same index again break; // Found } } else { break; // Side-effects } } } } return ast; }
void AnalyzeMoveNext() { MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); ILBlock ilMethod = CreateILAst(moveNextMethod); if (ilMethod.Body.Count == 0) throw new YieldAnalysisFailedException(); ILExpression lastReturnArg; if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) throw new YieldAnalysisFailedException(); // There are two possibilities: if (lastReturnArg.Code == ILCode.Ldloc) { // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks) returnVariable = (ILVariable)lastReturnArg.Operand; returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; if (returnLabel == null) throw new YieldAnalysisFailedException(); } else { // b) the compiler directly returns constants returnVariable = null; returnLabel = null; // In this case, the last return must return false. if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0) throw new YieldAnalysisFailedException(); } ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; List<ILNode> body; int bodyLength; if (tryFaultBlock != null) { // there are try-finally blocks if (returnVariable == null) // in this case, we must use a return variable throw new YieldAnalysisFailedException(); // must be a try-fault block: if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) throw new YieldAnalysisFailedException(); ILBlock faultBlock = tryFaultBlock.FaultBlock; // Ensure the fault block contains the call to Dispose(). if (faultBlock.Body.Count != 2) throw new YieldAnalysisFailedException(); MethodReference disposeMethodRef; ILExpression disposeArg; if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) throw new YieldAnalysisFailedException(); if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !LoadFromArgument.This.Match(disposeArg)) throw new YieldAnalysisFailedException(); if (!faultBlock.Body[1].Match(ILCode.Endfinally)) throw new YieldAnalysisFailedException(); body = tryFaultBlock.TryBlock.Body; bodyLength = body.Count; } else { // no try-finally blocks body = ilMethod.Body; if (returnVariable == null) bodyLength = body.Count - 1; // all except for the return statement else bodyLength = body.Count - 2; // all except for the return label and statement } // Now verify that the last instruction in the body is 'ret(false)' if (returnVariable != null) { // If we don't have a return variable, we already verified that above. // If we do have one, check for 'stloc(returnVariable, ldc.i4(0))' // Maybe might be a jump to the return label after the stloc: ILExpression leave = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; if (leave != null && (leave.Code == ILCode.Br || leave.Code == ILCode.Leave) && leave.Operand == returnLabel) bodyLength--; ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) throw new YieldAnalysisFailedException(); if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) throw new YieldAnalysisFailedException(); bodyLength--; // don't conside the stloc instruction to be part of the body } // verify that the last element in the body is a label pointing to the 'ret(false)' returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; if (returnFalseLabel == null) throw new YieldAnalysisFailedException(); InitStateRanges(body[0]); int pos = AssignStateRanges(body, bodyLength, forDispose: false); if (pos > 0 && body[pos - 1] is ILLabel) { pos--; } else { // ensure that the first element at body[pos] is a label: ILLabel newLabel = new ILLabel(); newLabel.Name = "YieldReturnEntryPoint"; ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] body.Insert(pos, newLabel); bodyLength++; } List<KeyValuePair<ILLabel, StateRange>> labels = new List<KeyValuePair<ILLabel, StateRange>>(); for (int i = pos; i < bodyLength; i++) { ILLabel label = body[i] as ILLabel; if (label != null) { labels.Add(new KeyValuePair<ILLabel, StateRange>(label, ranges[label])); } } ConvertBody(body, pos, bodyLength, labels); }
bool MatchFixedInitializer(List<ILNode> body, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos) { if (body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) { initValue = (ILExpression)body[i]; nextPos = i + 1; HandleStringFixing(pinnedVar, body, ref nextPos, ref initValue); return true; } ILCondition ifStmt = body[i] as ILCondition; ILExpression arrayLoadingExpr; if (ifStmt != null && MatchFixedArrayInitializerCondition(ifStmt.Condition, out arrayLoadingExpr)) { ILVariable arrayVariable = (ILVariable)arrayLoadingExpr.Operand; ILExpression trueValue; if (ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue) && pinnedVar.IsPinned && IsNullOrZero(trueValue)) { if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) { ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0]; ILVariable stlocVar; ILExpression falseValue; if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0 && fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar) { ILVariable loadedVariable; if (falseValue.Code == ILCode.Ldelema && falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable && IsNullOrZero(falseValue.Arguments[1])) { // OK, we detected the pattern for fixing an array. // Now check whether the loading expression was a store ot a temp. var // that can be eliminated. if (arrayLoadingExpr.Code == ILCode.Stloc) { ILInlining inlining = new ILInlining(method); if (inlining.numLdloc.GetOrDefault(arrayVariable) == 2 && inlining.numStloc.GetOrDefault(arrayVariable) == 1 && inlining.numLdloca.GetOrDefault(arrayVariable) == 0) { arrayLoadingExpr = arrayLoadingExpr.Arguments[0]; } } initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr); nextPos = i + 1; return true; } } } } } initValue = null; nextPos = -1; return false; }
/// <summary> /// Inlines 'expr' into 'next', if possible. /// </summary> bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive) { // ensure the variable is accessed only a single time if (numStloc.GetOrDefault(v) != 1) return false; int ldloc = numLdloc.GetOrDefault(v); if (ldloc > 1 || ldloc + numLdloca.GetOrDefault(v) != 1) return false; if (next is ILCondition) next = ((ILCondition)next).Condition; else if (next is ILWhileLoop) next = ((ILWhileLoop)next).Condition; ILExpression parent; int pos; if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) { if (ldloc == 0) { if (!IsGeneratedValueTypeTemporary((ILExpression)next, parent, pos, v, inlinedExpression)) return false; } else { if (!aggressive && !v.GeneratedByDecompiler && !NonAggressiveInlineInto((ILExpression)next, parent, inlinedExpression)) return false; } // Assign the ranges of the ldloc instruction: if (context.CalculateILRanges) parent.Arguments[pos].AddSelfAndChildrenRecursiveILRanges(inlinedExpression.ILRanges); if (ldloc == 0) { // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' so that the types // comes out correctly parent.Arguments[pos] = new ILExpression(ILCode.AddressOf, null, inlinedExpression); } else { parent.Arguments[pos] = inlinedExpression; } return true; } return false; }
bool HandleJaggedArrayInitializer(Block block, int pos, ILVariable store, int length, out ILVariable finalStore, out ILInstruction[] values, out int instructionsToRemove) { instructionsToRemove = 0; finalStore = null; values = new ILInstruction[length]; ILInstruction initializer; for (int i = 0; i < length; i++) { ILVariable temp; ILInstruction storeLoad; // 1. Instruction: (optional) temporary copy of store bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out temp, out storeLoad) && storeLoad.MatchLdLoc(store); if (hasTemporaryCopy) { if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer)) { return(false); } } else { if (!MatchJaggedArrayStore(block, pos, store, i, out initializer)) { return(false); } } values[i] = initializer; int inc = hasTemporaryCopy ? 3 : 2; pos += inc; instructionsToRemove += inc; } ILInstruction array; // In case there is an extra copy of the store variable // Remove it and use its value instead. if (block.Instructions[pos].MatchStLoc(out finalStore, out array)) { instructionsToRemove++; return(array.MatchLdLoc(store)); } finalStore = store; return(true); }
Expression Convert(Expression expr) { InvocationExpression invocation = expr as InvocationExpression; if (invocation != null) { MethodReference mr = invocation.Annotation <MethodReference>(); if (mr != null && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression") { switch (mr.Name) { case "Add": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Add, false)); case "AddChecked": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Add, true)); case "AddAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Add, false)); case "AddAssignChecked": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Add, true)); case "And": return(ConvertBinaryOperator(invocation, BinaryOperatorType.BitwiseAnd)); case "AndAlso": return(ConvertBinaryOperator(invocation, BinaryOperatorType.ConditionalAnd)); case "AndAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.BitwiseAnd)); case "ArrayAccess": case "ArrayIndex": return(ConvertArrayIndex(invocation)); case "ArrayLength": return(ConvertArrayLength(invocation)); case "Assign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Assign)); case "Call": return(ConvertCall(invocation)); case "Coalesce": return(ConvertBinaryOperator(invocation, BinaryOperatorType.NullCoalescing)); case "Condition": return(ConvertCondition(invocation)); case "Constant": if (invocation.Arguments.Count >= 1) { return(invocation.Arguments.First().Clone()); } else { return(NotSupported(expr)); } case "Convert": return(ConvertCast(invocation, false)); case "ConvertChecked": return(ConvertCast(invocation, true)); case "Divide": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Divide)); case "DivideAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Divide)); case "Equal": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Equality)); case "ExclusiveOr": return(ConvertBinaryOperator(invocation, BinaryOperatorType.ExclusiveOr)); case "ExclusiveOrAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.ExclusiveOr)); case "Field": return(ConvertField(invocation)); case "GreaterThan": return(ConvertBinaryOperator(invocation, BinaryOperatorType.GreaterThan)); case "GreaterThanOrEqual": return(ConvertBinaryOperator(invocation, BinaryOperatorType.GreaterThanOrEqual)); case "Invoke": return(ConvertInvoke(invocation)); case "Lambda": return(ConvertLambda(invocation)); case "LeftShift": return(ConvertBinaryOperator(invocation, BinaryOperatorType.ShiftLeft)); case "LeftShiftAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.ShiftLeft)); case "LessThan": return(ConvertBinaryOperator(invocation, BinaryOperatorType.LessThan)); case "LessThanOrEqual": return(ConvertBinaryOperator(invocation, BinaryOperatorType.LessThanOrEqual)); case "ListInit": return(ConvertListInit(invocation)); case "MemberInit": return(ConvertMemberInit(invocation)); case "Modulo": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Modulus)); case "ModuloAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Modulus)); case "Multiply": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Multiply, false)); case "MultiplyChecked": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Multiply, true)); case "MultiplyAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Multiply, false)); case "MultiplyAssignChecked": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Multiply, true)); case "Negate": return(ConvertUnaryOperator(invocation, UnaryOperatorType.Minus, false)); case "NegateChecked": return(ConvertUnaryOperator(invocation, UnaryOperatorType.Minus, true)); case "New": return(ConvertNewObject(invocation)); case "NewArrayBounds": return(ConvertNewArrayBounds(invocation)); case "NewArrayInit": return(ConvertNewArrayInit(invocation)); case "Not": return(ConvertUnaryOperator(invocation, UnaryOperatorType.Not)); case "NotEqual": return(ConvertBinaryOperator(invocation, BinaryOperatorType.InEquality)); case "OnesComplement": return(ConvertUnaryOperator(invocation, UnaryOperatorType.BitNot)); case "Or": return(ConvertBinaryOperator(invocation, BinaryOperatorType.BitwiseOr)); case "OrAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.BitwiseOr)); case "OrElse": return(ConvertBinaryOperator(invocation, BinaryOperatorType.ConditionalOr)); case "Property": return(ConvertProperty(invocation)); case "Quote": if (invocation.Arguments.Count == 1) { return(Convert(invocation.Arguments.Single())); } else { return(NotSupported(invocation)); } case "RightShift": return(ConvertBinaryOperator(invocation, BinaryOperatorType.ShiftRight)); case "RightShiftAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.ShiftRight)); case "Subtract": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Subtract, false)); case "SubtractChecked": return(ConvertBinaryOperator(invocation, BinaryOperatorType.Subtract, true)); case "SubtractAssign": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Subtract, false)); case "SubtractAssignChecked": return(ConvertAssignmentOperator(invocation, AssignmentOperatorType.Subtract, true)); case "TypeAs": return(ConvertTypeAs(invocation)); case "TypeIs": return(ConvertTypeIs(invocation)); } } } IdentifierExpression ident = expr as IdentifierExpression; if (ident != null) { ILVariable v = ident.Annotation <ILVariable>(); if (v != null) { foreach (LambdaExpression lambda in activeLambdas) { foreach (ParameterDeclaration p in lambda.Parameters) { if (p.Annotation <ILVariable>() == v) { return(new IdentifierExpression(p.Name).WithAnnotation(v)); } } } } } return(NotSupported(expr)); }
int AssignStateRanges(List<ILNode> body, int bodyLength, bool forDispose) { if (bodyLength == 0) return 0; for (int i = 0; i < bodyLength; i++) { StateRange nodeRange = ranges[body[i]]; nodeRange.Simplify(); ILLabel label = body[i] as ILLabel; if (label != null) { ranges[body[i + 1]].UnionWith(nodeRange); continue; } ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; if (tryFinally != null) { if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) throw new YieldAnalysisFailedException(); ranges[tryFinally.TryBlock].UnionWith(nodeRange); AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose); continue; } ILExpression expr = body[i] as ILExpression; if (expr == null) throw new YieldAnalysisFailedException(); switch (expr.Code) { case ILCode.Switch: { SymbolicValue val = Eval(expr.Arguments[0]); if (val.Type != SymbolicValueType.State) throw new YieldAnalysisFailedException(); ILLabel[] targetLabels = (ILLabel[])expr.Operand; for (int j = 0; j < targetLabels.Length; j++) { int state = j - val.Constant; ranges[targetLabels[j]].UnionWith(nodeRange, state, state); } StateRange nextRange = ranges[body[i + 1]]; nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); break; } case ILCode.Br: case ILCode.Leave: ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); break; case ILCode.Brtrue: { SymbolicValue val = Eval(expr.Arguments[0]); if (val.Type == SymbolicValueType.StateEquals) { ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); StateRange nextRange = ranges[body[i + 1]]; nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); } else if (val.Type == SymbolicValueType.StateInEquals) { ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); StateRange targetRange = ranges[(ILLabel)expr.Operand]; targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); } else { throw new YieldAnalysisFailedException(); } break; } case ILCode.Nop: ranges[body[i + 1]].UnionWith(nodeRange); break; case ILCode.Ret: break; case ILCode.Stloc: { SymbolicValue val = Eval(expr.Arguments[0]); if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null) rangeAnalysisStateVariable = (ILVariable)expr.Operand; else throw new YieldAnalysisFailedException(); goto case ILCode.Nop; } case ILCode.Call: // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks if (forDispose) { MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference); if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) throw new YieldAnalysisFailedException(); finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); } else { throw new YieldAnalysisFailedException(); } break; default: if (forDispose) throw new YieldAnalysisFailedException(); else return i; } } return bodyLength; }
internal static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, ILInstruction expressionBeingMoved) { Debug.Assert(child.Parent == call); if (call.IsInstanceCall && child.ChildIndex == 0) { return(FindResult.Stop); // cannot use named arg to move expressionBeingMoved before this pointer } if (call.Method.IsOperator || call.Method.IsAccessor) { return(FindResult.Stop); // cannot use named arg for operators or accessors } if (call.Method is VarArgInstanceMethod) { return(FindResult.Stop); // CallBuilder doesn't support named args when using varargs } if (call.Method.IsConstructor) { IType type = call.Method.DeclaringType; if (type.Kind == TypeKind.Delegate || type.IsAnonymousType()) { return(FindResult.Stop); } } if (call.Method.Parameters.Any(p => string.IsNullOrEmpty(p.Name))) { return(FindResult.Stop); // cannot use named arguments } for (int i = child.ChildIndex; i < call.Arguments.Count; i++) { var r = ILInlining.FindLoadInNext(call.Arguments[i], v, expressionBeingMoved, InliningOptions.None); if (r.Type == FindResultType.Found) { return(FindResult.NamedArgument(r.LoadInst, call.Arguments[i])); } } return(FindResult.Stop); }
void AnalyzeMoveNext() { MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); ILBlock ilMethod = CreateILAst(moveNextMethod); if (ilMethod.Body.Count == 0) throw new SymbolicAnalysisFailedException(); ILExpression lastReturnArg; if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) throw new SymbolicAnalysisFailedException(); // There are two possibilities: if (lastReturnArg.Code == ILCode.Ldloc) { // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks) returnVariable = (ILVariable)lastReturnArg.Operand; returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; if (returnLabel == null) throw new SymbolicAnalysisFailedException(); } else { // b) the compiler directly returns constants returnVariable = null; returnLabel = null; // In this case, the last return must return false. if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0) throw new SymbolicAnalysisFailedException(); } ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; List<ILNode> body; int bodyLength; if (tryFaultBlock != null) { // there are try-finally blocks if (returnVariable == null) // in this case, we must use a return variable throw new SymbolicAnalysisFailedException(); // must be a try-fault block: if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) throw new SymbolicAnalysisFailedException(); ILBlock faultBlock = tryFaultBlock.FaultBlock; // Ensure the fault block contains the call to Dispose(). if (faultBlock.Body.Count != 2) throw new SymbolicAnalysisFailedException(); MethodReference disposeMethodRef; ILExpression disposeArg; if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) throw new SymbolicAnalysisFailedException(); if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis()) throw new SymbolicAnalysisFailedException(); if (!faultBlock.Body[1].Match(ILCode.Endfinally)) throw new SymbolicAnalysisFailedException(); body = tryFaultBlock.TryBlock.Body; bodyLength = body.Count; } else { // no try-finally blocks body = ilMethod.Body; if (returnVariable == null) bodyLength = body.Count - 1; // all except for the return statement else bodyLength = body.Count - 2; // all except for the return label and statement } // Now verify that the last instruction in the body is 'ret(false)' if (returnVariable != null) { // If we don't have a return variable, we already verified that above. // If we do have one, check for 'stloc(returnVariable, ldc.i4(0))' // Maybe might be a jump to the return label after the stloc: ILExpression leave = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; if (leave != null && (leave.Code == ILCode.Br || leave.Code == ILCode.Leave) && leave.Operand == returnLabel) bodyLength--; ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) throw new SymbolicAnalysisFailedException(); if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) throw new SymbolicAnalysisFailedException(); bodyLength--; // don't conside the stloc instruction to be part of the body } // The last element in the body usually is a label pointing to the 'ret(false)' returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; // Note: in Roslyn-compiled code, returnFalseLabel may be null. var rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.IteratorMoveNext, stateField); int pos = rangeAnalysis.AssignStateRanges(body, bodyLength); rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength); var labels = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength); ConvertBody(body, pos, bodyLength, labels); }
string GenerateNameForVariable(ILVariable variable) { string proposedName = null; if (variable.Type.IsKnownType(KnownTypeCode.Int32)) { // test whether the variable might be a loop counter if (loopCounters.Contains(variable)) { // For loop variables, use i,j,k,l,m,n for (char c = 'i'; c <= maxLoopVariableName; c++) { if (!reservedVariableNames.ContainsKey(c.ToString())) { proposedName = c.ToString(); break; } } } } // The ComponentResourceManager inside InitializeComponent must be named "resources", // otherwise the WinForms designer won't load the Form. if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager") { proposedName = "resources"; } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForAddress = variable.AddressInstructions.OfType <LdLoca>() .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null) .Where(arg => !string.IsNullOrWhiteSpace(arg)) .Except(currentFieldNames).ToList(); if (proposedNameForAddress.Count > 0) { proposedName = proposedNameForAddress[0]; } } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForStores = variable.StoreInstructions.OfType <StLoc>() .Select(expr => GetNameFromInstruction(expr.Value)) .Except(currentFieldNames).ToList(); if (proposedNameForStores.Count == 1) { proposedName = proposedNameForStores[0]; } } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForLoads = variable.LoadInstructions .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex)) .Except(currentFieldNames).ToList(); if (proposedNameForLoads.Count == 1) { proposedName = proposedNameForLoads[0]; } } if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot) { var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType <StLoc>() .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context))) .Except(currentFieldNames).ToList(); if (proposedNameForStoresFromNewObj.Count == 1) { proposedName = proposedNameForStoresFromNewObj[0]; } } if (string.IsNullOrEmpty(proposedName)) { proposedName = GetNameByType(variable.Type); } // remove any numbers from the proposed name proposedName = SplitName(proposedName, out int number); if (!reservedVariableNames.ContainsKey(proposedName)) { reservedVariableNames.Add(proposedName, 0); } int count = ++reservedVariableNames[proposedName]; if (count > 1) { return(proposedName + count.ToString()); } else { return(proposedName); } }
/// <summary> /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="parent">The direct parent of the load within 'next'</param> /// <param name="pos">Index of the load within 'parent'</param> /// <param name="v">The variable being inlined.</param> /// <param name="inlinedExpression">The expression being inlined</param> bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression) { if (pos == 0 && v.Type != null && DnlibExtensions.IsValueType(v.Type)) { // Inlining a value type variable is allowed only if the resulting code will maintain the semantics // that the method is operating on a copy. // Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers switch (inlinedExpression.Code) { case ILCode.Ldloc: case ILCode.Stloc: case ILCode.CompoundAssignment: case ILCode.Ldelem: case ILCode.Ldelem_I: case ILCode.Ldelem_I1: case ILCode.Ldelem_I2: case ILCode.Ldelem_I4: case ILCode.Ldelem_I8: case ILCode.Ldelem_R4: case ILCode.Ldelem_R8: case ILCode.Ldelem_Ref: case ILCode.Ldelem_U1: case ILCode.Ldelem_U2: case ILCode.Ldelem_U4: case ILCode.Ldobj: case ILCode.Ldind_Ref: return false; case ILCode.Ldfld: case ILCode.Stfld: case ILCode.Ldsfld: case ILCode.Stsfld: // allow inlining field access only if it's a readonly field FieldDef f = ((IField)inlinedExpression.Operand).Resolve(); if (!(f != null && f.IsInitOnly)) return false; break; case ILCode.Call: case ILCode.CallGetter: // inlining runs both before and after IntroducePropertyAccessInstructions, // so we have to handle both 'call' and 'callgetter' IMethod mr = (IMethod)inlinedExpression.Operand; // ensure that it's not an multi-dimensional array getter TypeSig ts; if (mr.DeclaringType is TypeSpec && (ts = ((TypeSpec)mr.DeclaringType).TypeSig.RemovePinnedAndModifiers()) != null && ts.IsSingleOrMultiDimensionalArray) return false; goto case ILCode.Callvirt; case ILCode.Callvirt: case ILCode.CallvirtGetter: // don't inline foreach loop variables: mr = (IMethod)inlinedExpression.Operand; if (mr.Name == "get_Current" && mr.MethodSig != null && mr.MethodSig.HasThis) return false; break; case ILCode.Castclass: case ILCode.Unbox_Any: // These are valid, but might occur as part of a foreach loop variable. ILExpression arg = inlinedExpression.Arguments[0]; if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) { mr = (IMethod)arg.Operand; if (mr.Name == "get_Current" && mr.MethodSig != null && mr.MethodSig.HasThis) return false; // looks like a foreach loop variable, so don't inline it } break; } // inline the compiler-generated variable that are used when accessing a member on a value type: switch (parent.Code) { case ILCode.Call: case ILCode.CallGetter: case ILCode.CallSetter: case ILCode.Callvirt: case ILCode.CallvirtGetter: case ILCode.CallvirtSetter: IMethod mr = parent.Operand as IMethod; return mr == null || mr.MethodSig == null ? false : mr.MethodSig.HasThis; case ILCode.Stfld: case ILCode.Ldfld: case ILCode.Ldflda: case ILCode.Await: return true; } } return false; }
static Dictionary <string, int> CollectReservedVariableNames(ILFunction function, ILVariable existingVariable) { var reservedVariableNames = new Dictionary <string, int>(); var rootFunction = function.Ancestors.OfType <ILFunction>().Single(f => f.Parent == null); foreach (var f in rootFunction.Descendants.OfType <ILFunction>()) { foreach (var p in rootFunction.Parameters) { AddExistingName(reservedVariableNames, p.Name); } foreach (var v in f.Variables.Where(v => v.Kind != VariableKind.Parameter)) { if (v != existingVariable) { AddExistingName(reservedVariableNames, v.Name); } } } foreach (var f in rootFunction.Method.DeclaringTypeDefinition.GetFields().Select(f => f.Name)) { AddExistingName(reservedVariableNames, f); } return(reservedVariableNames); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos) { parent = null; pos = 0; if (expr == null) return false; for (int i = 0; i < expr.Arguments.Count; i++) { // Stop when seeing an opcode that does not guarantee that its operands will be evaluated. // Inlining in that case might result in the inlined expresion not being evaluted. if (i == 1 && (expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr || expr.Code == ILCode.TernaryOp || expr.Code == ILCode.NullCoalescing)) return false; ILExpression arg = expr.Arguments[i]; if ((arg.Code == ILCode.Ldloc || arg.Code == ILCode.Ldloca) && arg.Operand == v) { parent = expr; pos = i; return true; } bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos); if (r != null) return r; } if (IsSafeForInlineOver(expr, expressionBeingMoved)) return null; // continue searching else return false; // abort, inlining not possible }
internal static string GenerateForeachVariableName(ILFunction function, ILInstruction valueContext, ILVariable existingVariable = null) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (existingVariable != null && !existingVariable.HasGeneratedName) { return(existingVariable.Name); } var reservedVariableNames = CollectReservedVariableNames(function, existingVariable); string baseName = GetNameFromInstruction(valueContext); if (string.IsNullOrEmpty(baseName)) { if (valueContext is LdLoc ldloc && ldloc.Variable.Kind == VariableKind.Parameter) { baseName = ldloc.Variable.Name; } } string proposedName = "item"; if (!string.IsNullOrEmpty(baseName)) { if (!IsPlural(baseName, ref proposedName)) { if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal)) { proposedName = baseName.Substring(0, baseName.Length - 4); } else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase)) { proposedName = "item"; } else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase)) { proposedName = baseName.Remove(baseName.Length - 3); } } } // remove any numbers from the proposed name proposedName = SplitName(proposedName, out int number); if (!reservedVariableNames.ContainsKey(proposedName)) { reservedVariableNames.Add(proposedName, 0); } int count = ++reservedVariableNames[proposedName]; if (count > 1) { return(proposedName + count.ToString()); } else { return(proposedName); } }
bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable) { switch (expr.Code) { case ILCode.Ldloca: case ILCode.Ldelema: case ILCode.Ldflda: case ILCode.Ldsflda: // All address-loading instructions always return the same value for a given operand/argument combination, // so they can be safely copied. return true; case ILCode.Ldloc: ILVariable v = (ILVariable)expr.Operand; if (v.IsParameter) { // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0; } else { // Variables are be copied only if both they and the target copy variable are generated, // and if the variable has only a single assignment return v.GeneratedByDecompiler && copyVariable.GeneratedByDecompiler && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1; } default: return false; } }
internal static string GenerateVariableName(ILFunction function, IType type, ILInstruction valueContext = null, ILVariable existingVariable = null) { if (function == null) { throw new ArgumentNullException(nameof(function)); } var reservedVariableNames = CollectReservedVariableNames(function, existingVariable); string baseName = valueContext != null?GetNameFromInstruction(valueContext) ?? GetNameByType(type) : GetNameByType(type); string proposedName = "obj"; if (!string.IsNullOrEmpty(baseName)) { if (!IsPlural(baseName, ref proposedName)) { if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal)) { proposedName = baseName.Substring(0, baseName.Length - 4); } else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase)) { proposedName = "item"; } else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase)) { proposedName = baseName.Remove(baseName.Length - 3); } else { proposedName = baseName; } } } // remove any numbers from the proposed name proposedName = SplitName(proposedName, out int number); if (!reservedVariableNames.ContainsKey(proposedName)) { reservedVariableNames.Add(proposedName, 0); } int count = ++reservedVariableNames[proposedName]; if (count > 1) { return(proposedName + count.ToString()); } else { return(proposedName); } }
protected JSExpression Translate_Ldloc(ILExpression node, ILVariable variable) { JSExpression result; JSVariable renamed; if (RenamedVariables.TryGetValue(variable, out renamed)) result = new JSIndirectVariable(Variables, renamed.Identifier, ThisMethodReference); else result = new JSIndirectVariable(Variables, variable.Name, ThisMethodReference); var expectedType = node.ExpectedType ?? node.InferredType ?? variable.Type; if (!TypesAreAssignable(expectedType, variable.Type)) result = Translate_Conv(result, expectedType); return result; }
/// <summary> /// mcs likes to optimize closures in yield state machines away by moving the captured variables' fields into the state machine type, /// We construct a <see cref="DisplayClass"/> that spans the whole method body. /// </summary> bool HandleMonoStateMachine(ILFunction currentFunction, ILVariable thisVariable, SimpleTypeResolveContext decompilationContext, ILFunction nestedFunction) { if (!(nestedFunction.StateMachineCompiledWithMono && thisVariable.IsThis())) { return(false); } // Special case for Mono-compiled yield state machines ITypeDefinition closureType = thisVariable.Type.GetDefinition(); if (!(closureType != decompilationContext.CurrentTypeDefinition && IsPotentialClosure(decompilationContext.CurrentTypeDefinition, closureType))) { return(false); } var displayClass = new DisplayClass { IsMono = true, Initializer = nestedFunction.Body, Variable = thisVariable, Definition = thisVariable.Type.GetDefinition(), Variables = new Dictionary <IField, DisplayClassVariable>(), CaptureScope = (BlockContainer)nestedFunction.Body }; displayClasses.Add(thisVariable, displayClass); foreach (var stateMachineVariable in nestedFunction.Variables) { if (stateMachineVariable.StateMachineField == null || displayClass.Variables.ContainsKey(stateMachineVariable.StateMachineField)) { continue; } displayClass.Variables.Add(stateMachineVariable.StateMachineField, new DisplayClassVariable { Variable = stateMachineVariable, Value = new LdLoc(stateMachineVariable) }); } if (!currentFunction.Method.IsStatic && FindThisField(out var thisField)) { var thisVar = currentFunction.Variables .FirstOrDefault(t => t.IsThis() && t.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition); if (thisVar == null) { thisVar = new ILVariable(VariableKind.Parameter, decompilationContext.CurrentTypeDefinition, -1) { Name = "this" }; currentFunction.Variables.Add(thisVar); } displayClass.Variables.Add(thisField, new DisplayClassVariable { Variable = thisVar, Value = new LdLoc(thisVar) }); } return(true); bool FindThisField(out IField foundField) { foundField = null; foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.Variables.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) { thisField = field; return(true); } return(false); } }
protected JSExpression Translate_Stloc(ILExpression node, ILVariable variable) { if (node.Arguments[0].Code == ILCode.GetCallSite) DynamicCallSites.SetAlias(variable, (FieldReference)node.Arguments[0].Operand); // GetCallSite and CreateCallSite produce null expressions, so we want to ignore assignments containing them var value = TranslateNode(node.Arguments[0]); if ((value.IsNull) && !(value is JSUntranslatableExpression) && !(value is JSIgnoredMemberReference)) return new JSNullExpression(); var expectedType = node.ExpectedType ?? node.InferredType ?? variable.Type; if (!TypesAreAssignable(expectedType, value.GetExpectedType(TypeSystem))) value = Translate_Conv(value, expectedType); JSVariable jsv; if (RenamedVariables.TryGetValue(variable, out jsv)) jsv = new JSIndirectVariable(Variables, jsv.Identifier, ThisMethodReference); else jsv = new JSIndirectVariable(Variables, variable.Name, ThisMethodReference); if (jsv.IsReference) { JSExpression materializedValue; if (!JSReferenceExpression.TryMaterialize(JSIL, value, out materializedValue)) Console.Error.WriteLine(String.Format("Cannot store a non-reference into variable {0}: {1}", jsv, value)); else value = materializedValue; } return new JSBinaryOperatorExpression( JSOperator.Assignment, jsv, value, value.GetExpectedType(TypeSystem) ); }
public void Propagate(ILVariable variable) { Debug.Assert(declaredVariable == null || (variable == null && declaredVariable.StateMachineField == null)); this.declaredVariable = variable; this.CanPropagate = variable != null; }
/// <summary> /// Parses an object initializer. /// </summary> /// <param name="body">ILAst block</param> /// <param name="pos"> /// Input: position of the instruction assigning to 'v'. /// Output: first position after the object initializer /// </param> /// <param name="v">The variable that holds the object being initialized</param> /// <param name="newObjExpr">The newobj instruction</param> /// <returns>InitObject instruction</returns> ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection) { Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc); // Take care not to modify any existing ILExpressions in here. // We just construct new ones around the old ones, any modifications must wait until the whole // object/collection initializer was analyzed. ILExpression objectInitializer = new ILExpression(isCollection ? ILCode.InitCollection : ILCode.InitObject, null, newObjExpr); List<ILExpression> initializerStack = new List<ILExpression>(); initializerStack.Add(objectInitializer); while (++pos < body.Count) { ILExpression nextExpr = body[pos] as ILExpression; if (IsSetterInObjectInitializer(nextExpr)) { if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false)) { CleanupInitializerStackAfterFailedAdjustment(initializerStack); break; } initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); } else if (IsAddMethodCall(nextExpr)) { if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true)) { CleanupInitializerStackAfterFailedAdjustment(initializerStack); break; } initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); } else { // can't match any more initializers: end of object initializer break; } } return objectInitializer; }
void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, ILVariable v, bool allowPassIntoLoops) { // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block Statement declarationPoint = null; // Check whether we can move down the variable into the sub-blocks bool canMoveVariableIntoSubBlocks = FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); if (declarationPoint == null) { // The variable isn't used at all return; } if (canMoveVariableIntoSubBlocks) { // Declare the variable within the sub-blocks foreach (Statement stmt in block.Statements) { ForStatement forStmt = stmt as ForStatement; if (forStmt != null && forStmt.Initializers.Count == 1) { // handle the special case of moving a variable into the for initializer if (TryConvertAssignmentExpressionIntoVariableDeclaration(forStmt.Initializers.Single(), type, variableName)) { continue; } } UsingStatement usingStmt = stmt as UsingStatement; if (usingStmt != null && usingStmt.ResourceAcquisition is AssignmentExpression) { // handle the special case of moving a variable into a using statement if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName)) { continue; } } foreach (AstNode child in stmt.Children) { BlockStatement subBlock = child as BlockStatement; if (subBlock != null) { DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops); } else if (HasNestedBlocks(child)) { foreach (BlockStatement nestedSubBlock in child.Children.OfType <BlockStatement>()) { DeclareVariableInBlock(daa, nestedSubBlock, type, variableName, v, allowPassIntoLoops); } } } } } else { // Try converting an assignment expression into a VariableDeclarationStatement if (!TryConvertAssignmentExpressionIntoVariableDeclaration(declarationPoint, type, variableName)) { // Declare the variable in front of declarationPoint variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = v, InsertionPoint = declarationPoint }); } } }
void Reset() { this.A = null; this.B = null; this.Operator = null; this.SimpleOperand = null; this.SimpleLeftOperand = false; }
/// <summary> /// Block entryPoint (incoming: 1) { /// stloc temp(isinst exceptionType(ldloc exceptionVar)) /// if (comp(ldloc temp != ldnull)) br whenConditionBlock /// br falseBlock /// } /// </summary> bool MatchCatchWhenEntryPoint(ILVariable exceptionVar, BlockContainer container, Block entryPoint, out IType exceptionType, out ILInstruction exceptionSlot, out Block whenConditionBlock) { exceptionType = null; exceptionSlot = null; whenConditionBlock = null; if (entryPoint == null || entryPoint.IncomingEdgeCount != 1) { return(false); } if (entryPoint.Instructions.Count == 3) { // stloc temp(isinst exceptionType(ldloc exceptionVar)) // if (comp(ldloc temp != ldnull)) br whenConditionBlock // br falseBlock if (!entryPoint.Instructions[0].MatchStLoc(out var temp, out var isinst) || temp.Kind != VariableKind.StackSlot || !isinst.MatchIsInst(out exceptionSlot, out exceptionType)) { return(false); } if (!exceptionSlot.MatchLdLoc(exceptionVar)) { return(false); } if (!entryPoint.Instructions[1].MatchIfInstruction(out var condition, out var branch)) { return(false); } if (!condition.MatchCompNotEquals(out var left, out var right)) { return(false); } if (!entryPoint.Instructions[2].MatchBranch(out var falseBlock) || !MatchFalseBlock(container, falseBlock, out var returnVar, out var exitBlock)) { return(false); } if ((left.MatchLdNull() && right.MatchLdLoc(temp)) || (right.MatchLdNull() && left.MatchLdLoc(temp))) { return(branch.MatchBranch(out whenConditionBlock)); } } else if (entryPoint.Instructions.Count == 2) { // if (comp(isinst exceptionType(ldloc exceptionVar) != ldnull)) br whenConditionBlock // br falseBlock if (!entryPoint.Instructions[0].MatchIfInstruction(out var condition, out var branch)) { return(false); } if (!condition.MatchCompNotEquals(out var left, out var right)) { return(false); } if (!entryPoint.Instructions[1].MatchBranch(out var falseBlock) || !MatchFalseBlock(container, falseBlock, out var returnVar, out var exitBlock)) { return(false); } if (!left.MatchIsInst(out exceptionSlot, out exceptionType)) { return(false); } if (!exceptionSlot.MatchLdLoc(exceptionVar)) { return(false); } if (right.MatchLdNull()) { return(branch.MatchBranch(out whenConditionBlock)); } } return(false); }
public void AddStateVariable(ILVariable v) { if (!stateVariables.Contains(v)) stateVariables.Add(v); }
public DisplayClass(ILVariable variable, ITypeDefinition type) { Variable = variable; Type = type; VariablesToDeclare = new Dictionary <IField, VariableToDeclare>(); }
public void SetAlias (ILVariable variable, FieldReference fieldReference) { Aliases[variable.Name] = fieldReference; }
public void Propagate(ILVariable variable) { this.declaredVariable = variable; this.CanPropagate = variable != null; }
List<ByteCode> StackAnalysis(MethodDefinition methodDef) { // Create temporary structure for the stack analysis List<ByteCode> body = new List<ByteCode>(methodDef.Body.Instructions.Count); foreach(Instruction inst in methodDef.Body.Instructions) { OpCode opCode = inst.OpCode; object operand = inst.Operand; MethodBodyRocks.ExpandMacro(ref opCode, ref operand, methodDef.Body); ByteCode byteCode = new ByteCode() { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, OpCode = opCode, Operand = operand, PopCount = inst.GetPopCount(), PushCount = inst.GetPushCount() }; instrToByteCode[inst] = byteCode; body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } Queue<ByteCode> agenda = new Queue<ByteCode>(); // Add known states body[0].StackBefore = new List<StackSlot>(); agenda.Enqueue(body[0]); if(methodDef.Body.HasExceptionHandlers) { foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { ByteCode tryStart = instrToByteCode[ex.TryStart]; tryStart.StackBefore = new List<StackSlot>(); agenda.Enqueue(tryStart); ByteCode handlerStart = instrToByteCode[ex.HandlerType == ExceptionHandlerType.Filter ? ex.FilterStart : ex.HandlerStart]; handlerStart.StackBefore = new List<StackSlot>(); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { handlerStart.StackBefore.Add(new StackSlot(null)); } agenda.Enqueue(handlerStart); // Control flow is not required to reach endfilter if (ex.HandlerType == ExceptionHandlerType.Filter) { ByteCode endFilter = instrToByteCode[ex.FilterEnd.Previous]; endFilter.StackBefore = new List<StackSlot>(); } } } // Process agenda while(agenda.Count > 0) { ByteCode byteCode = agenda.Dequeue(); // Calculate new stack List<StackSlot> newStack = byteCode.CloneStack(byteCode.PopCount); for (int i = 0; i < byteCode.PushCount; i++) { newStack.Add(new StackSlot(byteCode)); } // Apply the state to any successors List<ByteCode> branchTargets = new List<ByteCode>(); if (byteCode.OpCode.CanFallThough()) { branchTargets.Add(byteCode.Next); } if (byteCode.OpCode.IsBranch()) { if (byteCode.Operand is Instruction[]) { foreach(Instruction inst in (Instruction[])byteCode.Operand) { ByteCode target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } else { ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } foreach (ByteCode branchTarget in branchTargets) { if (branchTarget.StackBefore == null) { branchTarget.StackBefore = newStack; // Do not share one stack for several bytecodes if (branchTargets.Count > 1) { branchTarget.StackBefore = branchTarget.CloneStack(0); } agenda.Enqueue(branchTarget); } else { if (branchTarget.StackBefore.Count != newStack.Count) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Merge stacks bool modified = false; for (int i = 0; i < newStack.Count; i++) { List<ByteCode> oldPushedBy = branchTarget.StackBefore[i].PushedBy; List<ByteCode> newPushedBy = oldPushedBy.Union(newStack[i].PushedBy).ToList(); if (newPushedBy.Count > oldPushedBy.Count) { branchTarget.StackBefore[i].PushedBy = newPushedBy; modified = true; } } if (modified) { agenda.Enqueue(branchTarget); } } } } // Genertate temporary variables to replace stack foreach(ByteCode byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { StackSlot arg = byteCode.StackBefore[i]; ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), IsGenerated = true }; arg.LoadFrom = tmpVar; foreach(ByteCode pushedBy in arg.PushedBy) { // TODO: Handle exception variables if (pushedBy != null) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List<ILVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } } if (arg.PushedBy.Count == 1) { allowInline[tmpVar] = true; } argIdx++; } } // Convert local varibles Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType }).ToList(); int[] numReads = new int[Variables.Count]; int[] numWrites = new int[Variables.Count]; foreach(ByteCode byteCode in body) { if (byteCode.OpCode == OpCodes.Ldloc) { int index = ((VariableDefinition)byteCode.Operand).Index; byteCode.Operand = Variables[index]; numReads[index]++; } if (byteCode.OpCode == OpCodes.Stloc) { int index = ((VariableDefinition)byteCode.Operand).Index; byteCode.Operand = Variables[index]; numWrites[index]++; } } // Find which variables we can inline if (this.optimize) { for (int i = 0; i < Variables.Count; i++) { if (numReads[i] == 1 && numWrites[i] == 1) { allowInline[Variables[i]] = true; } } } // Convert branch targets to labels foreach(ByteCode byteCode in body) { if (byteCode.Operand is Instruction[]) { List<ILLabel> newOperand = new List<ILLabel>(); foreach(Instruction target in (Instruction[])byteCode.Operand) { newOperand.Add(instrToByteCode[target].Label); } byteCode.Operand = newOperand.ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } } return body; }
/// <summary> /// Fill <c>allStores</c> and <c>storeIndexMap</c>. /// </summary> static List <ILInstruction>[] FindAllStoresByVariable(ILFunction scope, BitSet activeVariables, CancellationToken cancellationToken) { // For each variable, find the list of ILInstructions storing to that variable List <ILInstruction>[] storesByVar = new List <ILInstruction> [scope.Variables.Count]; for (int vi = 0; vi < storesByVar.Length; vi++) { if (activeVariables[vi]) { storesByVar[vi] = new List <ILInstruction> { null } } ; } foreach (var inst in scope.Descendants) { if (inst.HasDirectFlag(InstructionFlags.MayWriteLocals)) { cancellationToken.ThrowIfCancellationRequested(); ILVariable v = ((IInstructionWithVariableOperand)inst).Variable; if (v.Function == scope && activeVariables[v.IndexInFunction]) { storesByVar[v.IndexInFunction].Add(inst); } } } return(storesByVar); } /// <summary> /// Create the initial state (reachable bit + uninit variable bits set, store bits unset). /// </summary> State CreateInitialState() { BitSet initialState = new BitSet(allStores.Length); initialState.Set(ReachableBit); for (int vi = 0; vi < scope.Variables.Count; vi++) { if (analyzedVariables[vi]) { Debug.Assert(allStores[firstStoreIndexForVariable[vi]] == null); initialState.Set(firstStoreIndexForVariable[vi]); } } return(new State(initialState)); } #endregion #region Analysis void HandleStore(ILInstruction inst, ILVariable v) { cancellationToken.ThrowIfCancellationRequested(); if (v.Function == scope && analyzedVariables[v.IndexInFunction] && state.IsReachable) { // Clear the set of stores for this variable: state.KillStores(firstStoreIndexForVariable[v.IndexInFunction], firstStoreIndexForVariable[v.IndexInFunction + 1]); // And replace it with this store: int si = storeIndexMap[inst]; state.SetStore(si); // We should call PropagateStateOnException() here because we changed the state. // But that's equal to: currentStateOnException.UnionWith(state); // Because we're already guaranteed that state.LessThanOrEqual(currentStateOnException) // when entering HandleStore(), all we really need to do to achieve what PropagateStateOnException() does // is to add the single additional store to the exceptional state as well: currentStateOnException.SetStore(si); } }
bool StoreCanBeConvertedToAssignment(ILExpression store, ILVariable exprVar) { if (store == null) return false; switch (store.Code) { case ILCode.Stloc: case ILCode.Stfld: case ILCode.Stsfld: case ILCode.Stobj: case ILCode.CallSetter: case ILCode.CallvirtSetter: break; default: if (!store.Code.IsStoreToArray()) return false; break; } return store.Arguments.Last().Code == ILCode.Ldloc && store.Arguments.Last().Operand == exprVar; }
public bool IsAnalyzedVariable(ILVariable v) { return(v.Function == scope && analyzedVariables[v.IndexInFunction]); }
bool HandleStringFixing(ILVariable pinnedVar, List<ILNode> body, ref int pos, ref ILExpression fixedStmtInitializer) { // fixed (stloc(pinnedVar, ldloc(text))) { // var1 = var2 = conv.i(ldloc(pinnedVar)) // if (logicnot(logicnot(var1))) { // var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData)) // } // stloc(ptrVar, var2) // ... if (pos >= body.Count) return false; ILVariable var1, var2; ILExpression varAssignment, ptrInitialization; if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization))) return false; if (!(var1.IsGenerated && var2.IsGenerated)) return false; if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U) ptrInitialization = ptrInitialization.Arguments[0]; if (!ptrInitialization.MatchLdloc(pinnedVar)) return false; ILCondition ifStmt = body[pos + 1] as ILCondition; if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0))) return false; if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1)) return false; ILVariable assignedVar; ILExpression assignedExpr; if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add)) return false; MethodReference calledMethod; if (!(assignedExpr.Arguments[0].MatchLdloc(var1))) return false; if (!(assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod) || assignedExpr.Arguments[1].Match(ILCode.CallGetter, out calledMethod))) return false; if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers")) return false; ILVariable pointerVar; if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) { pos += 3; fixedStmtInitializer.Operand = pointerVar; return true; } return false; }
/// <summary> /// Determines a list of all store instructions that write to a given <paramref name="returnVar"/>. /// Returns false if any of these instructions does not meet the following criteria: /// - must be a stloc /// - must be a direct child of a block /// - must be the penultimate instruction /// - must be followed by a branch instruction to <paramref name="leaveBlock"/> /// - must have a BlockContainer as ancestor. /// Returns true, if all instructions meet these criteria, and <paramref name="instructionsToModify"/> contains a list of 3-tuples. /// Each tuple consists of the target block container, the leave block, and the branch instruction that should be modified. /// </summary> static bool CanModifyInstructions(ILVariable returnVar, Block leaveBlock, out List <(BlockContainer, Block, Branch)> instructionsToModify)