966bba02bb
Former-commit-id: bb0468d0f257ff100aa895eb5fe583fb5dfbf900
1393 lines
34 KiB
C#
1393 lines
34 KiB
C#
//
|
|
// 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
|
|
{
|
|
/// <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;
|
|
|
|
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;
|
|
}
|
|
|
|
/// <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) {
|
|
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;
|
|
}
|
|
}
|
|
}
|