//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft //------------------------------------------------------------------------------ using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Security; using System.Xml.XPath; using System.Xml.Xsl.IlGen; using System.Xml.Xsl.Qil; using System.Xml.Xsl.Runtime; using System.Runtime.Versioning; namespace System.Xml.Xsl { internal delegate void ExecuteDelegate(XmlQueryRuntime runtime); /// /// This internal class is the entry point for creating Msil assemblies from QilExpression. /// /// /// Generate will return an AssemblyBuilder with the following setup: /// Assembly Name = "MS.Internal.Xml.CompiledQuery" /// Module Dll Name = "MS.Internal.Xml.CompiledQuery.dll" /// public class MS.Internal.Xml.CompiledQuery.Test { /// public static void Execute(XmlQueryRuntime runtime); /// public static void Root(XmlQueryRuntime runtime); /// private static ... UserMethod1(XmlQueryRuntime runtime, ...); /// ... /// private static ... UserMethodN(XmlQueryRuntime runtime, ...); /// } /// /// XmlILGenerator incorporates a number of different technologies in order to generate efficient code that avoids caching /// large result sets in memory: /// /// 1. Code Iterators - Query results are computed using a set of composable, interlocking iterators that alone perform a /// simple task, but together execute complex queries. The iterators are actually little blocks of code /// that are connected to each other using a series of jumps. Because each iterator is not instantiated /// as a separate object, the number of objects and number of function calls is kept to a minimum during /// execution. Also, large result sets are often computed incrementally, with each iterator performing one step in a /// pipeline of sequence items. /// /// 2. Analyzers - During code generation, QilToMsil traverses the semantic tree representation of the query (QIL) several times. /// As visits to each node in the tree start and end, various Analyzers are invoked. These Analyzers incrementally /// collect and store information that is later used to generate faster and smaller code. /// internal class XmlILGenerator { private QilExpression qil; private GenerateHelper helper; private XmlILOptimizerVisitor optVisitor; private XmlILVisitor xmlIlVisitor; private XmlILModule module; /// /// Always output debug information in debug mode. /// public XmlILGenerator() { } /// /// Given the logical query plan (QilExpression) generate a physical query plan (MSIL) that can be executed. /// // SxS Note: The way the trace file names are created (hardcoded) is NOT SxS safe. However the files are // created only for internal tracing purposes. In addition XmlILTrace class is not compiled into retail // builds. As a result it is fine to suppress the FxCop SxS warning. [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] [ResourceExposure(ResourceScope.None)] public XmlILCommand Generate(QilExpression query, TypeBuilder typeBldr) { this.qil = query; bool useLRE = ( !this.qil.IsDebug && (typeBldr == null) #if DEBUG && !XmlILTrace.IsEnabled // Dump assembly to disk; can't do this when using LRE #endif ); bool emitSymbols = this.qil.IsDebug; // In debug code, ensure that input QIL is correct QilValidationVisitor.Validate(this.qil); #if DEBUG // Trace Qil before optimization XmlILTrace.WriteQil(this.qil, "qilbefore.xml"); // Trace optimizations XmlILTrace.TraceOptimizations(this.qil, "qilopt.xml"); #endif // Optimize and annotate the Qil graph this.optVisitor = new XmlILOptimizerVisitor(this.qil, !this.qil.IsDebug); this.qil = this.optVisitor.Optimize(); // In debug code, ensure that output QIL is correct QilValidationVisitor.Validate(this.qil); #if DEBUG // Trace Qil after optimization XmlILTrace.WriteQil(this.qil, "qilafter.xml"); #endif XmlILModule.CreateModulePermissionSet.Assert(); // Create module in which methods will be generated if (typeBldr != null) { this.module = new XmlILModule(typeBldr); } else { this.module = new XmlILModule(useLRE, emitSymbols); } // Create a code generation helper for the module; enable optimizations if IsDebug is false this.helper = new GenerateHelper(this.module, this.qil.IsDebug); // Create helper methods CreateHelperFunctions(); // Create metadata for the Execute function, which is the entry point to the query // public static void Execute(XmlQueryRuntime); MethodInfo methExec = this.module.DefineMethod("Execute", typeof(void), new Type[] { }, new string[] { }, XmlILMethodAttributes.NonUser); // Create metadata for the root expression // public void Root() Debug.Assert(this.qil.Root != null); XmlILMethodAttributes methAttrs = (this.qil.Root.SourceLine == null) ? XmlILMethodAttributes.NonUser : XmlILMethodAttributes.None; MethodInfo methRoot = this.module.DefineMethod("Root", typeof(void), new Type[] { }, new string[] { }, methAttrs); // Declare all early bound function objects foreach (EarlyBoundInfo info in this.qil.EarlyBoundTypes) { this.helper.StaticData.DeclareEarlyBound(info.NamespaceUri, info.EarlyBoundType); } // Create metadata for each QilExpression function that has at least one caller CreateFunctionMetadata(this.qil.FunctionList); // Create metadata for each QilExpression global variable and parameter CreateGlobalValueMetadata(this.qil.GlobalVariableList); CreateGlobalValueMetadata(this.qil.GlobalParameterList); // Generate Execute method GenerateExecuteFunction(methExec, methRoot); // Visit the QilExpression graph this.xmlIlVisitor = new XmlILVisitor(); this.xmlIlVisitor.Visit(this.qil, this.helper, methRoot); // Collect all static information required by the runtime XmlQueryStaticData staticData = new XmlQueryStaticData( this.qil.DefaultWriterSettings, this.qil.WhitespaceRules, this.helper.StaticData ); // Create static constructor that initializes XmlQueryStaticData instance at runtime if (typeBldr != null) { CreateTypeInitializer(staticData); // Finish up creation of the type this.module.BakeMethods(); return null; } else { // Finish up creation of the type this.module.BakeMethods(); // Create delegate over "Execute" method ExecuteDelegate delExec = (ExecuteDelegate)this.module.CreateDelegate("Execute", typeof(ExecuteDelegate)); return new XmlILCommand(delExec, staticData); } } /// /// Create MethodBuilder metadata for the specified QilExpression function. Annotate ndFunc with the /// MethodBuilder. Also, each QilExpression argument type should be converted to a corresponding Clr type. /// Each argument QilExpression node should be annotated with the resulting ParameterBuilder. /// private void CreateFunctionMetadata(IList funcList) { MethodInfo methInfo; Type[] paramTypes; string[] paramNames; Type typReturn; XmlILMethodAttributes methAttrs; foreach (QilFunction ndFunc in funcList) { paramTypes = new Type[ndFunc.Arguments.Count]; paramNames = new string[ndFunc.Arguments.Count]; // Loop through all other parameters and save their types in the array for (int arg = 0; arg < ndFunc.Arguments.Count; arg ++) { QilParameter ndParam = (QilParameter) ndFunc.Arguments[arg]; Debug.Assert(ndParam.NodeType == QilNodeType.Parameter); // Get the type of each argument as a Clr type paramTypes[arg] = XmlILTypeHelper.GetStorageType(ndParam.XmlType); // Get the name of each argument if (ndParam.DebugName != null) paramNames[arg] = ndParam.DebugName; } // Get the type of the return value if (XmlILConstructInfo.Read(ndFunc).PushToWriterLast) { // Push mode functions do not have a return value typReturn = typeof(void); } else { // Pull mode functions have a return value typReturn = XmlILTypeHelper.GetStorageType(ndFunc.XmlType); } // Create the method metadata methAttrs = ndFunc.SourceLine == null ? XmlILMethodAttributes.NonUser : XmlILMethodAttributes.None; methInfo = this.module.DefineMethod(ndFunc.DebugName, typReturn, paramTypes, paramNames, methAttrs); for (int arg = 0; arg < ndFunc.Arguments.Count; arg ++) { // Set location of parameter on Let node annotation XmlILAnnotation.Write(ndFunc.Arguments[arg]).ArgumentPosition = arg; } // Annotate function with the MethodInfo XmlILAnnotation.Write(ndFunc).FunctionBinding = methInfo; } } /// /// Generate metadata for a method that calculates a global value. /// private void CreateGlobalValueMetadata(IList globalList) { MethodInfo methInfo; Type typReturn; XmlILMethodAttributes methAttrs; foreach (QilReference ndRef in globalList) { // public T GlobalValue() typReturn = XmlILTypeHelper.GetStorageType(ndRef.XmlType); methAttrs = ndRef.SourceLine == null ? XmlILMethodAttributes.NonUser : XmlILMethodAttributes.None; methInfo = this.module.DefineMethod(ndRef.DebugName.ToString(), typReturn, new Type[] {}, new string[] {}, methAttrs); // Annotate function with MethodBuilder XmlILAnnotation.Write(ndRef).FunctionBinding = methInfo; } } /// /// Generate the "Execute" method, which is the entry point to the query. /// private MethodInfo GenerateExecuteFunction(MethodInfo methExec, MethodInfo methRoot) { this.helper.MethodBegin(methExec, null, false); // Force some or all global values to be evaluated at start of query EvaluateGlobalValues(this.qil.GlobalVariableList); EvaluateGlobalValues(this.qil.GlobalParameterList); // Root(runtime); this.helper.LoadQueryRuntime(); this.helper.Call(methRoot); this.helper.MethodEnd(); return methExec; } /// /// Create and generate various helper methods, which are called by the generated code. /// private void CreateHelperFunctions() { MethodInfo meth; Label lblClone; // public static XPathNavigator SyncToNavigator(XPathNavigator, XPathNavigator); meth = this.module.DefineMethod( "SyncToNavigator", typeof(XPathNavigator), new Type[] {typeof(XPathNavigator), typeof(XPathNavigator)}, new string[] {null, null}, XmlILMethodAttributes.NonUser | XmlILMethodAttributes.Raw); this.helper.MethodBegin(meth, null, false); // if (navigatorThis != null && navigatorThis.MoveTo(navigatorThat)) // return navigatorThis; lblClone = this.helper.DefineLabel(); this.helper.Emit(OpCodes.Ldarg_0); this.helper.Emit(OpCodes.Brfalse, lblClone); this.helper.Emit(OpCodes.Ldarg_0); this.helper.Emit(OpCodes.Ldarg_1); this.helper.Call(XmlILMethods.NavMoveTo); this.helper.Emit(OpCodes.Brfalse, lblClone); this.helper.Emit(OpCodes.Ldarg_0); this.helper.Emit(OpCodes.Ret); // LabelClone: // return navigatorThat.Clone(); this.helper.MarkLabel(lblClone); this.helper.Emit(OpCodes.Ldarg_1); this.helper.Call(XmlILMethods.NavClone); this.helper.MethodEnd(); } /// /// Generate code to force evaluation of some or all global variables and/or parameters. /// private void EvaluateGlobalValues(IList iterList) { MethodInfo methInfo; foreach (QilIterator ndIter in iterList) { // Evaluate global if generating debug code, or if global might have side effects if (this.qil.IsDebug || OptimizerPatterns.Read(ndIter).MatchesPattern(OptimizerPatternName.MaybeSideEffects)) { // Get MethodInfo that evaluates the global value and discard its return value methInfo = XmlILAnnotation.Write(ndIter).FunctionBinding; Debug.Assert(methInfo != null, "MethodInfo for global value should have been created previously."); this.helper.LoadQueryRuntime(); this.helper.Call(methInfo); this.helper.Emit(OpCodes.Pop); } } } /// /// Create static constructor that initializes XmlQueryStaticData instance at runtime. /// public void CreateTypeInitializer(XmlQueryStaticData staticData) { byte[] data; Type[] ebTypes; FieldInfo fldInitData, fldData, fldTypes; ConstructorInfo cctor; staticData.GetObjectData(out data, out ebTypes); fldInitData = this.module.DefineInitializedData("__" + XmlQueryStaticData.DataFieldName, data); fldData = this.module.DefineField(XmlQueryStaticData.DataFieldName, typeof(object)); fldTypes = this.module.DefineField(XmlQueryStaticData.TypesFieldName, typeof(Type[])); cctor = this.module.DefineTypeInitializer(); this.helper.MethodBegin(cctor, null, false); // s_data = new byte[s_initData.Length] { s_initData }; this.helper.LoadInteger(data.Length); this.helper.Emit(OpCodes.Newarr, typeof(byte)); this.helper.Emit(OpCodes.Dup); this.helper.Emit(OpCodes.Ldtoken, fldInitData); this.helper.Call(XmlILMethods.InitializeArray); this.helper.Emit(OpCodes.Stsfld, fldData); if (ebTypes != null) { // Type[] types = new Type[s_ebTypes.Length]; LocalBuilder locTypes = this.helper.DeclareLocal("$$$types", typeof(Type[])); this.helper.LoadInteger(ebTypes.Length); this.helper.Emit(OpCodes.Newarr, typeof(Type)); this.helper.Emit(OpCodes.Stloc, locTypes); for (int idx = 0; idx < ebTypes.Length; idx++) { // types[idx] = ebTypes[idx]; this.helper.Emit(OpCodes.Ldloc, locTypes); this.helper.LoadInteger(idx); this.helper.LoadType(ebTypes[idx]); this.helper.Emit(OpCodes.Stelem_Ref); } // s_types = types; this.helper.Emit(OpCodes.Ldloc, locTypes); this.helper.Emit(OpCodes.Stsfld, fldTypes); } this.helper.MethodEnd(); } } }