/// <summary> /// Creates a new expression visitor to rewrite expressions for JIT compilation /// support. /// </summary> /// <param name="analysis">Scope analysis of nodes in the tree.</param> /// <param name="methodTable">Method table parameter passed to the top-level lambda.</param> /// <param name="thunkFactory">Factory for thunk types.</param> public Impl(Dictionary <object, Scope> analysis, ParameterExpression methodTable, IThunkFactory thunkFactory) { _analysis = analysis; _methodTable = methodTable; _thunkFactory = thunkFactory; _scope = new CompilerScope(_methodTable); }
private static void AssertThunkType(IThunkFactory factory, Type delegateType, Dictionary <Type, object[]> values) { // // Get the delegate's Invoke method and infer parameter and return types. // var invokeMethod = delegateType.GetMethod("Invoke"); var retType = invokeMethod.ReturnType; var ret = retType != typeof(void) ? values[retType][0] : null; var parameterTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray(); var args = parameterTypes.Select(p => values[p][0]).ToArray(); // // Use a special closure type to assert behavior. // var closureType = typeof(AssertiveClosure); var closureAdd = closureType.GetMethod("Add"); var closureFlag = closureType.GetMethod("Flag"); // // This is the functionality we're testing. // var t = factory.GetThunkType(delegateType, closureType); // // Assert we got only one constructor. // var ctors = t.GetConstructors(); Assert.AreEqual(1, ctors.Length); var ctor = ctors[0]; var ctorParams = ctor.GetParameters(); Assert.AreEqual(1, ctorParams.Length); // // Obtain the expression tree type passed to the constructor. // var exprType = ctorParams[0].ParameterType; Assert.IsTrue(typeof(LambdaExpression).IsAssignableFrom(exprType)); Assert.IsTrue(exprType.IsGenericType); Assert.AreEqual(typeof(Expression <>), exprType.GetGenericTypeDefinition()); var innerDelegateType = exprType.GetGenericArguments()[0]; // // Build an expression tree of the required type, using the "assertive closure" // to record all the parameters passed in. // var closureParam = Expression.Parameter(closureType); var valueParams = parameterTypes.Select(p => Expression.Parameter(p)).ToArray(); var lambdaParams = new[] { closureParam }.Concat(valueParams).ToArray(); var exprs = new List <Expression>(); foreach (var valueParam in valueParams) { exprs.Add(Expression.Call(closureParam, closureAdd, Expression.Convert(valueParam, typeof(object)))); } if (retType != typeof(void)) { exprs.Add(Expression.Constant(ret, retType)); } else { exprs.Add(Expression.Constant(value: null)); } var e = Expression.Lambda( innerDelegateType, Expression.Block( retType, exprs ), lambdaParams ); // // Instantiate the thunk type. // var o = Activator.CreateInstance(t, e); var thunk = (Thunk)o; // // Ensure we can obtain the original expression at runtime. // Assert.AreSame(e, thunk.Lambda); // // Check the Target field and get its original value. // var target = t.GetField("Target"); Assert.IsNotNull(target); var jitCompiler = (Delegate)target.GetValue(o); // // Assert the shape of the CreateDelegate method. // var createDelegate = t.GetMethod("CreateDelegate"); Assert.IsNotNull(createDelegate); Assert.IsFalse(createDelegate.IsStatic); Assert.IsTrue(createDelegate.IsPublic); Assert.AreEqual(delegateType, createDelegate.ReturnType); var createDelegateParams = createDelegate.GetParameters(); Assert.AreEqual(1, createDelegateParams.Length); Assert.AreEqual(closureType, createDelegateParams[0].ParameterType); // // Invoke CreateDelegate with an "assertive closure" instance. // var closure = new AssertiveClosure(); var del = (Delegate)createDelegate.Invoke(o, new object[] { closure }); // // Prior to invoking the delegate, check we haven't triggered JIT compilation yet. // var stillJit = (Delegate)target.GetValue(o); Assert.AreSame(jitCompiler, stillJit); // // Invoke the delegate and assert the result through the "assertive closure". // var res = del.DynamicInvoke(args); Assert.AreEqual(ret, res); closure.Check(flag: false, args); // // Check we triggered a JIT compilation and the Target has been replaced. // var compiled = (Delegate)target.GetValue(o); Assert.AreNotSame(jitCompiler, compiled); // // Invoke the delegate again to assert we don't trigger recompilation. // closure.Clear(); res = del.DynamicInvoke(args); Assert.AreEqual(ret, res); closure.Check(flag: false, args); // // Invoke the delegate a couple more times to ensure it keeps working. // // NB: This also tests the tiered compilation logic, which is count-based at the moment. // for (var i = 0; i < 8; i++) { res = del.DynamicInvoke(args); Assert.AreEqual(ret, res); } // // Substitute the expression to assert reinstallation of the JIT. // exprs.Insert(0, Expression.Call(closureParam, closureFlag)); var newExpression = Expression.Lambda( innerDelegateType, Expression.Block( retType, exprs ), lambdaParams ); thunk.Lambda = newExpression; // // Assert the JIT gets re-installed. // // NB: Note we assert the target method and not the delegate instance. This is // because the instance can differ; we don't store the compiler delegate // to save space but implement a virtual Compiler property instead, whose // getter returns an instance of the JIT delegate. // var reinstalledJit = (Delegate)target.GetValue(o); Assert.AreSame(jitCompiler.Method, reinstalledJit.Method); // // Invoke the lambda and assert that the new lambda expression is used. // closure.Clear(); res = del.DynamicInvoke(args); Assert.AreEqual(ret, res); closure.Check(flag: true, args); // NB: Note the check for `flag` being set to `true`. // // Assert the target is substituted for the newly compiled delegate. // var recompiled = (Delegate)target.GetValue(o); Assert.AreNotSame(compiled, recompiled); Assert.AreNotSame(reinstalledJit, recompiled); }
/// <summary> /// Prepares the specified <paramref name="expression"/> for JIT compilation support. /// </summary> /// <param name="methodTable">The parameter containing a reference to the method table.</param> /// <param name="analysis">The scope analysis for the expression.</param> /// <param name="expression">The expression to prepare for JIT compilation.</param> /// <param name="thunkFactory">Factory for thunk types.</param> /// <returns>Information used to compile the expression.</returns> public static JitCompilationInfo Prepare(ParameterExpression methodTable, Dictionary <object, Scope> analysis, LambdaExpression expression, IThunkFactory thunkFactory) { // // Rewrite the expression tree to prepare it for JIT compilation. // This will replace all inner lambdas with explicit calls to thunk // methods to create a delegate using explicit closures. // var impl = new Impl(analysis, methodTable, thunkFactory); var expr = impl.Visit(expression); // // Get the method table whose entries contain the thunks for the // inner lambdas. The rewritten expression will use array indexing // operations into the `mtParam` parameter to obtain the thunks. // The instance of the method table has to be passed to the top- // level compiled delegate, which is done by the caller. // var mt = impl.GetMethodTable(); // // Return all the objects we've built as part of JIT compilation. // This enables the caller to intercept the method table we built // in order to perform instrumentation or to trigger compilation // of inner lambdas in a non-deferred manner. // return(new JitCompilationInfo { MethodTableParameter = methodTable, Expression = expr, MethodTable = mt, }); }