Browse Source

Fix #2527: Support skip locals init

pull/2529/head
Siegfried Pammer 4 years ago
parent
commit
e1ca4db851
  1. 4
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 89
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  3. 7
      ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs
  4. 3
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  5. 6
      ICSharpCode.Decompiler/IL/ILReader.cs
  6. 112
      ICSharpCode.Decompiler/IL/ILVariable.cs
  7. 6
      ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
  8. 7
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  9. 18
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

4
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2444,9 +2444,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2444,9 +2444,9 @@ namespace ICSharpCode.Decompiler.CSharp
var body = statementBuilder.ConvertAsBlock(container);
var comment = new Comment(" Could not convert BlockContainer to single expression");
body.InsertChildAfter(null, comment, Roles.Comment);
// set ILVariable.HasInitialValue for any variables being used inside the container
// set ILVariable.UsesInitialValue for any variables being used inside the container
foreach (var stloc in container.Descendants.OfType<StLoc>())
stloc.Variable.HasInitialValue = true;
stloc.Variable.UsesInitialValue = true;
var ame = new AnonymousMethodExpression { Body = body };
var systemFuncType = compilation.FindType(typeof(Func<>));
var blockReturnType = InferReturnType(body);

89
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -70,6 +70,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -70,6 +70,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
enum VariableInitKind
{
None,
NeedsDefaultValue,
NeedsSkipInit
}
[DebuggerDisplay("VariableToDeclare(Name={Name})")]
class VariableToDeclare
{
@ -80,7 +87,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -80,7 +87,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// <summary>
/// Whether the variable needs to be default-initialized.
/// </summary>
public bool DefaultInitialization;
public VariableInitKind DefaultInitialization;
/// <summary>
/// Integer value that can be used to compare to VariableToDeclare instances
@ -106,10 +113,24 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -106,10 +113,24 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public bool RemovedDueToCollision => ReplacementDueToCollision != null;
public bool DeclaredInDeconstruction;
public VariableToDeclare(ILVariable variable, bool defaultInitialization, InsertionPoint insertionPoint, IdentifierExpression firstUse, int sourceOrder)
public VariableToDeclare(ILVariable variable, InsertionPoint insertionPoint, IdentifierExpression firstUse, int sourceOrder)
{
this.ILVariable = variable;
this.DefaultInitialization = defaultInitialization;
if (variable.UsesInitialValue)
{
if (variable.InitialValueIsInitialized)
{
this.DefaultInitialization = VariableInitKind.NeedsDefaultValue;
}
else
{
this.DefaultInitialization = VariableInitKind.NeedsSkipInit;
}
}
else
{
this.DefaultInitialization = VariableInitKind.None;
}
this.InsertionPoint = insertionPoint;
this.FirstUse = firstUse;
this.SourceOrder = sourceOrder;
@ -304,7 +325,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -304,7 +325,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
else
{
newPoint = new InsertionPoint { level = nodeLevel, nextNode = identExpr };
if (variable.HasInitialValue)
if (variable.UsesInitialValue)
{
// Uninitialized variables are logically initialized at the beginning of the function
// Because it's possible that the variable has a loop-carried dependency,
@ -331,8 +352,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -331,8 +352,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
else
{
v = new VariableToDeclare(variable, variable.HasInitialValue,
newPoint, identExpr, sourceOrder: variableDict.Count);
v = new VariableToDeclare(variable, newPoint,
identExpr, sourceOrder: variableDict.Count);
variableDict.Add(variable, v);
}
}
@ -623,17 +644,69 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -623,17 +644,69 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// Insert a separate declaration statement.
Expression initializer = null;
AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type);
if (v.DefaultInitialization)
if (v.DefaultInitialization == VariableInitKind.NeedsDefaultValue)
{
initializer = new DefaultValueExpression(type.Clone());
}
var vds = new VariableDeclarationStatement(type, v.Name, initializer);
vds.Variables.Single().AddAnnotation(new ILVariableResolveResult(ilVariable));
Debug.Assert(v.InsertionPoint.nextNode.Role == BlockStatement.StatementRole);
if (v.DefaultInitialization == VariableInitKind.NeedsSkipInit)
{
AstType unsafeType = context.TypeSystemAstBuilder.ConvertType(
context.TypeSystem.FindType(KnownTypeCode.Unsafe));
if (context.Settings.OutVariables)
{
var outVarDecl = new OutVarDeclarationExpression(type.Clone(), v.Name);
outVarDecl.Variable.AddAnnotation(new ILVariableResolveResult(ilVariable));
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
v.InsertionPoint.nextNode,
new ExpressionStatement {
Expression = new InvocationExpression {
Target = new MemberReferenceExpression {
Target = new TypeReferenceExpression(unsafeType),
MemberName = "SkipInit"
},
Arguments = {
outVarDecl
}
}
},
BlockStatement.StatementRole);
}
else
{
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
v.InsertionPoint.nextNode,
vds,
BlockStatement.StatementRole);
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
v.InsertionPoint.nextNode,
new ExpressionStatement {
Expression = new InvocationExpression {
Target = new MemberReferenceExpression {
Target = new TypeReferenceExpression(unsafeType),
MemberName = "SkipInit"
},
Arguments = {
new DirectionExpression(
FieldDirection.Out,
new IdentifierExpression(v.Name)
.WithRR(new ILVariableResolveResult(ilVariable))
)
}
}
},
BlockStatement.StatementRole);
}
}
else
{
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
v.InsertionPoint.nextNode,
vds,
BlockStatement.StatementRole);
}
}
}
// perform replacements at end, so that we don't replace a node while it is still referenced by a VariableToDeclare
@ -650,7 +723,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -650,7 +723,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false;
if (!context.Settings.OutVariables)
return false;
if (v.DefaultInitialization)
if (v.DefaultInitialization != VariableInitKind.None)
return false;
for (AstNode node = v.FirstUse; node != null; node = node.Parent)
{

7
ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs

@ -296,14 +296,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -296,14 +296,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
var stores = storesByVar[vi];
if (stores != null)
{
int expectedStoreCount = scope.Variables[vi].StoreCount;
if (!scope.Variables[vi].HasInitialValue)
{
int expectedStoreCount = scope.Variables[vi].StoreInstructions.Count;
// Extra store for the uninitialized state.
expectedStoreCount += 1;
// Note that for variables with HasInitialValue=true,
// this extra store is already accounted for in ILVariable.StoreCount.
}
Debug.Assert(stores.Count == expectedStoreCount);
stores.CopyTo(allStores, si);
// Add all stores except for the first (representing the uninitialized state)

3
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -995,7 +995,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -995,7 +995,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
name = fieldDef.Name.Substring(1, pos - 1);
}
v = function.RegisterVariable(VariableKind.Local, ldflda.Field.ReturnType, name);
v.HasInitialValue = true; // the field was default-initialized, so keep those semantics for the variable
v.InitialValueIsInitialized = true; // the field was default-initialized, so keep those semantics for the variable
v.UsesInitialValue = true;
v.StateMachineField = ldflda.Field;
fieldToVariableMap.Add(fieldDef, v);
}

