You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
10 KiB

  1. // Copyright (c) 2016 Daniel Grunwald
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System.Collections.Generic;
  19. using System.Linq;
  20. using System.Threading;
  21. using ICSharpCode.Decompiler.FlowAnalysis;
  22. using ICSharpCode.Decompiler.TypeSystem;
  23. using ICSharpCode.Decompiler.Util;
  24. namespace ICSharpCode.Decompiler.IL.Transforms
  25. {
  26. /// <summary>
  27. /// Live range splitting for IL variables.
  28. /// </summary>
  29. public class SplitVariables : IILTransform
  30. {
  31. public void Run(ILFunction function, ILTransformContext context)
  32. {
  33. var groupStores = new GroupStores(function, context.CancellationToken);
  34. function.Body.AcceptVisitor(groupStores);
  35. // Replace analyzed variables with their split versions:
  36. foreach (var inst in function.Descendants.OfType<IInstructionWithVariableOperand>())
  37. {
  38. if (groupStores.IsAnalyzedVariable(inst.Variable))
  39. {
  40. inst.Variable = groupStores.GetNewVariable(inst);
  41. }
  42. }
  43. function.Variables.RemoveDead();
  44. }
  45. static bool IsCandidateVariable(ILVariable v)
  46. {
  47. switch (v.Kind)
  48. {
  49. case VariableKind.Local:
  50. foreach (var ldloca in v.AddressInstructions)
  51. {
  52. if (DetermineAddressUse(ldloca, ldloca.Variable) == AddressUse.Unknown)
  53. {
  54. // If we don't understand how the address is being used,
  55. // we can't split the variable.
  56. return false;
  57. }
  58. }
  59. return true;
  60. case VariableKind.StackSlot:
  61. // stack slots: are already split by construction,
  62. // except for the locals-turned-stackslots in async functions
  63. // or stack slots handled by the infeasible path transform
  64. if (v.Function.IsAsync || v.RemoveIfRedundant)
  65. goto case VariableKind.Local;
  66. else
  67. return false;
  68. default:
  69. // parameters: avoid splitting parameters
  70. // pinned locals: must not split (doing so would extend the life of the pin to the end of the method)
  71. return false;
  72. }
  73. }
  74. enum AddressUse
  75. {
  76. Unknown,
  77. /// <summary>
  78. /// Address is immediately used for reading and/or writing,
  79. /// without the possibility of the variable being directly stored to (via 'stloc')
  80. /// in between the 'ldloca' and the use of the address.
  81. /// </summary>
  82. Immediate,
  83. /// <summary>
  84. /// We support some limited form of ref locals referring to a target variable,
  85. /// without giving up splitting of the target variable.
  86. /// Requirements:
  87. /// * the ref local is single-assignment
  88. /// * the ref local is initialized directly with 'ldloca target; stloc ref_local',
  89. /// not a derived pointer (e.g. 'ldloca target; ldflda F; stloc ref_local').
  90. /// * all uses of the ref_local are immediate.
  91. /// There may be stores to the target variable in between the 'stloc ref_local' and its uses,
  92. /// but we handle that case by treating each use of the ref_local as an address access
  93. /// of the target variable (as if the ref_local was eliminated via copy propagation).
  94. /// </summary>
  95. WithSupportedRefLocals,
  96. }
  97. static AddressUse DetermineAddressUse(ILInstruction addressLoadingInstruction, ILVariable targetVar)
  98. {
  99. switch (addressLoadingInstruction.Parent)
  100. {
  101. case LdObj _:
  102. case StObj stobj when stobj.Target == addressLoadingInstruction:
  103. return AddressUse.Immediate;
  104. case LdFlda ldflda:
  105. return DetermineAddressUse(ldflda, targetVar);
  106. case Await await:
  107. // GetAwaiter() may write to the struct, but shouldn't store the address for later use
  108. return AddressUse.Immediate;
  109. case CallInstruction call:
  110. return HandleCall(addressLoadingInstruction, targetVar, call);
  111. case StLoc stloc when stloc.Variable.IsSingleDefinition:
  112. // Address stored in local variable: also check all uses of that variable.
  113. if (!(stloc.Variable.Kind == VariableKind.StackSlot || stloc.Variable.Kind == VariableKind.Local))
  114. return AddressUse.Unknown;
  115. var value = stloc.Value;
  116. while (value is LdFlda ldFlda)
  117. {
  118. value = ldFlda.Target;
  119. }
  120. if (value.OpCode != OpCode.LdLoca)
  121. {
  122. // GroupStores only handles ref-locals correctly when they are supported by GetAddressLoadForRefLocalUse(),
  123. // which only works for ldflda*(ldloca)
  124. return AddressUse.Unknown;
  125. }
  126. foreach (var load in stloc.Variable.LoadInstructions)
  127. {
  128. if (DetermineAddressUse(load, targetVar) != AddressUse.Immediate)
  129. return AddressUse.Unknown;
  130. }
  131. return AddressUse.WithSupportedRefLocals;
  132. default:
  133. return AddressUse.Unknown;
  134. }
  135. }
  136. static AddressUse HandleCall(ILInstruction addressLoadingInstruction, ILVariable targetVar, CallInstruction call)
  137. {
  138. // Address is passed to method.
  139. // We'll assume the method only uses the address locally,
  140. // unless we can see an address being returned from the method:
  141. IType returnType = (call is NewObj) ? call.Method.DeclaringType : call.Method.ReturnType;
  142. if (returnType.IsByRefLike)
  143. {
  144. // If the address is returned from the method, it check whether it's consumed immediately.
  145. // This can still be fine, as long as we also check the consumer's other arguments for 'stloc targetVar'.
  146. if (DetermineAddressUse(call, targetVar) != AddressUse.Immediate)
  147. return AddressUse.Unknown;
  148. }
  149. foreach (var p in call.Method.Parameters)
  150. {
  151. // catch "out Span<int>" and similar
  152. if (p.Type.SkipModifiers() is ByReferenceType brt && brt.ElementType.IsByRefLike)
  153. return AddressUse.Unknown;
  154. }
  155. // ensure there's no 'stloc target' in between the ldloca and the call consuming the address
  156. for (int i = addressLoadingInstruction.ChildIndex + 1; i < call.Arguments.Count; i++)
  157. {
  158. foreach (var inst in call.Arguments[i].Descendants)
  159. {
  160. if (inst is StLoc store && store.Variable == targetVar)
  161. return AddressUse.Unknown;
  162. }
  163. }
  164. return AddressUse.Immediate;
  165. }
  166. /// <summary>
  167. /// Given 'ldloc ref_local' and 'ldloca target; stloc ref_local', returns the ldloca.
  168. /// This function must return a non-null LdLoca for every use of a SupportedRefLocal.
  169. /// </summary>
  170. static LdLoca GetAddressLoadForRefLocalUse(LdLoc ldloc)
  171. {
  172. if (!ldloc.Variable.IsSingleDefinition)
  173. return null; // only single-definition variables can be supported ref locals
  174. var store = ldloc.Variable.StoreInstructions.SingleOrDefault();
  175. if (store is StLoc stloc)
  176. {
  177. var value = stloc.Value;
  178. while (value is LdFlda ldFlda)
  179. {
  180. value = ldFlda.Target;
  181. }
  182. return value as LdLoca;
  183. }
  184. return null;
  185. }
  186. /// <summary>
  187. /// Use the union-find structure to merge
  188. /// </summary>
  189. /// <remarks>
  190. /// Instructions in a group are stores to the same variable that must stay together (cannot be split).
  191. /// </remarks>
  192. class GroupStores : ReachingDefinitionsVisitor
  193. {
  194. readonly UnionFind<IInstructionWithVariableOperand> unionFind = new UnionFind<IInstructionWithVariableOperand>();
  195. /// <summary>
  196. /// For each uninitialized variable, one representative instruction that
  197. /// potentially observes the unintialized value of the variable.
  198. /// Used to merge together all such loads of the same uninitialized value.
  199. /// </summary>
  200. readonly Dictionary<ILVariable, IInstructionWithVariableOperand> uninitVariableUsage = new Dictionary<ILVariable, IInstructionWithVariableOperand>();
  201. public GroupStores(ILFunction scope, CancellationToken cancellationToken) : base(scope, IsCandidateVariable, cancellationToken)
  202. {
  203. }
  204. protected internal override void VisitLdLoc(LdLoc inst)
  205. {
  206. base.VisitLdLoc(inst);
  207. HandleLoad(inst);
  208. var refLocalAddressLoad = GetAddressLoadForRefLocalUse(inst);
  209. if (refLocalAddressLoad != null)
  210. {
  211. // SupportedRefLocal: act as if we copy-propagated the ldloca
  212. // to the point of use:
  213. HandleLoad(refLocalAddressLoad);
  214. }
  215. }
  216. protected internal override void VisitLdLoca(LdLoca inst)
  217. {
  218. base.VisitLdLoca(inst);
  219. HandleLoad(inst);
  220. }
  221. void HandleLoad(IInstructionWithVariableOperand inst)
  222. {
  223. if (IsAnalyzedVariable(inst.Variable))
  224. {
  225. if (IsPotentiallyUninitialized(state, inst.Variable))
  226. {
  227. // merge all uninit loads together:
  228. if (uninitVariableUsage.TryGetValue(inst.Variable, out var uninitLoad))
  229. {
  230. unionFind.Merge(inst, uninitLoad);
  231. }
  232. else
  233. {
  234. uninitVariableUsage.Add(inst.Variable, inst);
  235. }
  236. }
  237. foreach (var store in GetStores(state, inst.Variable))
  238. {
  239. unionFind.Merge(inst, (IInstructionWithVariableOperand)store);
  240. }
  241. }
  242. }
  243. readonly Dictionary<IInstructionWithVariableOperand, ILVariable> newVariables = new Dictionary<IInstructionWithVariableOperand, ILVariable>();
  244. /// <summary>
  245. /// Gets the new variable for a LdLoc, StLoc or TryCatchHandler instruction.
  246. /// </summary>
  247. internal ILVariable GetNewVariable(IInstructionWithVariableOperand inst)
  248. {
  249. var representative = unionFind.Find(inst);
  250. ILVariable v;
  251. if (!newVariables.TryGetValue(representative, out v))
  252. {
  253. v = new ILVariable(inst.Variable.Kind, inst.Variable.Type, inst.Variable.StackType, inst.Variable.Index);
  254. v.Name = inst.Variable.Name;
  255. v.HasGeneratedName = inst.Variable.HasGeneratedName;
  256. v.StateMachineField = inst.Variable.StateMachineField;
  257. v.InitialValueIsInitialized = inst.Variable.InitialValueIsInitialized;
  258. v.UsesInitialValue = false; // we'll set UsesInitialValue when we encounter an uninit load
  259. v.RemoveIfRedundant = inst.Variable.RemoveIfRedundant;
  260. newVariables.Add(representative, v);
  261. inst.Variable.Function.Variables.Add(v);
  262. }
  263. if (inst.Variable.UsesInitialValue && uninitVariableUsage.TryGetValue(inst.Variable, out var uninitLoad) && uninitLoad == inst)
  264. {
  265. v.UsesInitialValue = true;
  266. }
  267. return v;
  268. }
  269. }
  270. }
  271. }