Skip to content

Commit 3fdd774

Browse files
Copilothazzik
andcommitted
Generalize enum conversion logic - remove operation-specific special cases
Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com>
1 parent c2adcf6 commit 3fdd774

File tree

5 files changed

+93
-72
lines changed

5 files changed

+93
-72
lines changed

src/DelegateDecompiler/Processor.cs

Lines changed: 31 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -229,77 +229,18 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ
229229
left = AdjustBooleanConstant(left, rightType);
230230
right = AdjustBooleanConstant(right, leftType);
231231

232-
// For comparison, bitwise, and arithmetic operations, convert enums to handle type mismatches
233-
// This handles cases where an enum with a non-int underlying type (e.g., byte, long) is used
234-
// with an int constant loaded from IL
235-
if (IsComparisonOperation(expressionType) || IsBitwiseOperation(expressionType) || IsArithmeticOperation(expressionType))
236-
{
237-
// Determine if we're dealing with long enums
238-
var leftIsLongEnum = left.Type.IsEnum && (left.Type.GetEnumUnderlyingType() == typeof(long) || left.Type.GetEnumUnderlyingType() == typeof(ulong));
239-
var rightIsLongEnum = right.Type.IsEnum && (right.Type.GetEnumUnderlyingType() == typeof(long) || right.Type.GetEnumUnderlyingType() == typeof(ulong));
240-
241-
var hasLongEnum = leftIsLongEnum || rightIsLongEnum;
242-
243-
left = ConvertEnumExpressionToInt(left);
244-
right = ConvertEnumExpressionToInt(right);
245-
246-
// Ensure type compatibility after conversions
247-
if (!hasLongEnum)
248-
{
249-
// For byte/short enums, ensure both are int
250-
if (left.Type == typeof(byte) || left.Type == typeof(sbyte) || left.Type == typeof(short) || left.Type == typeof(ushort))
251-
left = Expression.Convert(left, typeof(int));
252-
if (right.Type == typeof(byte) || right.Type == typeof(sbyte) || right.Type == typeof(short) || right.Type == typeof(ushort))
253-
right = Expression.Convert(right, typeof(int));
254-
}
255-
}
256-
else
257-
{
258-
left = ConvertEnumExpressionToUnderlyingType(left);
259-
right = ConvertEnumExpressionToUnderlyingType(right);
260-
}
232+
// Convert enums to their appropriate type for operations
233+
// This uses ConvertEnumExpressionToInt which handles:
234+
// - byte/short enums -> int
235+
// - long/ulong enums -> long/ulong
236+
// - Optimizes Convert(intConstant, long) -> longConstant
237+
// - Handles Convert(Convert(enum, byte), int) -> Convert(enum, int)
238+
left = ConvertEnumExpressionToInt(left);
239+
right = ConvertEnumExpressionToInt(right);
261240

262241
return Expression.MakeBinary(expressionType, left, right);
263242
}
264243

265-
static bool IsComparisonOperation(ExpressionType expressionType)
266-
{
267-
return expressionType == ExpressionType.Equal ||
268-
expressionType == ExpressionType.NotEqual ||
269-
expressionType == ExpressionType.LessThan ||
270-
expressionType == ExpressionType.LessThanOrEqual ||
271-
expressionType == ExpressionType.GreaterThan ||
272-
expressionType == ExpressionType.GreaterThanOrEqual;
273-
}
274-
275-
static bool IsBitwiseOperation(ExpressionType expressionType)
276-
{
277-
return expressionType == ExpressionType.And ||
278-
expressionType == ExpressionType.Or ||
279-
expressionType == ExpressionType.ExclusiveOr;
280-
}
281-
282-
static bool IsArithmeticOperation(ExpressionType expressionType)
283-
{
284-
return expressionType == ExpressionType.Add ||
285-
expressionType == ExpressionType.Subtract ||
286-
expressionType == ExpressionType.Multiply ||
287-
expressionType == ExpressionType.Divide ||
288-
expressionType == ExpressionType.Modulo;
289-
}
290-
291-
static Expression UnwrapConstantConversion(Expression expression)
292-
{
293-
// If this is Convert(constant, targetType), unwrap it back to the constant
294-
if (expression is UnaryExpression unary &&
295-
unary.NodeType == ExpressionType.Convert &&
296-
unary.Operand is ConstantExpression)
297-
{
298-
return unary.Operand;
299-
}
300-
return expression;
301-
}
302-
303244
internal static Expression ConvertEnumExpressionToInt(Expression expression)
304245
{
305246
// If the expression is already an int, return as-is
@@ -331,6 +272,21 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression)
331272
return Expression.Convert(operand, typeof(int));
332273
}
333274