6
ICSharpCode.Decompiler/IL/ILReader.cs

@ -118,12 +118,10 @@ namespace ICSharpCode.Decompiler.IL @@ -118,12 +118,10 @@ namespace ICSharpCode.Decompiler.IL
this.methodReturnStackType = method.ReturnType.GetStackType();
InitParameterVariables();
localVariables = InitLocalVariables();
if (body.LocalVariablesInitialized)
{
foreach (var v in localVariables)
{
v.HasInitialValue = true;
}
v.InitialValueIsInitialized = body.LocalVariablesInitialized;
v.UsesInitialValue = true;
}
this.mainContainer = new BlockContainer(expectedResultType: methodReturnStackType);
this.instructionBuilder = new List<ILInstruction>();

112
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -252,13 +252,13 @@ namespace ICSharpCode.Decompiler.IL @@ -252,13 +252,13 @@ namespace ICSharpCode.Decompiler.IL
/// <item>stloc</item>
/// <item>TryCatchHandler (assigning the exception variable)</item>
/// <item>PinnedRegion (assigning the pointer variable)</item>
/// <item>initial values (<see cref="HasInitialValue"/>)</item>
/// <item>initial values (<see cref="UsesInitialValue"/>)</item>
/// </list>
/// </summary>
/// <remarks>
/// This variable is automatically updated when adding/removing stores instructions from the ILAst.
/// </remarks>
public int StoreCount => (hasInitialValue ? 1 : 0) + StoreInstructions.Count;
public int StoreCount => (usesInitialValue ? 1 : 0) + StoreInstructions.Count;
readonly List<IStoreInstruction> storeInstructions = new List<IStoreInstruction>();
@ -270,7 +270,7 @@ namespace ICSharpCode.Decompiler.IL @@ -270,7 +270,7 @@ namespace ICSharpCode.Decompiler.IL
/// <item>stloc</item>
/// <item>TryCatchHandler (assigning the exception variable)</item>
/// <item>PinnedRegion (assigning the pointer variable)</item>
/// <item>initial values (<see cref="HasInitialValue"/>) -- however, there is no instruction for
/// <item>initial values (<see cref="UsesInitialValue"/>) -- however, there is no instruction for
/// the initial value, so it is not contained in the store list.</item>
/// </list>
/// </summary>
@ -320,27 +320,86 @@ namespace ICSharpCode.Decompiler.IL @@ -320,27 +320,86 @@ namespace ICSharpCode.Decompiler.IL
list.RemoveAt(indexToMove);
}
bool hasInitialValue;
bool initialValueIsInitialized;
/// <summary>
/// Gets/Sets whether the variable has an initial value.
/// Gets/Sets whether the variable's initial value is initialized.
/// This is always <c>true</c> for parameters (incl. <c>this</c>).
///
/// Normal variables have an initial value if the function uses ".locals init"
/// and that initialization is not a dead store.
/// Normal variables have an initial value if the function uses ".locals init".
/// </summary>
public bool InitialValueIsInitialized {
get { return initialValueIsInitialized; }
set {
if (Kind == VariableKind.Parameter && !value)
throw new InvalidOperationException("Cannot remove InitialValueIsInitialized from parameters");
initialValueIsInitialized = value;
}
}
bool usesInitialValue;
/// <summary>
/// Gets/Sets whether the initial value of the variable is used.
/// This is always <c>true</c> for parameters (incl. <c>this</c>).
///
/// Normal variables use the initial value, if no explicit initialization is done.
/// </summary>
/// <remarks>
/// An initial value is counted as a store (adds 1 to StoreCount)
/// The following table shows the relationship between <see cref="InitialValueIsInitialized"/>
/// and <see cref="UsesInitialValue"/>.
/// <list type="table">
/// <listheader>
/// <term><see cref="InitialValueIsInitialized"/></term>
/// <term><see cref="UsesInitialValue"/></term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term><see langword="true" /></term>
/// <term><see langword="true" /></term>
/// <term>This variable's initial value is zero-initialized (<c>.locals init</c>) and the initial value is used.
/// From C#'s point of view a the value <c>default(T)</c> is assigned at the site of declaration.</term>
/// </item>
/// <item>
/// <term><see langword="true" /></term>
/// <term><see langword="false" /></term>
/// <term>This variable's initial value is zero-initialized (<c>.locals init</c>) and the initial value is not used.
/// From C#'s point of view no implicit initialization occurs, because the code assigns a value
/// explicitly, before the variable is first read.</term>
/// </item>
/// <item>
/// <term><see langword="false" /></term>
/// <term><see langword="true" /></term>
/// <term>This variable's initial value is uninitialized (<c>.locals</c> without <c>init</c>) and the
/// initial value is used.
/// From C#'s point of view a call to <see cref="System.Runtime.CompilerServices.Unsafe.SkipInit{T}(out T)"/>
/// is generated after the declaration.</term>
/// </item>
/// <item>
/// <term><see langword="false" /></term>
/// <term><see langword="false" /></term>
/// <term>This variable's initial value is uninitialized (<c>.locals</c> without <c>init</c>) and the
/// initial value is not used.
/// From C#'s point of view no implicit initialization occurs, because the code assigns a value
/// explicitly, before the variable is first read.</term>
/// </item>
/// </list>
/// </remarks>
public bool HasInitialValue {
get { return hasInitialValue; }
public bool UsesInitialValue {
get { return usesInitialValue; }
set {
if (Kind == VariableKind.Parameter && !value)
throw new InvalidOperationException("Cannot remove HasInitialValue from parameters");
hasInitialValue = value;
throw new InvalidOperationException("Cannot remove UsesInitialValue from parameters");
usesInitialValue = value;
}
}
[Obsolete("Use 'UsesInitialValue' instead.")]
public bool HasInitialValue {
get => UsesInitialValue;
set => UsesInitialValue = value;
}
/// <summary>
/// Gets whether the variable is in SSA form:
/// There is exactly 1 store, and every load sees the value from that store.
@ -362,7 +421,7 @@ namespace ICSharpCode.Decompiler.IL @@ -362,7 +421,7 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public bool IsDead {
get {
return StoreCount == (HasInitialValue ? 1 : 0)
return StoreInstructions.Count == 0
&& LoadCount == 0
&& AddressCount == 0;
}
@ -388,7 +447,10 @@ namespace ICSharpCode.Decompiler.IL @@ -388,7 +447,10 @@ namespace ICSharpCode.Decompiler.IL
this.StackType = type.GetStackType();
this.Index = index;
if (kind == VariableKind.Parameter)
this.HasInitialValue = true;
{
this.InitialValueIsInitialized = true;
this.UsesInitialValue = true;
}
CheckInvariant();
}
@ -401,7 +463,10 @@ namespace ICSharpCode.Decompiler.IL @@ -401,7 +463,10 @@ namespace ICSharpCode.Decompiler.IL
this.StackType = stackType;
this.Index = index;
if (kind == VariableKind.Parameter)
this.HasInitialValue = true;
{
this.InitialValueIsInitialized = true;
this.UsesInitialValue = true;
}
CheckInvariant();
}
@ -472,10 +537,25 @@ namespace ICSharpCode.Decompiler.IL @@ -472,10 +537,25 @@ namespace ICSharpCode.Decompiler.IL
output.Write("Index={0}, ", Index);
}
output.Write("LoadCount={0}, AddressCount={1}, StoreCount={2})", LoadCount, AddressCount, StoreCount);
if (hasInitialValue && Kind != VariableKind.Parameter)
if (Kind != VariableKind.Parameter)
{
if (initialValueIsInitialized)
{
output.Write(" init");
}
else
{
output.Write(" uninit");
}
if (usesInitialValue)
{
output.Write(" used");
}
else
{
output.Write(" unused");
}
}
if (CaptureScope != null)
{
output.Write(" captured in ");

6
ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs

@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
public void Run(ILFunction function, ILTransformContext context)
{
ResetHasInitialValueFlag(function, context);
ResetUsesInitialValueFlag(function, context);
// Remove dead stores to variables that are never read from.
// If the stored value has some side-effect, the value is unwrapped.
// This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler.
@ -105,7 +105,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -105,7 +105,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
internal static void ResetHasInitialValueFlag(ILFunction function, ILTransformContext context)
internal static void ResetUsesInitialValueFlag(ILFunction function, ILTransformContext context)
{
var visitor = new DefiniteAssignmentVisitor(function, context.CancellationToken);
function.AcceptVisitor(visitor);
@ -113,7 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -113,7 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
if (v.Kind != VariableKind.Parameter && !visitor.IsPotentiallyUsedUninitialized(v))
{
v.HasInitialValue = false;
v.UsesInitialValue = false;
}
}
}

7
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs

@ -270,14 +270,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -270,14 +270,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
v.Name = inst.Variable.Name;
v.HasGeneratedName = inst.Variable.HasGeneratedName;
v.StateMachineField = inst.Variable.StateMachineField;
v.HasInitialValue = false; // we'll set HasInitialValue when we encounter an uninit load
v.InitialValueIsInitialized = inst.Variable.InitialValueIsInitialized;
v.UsesInitialValue = false; // we'll set UsesInitialValue when we encounter an uninit load
v.RemoveIfRedundant = inst.Variable.RemoveIfRedundant;
newVariables.Add(representative, v);
inst.Variable.Function.Variables.Add(v);
}
if (inst.Variable.HasInitialValue && uninitVariableUsage.TryGetValue(inst.Variable, out var uninitLoad) && uninitLoad == inst)
if (inst.Variable.UsesInitialValue && uninitVariableUsage.TryGetValue(inst.Variable, out var uninitLoad) && uninitLoad == inst)
{
v.HasInitialValue = true;
v.UsesInitialValue = true;
}
return v;
}

