Browse Source

more attempts at integer conversion stuff

pull/728/head
Daniel Grunwald 9 years ago
parent
commit
95c8e4bd00
  1. 67
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 108
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 2
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

67
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -485,6 +485,11 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
/// <summary>
/// Whether we need to generate helper methods for u4->i or u->i8 conversions.
/// </summary>
internal bool needs_conv_i_ovf_un, needs_conv_i8_ovf_un;
EntityDeclaration DoDecompile(ITypeDefinition typeDef, ITypeResolveContext decompilationContext) EntityDeclaration DoDecompile(ITypeDefinition typeDef, ITypeResolveContext decompilationContext)
{ {
Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef); Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef);
@ -542,10 +547,69 @@ namespace ICSharpCode.Decompiler.CSharp
section.Remove(); section.Remove();
} }
} }
if (needs_conv_i_ovf_un) {
typeDecl.Members.Add(GenerateConvHelper(
"conv_i_ovf_un", KnownTypeCode.UInt32, KnownTypeCode.IntPtr, typeSystemAstBuilder,
// on 32-bit, 'conv.ovf u4->i' is like 'conv.ovf u4->i4'
new CheckedExpression(new CastExpression(
new NRefactory.CSharp.PrimitiveType("int"),
new IdentifierExpression("input")
)),
// on 64-bit, 'conv.ovf u4->i' is like 'conv.ovf u4->i8'
new IdentifierExpression("input")
));
needs_conv_i_ovf_un = false;
}
if (needs_conv_i8_ovf_un) {
typeDecl.Members.Add(GenerateConvHelper(
"conv_i8_ovf_un", KnownTypeCode.UIntPtr, KnownTypeCode.Int64, typeSystemAstBuilder,
// on 32-bit, 'conv.ovf u->i8' is like 'conv.ovf u4->i8'
new IdentifierExpression("input"),
// on 64-bit, 'conv.ovf u->i8' is like 'conv.ovf u8->i8'
new IdentifierExpression("input")
));
needs_conv_i8_ovf_un = false;
}
return typeDecl; return typeDecl;
} }
MethodDeclaration GenerateConvHelper(string name, KnownTypeCode source, KnownTypeCode target, TypeSystemAstBuilder typeSystemAstBuilder,
Expression intermediate32, Expression intermediate64)
{
MethodDeclaration method = new MethodDeclaration();
method.Name = name;
method.Modifiers = Modifiers.Private | Modifiers.Static;
method.Parameters.Add(new ParameterDeclaration(typeSystemAstBuilder.ConvertType(typeSystem.Compilation.FindType(source)), "input"));
method.ReturnType = typeSystemAstBuilder.ConvertType(typeSystem.Compilation.FindType(target));
method.Body = new BlockStatement {
new IfElseStatement {
Condition = new BinaryOperatorExpression {
Left = typeSystemAstBuilder.ConvertType(typeSystem.Compilation.FindType(KnownTypeCode.IntPtr)).Member("Size"),
Operator = BinaryOperatorType.Equality,
Right = new PrimitiveExpression(4)
},
TrueStatement = new BlockStatement { // 32-bit
new ReturnStatement(
new CastExpression(
method.ReturnType.Clone(),
intermediate32
)
)
},
FalseStatement = new BlockStatement { // 64-bit
new ReturnStatement(
new CastExpression(
method.ReturnType.Clone(),
intermediate64
)
)
},
}
};
return method;
}
EntityDeclaration DoDecompile(MethodDefinition methodDefinition, IMethod method, ITypeResolveContext decompilationContext) EntityDeclaration DoDecompile(MethodDefinition methodDefinition, IMethod method, ITypeResolveContext decompilationContext)
{ {
Debug.Assert(decompilationContext.CurrentMember == method); Debug.Assert(decompilationContext.CurrentMember == method);
@ -611,6 +675,9 @@ namespace ICSharpCode.Decompiler.CSharp
var statementBuilder = new StatementBuilder(decompilationContext, method); var statementBuilder = new StatementBuilder(decompilationContext, method);
var body = statementBuilder.ConvertAsBlock(function.Body); var body = statementBuilder.ConvertAsBlock(function.Body);
needs_conv_i_ovf_un |= statementBuilder.exprBuilder.needs_conv_i_ovf_un;
needs_conv_i8_ovf_un |= statementBuilder.exprBuilder.needs_conv_i8_ovf_un;
entityDecl.AddChild(body, Roles.Body); entityDecl.AddChild(body, Roles.Body);
} }

