1 //------------------------------------------------------------------------------
2 // <copyright file="XmlIlGenerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Diagnostics;
13 using System.Reflection;
14 using System.Reflection.Emit;
15 using System.Security;
16 using System.Xml.XPath;
17 using System.Xml.Xsl.IlGen;
18 using System.Xml.Xsl.Qil;
19 using System.Xml.Xsl.Runtime;
20 using System.Runtime.Versioning;
22 namespace System.Xml.Xsl {
24 internal delegate void ExecuteDelegate(XmlQueryRuntime runtime);
28 /// This internal class is the entry point for creating Msil assemblies from QilExpression.
31 /// Generate will return an AssemblyBuilder with the following setup:
32 /// Assembly Name = "MS.Internal.Xml.CompiledQuery"
33 /// Module Dll Name = "MS.Internal.Xml.CompiledQuery.dll"
34 /// public class MS.Internal.Xml.CompiledQuery.Test {
35 /// public static void Execute(XmlQueryRuntime runtime);
36 /// public static void Root(XmlQueryRuntime runtime);
37 /// private static ... UserMethod1(XmlQueryRuntime runtime, ...);
39 /// private static ... UserMethodN(XmlQueryRuntime runtime, ...);
42 /// XmlILGenerator incorporates a number of different technologies in order to generate efficient code that avoids caching
43 /// large result sets in memory:
45 /// 1. Code Iterators - Query results are computed using a set of composable, interlocking iterators that alone perform a
46 /// simple task, but together execute complex queries. The iterators are actually little blocks of code
47 /// that are connected to each other using a series of jumps. Because each iterator is not instantiated
48 /// as a separate object, the number of objects and number of function calls is kept to a minimum during
49 /// execution. Also, large result sets are often computed incrementally, with each iterator performing one step in a
50 /// pipeline of sequence items.
52 /// 2. Analyzers - During code generation, QilToMsil traverses the semantic tree representation of the query (QIL) several times.
53 /// As visits to each node in the tree start and end, various Analyzers are invoked. These Analyzers incrementally
54 /// collect and store information that is later used to generate faster and smaller code.
56 internal class XmlILGenerator {
57 private QilExpression qil;
58 private GenerateHelper helper;
59 private XmlILOptimizerVisitor optVisitor;
60 private XmlILVisitor xmlIlVisitor;
61 private XmlILModule module;
64 /// Always output debug information in debug mode.
66 public XmlILGenerator() {
70 /// Given the logical query plan (QilExpression) generate a physical query plan (MSIL) that can be executed.
72 // SxS Note: The way the trace file names are created (hardcoded) is NOT SxS safe. However the files are
73 // created only for internal tracing purposes. In addition XmlILTrace class is not compiled into retail
74 // builds. As a result it is fine to suppress the FxCop SxS warning.
75 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
76 [ResourceExposure(ResourceScope.None)]
77 public XmlILCommand Generate(QilExpression query, TypeBuilder typeBldr) {
84 && !XmlILTrace.IsEnabled // Dump assembly to disk; can't do this when using LRE
87 bool emitSymbols = this.qil.IsDebug;
89 // In debug code, ensure that input QIL is correct
90 QilValidationVisitor.Validate(this.qil);
93 // Trace Qil before optimization
94 XmlILTrace.WriteQil(this.qil, "qilbefore.xml");
96 // Trace optimizations
97 XmlILTrace.TraceOptimizations(this.qil, "qilopt.xml");
100 // Optimize and annotate the Qil graph
101 this.optVisitor = new XmlILOptimizerVisitor(this.qil, !this.qil.IsDebug);
102 this.qil = this.optVisitor.Optimize();
104 // In debug code, ensure that output QIL is correct
105 QilValidationVisitor.Validate(this.qil);
108 // Trace Qil after optimization
109 XmlILTrace.WriteQil(this.qil, "qilafter.xml");
112 XmlILModule.CreateModulePermissionSet.Assert();
114 // Create module in which methods will be generated
115 if (typeBldr != null) {
116 this.module = new XmlILModule(typeBldr);
118 this.module = new XmlILModule(useLRE, emitSymbols);
121 // Create a code generation helper for the module; enable optimizations if IsDebug is false
122 this.helper = new GenerateHelper(this.module, this.qil.IsDebug);
124 // Create helper methods
125 CreateHelperFunctions();
127 // Create metadata for the Execute function, which is the entry point to the query
128 // public static void Execute(XmlQueryRuntime);
129 MethodInfo methExec = this.module.DefineMethod("Execute", typeof(void), new Type[] { }, new string[] { }, XmlILMethodAttributes.NonUser);
131 // Create metadata for the root expression
132 // public void Root()
133 Debug.Assert(this.qil.Root != null);
134 XmlILMethodAttributes methAttrs = (this.qil.Root.SourceLine == null) ? XmlILMethodAttributes.NonUser : XmlILMethodAttributes.None;
135 MethodInfo methRoot = this.module.DefineMethod("Root", typeof(void), new Type[] { }, new string[] { }, methAttrs);
137 // Declare all early bound function objects
138 foreach (EarlyBoundInfo info in this.qil.EarlyBoundTypes) {
139 this.helper.StaticData.DeclareEarlyBound(info.NamespaceUri, info.EarlyBoundType);
142 // Create metadata for each QilExpression function that has at least one caller
143 CreateFunctionMetadata(this.qil.FunctionList);
145 // Create metadata for each QilExpression global variable and parameter
146 CreateGlobalValueMetadata(this.qil.GlobalVariableList);
147 CreateGlobalValueMetadata(this.qil.GlobalParameterList);
149 // Generate Execute method
150 GenerateExecuteFunction(methExec, methRoot);
152 // Visit the QilExpression graph
153 this.xmlIlVisitor = new XmlILVisitor();
154 this.xmlIlVisitor.Visit(this.qil, this.helper, methRoot);
156 // Collect all static information required by the runtime
157 XmlQueryStaticData staticData = new XmlQueryStaticData(
158 this.qil.DefaultWriterSettings,
159 this.qil.WhitespaceRules,
160 this.helper.StaticData
163 // Create static constructor that initializes XmlQueryStaticData instance at runtime
164 if (typeBldr != null) {
165 CreateTypeInitializer(staticData);
167 // Finish up creation of the type
168 this.module.BakeMethods();
172 // Finish up creation of the type
173 this.module.BakeMethods();
175 // Create delegate over "Execute" method
176 ExecuteDelegate delExec = (ExecuteDelegate)this.module.CreateDelegate("Execute", typeof(ExecuteDelegate));
177 return new XmlILCommand(delExec, staticData);
182 /// Create MethodBuilder metadata for the specified QilExpression function. Annotate ndFunc with the
183 /// MethodBuilder. Also, each QilExpression argument type should be converted to a corresponding Clr type.
184 /// Each argument QilExpression node should be annotated with the resulting ParameterBuilder.
186 private void CreateFunctionMetadata(IList<QilNode> funcList) {
191 XmlILMethodAttributes methAttrs;
193 foreach (QilFunction ndFunc in funcList) {
194 paramTypes = new Type[ndFunc.Arguments.Count];
195 paramNames = new string[ndFunc.Arguments.Count];
197 // Loop through all other parameters and save their types in the array
198 for (int arg = 0; arg < ndFunc.Arguments.Count; arg ++) {
199 QilParameter ndParam = (QilParameter) ndFunc.Arguments[arg];
200 Debug.Assert(ndParam.NodeType == QilNodeType.Parameter);
202 // Get the type of each argument as a Clr type
203 paramTypes[arg] = XmlILTypeHelper.GetStorageType(ndParam.XmlType);
205 // Get the name of each argument
206 if (ndParam.DebugName != null)
207 paramNames[arg] = ndParam.DebugName;
210 // Get the type of the return value
211 if (XmlILConstructInfo.Read(ndFunc).PushToWriterLast) {
212 // Push mode functions do not have a return value
213 typReturn = typeof(void);
216 // Pull mode functions have a return value
217 typReturn = XmlILTypeHelper.GetStorageType(ndFunc.XmlType);
220 // Create the method metadata
221 methAttrs = ndFunc.SourceLine == null ? XmlILMethodAttributes.NonUser : XmlILMethodAttributes.None;
222 methInfo = this.module.DefineMethod(ndFunc.DebugName, typReturn, paramTypes, paramNames, methAttrs);
224 for (int arg = 0; arg < ndFunc.Arguments.Count; arg ++) {
225 // Set location of parameter on Let node annotation
226 XmlILAnnotation.Write(ndFunc.Arguments[arg]).ArgumentPosition = arg;
229 // Annotate function with the MethodInfo
230 XmlILAnnotation.Write(ndFunc).FunctionBinding = methInfo;
235 /// Generate metadata for a method that calculates a global value.
237 private void CreateGlobalValueMetadata(IList<QilNode> globalList) {
240 XmlILMethodAttributes methAttrs;
242 foreach (QilReference ndRef in globalList) {
243 // public T GlobalValue()
244 typReturn = XmlILTypeHelper.GetStorageType(ndRef.XmlType);
245 methAttrs = ndRef.SourceLine == null ? XmlILMethodAttributes.NonUser : XmlILMethodAttributes.None;
246 methInfo = this.module.DefineMethod(ndRef.DebugName.ToString(), typReturn, new Type[] {}, new string[] {}, methAttrs);
248 // Annotate function with MethodBuilder
249 XmlILAnnotation.Write(ndRef).FunctionBinding = methInfo;
254 /// Generate the "Execute" method, which is the entry point to the query.
256 private MethodInfo GenerateExecuteFunction(MethodInfo methExec, MethodInfo methRoot) {
257 this.helper.MethodBegin(methExec, null, false);
259 // Force some or all global values to be evaluated at start of query
260 EvaluateGlobalValues(this.qil.GlobalVariableList);
261 EvaluateGlobalValues(this.qil.GlobalParameterList);
264 this.helper.LoadQueryRuntime();
265 this.helper.Call(methRoot);
267 this.helper.MethodEnd();
273 /// Create and generate various helper methods, which are called by the generated code.
275 private void CreateHelperFunctions() {
279 // public static XPathNavigator SyncToNavigator(XPathNavigator, XPathNavigator);
280 meth = this.module.DefineMethod(
282 typeof(XPathNavigator),
283 new Type[] {typeof(XPathNavigator), typeof(XPathNavigator)},
284 new string[] {null, null},
285 XmlILMethodAttributes.NonUser | XmlILMethodAttributes.Raw);
287 this.helper.MethodBegin(meth, null, false);
289 // if (navigatorThis != null && navigatorThis.MoveTo(navigatorThat))
290 // return navigatorThis;
291 lblClone = this.helper.DefineLabel();
292 this.helper.Emit(OpCodes.Ldarg_0);
293 this.helper.Emit(OpCodes.Brfalse, lblClone);
294 this.helper.Emit(OpCodes.Ldarg_0);
295 this.helper.Emit(OpCodes.Ldarg_1);
296 this.helper.Call(XmlILMethods.NavMoveTo);
297 this.helper.Emit(OpCodes.Brfalse, lblClone);
298 this.helper.Emit(OpCodes.Ldarg_0);
299 this.helper.Emit(OpCodes.Ret);
302 // return navigatorThat.Clone();
303 this.helper.MarkLabel(lblClone);
304 this.helper.Emit(OpCodes.Ldarg_1);
305 this.helper.Call(XmlILMethods.NavClone);
307 this.helper.MethodEnd();
311 /// Generate code to force evaluation of some or all global variables and/or parameters.
313 private void EvaluateGlobalValues(IList<QilNode> iterList) {
316 foreach (QilIterator ndIter in iterList) {
317 // Evaluate global if generating debug code, or if global might have side effects
318 if (this.qil.IsDebug || OptimizerPatterns.Read(ndIter).MatchesPattern(OptimizerPatternName.MaybeSideEffects)) {
319 // Get MethodInfo that evaluates the global value and discard its return value
320 methInfo = XmlILAnnotation.Write(ndIter).FunctionBinding;
321 Debug.Assert(methInfo != null, "MethodInfo for global value should have been created previously.");
323 this.helper.LoadQueryRuntime();
324 this.helper.Call(methInfo);
325 this.helper.Emit(OpCodes.Pop);
331 /// Create static constructor that initializes XmlQueryStaticData instance at runtime.
333 public void CreateTypeInitializer(XmlQueryStaticData staticData) {
336 FieldInfo fldInitData, fldData, fldTypes;
337 ConstructorInfo cctor;
339 staticData.GetObjectData(out data, out ebTypes);
340 fldInitData = this.module.DefineInitializedData("__" + XmlQueryStaticData.DataFieldName, data);
341 fldData = this.module.DefineField(XmlQueryStaticData.DataFieldName, typeof(object));
342 fldTypes = this.module.DefineField(XmlQueryStaticData.TypesFieldName, typeof(Type[]));
344 cctor = this.module.DefineTypeInitializer();
345 this.helper.MethodBegin(cctor, null, false);
347 // s_data = new byte[s_initData.Length] { s_initData };
348 this.helper.LoadInteger(data.Length);
349 this.helper.Emit(OpCodes.Newarr, typeof(byte));
350 this.helper.Emit(OpCodes.Dup);
351 this.helper.Emit(OpCodes.Ldtoken, fldInitData);
352 this.helper.Call(XmlILMethods.InitializeArray);
353 this.helper.Emit(OpCodes.Stsfld, fldData);
355 if (ebTypes != null) {
356 // Type[] types = new Type[s_ebTypes.Length];
357 LocalBuilder locTypes = this.helper.DeclareLocal("$$$types", typeof(Type[]));
358 this.helper.LoadInteger(ebTypes.Length);
359 this.helper.Emit(OpCodes.Newarr, typeof(Type));
360 this.helper.Emit(OpCodes.Stloc, locTypes);
362 for (int idx = 0; idx < ebTypes.Length; idx++) {
363 // types[idx] = ebTypes[idx];
364 this.helper.Emit(OpCodes.Ldloc, locTypes);
365 this.helper.LoadInteger(idx);
366 this.helper.LoadType(ebTypes[idx]);
367 this.helper.Emit(OpCodes.Stelem_Ref);
371 this.helper.Emit(OpCodes.Ldloc, locTypes);
372 this.helper.Emit(OpCodes.Stsfld, fldTypes);
375 this.helper.MethodEnd();