/// <summary> /// Create the Ctor /// </summary> private static void CreateDefaultCtor(ReachableContext reachableContext, TypeDefinition type) { var typeSystem = type.Module.TypeSystem; var ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, typeSystem.Void); ctor.DeclaringType = type; var body = new MethodBody(ctor); body.InitLocals = true; ctor.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add ctor type.Methods.Add(ctor); ctor.SetReachable(reachableContext); }
/// <summary> /// Ensure there is a class ctor. /// </summary> internal static ILSequence CreateInitializationCode(IEnumerable <FieldDefinition> structFields, bool isStatic) { // Prepare code var seq = new ILSequence(); foreach (var field in structFields) { // Create default instance var fieldType = field.FieldType.Resolve(); if (fieldType == null) { throw new CompilerException(string.Format("Cannot resolve field type of field {0}", field.MemberFullName())); } var ctor = fieldType.Methods.FirstOrDefault(x => x.IsConstructor && !x.IsStatic && (x.Parameters.Count == 0)); if (ctor == null) { throw new CompilerException(string.Format("Cannot find default ctor for type {0}", field.DeclaringType.FullName)); } var ctorRef = field.FieldType.CreateReference(ctor); if (isStatic) { seq.Emit(OpCodes.Newobj, ctorRef); seq.Emit(OpCodes.Stsfld, field); } else { seq.Emit(OpCodes.Ldarg_0); // this seq.Emit(OpCodes.Newobj, ctorRef); seq.Emit(OpCodes.Stfld, field); } } return(seq); }
/// <summary> /// Implement IAsyncSetThis. /// </summary> private static void ImplementISetThis(TypeDefinition type, ReachableContext reachableContext) { var thisField = type.Fields.FirstOrDefault(x => x.Name.StartsWith("<>") && x.Name.EndsWith("__this")); if (thisField == null) { return; } // Add interface var intfType = type.Module.Import(new TypeReference(InternalConstants.Dot42InternalNamespace, "IAsyncSetThis", type.Module, type.Module.Assembly.Name)); type.Interfaces.Add(new InterfaceImpl(intfType)); // Add "SetThis(object)" method var method = new MethodDefinition("SetThis", MethodAttributes.Public, type.Module.TypeSystem.Void); method.SetReachable(reachableContext); type.Methods.Add(method); var valueParam = new ParameterDefinition(type.Module.TypeSystem.Object); method.Parameters.Add(valueParam); method.Body = new MethodBody(method) { InitLocals = true }; var seq = new ILSequence(); seq.Emit(OpCodes.Ldarg_0); // this seq.Emit(OpCodes.Ldarg, valueParam); seq.Emit(OpCodes.Castclass, thisField.FieldType); seq.Emit(OpCodes.Stfld, thisField); seq.Emit(OpCodes.Ret); seq.AppendTo(method.Body); }
/// <summary> /// Implement IAsyncSetThis. /// </summary> private static void ImplementISetThis(TypeDefinition type, ReachableContext reachableContext) { var thisField = type.Fields.FirstOrDefault(x => x.Name.StartsWith("<>") && x.Name.EndsWith("__this")); if (thisField == null) return; // Add interface var intfType = type.Module.Import(new TypeReference(InternalConstants.Dot42InternalNamespace, "IAsyncSetThis", type.Module, type.Module.Assembly.Name)); type.Interfaces.Add(new InterfaceImpl(intfType)); // Add "SetThis(object)" method var method = new MethodDefinition("SetThis", MethodAttributes.Public, type.Module.TypeSystem.Void); method.SetReachable(reachableContext); type.Methods.Add(method); var valueParam = new ParameterDefinition(type.Module.TypeSystem.Object); method.Parameters.Add(valueParam); method.Body = new MethodBody(method) { InitLocals = true }; var seq = new ILSequence(); seq.Emit(OpCodes.Ldarg_0); // this seq.Emit(OpCodes.Ldarg, valueParam); seq.Emit(OpCodes.Castclass, thisField.FieldType); seq.Emit(OpCodes.Stfld, thisField); seq.Emit(OpCodes.Ret); seq.AppendTo(method.Body); }
/// <summary> /// Ensure there is a class ctor. /// </summary> internal static ILSequence CreateInitializationCode(IEnumerable<FieldDefinition> structFields, bool isStatic) { // Prepare code var seq = new ILSequence(); foreach (var field in structFields) { // Create default instance var fieldType = field.FieldType.Resolve(); if (fieldType == null) { throw new CompilerException(string.Format("Cannot resolve field type of field {0}", field.MemberFullName())); } var ctor = fieldType.Methods.FirstOrDefault(x => x.IsConstructor && !x.IsStatic && (x.Parameters.Count == 0)); if (ctor == null) { throw new CompilerException(string.Format("Cannot find default ctor for type {0}", field.DeclaringType.FullName)); } var ctorRef = field.FieldType.CreateReference(ctor); if (isStatic) { seq.Emit(OpCodes.Newobj, ctorRef); seq.Emit(OpCodes.Stsfld, field); } else { seq.Emit(OpCodes.Ldarg_0); // this seq.Emit(OpCodes.Newobj, ctorRef); seq.Emit(OpCodes.Stfld, field); } } return seq; }
/// <summary> /// Create a CopyFrom method. /// </summary> private static MethodDefinition CreateCopyFromMethod(ReachableContext reachableContext, TypeDefinition type) { var typeSystem = type.Module.TypeSystem; var method = new MethodDefinition(NameConstants.Struct.CopyFromMethodName, MethodAttributes.Public, type); var sourceParam = new ParameterDefinition(type); method.Parameters.Add(sourceParam); method.DeclaringType = type; var body = new MethodBody(method); body.InitLocals = true; method.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); // Call base CopyFrom var baseType = (type.BaseType != null) ? type.BaseType.GetElementType().Resolve() : null; if ((baseType != null) && baseType.IsValueType && (baseType.FullName != "System.ValueType")) { var baseMethod = new MethodReference(NameConstants.Struct.CopyFromMethodName, baseType, baseType) { HasThis = true }; baseMethod.Parameters.Add(new ParameterDefinition(baseType)); seq.Emit(OpCodes.Ldarg, sourceParam); seq.Emit(OpCodes.Call, baseMethod); } // Copy all fields foreach (var field in type.Fields.Where(x => !x.IsStatic)) { // Not need to bother with cloning struct-type fields here, // as this will be done automatically by one of the Converters. // Prepare for stfld seq.Emit(OpCodes.Ldarg, body.ThisParameter); // Load from source seq.Emit(OpCodes.Ldarg, sourceParam); seq.Emit(OpCodes.Ldfld, field); // Save in this seq.Emit(OpCodes.Stfld, field); } // Return this seq.Emit(OpCodes.Ldarg, body.ThisParameter); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add method type.Methods.Add(method); method.SetReachable(reachableContext); return method; }
/// <summary> /// Add a method that has the same signature as basemethod and calls method. /// </summary> private MethodDefinition AddBridge(MethodDefinition method, MethodDefinition baseMethod) { var bridge = new MethodDefinition(baseMethod.Name, baseMethod.Attributes, baseMethod.ReturnType) { HasThis = true }; var cloner = new TypeCloner(true, method.Module.TypeSystem); bridge.ReturnType = cloner.Get(baseMethod.ReturnType, bridge); bridge.IsAbstract = false; // Clone parameters foreach (var p in baseMethod.Parameters) { bridge.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, cloner.Get(p.ParameterType, bridge))); } // Create body var body = new MethodBody(bridge); bridge.Body = body; // Create code var seq = new ILSequence(); // this seq.Emit(OpCodes.Ldarg_0); // parameters for (var i = 0; i < bridge.Parameters.Count; i++) { var p = bridge.Parameters[i]; seq.Emit(OpCodes.Ldarg, p); if (baseMethod.Parameters[i].ParameterType.ContainsGenericParameter) { seq.Emit(OpCodes.Unbox, method.Parameters[i].ParameterType); } } // Call actual method seq.Emit(OpCodes.Call, method); // Return seq.Emit(OpCodes.Ret); // Add code to body seq.AppendTo(body); body.ComputeOffsets(); // add overrides, so that we can find the method later bridge.Overrides.Add(baseMethod); // Add to class method.DeclaringType.Methods.Add(bridge); bridge.SetReachable(reachableContext); return(bridge); }
/// <summary> /// Ensure there is a class ctor. /// </summary> private static MethodDefinition EnsureClassCtor(ReachableContext reachableContext, TypeDefinition type) { var ctor = type.GetClassCtor(); if (ctor != null) { return(ctor); // Already exists } // Create class ctor var typeSystem = type.Module.TypeSystem; ctor = new MethodDefinition(".cctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static | MethodAttributes.SpecialName, typeSystem.Void); ctor.DeclaringType = type; var body = new MethodBody(ctor); body.InitLocals = true; ctor.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add ctor type.Methods.Add(ctor); ctor.SetReachable(reachableContext); return(ctor); }
/// <summary> /// Create a Clone method. /// </summary> private static void CreateCloneMethod(ReachableContext reachableContext, TypeDefinition type, MethodDefinition copyFromMethod) { var method = new MethodDefinition(NameConstants.Struct.CloneMethodName, MethodAttributes.Public, type); method.DeclaringType = type; var body = new MethodBody(method); body.InitLocals = true; method.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); // Create new instance var defaultCtor = CreateDefaultCtorRef(type); seq.Emit(OpCodes.Newobj, defaultCtor); // Call clone.CopyFrom seq.Emit(OpCodes.Dup); seq.Emit(OpCodes.Ldarg, body.ThisParameter); seq.Emit(OpCodes.Call, copyFromMethod); // Return clone seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add method type.Methods.Add(method); method.SetReachable(reachableContext); }
/// <summary> /// Inline the call to the given ctor /// </summary> private static void InlineNewObjCall(Instruction instruction, MethodBody body, MethodDefinition ctor) { // Prepare var prefixSeq = new ILSequence(); ctor.Body.SimplifyMacros(); // Create "this" variable var thisVariable = new VariableDefinition(ctor.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; // Store argument in variables var paramVariables = new List<VariableDefinition>(); foreach (var parameter in ctor.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Clone variables first var source = ctor.Body; var variables = new List<VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition) instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Mono.Cecil.Cil.Code.Ldarg: { var index = ctor.Parameters.IndexOf((ParameterDefinition) instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Mono.Cecil.Cil.Code.Ldarga: { var index = ctor.Parameters.IndexOf((ParameterDefinition) instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Mono.Cecil.Cil.Code.Starg: { var index = ctor.Parameters.IndexOf((ParameterDefinition) instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[]) oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction) oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Find call to "this" ctor var callToCtors = seq.Where(x => IsCallToThisCtor(x, ctor)).ToList(); if (callToCtors.Count == 0) throw new CompilerException(string.Format("No call to another this ctor found in {0}", ctor)); if (callToCtors.Count > 1) throw new CompilerException(string.Format("Multiple calls to another this ctor found in {0}", ctor)); var callToCtor = callToCtors[0]; // Change "ld this" to nop var args = callToCtor.GetCallArguments(seq, true); args[0].ChangeToNop(); // Replace ldarg.0 // Replace call to this ctor with newobj var callSeq = new ILSequence(); callSeq.Emit(OpCodes.Newobj, (MethodReference) callToCtor.Operand); callSeq.Emit(OpCodes.Stloc, thisVariable); // Save new object callToCtor.ChangeToNop(); callSeq.InsertToBefore(callToCtor, seq); // Replace ret instructions var end = seq.Emit(OpCodes.Ldloc, thisVariable); var retInstructions = seq.Where(x => x.OpCode.Code == Mono.Cecil.Cil.Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Inline the call to the given method /// </summary> private static void InlineCall(Instruction instruction, MethodBody body, MethodDefinition targetMethod) { // Prepare var prefixSeq = new ILSequence(); // Create "this" variable VariableDefinition thisVariable = null; if (targetMethod.HasThis) { thisVariable = new VariableDefinition(targetMethod.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; } // Store argument in variables var paramVariables = new List <VariableDefinition>(); foreach (var parameter in targetMethod.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Store this argument (if any) if (thisVariable != null) { // Pop prefixSeq.Emit(OpCodes.Stloc, thisVariable); } // Clone variables first var source = targetMethod.Body; var variables = new List <VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Code.Ldarg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Code.Ldarga: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Code.Starg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { body.ComputeOffsets(); CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Replace ret instructions var end = seq.Emit(OpCodes.Nop); var retInstructions = seq.Where(x => x.OpCode.Code == Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // cast return type of a generic method. TODO: better might be to // correctly resolve calls to generic instances as well (above) if (targetMethod.ReturnType.IsGenericParameter) { var methodRef = (MethodReference)instruction.Operand; TypeReference returnType = null; var gp = (GenericParameter)methodRef.ReturnType; if (gp.Type == GenericParameterType.Type) { var gi = methodRef.DeclaringType as IGenericInstance; if (gi != null && gi.HasGenericArguments) { returnType = gi.GenericArguments[gp.Position]; } } else if (gp.Type == GenericParameterType.Method) { var gi = methodRef as IGenericInstance; if (gi != null && gi.HasGenericArguments) { returnType = gi.GenericArguments[gp.Position]; } } if (returnType != null) { if (!returnType.IsPrimitive) { seq.Emit(OpCodes.Castclass, returnType); } // todo: handle primitive types. unbox them? are structs correctly handled? enums? } } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Ensure there is a class ctor. /// </summary> private static MethodDefinition EnsureClassCtor(ReachableContext reachableContext, TypeDefinition type) { var ctor = type.GetClassCtor(); if (ctor != null) return ctor; // Already exists // Create class ctor var typeSystem = type.Module.TypeSystem; ctor = new MethodDefinition(".cctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static | MethodAttributes.SpecialName, typeSystem.Void); ctor.DeclaringType = type; var body = new MethodBody(ctor); body.InitLocals = true; ctor.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add ctor type.Methods.Add(ctor); ctor.SetReachable(reachableContext); return ctor; }
/// <summary> /// Add a method that has the same signature as basemethod and calls method. /// </summary> private MethodDefinition AddBridge(MethodDefinition method, MethodDefinition baseMethod) { var bridge = new MethodDefinition(baseMethod.Name, baseMethod.Attributes, baseMethod.ReturnType) { HasThis = true }; var cloner = new TypeCloner(true, method.Module.TypeSystem); bridge.ReturnType = cloner.Get(baseMethod.ReturnType, bridge); bridge.IsAbstract = false; // Clone parameters foreach (var p in baseMethod.Parameters) { bridge.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, cloner.Get(p.ParameterType, bridge))); } // Create body var body = new MethodBody(bridge); bridge.Body = body; // Create code var seq = new ILSequence(); // this seq.Emit(OpCodes.Ldarg_0); // parameters for (var i = 0; i < bridge.Parameters.Count; i++) { var p = bridge.Parameters[i]; seq.Emit(OpCodes.Ldarg, p); if (baseMethod.Parameters[i].ParameterType.ContainsGenericParameter) { seq.Emit(OpCodes.Unbox, method.Parameters[i].ParameterType); } } // Call actual method seq.Emit(OpCodes.Call, method); // Return seq.Emit(OpCodes.Ret); // Add code to body seq.AppendTo(body); body.ComputeOffsets(); // add overrides, so that we can find the method later bridge.Overrides.Add(baseMethod); // Add to class method.DeclaringType.Methods.Add(bridge); bridge.SetReachable(reachableContext); return bridge; }
/// <summary> /// Inline the call to the given ctor /// </summary> private static void InlineNewObjCall(Instruction instruction, MethodBody body, MethodDefinition ctor) { // Prepare var prefixSeq = new ILSequence(); ctor.Body.SimplifyMacros(); // Create "this" variable var thisVariable = new VariableDefinition(ctor.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; // Store argument in variables var paramVariables = new List <VariableDefinition>(); foreach (var parameter in ctor.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Clone variables first var source = ctor.Body; var variables = new List <VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Mono.Cecil.Cil.Code.Ldarg: { var index = ctor.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Mono.Cecil.Cil.Code.Ldarga: { var index = ctor.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Mono.Cecil.Cil.Code.Starg: { var index = ctor.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Find call to "this" ctor var callToCtors = seq.Where(x => IsCallToThisCtor(x, ctor)).ToList(); if (callToCtors.Count == 0) { throw new CompilerException(string.Format("No call to another this ctor found in {0}", ctor)); } if (callToCtors.Count > 1) { throw new CompilerException(string.Format("Multiple calls to another this ctor found in {0}", ctor)); } var callToCtor = callToCtors[0]; // Change "ld this" to nop var args = callToCtor.GetCallArguments(seq, true); args[0].ChangeToNop(); // Replace ldarg.0 // Replace call to this ctor with newobj var callSeq = new ILSequence(); callSeq.Emit(OpCodes.Newobj, (MethodReference)callToCtor.Operand); callSeq.Emit(OpCodes.Stloc, thisVariable); // Save new object callToCtor.ChangeToNop(); callSeq.InsertToBefore(callToCtor, seq); // Replace ret instructions var end = seq.Emit(OpCodes.Ldloc, thisVariable); var retInstructions = seq.Where(x => x.OpCode.Code == Mono.Cecil.Cil.Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Inline the call to the given method /// </summary> private static void InlineCall(Instruction instruction, MethodBody body, MethodDefinition targetMethod) { // Prepare var prefixSeq = new ILSequence(); // Create "this" variable VariableDefinition thisVariable = null; if (targetMethod.HasThis) { thisVariable = new VariableDefinition(targetMethod.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; } // Store argument in variables var paramVariables = new List <VariableDefinition>(); foreach (var parameter in targetMethod.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Store this argument (if any) if (thisVariable != null) { // Pop prefixSeq.Emit(OpCodes.Stloc, thisVariable); } // Clone variables first var source = targetMethod.Body; var variables = new List <VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Code.Ldarg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Code.Ldarga: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Code.Starg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { body.ComputeOffsets(); CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Replace ret instructions var end = seq.Emit(OpCodes.Nop); var retInstructions = seq.Where(x => x.OpCode.Code == Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Create a CopyFrom method. /// </summary> private static MethodDefinition CreateCopyFromMethod(ReachableContext reachableContext, TypeDefinition type) { var typeSystem = type.Module.TypeSystem; var method = new MethodDefinition(NameConstants.Struct.CopyFromMethodName, MethodAttributes.Public, type); var sourceParam = new ParameterDefinition(type); method.Parameters.Add(sourceParam); method.DeclaringType = type; var body = new MethodBody(method); body.InitLocals = true; method.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); // Call base CopyFrom var baseType = (type.BaseType != null) ? type.BaseType.GetElementType().Resolve() : null; if ((baseType != null) && baseType.IsValueType && (baseType.FullName != "System.ValueType")) { var baseMethod = new MethodReference(NameConstants.Struct.CopyFromMethodName, baseType, baseType) { HasThis = true }; baseMethod.Parameters.Add(new ParameterDefinition(baseType)); seq.Emit(OpCodes.Ldarg, sourceParam); seq.Emit(OpCodes.Call, baseMethod); } // Copy all fields foreach (var field in type.Fields.Where(x => !x.IsStatic)) { // Not need to bother with cloning struct-type fields here, // as this will be done automatically by one of the Converters. // Prepare for stfld seq.Emit(OpCodes.Ldarg, body.ThisParameter); // Load from source seq.Emit(OpCodes.Ldarg, sourceParam); seq.Emit(OpCodes.Ldfld, field); // Save in this seq.Emit(OpCodes.Stfld, field); } // Return this seq.Emit(OpCodes.Ldarg, body.ThisParameter); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add method type.Methods.Add(method); method.SetReachable(reachableContext); return(method); }
/// <summary> /// Inline the call to the given method /// </summary> private static void InlineCall(Instruction instruction, MethodBody body, MethodDefinition targetMethod) { // Prepare var prefixSeq = new ILSequence(); // Create "this" variable VariableDefinition thisVariable = null; if (targetMethod.HasThis) { thisVariable = new VariableDefinition(targetMethod.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; } // Store argument in variables var paramVariables = new List<VariableDefinition>(); foreach (var parameter in targetMethod.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Store this argument (if any) if (thisVariable != null) { // Pop prefixSeq.Emit(OpCodes.Stloc, thisVariable); } // Clone variables first var source = targetMethod.Body; var variables = new List<VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Code.Ldarg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Code.Ldarga: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Code.Starg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { body.ComputeOffsets(); CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Replace ret instructions var end = seq.Emit(OpCodes.Nop); var retInstructions = seq.Where(x => x.OpCode.Code == Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Inline the call to the given method /// </summary> private static void InlineCall(Instruction instruction, MethodBody body, MethodDefinition targetMethod) { // Prepare var prefixSeq = new ILSequence(); // Create "this" variable VariableDefinition thisVariable = null; if (targetMethod.HasThis) { thisVariable = new VariableDefinition(targetMethod.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; } // Store argument in variables var paramVariables = new List<VariableDefinition>(); foreach (var parameter in targetMethod.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Store this argument (if any) if (thisVariable != null) { // Pop prefixSeq.Emit(OpCodes.Stloc, thisVariable); } // Clone variables first var source = targetMethod.Body; var variables = new List<VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Code.Ldarg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Code.Ldarga: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Code.Starg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { body.ComputeOffsets(); CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Replace ret instructions var end = seq.Emit(OpCodes.Nop); var retInstructions = seq.Where(x => x.OpCode.Code == Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // cast return type of a generic method. TODO: better might be to // correctly resolve calls to generic instances as well (above) if (targetMethod.ReturnType.IsGenericParameter) { var methodRef = (MethodReference)instruction.Operand; TypeReference returnType = null; var gp = (GenericParameter)methodRef.ReturnType; if (gp.Type == GenericParameterType.Type) { var gi = methodRef.DeclaringType as IGenericInstance; if (gi != null && gi.HasGenericArguments) returnType = gi.GenericArguments[gp.Position]; } else if (gp.Type == GenericParameterType.Method) { var gi = methodRef as IGenericInstance; if (gi != null && gi.HasGenericArguments) returnType = gi.GenericArguments[gp.Position]; } if (returnType != null) { if (!returnType.IsPrimitive) { seq.Emit(OpCodes.Castclass, returnType); } // todo: handle primitive types. unbox them? are structs correctly handled? enums? } } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Convert all synchronized methods. /// </summary> private static void Convert(MethodBody body) { var typeSystem = body.Method.Module.TypeSystem; var monitorType = typeSystem.LookupType("System.Threading", "Monitor"); var enterMethod = new MethodReference("Enter", typeSystem.Void, monitorType); enterMethod.Parameters.Add(new ParameterDefinition(typeSystem.Object)); var exitMethod = new MethodReference("Exit", typeSystem.Void, monitorType); exitMethod.Parameters.Add(new ParameterDefinition(typeSystem.Object)); var firstInstr = body.Instructions[0]; // Expand macro's body.SimplifyMacros(); // Prepare new return var retSeq = new ILSequence(); retSeq.Emit(OpCodes.Nop); retSeq.Emit(OpCodes.Ret); // Monitor.Enter(this) var initSeq = new ILSequence(); initSeq.Emit(OpCodes.Ldarg_0); // ld this initSeq.Emit(OpCodes.Call, enterMethod); initSeq.InsertTo(0, body); // Leave sequence var leaveSeq = new ILSequence(); leaveSeq.Emit(OpCodes.Nop); leaveSeq.Emit(OpCodes.Leave, retSeq.First); leaveSeq.AppendTo(body); // Finally: Monitor.Exit(this) var finallySeq = new ILSequence(); finallySeq.Emit(OpCodes.Ldarg_0); // ld this finallySeq.Emit(OpCodes.Call, exitMethod); finallySeq.Emit(OpCodes.Endfinally); finallySeq.AppendTo(body); // Replace Ret instructions foreach (var instr in body.Instructions.Where(x => x.OpCode.Code == Mono.Cecil.Cil.Code.Ret)) { if (instr.Next == leaveSeq.First) { instr.ChangeToNop(); } else { instr.OpCode = OpCodes.Br; instr.Operand = leaveSeq.First; } } // Append ret sequence retSeq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add try/finally block var handler = new ExceptionHandler(ExceptionHandlerType.Finally); handler.TryStart = firstInstr; handler.TryEnd = finallySeq.First; // leaveSeq.Last; handler.HandlerStart = finallySeq.First; handler.HandlerEnd = retSeq.First; // finallySeq.Last; body.ExceptionHandlers.Insert(0, handler); }