108
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -609,6 +609,11 @@ namespace ICSharpCode.Decompiler.CSharp
return result; return result;
} }
/// <summary>
/// Whether we need to generate helper methods for u4->i or u->i8 conversions.
/// </summary>
internal bool needs_conv_i_ovf_un, needs_conv_i8_ovf_un;
protected internal override TranslatedExpression VisitConv(Conv inst) protected internal override TranslatedExpression VisitConv(Conv inst)
{ {
var arg = Translate(inst.Argument); var arg = Translate(inst.Argument);
@ -623,6 +628,61 @@ namespace ICSharpCode.Decompiler.CSharp
// Also, we need to be very careful with regards to the conversions we emit: // Also, we need to be very careful with regards to the conversions we emit:
// In C#, zero vs. sign-extension depends on the input type, // In C#, zero vs. sign-extension depends on the input type,
// but in the ILAst Conv instruction it depends on the output type. // but in the ILAst Conv instruction it depends on the output type.
if (inst.CheckForOverflow || inst.Kind == ConversionKind.IntToFloat) {
// We need to first convert the argument to the expected sign.
// We also need to perform any input narrowing conversion so that it doesn't get mixed up with the overflow check.
Debug.Assert(inst.InputSign != Sign.None);
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
if (inst.Kind == ConversionKind.ZeroExtend) {
// Zero extension with overflow check -> throws if the input value is negative.
// C# sign/zero extension depends on the source type, so we can't directly
// cast from the signed input type to the target type.
// Instead, perform a checked cast to the unsigned version of the input type
// (to throw an exception for negative values).
// Then the actual zero extension can be left to our parent instruction
// (due to the ExpressionBuilder post-condition being flexible with regards to the integer type width).
return arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this, true)
.WithILInstruction(inst);
} else if (inst.Kind == ConversionKind.SignExtend) {
// Sign extension with overflow check.
// Sign-extending conversions can fail when the "larger type" isn't actually larger, that is, in exactly two cases:
// * U4 -> I on 32-bit
// * U -> I8 on 64-bit
if (inst.InputSign == Sign.Unsigned && inputStackType == StackType.I4 && inst.TargetType == IL.PrimitiveType.I) {
// conv u4->i
// on 32-bit, this is a sign-changing conversion with overflow-check
// on 64-bit, this is a sign extension
needs_conv_i_ovf_un = true;
arg = arg.ConvertTo(compilation.FindType(KnownTypeCode.UInt32), this);
return new InvocationExpression(new IdentifierExpression("conv_i_ovf_un"), arg.Expression)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr)))
.WithILInstruction(inst);
} else if (inst.InputSign == Sign.Unsigned && (inputStackType == StackType.I && inst.TargetType == IL.PrimitiveType.I8)) {
// conv u->i8
// on 32-bit, this is a sign extension
// on 64-bit, this is a sign-changing conversion with overflow-check
needs_conv_i8_ovf_un = true;
arg = arg.ConvertTo(compilation.FindType(KnownTypeCode.UIntPtr), this);
return new InvocationExpression(new IdentifierExpression("conv_i8_ovf_un"), arg.Expression)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr)))
.WithILInstruction(inst);
} else {
// The overflow check cannot actually fail, so we can take the simple solution of performing
// an unchecked cast to signed int and let the parent instruction handle the actual sign extension.
return arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Signed)), this)
.WithILInstruction(inst);
}
} else {
// Size-preserving sign-changing conversion, or int-to-float conversion:
// We can directly cast to the target type.
return arg.ConvertTo(compilation.FindType(inst.TargetType.ToKnownTypeCode()), this, true)
.WithILInstruction(inst);
}
}
switch (inst.Kind) { switch (inst.Kind) {
case ConversionKind.StopGCTracking: case ConversionKind.StopGCTracking:
if (arg.Type.Kind == TypeKind.ByReference) { if (arg.Type.Kind == TypeKind.ByReference) {
@ -634,8 +694,6 @@ namespace ICSharpCode.Decompiler.CSharp
goto default; goto default;
} }
case ConversionKind.SignExtend: case ConversionKind.SignExtend:
// Sign extension is easy because it can never fail due to overflow checking.
// We just need to ensure the input type before the conversion is signed. // We just need to ensure the input type before the conversion is signed.
// Also, if the argument was translated into an oversized C# type, // Also, if the argument was translated into an oversized C# type,
// we need to perform the truncatation to the input stack type. // we need to perform the truncatation to the input stack type.
@ -650,43 +708,14 @@ namespace ICSharpCode.Decompiler.CSharp
// (our caller may have more information to pick a better fitting target type) // (our caller may have more information to pick a better fitting target type)
return arg.WithILInstruction(inst); return arg.WithILInstruction(inst);
case ConversionKind.ZeroExtend: case ConversionKind.ZeroExtend:
// Zero extension may involve an overflow check.
if (!inst.CheckForOverflow || inst.InputSign == Sign.Unsigned) {
// If overflow check cannot fail, handle this just like sign extension (except for swapped signs) // If overflow check cannot fail, handle this just like sign extension (except for swapped signs)
if (arg.Type.GetSign() != Sign.Unsigned || arg.Type.GetSize() > inputStackType.GetSize()) { if (arg.Type.GetSign() != Sign.Unsigned || arg.Type.GetSize() > inputStackType.GetSize()) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this); arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this);
} }
return arg.WithILInstruction(inst); return arg.WithILInstruction(inst);
}
// Zero extension that should fail if the input type is negative.
// Split this conversion into steps:
// 1) perform input truncation if necessary, and perform unchecked cast to signed input type
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
// 2) perform sign conversion to unsigned with overflow check (input zero/sign extension can be combined with this step),
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this, true);
// 3) leave the actual zero extension to our caller.
return arg.WithILInstruction(inst);
case ConversionKind.Nop: case ConversionKind.Nop:
// Conversion between two types of same size; possibly sign-changing; may involve overflow checking. // no need to generate any C# code for a nop conversion
if (!inst.CheckForOverflow || inst.InputSign == inst.TargetType.GetSign()) {
// a true nop: no need to generate any C# code
return arg.WithILInstruction(inst); return arg.WithILInstruction(inst);
}
// sign-changing, overflow-checking conversion
// If the input conversion is a truncation, we need to perform it (without overflow-checking).
// If the argument has a different type than that expected by the overflow check,
// we need to ensure we use the correct input type.
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
// Note that an input conversion that is a sign/zero-extension can be combined with the
// overflow-checking conversion
goto default; // Perform the actual overflow-checking conversion
case ConversionKind.Truncate: case ConversionKind.Truncate:
// Note: there are three sizes involved here: // Note: there are three sizes involved here:
// A = arg.Type.GetSize() // A = arg.Type.GetSize()
@ -700,16 +729,13 @@ namespace ICSharpCode.Decompiler.CSharp
// In cases 1-3, the overall conversion is a truncation or no-op. // In cases 1-3, the overall conversion is a truncation or no-op.
// In case 4, the overall conversion is a zero/sign extension, but to a smaller // In case 4, the overall conversion is a zero/sign extension, but to a smaller
// size than the original conversion. // size than the original conversion.
if (!inst.CheckForOverflow) {
// Truncation without overflow check.
if (inst.TargetType.IsSmallIntegerType()) { if (inst.TargetType.IsSmallIntegerType()) {
// If the target type is a small integer type, IL will implicitly sign- or zero-extend // If the target type is a small integer type, IL will implicitly sign- or zero-extend
// the result after the truncation back to StackType.I4. // the result after the truncation back to StackType.I4.
// (which means there's actually 3 conversions involved!) // (which means there's actually 3 conversions involved!)
if (arg.Type.GetSize() <= inst.TargetType.GetSize() && arg.Type.GetSign() == inst.TargetType.GetSign()) { if (arg.Type.GetSize() <= inst.TargetType.GetSize() && arg.Type.GetSign() == inst.TargetType.GetSign()) {
// There's no actual truncation involved, and the result of the Conv instruciton is extended // There's no actual truncation involved, and the result of the Conv instruction is extended
// the same way as the original instruction // the same way as the original instruction
// -> we can return arg directly // -> we can return arg directly
return arg.WithILInstruction(inst); return arg.WithILInstruction(inst);
@ -725,16 +751,6 @@ namespace ICSharpCode.Decompiler.CSharp
// Case 4 (left-over extension from implicit conversion) can also be handled by our caller. // Case 4 (left-over extension from implicit conversion) can also be handled by our caller.
return arg.WithILInstruction(inst); return arg.WithILInstruction(inst);
} }
}
// Truncation with overflow check
// Similar to nop-case: perform input truncation without overflow checking + ensure correct input sign.
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
goto default; // perform cast with overflow-check
case ConversionKind.IntToFloat: // TODO: handle these conversions correctly
case ConversionKind.FloatToInt:
case ConversionKind.FloatPrecisionChange:
default: default:
return arg.ConvertTo(compilation.FindType(inst.TargetType.ToKnownTypeCode()), this, inst.CheckForOverflow) return arg.ConvertTo(compilation.FindType(inst.TargetType.ToKnownTypeCode()), this, inst.CheckForOverflow)
.WithILInstruction(inst); .WithILInstruction(inst);

2
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -30,7 +30,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
class StatementBuilder : ILVisitor<Statement> class StatementBuilder : ILVisitor<Statement>
{ {
readonly ExpressionBuilder exprBuilder; internal readonly ExpressionBuilder exprBuilder;
readonly IMethod currentMethod; readonly IMethod currentMethod;
public StatementBuilder(ITypeResolveContext decompilationContext, IMethod currentMethod) public StatementBuilder(ITypeResolveContext decompilationContext, IMethod currentMethod)

Loading…
Cancel
Save