Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / XmlIlGenerator.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlIlGenerator.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Diagnostics;
12 using System.IO;
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;
21
22 namespace System.Xml.Xsl {
23
24     internal delegate void ExecuteDelegate(XmlQueryRuntime runtime);
25
26
27     /// <summary>
28     /// This internal class is the entry point for creating Msil assemblies from QilExpression.
29     /// </summary>
30     /// <remarks>
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, ...);
38     ///     ...
39     ///     private static ... UserMethodN(XmlQueryRuntime runtime, ...);
40     /// }
41     ///
42     /// XmlILGenerator incorporates a number of different technologies in order to generate efficient code that avoids caching
43     /// large result sets in memory:
44     ///
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.
51     ///
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.
55     /// </remarks>
56     internal class XmlILGenerator {
57         private QilExpression qil;
58         private GenerateHelper helper;
59         private XmlILOptimizerVisitor optVisitor;
60         private XmlILVisitor xmlIlVisitor;
61         private XmlILModule module;
62
63         /// <summary>
64         /// Always output debug information in debug mode.
65         /// </summary>
66         public XmlILGenerator() {
67         }
68
69         /// <summary>
70         /// Given the logical query plan (QilExpression) generate a physical query plan (MSIL) that can be executed.
71         /// </summary>
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) {
78             this.qil = query;
79
80             bool useLRE = (
81                 !this.qil.IsDebug &&
82                 (typeBldr == null)
83 #if DEBUG
84                 && !XmlILTrace.IsEnabled // Dump assembly to disk; can't do this when using LRE
85 #endif
86             );
87             bool emitSymbols = this.qil.IsDebug;
88
89             // In debug code, ensure that input QIL is correct
90             QilValidationVisitor.Validate(this.qil);
91
92         #if DEBUG
93             // Trace Qil before optimization
94             XmlILTrace.WriteQil(this.qil, "qilbefore.xml");
95
96             // Trace optimizations
97             XmlILTrace.TraceOptimizations(this.qil, "qilopt.xml");
98         #endif
99
100             // Optimize and annotate the Qil graph
101             this.optVisitor = new XmlILOptimizerVisitor(this.qil, !this.qil.IsDebug);
102             this.qil = this.optVisitor.Optimize();
103
104             // In debug code, ensure that output QIL is correct
105             QilValidationVisitor.Validate(this.qil);
106
107         #if DEBUG
108             // Trace Qil after optimization
109             XmlILTrace.WriteQil(this.qil, "qilafter.xml");
110         #endif
111
112             XmlILModule.CreateModulePermissionSet.Assert();
113
114             // Create module in which methods will be generated
115             if (typeBldr != null) {
116                 this.module = new XmlILModule(typeBldr);
117             } else {
118                 this.module = new XmlILModule(useLRE, emitSymbols);
119             }
120
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);
123
124             // Create helper methods
125             CreateHelperFunctions();
126
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);
130
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);
136
137             // Declare all early bound function objects
138             foreach (EarlyBoundInfo info in this.qil.EarlyBoundTypes) {
139                 this.helper.StaticData.DeclareEarlyBound(info.NamespaceUri, info.EarlyBoundType);
140             }
141
142             // Create metadata for each QilExpression function that has at least one caller
143             CreateFunctionMetadata(this.qil.FunctionList);
144
145             // Create metadata for each QilExpression global variable and parameter
146             CreateGlobalValueMetadata(this.qil.GlobalVariableList);
147             CreateGlobalValueMetadata(this.qil.GlobalParameterList);
148
149             // Generate Execute method
150             GenerateExecuteFunction(methExec, methRoot);
151
152             // Visit the QilExpression graph
153             this.xmlIlVisitor = new XmlILVisitor();
154             this.xmlIlVisitor.Visit(this.qil, this.helper, methRoot);
155
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
161             );
162
163             // Create static constructor that initializes XmlQueryStaticData instance at runtime
164             if (typeBldr != null) {
165                 CreateTypeInitializer(staticData);
166
167                 // Finish up creation of the type
168                 this.module.BakeMethods();
169
170                 return null;
171             } else {
172                 // Finish up creation of the type
173                 this.module.BakeMethods();
174
175                 // Create delegate over "Execute" method
176                 ExecuteDelegate delExec = (ExecuteDelegate)this.module.CreateDelegate("Execute", typeof(ExecuteDelegate));
177                 return new XmlILCommand(delExec, staticData);
178             }
179         }
180
181         /// <summary>
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.
185         /// </summary>
186         private void CreateFunctionMetadata(IList<QilNode> funcList) {
187             MethodInfo methInfo;
188             Type[] paramTypes;
189             string[] paramNames;
190             Type typReturn;
191             XmlILMethodAttributes methAttrs;
192
193             foreach (QilFunction ndFunc in funcList) {
194                 paramTypes = new Type[ndFunc.Arguments.Count];
195                 paramNames = new string[ndFunc.Arguments.Count];
196
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);
201
202                     // Get the type of each argument as a Clr type
203                     paramTypes[arg] = XmlILTypeHelper.GetStorageType(ndParam.XmlType);
204
205                     // Get the name of each argument
206                     if (ndParam.DebugName != null)
207                         paramNames[arg] = ndParam.DebugName;
208                 }
209
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);
214                 }
215                 else {
216                     // Pull mode functions have a return value
217                     typReturn = XmlILTypeHelper.GetStorageType(ndFunc.XmlType);
218                 }
219
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);
223
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;
227                 }
228
229                 // Annotate function with the MethodInfo
230                 XmlILAnnotation.Write(ndFunc).FunctionBinding = methInfo;
231             }
232         }
233
234         /// <summary>
235         /// Generate metadata for a method that calculates a global value.
236         /// </summary>
237         private void CreateGlobalValueMetadata(IList<QilNode> globalList) {
238             MethodInfo methInfo;
239             Type typReturn;
240             XmlILMethodAttributes methAttrs;
241
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);
247
248                 // Annotate function with MethodBuilder
249                 XmlILAnnotation.Write(ndRef).FunctionBinding = methInfo;
250             }
251         }
252
253         /// <summary>
254         /// Generate the "Execute" method, which is the entry point to the query.
255         /// </summary>
256         private MethodInfo GenerateExecuteFunction(MethodInfo methExec, MethodInfo methRoot) {
257             this.helper.MethodBegin(methExec, null, false);
258
259             // Force some or all global values to be evaluated at start of query
260             EvaluateGlobalValues(this.qil.GlobalVariableList);
261             EvaluateGlobalValues(this.qil.GlobalParameterList);
262
263             // Root(runtime);
264             this.helper.LoadQueryRuntime();
265             this.helper.Call(methRoot);
266
267             this.helper.MethodEnd();
268
269             return methExec;
270         }
271
272         /// <summary>
273         /// Create and generate various helper methods, which are called by the generated code.
274         /// </summary>
275         private void CreateHelperFunctions() {
276             MethodInfo meth;
277             Label lblClone;
278
279             // public static XPathNavigator SyncToNavigator(XPathNavigator, XPathNavigator);
280             meth = this.module.DefineMethod(
281                             "SyncToNavigator",
282                             typeof(XPathNavigator),
283                             new Type[] {typeof(XPathNavigator), typeof(XPathNavigator)},
284                             new string[] {null, null},
285                             XmlILMethodAttributes.NonUser | XmlILMethodAttributes.Raw);
286
287             this.helper.MethodBegin(meth, null, false);
288
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);
300
301             // LabelClone:
302             // return navigatorThat.Clone();
303             this.helper.MarkLabel(lblClone);
304             this.helper.Emit(OpCodes.Ldarg_1);
305             this.helper.Call(XmlILMethods.NavClone);
306
307             this.helper.MethodEnd();
308         }
309
310         /// <summary>
311         /// Generate code to force evaluation of some or all global variables and/or parameters.
312         /// </summary>
313         private void EvaluateGlobalValues(IList<QilNode> iterList) {
314             MethodInfo methInfo;
315
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.");
322
323                     this.helper.LoadQueryRuntime();
324                     this.helper.Call(methInfo);
325                     this.helper.Emit(OpCodes.Pop);
326                 }
327             }
328         }
329
330         /// <summary>
331         /// Create static constructor that initializes XmlQueryStaticData instance at runtime.
332         /// </summary>
333         public void CreateTypeInitializer(XmlQueryStaticData staticData) {
334             byte[] data;
335             Type[] ebTypes;
336             FieldInfo fldInitData, fldData, fldTypes;
337             ConstructorInfo cctor;
338
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[]));
343
344             cctor = this.module.DefineTypeInitializer();
345             this.helper.MethodBegin(cctor, null, false);
346
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);
354
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);
361
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);
368                 }
369
370                 // s_types = types;
371                 this.helper.Emit(OpCodes.Ldloc, locTypes);
372                 this.helper.Emit(OpCodes.Stsfld, fldTypes);
373             }
374
375             this.helper.MethodEnd();
376         }
377     }
378 }