18
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -56,7 +56,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -56,7 +56,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public string Name => field.Name;
public bool CanPropagate { get; private set; }
public bool HasInitialValue { get; set; }
public bool UsesInitialValue { get; set; }
public HashSet<ILInstruction> Initializers { get; } = new HashSet<ILInstruction>();
@ -80,7 +80,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -80,7 +80,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (declaredVariable != null)
return declaredVariable;
declaredVariable = container.Variable.Function.RegisterVariable(VariableKind.Local, field.Type, field.Name);
declaredVariable.HasInitialValue = HasInitialValue;
declaredVariable.InitialValueIsInitialized = true;
declaredVariable.UsesInitialValue = UsesInitialValue;
declaredVariable.CaptureScope = container.CaptureScope;
return declaredVariable;
}
@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (displayClass.VariablesToDeclare.ContainsKey(f))
continue;
var variable = AddVariable(displayClass, null, f);
variable.HasInitialValue = true;
variable.UsesInitialValue = true;
displayClass.VariablesToDeclare[(IField)f.MemberDefinition] = variable;
}
@ -304,7 +305,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -304,7 +305,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
HandleInitBlock(stloc.Parent as Block, stloc.ChildIndex + 1, result, result.Variable);
break;
case TypeKind.Struct:
if (v.StoreCount != (v.HasInitialValue ? 1 : 0))
if (v.StoreInstructions.Count != 0)
return null;
Debug.Assert(v.StoreInstructions.Count == 0);
result = new DisplayClass(v, definition) { CaptureScope = v.CaptureScope };
@ -509,8 +510,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -509,8 +510,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
variable.Propagate(ResolveVariableToPropagate(statement.Value, field.Type));
variable.Initializers.Add(statement);
}
variable.HasInitialValue =
result.Type.IsReferenceType != false || result.Variable.HasInitialValue;
variable.UsesInitialValue =
result.Type.IsReferenceType != false || result.Variable.UsesInitialValue;
return variable;
}
@ -583,7 +584,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -583,7 +584,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.Step($"ResetHasInitialValueFlag", function);
foreach (var f in function.Descendants.OfType<ILFunction>())
{
RemoveDeadVariableInit.ResetHasInitialValueFlag(f, context);
RemoveDeadVariableInit.ResetUsesInitialValueFlag(f, context);
f.CapturedVariables.RemoveWhere(v => v.IsDead);
}
}
@ -601,7 +602,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -601,7 +602,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
closureType = variable.Type.GetDefinition();
if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct && variable.HasInitialValue && IsPotentialClosure(context, closureType))
if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct
&& variable.UsesInitialValue && IsPotentialClosure(context, closureType))
{
initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First());
return true;

Loading…
Cancel
Save