mirror of https://github.com/icsharpcode/ILSpy.git
11 changed files with 430 additions and 44 deletions
@ -0,0 +1,159 @@
@@ -0,0 +1,159 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.Transforms |
||||
{ |
||||
class NullableLiftingTransform |
||||
{ |
||||
public static void Run(IfInstruction inst, ILTransformContext context) |
||||
{ |
||||
// if (call Nullable<inputUType>.get_HasValue(ldloca v))
|
||||
// newobj Nullable<utype>.ctor(...)
|
||||
// else
|
||||
// default.value System.Nullable<utype>[[System.Int32]]
|
||||
if (MatchHasValueCall(inst.Condition, out var v) |
||||
&& MatchNullableCtor(inst.TrueInst, out var utype, out var arg) |
||||
&& MatchNull(inst.FalseInst, utype)) |
||||
{ |
||||
ILInstruction lifted; |
||||
if (MatchGetValueOrDefault(arg, v)) { |
||||
// v != null ? call GetValueOrDefault(ldloca v) : null
|
||||
// => conv.nop.lifted(ldloc v)
|
||||
// This case is handled separately from LiftUnary() because
|
||||
// that doesn't introduce nop-conversions.
|
||||
context.Step("if => conv.nop.lifted", inst); |
||||
var inputUType = NullableType.GetUnderlyingType(v.Type); |
||||
lifted = new Conv(new LdLoc(v), inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), false) { |
||||
IsLifted = true, |
||||
ILRange = inst.ILRange |
||||
}; |
||||
} else { |
||||
lifted = LiftUnary(arg, v, context); |
||||
} |
||||
if (lifted != null) { |
||||
Debug.Assert(IsLifted(lifted)); |
||||
inst.ReplaceWith(lifted); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Lifting for unary expressions.
|
||||
/// Recurses into the instruction and checks that everything can be lifted,
|
||||
/// and that the chain ends with `call GetValueOrDefault(ldloca inputVar)`.
|
||||
/// </summary>
|
||||
static ILInstruction LiftUnary(ILInstruction inst, ILVariable inputVar, ILTransformContext context) |
||||
{ |
||||
if (MatchGetValueOrDefault(inst, inputVar)) { |
||||
// We found the end: the whole chain can be lifted.
|
||||
context.Step("NullableLiftingTransform.LiftUnary", inst); |
||||
return new LdLoc(inputVar) { ILRange = inst.ILRange }; |
||||
} else if (inst is Conv conv) { |
||||
var arg = LiftUnary(conv.Argument, inputVar, context); |
||||
if (arg != null) { |
||||
conv.Argument = arg; |
||||
conv.IsLifted = true; |
||||
return conv; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
static bool IsLifted(ILInstruction inst) |
||||
{ |
||||
return inst is ILiftableInstruction liftable && liftable.IsLifted; |
||||
} |
||||
|
||||
#region Match...Call
|
||||
/// <summary>
|
||||
/// Matches 'call get_HasValue(ldloca v)'
|
||||
/// </summary>
|
||||
static bool MatchHasValueCall(ILInstruction inst, out ILVariable v) |
||||
{ |
||||
v = null; |
||||
if (!(inst is Call call)) |
||||
return false; |
||||
if (call.Arguments.Count != 1) |
||||
return false; |
||||
if (call.Method.Name != "get_HasValue") |
||||
return false; |
||||
if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT) |
||||
return false; |
||||
return call.Arguments[0].MatchLdLoca(out v); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Matches 'newobj Nullable{underlyingType}.ctor(arg)'
|
||||
/// </summary>
|
||||
static bool MatchNullableCtor(ILInstruction inst, out IType underlyingType, out ILInstruction arg) |
||||
{ |
||||
underlyingType = null; |
||||
arg = null; |
||||
if (!(inst is NewObj newobj)) |
||||
return false; |
||||
if (!newobj.Method.IsConstructor || newobj.Arguments.Count != 1) |
||||
return false; |
||||
if (newobj.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT) |
||||
return false; |
||||
arg = newobj.Arguments[0]; |
||||
underlyingType = NullableType.GetUnderlyingType(newobj.Method.DeclaringType); |
||||
return true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Matches 'call Nullable{T}.GetValueOrDefault(arg)'
|
||||
/// </summary>
|
||||
static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction arg) |
||||
{ |
||||
arg = null; |
||||
if (!(inst is Call call)) |
||||
return false; |
||||
if (call.Method.Name != "GetValueOrDefault" || call.Arguments.Count != 1) |
||||
return false; |
||||
if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT) |
||||
return false; |
||||
arg = call.Arguments[0]; |
||||
return true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)'
|
||||
/// </summary>
|
||||
static bool MatchGetValueOrDefault(ILInstruction inst, out ILVariable v) |
||||
{ |
||||
v = null; |
||||
return MatchGetValueOrDefault(inst, out ILInstruction arg) |
||||
&& arg.MatchLdLoca(out v); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)'
|
||||
/// </summary>
|
||||
static bool MatchGetValueOrDefault(ILInstruction inst, ILVariable v) |
||||
{ |
||||
return MatchGetValueOrDefault(inst, out ILVariable v2) && v == v2; |
||||
} |
||||
|
||||
static bool MatchNull(ILInstruction inst, out IType underlyingType) |
||||
{ |
||||
underlyingType = null; |
||||
if (inst.MatchDefaultValue(out IType type)) { |
||||
underlyingType = NullableType.GetUnderlyingType(type); |
||||
return NullableType.IsNullable(type); |
||||
} |
||||
underlyingType = null; |
||||
return false; |
||||
} |
||||
|
||||
static bool MatchNull(ILInstruction inst, IType underlyingType) |
||||
{ |
||||
return MatchNull(inst, out var utype) && utype.Equals(underlyingType); |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
Loading…
Reference in new issue