PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/DLR_Main/Runtime/Microsoft.Dynamic/Interpreter/Instructions/ControlFlowInstructions.cs

https://bitbucket.org/mdavid/dlr
C# | 580 lines | 371 code | 116 blank | 93 comment | 37 complexity | 884b49ac399acaa7a9a4be7679452cf9 MD5 | raw file
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. #if !CLR2
  16. using System.Linq.Expressions;
  17. #endif
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Runtime.CompilerServices;
  22. using System.Threading;
  23. using Microsoft.Scripting.Ast;
  24. using Microsoft.Scripting.Utils;
  25. namespace Microsoft.Scripting.Interpreter {
  26. using LoopFunc = Func<object[], StrongBox<object>[], InterpretedFrame, int>;
  27. internal abstract class OffsetInstruction : Instruction {
  28. internal const int Unknown = Int32.MinValue;
  29. internal const int CacheSize = 32;
  30. // the offset to jump to (relative to this instruction):
  31. protected int _offset = Unknown;
  32. public int Offset { get { return _offset; } }
  33. public abstract Instruction[] Cache { get; }
  34. public Instruction Fixup(int offset) {
  35. Debug.Assert(_offset == Unknown && offset != Unknown);
  36. _offset = offset;
  37. var cache = Cache;
  38. if (cache != null && offset >= 0 && offset < cache.Length) {
  39. return cache[offset] ?? (cache[offset] = this);
  40. }
  41. return this;
  42. }
  43. public override string ToDebugString(int instructionIndex, object cookie, Func<int, int> labelIndexer, IList<object> objects) {
  44. return ToString() + (_offset != Unknown ? " -> " + (instructionIndex + _offset).ToString() : "");
  45. }
  46. public override string ToString() {
  47. return InstructionName + (_offset == Unknown ? "(?)" : "(" + _offset + ")");
  48. }
  49. }
  50. internal sealed class BranchFalseInstruction : OffsetInstruction {
  51. private static Instruction[] _cache;
  52. public override Instruction[] Cache {
  53. get {
  54. if (_cache == null) {
  55. _cache = new Instruction[CacheSize];
  56. }
  57. return _cache;
  58. }
  59. }
  60. internal BranchFalseInstruction() {
  61. }
  62. public override int ConsumedStack { get { return 1; } }
  63. public override int Run(InterpretedFrame frame) {
  64. Debug.Assert(_offset != Unknown);
  65. if (!(bool)frame.Pop()) {
  66. return _offset;
  67. }
  68. return +1;
  69. }
  70. }
  71. internal sealed class BranchTrueInstruction : OffsetInstruction {
  72. private static Instruction[] _cache;
  73. public override Instruction[] Cache {
  74. get {
  75. if (_cache == null) {
  76. _cache = new Instruction[CacheSize];
  77. }
  78. return _cache;
  79. }
  80. }
  81. internal BranchTrueInstruction() {
  82. }
  83. public override int ConsumedStack { get { return 1; } }
  84. public override int Run(InterpretedFrame frame) {
  85. Debug.Assert(_offset != Unknown);
  86. if ((bool)frame.Pop()) {
  87. return _offset;
  88. }
  89. return +1;
  90. }
  91. }
  92. internal sealed class CoalescingBranchInstruction : OffsetInstruction {
  93. private static Instruction[] _cache;
  94. public override Instruction[] Cache {
  95. get {
  96. if (_cache == null) {
  97. _cache = new Instruction[CacheSize];
  98. }
  99. return _cache;
  100. }
  101. }
  102. internal CoalescingBranchInstruction() {
  103. }
  104. public override int ConsumedStack { get { return 1; } }
  105. public override int ProducedStack { get { return 1; } }
  106. public override int Run(InterpretedFrame frame) {
  107. Debug.Assert(_offset != Unknown);
  108. if (frame.Peek() != null) {
  109. return _offset;
  110. }
  111. return +1;
  112. }
  113. }
  114. internal class BranchInstruction : OffsetInstruction {
  115. private static Instruction[][][] _caches;
  116. public override Instruction[] Cache {
  117. get {
  118. if (_caches == null) {
  119. _caches = new Instruction[2][][] { new Instruction[2][], new Instruction[2][] };
  120. }
  121. return _caches[ConsumedStack][ProducedStack] ?? (_caches[ConsumedStack][ProducedStack] = new Instruction[CacheSize]);
  122. }
  123. }
  124. internal readonly bool _hasResult;
  125. internal readonly bool _hasValue;
  126. internal BranchInstruction()
  127. : this(false, false) {
  128. }
  129. public BranchInstruction(bool hasResult, bool hasValue) {
  130. _hasResult = hasResult;
  131. _hasValue = hasValue;
  132. }
  133. public override int ConsumedStack {
  134. get { return _hasValue ? 1 : 0; }
  135. }
  136. public override int ProducedStack {
  137. get { return _hasResult ? 1 : 0; }
  138. }
  139. public override int Run(InterpretedFrame frame) {
  140. Debug.Assert(_offset != Unknown);
  141. return _offset;
  142. }
  143. }
  144. internal abstract class IndexedBranchInstruction : Instruction {
  145. protected const int CacheSize = 32;
  146. internal readonly int _labelIndex;
  147. public IndexedBranchInstruction(int labelIndex) {
  148. _labelIndex = labelIndex;
  149. }
  150. public RuntimeLabel GetLabel(InterpretedFrame frame) {
  151. return frame.Interpreter._labels[_labelIndex];
  152. }
  153. public override string ToDebugString(int instructionIndex, object cookie, Func<int, int> labelIndexer, IList<object> objects) {
  154. int targetIndex = labelIndexer(_labelIndex);
  155. return ToString() + (targetIndex != BranchLabel.UnknownIndex ? " -> " + targetIndex.ToString() : "");
  156. }
  157. public override string ToString() {
  158. return InstructionName + "[" + _labelIndex + "]";
  159. }
  160. }
  161. /// <summary>
  162. /// This instruction implements a goto expression that can jump out of any expression.
  163. /// It pops values (arguments) from the evaluation stack that the expression tree nodes in between
  164. /// the goto expression and the target label node pushed and not consumed yet.
  165. /// A goto expression can jump into a node that evaluates arguments only if it carries
  166. /// a value and jumps right after the first argument (the carried value will be used as the first argument).
  167. /// Goto can jump into an arbitrary child of a BlockExpression since the block doesn�t accumulate values
  168. /// on evaluation stack as its child expressions are being evaluated.
  169. ///
  170. /// Goto needs to execute any finally blocks on the way to the target label.
  171. /// <example>
  172. /// {
  173. /// f(1, 2, try { g(3, 4, try { goto L } finally { ... }, 6) } finally { ... }, 7, 8)
  174. /// L: ...
  175. /// }
  176. /// </example>
  177. /// The goto expression here jumps to label L while having 4 items on evaluation stack (1, 2, 3 and 4).
  178. /// The jump needs to execute both finally blocks, the first one on stack level 4 the
  179. /// second one on stack level 2. So, it needs to jump the first finally block, pop 2 items from the stack,
  180. /// run second finally block and pop another 2 items from the stack and set instruction pointer to label L.
  181. ///
  182. /// Goto also needs to rethrow ThreadAbortException iff it jumps out of a catch handler and
  183. /// the current thread is in "abort requested" state.
  184. /// </summary>
  185. internal sealed class GotoInstruction : IndexedBranchInstruction {
  186. private const int Variants = 4;
  187. private static readonly GotoInstruction[] Cache = new GotoInstruction[Variants * CacheSize];
  188. private readonly bool _hasResult;
  189. // TODO: We can remember hasValue in label and look it up when calculating stack balance. That would save some cache.
  190. private readonly bool _hasValue;
  191. // The values should technically be Consumed = 1, Produced = 1 for gotos that target a label whose continuation depth
  192. // is different from the current continuation depth. However, in case of forward gotos, we don't not know that is the
  193. // case until the label is emitted. By then the consumed and produced stack information is useless.
  194. // The important thing here is that the stack balance is 0.
  195. public override int ConsumedContinuations { get { return 0; } }
  196. public override int ProducedContinuations { get { return 0; } }
  197. public override int ConsumedStack {
  198. get { return _hasValue ? 1 : 0; }
  199. }
  200. public override int ProducedStack {
  201. get { return _hasResult ? 1 : 0; }
  202. }
  203. private GotoInstruction(int targetIndex, bool hasResult, bool hasValue)
  204. : base(targetIndex) {
  205. _hasResult = hasResult;
  206. _hasValue = hasValue;
  207. }
  208. internal static GotoInstruction Create(int labelIndex, bool hasResult, bool hasValue) {
  209. if (labelIndex < CacheSize) {
  210. var index = Variants * labelIndex | (hasResult ? 2 : 0) | (hasValue ? 1 : 0);
  211. return Cache[index] ?? (Cache[index] = new GotoInstruction(labelIndex, hasResult, hasValue));
  212. }
  213. return new GotoInstruction(labelIndex, hasResult, hasValue);
  214. }
  215. public override int Run(InterpretedFrame frame) {
  216. // Are we jumping out of catch/finally while aborting the current thread?
  217. Interpreter.AbortThreadIfRequested(frame, _labelIndex);
  218. // goto the target label or the current finally continuation:
  219. return frame.Goto(_labelIndex, _hasValue ? frame.Pop() : Interpreter.NoValue);
  220. }
  221. }
  222. internal sealed class EnterTryFinallyInstruction : IndexedBranchInstruction {
  223. private readonly static EnterTryFinallyInstruction[] Cache = new EnterTryFinallyInstruction[CacheSize];
  224. public override int ProducedContinuations { get { return 1; } }
  225. private EnterTryFinallyInstruction(int targetIndex)
  226. : base(targetIndex) {
  227. }
  228. internal static EnterTryFinallyInstruction Create(int labelIndex) {
  229. if (labelIndex < CacheSize) {
  230. return Cache[labelIndex] ?? (Cache[labelIndex] = new EnterTryFinallyInstruction(labelIndex));
  231. }
  232. return new EnterTryFinallyInstruction(labelIndex);
  233. }
  234. public override int Run(InterpretedFrame frame) {
  235. // Push finally.
  236. frame.PushContinuation(_labelIndex);
  237. return 1;
  238. }
  239. }
  240. /// <summary>
  241. /// The first instruction of finally block.
  242. /// </summary>
  243. internal sealed class EnterFinallyInstruction : Instruction {
  244. internal static readonly Instruction Instance = new EnterFinallyInstruction();
  245. public override int ProducedStack { get { return 2; } }
  246. public override int ConsumedContinuations { get { return 1; } }
  247. private EnterFinallyInstruction() {
  248. }
  249. public override int Run(InterpretedFrame frame) {
  250. frame.PushPendingContinuation();
  251. frame.RemoveContinuation();
  252. return 1;
  253. }
  254. }
  255. /// <summary>
  256. /// The last instruction of finally block.
  257. /// </summary>
  258. internal sealed class LeaveFinallyInstruction : Instruction {
  259. internal static readonly Instruction Instance = new LeaveFinallyInstruction();
  260. public override int ConsumedStack { get { return 2; } }
  261. private LeaveFinallyInstruction() {
  262. }
  263. public override int Run(InterpretedFrame frame) {
  264. frame.PopPendingContinuation();
  265. // jump to goto target or to the next finally:
  266. return frame.YieldToPendingContinuation();
  267. }
  268. }
  269. // no-op: we need this just to balance the stack depth.
  270. internal sealed class EnterExceptionHandlerInstruction : Instruction {
  271. internal static readonly EnterExceptionHandlerInstruction Void = new EnterExceptionHandlerInstruction(false);
  272. internal static readonly EnterExceptionHandlerInstruction NonVoid = new EnterExceptionHandlerInstruction(true);
  273. // True if try-expression is non-void.
  274. private readonly bool _hasValue;
  275. private EnterExceptionHandlerInstruction(bool hasValue) {
  276. _hasValue = hasValue;
  277. }
  278. // If an exception is throws in try-body the expression result of try-body is not evaluated and loaded to the stack.
  279. // So the stack doesn't contain the try-body's value when we start executing the handler.
  280. // However, while emitting instructions try block falls thru the catch block with a value on stack.
  281. // We need to declare it consumed so that the stack state upon entry to the handler corresponds to the real
  282. // stack depth after throw jumped to this catch block.
  283. public override int ConsumedStack { get { return _hasValue ? 1 : 0; } }
  284. // A variable storing the current exception is pushed to the stack by exception handling.
  285. // Catch handlers: The value is immediately popped and stored into a local.
  286. // Fault handlers: The value is kept on stack during fault handler evaluation.
  287. public override int ProducedStack { get { return 1; } }
  288. public override int Run(InterpretedFrame frame) {
  289. // nop (the exception value is pushed by the interpreter in HandleCatch)
  290. return 1;
  291. }
  292. }
  293. /// <summary>
  294. /// The last instruction of a catch exception handler.
  295. /// </summary>
  296. internal sealed class LeaveExceptionHandlerInstruction : IndexedBranchInstruction {
  297. private static LeaveExceptionHandlerInstruction[] Cache = new LeaveExceptionHandlerInstruction[2 * CacheSize];
  298. private readonly bool _hasValue;
  299. // The catch block yields a value if the body is non-void. This value is left on the stack.
  300. public override int ConsumedStack {
  301. get { return _hasValue ? 1 : 0; }
  302. }
  303. public override int ProducedStack {
  304. get { return _hasValue ? 1 : 0; }
  305. }
  306. private LeaveExceptionHandlerInstruction(int labelIndex, bool hasValue)
  307. : base(labelIndex) {
  308. _hasValue = hasValue;
  309. }
  310. internal static LeaveExceptionHandlerInstruction Create(int labelIndex, bool hasValue) {
  311. if (labelIndex < CacheSize) {
  312. int index = (2 * labelIndex) | (hasValue ? 1 : 0);
  313. return Cache[index] ?? (Cache[index] = new LeaveExceptionHandlerInstruction(labelIndex, hasValue));
  314. }
  315. return new LeaveExceptionHandlerInstruction(labelIndex, hasValue);
  316. }
  317. public override int Run(InterpretedFrame frame) {
  318. // CLR rethrows ThreadAbortException when leaving catch handler if abort is requested on the current thread.
  319. Interpreter.AbortThreadIfRequested(frame, _labelIndex);
  320. return GetLabel(frame).Index - frame.InstructionIndex;
  321. }
  322. }
  323. /// <summary>
  324. /// The last instruction of a fault exception handler.
  325. /// </summary>
  326. internal sealed class LeaveFaultInstruction : Instruction {
  327. internal static readonly Instruction NonVoid = new LeaveFaultInstruction(true);
  328. internal static readonly Instruction Void = new LeaveFaultInstruction(false);
  329. private readonly bool _hasValue;
  330. // The fault block has a value if the body is non-void, but the value is never used.
  331. // We compile the body of a fault block as void.
  332. // However, we keep the exception object that was pushed upon entering the fault block on the stack during execution of the block
  333. // and pop it at the end.
  334. public override int ConsumedStack {
  335. get { return 1; }
  336. }
  337. // While emitting instructions a non-void try-fault expression is expected to produce a value.
  338. public override int ProducedStack {
  339. get { return _hasValue ? 1 : 0; }
  340. }
  341. private LeaveFaultInstruction(bool hasValue) {
  342. _hasValue = hasValue;
  343. }
  344. public override int Run(InterpretedFrame frame) {
  345. // TODO: ThreadAbortException ?
  346. object exception = frame.Pop();
  347. ExceptionHandler handler;
  348. return frame.Interpreter.GotoHandler(frame, exception, out handler);
  349. }
  350. }
  351. internal sealed class ThrowInstruction : Instruction {
  352. internal static readonly ThrowInstruction Throw = new ThrowInstruction(true, false);
  353. internal static readonly ThrowInstruction VoidThrow = new ThrowInstruction(false, false);
  354. internal static readonly ThrowInstruction Rethrow = new ThrowInstruction(true, true);
  355. internal static readonly ThrowInstruction VoidRethrow = new ThrowInstruction(false, true);
  356. private readonly bool _hasResult, _rethrow;
  357. private ThrowInstruction(bool hasResult, bool isRethrow) {
  358. _hasResult = hasResult;
  359. _rethrow = isRethrow;
  360. }
  361. public override int ProducedStack {
  362. get { return _hasResult ? 1 : 0; }
  363. }
  364. public override int ConsumedStack {
  365. get {
  366. return 1;
  367. }
  368. }
  369. public override int Run(InterpretedFrame frame) {
  370. var ex = (Exception)frame.Pop();
  371. if (_rethrow) {
  372. ExceptionHandler handler;
  373. return frame.Interpreter.GotoHandler(frame, ex, out handler);
  374. }
  375. throw ex;
  376. }
  377. }
  378. internal sealed class SwitchInstruction : Instruction {
  379. private readonly Dictionary<int, int> _cases;
  380. internal SwitchInstruction(Dictionary<int, int> cases) {
  381. Assert.NotNull(cases);
  382. _cases = cases;
  383. }
  384. public override int ConsumedStack { get { return 1; } }
  385. public override int ProducedStack { get { return 0; } }
  386. public override int Run(InterpretedFrame frame) {
  387. int target;
  388. return _cases.TryGetValue((int)frame.Pop(), out target) ? target : 1;
  389. }
  390. }
  391. internal sealed class EnterLoopInstruction : Instruction {
  392. private readonly int _instructionIndex;
  393. private Dictionary<ParameterExpression, LocalVariable> _variables;
  394. private Dictionary<ParameterExpression, LocalVariable> _closureVariables;
  395. private LoopExpression _loop;
  396. private int _loopEnd;
  397. private int _compilationThreshold;
  398. internal EnterLoopInstruction(LoopExpression loop, LocalVariables locals, int compilationThreshold, int instructionIndex) {
  399. _loop = loop;
  400. _variables = locals.CopyLocals();
  401. _closureVariables = locals.ClosureVariables;
  402. _compilationThreshold = compilationThreshold;
  403. _instructionIndex = instructionIndex;
  404. }
  405. internal void FinishLoop(int loopEnd) {
  406. _loopEnd = loopEnd;
  407. }
  408. public override int Run(InterpretedFrame frame) {
  409. // Don't lock here, it's a frequently hit path.
  410. //
  411. // There could be multiple threads racing, but that is okay.
  412. // Two bad things can happen:
  413. // * We miss decrements (some thread sets the counter forward)
  414. // * We might enter the "if" branch more than once.
  415. //
  416. // The first is okay, it just means we take longer to compile.
  417. // The second we explicitly guard against inside of Compile().
  418. //
  419. // We can't miss 0. The first thread that writes -1 must have read 0 and hence start compilation.
  420. if (unchecked(_compilationThreshold--) == 0) {
  421. if (frame.Interpreter.CompileSynchronously) {
  422. Compile(frame);
  423. } else {
  424. // Kick off the compile on another thread so this one can keep going
  425. ThreadPool.QueueUserWorkItem(Compile, frame);
  426. }
  427. }
  428. return 1;
  429. }
  430. private bool Compiled {
  431. get { return _loop == null; }
  432. }
  433. private void Compile(object frameObj) {
  434. if (Compiled) {
  435. return;
  436. }
  437. lock (this) {
  438. if (Compiled) {
  439. return;
  440. }
  441. PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Interpreted loop compiled");
  442. InterpretedFrame frame = (InterpretedFrame)frameObj;
  443. var compiler = new LoopCompiler(_loop, frame.Interpreter.LabelMapping, _variables, _closureVariables, _instructionIndex, _loopEnd);
  444. var instructions = frame.Interpreter.Instructions.Instructions;
  445. // replace this instruction with an optimized one:
  446. instructions[_instructionIndex] = new CompiledLoopInstruction(compiler.CreateDelegate());
  447. // invalidate this instruction, some threads may still hold on it:
  448. _loop = null;
  449. _variables = null;
  450. _closureVariables = null;
  451. }
  452. }
  453. }
  454. internal sealed class CompiledLoopInstruction : Instruction {
  455. private readonly LoopFunc _compiledLoop;
  456. public CompiledLoopInstruction(LoopFunc compiledLoop) {
  457. Assert.NotNull(compiledLoop);
  458. _compiledLoop = compiledLoop;
  459. }
  460. public override int Run(InterpretedFrame frame) {
  461. return _compiledLoop(frame.Data, frame.Closure, frame);
  462. }
  463. }
  464. }