1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
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.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using System
.Threading
.Tasks
;
21 using System
.Linq
.Expressions
;
25 using System
.Collections
.Generic
;
26 using System
.Diagnostics
;
27 using System
.Runtime
.CompilerServices
;
28 using System
.Threading
;
29 using Microsoft
.Scripting
.Ast
;
30 using Microsoft
.Scripting
.Utils
;
32 namespace Microsoft
.Scripting
.Interpreter
{
33 using LoopFunc
= Func
<object[], StrongBox
<object>[], InterpretedFrame
, int>;
35 internal abstract class OffsetInstruction
: Instruction
{
36 internal const int Unknown
= Int32
.MinValue
;
37 internal const int CacheSize
= 32;
39 // the offset to jump to (relative to this instruction):
40 protected int _offset
= Unknown
;
42 public int Offset { get { return _offset; }
}
43 public abstract Instruction
[] Cache { get; }
45 public Instruction
Fixup(int offset
) {
46 Debug
.Assert(_offset
== Unknown
&& offset
!= Unknown
);
50 if (cache
!= null && offset
>= 0 && offset
< cache
.Length
) {
51 return cache
[offset
] ?? (cache
[offset
] = this);
57 public override string ToDebugString(int instructionIndex
, object cookie
, Func
<int, int> labelIndexer
, IList
<object> objects
) {
58 return ToString() + (_offset
!= Unknown
? " -> " + (instructionIndex
+ _offset
).ToString() : "");
61 public override string ToString() {
62 return InstructionName
+ (_offset
== Unknown
? "(?)" : "(" + _offset
+ ")");
66 internal sealed class BranchFalseInstruction
: OffsetInstruction
{
67 private static Instruction
[] _cache
;
69 public override Instruction
[] Cache
{
72 _cache
= new Instruction
[CacheSize
];
78 internal BranchFalseInstruction() {
81 public override int ConsumedStack { get { return 1; }
}
83 public override int Run(InterpretedFrame frame
) {
84 Debug
.Assert(_offset
!= Unknown
);
86 if (!(bool)frame
.Pop()) {
94 internal sealed class BranchTrueInstruction
: OffsetInstruction
{
95 private static Instruction
[] _cache
;
97 public override Instruction
[] Cache
{
100 _cache
= new Instruction
[CacheSize
];
106 internal BranchTrueInstruction() {
109 public override int ConsumedStack { get { return 1; }
}
111 public override int Run(InterpretedFrame frame
) {
112 Debug
.Assert(_offset
!= Unknown
);
114 if ((bool)frame
.Pop()) {
122 internal sealed class BranchNullInstruction
: OffsetInstruction
{
123 private static Instruction
[] _cache
;
125 public override Instruction
[] Cache
{
127 if (_cache
== null) {
128 _cache
= new Instruction
[CacheSize
];
134 internal BranchNullInstruction() {
137 public override int ConsumedStack { get { return 1; }
}
139 public override int Run(InterpretedFrame frame
) {
140 Debug
.Assert(_offset
!= Unknown
);
142 if (frame
.Pop() == null) {
150 internal sealed class CoalescingBranchInstruction
: OffsetInstruction
{
151 private static Instruction
[] _cache
;
153 public override Instruction
[] Cache
{
155 if (_cache
== null) {
156 _cache
= new Instruction
[CacheSize
];
162 internal CoalescingBranchInstruction() {
165 public override int ConsumedStack { get { return 1; }
}
166 public override int ProducedStack { get { return 1; }
}
168 public override int Run(InterpretedFrame frame
) {
169 Debug
.Assert(_offset
!= Unknown
);
171 if (frame
.Peek() != null) {
179 internal class BranchInstruction
: OffsetInstruction
{
180 private static Instruction
[][][] _caches
;
182 public override Instruction
[] Cache
{
184 if (_caches
== null) {
185 _caches
= new Instruction
[2][][] { new Instruction[2][], new Instruction[2][] }
;
187 return _caches
[ConsumedStack
][ProducedStack
] ?? (_caches
[ConsumedStack
][ProducedStack
] = new Instruction
[CacheSize
]);
191 internal readonly bool _hasResult
;
192 internal readonly bool _hasValue
;
194 internal BranchInstruction()
195 : this(false, false) {
198 public BranchInstruction(bool hasResult
, bool hasValue
) {
199 _hasResult
= hasResult
;
200 _hasValue
= hasValue
;
203 public override int ConsumedStack
{
204 get { return _hasValue ? 1 : 0; }
207 public override int ProducedStack
{
208 get { return _hasResult ? 1 : 0; }
211 public override int Run(InterpretedFrame frame
) {
212 Debug
.Assert(_offset
!= Unknown
);
218 internal abstract class IndexedBranchInstruction
: Instruction
{
219 protected const int CacheSize
= 32;
221 internal readonly int _labelIndex
;
223 public IndexedBranchInstruction(int labelIndex
) {
224 _labelIndex
= labelIndex
;
227 public RuntimeLabel
GetLabel(InterpretedFrame frame
) {
228 return frame
.Interpreter
._labels
[_labelIndex
];
231 public override string ToDebugString(int instructionIndex
, object cookie
, Func
<int, int> labelIndexer
, IList
<object> objects
) {
232 int targetIndex
= labelIndexer(_labelIndex
);
233 return ToString() + (targetIndex
!= BranchLabel
.UnknownIndex
? " -> " + targetIndex
.ToString() : "");
236 public override string ToString() {
237 return InstructionName
+ "[" + _labelIndex
+ "]";
242 /// This instruction implements a goto expression that can jump out of any expression.
243 /// It pops values (arguments) from the evaluation stack that the expression tree nodes in between
244 /// the goto expression and the target label node pushed and not consumed yet.
245 /// A goto expression can jump into a node that evaluates arguments only if it carries
246 /// a value and jumps right after the first argument (the carried value will be used as the first argument).
247 /// Goto can jump into an arbitrary child of a BlockExpression since the block doesn’t accumulate values
248 /// on evaluation stack as its child expressions are being evaluated.
250 /// Goto needs to execute any finally blocks on the way to the target label.
253 /// f(1, 2, try { g(3, 4, try { goto L } finally { ... }, 6) } finally { ... }, 7, 8)
257 /// The goto expression here jumps to label L while having 4 items on evaluation stack (1, 2, 3 and 4).
258 /// The jump needs to execute both finally blocks, the first one on stack level 4 the
259 /// second one on stack level 2. So, it needs to jump the first finally block, pop 2 items from the stack,
260 /// run second finally block and pop another 2 items from the stack and set instruction pointer to label L.
262 /// Goto also needs to rethrow ThreadAbortException iff it jumps out of a catch handler and
263 /// the current thread is in "abort requested" state.
265 internal sealed class GotoInstruction
: IndexedBranchInstruction
{
266 private const int Variants
= 4;
267 private static readonly GotoInstruction
[] Cache
= new GotoInstruction
[Variants
* CacheSize
];
269 private readonly bool _hasResult
;
271 // TODO: We can remember hasValue in label and look it up when calculating stack balance. That would save some cache.
272 private readonly bool _hasValue
;
274 // The values should technically be Consumed = 1, Produced = 1 for gotos that target a label whose continuation depth
275 // is different from the current continuation depth. However, in case of forward gotos, we don't not know that is the
276 // case until the label is emitted. By then the consumed and produced stack information is useless.
277 // The important thing here is that the stack balance is 0.
278 public override int ConsumedContinuations { get { return 0; }
}
279 public override int ProducedContinuations { get { return 0; }
}
281 public override int ConsumedStack
{
282 get { return _hasValue ? 1 : 0; }
285 public override int ProducedStack
{
286 get { return _hasResult ? 1 : 0; }
289 private GotoInstruction(int targetIndex
, bool hasResult
, bool hasValue
)
290 : base(targetIndex
) {
291 _hasResult
= hasResult
;
292 _hasValue
= hasValue
;
295 internal static GotoInstruction
Create(int labelIndex
, bool hasResult
, bool hasValue
) {
296 if (labelIndex
< CacheSize
) {
297 var index
= Variants
* labelIndex
| (hasResult
? 2 : 0) | (hasValue
? 1 : 0);
298 return Cache
[index
] ?? (Cache
[index
] = new GotoInstruction(labelIndex
, hasResult
, hasValue
));
300 return new GotoInstruction(labelIndex
, hasResult
, hasValue
);
303 public override int Run(InterpretedFrame frame
) {
304 // Are we jumping out of catch/finally while aborting the current thread?
305 Interpreter
.AbortThreadIfRequested(frame
, _labelIndex
);
307 // goto the target label or the current finally continuation:
308 return frame
.Goto(_labelIndex
, _hasValue
? frame
.Pop() : Interpreter
.NoValue
);
312 internal sealed class EnterTryFinallyInstruction
: IndexedBranchInstruction
{
313 private readonly static EnterTryFinallyInstruction
[] Cache
= new EnterTryFinallyInstruction
[CacheSize
];
315 public override int ProducedContinuations { get { return 1; }
}
317 private EnterTryFinallyInstruction(int targetIndex
)
318 : base(targetIndex
) {
321 internal static EnterTryFinallyInstruction
Create(int labelIndex
) {
322 if (labelIndex
< CacheSize
) {
323 return Cache
[labelIndex
] ?? (Cache
[labelIndex
] = new EnterTryFinallyInstruction(labelIndex
));
325 return new EnterTryFinallyInstruction(labelIndex
);
328 public override int Run(InterpretedFrame frame
) {
330 frame
.PushContinuation(_labelIndex
);
336 /// The first instruction of finally block.
338 internal sealed class EnterFinallyInstruction
: Instruction
{
339 internal static readonly Instruction Instance
= new EnterFinallyInstruction();
341 public override int ProducedStack { get { return 2; }
}
342 public override int ConsumedContinuations { get { return 1; }
}
344 private EnterFinallyInstruction() {
347 public override int Run(InterpretedFrame frame
) {
348 frame
.PushPendingContinuation();
349 frame
.RemoveContinuation();
355 /// The last instruction of finally block.
357 internal sealed class LeaveFinallyInstruction
: Instruction
{
358 internal static readonly Instruction Instance
= new LeaveFinallyInstruction();
360 public override int ConsumedStack { get { return 2; }
}
362 private LeaveFinallyInstruction() {
365 public override int Run(InterpretedFrame frame
) {
366 frame
.PopPendingContinuation();
368 // jump to goto target or to the next finally:
369 return frame
.YieldToPendingContinuation();
373 // no-op: we need this just to balance the stack depth.
374 internal sealed class EnterExceptionHandlerInstruction
: Instruction
{
375 internal static readonly EnterExceptionHandlerInstruction Void
= new EnterExceptionHandlerInstruction(false);
376 internal static readonly EnterExceptionHandlerInstruction NonVoid
= new EnterExceptionHandlerInstruction(true);
378 // True if try-expression is non-void.
379 private readonly bool _hasValue
;
381 private EnterExceptionHandlerInstruction(bool hasValue
) {
382 _hasValue
= hasValue
;
385 // If an exception is throws in try-body the expression result of try-body is not evaluated and loaded to the stack.
386 // So the stack doesn't contain the try-body's value when we start executing the handler.
387 // However, while emitting instructions try block falls thru the catch block with a value on stack.
388 // We need to declare it consumed so that the stack state upon entry to the handler corresponds to the real
389 // stack depth after throw jumped to this catch block.
390 public override int ConsumedStack { get { return _hasValue ? 1 : 0; }
}
392 // A variable storing the current exception is pushed to the stack by exception handling.
393 // Catch handlers: The value is immediately popped and stored into a local.
394 // Fault handlers: The value is kept on stack during fault handler evaluation.
395 public override int ProducedStack { get { return 1; }
}
397 public override int Run(InterpretedFrame frame
) {
398 // nop (the exception value is pushed by the interpreter in HandleCatch)
404 /// The last instruction of a catch exception handler.
406 internal sealed class LeaveExceptionHandlerInstruction
: IndexedBranchInstruction
{
407 private static LeaveExceptionHandlerInstruction
[] Cache
= new LeaveExceptionHandlerInstruction
[2 * CacheSize
];
409 private readonly bool _hasValue
;
411 // The catch block yields a value if the body is non-void. This value is left on the stack.
412 public override int ConsumedStack
{
413 get { return _hasValue ? 1 : 0; }
416 public override int ProducedStack
{
417 get { return _hasValue ? 1 : 0; }
420 private LeaveExceptionHandlerInstruction(int labelIndex
, bool hasValue
)
422 _hasValue
= hasValue
;
425 internal static LeaveExceptionHandlerInstruction
Create(int labelIndex
, bool hasValue
) {
426 if (labelIndex
< CacheSize
) {
427 int index
= (2 * labelIndex
) | (hasValue
? 1 : 0);
428 return Cache
[index
] ?? (Cache
[index
] = new LeaveExceptionHandlerInstruction(labelIndex
, hasValue
));
430 return new LeaveExceptionHandlerInstruction(labelIndex
, hasValue
);
433 public override int Run(InterpretedFrame frame
) {
434 // CLR rethrows ThreadAbortException when leaving catch handler if abort is requested on the current thread.
435 Interpreter
.AbortThreadIfRequested(frame
, _labelIndex
);
436 return GetLabel(frame
).Index
- frame
.InstructionIndex
;
441 /// The last instruction of a fault exception handler.
443 internal sealed class LeaveFaultInstruction
: Instruction
{
444 internal static readonly Instruction NonVoid
= new LeaveFaultInstruction(true);
445 internal static readonly Instruction Void
= new LeaveFaultInstruction(false);
447 private readonly bool _hasValue
;
449 // The fault block has a value if the body is non-void, but the value is never used.
450 // We compile the body of a fault block as void.
451 // However, we keep the exception object that was pushed upon entering the fault block on the stack during execution of the block
452 // and pop it at the end.
453 public override int ConsumedStack
{
457 // While emitting instructions a non-void try-fault expression is expected to produce a value.
458 public override int ProducedStack
{
459 get { return _hasValue ? 1 : 0; }
462 private LeaveFaultInstruction(bool hasValue
) {
463 _hasValue
= hasValue
;
466 public override int Run(InterpretedFrame frame
) {
467 // TODO: ThreadAbortException ?
469 object exception
= frame
.Pop();
470 ExceptionHandler handler
;
471 return frame
.Interpreter
.GotoHandler(frame
, exception
, out handler
);
476 internal sealed class ThrowInstruction
: Instruction
{
477 internal static readonly ThrowInstruction Throw
= new ThrowInstruction(true, false);
478 internal static readonly ThrowInstruction VoidThrow
= new ThrowInstruction(false, false);
479 internal static readonly ThrowInstruction Rethrow
= new ThrowInstruction(true, true);
480 internal static readonly ThrowInstruction VoidRethrow
= new ThrowInstruction(false, true);
482 private readonly bool _hasResult
, _rethrow
;
484 private ThrowInstruction(bool hasResult
, bool isRethrow
) {
485 _hasResult
= hasResult
;
486 _rethrow
= isRethrow
;
489 public override int ProducedStack
{
490 get { return _hasResult ? 1 : 0; }
493 public override int ConsumedStack
{
499 public override int Run(InterpretedFrame frame
) {
500 var ex
= (Exception
)frame
.Pop();
502 ExceptionHandler handler
;
503 return frame
.Interpreter
.GotoHandler(frame
, ex
, out handler
);
509 internal sealed class SwitchInstruction
: Instruction
{
510 private readonly Dictionary
<int, int> _cases
;
512 internal SwitchInstruction(Dictionary
<int, int> cases
) {
513 Assert
.NotNull(cases
);
517 public override int ConsumedStack { get { return 1; }
}
518 public override int ProducedStack { get { return 0; }
}
520 public override int Run(InterpretedFrame frame
) {
522 return _cases
.TryGetValue((int)frame
.Pop(), out target
) ? target
: 1;
526 internal sealed class EnterLoopInstruction
: Instruction
{
527 private readonly int _instructionIndex
;
528 private Dictionary
<ParameterExpression
, LocalVariable
> _variables
;
529 private Dictionary
<ParameterExpression
, LocalVariable
> _closureVariables
;
530 private LoopExpression _loop
;
531 private int _loopEnd
;
532 private int _compilationThreshold
;
534 internal EnterLoopInstruction(LoopExpression loop
, LocalVariables locals
, int compilationThreshold
, int instructionIndex
) {
536 _variables
= locals
.CopyLocals();
537 _closureVariables
= locals
.ClosureVariables
;
538 _compilationThreshold
= compilationThreshold
;
539 _instructionIndex
= instructionIndex
;
542 internal void FinishLoop(int loopEnd
) {
546 public override int Run(InterpretedFrame frame
) {
547 // Don't lock here, it's a frequently hit path.
549 // There could be multiple threads racing, but that is okay.
550 // Two bad things can happen:
551 // * We miss decrements (some thread sets the counter forward)
552 // * We might enter the "if" branch more than once.
554 // The first is okay, it just means we take longer to compile.
555 // The second we explicitly guard against inside of Compile().
557 // We can't miss 0. The first thread that writes -1 must have read 0 and hence start compilation.
558 if (unchecked(_compilationThreshold
--) == 0) {
560 if (PlatformAdaptationLayer
.IsCompactFramework
) {
561 _compilationThreshold
= Int32
.MaxValue
;
565 if (frame
.Interpreter
.CompileSynchronously
) {
568 // Kick off the compile on another thread so this one can keep going,
569 // Compile method backpatches the instruction when finished so we don't need to await the task.
571 new Task(Compile
, frame
).Start();
573 ThreadPool
.QueueUserWorkItem(Compile
, frame
);
580 private bool Compiled
{
581 get { return _loop == null; }
584 private void Compile(object frameObj
) {
594 PerfTrack
.NoteEvent(PerfTrack
.Categories
.Compiler
, "Interpreted loop compiled");
596 InterpretedFrame frame
= (InterpretedFrame
)frameObj
;
597 var compiler
= new LoopCompiler(_loop
, frame
.Interpreter
.LabelMapping
, _variables
, _closureVariables
, _instructionIndex
, _loopEnd
);
598 var instructions
= frame
.Interpreter
.Instructions
.Instructions
;
600 // replace this instruction with an optimized one:
601 Interlocked
.Exchange(ref instructions
[_instructionIndex
], new CompiledLoopInstruction(compiler
.CreateDelegate()));
603 // invalidate this instruction, some threads may still hold on it:
606 _closureVariables
= null;
611 internal sealed class CompiledLoopInstruction
: Instruction
{
612 private readonly LoopFunc _compiledLoop
;
614 public CompiledLoopInstruction(LoopFunc compiledLoop
) {
615 Assert
.NotNull(compiledLoop
);
616 _compiledLoop
= compiledLoop
;
619 public override int Run(InterpretedFrame frame
) {
620 return _compiledLoop(frame
.Data
, frame
.Closure
, frame
);