// // codegen.cs: The code generator // // Authors: // Miguel de Icaza (miguel@ximian.com) // Marek Safar (marek.safar@gmail.com) // // Copyright 2001, 2002, 2003 Ximian, Inc. // Copyright 2004 Novell, Inc. // Copyright 2011 Xamarin Inc // using System; using System.Collections.Generic; using Mono.CompilerServices.SymbolWriter; #if STATIC using MetaType = IKVM.Reflection.Type; using IKVM.Reflection; using IKVM.Reflection.Emit; #else using MetaType = System.Type; using System.Reflection; using System.Reflection.Emit; #endif namespace Mono.CSharp { /// /// An Emit Context is created for each body of code (from methods, /// properties bodies, indexer bodies or constructor bodies) /// public class EmitContext : BuilderContext { // TODO: Has to be private public readonly ILGenerator ig; /// /// The value that is allowed to be returned or NULL if there is no /// return type. /// readonly TypeSpec return_type; /// /// Keeps track of the Type to LocalBuilder temporary storage created /// to store structures (used to compute the address of the structure /// value on structure method invocations) /// Dictionary temporary_storage; /// /// The location where we store the return value. /// public LocalBuilder return_value; /// /// Current loop begin and end labels. /// public Label LoopBegin, LoopEnd; /// /// Default target in a switch statement. Only valid if /// InSwitch is true /// public Label DefaultTarget; /// /// If this is non-null, points to the current switch statement /// public Switch Switch; /// /// Whether we are inside an anonymous method. /// public AnonymousExpression CurrentAnonymousMethod; readonly IMemberContext member_context; readonly SourceMethodBuilder methodSymbols; DynamicSiteClass dynamic_site_container; Label? return_label; List epilogue_expressions; public EmitContext (IMemberContext rc, ILGenerator ig, TypeSpec return_type, SourceMethodBuilder methodSymbols) { this.member_context = rc; this.ig = ig; this.return_type = return_type; if (rc.Module.Compiler.Settings.Checked) flags |= Options.CheckedScope; if (methodSymbols != null) { this.methodSymbols = methodSymbols; if (!rc.Module.Compiler.Settings.Optimize) flags |= Options.AccurateDebugInfo; } else { flags |= Options.OmitDebugInfo; } #if STATIC ig.__CleverExceptionBlockAssistance (); #endif } #region Properties internal AsyncTaskStorey AsyncTaskStorey { get { return CurrentAnonymousMethod.Storey as AsyncTaskStorey; } } public BuiltinTypes BuiltinTypes { get { return MemberContext.Module.Compiler.BuiltinTypes; } } public ConditionalAccessContext ConditionalAccess { get; set; } public TypeSpec CurrentType { get { return member_context.CurrentType; } } public TypeParameters CurrentTypeParameters { get { return member_context.CurrentTypeParameters; } } public MemberCore CurrentTypeDefinition { get { return member_context.CurrentMemberDefinition; } } public bool EmitAccurateDebugInfo { get { return (flags & Options.AccurateDebugInfo) != 0; } } public bool HasMethodSymbolBuilder { get { return methodSymbols != null; } } public bool HasReturnLabel { get { return return_label.HasValue; } } public bool IsStatic { get { return member_context.IsStatic; } } public bool IsStaticConstructor { get { return member_context.IsStatic && (flags & Options.ConstructorScope) != 0; } } public bool IsAnonymousStoreyMutateRequired { get { return CurrentAnonymousMethod != null && CurrentAnonymousMethod.Storey != null && CurrentAnonymousMethod.Storey.Mutator != null; } } public IMemberContext MemberContext { get { return member_context; } } public ModuleContainer Module { get { return member_context.Module; } } public bool NotifyEvaluatorOnStore { get { return Module.Evaluator != null && Module.Evaluator.ModificationListener != null; } } // Has to be used for specific emitter errors only any // possible resolver errors have to be reported during Resolve public Report Report { get { return member_context.Module.Compiler.Report; } } public TypeSpec ReturnType { get { return return_type; } } // // The label where we have to jump before leaving the context // public Label ReturnLabel { get { return return_label.Value; } } public List StatementEpilogue { get { return epilogue_expressions; } } public LocalVariable AsyncThrowVariable { get; set; } public List TryFinallyUnwind { get; set; } public Label RecursivePatternLabel { get; set; } #endregion public void AddStatementEpilog (IExpressionCleanup cleanupExpression) { if (epilogue_expressions == null) { epilogue_expressions = new List (); } else if (epilogue_expressions.Contains (cleanupExpression)) { return; } epilogue_expressions.Add (cleanupExpression); } public void AssertEmptyStack () { #if STATIC if (ig.__StackHeight != 0) throw new InternalErrorException ("Await yields with non-empty stack in `{0}", member_context.GetSignatureForError ()); #endif } /// /// This is called immediately before emitting an IL opcode to tell the symbol /// writer to which source line this opcode belongs. /// public bool Mark (Location loc) { if ((flags & Options.OmitDebugInfo) != 0) return false; if (loc.IsNull || methodSymbols == null) return false; var sf = loc.SourceFile; if (sf.IsHiddenLocation (loc)) return false; methodSymbols.MarkSequencePoint (ig.ILOffset, sf.SourceFileEntry, loc.Row, loc.Column, false); return true; } public void MarkCallEntry (Location loc) { if (!EmitAccurateDebugInfo) return; // // TODO: This should emit different kind of sequence point to make // step-over work for statement over multiple lines // // Debugging experience for Foo (A () + B ()) where A and B are // on separate lines is not great // Mark (loc); } public void DefineLocalVariable (string name, LocalBuilder builder) { if ((flags & Options.OmitDebugInfo) != 0) return; methodSymbols.AddLocal (builder.LocalIndex, name); } public void BeginCatchBlock (TypeSpec type) { if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); ig.BeginCatchBlock (type.GetMetaInfo ()); } public void BeginFilterHandler () { ig.BeginCatchBlock (null); } public void BeginExceptionBlock () { ig.BeginExceptionBlock (); } public void BeginExceptionFilterBlock () { ig.BeginExceptFilterBlock (); } public void BeginFinallyBlock () { ig.BeginFinallyBlock (); } public void BeginScope (int scopeIndex) { if ((flags & Options.OmitDebugInfo) != 0) return; methodSymbols.StartBlock (CodeBlockEntry.Type.Lexical, ig.ILOffset, scopeIndex); } public void BeginCompilerScope (int scopeIndex) { if ((flags & Options.OmitDebugInfo) != 0) return; methodSymbols.StartBlock (CodeBlockEntry.Type.CompilerGenerated, ig.ILOffset, scopeIndex); } public void EndExceptionBlock () { ig.EndExceptionBlock (); } public void EndScope () { if ((flags & Options.OmitDebugInfo) != 0) return; methodSymbols.EndBlock (ig.ILOffset); } public void CloseConditionalAccess (TypeSpec type) { if (type != null) Emit (OpCodes.Newobj, Nullable.NullableInfo.GetConstructor (type)); MarkLabel (ConditionalAccess.EndLabel); ConditionalAccess = null; } // // Creates a nested container in this context for all dynamic compiler generated stuff // internal DynamicSiteClass CreateDynamicSite () { if (dynamic_site_container == null) { var mc = member_context.CurrentMemberDefinition as MemberBase; dynamic_site_container = new DynamicSiteClass (CurrentTypeDefinition.Parent.PartialContainer, mc, member_context.CurrentTypeParameters); CurrentTypeDefinition.Module.AddCompilerGeneratedClass (dynamic_site_container); dynamic_site_container.CreateContainer (); dynamic_site_container.DefineContainer (); dynamic_site_container.Define (); var inflator = new TypeParameterInflator (Module, CurrentType, TypeParameterSpec.EmptyTypes, TypeSpec.EmptyTypes); var inflated = dynamic_site_container.CurrentType.InflateMember (inflator); CurrentType.MemberCache.AddMember (inflated); } return dynamic_site_container; } public Label CreateReturnLabel () { if (!return_label.HasValue) return_label = DefineLabel (); return return_label.Value; } public LocalBuilder DeclareLocal (TypeSpec type, bool pinned) { if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); if (pinned) { // // This is for .net compatibility. I am not sure why pinned // pointer temps are converted to & even if they are pointers to // pointers. // var pt = type as PointerContainer; if (pt != null) { type = pt.Element; if (type.Kind == MemberKind.Void) type = Module.Compiler.BuiltinTypes.IntPtr; return ig.DeclareLocal (type.GetMetaInfo ().MakeByRefType (), true); } } return ig.DeclareLocal (type.GetMetaInfo (), pinned); } public Label DefineLabel () { return ig.DefineLabel (); } // // Creates temporary field in current async storey // public StackFieldExpr GetTemporaryField (TypeSpec type, bool initializedFieldRequired = false) { var f = AsyncTaskStorey.AddCapturedLocalVariable (type, initializedFieldRequired); var fexpr = new StackFieldExpr (f); fexpr.InstanceExpression = new CompilerGeneratedThis (CurrentType, Location.Null); return fexpr; } public void MarkLabel (Label label) { ig.MarkLabel (label); } public void Emit (OpCode opcode) { ig.Emit (opcode); } public void Emit (OpCode opcode, LocalBuilder local) { ig.Emit (opcode, local); } public void Emit (OpCode opcode, string arg) { ig.Emit (opcode, arg); } public void Emit (OpCode opcode, double arg) { ig.Emit (opcode, arg); } public void Emit (OpCode opcode, float arg) { ig.Emit (opcode, arg); } public void Emit (OpCode opcode, Label label) { ig.Emit (opcode, label); } public void Emit (OpCode opcode, Label[] labels) { ig.Emit (opcode, labels); } public void Emit (OpCode opcode, TypeSpec type) { if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); ig.Emit (opcode, type.GetMetaInfo ()); } public void Emit (OpCode opcode, FieldSpec field) { if (IsAnonymousStoreyMutateRequired) field = field.Mutate (CurrentAnonymousMethod.Storey.Mutator); ig.Emit (opcode, field.GetMetaInfo ()); } public void Emit (OpCode opcode, MethodSpec method) { if (IsAnonymousStoreyMutateRequired) method = method.Mutate (CurrentAnonymousMethod.Storey.Mutator); if (method.IsConstructor) ig.Emit (opcode, (ConstructorInfo) method.GetMetaInfo ()); else ig.Emit (opcode, (MethodInfo) method.GetMetaInfo ()); } // TODO: REMOVE breaks mutator public void Emit (OpCode opcode, MethodInfo method) { ig.Emit (opcode, method); } public void Emit (OpCode opcode, MethodSpec method, MetaType[] vargs) { // TODO MemberCache: This should mutate too ig.EmitCall (opcode, (MethodInfo) method.GetMetaInfo (), vargs); } public void EmitArrayNew (ArrayContainer ac) { if (ac.Rank == 1) { var type = IsAnonymousStoreyMutateRequired ? CurrentAnonymousMethod.Storey.Mutator.Mutate (ac.Element) : ac.Element; ig.Emit (OpCodes.Newarr, type.GetMetaInfo ()); } else { if (IsAnonymousStoreyMutateRequired) ac = (ArrayContainer) ac.Mutate (CurrentAnonymousMethod.Storey.Mutator); ig.Emit (OpCodes.Newobj, ac.GetConstructor ()); } } public void EmitArrayAddress (ArrayContainer ac) { if (ac.Rank > 1) { if (IsAnonymousStoreyMutateRequired) ac = (ArrayContainer) ac.Mutate (CurrentAnonymousMethod.Storey.Mutator); ig.Emit (OpCodes.Call, ac.GetAddressMethod ()); } else { var type = IsAnonymousStoreyMutateRequired ? CurrentAnonymousMethod.Storey.Mutator.Mutate (ac.Element) : ac.Element; ig.Emit (OpCodes.Ldelema, type.GetMetaInfo ()); } } // // Emits the right opcode to load from an array // public void EmitArrayLoad (ArrayContainer ac) { if (ac.Rank > 1) { if (IsAnonymousStoreyMutateRequired) ac = (ArrayContainer) ac.Mutate (CurrentAnonymousMethod.Storey.Mutator); ig.Emit (OpCodes.Call, ac.GetGetMethod ()); return; } var type = ac.Element; if (type.Kind == MemberKind.Enum) type = EnumSpec.GetUnderlyingType (type); switch (type.BuiltinType) { case BuiltinTypeSpec.Type.Bool: // // bool array can actually store any byte value in underlying byte slot // and C# spec does not specify any normalization rule, except the result // is undefined // case BuiltinTypeSpec.Type.Byte: ig.Emit (OpCodes.Ldelem_U1); break; case BuiltinTypeSpec.Type.SByte: ig.Emit (OpCodes.Ldelem_I1); break; case BuiltinTypeSpec.Type.Short: ig.Emit (OpCodes.Ldelem_I2); break; case BuiltinTypeSpec.Type.UShort: case BuiltinTypeSpec.Type.Char: ig.Emit (OpCodes.Ldelem_U2); break; case BuiltinTypeSpec.Type.Int: ig.Emit (OpCodes.Ldelem_I4); break; case BuiltinTypeSpec.Type.UInt: ig.Emit (OpCodes.Ldelem_U4); break; case BuiltinTypeSpec.Type.ULong: case BuiltinTypeSpec.Type.Long: ig.Emit (OpCodes.Ldelem_I8); break; case BuiltinTypeSpec.Type.Float: ig.Emit (OpCodes.Ldelem_R4); break; case BuiltinTypeSpec.Type.Double: ig.Emit (OpCodes.Ldelem_R8); break; case BuiltinTypeSpec.Type.IntPtr: ig.Emit (OpCodes.Ldelem_I); break; default: switch (type.Kind) { case MemberKind.Struct: if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); ig.Emit (OpCodes.Ldelema, type.GetMetaInfo ()); ig.Emit (OpCodes.Ldobj, type.GetMetaInfo ()); break; case MemberKind.TypeParameter: if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); ig.Emit (OpCodes.Ldelem, type.GetMetaInfo ()); break; case MemberKind.PointerType: ig.Emit (OpCodes.Ldelem_I); break; default: ig.Emit (OpCodes.Ldelem_Ref); break; } break; } } // // Emits the right opcode to store to an array // public void EmitArrayStore (ArrayContainer ac) { if (ac.Rank > 1) { if (IsAnonymousStoreyMutateRequired) ac = (ArrayContainer) ac.Mutate (CurrentAnonymousMethod.Storey.Mutator); ig.Emit (OpCodes.Call, ac.GetSetMethod ()); return; } var type = ac.Element; if (type.Kind == MemberKind.Enum) type = EnumSpec.GetUnderlyingType (type); switch (type.BuiltinType) { case BuiltinTypeSpec.Type.Byte: case BuiltinTypeSpec.Type.SByte: case BuiltinTypeSpec.Type.Bool: Emit (OpCodes.Stelem_I1); return; case BuiltinTypeSpec.Type.Short: case BuiltinTypeSpec.Type.UShort: case BuiltinTypeSpec.Type.Char: Emit (OpCodes.Stelem_I2); return; case BuiltinTypeSpec.Type.Int: case BuiltinTypeSpec.Type.UInt: Emit (OpCodes.Stelem_I4); return; case BuiltinTypeSpec.Type.Long: case BuiltinTypeSpec.Type.ULong: Emit (OpCodes.Stelem_I8); return; case BuiltinTypeSpec.Type.Float: Emit (OpCodes.Stelem_R4); return; case BuiltinTypeSpec.Type.Double: Emit (OpCodes.Stelem_R8); return; } switch (type.Kind) { case MemberKind.Struct: Emit (OpCodes.Stobj, type); break; case MemberKind.TypeParameter: Emit (OpCodes.Stelem, type); break; case MemberKind.PointerType: Emit (OpCodes.Stelem_I); break; default: Emit (OpCodes.Stelem_Ref); break; } } public void EmitInt (int i) { EmitIntConstant (i); } void EmitIntConstant (int i) { switch (i) { case -1: ig.Emit (OpCodes.Ldc_I4_M1); break; case 0: ig.Emit (OpCodes.Ldc_I4_0); break; case 1: ig.Emit (OpCodes.Ldc_I4_1); break; case 2: ig.Emit (OpCodes.Ldc_I4_2); break; case 3: ig.Emit (OpCodes.Ldc_I4_3); break; case 4: ig.Emit (OpCodes.Ldc_I4_4); break; case 5: ig.Emit (OpCodes.Ldc_I4_5); break; case 6: ig.Emit (OpCodes.Ldc_I4_6); break; case 7: ig.Emit (OpCodes.Ldc_I4_7); break; case 8: ig.Emit (OpCodes.Ldc_I4_8); break; default: if (i >= -128 && i <= 127) { ig.Emit (OpCodes.Ldc_I4_S, (sbyte) i); } else ig.Emit (OpCodes.Ldc_I4, i); break; } } public void EmitLong (long l) { if (l >= int.MinValue && l <= int.MaxValue) { EmitIntConstant (unchecked ((int) l)); ig.Emit (OpCodes.Conv_I8); } else if (l >= 0 && l <= uint.MaxValue) { EmitIntConstant (unchecked ((int) l)); ig.Emit (OpCodes.Conv_U8); } else { ig.Emit (OpCodes.Ldc_I8, l); } } // // Load the object from the pointer. // public void EmitLoadFromPtr (TypeSpec type) { if (type.Kind == MemberKind.Enum) type = EnumSpec.GetUnderlyingType (type); switch (type.BuiltinType) { case BuiltinTypeSpec.Type.Int: ig.Emit (OpCodes.Ldind_I4); break; case BuiltinTypeSpec.Type.UInt: ig.Emit (OpCodes.Ldind_U4); break; case BuiltinTypeSpec.Type.Short: ig.Emit (OpCodes.Ldind_I2); break; case BuiltinTypeSpec.Type.UShort: case BuiltinTypeSpec.Type.Char: ig.Emit (OpCodes.Ldind_U2); break; case BuiltinTypeSpec.Type.Byte: ig.Emit (OpCodes.Ldind_U1); break; case BuiltinTypeSpec.Type.SByte: case BuiltinTypeSpec.Type.Bool: ig.Emit (OpCodes.Ldind_I1); break; case BuiltinTypeSpec.Type.ULong: case BuiltinTypeSpec.Type.Long: ig.Emit (OpCodes.Ldind_I8); break; case BuiltinTypeSpec.Type.Float: ig.Emit (OpCodes.Ldind_R4); break; case BuiltinTypeSpec.Type.Double: ig.Emit (OpCodes.Ldind_R8); break; case BuiltinTypeSpec.Type.IntPtr: ig.Emit (OpCodes.Ldind_I); break; default: switch (type.Kind) { case MemberKind.Struct: case MemberKind.TypeParameter: if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); ig.Emit (OpCodes.Ldobj, type.GetMetaInfo ()); break; case MemberKind.PointerType: ig.Emit (OpCodes.Ldind_I); break; default: ig.Emit (OpCodes.Ldind_Ref); break; } break; } } public void EmitNull () { ig.Emit (OpCodes.Ldnull); } public void EmitArgumentAddress (int pos) { if (!IsStatic) ++pos; if (pos > byte.MaxValue) ig.Emit (OpCodes.Ldarga, pos); else ig.Emit (OpCodes.Ldarga_S, (byte) pos); } public void EmitArgumentLoad (int pos) { if (!IsStatic) ++pos; switch (pos) { case 0: ig.Emit (OpCodes.Ldarg_0); break; case 1: ig.Emit (OpCodes.Ldarg_1); break; case 2: ig.Emit (OpCodes.Ldarg_2); break; case 3: ig.Emit (OpCodes.Ldarg_3); break; default: if (pos > byte.MaxValue) ig.Emit (OpCodes.Ldarg, pos); else ig.Emit (OpCodes.Ldarg_S, (byte) pos); break; } } public void EmitArgumentStore (int pos) { if (!IsStatic) ++pos; if (pos > byte.MaxValue) ig.Emit (OpCodes.Starg, pos); else ig.Emit (OpCodes.Starg_S, (byte) pos); } // // The stack contains the pointer and the value of type `type' // public void EmitStoreFromPtr (TypeSpec type) { if (type.IsEnum) type = EnumSpec.GetUnderlyingType (type); switch (type.BuiltinType) { case BuiltinTypeSpec.Type.Int: case BuiltinTypeSpec.Type.UInt: ig.Emit (OpCodes.Stind_I4); return; case BuiltinTypeSpec.Type.Long: case BuiltinTypeSpec.Type.ULong: ig.Emit (OpCodes.Stind_I8); return; case BuiltinTypeSpec.Type.Char: case BuiltinTypeSpec.Type.Short: case BuiltinTypeSpec.Type.UShort: ig.Emit (OpCodes.Stind_I2); return; case BuiltinTypeSpec.Type.Float: ig.Emit (OpCodes.Stind_R4); return; case BuiltinTypeSpec.Type.Double: ig.Emit (OpCodes.Stind_R8); return; case BuiltinTypeSpec.Type.Byte: case BuiltinTypeSpec.Type.SByte: case BuiltinTypeSpec.Type.Bool: ig.Emit (OpCodes.Stind_I1); return; case BuiltinTypeSpec.Type.IntPtr: ig.Emit (OpCodes.Stind_I); return; } switch (type.Kind) { case MemberKind.Struct: case MemberKind.TypeParameter: if (IsAnonymousStoreyMutateRequired) type = CurrentAnonymousMethod.Storey.Mutator.Mutate (type); ig.Emit (OpCodes.Stobj, type.GetMetaInfo ()); break; case MemberKind.PointerType: ig.Emit (OpCodes.Stind_I); break; default: ig.Emit (OpCodes.Stind_Ref); break; } } public void EmitThis () { ig.Emit (OpCodes.Ldarg_0); } public void EmitEpilogue () { if (epilogue_expressions == null) return; foreach (var e in epilogue_expressions) e.EmitCleanup (this); epilogue_expressions = null; } /// /// Returns a temporary storage for a variable of type t as /// a local variable in the current body. /// public LocalBuilder GetTemporaryLocal (TypeSpec t) { if (temporary_storage != null) { object o; if (temporary_storage.TryGetValue (t, out o)) { if (o is Stack) { var s = (Stack) o; o = s.Count == 0 ? null : s.Pop (); } else { temporary_storage.Remove (t); } } if (o != null) return (LocalBuilder) o; } return DeclareLocal (t, false); } public void FreeTemporaryLocal (LocalBuilder b, TypeSpec t) { if (temporary_storage == null) { temporary_storage = new Dictionary (ReferenceEquality.Default); temporary_storage.Add (t, b); return; } object o; if (!temporary_storage.TryGetValue (t, out o)) { temporary_storage.Add (t, b); return; } var s = o as Stack; if (s == null) { s = new Stack (); s.Push ((LocalBuilder)o); temporary_storage [t] = s; } s.Push (b); } /// /// ReturnValue creates on demand the LocalBuilder for the /// return value from the function. By default this is not /// used. This is only required when returns are found inside /// Try or Catch statements. /// /// This method is typically invoked from the Emit phase, so /// we allow the creation of a return label if it was not /// requested during the resolution phase. Could be cleaned /// up, but it would replicate a lot of logic in the Emit phase /// of the code that uses it. /// public LocalBuilder TemporaryReturn () { if (return_value == null){ return_value = DeclareLocal (return_type, false); } return return_value; } } public class ConditionalAccessContext { public ConditionalAccessContext (TypeSpec type, Label endLabel) { Type = type; EndLabel = endLabel; } public bool Statement { get; set; } public Label EndLabel { get; private set; } public TypeSpec Type { get; private set; } } struct CallEmitter { public Expression InstanceExpression; // // When call has to leave an extra copy of all arguments on the stack // public bool DuplicateArguments; // // Does not emit InstanceExpression load when InstanceExpressionOnStack // is set. Used by compound assignments. // public bool InstanceExpressionOnStack; // // Any of arguments contains await expression // public bool HasAwaitArguments; public bool ConditionalAccess; // // When dealing with await arguments the original arguments are converted // into a new set with hoisted stack results // public Arguments EmittedArguments; public void Emit (EmitContext ec, MethodSpec method, Arguments Arguments, Location loc) { EmitPredefined (ec, method, Arguments, false, loc); } public void EmitStatement (EmitContext ec, MethodSpec method, Arguments Arguments, Location loc) { EmitPredefined (ec, method, Arguments, true, loc); } public void EmitPredefined (EmitContext ec, MethodSpec method, Arguments Arguments, bool statement = false, Location? loc = null) { Expression instance_copy = null; if (!HasAwaitArguments && ec.HasSet (BuilderContext.Options.AsyncBody)) { HasAwaitArguments = Arguments != null && Arguments.ContainsEmitWithAwait (); if (HasAwaitArguments && InstanceExpressionOnStack) { throw new NotSupportedException (); } } OpCode call_op; LocalTemporary lt = null; if (method.IsStatic) { call_op = OpCodes.Call; } else { call_op = IsVirtualCallRequired (InstanceExpression, method) ? OpCodes.Callvirt : OpCodes.Call; if (HasAwaitArguments) { instance_copy = InstanceExpression.EmitToField (ec); var ie = new InstanceEmitter (instance_copy, IsAddressCall (instance_copy, call_op, method.DeclaringType)); if (Arguments == null) { if (ConditionalAccess) ie.Emit (ec, true); else ie.EmitLoad (ec, true); } } else if (!InstanceExpressionOnStack) { var ie = new InstanceEmitter (InstanceExpression, IsAddressCall (InstanceExpression, call_op, method.DeclaringType)); ie.Emit (ec, ConditionalAccess); if (DuplicateArguments) { ec.Emit (OpCodes.Dup); if (Arguments != null && Arguments.Count != 0) { lt = new LocalTemporary (ie.GetStackType (ec)); lt.Store (ec); instance_copy = lt; } } } } if (Arguments != null && !InstanceExpressionOnStack) { EmittedArguments = Arguments.Emit (ec, DuplicateArguments, HasAwaitArguments); if (EmittedArguments != null) { if (instance_copy != null) { var ie = new InstanceEmitter (instance_copy, IsAddressCall (instance_copy, call_op, method.DeclaringType)); ie.Emit (ec, ConditionalAccess); if (lt != null) lt.Release (ec); } EmittedArguments.Emit (ec); } } if (call_op == OpCodes.Callvirt && (InstanceExpression.Type.IsGenericParameter || InstanceExpression.Type.IsStructOrEnum)) { ec.Emit (OpCodes.Constrained, InstanceExpression.Type); } if (loc != null) { // // Emit explicit sequence point for expressions like Foo.Bar () to help debugger to // break at right place when LHS expression can be stepped-into // ec.MarkCallEntry (loc.Value); } // // Set instance expression to actual result expression. When it contains await it can be // picked up by caller // InstanceExpression = instance_copy; if (method.Parameters.HasArglist) { var varargs_types = GetVarargsTypes (method, Arguments); ec.Emit (call_op, method, varargs_types); } else { // // If you have: // this.DoFoo (); // and DoFoo is not virtual, you can omit the callvirt, // because you don't need the null checking behavior. // ec.Emit (call_op, method); } // // Pop the return value if there is one and stack should be empty // if (statement && method.ReturnType.Kind != MemberKind.Void) ec.Emit (OpCodes.Pop); } static MetaType[] GetVarargsTypes (MethodSpec method, Arguments arguments) { AParametersCollection pd = method.Parameters; Argument a = arguments[pd.Count - 1]; Arglist list = (Arglist) a.Expr; return list.ArgumentTypes; } // // Used to decide whether call or callvirt is needed // static bool IsVirtualCallRequired (Expression instance, MethodSpec method) { // // There are 2 scenarious where we emit callvirt // // Case 1: A method is virtual and it's not used to call base // Case 2: A method instance expression can be null. In this casen callvirt ensures // correct NRE exception when the method is called // var decl_type = method.DeclaringType; if (decl_type.IsStruct || decl_type.IsEnum) return false; if (instance is BaseThis) return false; // // It's non-virtual and will never be null and it can be determined // whether it's known value or reference type by verifier // if (!method.IsVirtual && Expression.IsNeverNull (instance) && !instance.Type.IsGenericParameter) return false; return true; } static bool IsAddressCall (Expression instance, OpCode callOpcode, TypeSpec declaringType) { var instance_type = instance.Type; return (instance_type.IsStructOrEnum && (callOpcode == OpCodes.Callvirt || (callOpcode == OpCodes.Call && declaringType.IsStruct))) || instance_type.IsGenericParameter || declaringType.IsNullableType; } } public struct InstanceEmitter { readonly Expression instance; readonly bool addressRequired; public InstanceEmitter (Expression instance, bool addressLoad) { this.instance = instance; this.addressRequired = addressLoad; } public void Emit (EmitContext ec, bool conditionalAccess) { Label NullOperatorLabel; Nullable.Unwrap unwrap; if (conditionalAccess && Expression.IsNeverNull (instance)) conditionalAccess = false; if (conditionalAccess) { NullOperatorLabel = ec.DefineLabel (); unwrap = instance as Nullable.Unwrap; } else { NullOperatorLabel = new Label (); unwrap = null; } IMemoryLocation instance_address = null; bool conditional_access_dup = false; if (unwrap != null) { unwrap.Store (ec); unwrap.EmitCheck (ec); ec.Emit (OpCodes.Brtrue_S, NullOperatorLabel); } else { if (conditionalAccess && addressRequired) { // // Don't allocate temp variable when instance load is cheap and load and load-address // operate on same memory // instance_address = instance as VariableReference; if (instance_address == null) instance_address = instance as LocalTemporary; if (instance_address == null) { EmitLoad (ec, false); ec.Emit (OpCodes.Dup); ec.EmitLoadFromPtr (instance.Type); conditional_access_dup = true; } else { instance.Emit (ec); } } else { EmitLoad (ec, !conditionalAccess); if (conditionalAccess) { conditional_access_dup = !ExpressionAnalyzer.IsInexpensiveLoad (instance); if (conditional_access_dup) ec.Emit (OpCodes.Dup); } } if (conditionalAccess) { if (instance.Type.Kind == MemberKind.TypeParameter) ec.Emit (OpCodes.Box, instance.Type); ec.Emit (OpCodes.Brtrue_S, NullOperatorLabel); if (conditional_access_dup) ec.Emit (OpCodes.Pop); } } if (conditionalAccess) { if (!ec.ConditionalAccess.Statement) { var t = ec.ConditionalAccess.Type; if (t.IsNullableType) Nullable.LiftedNull.Create (t, Location.Null).Emit (ec); else { ec.EmitNull (); if (t.IsGenericParameter) ec.Emit (OpCodes.Unbox_Any, t); } } ec.Emit (OpCodes.Br, ec.ConditionalAccess.EndLabel); ec.MarkLabel (NullOperatorLabel); if (instance_address != null) { instance_address.AddressOf (ec, AddressOp.Load); } else if (unwrap != null) { unwrap.Emit (ec); if (addressRequired) { var tmp = ec.GetTemporaryLocal (unwrap.Type); ec.Emit (OpCodes.Stloc, tmp); ec.Emit (OpCodes.Ldloca, tmp); ec.FreeTemporaryLocal (tmp, unwrap.Type); } } else if (!conditional_access_dup) { instance.Emit (ec); } } } public void EmitLoad (EmitContext ec, bool boxInstance) { var instance_type = instance.Type; // // Push the instance expression // if (addressRequired) { // // If the expression implements IMemoryLocation, then // we can optimize and use AddressOf on the // return. // // If not we have to use some temporary storage for // it. var iml = instance as IMemoryLocation; if (iml != null) { iml.AddressOf (ec, AddressOp.Load); } else { LocalTemporary temp = new LocalTemporary (instance_type); instance.Emit (ec); temp.Store (ec); temp.AddressOf (ec, AddressOp.Load); } return; } instance.Emit (ec); // Only to make verifier happy if (boxInstance && ExpressionAnalyzer.RequiresBoxing (instance)) { ec.Emit (OpCodes.Box, instance_type); } } public TypeSpec GetStackType (EmitContext ec) { var instance_type = instance.Type; if (addressRequired) return ReferenceContainer.MakeType (ec.Module, instance_type); if (instance_type.IsStructOrEnum) return ec.Module.Compiler.BuiltinTypes.Object; return instance_type; } } static class ExpressionAnalyzer { public static bool RequiresBoxing (Expression instance) { var instance_type = instance.Type; if (instance_type.IsGenericParameter && !(instance is This) && TypeSpec.IsReferenceType (instance_type)) return true; if (instance_type.IsStructOrEnum) return true; return false; } // // Returns true for cheap race-free load, where we can avoid using dup // public static bool IsInexpensiveLoad (Expression instance) { if (instance is Constant) return instance.IsSideEffectFree; if (RequiresBoxing (instance)) return false; var vr = instance as VariableReference; if (vr != null) { // Load from captured local would be racy without dup return !vr.IsRef && !vr.IsHoisted; } if (instance is LocalTemporary) return true; var fe = instance as FieldExpr; if (fe != null) return fe.IsStatic || fe.InstanceExpression is This; return false; } } }