Interpreter, backtrace, and call site caching

For backtraces to work correctly in interpreter mode, it is required
that the interpreter guard every call to C# code with a try-catch, so
that the catch block has a chance to stash away the backtrace if an
exception is thrown. This is done in Interpreter.InvokeMethod, and the
catch block gives the language a chance to save the backtrace by calling
LanguageContext.InterpretExceptionThrow.

However, with call-site caching enabled in interpreter mode,
Interpreter.InterpretMetaAction can directl invoke the compiled delegate
instead of calling Interpreter.InvokeMethod. This breaks the backtrace.

    private static object InterpretMetaAction(InterpreterState 

state, DynamicMetaObjectBinder action, DynamicExpression node, object[]
argValues) {

        callSiteInfo.Counter++;
        if (callSiteInfo.Counter > SiteCompileThreshold) {
            if (callSiteInfo.CallSite == null) {
                SetCallSite(callSiteInfo, node);
            }
            return callSiteInfo.CallerTarget(callSiteInfo.CallSite, 

argValues);
}


var result = Interpret(state, binding.Expression);
return result;
}

Is this a known issue? The interpreter stack trace in the unoptimized
case is shown below. The fix could be for the interpreter to maintain a
separate cache of compiled rules which have a try-catch generated for
all MethodCallExpression nodes so as to match the unoptimized code
behavior.

Thanks,
Shri

            Microsoft.Scripting.dll!Microsoft.Scripting.Utils.ReflectedCaller.Invoke(object[] 

args = {object[0x00000001]}) Line 46 + 0x19 bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InvokeMethod(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Reflection.MethodInfo method =
{System.Reflection.RuntimeMethodInfo}, object instance = null, object[]
parameters = {object[0x00000001]}) Line 135 + 0xb bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretMethodCallExpression(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.MethodCallExpressionN}) Line 251 + 0x30 bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.Interpret(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.MethodCallExpressionN}) Line 38 + 0xb bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretAndCheckFlow(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression node =
{System.Linq.Expressions.MethodCallExpressionN}, out object result =
null) Line 76 + 0x11 bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretBlockExpression(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.Block2}) Line 1256 + 0xf bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.Interpret(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.Block2}) Line 79 + 0xb bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretAndCheckYield(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression target =
{System.Linq.Expressions.Block2}, out object res = true) Line 86 + 0x11
bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretConditionalExpression(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.FullConditionalExpression}) Line 113 + 0x22
bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.Interpret(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.FullConditionalExpression}) Line 40 + 0xb bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretMetaAction(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Dynamic.DynamicMetaObjectBinder action =
{IronRuby.Runtime.Calls.RubyCallAction},
System.Linq.Expressions.DynamicExpression node =
{System.Linq.Expressions.DynamicExpression2}, object[] argValues =
{object[0x00000002]}) Line 810 + 0x2a bytes C#

Adding a try-catch around the optimized case (callSiteInfo.CallerTarget)
fixes the problem. However, this points out that we need to have a
try-catch around every operation in Interpreter.cs that could cause an
exception and have the catch call
LanguageContext.InterpretExceptionThrow.

From: [email protected]
[mailto:[email protected]] On Behalf Of Shri B.
Sent: Tuesday, December 30, 2008 2:28 PM
To: [email protected]
Subject: [Ironruby-core] Interpreter, backtrace, and call site caching

For backtraces to work correctly in interpreter mode, it is required
that the interpreter guard every call to C# code with a try-catch, so
that the catch block has a chance to stash away the backtrace if an
exception is thrown. This is done in Interpreter.InvokeMethod, and the
catch block gives the language a chance to save the backtrace by calling
LanguageContext.InterpretExceptionThrow.

However, with call-site caching enabled in interpreter mode,
Interpreter.InterpretMetaAction can directl invoke the compiled delegate
instead of calling Interpreter.InvokeMethod. This breaks the backtrace.

    private static object InterpretMetaAction(InterpreterState 

state, DynamicMetaObjectBinder action, DynamicExpression node, object[]
argValues) {

        callSiteInfo.Counter++;
        if (callSiteInfo.Counter > SiteCompileThreshold) {
            if (callSiteInfo.CallSite == null) {
                SetCallSite(callSiteInfo, node);
            }
            return callSiteInfo.CallerTarget(callSiteInfo.CallSite, 

argValues);
}


var result = Interpret(state, binding.Expression);
return result;
}

Is this a known issue? The interpreter stack trace in the unoptimized
case is shown below. The fix could be for the interpreter to maintain a
separate cache of compiled rules which have a try-catch generated for
all MethodCallExpression nodes so as to match the unoptimized code
behavior.

Thanks,
Shri

            Microsoft.Scripting.dll!Microsoft.Scripting.Utils.ReflectedCaller.Invoke(object[] 

args = {object[0x00000001]}) Line 46 + 0x19 bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InvokeMethod(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Reflection.MethodInfo method =
{System.Reflection.RuntimeMethodInfo}, object instance = null, object[]
parameters = {object[0x00000001]}) Line 135 + 0xb bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretMethodCallExpression(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.MethodCallExpressionN}) Line 251 + 0x30 bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.Interpret(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.MethodCallExpressionN}) Line 38 + 0xb bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretAndCheckFlow(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression node =
{System.Linq.Expressions.MethodCallExpressionN}, out object result =
null) Line 76 + 0x11 bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretBlockExpression(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.Block2}) Line 1256 + 0xf bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.Interpret(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.Block2}) Line 79 + 0xb bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretAndCheckYield(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression target =
{System.Linq.Expressions.Block2}, out object res = true) Line 86 + 0x11
bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretConditionalExpression(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.FullConditionalExpression}) Line 113 + 0x22
bytes C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.Interpret(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Linq.Expressions.Expression expr =
{System.Linq.Expressions.FullConditionalExpression}) Line 40 + 0xb bytes
C#
Microsoft.Scripting.dll!Microsoft.Scripting.Interpretation.Interpreter.InterpretMetaAction(Microsoft.Scripting.Interpretation.InterpreterState
state = {Microsoft.Scripting.Interpretation.InterpreterState},
System.Dynamic.DynamicMetaObjectBinder action =
{IronRuby.Runtime.Calls.RubyCallAction},
System.Linq.Expressions.DynamicExpression node =
{System.Linq.Expressions.DynamicExpression2}, object[] argValues =
{object[0x00000002]}) Line 810 + 0x2a bytes C#