//
// 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; }
#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;
#if NET_4_0
methodSymbols.MarkSequencePoint (ig.ILOffset, sf.SourceFileEntry, loc.Row, loc.Column, false);
#endif
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 ()
{
if ((flags & Options.OmitDebugInfo) != 0)
return;
#if NET_4_0
methodSymbols.StartBlock (CodeBlockEntry.Type.Lexical, ig.ILOffset);
#endif
}
public void BeginCompilerScope ()
{
if ((flags & Options.OmitDebugInfo) != 0)
return;
#if NET_4_0
methodSymbols.StartBlock (CodeBlockEntry.Type.CompilerGenerated, ig.ILOffset);
#endif
}
public void EndExceptionBlock ()
{
ig.EndExceptionBlock ();
}
public void EndScope ()
{
if ((flags & Options.OmitDebugInfo) != 0)
return;
#if NET_4_0
methodSymbols.EndBlock (ig.ILOffset);
#endif
}
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);
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:
//
// Workaround MSIL limitation. Load bool element as single bit,
// bool array can actually store any byte value
//
ig.Emit (OpCodes.Ldelem_U1);
ig.Emit (OpCodes.Ldc_I4_0);
ig.Emit (OpCodes.Cgt_Un);
break;
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:
ig.Emit (OpCodes.Ldind_I1);
break;
case BuiltinTypeSpec.Type.Bool:
ig.Emit (OpCodes.Ldind_I1);
ig.Emit (OpCodes.Ldc_I4_0);
ig.Emit (OpCodes.Cgt_Un);
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;
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) {
ie.EmitLoad (ec);
}
} 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);
ec.Emit (OpCodes.Dup);
ec.EmitLoadFromPtr (instance.Type);
conditional_access_dup = true;
} else {
instance.Emit (ec);
}
if (instance.Type.Kind == MemberKind.TypeParameter)
ec.Emit (OpCodes.Box, instance.Type);
} else {
EmitLoad (ec);
if (conditionalAccess) {
conditional_access_dup = !IsInexpensiveLoad ();
if (conditional_access_dup)
ec.Emit (OpCodes.Dup);
}
}
if (conditionalAccess) {
ec.Emit (OpCodes.Brtrue_S, NullOperatorLabel);
if (conditional_access_dup)
ec.Emit (OpCodes.Pop);
}
}
if (conditionalAccess) {
if (!ec.ConditionalAccess.Statement) {
if (ec.ConditionalAccess.Type.IsNullableType)
Nullable.LiftedNull.Create (ec.ConditionalAccess.Type, Location.Null).Emit (ec);
else
ec.EmitNull ();
}
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);
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)
{
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 (RequiresBoxing ())
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;
}
bool RequiresBoxing ()
{
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;
}
bool IsInexpensiveLoad ()
{
if (instance is Constant)
return instance.IsSideEffectFree;
if (RequiresBoxing ())
return false;
var vr = instance as VariableReference;
if (vr != null)
return !vr.IsRef;
if (instance is LocalTemporary)
return true;
var fe = instance as FieldExpr;
if (fe != null)
return fe.IsStatic || fe.InstanceExpression is This;
return false;
}
}
}