public void Run(Block block, BlockTransformContext context) { for (int i = 0; i < block.Instructions.Count; i++) { ILVariable v; ILInstruction copiedExpr; if (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack if (SemanticHelper.IsPure(copiedExpr.Flags)) { // no-op -> delete context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); block.Instructions.RemoveAt(i--); } else { // evaluate the value for its side-effects context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]); copiedExpr.AddILRange(block.Instructions[i].ILRange); block.Instructions[i] = copiedExpr; } } else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr)) { DoPropagate(v, copiedExpr, block, ref i, context); } } } }
/// <summary> /// stloc lockObj(lockExpression) /// call Enter(ldloc lockObj) /// .try BlockContainer { /// Block lockBlock (incoming: 1) { /// call WriteLine() /// leave lockBlock (nop) /// } /// /// } finally BlockContainer { /// Block exitBlock (incoming: 1) { /// call Exit(ldloc lockObj) /// leave exitBlock (nop) /// } /// /// } /// => /// .lock (lockExpression) BlockContainer { /// Block lockBlock (incoming: 1) { /// call WriteLine() /// leave lockBlock (nop) /// } /// } /// </summary> bool TransformLockMCS(Block block, int i) { if (i < 2) { return(false); } if (!(block.Instructions[i] is TryFinally body) || !(block.Instructions[i - 2] is StLoc objectStore) || !MatchCall(block.Instructions[i - 1] as Call, "Enter", objectStore.Variable)) { return(false); } if (!objectStore.Variable.IsSingleDefinition) { return(false); } if (!(body.TryBlock is BlockContainer tryContainer) || tryContainer.EntryPoint.Instructions.Count == 0 || tryContainer.EntryPoint.IncomingEdgeCount != 1) { return(false); } if (!(body.FinallyBlock is BlockContainer finallyContainer) || !MatchExitBlock(finallyContainer.EntryPoint, null, objectStore.Variable)) { return(false); } if (objectStore.Variable.LoadCount > 2) { return(false); } context.Step("LockTransformMCS", block); block.Instructions.RemoveAt(i - 1); block.Instructions.RemoveAt(i - 2); body.ReplaceWith(new LockInstruction(objectStore.Value, body.TryBlock).WithILRange(objectStore)); return(true); }
/// <code> /// stloc s(value) /// stloc l(ldloc s) /// stobj(..., ldloc s) /// --> /// stloc l(stobj (..., value)) /// </code> /// -or- /// <code> /// stloc s(value) /// stobj (..., ldloc s) /// --> /// stloc s(stobj (..., value)) /// </code> bool TransformInlineAssignmentStObj(Block block, int i) { var inst = block.Instructions[i] as StLoc; // in some cases it can be a compiler-generated local if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local)) { return(false); } var nextInst = block.Instructions.ElementAtOrDefault(i + 1); ILInstruction replacement; StObj fieldStore; ILVariable local; if (nextInst is StLoc) // instance fields { var localStore = (StLoc)nextInst; if (localStore.Variable.Kind == VariableKind.StackSlot || !localStore.Value.MatchLdLoc(inst.Variable)) { return(false); } var memberStore = block.Instructions.ElementAtOrDefault(i + 2); if (memberStore is StObj) { fieldStore = memberStore as StObj; if (!fieldStore.Value.MatchLdLoc(inst.Variable)) { return(false); } replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type); } else // otherwise it must be local { return(TransformInlineAssignmentLocal(block, i)); } context.Step("Inline assignment to instance field", fieldStore); local = localStore.Variable; block.Instructions.RemoveAt(i + 1); } else if (nextInst is StObj) // static fields { fieldStore = (StObj)nextInst; if (!fieldStore.Value.MatchLdLoc(inst.Variable) || (fieldStore.Target.MatchLdFlda(out var target, out _) && target.MatchLdLoc(inst.Variable))) { return(false); } context.Step("Inline assignment to static field", fieldStore); local = inst.Variable; replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type); } else { return(false); } block.Instructions.RemoveAt(i + 1); inst.ReplaceWith(new StLoc(local, replacement)); return(true); }
/// <summary> /// stloc s(valueInst) /// if (comp(ldloc s == ldnull)) { /// stloc s(fallbackInst) /// } /// => /// stloc s(if.notnull(valueInst, fallbackInst)) /// </summary> bool TransformNullCoalescing(Block block, int i) { if (i == 0) { return(false); } if (!(block.Instructions[i - 1] is StLoc stloc)) { return(false); } if (stloc.Variable.Kind != VariableKind.StackSlot) { return(false); } if (!block.Instructions[i].MatchIfInstruction(out var condition, out var trueInst)) { return(false); } trueInst = Block.Unwrap(trueInst); if (condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull() && trueInst.MatchStLoc(stloc.Variable, out var fallbackValue) ) { context.Step("TransformNullCoalescing", stloc); stloc.Value = new NullCoalescingInstruction(stloc.Value, fallbackValue); return(true); // returning true removes the if instruction } return(false); }
/// <summary> /// stloc obj(resourceExpression) /// .try BlockContainer { /// Block IL_0003(incoming: 1) { /// call WriteLine(ldstr "using (null)") /// leave IL_0003(nop) /// } /// } finally BlockContainer { /// Block IL_0012(incoming: 1) { /// if (comp(ldloc obj != ldnull)) Block IL_001a { /// callvirt Dispose(ldnull) /// } /// leave IL_0012(nop) /// } /// } /// leave IL_0000(nop) /// => /// using (resourceExpression) { /// BlockContainer { /// Block IL_0003(incoming: 1) { /// call WriteLine(ldstr "using (null)") /// leave IL_0003(nop) /// } /// } /// } /// </summary> bool TransformUsing(Block block, int i) { if (i + 1 >= block.Instructions.Count) { return(false); } if (!(block.Instructions[i + 1] is TryFinally tryFinally) || !(block.Instructions[i] is StLoc storeInst)) { return(false); } if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type))) { return(false); } if (storeInst.Variable.Kind != VariableKind.Local) { return(false); } if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally))) { return(false); } if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la)))) { return(false); } if (storeInst.Variable.StoreInstructions.Count > 1) { return(false); } if (!(tryFinally.FinallyBlock is BlockContainer container)) { return(false); } if (!MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) { return(false); } context.Step("UsingTransform", tryFinally); storeInst.Variable.Kind = VariableKind.UsingLocal; block.Instructions.RemoveAt(i + 1); block.Instructions[i] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike }.WithILRange(storeInst); return(true); }
/// <summary> /// stloc obj(resourceExpression) /// .try BlockContainer { /// Block IL_0003(incoming: 1) { /// call WriteLine(ldstr "using (null)") /// leave IL_0003(nop) /// } /// } finally BlockContainer { /// Block IL_0012(incoming: 1) { /// if (comp(ldloc obj != ldnull)) Block IL_001a { /// callvirt Dispose(ldnull) /// } /// leave IL_0012(nop) /// } /// } /// leave IL_0000(nop) /// => /// using (resourceExpression) { /// BlockContainer { /// Block IL_0003(incoming: 1) { /// call WriteLine(ldstr "using (null)") /// leave IL_0003(nop) /// } /// } /// } /// </summary> bool TransformUsing(Block block, int i) { if (i < 1) { return(false); } if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) { return(false); } if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type))) { return(false); } if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally))) { return(false); } if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la)))) { return(false); } if (storeInst.Variable.StoreInstructions.Count > 1) { return(false); } if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) { return(false); } context.Step("UsingTransform", tryFinally); storeInst.Variable.Kind = VariableKind.UsingLocal; block.Instructions.RemoveAt(i); block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { ILRange = storeInst.ILRange }; return(true); }
/// <summary> /// if (comp(ldsfld CachedAnonMethodDelegate == ldnull)) { /// stsfld CachedAnonMethodDelegate(DelegateConstruction) /// } /// ... one usage of CachedAnonMethodDelegate ... /// => /// ... one usage of DelegateConstruction ... /// </summary> bool CachedDelegateInitializationWithField(IfInstruction inst) { Block trueInst = inst.TrueInst as Block; if (trueInst == null || trueInst.Instructions.Count != 1 || !inst.FalseInst.MatchNop()) { return(false); } var storeInst = trueInst.Instructions[0]; if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdsFld(out IField field) || !right.MatchLdNull()) { return(false); } if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { return(false); } if (!DelegateConstruction.IsDelegateConstruction(value.UnwrapConv(ConversionKind.Invalid) as NewObj, true)) { return(false); } var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1); if (nextInstruction == null) { return(false); } var usages = nextInstruction.Descendants.Where(i => i.MatchLdsFld(field)).ToArray(); if (usages.Length != 1) { return(false); } context.Step("CachedDelegateInitializationWithField", inst); usages[0].ReplaceWith(value); return(true); }
public void Run(Block block, BlockTransformContext context) { if (running) { throw new InvalidOperationException("LoopingBlockTransform already running. Transforms (and the CSharpDecompiler) are neither neither thread-safe nor re-entrant."); } running = true; try { int count = 1; do { block.ResetDirty(); block.RunTransforms(children, context); if (block.IsDirty) { context.Step($"Block is dirty; running loop iteration #{++count}.", block); } } while (block.IsDirty); } finally { running = false; } }
/// <summary> /// stloc s(valueInst) /// if (comp(ldloc s == ldnull)) { /// stloc s(fallbackInst) /// } /// => /// stloc s(if.notnull(valueInst, fallbackInst)) /// </summary> bool TransformNullCoalescing(Block block, int i) { if (i == 0) { return(false); } if (!(block.Instructions[i] is IfInstruction ifInstruction) || !(block.Instructions[i - 1] is StLoc stloc) || stloc.Variable.Kind != VariableKind.StackSlot) { return(false); } if (!ifInstruction.Condition.MatchCompEquals(out var left, out var right) || !left.MatchLdLoc(stloc.Variable) || !right.MatchLdNull()) { return(false); } if (!ifInstruction.FalseInst.MatchNop() || !(ifInstruction.TrueInst is Block b) || b.Instructions.Count != 1 || !(b.Instructions[0] is StLoc fallbackStore) || fallbackStore.Variable != stloc.Variable) { return(false); } context.Step("TransformNullCoalescing", stloc); stloc.Value = new NullCoalescingInstruction(stloc.Value, fallbackStore.Value); return(true); }
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)) { Block initializerBlock = null; IType instType; switch (initInst) { case NewObj newObjInst: if (newObjInst.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); } // 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: instType = defaultVal.Type; break; case Block existingInitializer: if (existingInitializer.Type == BlockType.CollectionInitializer || existingInitializer.Type == BlockType.ObjectInitializer) { initializerBlock = existingInitializer; var value = ((StLoc)initializerBlock.Instructions[0]).Value; if (value is NewObj no) { instType = no.Method.DeclaringType; } else { instType = ((DefaultValue)value).Type; } break; } return(false); default: return(false); } context.Step("CollectionOrObjectInitializer", inst); int initializerItemsCount = 0; var blockType = initializerBlock?.Type ?? BlockType.CollectionInitializer; var possibleIndexVariables = new Dictionary <ILVariable, (int Index, ILInstruction Value)>(); // 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 blockType, possibleIndexVariables)) { initializerItemsCount++; } var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index); if (index != null) { initializerItemsCount = index.Value - pos - 1; } if (initializerItemsCount <= 0) { return(false); } ILVariable finalSlot; if (initializerBlock == null) { initializerBlock = new Block(blockType); finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); initializerBlock.FinalInstruction = new LdLoc(finalSlot); initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone())); } else { finalSlot = ((LdLoc)initializerBlock.FinalInstruction).Variable; } 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 = (CallInstruction)call.Clone(); 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)stObj.Clone(); 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)stLoc.Clone(); initializerBlock.Instructions.Add(newStLoc); break; } } initInst.ReplaceWith(initializerBlock); for (int i = 0; i < initializerItemsCount; i++) { body.Instructions.RemoveAt(pos + 1); } ILInlining.InlineIfPossible(body, ref pos, context); } return(true); }
bool DoTransform(Block body, int pos) { if (pos >= body.Instructions.Count - 2) { return(false); } ILInstruction inst = body.Instructions[pos]; ILVariable v; ILInstruction newarrExpr; IType elementType; int[] arrayLength; if (inst.MatchStLoc(out v, out newarrExpr) && MatchNewArr(newarrExpr, out elementType, out arrayLength)) { ILInstruction[] values; int initArrayPos; if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out values, out initArrayPos)) { context.Step("ForwardScanInitializeArrayRuntimeHelper", inst); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); var block = BlockFromInitializer(tempStore, elementType, arrayLength, values); body.Instructions[pos].ReplaceWith(new StLoc(v, block)); body.Instructions.RemoveAt(initArrayPos); ILInlining.InlineIfPossible(body, ref pos, context); return(true); } if (arrayLength.Length == 1) { ILVariable finalStore; int instructionsToRemove; if (HandleSimpleArrayInitializer(body, pos + 1, v, arrayLength[0], out finalStore, out values, out instructionsToRemove)) { context.Step("HandleSimpleArrayInitializer", inst); var block = new Block(BlockType.ArrayInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); block.Instructions.AddRange(values.SelectWithIndex( (i, value) => { if (value == null) { value = GetNullExpression(elementType); } return(StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType)); } )); block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos].ReplaceWith(new StLoc(finalStore ?? v, block)); RemoveInstructions(body, pos + 1, instructionsToRemove); //body.Instructions.RemoveRange(pos + 1, values.Length + 1); ILInlining.InlineIfPossible(body, ref pos, context); return(true); } if (HandleJaggedArrayInitializer(body, pos + 1, v, arrayLength[0], out finalStore, out values, out instructionsToRemove)) { context.Step("HandleJaggedArrayInitializer", inst); var block = new Block(BlockType.ArrayInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType))); block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos].ReplaceWith(new StLoc(finalStore, block)); RemoveInstructions(body, pos + 1, instructionsToRemove); ILInlining.InlineIfPossible(body, ref pos, context); return(true); } } // Put in a limit so that we don't consume too much memory if the code allocates a huge array // and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler! // const int maxConsecutiveDefaultValueExpressions = 300; // var operands = new List<ILInstruction>(); // int numberOfInstructionsToRemove = 0; // for (int j = pos + 1; j < body.Instructions.Count; j++) { // var nextExpr = body.Instructions[j] as Void; // int arrayPos; // if (nextExpr != null && nextExpr is a.IsStoreToArray() && // nextExpr.Arguments[0].Match(ILCode.Ldloc, out v3) && // v == v3 && // nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) && // arrayPos >= operands.Count && // arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions && // !nextExpr.Arguments[2].ContainsReferenceTo(v3)) // { // while (operands.Count < arrayPos) // operands.Add(new ILExpression(ILCode.DefaultValue, elementType)); // operands.Add(nextExpr.Arguments[2]); // numberOfInstructionsToRemove++; // } else { // break; // } // } } return(false); }