275+
// Optimize Convert(Convert(X, byte/short), Y) when going through unnecessary intermediate conversions
276+
// This happens with operations like NOT that return int but IL converts to byte before enum
277+
if (operand is UnaryExpression innerUnary && innerUnary.NodeType == ExpressionType.Convert)
278+
{
279+
var innerOperand = innerUnary.Operand;
280+
// If we're converting from int through byte/short, skip the intermediate conversion
281+
if (innerOperand.Type == typeof(int) &&
282+
(innerUnary.Type == typeof(byte) || innerUnary.Type == typeof(sbyte) ||
283+
innerUnary.Type == typeof(short) || innerUnary.Type == typeof(ushort)))
284+
{
285+
// Keep just the inner operand, let the outer conversion happen
286+
return Expression.Convert(innerOperand, expression.Type);
287+
}
288+
}
289+
334290
// Optimize Convert(intConstant, long) to a direct long constant
335291
if (operand is ConstantExpression constant &&
336292
constant.Type == typeof(int) &&
@@ -347,7 +303,14 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression)
347303
internal static Expression ConvertEnumExpressionToUnderlyingType(Expression expression)
348304
{
349305
if (expression.Type.IsEnum)
350-
return Expression.Convert(expression, expression.Type.GetEnumUnderlyingType());
306+
{
307+
var underlyingType = expression.Type.GetEnumUnderlyingType();
308+
// C# promotes byte/sbyte/short/ushort enums to int for operations
309+
// Only long/ulong stay as their underlying type
310+
if (underlyingType == typeof(long) || underlyingType == typeof(ulong))
311+
return Expression.Convert(expression, underlyingType);
312+
return Expression.Convert(expression, typeof(int));
313+
}
351314

352315
return expression;
353316
}

src/DelegateDecompiler/Processors/BoxProcessor.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ static Expression Box(Expression expression, Type type)
3232
if (expression.Type.IsEnum)
3333
return Expression.Convert(expression, type);
3434

35+
// Optimize Convert(Convert(int, byte/short), enum) -> Convert(int, enum)
36+
// This happens when operations like NOT return int, IL converts to byte, then boxes to enum
37+
if (type.IsEnum && expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
38+
{
39+
var operand = unary.Operand;
40+
// Skip intermediate conversions from int to byte/short when boxing to enum
41+
if (operand.Type == typeof(int) &&
42+
(unary.Type == typeof(byte) || unary.Type == typeof(sbyte) ||
43+
unary.Type == typeof(short) || unary.Type == typeof(ushort)))
44+
{
45+
// Box the int directly to the enum
46+
return Expression.Convert(operand, type);
47+
}
48+
}
49+
3550
return expression;
3651
}
3752
}

src/DelegateDecompiler/Processors/ConvertProcessor.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ public static void Register(Dictionary<OpCode, IProcessor> processors)
2323
public void Process(ProcessorState state, Instruction instruction)
2424
{
2525
var val = state.Stack.Pop();
26-
state.Stack.Push(Expression.Convert(val, targetType));
26+
Expression expr = val;
27+
28+
// Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X)
29+
// This happens when operations like NOT return int, IL converts to byte, then to enum
30+
if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
31+
{
32+
var operand = unary.Operand;
33+
// Skip intermediate conversions from int to byte/short when going to another type
34+
if (operand.Type == typeof(int) &&
35+
(unary.Type == typeof(byte) || unary.Type == typeof(sbyte) ||
36+
unary.Type == typeof(short) || unary.Type == typeof(ushort)))
37+
{
38+
// Use the int directly, skip the intermediate byte/short conversion
39+
expr = operand;
40+
}
41+
}
42+
43+
state.Stack.Push(Expression.Convert(expr, targetType));
2744
}
2845
}

src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,25 @@ public static void Register(Dictionary<OpCode, IProcessor> processors)
1515

1616
public void Process(ProcessorState state, Instruction instruction)
1717
{
18-
var expression = state.Stack.Pop();
19-
state.Stack.Push(Expression.Convert(expression, (Type)instruction.Operand));
18+
var address = state.Stack.Pop();
19+
Expression expression = address;
20+
var targetType = (Type)instruction.Operand;
21+
22+
// Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X)
23+
// This happens when operations like NOT return int, IL converts to byte, then casts to enum
24+
if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
25+
{
26+
var operand = unary.Operand;
27+
// Skip intermediate conversions from int to byte/short
28+
if (operand.Type == typeof(int) &&
29+
(unary.Type == typeof(byte) || unary.Type == typeof(sbyte) ||
30+
unary.Type == typeof(short) || unary.Type == typeof(ushort)))
31+
{
32+
expression = operand;
33+
}
34+
}
35+
36+
state.Stack.Push(Expression.Convert(expression, targetType));
2037
}
2138
}
2239
}

src/DelegateDecompiler/Processors/UnaryExpressionProcessor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,16 @@ public void Process(ProcessorState state, Instruction instruction)
2121

2222
static UnaryExpression MakeUnaryExpression(Expression operand, ExpressionType expressionType)
2323
{
24-
operand = Processor.ConvertEnumExpressionToUnderlyingType(operand);
24+
// For bitwise NOT on enums, convert to int (for byte/short) or underlying type (for long)
25+
// to match the behavior of binary operations
26+
if (expressionType == ExpressionType.Not && operand.Type.IsEnum)
27+
{
28+
operand = Processor.ConvertEnumExpressionToInt(operand);
29+
}
30+
else
31+
{
32+
operand = Processor.ConvertEnumExpressionToUnderlyingType(operand);
33+
}
2534

2635
return Expression.MakeUnary(expressionType, operand, operand.Type);
2736
}

0 commit comments

Comments
 (0)