/// <summary> /// Create the body of the values() method. /// </summary> private AstBlock CreateValuesBody() { var fields = XType.Fields.Where(x => x.IsStatic && !(x is XSyntheticFieldDefinition)).ToList(); // Allocate array var arrVar = new AstGeneratedVariable("array", "array") { Type = new XArrayType(XType) }; var createArr = new AstExpression(AstNode.NoSource, AstCode.Stloc, arrVar, new AstExpression(AstNode.NoSource, AstCode.Newarr, XType, new AstExpression(AstNode.NoSource, AstCode.Ldc_I4, fields.Count))); var ast = AstBlock.CreateOptimizedForTarget(createArr); // Initialize array var index = 0; foreach (var field in fields) { ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Stelem_Any, null, new AstExpression(AstNode.NoSource, AstCode.Ldloc, arrVar), new AstExpression(AstNode.NoSource, AstCode.Ldc_I4, index), new AstExpression(AstNode.NoSource, AstCode.Ldsfld, field))); index++; } // Return array ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Ret, null, new AstExpression(AstNode.NoSource, AstCode.Ldloc, arrVar))); return(ast); }
private static void ConvertAsNativeIFormattable(AstExpression node, XTypeSystem typeSystem) { var method = (XMethodReference)node.Operand; var type = method.ReturnType; if (method.Name == "AsNativeIFormattable" && method.DeclaringType.Name == InternalConstants.CompilerHelperName && type.FullName == "System.IFormattable") { // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$$", null) { Type = typeSystem.Object }; var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, node.Arguments[0]) { ExpectedType = typeSystem.Object }; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar).SetType(typeSystem.Object); // Convert to "(x instanceof T) ? (T)x : null" // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // null var nullExpr = new AstExpression(node.SourceLocation, AstCode.Ldnull, null).SetType(type); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, nullExpr).SetType(type); node.CopyFrom(conditional); } }
/// <summary> /// Convert a nullable .GetValueOrDefault() /// </summary> private static void ConvertGetValueOrDefault(AstExpression node, XTypeReference type, XModule module) { var defExpr = node.Arguments.Count == 1 ? new AstExpression(node.SourceLocation, AstCode.DefaultValue, type).SetType(type) : node.Arguments[1]; defExpr.ExpectedType = type; if (type.IsPrimitive) { // replace with obj != null ? unbox(obj) : defExpr AstExpression compareExpr, valueExpr; var loadExpr = node.Arguments[0]; ConvertLoad(loadExpr); loadExpr.InferredType = module.TypeSystem.Object; loadExpr.ExpectedType = module.TypeSystem.Object; if (loadExpr.Code != AstCode.Ldloc) { // TODO: how can we get the backend to remove/combine these variables again? var tmpVar = new AstGeneratedVariable("tmp$", null) { Type = module.TypeSystem.Object }; compareExpr = new AstExpression(node.SourceLocation, AstCode.Stloc, tmpVar, loadExpr).SetType(module.TypeSystem.Object); valueExpr = new AstExpression(node.SourceLocation, AstCode.Ldloc, tmpVar).SetType(module.TypeSystem.Object); } else { compareExpr = loadExpr; valueExpr = loadExpr; } valueExpr = new AstExpression(node.SourceLocation, AstCode.Unbox, type, valueExpr).SetType(type); var newNode = new AstExpression(node.SourceLocation, AstCode.Conditional, type, compareExpr, valueExpr, defExpr) .SetType(type); node.CopyFrom(newNode); } else { // replace with obj ?? defExpr var loadExpr = node.Arguments[0]; ConvertLoad(loadExpr); if (!type.IsSame(loadExpr.InferredType)) { //loadExpr.ExpectedType = type; // todo: how to get the cast inserted automatically? loadExpr = new AstExpression(loadExpr.SourceLocation, AstCode.SimpleCastclass, type, loadExpr); } var nullCoalescing = new AstExpression(node.SourceLocation, AstCode.NullCoalescing, null, loadExpr, defExpr); nullCoalescing.InferredType = type; node.CopyFrom(nullCoalescing); } }
/// <summary> /// Convert the given set of bytecodes into a list of Ast nodes. /// </summary> private static IEnumerable <AstNode> ConvertRangeToAst(IEnumerable <ByteCode> range) { var ast = new List <AstNode>(); // Convert stack-based java bytecode code to Ast tree foreach (var byteCode in range) { var ilRange = new InstructionRange(byteCode.Offset, byteCode.EndOffset); if (byteCode.StackBefore == null) { // Unreachable code continue; } var expr = new AstExpression(byteCode.SourceLocation, byteCode.Code, byteCode.Operand); expr.ILRanges.Add(ilRange); // Label for this instruction if (byteCode.Label(false) != null) { ast.Add(byteCode.Label(false)); } // Reference arguments using temporary variables var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var slot = byteCode.StackBefore[i]; expr.Arguments.Add(new AstExpression(byteCode.SourceLocation, AstCode.Ldloc, slot.LoadFrom)); } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new AstExpression(byteCode.SourceLocation, AstCode.Stloc, byteCode.StoreTo[0], expr)); } else { var tmpVar = new AstGeneratedVariable("expr_" + byteCode.Offset.ToString("X2"), byteCode.StoreTo.Select(x => x.OriginalName).FirstOrDefault()); ast.Add(new AstExpression(byteCode.SourceLocation, AstCode.Stloc, tmpVar, expr)); foreach (var storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { ast.Add(new AstExpression(byteCode.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(byteCode.SourceLocation, AstCode.Ldloc, tmpVar))); } } } return(ast); }
/// <summary> /// Runs a very simple form of copy propagation. /// Copy propagation is used in two cases: /// 1) assignments from arguments to local variables /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg), /// then we can replace the variable with the argument. /// 2) assignments of address-loading instructions to local variables /// </summary> public void CopyPropagation() { foreach (AstBlock block in method.GetSelfAndChildrenRecursive <AstBlock>()) { for (int i = 0; i < block.Body.Count; i++) { AstVariable v; AstExpression copiedExpr; if (block.Body[i].Match(AstCode.Stloc, out v, out copiedExpr) && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 && CanPerformCopyPropagation(copiedExpr, v)) { // un-inline the arguments of the ldArg instruction var uninlinedArgs = new AstVariable[copiedExpr.Arguments.Count]; for (int j = 0; j < uninlinedArgs.Length; j++) { uninlinedArgs[j] = new AstGeneratedVariable(v.Name + "_cp_" + j, v.OriginalName); block.Body.Insert(i++, new AstExpression(copiedExpr.SourceLocation, AstCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j])); } // perform copy propagation: foreach (var expr in method.GetSelfAndChildrenRecursive <AstExpression>()) { if (expr.Code == AstCode.Ldloc && expr.Operand == v) { expr.Code = copiedExpr.Code; expr.Operand = copiedExpr.Operand; for (int j = 0; j < uninlinedArgs.Length; j++) { expr.Arguments.Add(new AstExpression(copiedExpr.SourceLocation, AstCode.Ldloc, uninlinedArgs[j])); } } } block.Body.RemoveAt(i); if (uninlinedArgs.Length > 0) { // if we un-inlined stuff; we need to update the usage counters AnalyzeMethod(); } InlineInto(block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i] i -= uninlinedArgs.Length + 1; } } } }
private static void ConvertUnboxStruct(AstExpression node, XTypeReference resultType, XTypeSystem typeSystem) { // Structs must never be null. We have to handle structs here, since a newobj // might need generic arguments. These would be difficult to provide at "UnboxFromGeneric", // but will be automatically filled in by the GenericInstanceConverter // convert to (temp$ = (T)x) != null ? temp$ : default(T) // replace any unbox, but keep if otherwise. var clone = node.Code == AstCode.Unbox ? new AstExpression(node.Arguments[0]) : new AstExpression(node); // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$", "") { Type = typeSystem.Object }; // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, resultType, clone) .SetType(resultType); // temporary storage var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, txExpr) { ExpectedType = resultType }; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar) .SetType(resultType); // default (T) var defaultT = new AstExpression(node.SourceLocation, AstCode.DefaultValue, resultType).SetType(resultType); var constructor = StructCallConverter.GetDefaultValueCtor(resultType.Resolve()); StructCallConverter.ConvertDefaultValue(defaultT, constructor); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, resultType, storeTempVar, loadTempVar, defaultT) .SetType(resultType); node.CopyFrom(conditional); }
private static void AddJumpInstruction(List <AstNode> body, ISourceLocation currentLoc, AstGeneratedVariable setInstructionTarget, ref int idx, ref AstLabel label, int firstValidExpression, AstExpression initExpr, ref int labelCount) { if (label == null) { label = new AstLabel(AstNode.NoSource, "setInstructionTarget_" + (++labelCount)); body.Insert(firstValidExpression, label); body.Insert(firstValidExpression + 1, initExpr); idx += 2; } var branch = new AstExpression(currentLoc, AstCode.Brtrue, label, new AstExpression(currentLoc, AstCode.Ldloc, setInstructionTarget) { InferredType = setInstructionTarget.Type }); body.Insert(idx, branch); idx += 1; }
/// <summary> /// Convert node with code Cast. /// </summary> private static void ConvertCastclass(AssemblyCompiler compiler, AstExpression node, XTypeSystem typeSystem) { var type = (XTypeReference)node.Operand; if (type.IsSystemArray()) { // Call cast method var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var castToArray = arrayHelper.Methods.First(x => x.Name == "CastToArray"); var castToArrayExpr = new AstExpression(node.SourceLocation, AstCode.Call, castToArray, node.Arguments).SetType(type); node.CopyFrom(castToArrayExpr); return; } string castMethod = null; if (type.IsSystemCollectionsIEnumerable() || type.IsSystemCollectionsIEnumerableT()) { castMethod = "CastToEnumerable"; } else if (type.IsSystemCollectionsICollection() || type.IsSystemCollectionsICollectionT()) { castMethod = "CastToCollection"; } else if (type.IsSystemCollectionsIList() || type.IsSystemCollectionsIListT()) { castMethod = "CastToList"; } else if (type.IsSystemIFormattable()) { castMethod = "CastToFormattable"; } if (castMethod != null) { // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$$", null) { Type = compiler.Module.TypeSystem.Object }; var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, node.Arguments[0]) { ExpectedType = compiler.Module.TypeSystem.Object }; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar).SetType(compiler.Module.TypeSystem.Object); // Call cast method var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var castToArray = arrayHelper.Methods.First(x => x.Name == castMethod); // Call "(x instanceof T) ? (T)x : asMethod(x)" // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // CastX(x) var castXExpr = new AstExpression(node.SourceLocation, AstCode.Call, castToArray, loadTempVar).SetType(typeSystem.Object); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, castXExpr).SetType(type); node.CopyFrom(conditional); return; } // Normal castclass node.Code = AstCode.SimpleCastclass; }
/// <summary> /// Create the body of the class ctor. /// </summary> private AstBlock CreateClassCtorBody(bool isWide, XFieldDefinition enumInfoField, XFieldDefinition defaultField, XMethodReference enumInfoCtor, XTypeReference valueType, XTypeSystem typeSystem) { var internalEnumType = Compiler.GetDot42InternalType("Enum"); var internalEnumInfoType = Compiler.GetDot42InternalType("EnumInfo"); var valueToFieldMap = new Dictionary <object, XFieldDefinition>(); var ldc = isWide ? AstCode.Ldc_I8 : AstCode.Ldc_I4; var nameVar = new AstGeneratedVariable("enumName", null) { Type = typeSystem.String }; var enumInfoVar = new AstGeneratedVariable("enumInfo", null) { Type = internalEnumInfoType }; var valVar = new AstGeneratedVariable("val", null) { Type = enumInfoField.FieldType }; var ast = AstBlock.CreateOptimizedForTarget( // Instantiate enum info field new AstExpression(AstNode.NoSource, AstCode.Stsfld, enumInfoField, new AstExpression(AstNode.NoSource, AstCode.Stloc, enumInfoVar, new AstExpression(AstNode.NoSource, AstCode.Newobj, enumInfoCtor)))); // Instantiate values for each field var ordinal = 0; foreach (var field in XType.Fields.Where(x => x.IsStatic && !(x is XSyntheticFieldDefinition))) { // Find dex field object value; if (!field.TryGetEnumValue(out value)) { throw new CompilerException(string.Format("Cannot get enum value from field {0}", field.FullName)); } value = isWide ? (object)XConvert.ToLong(value) : (object)XConvert.ToInt(value); XFieldDefinition existingField; AstExpression valueExpr; if (valueToFieldMap.TryGetValue(value, out existingField)) { // Re-use instance of existing field valueExpr = new AstExpression(AstNode.NoSource, AstCode.Ldsfld, existingField); } else { // Record valueToFieldMap[value] = field; // Call ctor valueExpr = new AstExpression(AstNode.NoSource, AstCode.Newobj, ctor, new AstExpression(AstNode.NoSource, AstCode.Stloc, nameVar, new AstExpression(AstNode.NoSource, AstCode.Ldstr, field.Name)), new AstExpression(AstNode.NoSource, AstCode.Ldc_I4, ordinal), new AstExpression(AstNode.NoSource, AstCode.Stloc, valVar, new AstExpression(AstNode.NoSource, ldc, value))); } // Initialize static field var storeExpression = new AstExpression(AstNode.NoSource, AstCode.Stsfld, field, valueExpr); // Add to info var addMethod = new XMethodReference.Simple("Add", true, typeSystem.Void, internalEnumInfoType, XParameter.Create("value", valueType), XParameter.Create("name", typeSystem.String), XParameter.Create("instance", internalEnumType)); ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Call, addMethod, new AstExpression(AstNode.NoSource, AstCode.Ldloc, enumInfoVar), new AstExpression(AstNode.NoSource, AstCode.Ldloc, valVar), new AstExpression(AstNode.NoSource, AstCode.Ldloc, nameVar), storeExpression)); // Increment ordinal ordinal++; } // Initialize default field var getValueMethod = new XMethodReference.Simple("GetValue", true, internalEnumType, internalEnumInfoType, XParameter.Create("value", valueType)); ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Stsfld, defaultField, new AstExpression(AstNode.NoSource, AstCode.SimpleCastclass, XType, new AstExpression(AstNode.NoSource, AstCode.Call, getValueMethod, new AstExpression(AstNode.NoSource, AstCode.Ldsfld, enumInfoField), new AstExpression(AstNode.NoSource, ldc, 0))))); // Return ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Ret, null)); return(ast); }
List<AstNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs) { var ast = new List<AstNode>(); while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.TryStart.Offset); var tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).OrderBy(eh => eh.TryStart.Offset).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) tryStartIdx++; ast.AddRange(ConvertToAst(CollectionExtensions.CutRange(body, 0, tryStartIdx))); } // Cut the try block { var nestedEHs = new HashSet<ExceptionHandler>( ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) tryEndIdx++; var converted = ConvertToAst(CollectionExtensions.CutRange(body, 0, tryEndIdx), nestedEHs); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var eh in handlers) { var handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; var startIdx = 0; while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) startIdx++; var endIdx = 0; while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) endIdx++; var nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); ehs.ExceptWith(nestedEHs); var handlerAst = ConvertToAst(CollectionExtensions.CutRange(body, startIdx, endIdx - startIdx), nestedEHs); if (eh.HandlerType == ExceptionHandlerType.Catch) { var catchType = eh.CatchType.IsSystemObject() ? module.TypeSystem.Exception : XBuilder.AsTypeReference(module, eh.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handlerAst.Select(x => x.SourceLocation).FirstOrDefault(), tryCatchBlock) { ExceptionType = catchType, Body = handlerAst }; // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { var first = catchBlock.Body[0] as AstExpression; if (first != null && first.Code == AstCode.Pop && first.Arguments[0].Code == AstCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks) catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); else catchBlock.ExceptionVariable = null; catchBlock.Body.RemoveAt(0); } else { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new AstBlock(handlerAst); } else if (eh.HandlerType == ExceptionHandlerType.Fault) { tryCatchBlock.FaultBlock = new AstBlock(handlerAst); } else { // TODO: ExceptionHandlerType.Filter } } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertToAst(body)); return ast; }
private List <AstNode> ConvertToAst(IEnumerable <ByteCode> body) { var ast = new List <AstNode>(); // Convert stack-based IL code to ILAst tree foreach (var byteCode in body) { var ilRange = new InstructionRange(byteCode.Offset, byteCode.EndOffset); if (byteCode.StackBefore == null) { // Unreachable code continue; } var expr = new AstExpression(byteCode.SequencePoint, byteCode.Code, byteCode.Operand); expr.ILRanges.Add(ilRange); if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) { var prefixes = new AstExpressionPrefix[byteCode.Prefixes.Length]; for (var i = 0; i < prefixes.Length; i++) { var operand = byteCode.Prefixes[i].Operand; if (operand is FieldReference) { operand = XBuilder.AsFieldReference(module, (FieldReference)operand); } else if (operand is MethodReference) { operand = XBuilder.AsMethodReference(module, (MethodReference)operand); } else if (operand is TypeReference) { operand = XBuilder.AsTypeReference(module, (TypeReference)operand); } prefixes[i] = new AstExpressionPrefix((AstCode)byteCode.Prefixes[i].OpCode.Code, operand); } expr.Prefixes = prefixes; } // Label for this instruction if (byteCode.Label != null) { ast.Add(byteCode.Label); } // Reference arguments using temporary variables var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var slot = byteCode.StackBefore[i]; expr.Arguments.Add(new AstExpression(byteCode.SequencePoint, AstCode.Ldloc, slot.LoadFrom)); } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new AstExpression(byteCode.SequencePoint, AstCode.Stloc, byteCode.StoreTo[0], expr)); } else { var tmpVar = new AstGeneratedVariable("expr_" + byteCode.Offset.ToString("X2"), byteCode.StoreTo.Select(x => x.OriginalName).FirstOrDefault()); ast.Add(new AstExpression(byteCode.SequencePoint, AstCode.Stloc, tmpVar, expr)); foreach (var storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { ast.Add(new AstExpression(byteCode.SequencePoint, AstCode.Stloc, storeTo, new AstExpression(byteCode.SequencePoint, AstCode.Ldloc, tmpVar))); } } } return(ast); }
/// <summary> /// Runs a very simple form of copy propagation. /// Copy propagation is used in two cases: /// 1) assignments from arguments to local variables /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg), /// then we can replace the variable with the argument. /// 2) assignments of address-loading instructions to local variables /// </summary> public void CopyPropagation() { foreach (AstBlock block in method.GetSelfAndChildrenRecursive<AstBlock>()) { for (int i = 0; i < block.Body.Count; i++) { AstVariable v; AstExpression copiedExpr; if (block.Body[i].Match(AstCode.Stloc, out v, out copiedExpr) && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 && CanPerformCopyPropagation(copiedExpr, v)) { // un-inline the arguments of the ldArg instruction var uninlinedArgs = new AstVariable[copiedExpr.Arguments.Count]; for (int j = 0; j < uninlinedArgs.Length; j++) { uninlinedArgs[j] = new AstGeneratedVariable(v.Name + "_cp_" + j, v.OriginalName); block.Body.Insert(i++, new AstExpression(copiedExpr.SourceLocation, AstCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j])); } // perform copy propagation: foreach (var expr in method.GetSelfAndChildrenRecursive<AstExpression>()) { if (expr.Code == AstCode.Ldloc && expr.Operand == v) { expr.Code = copiedExpr.Code; expr.Operand = copiedExpr.Operand; for (int j = 0; j < uninlinedArgs.Length; j++) { expr.Arguments.Add(new AstExpression(copiedExpr.SourceLocation, AstCode.Ldloc, uninlinedArgs[j])); } } } block.Body.RemoveAt(i); if (uninlinedArgs.Length > 0) { // if we un-inlined stuff; we need to update the usage counters AnalyzeMethod(); } InlineInto(block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i] i -= uninlinedArgs.Length + 1; } } } }
/// <summary> /// Convert a nullable .GetValueOrDefault() /// </summary> private static void ConvertGetValueOrDefault(AstExpression node,XTypeReference type, XModule module) { var defExpr = node.Arguments.Count == 1 ? new AstExpression(node.SourceLocation, AstCode.DefaultValue, type).SetType(type) : node.Arguments[1]; defExpr.ExpectedType = type; if (type.IsPrimitive) { // replace with obj != null ? unbox(obj) : defExpr AstExpression compareExpr, valueExpr; var loadExpr = node.Arguments[0]; ConvertLoad(loadExpr); loadExpr.InferredType = module.TypeSystem.Object; loadExpr.ExpectedType = module.TypeSystem.Object; if (loadExpr.Code != AstCode.Ldloc) { // TODO: how can we get the backend to remove/combine these variables again? var tmpVar = new AstGeneratedVariable("tmp$", null) { Type = module.TypeSystem.Object }; compareExpr = new AstExpression(node.SourceLocation, AstCode.Stloc, tmpVar, loadExpr).SetType(module.TypeSystem.Object); valueExpr = new AstExpression(node.SourceLocation, AstCode.Ldloc, tmpVar).SetType(module.TypeSystem.Object); } else { compareExpr = loadExpr; valueExpr = loadExpr; } valueExpr = new AstExpression(node.SourceLocation, AstCode.Unbox, type, valueExpr).SetType(type); var newNode = new AstExpression(node.SourceLocation, AstCode.Conditional, type, compareExpr, valueExpr, defExpr) .SetType(type); node.CopyFrom(newNode); } else { // replace with obj ?? defExpr var loadExpr = node.Arguments[0]; ConvertLoad(loadExpr); if(!type.IsSame(loadExpr.InferredType)) { //loadExpr.ExpectedType = type; // todo: how to get the cast inserted automatically? loadExpr = new AstExpression(loadExpr.SourceLocation, AstCode.SimpleCastclass, type, loadExpr); } var nullCoalescing = new AstExpression(node.SourceLocation, AstCode.NullCoalescing, null, loadExpr, defExpr); nullCoalescing.InferredType = type; node.CopyFrom(nullCoalescing); } }
private static void AddJumpInstruction(List<AstNode> body, ISourceLocation currentLoc, AstGeneratedVariable setInstructionTarget, ref int idx, ref AstLabel label, int firstValidExpression, AstExpression initExpr, ref int labelCount) { if (label == null) { label = new AstLabel(AstNode.NoSource, "setInstructionTarget_" + (++labelCount)); body.Insert(firstValidExpression, label); body.Insert(firstValidExpression + 1, initExpr); idx += 2; } var branch = new AstExpression(currentLoc, AstCode.Brtrue, label, new AstExpression(currentLoc, AstCode.Ldloc, setInstructionTarget) { InferredType = setInstructionTarget.Type }); body.Insert(idx, branch); idx += 1; }
/// <summary> /// Convert the given set of bytecodes to an Ast node list. /// Split exception handlers into Ast try/catch blocks. /// </summary> private List<AstNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs, List<ByteCodeBlock> blockStarts, int nestingLevel, Dictionary<int, ByteCode> offset2ByteCode) { var ast = new List<AstNode>(); // Split body in blocks while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.StartPc); var tryEnd = ehs.Where(eh => eh.StartPc == tryStart).Max(eh => eh.EndPc); var handlers = ehs.Where(eh => (eh.StartPc == tryStart) && (eh.EndPc == tryEnd)).OrderBy(eh => eh.HandlerPc).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while ((tryStartIdx < body.Count) && (body[tryStartIdx].Offset < tryStart)) { tryStartIdx++; } if (tryStartIdx > 0) { ast.AddRange(ConvertRangeToAst(body.CutRange(0, tryStartIdx))); // Make sure the block before the try block ends with an unconditional control flow AddUnconditionalBranchToNext(ast, body, 0); } } // Cut the try block { var nestedEHs = new HashSet<ExceptionHandler>( ehs.Where(eh => ((tryStart <= eh.StartPc) && (eh.EndPc < tryEnd)) || ((tryStart < eh.StartPc) && (eh.EndPc <= tryEnd)))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while ((tryEndIdx < body.Count) && (body[tryEndIdx].Offset < tryEnd)) { tryEndIdx++; } var converted = ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs, blockStarts, nestingLevel + 1, offset2ByteCode); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); // Make sure the try block ends with an unconditional control flow AddUnconditionalBranchToNext(tryCatchBlock.TryBlock.Body, body, 0); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var iterator in handlers) { var eh = iterator; var handler = offset2ByteCode[eh.HandlerPc]; // body.First(x => x.Offset == eh.HandlerPc); var catchType = eh.IsCatchAll ? typeSystem.Object : AsTypeReference(eh.CatchType, XTypeUsageFlags.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handler.SourceLocation, tryCatchBlock) { ExceptionType = catchType, Body = new List<AstNode>() }; // Create catch "body" (actually a jump to the handler) // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; catchBlock.Body.Add(new AstExpression(handler.SourceLocation, AstCode.Br, handler.Label(true))); if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { /*var first = catchBlock.Body.FirstOrDefault() as AstExpression; if (first != null && first.Code == AstCode.Pop && first.Arguments[0].Code == AstCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2")); catchBlock.Body.RemoveAt(0); } else*/ { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertRangeToAst(body)); return ast; }
/// <summary> /// Analyse the instructions in the method code and convert them to a ByteCode list. /// </summary> private List<ByteCode> StackAnalysis() { // Map from instruction to bytecode. var instrToByteCode = new Dictionary<Instruction, ByteCode>(); // Create temporary structure for the stack analysis var body = new List<ByteCode>(codeAttr.Code.Length); foreach (var inst in codeAttr.Instructions) { var first = true; foreach (var byteCode in Create(inst, module)) { if (first) { instrToByteCode[inst] = byteCode; first = false; } body.Add(byteCode); } } // Connect bytecodes to the next for (var i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } var agenda = new Stack<ByteCode>(); var localVarsCount = codeAttr.MaxLocals; // methodDef.GetParametersLocalVariableSlots(); // All bytecodes that are the start of an exception handler. var exceptionHandlerStarts = new HashSet<ByteCode>(validExceptionHandlers.Select(eh => instrToByteCode[eh.Handler])); var exceptionTryStarts = new DefaultDictionary<ByteCode, List<ExceptionHandler>>(x => new List<ExceptionHandler>()); foreach (var eh in validExceptionHandlers) { exceptionTryStarts[instrToByteCode[eh.Start]].Add(eh); } // Add known states var ldExceptionByHandlerPc = new Dictionary<int, ByteCode>(); foreach (var ex in validExceptionHandlers) { ByteCode ldexception; if (ldExceptionByHandlerPc.TryGetValue(ex.HandlerPc, out ldexception)) { // Re-use ldexception (that we've created for the same handler PC for another exception handler before) } else { // No handler at handlerPc processed before, do that now var handlerStart = instrToByteCode[ex.Handler]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount); { // Catch handlers start with the exeption on the stack ldexception = new ByteCode { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1, Offset = handlerStart.Offset, Next = handlerStart, StackBefore = new StackSlot[0], VariablesBefore = handlerStart.VariablesBefore }; handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; } ldExceptionByHandlerPc[ex.HandlerPc] = ldexception; agenda.Push(handlerStart); } // Store ldexception by exception handler ldexceptions[ex] = ldexception; } // At the start of the method the stack is empty and all local variables have unknown state body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { var byteCode = agenda.Pop(); // Calculate new stack var newStack = byteCode.CreateNewStack(); // Calculate new variable state var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((LocalVariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == AstCode.Leave) { newVariableState = VariableSlot.MakeUnknownState(localVarsCount); } // Find all successors var branchTargets = FindBranchTargets(byteCode, instrToByteCode, exceptionHandlerStarts); // Apply the state to successors foreach (var branchTarget in branchTargets) { UpdateBranchTarget(byteCode, branchTarget, (branchTargets.Count == 1), newStack, newVariableState, agenda); } // Apply state to handlers when a branch target is the start of an exception handler foreach (var branchTarget in branchTargets.Where(exceptionTryStarts.ContainsKey)) { // The branch target is the start of a try block. UpdateTryStartBranchTarget(branchTarget, exceptionTryStarts[branchTarget], instrToByteCode, newVariableState, agenda); } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I believe it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Generate temporary variables to replace stack foreach (var byteCode in body) { var argIdx = 0; var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null); byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (var pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List<AstVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possible (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (var byteCode in body) { if ((byteCode.StoreTo == null) || (byteCode.StoreTo.Count <= 1)) continue; var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => (slot.Definitions.Length == 1) && (slot.Definitions[0] == byteCode))) { // Great - we can reduce everything into single variable var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault()); byteCode.StoreTo = new List<AstVariable> { tmpVar }; foreach (var bc in body) { for (var i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels foreach (var byteCode in body) { if (byteCode.Operand is Instruction[]) { byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label(true)).ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label(true); } else if (byteCode.Operand is LookupSwitchData) { var data = (LookupSwitchData) byteCode.Operand; byteCode.Operand = data.Pairs.Select(x => new AstLabelKeyPair(instrToByteCode[x.Target].Label(true), x.Match)).ToArray(); } } // Convert parameters to ILVariables ConvertParameters(body); // Replace temporary opcodes foreach (var byteCode in body) { switch (byteCode.Code) { case AstCode.Dup_x1: case AstCode.Dup_x2: case AstCode.Dup2: case AstCode.Dup2_x1: case AstCode.Dup2_x2: case AstCode.Swap: byteCode.Code = AstCode.Dup; break; case AstCode.Pop2: byteCode.Code = AstCode.Pop; break; } } return body; }
/// <summary> /// Create the body of the values() method. /// </summary> private AstBlock CreateValuesBody() { var fields = XType.Fields.Where(x => x.IsStatic && !(x is XSyntheticFieldDefinition)).ToList(); // Allocate array var arrVar = new AstGeneratedVariable("array", "array") { Type = new XArrayType(XType) }; var createArr = new AstExpression(AstNode.NoSource, AstCode.Stloc, arrVar, new AstExpression(AstNode.NoSource, AstCode.Newarr, XType, new AstExpression(AstNode.NoSource, AstCode.Ldc_I4, fields.Count))); var ast = AstBlock.CreateOptimizedForTarget(createArr); // Initialize array var index = 0; foreach (var field in fields) { ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Stelem_Any, null, new AstExpression(AstNode.NoSource, AstCode.Ldloc, arrVar), new AstExpression(AstNode.NoSource, AstCode.Ldc_I4, index), new AstExpression(AstNode.NoSource, AstCode.Ldsfld, field))); index++; } // Return array ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Ret, null, new AstExpression(AstNode.NoSource, AstCode.Ldloc, arrVar))); return ast; }
/// <summary> /// Convert the given set of bytecodes to an Ast node list. /// Split exception handlers into Ast try/catch blocks. /// </summary> private List <AstNode> ConvertToAst(List <ByteCode> body, HashSet <ExceptionHandler> ehs, List <ByteCodeBlock> blockStarts, int nestingLevel, Dictionary <int, ByteCode> offset2ByteCode) { var ast = new List <AstNode>(); // Split body in blocks while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.StartPc); var tryEnd = ehs.Where(eh => eh.StartPc == tryStart).Max(eh => eh.EndPc); var handlers = ehs.Where(eh => (eh.StartPc == tryStart) && (eh.EndPc == tryEnd)).OrderBy(eh => eh.HandlerPc).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while ((tryStartIdx < body.Count) && (body[tryStartIdx].Offset < tryStart)) { tryStartIdx++; } if (tryStartIdx > 0) { ast.AddRange(ConvertRangeToAst(body.CutRange(0, tryStartIdx))); // Make sure the block before the try block ends with an unconditional control flow AddUnconditionalBranchToNext(ast, body, 0); } } // Cut the try block { var nestedEHs = new HashSet <ExceptionHandler>( ehs.Where(eh => ((tryStart <= eh.StartPc) && (eh.EndPc < tryEnd)) || ((tryStart < eh.StartPc) && (eh.EndPc <= tryEnd)))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while ((tryEndIdx < body.Count) && (body[tryEndIdx].Offset < tryEnd)) { tryEndIdx++; } var converted = ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs, blockStarts, nestingLevel + 1, offset2ByteCode); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); // Make sure the try block ends with an unconditional control flow AddUnconditionalBranchToNext(tryCatchBlock.TryBlock.Body, body, 0); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var iterator in handlers) { var eh = iterator; var handler = offset2ByteCode[eh.HandlerPc]; // body.First(x => x.Offset == eh.HandlerPc); var catchType = eh.IsCatchAll ? typeSystem.Object : AsTypeReference(eh.CatchType, XTypeUsageFlags.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handler.SourceLocation, tryCatchBlock) { ExceptionType = catchType, Body = new List <AstNode>() }; // Create catch "body" (actually a jump to the handler) // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; catchBlock.Body.Add(new AstExpression(handler.SourceLocation, AstCode.Br, handler.Label(true))); if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { /*var first = catchBlock.Body.FirstOrDefault() as AstExpression; * if (first != null && * first.Code == AstCode.Pop && * first.Arguments[0].Code == AstCode.Ldloc && * first.Arguments[0].Operand == ldexception.StoreTo[0]) * { * // The exception is just poped - optimize it all away; * catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2")); * catchBlock.Body.RemoveAt(0); * } * else*/ { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertRangeToAst(body)); return(ast); }
/// <summary> /// Analyse the instructions in the method code and convert them to a ByteCode list. /// </summary> private List <ByteCode> StackAnalysis() { // Map from instruction to bytecode. var instrToByteCode = new Dictionary <Instruction, ByteCode>(); // Create temporary structure for the stack analysis var body = new List <ByteCode>(codeAttr.Code.Length); foreach (var inst in codeAttr.Instructions) { var first = true; foreach (var byteCode in Create(inst, module)) { if (first) { instrToByteCode[inst] = byteCode; first = false; } body.Add(byteCode); } } // Connect bytecodes to the next for (var i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } var agenda = new Stack <ByteCode>(); var localVarsCount = codeAttr.MaxLocals; // methodDef.GetParametersLocalVariableSlots(); // All bytecodes that are the start of an exception handler. var exceptionHandlerStarts = new HashSet <ByteCode>(validExceptionHandlers.Select(eh => instrToByteCode[eh.Handler])); var exceptionTryStarts = new DefaultDictionary <ByteCode, List <ExceptionHandler> >(x => new List <ExceptionHandler>()); foreach (var eh in validExceptionHandlers) { exceptionTryStarts[instrToByteCode[eh.Start]].Add(eh); } // Add known states var ldExceptionByHandlerPc = new Dictionary <int, ByteCode>(); foreach (var ex in validExceptionHandlers) { ByteCode ldexception; if (ldExceptionByHandlerPc.TryGetValue(ex.HandlerPc, out ldexception)) { // Re-use ldexception (that we've created for the same handler PC for another exception handler before) } else { // No handler at handlerPc processed before, do that now var handlerStart = instrToByteCode[ex.Handler]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount); { // Catch handlers start with the exeption on the stack ldexception = new ByteCode { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1, Offset = handlerStart.Offset, Next = handlerStart, StackBefore = new StackSlot[0], VariablesBefore = handlerStart.VariablesBefore }; handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; } ldExceptionByHandlerPc[ex.HandlerPc] = ldexception; agenda.Push(handlerStart); } // Store ldexception by exception handler ldexceptions[ex] = ldexception; } // At the start of the method the stack is empty and all local variables have unknown state body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { var byteCode = agenda.Pop(); // Calculate new stack var newStack = byteCode.CreateNewStack(); // Calculate new variable state var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((LocalVariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == AstCode.Leave) { newVariableState = VariableSlot.MakeUnknownState(localVarsCount); } // Find all successors var branchTargets = FindBranchTargets(byteCode, instrToByteCode, exceptionHandlerStarts); // Apply the state to successors foreach (var branchTarget in branchTargets) { UpdateBranchTarget(byteCode, branchTarget, (branchTargets.Count == 1), newStack, newVariableState, agenda); } // Apply state to handlers when a branch target is the start of an exception handler foreach (var branchTarget in branchTargets.Where(exceptionTryStarts.ContainsKey)) { // The branch target is the start of a try block. UpdateTryStartBranchTarget(branchTarget, exceptionTryStarts[branchTarget], instrToByteCode, newVariableState, agenda); } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I believe it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Generate temporary variables to replace stack foreach (var byteCode in body) { var argIdx = 0; var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null); byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (var pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List <AstVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possible (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (var byteCode in body) { if ((byteCode.StoreTo == null) || (byteCode.StoreTo.Count <= 1)) { continue; } var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => (slot.Definitions.Length == 1) && (slot.Definitions[0] == byteCode))) { // Great - we can reduce everything into single variable var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault()); byteCode.StoreTo = new List <AstVariable> { tmpVar }; foreach (var bc in body) { for (var i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels foreach (var byteCode in body) { if (byteCode.Operand is Instruction[]) { byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label(true)).ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label(true); } else if (byteCode.Operand is LookupSwitchData) { var data = (LookupSwitchData)byteCode.Operand; byteCode.Operand = data.Pairs.Select(x => new AstLabelKeyPair(instrToByteCode[x.Target].Label(true), x.Match)).ToArray(); } } // Convert parameters to ILVariables ConvertParameters(body); // Replace temporary opcodes foreach (var byteCode in body) { switch (byteCode.Code) { case AstCode.Dup_x1: case AstCode.Dup_x2: case AstCode.Dup2: case AstCode.Dup2_x1: case AstCode.Dup2_x2: case AstCode.Swap: byteCode.Code = AstCode.Dup; break; case AstCode.Pop2: byteCode.Code = AstCode.Pop; break; } } return(body); }
/// <summary> /// Analyse the instructions in the method code and convert them to a ByteCode list. /// </summary> private List <ByteCode> StackAnalysis() { var instrToByteCode = new Dictionary <Instruction, ByteCode>(); // Create temporary structure for the stack analysis var body = new List <ByteCode>(methodDef.Body.Instructions.Count); List <Instruction> prefixes = null; foreach (var inst in methodDef.Body.Instructions) { if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { if (prefixes == null) { prefixes = new List <Instruction>(1); } prefixes.Add(inst); continue; } var code = (AstCode)inst.OpCode.Code; var operand = inst.Operand; AstCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); var byteCode = new ByteCode { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, Code = code, Operand = operand, PopCount = inst.GetPopDelta(methodDef), PushCount = inst.GetPushDelta(), SequencePoint = SequencePointWrapper.Wrap(inst.SequencePoint) }; if (prefixes != null) { instrToByteCode[prefixes[0]] = byteCode; byteCode.Offset = prefixes[0].Offset; byteCode.Prefixes = prefixes.ToArray(); prefixes = null; } else { instrToByteCode[inst] = byteCode; } body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } var agenda = new Stack <ByteCode>(); var varCount = methodDef.Body.Variables.Count; var exceptionHandlerStarts = new HashSet <ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart])); // Add known states if (methodDef.Body.HasExceptionHandlers) { foreach (var ex in methodDef.Body.ExceptionHandlers) { var handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { // Catch and Filter handlers start with the exeption on the stack var ldexception = new ByteCode() { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldexceptions[ex] = ldexception; handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; } agenda.Push(handlerStart); if (ex.HandlerType == ExceptionHandlerType.Filter) { var filterStart = instrToByteCode[ex.FilterStart]; var ldexception = new ByteCode { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; // TODO: ldexceptions[ex] = ldexception; filterStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(filterStart); } } } body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { var byteCode = agenda.Pop(); // Calculate new stack var newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode); // Calculate new variable state var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == AstCode.Leave) { newVariableState = VariableSlot.MakeUknownState(varCount); } // Find all successors var branchTargets = new List <ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { if (exceptionHandlerStarts.Contains(byteCode.Next)) { // Do not fall though down to exception handler // It is invalid IL as per ECMA-335 ยง12.4.2.8.1, but some obfuscators produce it } else { branchTargets.Add(byteCode.Next); } } if (byteCode.Operand is Instruction[]) { foreach (var inst in (Instruction[])byteCode.Operand) { var target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new AstLabel(target.SequencePoint, target.Name); } } } else if (byteCode.Operand is Instruction) { var target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new AstLabel(target.SequencePoint, target.Name); } } // Apply the state to successors foreach (var branchTarget in branchTargets) { if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { if (branchTargets.Count == 1) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Length != newStack.Length) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. bool modified = false; // Merge stacks - modify the target for (int i = 0; i < newStack.Length; i++) { var oldDefs = branchTarget.StackBefore[i].Definitions; var newDefs = oldDefs.Union(newStack[i].Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.StackBefore[i] = new StackSlot(newDefs, null); modified = true; } } // Merge variables - modify the target for (int i = 0; i < newVariableState.Length; i++) { var oldSlot = branchTarget.VariablesBefore[i]; var newSlot = newVariableState[i]; if (!oldSlot.UnknownDefinition) { if (newSlot.UnknownDefinition) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { ByteCode[] oldDefs = oldSlot.Definitions; ByteCode[] newDefs = CollectionExtensions.Union(oldDefs, newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } } if (modified) { agenda.Push(branchTarget); } } } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I belive it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Genertate temporary variables to replace stack foreach (var byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null); byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (ByteCode pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List <AstVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (var byteCode in body) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) { // Great - we can reduce everything into single variable var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault()); byteCode.StoreTo = new List <AstVariable>() { tmpVar }; foreach (var bc in body) { for (int i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels and references to xreferences foreach (var byteCode in body) { if (byteCode.Operand is Instruction[]) { byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label).ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } else if (byteCode.Operand is FieldReference) { byteCode.Operand = XBuilder.AsFieldReference(module, (FieldReference)byteCode.Operand); } else if (byteCode.Operand is MethodReference) { byteCode.Operand = XBuilder.AsMethodReference(module, (MethodReference)byteCode.Operand); } else if (byteCode.Operand is TypeReference) { byteCode.Operand = XBuilder.AsTypeReference(module, (TypeReference)byteCode.Operand); } } // Convert parameters to ILVariables ConvertParameters(body); return(body); }
public static void Convert(AstBlock ast, MethodSource currentMethod, AssemblyCompiler compiler) { var setInstructionTarget = new AstGeneratedVariable(DebuggerConstants.SetNextInstructionVariableName, null, true) { Type = compiler.Module.TypeSystem.Bool }; int labelCount = 0; var initExpr = new AstExpression(AstNode.NoSource, AstCode.Stloc, setInstructionTarget, new AstExpression(AstNode.NoSource, AstCode.Ldnull, null) .SetType(compiler.Module.TypeSystem.Int)); int lastBaseCtorCall = -1; // can't jump before base-class constructor call. if (currentMethod.IsCtor) { lastBaseCtorCall = FindLastCtorCall(ast); } ISourceLocation currentLoc = null; foreach (var block in ast.GetSelfAndChildrenRecursive <AstBlock>()) { if (block.EntryGoto != null) // only handle simple cases atm. { return; } var body = block.Body; AstLabel label = null; int firstValidExpression = -1; bool lastExprWasCall = false; var startIdx = lastBaseCtorCall == -1?0:lastBaseCtorCall + 1; lastBaseCtorCall = -1; for (int idx = startIdx; idx < body.Count; ++idx) { var expr = body[idx] as AstExpression; if (expr == null) { continue; } if (expr.SourceLocation == null) { continue; } if (expr.SourceLocation.Equals(currentLoc) && !lastExprWasCall) { continue; } currentLoc = expr.SourceLocation; lastExprWasCall = expr.Code.IsCall(); if (firstValidExpression == -1) { firstValidExpression = idx; continue; } AddJumpInstruction(body, currentLoc, setInstructionTarget, ref idx, ref label, firstValidExpression, initExpr, ref labelCount); } //if (!lastExprWasRet && firstValidExpression != -1) //{ // int idx = body.Count; // AddJumpInstruction(body, currentLoc, setInstructionTarget, ref idx, // ref label, firstValidExpression, initExpr, ref labelCount); //} } }
/// <summary> /// Convert the given set of bytecodes into a list of Ast nodes. /// </summary> private static IEnumerable<AstNode> ConvertRangeToAst(IEnumerable<ByteCode> range) { var ast = new List<AstNode>(); // Convert stack-based java bytecode code to Ast tree foreach (var byteCode in range) { var ilRange = new InstructionRange(byteCode.Offset, byteCode.EndOffset); if (byteCode.StackBefore == null) { // Unreachable code continue; } var expr = new AstExpression(byteCode.SourceLocation, byteCode.Code, byteCode.Operand); expr.ILRanges.Add(ilRange); // Label for this instruction if (byteCode.Label(false) != null) { ast.Add(byteCode.Label(false)); } // Reference arguments using temporary variables var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var slot = byteCode.StackBefore[i]; expr.Arguments.Add(new AstExpression(byteCode.SourceLocation, AstCode.Ldloc, slot.LoadFrom)); } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new AstExpression(byteCode.SourceLocation, AstCode.Stloc, byteCode.StoreTo[0], expr)); } else { var tmpVar = new AstGeneratedVariable("expr_" + byteCode.Offset.ToString("X2"), byteCode.StoreTo.Select(x => x.OriginalName).FirstOrDefault()); ast.Add(new AstExpression(byteCode.SourceLocation, AstCode.Stloc, tmpVar, expr)); foreach (var storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { ast.Add(new AstExpression(byteCode.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(byteCode.SourceLocation, AstCode.Ldloc, tmpVar))); } } } return ast; }
public static void Convert(AstBlock ast, MethodSource currentMethod, AssemblyCompiler compiler) { var setInstructionTarget = new AstGeneratedVariable(DebuggerConstants.SetNextInstructionVariableName, null, true) { Type = compiler.Module.TypeSystem.Bool }; int labelCount = 0; var initExpr = new AstExpression(AstNode.NoSource, AstCode.Stloc, setInstructionTarget, new AstExpression(AstNode.NoSource, AstCode.Ldnull, null) .SetType(compiler.Module.TypeSystem.Int)); int lastBaseCtorCall = -1; // can't jump before base-class constructor call. if (currentMethod.IsCtor) lastBaseCtorCall = FindLastCtorCall(ast); ISourceLocation currentLoc = null; foreach (var block in ast.GetSelfAndChildrenRecursive<AstBlock>()) { if (block.EntryGoto != null) // only handle simple cases atm. return; var body = block.Body; AstLabel label = null; int firstValidExpression = -1; bool lastExprWasCall = false; var startIdx = lastBaseCtorCall == -1?0:lastBaseCtorCall + 1; lastBaseCtorCall = -1; for (int idx = startIdx; idx < body.Count; ++idx) { var expr = body[idx] as AstExpression; if(expr == null) continue; if (expr.SourceLocation == null) continue; if (expr.SourceLocation.Equals(currentLoc) && !lastExprWasCall) continue; currentLoc = expr.SourceLocation; lastExprWasCall = expr.Code.IsCall(); if (firstValidExpression == -1) { firstValidExpression = idx; continue; } AddJumpInstruction(body, currentLoc, setInstructionTarget, ref idx, ref label, firstValidExpression, initExpr, ref labelCount); } //if (!lastExprWasRet && firstValidExpression != -1) //{ // int idx = body.Count; // AddJumpInstruction(body, currentLoc, setInstructionTarget, ref idx, // ref label, firstValidExpression, initExpr, ref labelCount); //} } }
/// <summary> /// Convert node with code InstanceOf. /// </summary> private static void ConvertInstanceOf(AssemblyCompiler compiler, AstExpression node, XTypeSystem typeSystem) { var type = (XTypeReference)node.Operand; if (type.IsSystemArray()) // "is System.Array" { // Call ArrayHelper.IsArray var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var isArray = arrayHelper.Methods.First(x => x.Name == "IsArray"); var isArrayExpr = new AstExpression(node.SourceLocation, AstCode.Call, isArray, node.Arguments).SetType(typeSystem.Bool); node.CopyFrom(isArrayExpr); return; } // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$$", null) { Type = compiler.Module.TypeSystem.Object }; var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, node.Arguments[0]) { ExpectedType = compiler.Module.TypeSystem.Object }; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar).SetType(compiler.Module.TypeSystem.Object); if (type.IsSystemCollectionsIEnumerable() || type.IsSystemCollectionsICollection() || type.IsSystemCollectionsIList()) { // Call "(is x) || IsArray(x)" var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var isArray = arrayHelper.Methods.First(x => x.Name == "IsArray" && x.Parameters.Count == 1); // "is" var isExpr = new AstExpression(node).SetArguments(storeTempVar).SetCode(AstCode.SimpleInstanceOf); // Call IsArray var isArrayExpr = new AstExpression(node.SourceLocation, AstCode.Call, isArray, loadTempVar).SetType(typeSystem.Bool); // Combined var combined = new AstExpression(node.SourceLocation, AstCode.Or, null, isExpr, isArrayExpr).SetType(typeSystem.Bool); node.CopyFrom(combined); return; } if (type.IsSystemCollectionsIEnumerableT() || type.IsSystemCollectionsICollectionT() || type.IsSystemCollectionsIListT()) { // TODO: implement InstanceOf with type check for array types. // (is that even possible here?) } if (type.IsSystemIFormattable()) { // Call "(is x) || IsFormattable(x)" var formattable = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var isFormattable = formattable.Methods.First(x => x.Name == "IsVirtualFormattable"); // "is" var isExpr = new AstExpression(node).SetArguments(storeTempVar).SetCode(AstCode.SimpleInstanceOf); // Call IsFormattable var isFormattableExpr = new AstExpression(node.SourceLocation, AstCode.Call, isFormattable, loadTempVar).SetType(typeSystem.Bool); // Combined var combined = new AstExpression(node.SourceLocation, AstCode.Or, null, isExpr, isFormattableExpr).SetType(typeSystem.Bool); node.CopyFrom(combined); return; } // Normal instanceof node.Code = AstCode.SimpleInstanceOf; }
/// <summary> /// Convert node with code Cast. /// </summary> private static void ConvertCastclass(AssemblyCompiler compiler, AstExpression node, XTypeSystem typeSystem) { var type = (XTypeReference) node.Operand; if (type.IsSystemArray()) { // Call cast method var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var castToArray = arrayHelper.Methods.First(x => x.Name == "CastToArray"); var castToArrayExpr = new AstExpression(node.SourceLocation, AstCode.Call, castToArray, node.Arguments).SetType(type); node.CopyFrom(castToArrayExpr); return; } string castMethod = null; if (type.IsSystemCollectionsIEnumerable() || type.IsSystemCollectionsIEnumerableT()) { castMethod = "CastToEnumerable"; } else if (type.IsSystemCollectionsICollection() || type.IsSystemCollectionsICollectionT()) { castMethod = "CastToCollection"; } else if (type.IsSystemCollectionsIList() || type.IsSystemCollectionsIListT()) { castMethod = "CastToList"; } else if (type.IsSystemIFormattable()) { castMethod = "CastToFormattable"; } if (castMethod != null) { // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$$", null) { Type = compiler.Module.TypeSystem.Object }; var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, node.Arguments[0]) { ExpectedType =compiler.Module.TypeSystem.Object} ; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar).SetType(compiler.Module.TypeSystem.Object); // Call cast method var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var castToArray = arrayHelper.Methods.First(x => x.Name == castMethod); // Call "(x instanceof T) ? (T)x : asMethod(x)" // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // CastX(x) var castXExpr = new AstExpression(node.SourceLocation, AstCode.Call, castToArray, loadTempVar).SetType(typeSystem.Object); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, castXExpr).SetType(type); node.CopyFrom(conditional); return; } // Normal castclass node.Code = AstCode.SimpleCastclass; }
private static void FailsafeInterlockedUsingLocking(XFieldReference field, AstExpression expr, AstExpression targetExpr, AstBlock block, int idx, AssemblyCompiler compiler, MethodSource currentMethod) { if (currentMethod.IsDotNet && !currentMethod.ILMethod.DeclaringType.HasSuppressMessageAttribute("InterlockedFallback")) { bool isStatic = field != null && field.Resolve().IsStatic; DLog.Warning(DContext.CompilerCodeGenerator, "Emulating Interlocked call using failsafe locking mechanism in {0}{1}. Consider using AtomicXXX classes instead. You can suppress this message with an [SuppressMessage(\"dot42\", \"InterlockedFallback\")] attribute on the class.", currentMethod.Method.FullName, !isStatic ? "" : " because a static field is referenced"); } // replace with: // Monitor.Enter(); // try { <original expression> } finally { Monitor.Exit(); } // // note that the lock is larger than it has to be, since it also sourrounds // the parameter evaluation. // It must sourround the storing and loading of the reference parameters though. var typeSystem = compiler.Module.TypeSystem; var monitorType = compiler.GetDot42InternalType("System.Threading", "Monitor"); var enterMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Enter"); var exitMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Exit"); AstExpression loadLockTarget = null; if (field != null) { if (field.Resolve().IsStatic) { // lock on the field's class typedef. // but always the element type, not on a generic instance (until Dot42 implements proper generic static field handling) loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, field.DeclaringType.GetElementType()) .SetType(typeSystem.Type); } else { // lock on the fields object loadLockTarget = targetExpr.Arguments[0]; } } if (loadLockTarget == null) { // something went wrong. use a global lock. DLog.Warning(DContext.CompilerCodeGenerator, "unable to infer target of Interlocked call. using global lock."); var interlockedType = compiler.GetDot42InternalType("System.Threading", "Interlocked"); loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, interlockedType) .SetType(typeSystem.Type); } var lockVar = new AstGeneratedVariable("lockTarget$", "") {Type = typeSystem.Object}; var storeLockVar = new AstExpression(expr.SourceLocation, AstCode.Stloc, lockVar, loadLockTarget); var loadLockVar = new AstExpression(expr.SourceLocation, AstCode.Ldloc, lockVar); var enterCall = new AstExpression(expr.SourceLocation, AstCode.Call, enterMethod, storeLockVar); var replacementBlock = new AstBlock(expr.SourceLocation); replacementBlock.Body.Add(enterCall); var tryCatch = new AstTryCatchBlock(expr.SourceLocation) { TryBlock = new AstBlock(expr.SourceLocation, expr), FinallyBlock = new AstBlock(expr.SourceLocation, new AstExpression(block.SourceLocation, AstCode.Call, exitMethod, loadLockVar)) }; replacementBlock.Body.Add(tryCatch); if (block.EntryGoto == expr) block.EntryGoto = enterCall; block.Body[idx] = replacementBlock; }
List <AstNode> ConvertToAst(List <ByteCode> body, HashSet <ExceptionHandler> ehs) { var ast = new List <AstNode>(); while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.TryStart.Offset); var tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).OrderBy(eh => eh.TryStart.Offset).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) { tryStartIdx++; } ast.AddRange(ConvertToAst(CollectionExtensions.CutRange(body, 0, tryStartIdx))); } // Cut the try block { var nestedEHs = new HashSet <ExceptionHandler>( ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) { tryEndIdx++; } var converted = ConvertToAst(CollectionExtensions.CutRange(body, 0, tryEndIdx), nestedEHs); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var eh in handlers) { var handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; var startIdx = 0; while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) { startIdx++; } var endIdx = 0; while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) { endIdx++; } var nestedEHs = new HashSet <ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); ehs.ExceptWith(nestedEHs); var handlerAst = ConvertToAst(CollectionExtensions.CutRange(body, startIdx, endIdx - startIdx), nestedEHs); if (eh.HandlerType == ExceptionHandlerType.Catch) { var catchType = eh.CatchType.IsSystemObject() ? module.TypeSystem.Exception : XBuilder.AsTypeReference(module, eh.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handlerAst.Select(x => x.SourceLocation).FirstOrDefault(), tryCatchBlock) { ExceptionType = catchType, Body = handlerAst }; // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { var first = catchBlock.Body[0] as AstExpression; if (first != null && first.Code == AstCode.Pop && first.Arguments[0].Code == AstCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks) { catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); } else { catchBlock.ExceptionVariable = null; } catchBlock.Body.RemoveAt(0); } else { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new AstBlock(handlerAst); } else if (eh.HandlerType == ExceptionHandlerType.Fault) { tryCatchBlock.FaultBlock = new AstBlock(handlerAst); } else { // TODO: ExceptionHandlerType.Filter } } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertToAst(body)); return(ast); }
/// <summary> /// Convert node with code IsInst. /// </summary> private static void ConvertIsInst(AssemblyCompiler compiler, AstExpression node, XTypeSystem typeSystem) { var type = (XTypeReference)node.Operand; if (type.IsSystemArray()) { // Call ArrayHelper.AsArray var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var asArray = arrayHelper.Methods.First(x => x.Name == "AsArray"); var asArrayExpr = new AstExpression(node.SourceLocation, AstCode.Call, asArray, node.Arguments).SetType(typeSystem.Bool); node.CopyFrom(asArrayExpr); return; } string asMethod = GetCollectionConvertMethodName(type); if (asMethod != null) { asMethod = "As" + asMethod; } else if (type.IsSystemIFormattable()) { asMethod = "AsFormattable"; } // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$$", null) { Type = compiler.Module.TypeSystem.Object }; var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, node.Arguments[0]) { ExpectedType = compiler.Module.TypeSystem.Object }; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar).SetType(compiler.Module.TypeSystem.Object); if (asMethod != null) { // Call "(x instanceof T) ? (T)x : asMethod(x)" var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var asArray = arrayHelper.Methods.First(x => x.Name == asMethod); // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // AsX(x) var asXExpr = new AstExpression(node.SourceLocation, AstCode.Call, asArray, loadTempVar).SetType(typeSystem.Object); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, asXExpr).SetType(type); node.CopyFrom(conditional); return; } // Normal "as": Convert to (x instanceof T) ? (T)x : null if(!type.IsPrimitive) { // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // null var nullExpr = new AstExpression(node.SourceLocation, AstCode.Ldnull, null).SetType(typeSystem.Object); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, nullExpr).SetType(type); node.CopyFrom(conditional); return; } else { // treat as "x is T" if(!node.ExpectedType.IsBoolean()) throw new NotImplementedException(); // can this happen? node.Code = AstCode.SimpleInstanceOf; } }
private static void FailsafeInterlockedUsingLocking(XFieldReference field, AstExpression expr, AstExpression targetExpr, AstBlock block, int idx, AssemblyCompiler compiler, MethodSource currentMethod) { if (currentMethod.IsDotNet && !currentMethod.ILMethod.DeclaringType.HasSuppressMessageAttribute("InterlockedFallback")) { bool isStatic = field != null && field.Resolve().IsStatic; DLog.Warning(DContext.CompilerCodeGenerator, "Emulating Interlocked call using failsafe locking mechanism in {0}{1}. Consider using AtomicXXX classes instead. You can suppress this message with an [SuppressMessage(\"dot42\", \"InterlockedFallback\")] attribute on the class.", currentMethod.Method.FullName, !isStatic ? "" : " because a static field is referenced"); } // replace with: // Monitor.Enter(); // try { <original expression> } finally { Monitor.Exit(); } // // note that the lock is larger than it has to be, since it also sourrounds // the parameter evaluation. // It must sourround the storing and loading of the reference parameters though. var typeSystem = compiler.Module.TypeSystem; var monitorType = compiler.GetDot42InternalType("System.Threading", "Monitor"); var enterMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Enter"); var exitMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Exit"); AstExpression loadLockTarget = null; if (field != null) { if (field.Resolve().IsStatic) { // lock on the field's class typedef. // but always the element type, not on a generic instance (until Dot42 implements proper generic static field handling) loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, field.DeclaringType.GetElementType()) .SetType(typeSystem.Type); } else { // lock on the fields object loadLockTarget = targetExpr.Arguments[0]; } } if (loadLockTarget == null) { // something went wrong. use a global lock. DLog.Warning(DContext.CompilerCodeGenerator, "unable to infer target of Interlocked call. using global lock."); var interlockedType = compiler.GetDot42InternalType("System.Threading", "Interlocked"); loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, interlockedType) .SetType(typeSystem.Type); } var lockVar = new AstGeneratedVariable("lockTarget$", "") { Type = typeSystem.Object }; var storeLockVar = new AstExpression(expr.SourceLocation, AstCode.Stloc, lockVar, loadLockTarget); var loadLockVar = new AstExpression(expr.SourceLocation, AstCode.Ldloc, lockVar); var enterCall = new AstExpression(expr.SourceLocation, AstCode.Call, enterMethod, storeLockVar); var replacementBlock = new AstBlock(expr.SourceLocation); replacementBlock.Body.Add(enterCall); var tryCatch = new AstTryCatchBlock(expr.SourceLocation) { TryBlock = new AstBlock(expr.SourceLocation, expr), FinallyBlock = new AstBlock(expr.SourceLocation, new AstExpression(block.SourceLocation, AstCode.Call, exitMethod, loadLockVar)) }; replacementBlock.Body.Add(tryCatch); if (block.EntryGoto == expr) { block.EntryGoto = enterCall; } block.Body[idx] = replacementBlock; }
/// <summary> /// Convert node with code IsInst. /// </summary> private static void ConvertIsInst(AssemblyCompiler compiler, AstExpression node, XTypeSystem typeSystem) { var type = (XTypeReference)node.Operand; if (type.IsSystemArray()) { // Call ArrayHelper.AsArray var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var asArray = arrayHelper.Methods.First(x => x.Name == "AsArray"); var asArrayExpr = new AstExpression(node.SourceLocation, AstCode.Call, asArray, node.Arguments).SetType(typeSystem.Bool); node.CopyFrom(asArrayExpr); return; } string asMethod = GetCollectionConvertMethodName(type); if (asMethod != null) { asMethod = "As" + asMethod; } else if (type.IsSystemIFormattable()) { asMethod = "AsFormattable"; } // make sure we don't evaluate the expression twice. var tempVar = new AstGeneratedVariable("temp$$", null) { Type = compiler.Module.TypeSystem.Object }; var storeTempVar = new AstExpression(node.SourceLocation, AstCode.Stloc, tempVar, node.Arguments[0]) { ExpectedType = compiler.Module.TypeSystem.Object }; var loadTempVar = new AstExpression(node.SourceLocation, AstCode.Ldloc, tempVar).SetType(compiler.Module.TypeSystem.Object); if (asMethod != null) { // Call "(x instanceof T) ? (T)x : asMethod(x)" var arrayHelper = compiler.GetDot42InternalType(InternalConstants.CompilerHelperName).Resolve(); var asArray = arrayHelper.Methods.First(x => x.Name == asMethod); // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // AsX(x) var asXExpr = new AstExpression(node.SourceLocation, AstCode.Call, asArray, loadTempVar).SetType(typeSystem.Object); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, asXExpr).SetType(type); node.CopyFrom(conditional); return; } // Normal "as": Convert to (x instanceof T) ? (T)x : null if (!type.IsPrimitive) { // "instanceof x" var instanceofExpr = new AstExpression(node.SourceLocation, AstCode.SimpleInstanceOf, type, storeTempVar).SetType(typeSystem.Bool); // T(x) var txExpr = new AstExpression(node.SourceLocation, AstCode.SimpleCastclass, type, loadTempVar).SetType(type); // null var nullExpr = new AstExpression(node.SourceLocation, AstCode.Ldnull, null).SetType(typeSystem.Object); // Combine var conditional = new AstExpression(node.SourceLocation, AstCode.Conditional, type, instanceofExpr, txExpr, nullExpr).SetType(type); node.CopyFrom(conditional); return; } else { // treat as "x is T" if (!node.ExpectedType.IsBoolean()) { throw new NotImplementedException(); // can this happen? } node.Code = AstCode.SimpleInstanceOf; } }
/// <summary> /// Analyse the instructions in the method code and convert them to a ByteCode list. /// </summary> private List<ByteCode> StackAnalysis() { var instrToByteCode = new Dictionary<Instruction, ByteCode>(); // Create temporary structure for the stack analysis var body = new List<ByteCode>(methodDef.Body.Instructions.Count); List<Instruction> prefixes = null; foreach (var inst in methodDef.Body.Instructions) { if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { if (prefixes == null) prefixes = new List<Instruction>(1); prefixes.Add(inst); continue; } var code = (AstCode)inst.OpCode.Code; var operand = inst.Operand; AstCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); var byteCode = new ByteCode { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, Code = code, Operand = operand, PopCount = inst.GetPopDelta(methodDef), PushCount = inst.GetPushDelta(), SequencePoint = SequencePointWrapper.Wrap(inst.SequencePoint) }; if (prefixes != null) { instrToByteCode[prefixes[0]] = byteCode; byteCode.Offset = prefixes[0].Offset; byteCode.Prefixes = prefixes.ToArray(); prefixes = null; } else { instrToByteCode[inst] = byteCode; } body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } var agenda = new Stack<ByteCode>(); var varCount = methodDef.Body.Variables.Count; var exceptionHandlerStarts = new HashSet<ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart])); // Add known states if (methodDef.Body.HasExceptionHandlers) { foreach (var ex in methodDef.Body.ExceptionHandlers) { var handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { // Catch and Filter handlers start with the exeption on the stack var ldexception = new ByteCode() { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldexceptions[ex] = ldexception; handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; } agenda.Push(handlerStart); if (ex.HandlerType == ExceptionHandlerType.Filter) { var filterStart = instrToByteCode[ex.FilterStart]; var ldexception = new ByteCode { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; // TODO: ldexceptions[ex] = ldexception; filterStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(filterStart); } } } body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { var byteCode = agenda.Pop(); // Calculate new stack var newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode); // Calculate new variable state var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == AstCode.Leave) { newVariableState = VariableSlot.MakeUknownState(varCount); } // Find all successors var branchTargets = new List<ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { if (exceptionHandlerStarts.Contains(byteCode.Next)) { // Do not fall though down to exception handler // It is invalid IL as per ECMA-335 ยง12.4.2.8.1, but some obfuscators produce it } else { branchTargets.Add(byteCode.Next); } } if (byteCode.Operand is Instruction[]) { foreach (var inst in (Instruction[])byteCode.Operand) { var target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new AstLabel(target.SequencePoint, target.Name); } } } else if (byteCode.Operand is Instruction) { var target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new AstLabel(target.SequencePoint, target.Name); } } // Apply the state to successors foreach (var branchTarget in branchTargets) { if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { if (branchTargets.Count == 1) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Length != newStack.Length) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. bool modified = false; // Merge stacks - modify the target for (int i = 0; i < newStack.Length; i++) { var oldDefs = branchTarget.StackBefore[i].Definitions; var newDefs = oldDefs.Union(newStack[i].Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.StackBefore[i] = new StackSlot(newDefs, null); modified = true; } } // Merge variables - modify the target for (int i = 0; i < newVariableState.Length; i++) { var oldSlot = branchTarget.VariablesBefore[i]; var newSlot = newVariableState[i]; if (!oldSlot.UnknownDefinition) { if (newSlot.UnknownDefinition) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { ByteCode[] oldDefs = oldSlot.Definitions; ByteCode[] newDefs = CollectionExtensions.Union(oldDefs, newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } } if (modified) { agenda.Push(branchTarget); } } } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I belive it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Genertate temporary variables to replace stack foreach (var byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null); byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (ByteCode pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List<AstVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (var byteCode in body) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) { // Great - we can reduce everything into single variable var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault()); byteCode.StoreTo = new List<AstVariable>() { tmpVar }; foreach (var bc in body) { for (int i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels and references to xreferences foreach (var byteCode in body) { if (byteCode.Operand is Instruction[]) { byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label).ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } else if (byteCode.Operand is FieldReference) { byteCode.Operand = XBuilder.AsFieldReference(module, (FieldReference)byteCode.Operand); } else if (byteCode.Operand is MethodReference) { byteCode.Operand = XBuilder.AsMethodReference(module, (MethodReference)byteCode.Operand); } else if (byteCode.Operand is TypeReference) { byteCode.Operand = XBuilder.AsTypeReference(module, (TypeReference)byteCode.Operand); } } // Convert parameters to ILVariables ConvertParameters(body); return body; }
private List<AstNode> ConvertToAst(IEnumerable<ByteCode> body) { var ast = new List<AstNode>(); // Convert stack-based IL code to ILAst tree foreach (var byteCode in body) { var ilRange = new InstructionRange(byteCode.Offset, byteCode.EndOffset); if (byteCode.StackBefore == null) { // Unreachable code continue; } var expr = new AstExpression(byteCode.SequencePoint, byteCode.Code, byteCode.Operand); expr.ILRanges.Add(ilRange); if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) { var prefixes = new AstExpressionPrefix[byteCode.Prefixes.Length]; for (var i = 0; i < prefixes.Length; i++) { var operand = byteCode.Prefixes[i].Operand; if (operand is FieldReference) { operand = XBuilder.AsFieldReference(module, (FieldReference) operand); } else if (operand is MethodReference) { operand = XBuilder.AsMethodReference(module, (MethodReference)operand); } else if (operand is TypeReference) { operand = XBuilder.AsTypeReference(module, (TypeReference) operand); } prefixes[i] = new AstExpressionPrefix((AstCode)byteCode.Prefixes[i].OpCode.Code, operand); } expr.Prefixes = prefixes; } // Label for this instruction if (byteCode.Label != null) { ast.Add(byteCode.Label); } // Reference arguments using temporary variables var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var slot = byteCode.StackBefore[i]; expr.Arguments.Add(new AstExpression(byteCode.SequencePoint, AstCode.Ldloc, slot.LoadFrom)); } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new AstExpression(byteCode.SequencePoint, AstCode.Stloc, byteCode.StoreTo[0], expr)); } else { var tmpVar = new AstGeneratedVariable("expr_" + byteCode.Offset.ToString("X2"), byteCode.StoreTo.Select(x => x.OriginalName).FirstOrDefault()); ast.Add(new AstExpression(byteCode.SequencePoint, AstCode.Stloc, tmpVar, expr)); foreach (var storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { ast.Add(new AstExpression(byteCode.SequencePoint, AstCode.Stloc, storeTo, new AstExpression(byteCode.SequencePoint, AstCode.Ldloc, tmpVar))); } } } return ast; }
/// <summary> /// Create the body of the class ctor. /// </summary> private AstBlock CreateClassCtorBody(bool isWide, XFieldDefinition enumInfoField, XFieldDefinition defaultField, XMethodReference enumInfoCtor, XTypeReference valueType, XTypeSystem typeSystem) { var internalEnumType = Compiler.GetDot42InternalType("Enum"); var internalEnumInfoType = Compiler.GetDot42InternalType("EnumInfo"); var valueToFieldMap = new Dictionary<object, XFieldDefinition>(); var ldc = isWide ? AstCode.Ldc_I8 : AstCode.Ldc_I4; var nameVar = new AstGeneratedVariable("enumName", null) {Type = typeSystem.String}; var enumInfoVar = new AstGeneratedVariable("enumInfo", null) {Type = internalEnumInfoType}; var valVar = new AstGeneratedVariable("val", null) { Type = enumInfoField.FieldType }; var ast = AstBlock.CreateOptimizedForTarget( // Instantiate enum info field new AstExpression(AstNode.NoSource, AstCode.Stsfld, enumInfoField, new AstExpression(AstNode.NoSource, AstCode.Stloc, enumInfoVar, new AstExpression(AstNode.NoSource, AstCode.Newobj, enumInfoCtor)))); // Instantiate values for each field var ordinal = 0; foreach (var field in XType.Fields.Where(x => x.IsStatic && !(x is XSyntheticFieldDefinition))) { // Find dex field object value; if (!field.TryGetEnumValue(out value)) throw new CompilerException(string.Format("Cannot get enum value from field {0}", field.FullName)); value = isWide ? (object)XConvert.ToLong(value) : (object)XConvert.ToInt(value); XFieldDefinition existingField; AstExpression valueExpr; if (valueToFieldMap.TryGetValue(value, out existingField)) { // Re-use instance of existing field valueExpr = new AstExpression(AstNode.NoSource, AstCode.Ldsfld, existingField); } else { // Record valueToFieldMap[value] = field; // Call ctor valueExpr = new AstExpression(AstNode.NoSource, AstCode.Newobj, ctor, new AstExpression(AstNode.NoSource, AstCode.Stloc, nameVar, new AstExpression(AstNode.NoSource, AstCode.Ldstr, field.Name)), new AstExpression(AstNode.NoSource, AstCode.Ldc_I4, ordinal), new AstExpression(AstNode.NoSource, AstCode.Stloc, valVar, new AstExpression(AstNode.NoSource, ldc, value))); } // Initialize static field var storeExpression = new AstExpression(AstNode.NoSource, AstCode.Stsfld, field, valueExpr); // Add to info var addMethod = new XMethodReference.Simple("Add", true, typeSystem.Void, internalEnumInfoType, XParameter.Create("value", valueType), XParameter.Create("name", typeSystem.String), XParameter.Create("instance", internalEnumType)); ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Call, addMethod, new AstExpression(AstNode.NoSource, AstCode.Ldloc, enumInfoVar), new AstExpression(AstNode.NoSource, AstCode.Ldloc, valVar), new AstExpression(AstNode.NoSource, AstCode.Ldloc, nameVar), storeExpression)); // Increment ordinal ordinal++; } // Initialize default field var getValueMethod = new XMethodReference.Simple("GetValue", true, internalEnumType, internalEnumInfoType, XParameter.Create("value", valueType)); ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Stsfld, defaultField, new AstExpression(AstNode.NoSource, AstCode.SimpleCastclass, XType, new AstExpression(AstNode.NoSource, AstCode.Call, getValueMethod, new AstExpression(AstNode.NoSource, AstCode.Ldsfld, enumInfoField), new AstExpression(AstNode.NoSource, ldc, 0))))); // Return ast.Body.Add(new AstExpression(AstNode.NoSource, AstCode.Ret, null)); return ast; }