//
// 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 ICSharpCode.NRefactory.MonoCSharp
{
	/// <summary>
	///   An Emit Context is created for each body of code (from methods,
	///   properties bodies, indexer bodies or constructor bodies)
	/// </summary>
	public class EmitContext : BuilderContext
	{
		// TODO: Has to be private
		public readonly ILGenerator ig;

		/// <summary>
		///   The value that is allowed to be returned or NULL if there is no
		///   return type.
		/// </summary>
		readonly TypeSpec return_type;

		/// <summary>
		///   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)
		/// </summary>
		Dictionary<TypeSpec, object> temporary_storage;

		/// <summary>
		///   The location where we store the return value.
		/// </summary>
		public LocalBuilder return_value;


		/// <summary>
		///   Current loop begin and end labels.
		/// </summary>
		public Label LoopBegin, LoopEnd;

		/// <summary>
		///   Default target in a switch statement.   Only valid if
		///   InSwitch is true
		/// </summary>
		public Label DefaultTarget;

		/// <summary>
		///   If this is non-null, points to the current switch statement
		/// </summary>
		public Switch Switch;

		/// <summary>
		///  Whether we are inside an anonymous method.
		/// </summary>
		public AnonymousExpression CurrentAnonymousMethod;
		
		readonly IMemberContext member_context;

		readonly SourceMethodBuilder methodSymbols;

		DynamicSiteClass dynamic_site_container;

		Label? return_label;

		List<IExpressionCleanup> 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<IExpressionCleanup> StatementEpilogue {
			get {
				return epilogue_expressions;
			}
		}

		public LocalVariable AsyncThrowVariable { get; set; }

		public List<TryFinally> TryFinallyUnwind { get; set; }

		public Label RecursivePatternLabel { get; set; }

		#endregion

		public void AddStatementEpilog (IExpressionCleanup cleanupExpression)
		{
			if (epilogue_expressions == null) {
				epilogue_expressions = new List<IExpressionCleanup> ();
			} 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
		}

		/// <summary>
		///   This is called immediately before emitting an IL opcode to tell the symbol
		///   writer to which source line this opcode belongs.
		/// </summary>
		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:
				//
				// 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;
			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;
		}

		/// <summary>
		///   Returns a temporary storage for a variable of type t as 
		///   a local variable in the current body.
		/// </summary>
		public LocalBuilder GetTemporaryLocal (TypeSpec t)
		{
			if (temporary_storage != null) {
				object o;
				if (temporary_storage.TryGetValue (t, out o)) {
					if (o is Stack<LocalBuilder>) {
						var s = (Stack<LocalBuilder>) 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<TypeSpec, object> (ReferenceEquality<TypeSpec>.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<LocalBuilder>;
			if (s == null) {
				s = new Stack<LocalBuilder> ();
				s.Push ((LocalBuilder)o);
				temporary_storage [t] = s;
			}
			s.Push (b);
		}

		/// <summary>
		///   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.
		/// </summary>
		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, 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 = !IsInexpensiveLoad ();
						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) {
					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, 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 && 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;
		}

		//
		// Returns true for cheap race-free load, where we can avoid using dup
		//
		bool IsInexpensiveLoad ()
		{
			if (instance is Constant)
				return instance.IsSideEffectFree;

			if (RequiresBoxing ())
				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;
		}
	}
}
