}
}
+ public ConditionalAccessContext ConditionalAccess { get; set; }
+
public TypeSpec CurrentType {
get { return member_context.CurrentType; }
}
get { return member_context.IsStatic; }
}
+ public bool IsStaticConstructor {
+ get {
+ return member_context.IsStatic && (flags & Options.ConstructorScope) != 0;
+ }
+ }
+
public bool IsAnonymousStoreyMutateRequired {
get {
return CurrentAnonymousMethod != null &&
}
}
+ 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 {
}
}
+ public LocalVariable AsyncThrowVariable { get; set; }
+
+ public List<TryFinally> TryFinallyUnwind { get; set; }
+
#endregion
public void AddStatementEpilog (IExpressionCleanup cleanupExpression)
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 ();
#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
//
//
// Creates temporary field in current async storey
//
- public FieldExpr GetTemporaryField (TypeSpec type)
+ public StackFieldExpr GetTemporaryField (TypeSpec type, bool initializedFieldRequired = false)
{
- var f = AsyncTaskStorey.AddCapturedLocalVariable (type);
+ var f = AsyncTaskStorey.AddCapturedLocalVariable (type, initializedFieldRequired);
var fexpr = new StackFieldExpr (f);
fexpr.InstanceExpression = new CompilerGeneratedThis (CurrentType, Location.Null);
return fexpr;
type = EnumSpec.GetUnderlyingType (type);
switch (type.BuiltinType) {
- case BuiltinTypeSpec.Type.Byte:
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.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:
}
}
+ 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 set leaves an extra copy of all arguments on the stack
+ // When call has to leave an extra copy of all arguments on the stack
//
public bool DuplicateArguments;
//
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 void Emit (EmitContext ec, MethodSpec method, Arguments Arguments, Location loc)
{
- // Speed up the check by not doing it on not allowed targets
- if (method.ReturnType.Kind == MemberKind.Void && method.IsConditionallyExcluded (ec.MemberContext))
- return;
+ EmitPredefined (ec, method, Arguments, false, loc);
+ }
- EmitPredefined (ec, method, Arguments, 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, Location? loc = null)
+ public void EmitPredefined (EmitContext ec, MethodSpec method, Arguments Arguments, bool statement = false, Location? loc = null)
{
Expression instance_copy = null;
if (method.IsStatic) {
call_op = OpCodes.Call;
} else {
- if (IsVirtualCallRequired (InstanceExpression, method)) {
- call_op = OpCodes.Callvirt;
- } else {
- call_op = OpCodes.Call;
- }
+ call_op = IsVirtualCallRequired (InstanceExpression, method) ? OpCodes.Callvirt : OpCodes.Call;
if (HasAwaitArguments) {
instance_copy = InstanceExpression.EmitToField (ec);
- if (Arguments == null)
- EmitCallInstance (ec, instance_copy, method.DeclaringType, call_op);
+ var ie = new InstanceEmitter (instance_copy, IsAddressCall (instance_copy, call_op, method.DeclaringType));
+
+ if (Arguments == null) {
+ ie.EmitLoad (ec);
+ }
} else if (!InstanceExpressionOnStack) {
- var instance_on_stack_type = EmitCallInstance (ec, InstanceExpression, method.DeclaringType, call_op);
+ 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 (instance_on_stack_type);
+ lt = new LocalTemporary (ie.GetStackType (ec));
lt.Store (ec);
instance_copy = lt;
}
EmittedArguments = Arguments.Emit (ec, DuplicateArguments, HasAwaitArguments);
if (EmittedArguments != null) {
if (instance_copy != null) {
- EmitCallInstance (ec, instance_copy, method.DeclaringType, call_op);
+ var ie = new InstanceEmitter (instance_copy, IsAddressCall (instance_copy, call_op, method.DeclaringType));
+ ie.Emit (ec, ConditionalAccess);
if (lt != null)
lt.Release (ec);
}
}
- if (call_op == OpCodes.Callvirt && (InstanceExpression.Type.IsGenericParameter || InstanceExpression.Type.IsStruct)) {
+ if (call_op == OpCodes.Callvirt && (InstanceExpression.Type.IsGenericParameter || InstanceExpression.Type.IsStructOrEnum)) {
ec.Emit (OpCodes.Constrained, InstanceExpression.Type);
}
if (method.Parameters.HasArglist) {
var varargs_types = GetVarargsTypes (method, Arguments);
ec.Emit (call_op, method, varargs_types);
- return;
+ } 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;
+
//
- // If you have:
- // this.DoFoo ();
- // and DoFoo is not virtual, you can omit the callvirt,
- // because you don't need the null checking behavior.
+ // It's non-virtual and will never be null and it can be determined
+ // whether it's known value or reference type by verifier
//
- ec.Emit (call_op, method);
+ 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);
+ }
+ }
}
- static TypeSpec EmitCallInstance (EmitContext ec, Expression instance, TypeSpec declaringType, OpCode callOpcode)
+ public void EmitLoad (EmitContext ec)
{
var instance_type = instance.Type;
//
// Push the instance expression
//
- if ((instance_type.IsStruct && (callOpcode == OpCodes.Callvirt || (callOpcode == OpCodes.Call && declaringType.IsStruct))) ||
- instance_type.IsGenericParameter || declaringType.IsNullableType) {
+ if (addressRequired) {
//
// If the expression implements IMemoryLocation, then
// we can optimize and use AddressOf on the
temp.AddressOf (ec, AddressOp.Load);
}
- return ReferenceContainer.MakeType (ec.Module, instance_type);
+ return;
}
- if (instance_type.IsEnum || instance_type.IsStruct) {
- instance.Emit (ec);
+ instance.Emit (ec);
+
+ // Only to make verifier happy
+ if (RequiresBoxing ())
ec.Emit (OpCodes.Box, instance_type);
- return ec.BuiltinTypes.Object;
- }
+ }
+
+ 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;
- instance.Emit (ec);
return instance_type;
}
- static MetaType[] GetVarargsTypes (MethodSpec method, Arguments arguments)
+ bool RequiresBoxing ()
{
- AParametersCollection pd = method.Parameters;
+ var instance_type = instance.Type;
+ if (instance_type.IsGenericParameter && !(instance is This) && TypeSpec.IsReferenceType (instance_type))
+ return true;
- Argument a = arguments[pd.Count - 1];
- Arglist list = (Arglist) a.Expr;
+ if (instance_type.IsStructOrEnum)
+ return true;
- return list.ArgumentTypes;
+ return false;
}
- //
- // Used to decide whether call or callvirt is needed
- //
- static bool IsVirtualCallRequired (Expression instance, MethodSpec method)
+ bool IsInexpensiveLoad ()
{
- //
- // 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 Constant)
+ return instance.IsSideEffectFree;
- if (instance is BaseThis)
+ if (RequiresBoxing ())
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 && (instance is This || instance is New || instance is ArrayCreation || instance is DelegateCreation) &&
- !instance.Type.IsGenericParameter)
- return false;
+ var vr = instance as VariableReference;
+ if (vr != null)
+ return !vr.IsRef;
- return true;
+ if (instance is LocalTemporary)
+ return true;
+
+ var fe = instance as FieldExpr;
+ if (fe != null)
+ return fe.IsStatic || fe.InstanceExpression is This;
+
+ return false;
}
}
}