From c4e7f1207e32b81b4a51039ef022663d173c85f5 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Thu, 21 Dec 2023 15:50:10 -0800 Subject: [PATCH 1/4] Fix/refactor ModuleBuilder.Get***MetadatToken, add ILGenerator.EmitCalli implementation --- .../src/Resources/Strings.resx | 3 + .../System/Reflection/Emit/ILGeneratorImpl.cs | 169 ++++++++++---- .../Reflection/Emit/MethodBuilderImpl.cs | 2 +- .../Reflection/Emit/ModuleBuilderImpl.cs | 220 ++++++++++++++---- .../System/Reflection/Emit/SignatureHelper.cs | 28 ++- .../AssemblySaveILGeneratorTests.cs | 170 +++++++++++++- 6 files changed, 484 insertions(+), 108 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index 6572d94d71d67b..8e61db2ee5f0fc 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -228,4 +228,7 @@ {0} is not a GenericMethodDefinition. MakeGenericMethod may only be called on a method for which MethodBase.IsGenericMethodDefinition is true. + + Calling convention must be VarArgs. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index 8810a1819df38c..c96a0a8c34e16e 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -12,7 +12,8 @@ namespace System.Reflection.Emit internal sealed class ILGeneratorImpl : ILGenerator { private const int DefaultSize = 16; - private readonly MethodBuilder _methodBuilder; + private readonly MethodBuilderImpl _methodBuilder; + private readonly ModuleBuilderImpl _moduleBuilder; private readonly BlobBuilder _builder; private readonly InstructionEncoder _il; private readonly ControlFlowBuilder _cfBuilder; @@ -21,12 +22,13 @@ internal sealed class ILGeneratorImpl : ILGenerator private int _currentStack; private List _locals = new(); private Dictionary _labelTable = new(2); - private List> _memberReferences = new(); + private List> _memberReferences = new(); private List _exceptionStack = new(); - internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) + internal ILGeneratorImpl(MethodBuilderImpl methodBuilder, int size) { _methodBuilder = methodBuilder; + _moduleBuilder = (ModuleBuilderImpl)methodBuilder.Module; // For compat, runtime implementation doesn't throw for negative or zero value. _builder = new BlobBuilder(Math.Max(size, DefaultSize)); _cfBuilder = new ControlFlowBuilder(); @@ -34,7 +36,7 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) } internal int GetMaxStackSize() => _maxStackSize; - internal List> GetMemberReferences() => _memberReferences; + internal List> GetMemberReferences() => _memberReferences; internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; internal List Locals => _locals; @@ -76,9 +78,8 @@ public override void BeginCatchBlock(Type? exceptionType) currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = DefineLabel(); - ModuleBuilderImpl module = (ModuleBuilderImpl)_methodBuilder.Module; _cfBuilder.AddCatchRegion(_labelTable[currentExBlock.TryStart], _labelTable[currentExBlock.TryEnd], - _labelTable[currentExBlock.HandleStart], _labelTable[currentExBlock.HandleEnd], module.GetTypeHandle(exceptionType)); + _labelTable[currentExBlock.HandleStart], _labelTable[currentExBlock.HandleEnd], _moduleBuilder.GetTypeHandle(exceptionType)); MarkLabel(currentExBlock.HandleStart); } @@ -183,12 +184,9 @@ public override void BeginFinallyBlock() public override LocalBuilder DeclareLocal(Type localType, bool pinned) { - if (_methodBuilder is not MethodBuilderImpl methodBuilder) - throw new NotSupportedException(); - ArgumentNullException.ThrowIfNull(localType); - LocalBuilder local = new LocalBuilderImpl(_locals.Count, localType, methodBuilder, pinned); + LocalBuilder local = new LocalBuilderImpl(_locals.Count, localType, _methodBuilder, pinned); _locals.Add(local); return local; @@ -207,9 +205,8 @@ private void UpdateStackSize(OpCode opCode) _maxStackSize = Math.Max(_maxStackSize, _currentStack); } - private void UpdateStackSize(OpCode opCode, int stackChange) + private void UpdateStackSize(int stackChange) { - _currentStack += opCode.EvaluationStackDelta; _currentStack += stackChange; _maxStackSize = Math.Max(_maxStackSize, _currentStack); } @@ -348,12 +345,9 @@ public override void Emit(OpCode opcode, long arg) public override void Emit(OpCode opcode, string str) { - // Puts the opcode onto the IL stream followed by the metadata token - // represented by str. - ModuleBuilder modBuilder = (ModuleBuilder)_methodBuilder.Module; - int tempVal = modBuilder.GetStringMetadataToken(str); + // Puts the opcode onto the IL stream followed by the metadata token represented by str. EmitOpcode(opcode); - _il.Token(tempVal); + _il.Token(_moduleBuilder.GetStringMetadataToken(str)); } public override void Emit(OpCode opcode, ConstructorInfo con) @@ -384,10 +378,24 @@ public override void Emit(OpCode opcode, ConstructorInfo con) stackChange--; } - UpdateStackSize(opcode, stackChange); - _il.OpCode((ILOpCode)opcode.Value); - _memberReferences.Add(new KeyValuePair - (con, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + EmitOpcode(opcode); + UpdateStackSize(stackChange); + WriteOrReserveToken(_moduleBuilder.GetMethodMetadataToken(con), con); + } + + private void WriteOrReserveToken(int token, object member) + { + if (token == -1) + { + // The member is a `*BuilderImpl` and its token is not yet defined. + // Reserve the token bytes and write them later when its ready + _memberReferences.Add(new KeyValuePair + (member, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + } + else + { + _il.Token(token); + } } public override void Emit(OpCode opcode, Label label) @@ -449,13 +457,33 @@ public override void Emit(OpCode opcode, LocalBuilder local) UpdateStackSize(opcode); } - public override void Emit(OpCode opcode, SignatureHelper signature) => throw new NotImplementedException(); + public override void Emit(OpCode opcode, SignatureHelper signature) + { + ArgumentNullException.ThrowIfNull(signature); + + EmitOpcode(opcode); + // The only IL instruction that has VarPop behaviour, that takes a Signature + // token as a parameter is Calli. Pop the parameters and the native function + // pointer. Used reflection since ArgumentCount property is not public. + if (opcode.StackBehaviourPop == StackBehaviour.Varpop) + { + Debug.Assert(opcode.Equals(OpCodes.Calli), "Unexpected opcode encountered for StackBehaviour VarPop."); + // Pop the arguments. + PropertyInfo argCountProperty = typeof(SignatureHelper).GetProperty("ArgumentCount", BindingFlags.NonPublic | BindingFlags.Instance)!; + int stackChange = -(int)argCountProperty.GetValue(signature)!; + // Pop native function pointer off the stack. + stackChange--; + UpdateStackSize(stackChange); + } + _il.Token(_moduleBuilder.GetSignatureMetadataToken(signature)); + } public override void Emit(OpCode opcode, FieldInfo field) { ArgumentNullException.ThrowIfNull(field); - EmitMember(opcode, field); + EmitOpcode(opcode); + WriteOrReserveToken(_moduleBuilder.GetFieldMetadataToken(field), field); } public override void Emit(OpCode opcode, MethodInfo meth) @@ -468,24 +496,17 @@ public override void Emit(OpCode opcode, MethodInfo meth) } else { - EmitMember(opcode, meth); + EmitOpcode(opcode); + WriteOrReserveToken(_moduleBuilder.GetMethodMetadataToken(meth), meth); } } - private void EmitMember(OpCode opcode, MemberInfo member) - { - EmitOpcode(opcode); - _memberReferences.Add(new KeyValuePair - (member, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); - } - public override void Emit(OpCode opcode, Type cls) { ArgumentNullException.ThrowIfNull(cls); EmitOpcode(opcode); - ModuleBuilder module = (ModuleBuilder)_methodBuilder.Module; - _il.Token(module.GetTypeMetadataToken(cls)); + WriteOrReserveToken(_moduleBuilder.GetTypeMetadataToken(cls), cls); } public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) @@ -497,10 +518,17 @@ public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? opti throw new ArgumentException(SR.Argument_NotMethodCallOpcode, nameof(opcode)); } - _il.OpCode((ILOpCode)opcode.Value); - UpdateStackSize(opcode, GetStackChange(opcode, methodInfo, optionalParameterTypes)); - _memberReferences.Add(new KeyValuePair - (methodInfo, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + EmitOpcode(opcode); + UpdateStackSize(GetStackChange(opcode, methodInfo, optionalParameterTypes)); + if (optionalParameterTypes == null || optionalParameterTypes.Length == 0) + { + WriteOrReserveToken(_moduleBuilder.GetMethodMetadataToken(methodInfo), methodInfo); + } + else + { + WriteOrReserveToken(_moduleBuilder.GetMethodMetadataToken(methodInfo, optionalParameterTypes), + new KeyValuePair(methodInfo, optionalParameterTypes)); + } } private static int GetStackChange(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) @@ -539,8 +567,61 @@ private static int GetStackChange(OpCode opcode, MethodInfo methodInfo, Type[]? return stackChange; } - public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) => throw new NotImplementedException(); - public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes) => throw new NotImplementedException(); + public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, + Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) + { + if (optionalParameterTypes != null) + { + if ((callingConvention & CallingConventions.VarArgs) == 0) + { + // Client should not supply optional parameter in default calling convention + throw new InvalidOperationException(SR.InvalidOperation_NotAVarArgCallingConvention); + } + } + + int stackChange = GetStackChange(returnType, parameterTypes); + + // Pop off vararg arguments. + if (optionalParameterTypes != null) + { + stackChange -= optionalParameterTypes.Length; + } + // Pop the this parameter if the method has a this parameter. + if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) + { + stackChange--; + } + + UpdateStackSize(stackChange); + EmitOpcode(OpCodes.Calli); + _il.Token(_moduleBuilder.GetSignatureToken(callingConvention, returnType, parameterTypes, optionalParameterTypes)); + } + + public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes) + { + int stackChange = GetStackChange(returnType, parameterTypes); + UpdateStackSize(stackChange); + Emit(OpCodes.Calli); + _il.Token(_moduleBuilder.GetSignatureToken(unmanagedCallConv, returnType, parameterTypes)); + } + + private static int GetStackChange(Type? returnType, Type[]? parameterTypes) + { + int stackChange = 0; + // If there is a non-void return type, push one. + if (returnType != typeof(void)) + { + stackChange++; + } + // Pop off arguments if any. + if (parameterTypes != null) + { + stackChange -= parameterTypes.Length; + } + // Pop the native function pointer. + stackChange--; + return stackChange; + } public override void EndExceptionBlock() { @@ -574,7 +655,10 @@ public override void EndExceptionBlock() _exceptionStack.Remove(currentExBlock); } - public override void EndScope() => throw new NotImplementedException(); + public override void EndScope() + { + // TODO: + } public override void MarkLabel(Label loc) { @@ -588,7 +672,10 @@ public override void MarkLabel(Label loc) } } - public override void UsingNamespace(string usingNamespace) => throw new NotImplementedException(); + public override void UsingNamespace(string usingNamespace) + { + // TODO: looks does nothing + } } internal sealed class ExceptionBlock diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs index 0ff656d3b290de..614877922bbd0a 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs @@ -70,7 +70,7 @@ internal MethodBuilderImpl(string name, MethodAttributes attributes, CallingConv internal ILGeneratorImpl? ILGeneratorImpl => _ilGenerator; - internal BlobBuilder GetMethodSignatureBlob() => MetadataSignatureHelper.MethodSignatureEncoder(_module, + internal BlobBuilder GetMethodSignatureBlob() => MetadataSignatureHelper.GetMethodSignature(_module, _parameterTypes, ReturnType, GetSignatureConvention(_callingConventions), GetGenericArguments().Length, !IsStatic); internal static SignatureCallingConvention GetSignatureConvention(CallingConventions callingConventions) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index 7cc2ada141c7f0..5ed16a22784a3a 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -18,9 +18,8 @@ internal sealed class ModuleBuilderImpl : ModuleBuilder private readonly AssemblyBuilderImpl _assemblyBuilder; private readonly Dictionary _assemblyReferences = new(); private readonly Dictionary _typeReferences = new(); - private readonly Dictionary _memberReferences = new(); + private readonly Dictionary _memberReferences = new(); private readonly List _typeDefinitions = new(); - private readonly Dictionary _ctorReferences = new(); private Dictionary? _moduleReferences; private List? _customAttributes; private int _nextTypeDefRowId = 1; @@ -240,7 +239,7 @@ private void WriteProperties(TypeBuilderImpl typeBuilder) AddPropertyMap(typeBuilder._handle, typeBuilder._firstPropertyToken); foreach (PropertyBuilderImpl property in typeBuilder._propertyDefinitions) { - PropertyDefinitionHandle propertyHandle = AddPropertyDefinition(property, MetadataSignatureHelper.PropertySignatureEncoder(property, this)); + PropertyDefinitionHandle propertyHandle = AddPropertyDefinition(property, MetadataSignatureHelper.GetPropertySignature(property, this)); WriteCustomAttributes(property._customAttributes, propertyHandle); if (property.GetMethod is MethodBuilderImpl gMb) @@ -323,7 +322,7 @@ private void WriteMethods(List methods, List methods, List pair in il.GetMemberReferences()) + foreach (KeyValuePair pair in il.GetMemberReferences()) { - pair.Value.WriteInt32(MetadataTokens.GetToken(GetMemberHandle(pair.Key))); + if (pair.Key is MemberInfo member) + { + pair.Value.WriteInt32(MetadataTokens.GetToken(GetMemberHandle(member))); + } + + if (pair.Key is KeyValuePair pair2) + { + pair.Value.WriteInt32(MetadataTokens.GetToken(GetMethodReference(pair2.Key, pair2.Value))); + } } } @@ -393,7 +400,7 @@ private void WriteFields(TypeBuilderImpl typeBuilder) { foreach (FieldBuilderImpl field in typeBuilder._fieldDefinitions) { - FieldDefinitionHandle handle = AddFieldDefinition(field, MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this)); + FieldDefinitionHandle handle = AddFieldDefinition(field, MetadataSignatureHelper.GetFieldSignature(field.FieldType, this)); Debug.Assert(field._handle == handle); WriteCustomAttributes(field._customAttributes, handle); @@ -433,24 +440,12 @@ internal void WriteCustomAttributes(List? customAttribut { foreach (CustomAttributeWrapper customAttribute in customAttributes) { - _metadataBuilder.AddCustomAttribute(parent, GetConstructorHandle(customAttribute.Ctor), + _metadataBuilder.AddCustomAttribute(parent, GetMemberHandle(customAttribute.Ctor), _metadataBuilder.GetOrAddBlob(customAttribute.Data)); } } } - private MemberReferenceHandle GetConstructorHandle(ConstructorInfo constructorInfo) - { - if (!_ctorReferences.TryGetValue(constructorInfo, out var constructorHandle)) - { - EntityHandle parentHandle = GetTypeReferenceOrSpecificationHandle(constructorInfo.DeclaringType!); - constructorHandle = AddConstructorReference(parentHandle, constructorInfo); - _ctorReferences.Add(constructorInfo, constructorHandle); - } - - return constructorHandle; - } - private EntityHandle GetTypeReferenceOrSpecificationHandle(Type type) { if (!_typeReferences.TryGetValue(type, out var typeHandle)) @@ -479,17 +474,31 @@ private MethodSpecificationHandle AddMethodSpecification(EntityHandle methodHand method: methodHandle, instantiation: _metadataBuilder.GetOrAddBlob(MetadataSignatureHelper.GetMethodSpecificationSignature(genericArgs, this))); - private EntityHandle GetMemberReference(MemberInfo member) + private EntityHandle GetMemberReferenceHandle(MemberInfo member) { if (!_memberReferences.TryGetValue(member, out var memberHandle)) { - if (member is MethodInfo method && method.IsConstructedGenericMethod) + switch (member) { - memberHandle = AddMethodSpecification(GetMemberReference(method.GetGenericMethodDefinition()), method.GetGenericArguments()); - } - else - { - memberHandle = AddMemberReference(member.Name, GetTypeHandle(member.DeclaringType!), GetMemberSignature(member)); + case FieldInfo field: + memberHandle = AddMemberReference(field.Name, GetTypeHandle(field.DeclaringType!), MetadataSignatureHelper.GetFieldSignature(field.FieldType, this)); + break; + case ConstructorInfo ctor: + memberHandle = AddMemberReference(ctor.Name, GetTypeHandle(ctor.DeclaringType!), GetMemberSignature(ctor)); + break; + case MethodInfo method: + if (method.IsConstructedGenericMethod) + { + memberHandle = AddMethodSpecification(GetMemberHandle(method.GetGenericMethodDefinition()), method.GetGenericArguments()); + } + else + { + memberHandle = AddMemberReference(member.Name, GetTypeHandle(member.DeclaringType!), GetMemberSignature(member)); + } + break; + default: + throw new NotSupportedException(); + } _memberReferences.Add(member, memberHandle); @@ -498,22 +507,40 @@ private EntityHandle GetMemberReference(MemberInfo member) return memberHandle; } + private EntityHandle GetMethodReference(MethodInfo method, Type[] optionalParameterTypes) + { + BlobBuilder signature = GetMethodSignature(method, optionalParameterTypes); + KeyValuePair pair = new(method, signature); + if (!_memberReferences.TryGetValue(pair, out var memberHandle)) + { + memberHandle = AddMemberReference(method.Name, GetMemberHandle(method), signature); + + _memberReferences.Add(pair, memberHandle); + } + + return memberHandle; + } + + private BlobBuilder GetMethodSignature(MethodInfo method, Type[] optionalParameterTypes) => + MetadataSignatureHelper.GetMethodSignature(this, ParameterTypes(method.GetParameters()), method.ReturnType, + MethodBuilderImpl.GetSignatureConvention(method.CallingConvention), method.GetGenericArguments().Length, !method.IsStatic, optionalParameterTypes); + private BlobBuilder GetMemberSignature(MemberInfo member) { if (member is MethodInfo method) { - return MetadataSignatureHelper.MethodSignatureEncoder(this, ParameterTypes(method.GetParameters()), method.ReturnType, + return MetadataSignatureHelper.GetMethodSignature(this, ParameterTypes(method.GetParameters()), method.ReturnType, MethodBuilderImpl.GetSignatureConvention(method.CallingConvention), method.GetGenericArguments().Length, !method.IsStatic); } if (member is FieldInfo field) { - return MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this); + return MetadataSignatureHelper.GetFieldSignature(field.FieldType, this); } if (member is ConstructorInfo ctor) { - return MetadataSignatureHelper.ConstructorSignatureEncoder(ctor.GetParameters(), this); + return MetadataSignatureHelper.GetConstructorSignature(ctor.GetParameters(), this); } throw new NotSupportedException(); @@ -630,15 +657,6 @@ private MemberReferenceHandle AddMemberReference(string memberName, EntityHandle name: _metadataBuilder.GetOrAddString(memberName), signature: _metadataBuilder.GetOrAddBlob(signature)); - private MemberReferenceHandle AddConstructorReference(EntityHandle parent, ConstructorInfo method) - { - var blob = MetadataSignatureHelper.ConstructorSignatureEncoder(method.GetParameters(), this); - return _metadataBuilder.AddMemberReference( - parent: parent, - name: _metadataBuilder.GetOrAddString(method.Name), - signature: _metadataBuilder.GetOrAddBlob(blob)); - } - private void AddMethodImport(MethodDefinitionHandle methodHandle, string name, MethodImportAttributes attributes, ModuleReferenceHandle moduleHandle) => _metadataBuilder.AddMethodImport( @@ -689,22 +707,42 @@ internal EntityHandle GetTypeHandle(Type type) internal EntityHandle GetMemberHandle(MemberInfo member) { + if (member is TypeBuilderImpl tb && Equals(tb.Module)) + { + return tb._handle; + } + + if (member is Type type) + { + return GetTypeReferenceOrSpecificationHandle(type); + } + if (member is MethodBuilderImpl mb && Equals(mb.Module)) { return mb._handle; } + if (member is ConstructorBuilderImpl ctor && Equals(ctor.Module)) + { + return ctor._methodBuilder._handle; + } + if (member is FieldBuilderImpl fb && Equals(fb.Module)) { return fb._handle; } - if (member is ConstructorBuilderImpl ctor && Equals(ctor.Module)) + if (member is PropertyBuilderImpl prop && Equals(prop.Module)) { - return ctor._methodBuilder._handle; + return prop._handle; + } + + if (member is EnumBuilderImpl en && Equals(en.Module)) + { + return en._typeBuilder._handle; } - return GetMemberReference(member); + return GetMemberReferenceHandle(member); } internal TypeBuilder DefineNestedType(string name, TypeAttributes attr, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? parent, @@ -723,29 +761,87 @@ internal TypeBuilder DefineNestedType(string name, TypeAttributes attr, [Dynamic public override int GetFieldMetadataToken(FieldInfo field) { - if (field is FieldBuilderImpl fb && fb._handle != default) + if (field is FieldBuilderImpl fb) { - return MetadataTokens.GetToken(fb._handle); + return fb._handle != default ? MetadataTokens.GetToken(fb._handle) : -1; } - return 0; + if (IsConstuctedFromNotBakedTypeBuilder(field.DeclaringType!)) + { + return -1; + } + + return MetadataTokens.GetToken(GetMemberReferenceHandle(field)); } - public override int GetMethodMetadataToken(ConstructorInfo constructor) => throw new NotImplementedException(); + private static bool IsConstuctedFromNotBakedTypeBuilder(Type type) => type.IsConstructedGenericType && + type.GetGenericTypeDefinition() is TypeBuilderImpl tb && tb._handle == default; + + public override int GetMethodMetadataToken(ConstructorInfo constructor) + { + if (constructor is ConstructorBuilderImpl cb) + { + return cb._methodBuilder._handle != default ? MetadataTokens.GetToken(cb._methodBuilder._handle) : -1; + } + + if (IsConstuctedFromNotBakedTypeBuilder(constructor.DeclaringType!)) + { + return -1; + } + + return MetadataTokens.GetToken(GetMemberReferenceHandle(constructor)); + } public override int GetMethodMetadataToken(MethodInfo method) { - if (method is MethodBuilderImpl mb && mb._handle != default) + if (method is MethodBuilderImpl mb) + { + return mb._handle != default ? MetadataTokens.GetToken(mb._handle) : -1; + } + + if (IsConstructedMethodFromNotBakedMethodBuilder(method)) + { + return -1; + } + + return MetadataTokens.GetToken(GetMemberReferenceHandle(method)); + } + + private static bool IsConstructedMethodFromNotBakedMethodBuilder(MethodInfo method) => + method.IsConstructedGenericMethod && method.GetGenericMethodDefinition() is MethodBuilderImpl mb2 && mb2._handle == default; + + internal int GetMethodMetadataToken(MethodInfo method, Type[] optionalParameterTypes) + { + if ((method.CallingConvention & CallingConventions.VarArgs) == 0) { - return MetadataTokens.GetToken(mb._handle); + // Client should not supply optional parameter in default calling convention + throw new InvalidOperationException(SR.InvalidOperation_NotAVarArgCallingConvention); } - return 0; + if (method is MethodBuilderImpl mb && mb._handle == default || IsConstructedMethodFromNotBakedMethodBuilder(method)) + { + return -1; + } + + return MetadataTokens.GetToken(GetMethodReference(method, optionalParameterTypes)); } public override int GetStringMetadataToken(string stringConstant) => MetadataTokens.GetToken(_metadataBuilder.GetOrAddUserString(stringConstant)); - public override int GetTypeMetadataToken(Type type) => MetadataTokens.GetToken(GetTypeHandle(type)); + public override int GetTypeMetadataToken(Type type) + { + if (type is TypeBuilderImpl tb && Equals(tb.Module)) + { + return tb._handle != default ? MetadataTokens.GetToken(tb._handle) : -1; + } + + if (type is EnumBuilderImpl eb && Equals(eb.Module)) + { + return eb._typeBuilder._handle != default ? MetadataTokens.GetToken(eb._typeBuilder._handle) : -1; + } + + return MetadataTokens.GetToken(GetTypeReferenceOrSpecificationHandle(type)); + } protected override void CreateGlobalFunctionsCore() => throw new NotImplementedException(); @@ -775,6 +871,28 @@ protected override void SetCustomAttributeCore(ConstructorInfo con, ReadOnlySpan _customAttributes ??= new List(); _customAttributes.Add(new CustomAttributeWrapper(con, binaryAttribute)); } - public override int GetSignatureMetadataToken(SignatureHelper signature) => throw new NotImplementedException(); + + public override int GetSignatureMetadataToken(SignatureHelper signature) => + MetadataTokens.GetToken(_metadataBuilder.AddStandaloneSignature(_metadataBuilder.GetOrAddBlob(signature.GetSignature()))); + + internal int GetSignatureToken(CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) => + MetadataTokens.GetToken(_metadataBuilder.AddStandaloneSignature( + _metadataBuilder.GetOrAddBlob(MetadataSignatureHelper.GetMethodSignature(this, parameterTypes, returnType, + MethodBuilderImpl.GetSignatureConvention(callingConvention), optionalParameterTypes: optionalParameterTypes)))); + + internal int GetSignatureToken(CallingConvention callingConvention, Type? returnType, Type[]? parameterTypes) => + MetadataTokens.GetToken(_metadataBuilder.AddStandaloneSignature(_metadataBuilder.GetOrAddBlob( + MetadataSignatureHelper.GetMethodSignature(this, parameterTypes, returnType, GetSignatureConvention(callingConvention))))); + + private static SignatureCallingConvention GetSignatureConvention(CallingConvention callingConvention) => + callingConvention switch + { + CallingConvention.Winapi => SignatureCallingConvention.Default, // TODO: platform-specific + CallingConvention.Cdecl => SignatureCallingConvention.CDecl, + CallingConvention.StdCall => SignatureCallingConvention.StdCall, + CallingConvention.ThisCall => SignatureCallingConvention.ThisCall, + CallingConvention.FastCall => SignatureCallingConvention.FastCall, + _ => SignatureCallingConvention.Default, + }; } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index d5243251a36a98..2fd790cd324df8 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -8,10 +8,9 @@ namespace System.Reflection.Emit { - // TODO: Only support simple signatures. More complex signatures (generics, array, byref, pointers etc) will be added. internal static class MetadataSignatureHelper { - internal static BlobBuilder LocalSignatureEncoder(List locals, ModuleBuilderImpl module) + internal static BlobBuilder GetLocalSignature(List locals, ModuleBuilderImpl module) { BlobBuilder localSignature = new(); LocalVariablesEncoder encoder = new BlobEncoder(localSignature).LocalVariableSignature(locals.Count); @@ -25,7 +24,7 @@ internal static BlobBuilder LocalSignatureEncoder(List locals, Mod return localSignature; } - internal static BlobBuilder FieldSignatureEncoder(Type fieldType, ModuleBuilderImpl module) + internal static BlobBuilder GetFieldSignature(Type fieldType, ModuleBuilderImpl module) { BlobBuilder fieldSignature = new(); WriteSignatureForType(new BlobEncoder(fieldSignature).Field().Type(), fieldType, module); @@ -33,7 +32,7 @@ internal static BlobBuilder FieldSignatureEncoder(Type fieldType, ModuleBuilderI return fieldSignature; } - internal static BlobBuilder ConstructorSignatureEncoder(ParameterInfo[]? parameters, ModuleBuilderImpl module) + internal static BlobBuilder GetConstructorSignature(ParameterInfo[]? parameters, ModuleBuilderImpl module) { BlobBuilder constructorSignature = new(); @@ -71,27 +70,32 @@ internal static BlobBuilder GetMethodSpecificationSignature(Type[] genericArgume return methodSpecSignature; } - internal static BlobBuilder MethodSignatureEncoder(ModuleBuilderImpl module, Type[]? parameters, - Type? returnType, SignatureCallingConvention convention, int genParamCount, bool isInstance) + internal static BlobBuilder GetMethodSignature(ModuleBuilderImpl module, Type[]? parameters, Type? returnType, + SignatureCallingConvention convention, int genParamCount = 0, bool isInstance = false, Type[]? optionalParameterTypes = null) { - // Encoding return type and parameters. BlobBuilder methodSignature = new(); - new BlobEncoder(methodSignature). - MethodSignature(convention: convention, genericParameterCount: genParamCount, isInstanceMethod: isInstance). - Parameters((parameters == null) ? 0 : parameters.Length, out ReturnTypeEncoder retEncoder, out ParametersEncoder parEncoder); + int paramsLength = ((parameters == null) ? 0 : parameters.Length) + ((optionalParameterTypes == null) ? 0 : optionalParameterTypes.Length); + + new BlobEncoder(methodSignature).MethodSignature(convention, genParamCount, isInstance). + Parameters(paramsLength, out ReturnTypeEncoder retEncoder, out ParametersEncoder parEncoder); if (returnType != null && returnType != module.GetTypeFromCoreAssembly(CoreTypeId.Void)) { WriteSignatureForType(retEncoder.Type(), returnType, module); } - else // If null mark ReturnTypeEncoder as void + else { retEncoder.Void(); } WriteParametersSignature(module, parameters, parEncoder); + if (optionalParameterTypes != null && optionalParameterTypes.Length != 0) + { + WriteParametersSignature(module, optionalParameterTypes, parEncoder.StartVarArgs()); + } + return methodSignature; } @@ -106,7 +110,7 @@ private static void WriteParametersSignature(ModuleBuilderImpl module, Type[]? p } } - internal static BlobBuilder PropertySignatureEncoder(PropertyBuilderImpl property, ModuleBuilderImpl module) + internal static BlobBuilder GetPropertySignature(PropertyBuilderImpl property, ModuleBuilderImpl module) { BlobBuilder propertySignature = new(); diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index a5d646811c76d4..7099987d9f80a1 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -3,6 +3,8 @@ using System.Buffers.Binary; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.TestPlatform.TestHost; using Xunit; namespace System.Reflection.Emit.Tests @@ -428,15 +430,15 @@ public void LocalBuilderMultipleLocalsUsage() Assert.Equal(120, BinaryPrimitives.ReadInt32LittleEndian(bodyBytes.AsSpan().Slice(14, 4))); Assert.Equal(0xFE, bodyBytes[18]); // Stloc instruction occupies 2 bytes 0xfe0e Assert.Equal(0x0E, bodyBytes[19]); - Assert.Equal(2, BinaryPrimitives.ReadInt32LittleEndian(bodyBytes.AsSpan().Slice(20, 4))); // index 2 of 'il.Emit(OpCodes.Stloc, 2);' instruction + Assert.Equal(2, BinaryPrimitives.ReadInt32LittleEndian(bodyBytes.AsSpan().Slice(20, 4))); // index 2 of 'il2.Emit(OpCodes.Stloc, 2);' instruction Assert.Equal((byte)OpCodes.Ldloc_2.Value, bodyBytes[24]); Assert.Equal(0xFE, bodyBytes[25]); // Ldloc = 0xfe0c Assert.Equal(0x0C, bodyBytes[26]); - Assert.Equal(0, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(27, 4))); // index 0 of 'il.Emit(OpCodes.Ldloc, 0);' instruction + Assert.Equal(0, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(27, 4))); // index 0 of 'il2.Emit(OpCodes.Ldloc, 0);' instruction Assert.Equal((byte)OpCodes.Add.Value, bodyBytes[31]); Assert.Equal((byte)OpCodes.Stloc_0.Value, bodyBytes[32]); Assert.Equal((byte)OpCodes.Ldloca_S.Value, bodyBytes[33]); - Assert.Equal(0, bodyBytes[34]); // intLocal index is 0 for 'il.Emit(OpCodes.Ldloca, intLocal);' instruction + Assert.Equal(0, bodyBytes[34]); // intLocal index is 0 for 'il2.Emit(OpCodes.Ldloca, intLocal);' instruction Assert.Equal((byte)OpCodes.Ldind_I.Value, bodyBytes[35]); Assert.Equal((byte)OpCodes.Stloc_3.Value, bodyBytes[36]); Assert.Equal((byte)OpCodes.Ldloc_3.Value, bodyBytes[37]); @@ -1486,5 +1488,167 @@ public void DeeperNestedTryCatchFilterFinallyBlocks() Assert.Equal(ExceptionHandlingClauseOptions.Clause, body.ExceptionHandlingClauses[5].Flags); } } + + [Fact] + public void EmitCall_VarArgsMethodInIL() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder mb1 = tb.DefineMethod("VarargMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); + ILGenerator il1 = mb1.GetILGenerator(); + LocalBuilder locAi = il1.DeclareLocal(typeof(ArgIterator)); + LocalBuilder locNext = il1.DeclareLocal(typeof(bool)); + Label labelCheckCondition = il1.DefineLabel(); + Label labelNext = il1.DefineLabel(); + // Load the fixed argument and print it. + il1.Emit(OpCodes.Ldarg_0); + il1.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", [typeof(string)])); + // Load the address of the local variable represented by + // locAi, which will hold the ArgIterator. + il1.Emit(OpCodes.Ldloca_S, locAi); + // Load the address of the argument list, and call the ArgIterator + // constructor that takes an array of runtime argument handles. + il1.Emit(OpCodes.Arglist); + il1.Emit(OpCodes.Call, typeof(ArgIterator).GetConstructor([typeof(RuntimeArgumentHandle)])); + // Enter the loop at the point where the remaining argument + // count is tested. + il1.Emit(OpCodes.Br_S, labelCheckCondition); + // At the top of the loop, call GetNextArg to get the next + // argument from the ArgIterator. Convert the typed reference + // to an object reference and write the object to the console. + il1.MarkLabel(labelNext); + il1.Emit(OpCodes.Ldloca_S, locAi); + il1.Emit(OpCodes.Call, typeof(ArgIterator).GetMethod("GetNextArg", Type.EmptyTypes)); + il1.Emit(OpCodes.Call, typeof(TypedReference).GetMethod("ToObject")); + il1.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", [typeof(object)])); + il1.MarkLabel(labelCheckCondition); + il1.Emit(OpCodes.Ldloca_S, locAi); + il1.Emit(OpCodes.Call, typeof(ArgIterator).GetMethod("GetRemainingCount")); + // If the remaining count is greater than zero, go to + // the top of the loop. + il1.Emit(OpCodes.Ldc_I4_0); + il1.Emit(OpCodes.Cgt); + il1.Emit(OpCodes.Stloc_1); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Brtrue_S, labelNext); + il1.Emit(OpCodes.Ret); + + // Create a method that contains a call to the vararg method. + MethodBuilder mb2 = tb.DefineMethod("CallVarargMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); + ILGenerator il2 = mb2.GetILGenerator(); + // Push arguments on the stack: one for the fixed string + // parameter, and two for the list. + il2.Emit(OpCodes.Ldstr, "Hello "); + il2.Emit(OpCodes.Ldstr, "world "); + il2.Emit(OpCodes.Ldc_I4, 2006); + // Call the vararg method, specifying the types of the + // arguments in the list. + il2.EmitCall(OpCodes.Call, mb1, [typeof(string), typeof(int)]); + il2.Emit(OpCodes.Ret); + Type type = tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarargMethod"); + Assert.Equal(CallingConventions.VarArgs, varargMethodFromDisk.CallingConvention); + ParameterInfo[] parameters = varargMethodFromDisk.GetParameters(); + Assert.Equal(1, parameters.Length); // TODO: how to get the vararg parameter? + } + } + + [Fact] + public void EmitCalli_CallFixedAndVarargMethodsInIL() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodInfo getpid = typeof(AssemblySaveILGeneratorTests).GetMethod("getpid", BindingFlags.Static | BindingFlags.NonPublic); + MethodInfo print = typeof(AssemblySaveILGeneratorTests).GetMethod("Print", BindingFlags.Static | BindingFlags.NonPublic); + MethodBuilder mb2 = tb.DefineMethod("CallingMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); + ILGenerator il2 = mb2.GetILGenerator(); + LocalBuilder local = il2.DeclareLocal(typeof(int)); + il2.EmitWriteLine("Calling native functions"); + il2.Emit(OpCodes.Ldftn, getpid); + il2.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, typeof(int), Type.EmptyTypes); + il2.Emit(OpCodes.Stloc_0); + il2.Emit(OpCodes.Ldstr, "Hello "); + il2.Emit(OpCodes.Ldstr, "world "); + il2.Emit(OpCodes.Ldloc_0); + il2.Emit(OpCodes.Ldftn, print); + il2.EmitCalli(OpCodes.Calli, CallingConventions.VarArgs, typeof(void), [typeof(string)], [typeof(string), typeof(int)]); + il2.Emit(OpCodes.Ret); + Type type = tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarargMethod"); + } + } + + [DllImport("libSystem.dylib")] + private static extern int getpid(); + + internal static void Print(string format, params object[] args) + { + Console.WriteLine(format, args); + } + + [Fact] + public void Emit_CallBySignature() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder mb1 = tb.DefineMethod("VarargMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); + ILGenerator il1 = mb1.GetILGenerator(); + LocalBuilder locAi = il1.DeclareLocal(typeof(ArgIterator)); + LocalBuilder locNext = il1.DeclareLocal(typeof(bool)); + Label labelCheckCondition = il1.DefineLabel(); + Label labelNext = il1.DefineLabel(); + il1.Emit(OpCodes.Ldarg_0); + il1.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", [typeof(string)])); + il1.Emit(OpCodes.Ldloca_S, locAi); + il1.Emit(OpCodes.Arglist); + il1.Emit(OpCodes.Call, typeof(ArgIterator).GetConstructor([typeof(RuntimeArgumentHandle)])); + il1.Emit(OpCodes.Br_S, labelCheckCondition); + il1.MarkLabel(labelNext); + il1.Emit(OpCodes.Ldloca_S, locAi); + il1.Emit(OpCodes.Call, typeof(ArgIterator).GetMethod("GetNextArg", Type.EmptyTypes)); + il1.Emit(OpCodes.Call, typeof(TypedReference).GetMethod("ToObject")); + il1.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", [typeof(object)])); + il1.MarkLabel(labelCheckCondition); + il1.Emit(OpCodes.Ldloca_S, locAi); + il1.Emit(OpCodes.Call, typeof(ArgIterator).GetMethod("GetRemainingCount")); + il1.Emit(OpCodes.Ldc_I4_0); + il1.Emit(OpCodes.Cgt); + il1.Emit(OpCodes.Stloc_1); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Brtrue_S, labelNext); + il1.Emit(OpCodes.Ret); + + MethodBuilder mb2 = tb.DefineMethod("CallingMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); + ILGenerator il2 = mb2.GetILGenerator(); + il2.Emit(OpCodes.Ldstr, "Hello "); + il2.Emit(OpCodes.Ldstr, "world "); + il2.Emit(OpCodes.Ldc_I4, 2024); + il2.Emit(OpCodes.Ldftn, mb1); + SignatureHelper signature = SignatureHelper.GetMethodSigHelper(CallingConventions.VarArgs, typeof(void)); + signature.AddArgument(typeof(string)); + signature.AddSentinel(); + signature.AddArgument(typeof(string)); + signature.AddArgument(typeof(int)); + il2.Emit(OpCodes.Calli, signature); + il2.Emit(OpCodes.Ret); + Type type = tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarargMethod"); + } + } } } From 862bbbe0a48b2788482d6f0144897cdc02b39cd2 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 27 Dec 2023 12:14:20 -0800 Subject: [PATCH 2/4] Fix Assembly.LoadFrom(...) issue, add more tests --- .../Reflection/Emit/AssemblyBuilderImpl.cs | 15 +- .../System/Reflection/Emit/ILGeneratorImpl.cs | 13 +- .../AssemblySaveILGeneratorTests.cs | 453 ++++++++++++++++-- 3 files changed, 430 insertions(+), 51 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs index 2048b14fff0f1d..aed4d7efb3c1d9 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs @@ -40,20 +40,23 @@ internal AssemblyBuilderImpl(AssemblyName name, Assembly coreAssembly, IEnumerab } } - internal static AssemblyBuilderImpl DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, IEnumerable? assemblyAttributes) + internal static AssemblyBuilderImpl DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, + IEnumerable? assemblyAttributes) => new AssemblyBuilderImpl(name, coreAssembly, assemblyAttributes); private void WritePEImage(Stream peStream, BlobBuilder ilBuilder) { // Create executable with the managed metadata from the specified MetadataBuilder. var peHeaderBuilder = new PEHeaderBuilder( - imageCharacteristics: Characteristics.Dll // Start off with a simple DLL - ); + // When only Characteristics.Dll is set .NET runtime throws when try to load generated assembly, + // had add Characteristics.ExecutableImage too. + imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll); var peBuilder = new ManagedPEBuilder( - peHeaderBuilder, - new MetadataRootBuilder(_metadataBuilder), - ilBuilder); + header: peHeaderBuilder, + metadataRootBuilder: new MetadataRootBuilder(_metadataBuilder), + ilStream: ilBuilder, + strongNameSignatureSize: 0); // Write executable into the specified stream. var peBlob = new BlobBuilder(); diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index c96a0a8c34e16e..ba4e47eb800504 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -463,12 +463,11 @@ public override void Emit(OpCode opcode, SignatureHelper signature) EmitOpcode(opcode); // The only IL instruction that has VarPop behaviour, that takes a Signature - // token as a parameter is Calli. Pop the parameters and the native function - // pointer. Used reflection since ArgumentCount property is not public. + // token as a parameter is Calli. Pop the parameters and the native function pointer. if (opcode.StackBehaviourPop == StackBehaviour.Varpop) { Debug.Assert(opcode.Equals(OpCodes.Calli), "Unexpected opcode encountered for StackBehaviour VarPop."); - // Pop the arguments. + // Pop the arguments. Used reflection since ArgumentCount property is not public. PropertyInfo argCountProperty = typeof(SignatureHelper).GetProperty("ArgumentCount", BindingFlags.NonPublic | BindingFlags.Instance)!; int stackChange = -(int)argCountProperty.GetValue(signature)!; // Pop native function pointer off the stack. @@ -570,7 +569,7 @@ private static int GetStackChange(OpCode opcode, MethodInfo methodInfo, Type[]? public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) { - if (optionalParameterTypes != null) + if (optionalParameterTypes != null && optionalParameterTypes.Length > 0) { if ((callingConvention & CallingConventions.VarArgs) == 0) { @@ -581,7 +580,7 @@ public override void EmitCalli(OpCode opcode, CallingConventions callingConventi int stackChange = GetStackChange(returnType, parameterTypes); - // Pop off vararg arguments. + // Pop off VarArg arguments. if (optionalParameterTypes != null) { stackChange -= optionalParameterTypes.Length; @@ -657,7 +656,7 @@ public override void EndExceptionBlock() public override void EndScope() { - // TODO: + // TODO: No-op, will be implemented wit PDB support } public override void MarkLabel(Label loc) @@ -674,7 +673,7 @@ public override void MarkLabel(Label loc) public override void UsingNamespace(string usingNamespace) { - // TODO: looks does nothing + // TODO: No-op, will be implemented wit PDB support } } diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 7099987d9f80a1..27191b7f12f439 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using Microsoft.VisualStudio.TestPlatform.TestHost; using Xunit; namespace System.Reflection.Emit.Tests @@ -243,7 +242,7 @@ public void ILMaxStack_Test() } } - private MethodInfo LoadILGenerator_GetMaxStackSizeMethod() + private static MethodInfo LoadILGenerator_GetMaxStackSizeMethod() { Type ilgType = Type.GetType("System.Reflection.Emit.ILGeneratorImpl, System.Reflection.Emit", throwOnError: true)!; return ilgType.GetMethod("GetMaxStackSize", BindingFlags.NonPublic | BindingFlags.Instance, Type.EmptyTypes); @@ -430,15 +429,15 @@ public void LocalBuilderMultipleLocalsUsage() Assert.Equal(120, BinaryPrimitives.ReadInt32LittleEndian(bodyBytes.AsSpan().Slice(14, 4))); Assert.Equal(0xFE, bodyBytes[18]); // Stloc instruction occupies 2 bytes 0xfe0e Assert.Equal(0x0E, bodyBytes[19]); - Assert.Equal(2, BinaryPrimitives.ReadInt32LittleEndian(bodyBytes.AsSpan().Slice(20, 4))); // index 2 of 'il2.Emit(OpCodes.Stloc, 2);' instruction + Assert.Equal(2, BinaryPrimitives.ReadInt32LittleEndian(bodyBytes.AsSpan().Slice(20, 4))); // index 2 of 'il.Emit(OpCodes.Stloc, 2);' instruction Assert.Equal((byte)OpCodes.Ldloc_2.Value, bodyBytes[24]); Assert.Equal(0xFE, bodyBytes[25]); // Ldloc = 0xfe0c Assert.Equal(0x0C, bodyBytes[26]); - Assert.Equal(0, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(27, 4))); // index 0 of 'il2.Emit(OpCodes.Ldloc, 0);' instruction + Assert.Equal(0, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(27, 4))); // index 0 of 'il.Emit(OpCodes.Ldloc, 0);' instruction Assert.Equal((byte)OpCodes.Add.Value, bodyBytes[31]); Assert.Equal((byte)OpCodes.Stloc_0.Value, bodyBytes[32]); Assert.Equal((byte)OpCodes.Ldloca_S.Value, bodyBytes[33]); - Assert.Equal(0, bodyBytes[34]); // intLocal index is 0 for 'il2.Emit(OpCodes.Ldloca, intLocal);' instruction + Assert.Equal(0, bodyBytes[34]); // intLocal index is 0 for 'il.Emit(OpCodes.Ldloca, intLocal);' instruction Assert.Equal((byte)OpCodes.Ldind_I.Value, bodyBytes[35]); Assert.Equal((byte)OpCodes.Stloc_3.Value, bodyBytes[36]); Assert.Equal((byte)OpCodes.Ldloc_3.Value, bodyBytes[37]); @@ -542,9 +541,9 @@ public void LocalBuilderExceptions() LocalBuilder stringLocal = il.DeclareLocal(typeof(string)); LocalBuilder nullBuilder = null; - Assert.Throws(() => il.DeclareLocal(null!)); - Assert.Throws(() => il.Emit(OpCodes.Ldloc, nullBuilder)); - Assert.Throws(() => anotherIL.Emit(OpCodes.Ldloc, stringLocal)); + Assert.Throws("localType", () => il.DeclareLocal(null!)); + Assert.Throws("local", () => il.Emit(OpCodes.Ldloc, nullBuilder)); + Assert.Throws("local", () => anotherIL.Emit(OpCodes.Ldloc, stringLocal)); } [Fact] @@ -943,21 +942,24 @@ public void MemberReferenceExceptions() FieldInfo nullField = null; Label[] nullArray = null; Type nullType = null; - - Assert.Throws(() => il.Emit(OpCodes.Call, nullMethod)); - Assert.Throws(() => il.Emit(OpCodes.Callvirt, nullConstructor)); - Assert.Throws(() => il.Emit(OpCodes.Ldfld, nullField)); - Assert.Throws(() => il.Emit(OpCodes.Switch, nullArray)); - Assert.Throws(() => il.Emit(OpCodes.Switch, nullType)); - Assert.Throws(() => il.EmitCall(OpCodes.Call, nullMethod, null)); + SignatureHelper signature = null; + + Assert.Throws("meth", () => il.Emit(OpCodes.Call, nullMethod)); + Assert.Throws("con", () => il.Emit(OpCodes.Callvirt, nullConstructor)); + Assert.Throws("field", () => il.Emit(OpCodes.Ldfld, nullField)); + Assert.Throws("labels", () => il.Emit(OpCodes.Switch, nullArray)); + Assert.Throws("cls", () => il.Emit(OpCodes.Switch, nullType)); + Assert.Throws("methodInfo", () => il.EmitCall(OpCodes.Call, nullMethod, null)); // only OpCodes.Switch expected - Assert.Throws(() => il.Emit(OpCodes.Call, new Label[0])); + Assert.Throws("opcode", () => il.Emit(OpCodes.Call, new Label[0])); // only OpCodes.Call or .OpCodes.Callvirt or OpCodes.Newob expected - Assert.Throws(() => il.Emit(OpCodes.Switch, typeof(object).GetConstructor(Type.EmptyTypes))); + Assert.Throws("opcode", () => il.Emit(OpCodes.Switch, typeof(object).GetConstructor(Type.EmptyTypes))); // Undefined label Assert.Throws(() => il.MarkLabel(new Label())); // only OpCodes.Call or OpCodes.Callvirt or OpCodes.Newob expected - Assert.Throws(() => il.EmitCall(OpCodes.Ldfld, method, null)); + Assert.Throws("opcode", () => il.EmitCall(OpCodes.Ldfld, method, null)); + Assert.Throws("signature", () => il.Emit(OpCodes.Calli, signature)); + Assert.Throws(() => il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, null, null, [typeof(string)])); } [Fact] @@ -1489,13 +1491,120 @@ public void DeeperNestedTryCatchFilterFinallyBlocks() } } + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int Int32SumStdCall(int a, int b); + + private static int Int32Sum(int a, int b) => a + b; + + [Fact] + public void TestEmitCalliBlittable() + { + int a = 1, b = 1, result = 2; + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + Type returnType = typeof(int); + MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); + methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, returnType, [typeof(int), typeof(int)]); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + var del = new Int32SumStdCall(Int32Sum); + IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); + object resultValue = typeFromDisk + .GetMethod("F", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, [funcPtr, a, b]); + GC.KeepAlive(del); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + } + + [Fact] + public void TestEmitCalliManagedBlittable() + { + int a = 1, b = 1, result = 2; + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + Type returnType = typeof(int); + MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); + methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); + MethodInfo method = typeof(AssemblySaveILGeneratorTests).GetMethod(nameof(Int32Sum), BindingFlags.NonPublic | BindingFlags.Static)!; + IntPtr funcPtr = method.MethodHandle.GetFunctionPointer(); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, returnType, [typeof(int), typeof(int)], null); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + object resultValue = typeFromDisk + .GetMethod("F", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, [funcPtr, a, b]); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate string StringReverseCdecl(string a); + + private static string StringReverse(string a) => string.Join("", a.Reverse()); + + [Fact] + public void TestEmitCalliNonBlittable() + { + string input = "Test string!", result = "!gnirts tseT"; + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + Type returnType = typeof(string); + MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(string)]); + methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(OpCodes.Calli, CallingConvention.Cdecl, returnType, [typeof(string)]); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + var del = new StringReverseCdecl(StringReverse); + IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); + object resultValue = typeFromDisk + .GetMethod("F", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, [funcPtr, input]); + GC.KeepAlive(del); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + } + [Fact] public void EmitCall_VarArgsMethodInIL() { using (TempFile file = TempFile.Create()) { AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); - MethodBuilder mb1 = tb.DefineMethod("VarargMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); + MethodBuilder mb1 = tb.DefineMethod("VarArgMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); ILGenerator il1 = mb1.GetILGenerator(); LocalBuilder locAi = il1.DeclareLocal(typeof(ArgIterator)); LocalBuilder locNext = il1.DeclareLocal(typeof(bool)); @@ -1535,7 +1644,7 @@ public void EmitCall_VarArgsMethodInIL() il1.Emit(OpCodes.Ret); // Create a method that contains a call to the vararg method. - MethodBuilder mb2 = tb.DefineMethod("CallVarargMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); + MethodBuilder mb2 = tb.DefineMethod("CallVarArgMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); ILGenerator il2 = mb2.GetILGenerator(); // Push arguments on the stack: one for the fixed string // parameter, and two for the list. @@ -1551,7 +1660,7 @@ public void EmitCall_VarArgsMethodInIL() Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); - MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarargMethod"); + MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarArgMethod"); Assert.Equal(CallingConventions.VarArgs, varargMethodFromDisk.CallingConvention); ParameterInfo[] parameters = varargMethodFromDisk.GetParameters(); Assert.Equal(1, parameters.Length); // TODO: how to get the vararg parameter? @@ -1559,32 +1668,32 @@ public void EmitCall_VarArgsMethodInIL() } [Fact] - public void EmitCalli_CallFixedAndVarargMethodsInIL() + public void EmitCalli_CallFixedAndVarArgMethodsInIL() { using (TempFile file = TempFile.Create()) { AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); MethodInfo getpid = typeof(AssemblySaveILGeneratorTests).GetMethod("getpid", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo print = typeof(AssemblySaveILGeneratorTests).GetMethod("Print", BindingFlags.Static | BindingFlags.NonPublic); - MethodBuilder mb2 = tb.DefineMethod("CallingMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); - ILGenerator il2 = mb2.GetILGenerator(); - LocalBuilder local = il2.DeclareLocal(typeof(int)); - il2.EmitWriteLine("Calling native functions"); - il2.Emit(OpCodes.Ldftn, getpid); - il2.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, typeof(int), Type.EmptyTypes); - il2.Emit(OpCodes.Stloc_0); - il2.Emit(OpCodes.Ldstr, "Hello "); - il2.Emit(OpCodes.Ldstr, "world "); - il2.Emit(OpCodes.Ldloc_0); - il2.Emit(OpCodes.Ldftn, print); - il2.EmitCalli(OpCodes.Calli, CallingConventions.VarArgs, typeof(void), [typeof(string)], [typeof(string), typeof(int)]); - il2.Emit(OpCodes.Ret); + MethodBuilder mb = tb.DefineMethod("CallingMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); + ILGenerator il = mb.GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + il.EmitWriteLine("Calling native functions"); + il.Emit(OpCodes.Ldftn, getpid); + il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, typeof(int), Type.EmptyTypes); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldstr, "Hello "); + il.Emit(OpCodes.Ldstr, "world "); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldftn, print); + il.EmitCalli(OpCodes.Calli, CallingConventions.VarArgs, typeof(void), [typeof(string)], [typeof(string), typeof(int)]); + il.Emit(OpCodes.Ret); Type type = tb.CreateType(); saveMethod.Invoke(ab, [file.Path]); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); - MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarargMethod"); + MethodInfo methodFromDisk = typeFromDisk.GetMethod("CallingMethod"); } } @@ -1602,7 +1711,7 @@ public void Emit_CallBySignature() using (TempFile file = TempFile.Create()) { AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); - MethodBuilder mb1 = tb.DefineMethod("VarargMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); + MethodBuilder mb1 = tb.DefineMethod("VarArgMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); ILGenerator il1 = mb1.GetILGenerator(); LocalBuilder locAi = il1.DeclareLocal(typeof(ArgIterator)); LocalBuilder locNext = il1.DeclareLocal(typeof(bool)); @@ -1647,7 +1756,275 @@ public void Emit_CallBySignature() Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); - MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarargMethod"); + MethodInfo varArgMethodFromDisk = typeFromDisk.GetMethod("VarArgMethod"); + MethodInfo methodFromDisk = typeFromDisk.GetMethod("CallingMethod"); + } + } + + [Fact] + public void MaxStackOverflowTest() + { + GetCode(1 << 5); + + // Previously this threw because the computed stack depth was 2^16 + 1, which is 1 mod 2^16 + // and 1 is too small. + GetCode(1 << 14); + + /// + /// The parameter is the number of basic blocks. Each has a max stack + /// depth of four. There is one final basic block with max stack of one. The ILGenerator + /// erroneously adds these, so the final value can overflow 2^16. When that result mod 2^16 + /// is less than required, the CLR throws an . + /// + static void GetCode(int num) + { + AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _); + MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); + var ilg = method.GetILGenerator(); + + var loc = ilg.DeclareLocal(typeof(int)); + ilg.Emit(OpCodes.Ldc_I4_0); + ilg.Emit(OpCodes.Stloc, loc); + + for (int i = 0; i < num; i++) + { + ilg.Emit(OpCodes.Ldloc, loc); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_2); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Stloc, loc); + + // Unconditional jump to next block. + var labNext = ilg.DefineLabel(); + ilg.Emit(OpCodes.Br, labNext); + ilg.MarkLabel(labNext); + } + + ilg.Emit(OpCodes.Ldloc, loc); + ilg.Emit(OpCodes.Ret); + + type.CreateTypeInfo(); + MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); + Assert.Equal(4, getMaxStackSizeMethod.Invoke(ilg, [])); + } + } + + [Fact] + public void MaxStackNonEmptyForward() + { + // This test uses forward branches to "new" basic blocks where the stack depth + // at the branch location is non-empty. + + GetCode(1 << 0); + GetCode(1 << 1); + GetCode(1 << 5); + + static void GetCode(int num) + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); + MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); + var ilg = method.GetILGenerator(); + + ilg.Emit(OpCodes.Ldc_I4_0); + for (int i = 0; i < num; i++) + { + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + + // Unconditional jump to next block. + var labNext = ilg.DefineLabel(); + ilg.Emit(OpCodes.Br, labNext); + ilg.MarkLabel(labNext); + } + + // Each block leaves two values on the stack. Add them into the previous value. + for (int i = 0; i < num; i++) + { + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + } + + ilg.Emit(OpCodes.Ret); + + type.CreateTypeInfo(); + MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); + Assert.Equal(2 * num + 3, getMaxStackSizeMethod.Invoke(ilg, [])); + + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + } + } + } + + [Fact] + public void MaxStackNonEmptyBackward() + { + // This test uses backward branches to "new" basic blocks where the stack depth + // at the branch location is non-empty. + + GetCode(1 << 1); + GetCode(1 << 2); + GetCode(1 << 3); + GetCode(1 << 4); + GetCode(1 << 5); + + static void GetCode(int num) + { + AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _); + MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); + var ilg = method.GetILGenerator(); + + var labels = new Label[num + 1]; + for (int i = 0; i <= num; i++) + labels[i] = ilg.DefineLabel(); + + ilg.Emit(OpCodes.Ldc_I4_0); + ilg.Emit(OpCodes.Br, labels[0]); + + for (int i = num; --i >= 0;) + { + ilg.MarkLabel(labels[i]); + + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + + // Unconditional jump to "next" block (which is really before this code). + ilg.Emit(OpCodes.Br, labels[i + 1]); + } + + ilg.MarkLabel(labels[num]); + + // Each block leaves two values on the stack. Add them into the previous value. + for (int i = 0; i < num; i++) + { + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + } + + ilg.Emit(OpCodes.Ret); + + type.CreateTypeInfo(); + MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); + // It was (4 * num + 2) in the original test, not sure why old ILGenerator calculation produced this, + // but there should be no difference between forward and backward loops max stack calculation + Assert.Equal(2 * num + 3, getMaxStackSizeMethod.Invoke(ilg, [])); + } + } + + [Fact] + public void AmbiguousDepth() + { + GetCode(); + + static void GetCode() + { + AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _); + MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new[] { typeof(bool) }); + var ilg = method.GetILGenerator(); + + // The label is targeted with stack depth zero. + var lab = ilg.DefineLabel(); + ilg.Emit(OpCodes.Ldarg_0); + ilg.Emit(OpCodes.Brfalse, lab); + + // The label is marked with a larger stack depth, one. This IL is invalid. + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.MarkLabel(lab); + + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Ret); + + type.CreateTypeInfo(); + MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); + Assert.Equal(2, getMaxStackSizeMethod.Invoke(ilg, [])); // TODO:it was 2 + 1, + 1 was for fixup + } + } + + [Fact] + public void UnreachableDepth() + { + GetCode(); + + static void GetCode() + { + AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _); + MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); + var ilg = method.GetILGenerator(); + + var lab = ilg.DefineLabel(); + + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Br, lab); + + // Unreachable. + ilg.Emit(OpCodes.Ldarg_0); + + // Depth + ilg.MarkLabel(lab); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Ret); + + type.CreateTypeInfo(); + MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); + Assert.Equal(2, getMaxStackSizeMethod.Invoke(ilg, [])); // TODO: was 3 + } + } + + [Fact] + public void SimpleForLoopTest() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder mb2 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); + ILGenerator il = mb2.GetILGenerator(); + LocalBuilder sum = il.DeclareLocal(typeof(int)); + LocalBuilder i = il.DeclareLocal(typeof(int)); + Label loopEnd = il.DefineLabel(); + Label loopStart = il.DefineLabel(); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Stloc_1); + il.MarkLabel(loopStart); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Bgt, loopEnd); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Br, loopStart); + il.MarkLabel(loopEnd); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + MethodInfo sumMethodFromDisk = typeFromDisk.GetMethod("SumMethod"); + Assert.Equal(55, sumMethodFromDisk.Invoke(null, [10])); } } } From 0ba232e70e824474baa10e29350bd5c5552a3f85 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Fri, 29 Dec 2023 15:18:28 -0800 Subject: [PATCH 3/4] Calculate max stack correctly when branching, add more tests --- .../src/Resources/Strings.resx | 3 + .../Reflection/Emit/AssemblyBuilderImpl.cs | 5 +- .../System/Reflection/Emit/ILGeneratorImpl.cs | 187 +++++-- .../Reflection/Emit/ModuleBuilderImpl.cs | 2 +- .../AssemblySaveILGeneratorTests.cs | 484 ++++++++++++------ .../AssemblySaveTools.cs | 5 +- 6 files changed, 475 insertions(+), 211 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index 70322d32da5368..d8c595b9878ed0 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -264,4 +264,7 @@ Method '{0}' does not have a method body. + + Label multiply defined. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs index 370327a8d9f9de..2e829ddfa57433 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs @@ -46,10 +46,9 @@ internal static AssemblyBuilderImpl DefinePersistedAssembly(AssemblyName name, A private void WritePEImage(Stream peStream, BlobBuilder ilBuilder) { - // Create executable with the managed metadata from the specified MetadataBuilder. var peHeaderBuilder = new PEHeaderBuilder( - // When only Characteristics.Dll is set .NET runtime throws when try to load generated assembly, - // had add Characteristics.ExecutableImage too. + // When only Characteristics.Dll is set and try to load the generated assembly from file, + // runtime throws BadImageFormatException, had to add Characteristics.ExecutableImage. imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll); var peBuilder = new ManagedPEBuilder( diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index ba4e47eb800504..f78b3962d77824 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -18,10 +18,14 @@ internal sealed class ILGeneratorImpl : ILGenerator private readonly InstructionEncoder _il; private readonly ControlFlowBuilder _cfBuilder; private bool _hasDynamicStackAllocation; - private int _maxStackSize; - private int _currentStack; + private int _maxStackDepth; + private int _currentStackDepth; // Current stack labelStartDepth + private int _targetDepth; // Stack labelStartDepth at a target of the previous instruction (when it is branching) + // Adjustment to add to _maxStackDepth for incorrect/invalid IL. For example, when branch + // instructions branches backward with non zero stack depths targeting the same label. + private int _depthAdjustment; private List _locals = new(); - private Dictionary _labelTable = new(2); + private Dictionary _labelTable = new(2); private List> _memberReferences = new(); private List _exceptionStack = new(); @@ -35,7 +39,7 @@ internal ILGeneratorImpl(MethodBuilderImpl methodBuilder, int size) _il = new InstructionEncoder(_builder, _cfBuilder); } - internal int GetMaxStackSize() => _maxStackSize; + internal int GetMaxStack() => Math.Min(ushort.MaxValue, _maxStackDepth + _depthAdjustment); internal List> GetMemberReferences() => _memberReferences; internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; @@ -78,14 +82,18 @@ public override void BeginCatchBlock(Type? exceptionType) currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = DefineLabel(); - _cfBuilder.AddCatchRegion(_labelTable[currentExBlock.TryStart], _labelTable[currentExBlock.TryEnd], - _labelTable[currentExBlock.HandleStart], _labelTable[currentExBlock.HandleEnd], _moduleBuilder.GetTypeHandle(exceptionType)); + _cfBuilder.AddCatchRegion(GetMetaLabel(currentExBlock.TryStart), GetMetaLabel(currentExBlock.TryEnd), + GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd), _moduleBuilder.GetTypeHandle(exceptionType)); MarkLabel(currentExBlock.HandleStart); } + // Stack depth for "catch" starts at one. + _currentStackDepth = 1; currentExBlock.State = ExceptionState.Catch; } + private LabelHandle GetMetaLabel(Label label) => _labelTable[label]._metaLabel; + public override void BeginExceptFilterBlock() { if (_exceptionStack.Count < 1) @@ -107,10 +115,12 @@ public override void BeginExceptFilterBlock() currentExBlock.FilterStart = DefineLabel(); currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = DefineLabel(); - _cfBuilder.AddFilterRegion(_labelTable[currentExBlock.TryStart], _labelTable[currentExBlock.TryEnd], - _labelTable[currentExBlock.HandleStart], _labelTable[currentExBlock.HandleEnd], _labelTable[currentExBlock.FilterStart]); + _cfBuilder.AddFilterRegion(GetMetaLabel(currentExBlock.TryStart), GetMetaLabel(currentExBlock.TryEnd), + GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd), GetMetaLabel(currentExBlock.FilterStart)); currentExBlock.State = ExceptionState.Filter; MarkLabel(currentExBlock.FilterStart); + // Stack depth for "filter" starts at one. + _currentStackDepth = 1; } public override Label BeginExceptionBlock() @@ -122,6 +132,8 @@ public override Label BeginExceptionBlock() MarkLabel(currentExBlock.TryStart); currentExBlock.State = ExceptionState.Try; _exceptionStack.Add(currentExBlock); + // Stack depth for "try" starts at zero. + _currentStackDepth = 0; return currentExBlock.EndLabel; } @@ -145,10 +157,12 @@ public override void BeginFaultBlock() currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = DefineLabel(); - _cfBuilder.AddFaultRegion(_labelTable[currentExBlock.TryStart], _labelTable[currentExBlock.TryEnd], - _labelTable[currentExBlock.HandleStart], _labelTable[currentExBlock.HandleEnd]); + _cfBuilder.AddFaultRegion(GetMetaLabel(currentExBlock.TryStart), GetMetaLabel(currentExBlock.TryEnd), + GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd)); currentExBlock.State = ExceptionState.Fault; MarkLabel(currentExBlock.HandleStart); + // Stack depth for "fault" starts at zero. + _currentStackDepth = 0; } public override void BeginFinallyBlock() @@ -174,13 +188,18 @@ public override void BeginFinallyBlock() MarkLabel(currentExBlock.TryEnd); currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = finallyEndLabel; - _cfBuilder.AddFinallyRegion(_labelTable[currentExBlock.TryStart], _labelTable[currentExBlock.TryEnd], - _labelTable[currentExBlock.HandleStart], _labelTable[currentExBlock.HandleEnd]); + _cfBuilder.AddFinallyRegion(GetMetaLabel(currentExBlock.TryStart), GetMetaLabel(currentExBlock.TryEnd), + GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd)); currentExBlock.State = ExceptionState.Finally; MarkLabel(currentExBlock.HandleStart); + // Stack depth for "finally" starts at zero. + _currentStackDepth = 0; } - public override void BeginScope() => throw new NotImplementedException(); + public override void BeginScope() + { + // TODO: No-op, will be implemented wit PDB support + } public override LocalBuilder DeclareLocal(Type localType, bool pinned) { @@ -196,19 +215,29 @@ public override Label DefineLabel() { LabelHandle metadataLabel = _il.DefineLabel(); Label emitLabel = CreateLabel(metadataLabel.Id); - _labelTable.Add(emitLabel, metadataLabel); + _labelTable.Add(emitLabel, new LabelInfo(metadataLabel)); return emitLabel; } + private void UpdateStackSize(OpCode opCode) { - _currentStack += opCode.EvaluationStackDelta; - _maxStackSize = Math.Max(_maxStackSize, _currentStack); + UpdateStackSize(opCode.EvaluationStackDelta); + + if (UnconditionalJump(opCode)) + { + _currentStackDepth = 0; + } } + private static bool UnconditionalJump(OpCode opCode) => + opCode.FlowControl == FlowControl.Throw || opCode.FlowControl == FlowControl.Return || opCode == OpCodes.Jmp; + private void UpdateStackSize(int stackChange) { - _currentStack += stackChange; - _maxStackSize = Math.Max(_maxStackSize, _currentStack); + _currentStackDepth += stackChange; + _maxStackDepth = Math.Max(_maxStackDepth, _currentStackDepth); + // Record the "target" stack depth at this instruction. + _targetDepth = _currentStackDepth; } public void EmitOpcode(OpCode opcode) @@ -360,22 +389,31 @@ public override void Emit(OpCode opcode, ConstructorInfo con) } int stackChange = 0; - // Push the return value - stackChange++; - // Pop the parameters. - if (con is ConstructorBuilderImpl builder) - { - stackChange -= builder._methodBuilder.ParameterCount; - } - else + if (opcode.StackBehaviourPush == StackBehaviour.Varpush) { - stackChange -= con.GetParameters().Length; + // Instruction must be one of call or callvirt. + Debug.Assert(opcode.Equals(OpCodes.Call) || + opcode.Equals(OpCodes.Callvirt), + "Unexpected opcode encountered for StackBehaviour of VarPush."); + stackChange++; } - // Pop the this parameter if the constructor is non-static and the - // instruction is not newobj. - if (!con.IsStatic && !opcode.Equals(OpCodes.Newobj)) + + if (opcode.StackBehaviourPop == StackBehaviour.Varpop) { - stackChange--; + // Instruction must be one of call, callvirt or newobj. + Debug.Assert(opcode.Equals(OpCodes.Call) || + opcode.Equals(OpCodes.Callvirt) || + opcode.Equals(OpCodes.Newobj), + "Unexpected opcode encountered for StackBehaviour of VarPop."); + + if (con is ConstructorBuilderImpl builder) + { + stackChange -= builder._methodBuilder.ParameterCount; + } + else + { + stackChange -= con.GetParameters().Length; + } } EmitOpcode(opcode); @@ -398,12 +436,43 @@ private void WriteOrReserveToken(int token, object member) } } + private void AdjustDepth(OpCode opcode, LabelInfo label) + { + int labelStartDepth = label._startDepth; + int targetDepth = _targetDepth; + Debug.Assert(labelStartDepth >= -1); + Debug.Assert(targetDepth >= -1); + if (labelStartDepth < targetDepth) + { + // Either unknown depth for this label or this branch location has a larger depth than previously recorded. + // In the latter case, the IL is (likely) invalid, but we just compensate for it using _depthAdjustment. + if (labelStartDepth >= 0) + { + _depthAdjustment += targetDepth - labelStartDepth; + } + + // Keep the target depth, it will used as starting stack size from the marked location. + label._startDepth = targetDepth; + } + + // If it is unconditionally branching to a new location, for the next instruction invocation stack should be empty. + // if this location is marked with a label, the starting stack size will be adjusted with label._startDepth. + if (UnconditionalBranching(opcode)) + { + _currentStackDepth = 0; + } + } + + private static bool UnconditionalBranching(OpCode opcode) => + opcode.FlowControl == FlowControl.Branch; + public override void Emit(OpCode opcode, Label label) { - if (_labelTable.TryGetValue(label, out LabelHandle labelHandle)) + if (_labelTable.TryGetValue(label, out LabelInfo? labelInfo)) { - _il.Branch((ILOpCode)opcode.Value, labelHandle); + _il.Branch((ILOpCode)opcode.Value, labelInfo._metaLabel); UpdateStackSize(opcode); + AdjustDepth(opcode, labelInfo); } else { @@ -425,7 +494,9 @@ public override void Emit(OpCode opcode, Label[] labels) foreach (Label label in labels) { - switchEncoder.Branch(_labelTable[label]); + LabelInfo labelInfo = _labelTable[label]; + switchEncoder.Branch(labelInfo._metaLabel); + AdjustDepth(opcode, labelInfo); } } @@ -661,9 +732,40 @@ public override void EndScope() public override void MarkLabel(Label loc) { - if (_labelTable.TryGetValue(loc, out LabelHandle labelHandle)) + if (_labelTable.TryGetValue(loc, out LabelInfo? labelInfo)) { - _il.MarkLabel(labelHandle); + if (labelInfo._position != -1) + { + throw new ArgumentException(SR.Argument_RedefinedLabel); + } + + _il.MarkLabel(labelInfo._metaLabel); + labelInfo._position = _il.Offset; + int depth = labelInfo._startDepth; + if (depth < 0) + { + // Unknown start depth for this label, indicating that it hasn't been used yet. + // Or we're in the Backward branch constraint case mentioned in ECMA-335 III.1.7.5. + // But the constraint is not enforced by any mainstream .NET runtime and they are not + // respected by .NET compilers. The _depthAdjustment field will compensate for violations + // of this constraint, as we discover them, check AdjustDepth method for detail. Here + // we assume a depth of zero. If a (later) branch to this label has a positive stack + // depth, we'll record that as the new depth and add the delta into _depthAdjustment. + labelInfo._startDepth = _currentStackDepth; + } + else if (depth < _currentStackDepth) + { + // A branch location with smaller stack targets this label. In this case, the IL is invalid + // but we just compensate for it. + _depthAdjustment += _currentStackDepth - depth; + labelInfo._startDepth = _currentStackDepth; + } + else if (depth > _currentStackDepth) + { + // A branch location with larger stack depth targets this label, can be invalid IL. + // Either case adjust the current stack depth. + _currentStackDepth = depth; + } } else { @@ -698,4 +800,17 @@ internal enum ExceptionState Fault, Done } + + internal sealed class LabelInfo + { + internal LabelInfo(LabelHandle metaLabel) + { + _position = -1; + _startDepth = -1; + _metaLabel = metaLabel; + } + internal int _position; // Position in the il stream, with -1 meaning unknown. + internal int _startDepth; // Stack labelStartDepth, with -1 meaning unknown. + internal LabelHandle _metaLabel; + } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index 200055766ff244..f3ff1297352729 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -409,7 +409,7 @@ private void FillMemberReferences(ILGeneratorImpl il) private static int AddMethodBody(MethodBuilderImpl method, ILGeneratorImpl il, StandaloneSignatureHandle signature, MethodBodyStreamEncoder bodyEncoder) => bodyEncoder.AddMethodBody( instructionEncoder: il.Instructions, - maxStack: il.GetMaxStackSize(), + maxStack: il.GetMaxStack(), localVariablesSignature: signature, attributes: method.InitLocals ? MethodBodyAttributes.InitLocals : MethodBodyAttributes.None, hasDynamicStackAllocation: il.HasDynamicStackAllocation); diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 27191b7f12f439..c47afe5778029e 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Binary; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -229,9 +230,9 @@ public void ILMaxStack_Test() type.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(9, getMaxStackSizeMethod.Invoke(il1, new object[0])); - Assert.Equal(3, getMaxStackSizeMethod.Invoke(il2, new object[0])); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(9, getMaxStackMethod.Invoke(il1, null)); + Assert.Equal(3, getMaxStackMethod.Invoke(il2, null)); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); @@ -242,10 +243,17 @@ public void ILMaxStack_Test() } } - private static MethodInfo LoadILGenerator_GetMaxStackSizeMethod() + private static MethodInfo GetMaxStackMethod() { Type ilgType = Type.GetType("System.Reflection.Emit.ILGeneratorImpl, System.Reflection.Emit", throwOnError: true)!; - return ilgType.GetMethod("GetMaxStackSize", BindingFlags.NonPublic | BindingFlags.Instance, Type.EmptyTypes); + return ilgType.GetMethod("GetMaxStack", BindingFlags.NonPublic | BindingFlags.Instance, Type.EmptyTypes); + } + + private static FieldInfo GetMaxStackDepthAndCurrentStackDepthField(out FieldInfo currentStack) + { + Type ilgType = Type.GetType("System.Reflection.Emit.ILGeneratorImpl, System.Reflection.Emit", throwOnError: true)!; + currentStack = ilgType.GetField("_currentStackDepth", BindingFlags.NonPublic | BindingFlags.Instance)!; + return ilgType.GetField("_maxStackDepth", BindingFlags.NonPublic | BindingFlags.Instance); } [Fact] @@ -280,8 +288,8 @@ public void Label_ConditionalBranching() type.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(2, getMaxStackSizeMethod.Invoke(il, new object[0])); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(il, null)); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); @@ -351,8 +359,8 @@ public void Label_SwitchCase() type.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(6, getMaxStackSizeMethod.Invoke(il, new object[0])); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(1, getMaxStackMethod.Invoke(il, null)); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); @@ -396,8 +404,8 @@ public void LocalBuilderMultipleLocalsUsage() il.Emit(OpCodes.Ldloc_3); il.Emit(OpCodes.Ret); type.CreateType(); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(2, getMaxStackSizeMethod.Invoke(il, new object[0])); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(il, null)); saveMethod.Invoke(ab, new object[] { file.Path }); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); @@ -972,6 +980,7 @@ public void SimpleTryCatchBlock() Type dBZException = typeof(DivideByZeroException); ILGenerator ilGenerator = method.GetILGenerator(); LocalBuilder local = ilGenerator.DeclareLocal(typeof(float)); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); Label exBlock = ilGenerator.BeginExceptionBlock(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_1); @@ -985,6 +994,8 @@ public void SimpleTryCatchBlock() ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ret); tb.CreateType(); + + Assert.Equal(3, getMaxStackMethod.Invoke(ilGenerator, null)); saveMethod.Invoke(ab, new object[] { file.Path }); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); @@ -1020,26 +1031,38 @@ public void TryMultipleCatchBlocks() Type dBZException = typeof(DivideByZeroException); Type exception = typeof(Exception); ILGenerator ilGenerator = method.GetILGenerator(); + FieldInfo maxStackField = GetMaxStackDepthAndCurrentStackDepthField(out FieldInfo currentStack); LocalBuilder local = ilGenerator.DeclareLocal(typeof(float)); Label exBlock = ilGenerator.BeginExceptionBlock(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.Emit(OpCodes.Div); ilGenerator.Emit(OpCodes.Stloc_0); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); ilGenerator.BeginCatchBlock(dBZException); + Assert.Equal(1, currentStack.GetValue(ilGenerator)); ilGenerator.EmitWriteLine("Error: division by zero"); ilGenerator.Emit(OpCodes.Ldc_R4, 0.0f); ilGenerator.Emit(OpCodes.Stloc_0); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); + ilGenerator.Emit(OpCodes.Pop); // pop the exception in the stack, else its gonna added to the _depthAdjustment ilGenerator.BeginCatchBlock(exception); + Assert.Equal(1, currentStack.GetValue(ilGenerator)); ilGenerator.EmitWriteLine("Error: generic Exception"); ilGenerator.Emit(OpCodes.Ldc_R4, 0.0f); ilGenerator.Emit(OpCodes.Stloc_0); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EndExceptionBlock(); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ret); tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1058,14 +1081,16 @@ public void TryMultipleCatchBlocks() Assert.Equal(OpCodes.Call.Value, bodyBytes[14]); // Calls Console.WriteLine Assert.Equal(OpCodes.Ldc_R4.Value, bodyBytes[19]); Assert.Equal(OpCodes.Stloc_0.Value, bodyBytes[24]); - Assert.Equal(OpCodes.Leave.Value, bodyBytes[25]); - Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[30]); // "Error: division by zero" - Assert.Equal(OpCodes.Call.Value, bodyBytes[35]); // Calls Console.WriteLine - Assert.Equal(OpCodes.Ldc_R4.Value, bodyBytes[40]); - Assert.Equal(OpCodes.Stloc_0.Value, bodyBytes[45]); - Assert.Equal(OpCodes.Leave.Value, bodyBytes[46]); - Assert.Equal(OpCodes.Ldloc_0.Value, bodyBytes[51]); - Assert.Equal(OpCodes.Ret.Value, bodyBytes[52]); + Assert.Equal(OpCodes.Pop.Value, bodyBytes[25]); + Assert.Equal(OpCodes.Leave.Value, bodyBytes[26]); + Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[31]); // "Error: division by zero" + Assert.Equal(OpCodes.Call.Value, bodyBytes[36]); // Calls Console.WriteLine + Assert.Equal(OpCodes.Ldc_R4.Value, bodyBytes[41]); + Assert.Equal(OpCodes.Stloc_0.Value, bodyBytes[46]); + Assert.Equal(OpCodes.Pop.Value, bodyBytes[47]); + Assert.Equal(OpCodes.Leave.Value, bodyBytes[48]); + Assert.Equal(OpCodes.Ldloc_0.Value, bodyBytes[53]); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[54]); } } @@ -1116,6 +1141,9 @@ public void TryFilterCatchBlock() tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1137,6 +1165,7 @@ public void TryCatchFilterCatchBlock() Type overflowException = typeof(OverflowException); Type exception = typeof(Exception); ILGenerator ilGenerator = method.GetILGenerator(); + FieldInfo maxStackField = GetMaxStackDepthAndCurrentStackDepthField(out FieldInfo _); LocalBuilder local = ilGenerator.DeclareLocal(typeof(float)); Label filterEnd = ilGenerator.DefineLabel(); Label filterCheck = ilGenerator.DefineLabel(); @@ -1147,9 +1176,11 @@ public void TryCatchFilterCatchBlock() ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.Emit(OpCodes.Div); ilGenerator.Emit(OpCodes.Stloc_0); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); ilGenerator.BeginCatchBlock(overflowException); ilGenerator.EmitWriteLine("Overflow Exception!"); ilGenerator.ThrowException(overflowException); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); ilGenerator.BeginExceptFilterBlock(); ilGenerator.Emit(OpCodes.Isinst, dBZException); ilGenerator.Emit(OpCodes.Dup); @@ -1163,6 +1194,7 @@ public void TryCatchFilterCatchBlock() ilGenerator.Emit(OpCodes.Ldc_I4_0); ilGenerator.Emit(OpCodes.Cgt_Un); ilGenerator.MarkLabel(filterEnd); + Assert.Equal(2, maxStackField.GetValue(ilGenerator)); ilGenerator.BeginCatchBlock(null); ilGenerator.EmitWriteLine("Filtered division by zero"); ilGenerator.Emit(OpCodes.Ldc_R4, 0.0f); @@ -1179,6 +1211,9 @@ public void TryCatchFilterCatchBlock() tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1213,6 +1248,9 @@ public void TryFinallyBlock() tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1254,6 +1292,9 @@ public void TryCatchFinallyBlock() tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1275,49 +1316,54 @@ public void TryFilterCatchFinallyBlock() ConstructorInfo myConstructorInfo = overflowEType.GetConstructor(new [] { typeof(string) }); MethodInfo myExToStrMI = overflowEType.GetMethod("ToString"); MethodInfo myWriteLineMI = typeof(Console).GetMethod("WriteLine", new [] {typeof(string),typeof(object) }); - ILGenerator myAdderIL = method.GetILGenerator(); - LocalBuilder myLocalBuilder1 = myAdderIL.DeclareLocal(typeof(int)); - LocalBuilder myLocalBuilder2 = myAdderIL.DeclareLocal(overflowEType); - - Label myFailedLabel = myAdderIL.DefineLabel(); - Label myEndOfMethodLabel = myAdderIL.DefineLabel(); - Label myLabel = myAdderIL.BeginExceptionBlock(); - myAdderIL.Emit(OpCodes.Ldarg_0); - myAdderIL.Emit(OpCodes.Ldc_I4_S, 10); - myAdderIL.Emit(OpCodes.Bgt_S, myFailedLabel); - myAdderIL.Emit(OpCodes.Ldarg_1); - myAdderIL.Emit(OpCodes.Ldc_I4_S, 10); - myAdderIL.Emit(OpCodes.Bgt_S, myFailedLabel); - myAdderIL.Emit(OpCodes.Ldarg_0); - myAdderIL.Emit(OpCodes.Ldarg_1); - myAdderIL.Emit(OpCodes.Add_Ovf_Un); - myAdderIL.Emit(OpCodes.Stloc_S, myLocalBuilder1); - myAdderIL.Emit(OpCodes.Br_S, myEndOfMethodLabel); - myAdderIL.MarkLabel(myFailedLabel); - myAdderIL.Emit(OpCodes.Ldstr, "Cannot accept values over 10 for add."); - myAdderIL.Emit(OpCodes.Newobj, myConstructorInfo); - myAdderIL.Emit(OpCodes.Stloc_S, myLocalBuilder2); - myAdderIL.Emit(OpCodes.Ldloc_S, myLocalBuilder2); - myAdderIL.Emit(OpCodes.Throw); - myAdderIL.BeginExceptFilterBlock(); - myAdderIL.BeginCatchBlock(null); - myAdderIL.EmitWriteLine("Except filter block handled."); - myAdderIL.BeginCatchBlock(overflowEType); - myAdderIL.Emit(OpCodes.Ldstr, "{0}"); - myAdderIL.Emit(OpCodes.Ldloc_S, myLocalBuilder2); - myAdderIL.EmitCall(OpCodes.Callvirt, myExToStrMI, null); - myAdderIL.EmitCall(OpCodes.Call, myWriteLineMI, null); - myAdderIL.Emit(OpCodes.Ldc_I4_M1); - myAdderIL.Emit(OpCodes.Stloc_S, myLocalBuilder1); - myAdderIL.BeginFinallyBlock(); - myAdderIL.EmitWriteLine("Finally block handled."); - myAdderIL.EndExceptionBlock(); - myAdderIL.MarkLabel(myEndOfMethodLabel); - myAdderIL.Emit(OpCodes.Ldloc_S, myLocalBuilder1); - myAdderIL.Emit(OpCodes.Ret); + ILGenerator ilGenerator = method.GetILGenerator(); + LocalBuilder myLocalBuilder1 = ilGenerator.DeclareLocal(typeof(int)); + LocalBuilder myLocalBuilder2 = ilGenerator.DeclareLocal(overflowEType); + + Label myFailedLabel = ilGenerator.DefineLabel(); + Label myEndOfMethodLabel = ilGenerator.DefineLabel(); + Label myLabel = ilGenerator.BeginExceptionBlock(); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Ldc_I4_S, 10); + ilGenerator.Emit(OpCodes.Bgt_S, myFailedLabel); + ilGenerator.Emit(OpCodes.Ldarg_1); + ilGenerator.Emit(OpCodes.Ldc_I4_S, 10); + ilGenerator.Emit(OpCodes.Bgt_S, myFailedLabel); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Ldarg_1); + ilGenerator.Emit(OpCodes.Add_Ovf_Un); + ilGenerator.Emit(OpCodes.Stloc_S, myLocalBuilder1); + ilGenerator.Emit(OpCodes.Br_S, myEndOfMethodLabel); + ilGenerator.MarkLabel(myFailedLabel); + ilGenerator.Emit(OpCodes.Ldstr, "Cannot accept values over 10 for add."); + ilGenerator.Emit(OpCodes.Newobj, myConstructorInfo); + ilGenerator.Emit(OpCodes.Stloc_S, myLocalBuilder2); + ilGenerator.Emit(OpCodes.Ldloc_S, myLocalBuilder2); + ilGenerator.Emit(OpCodes.Throw); + ilGenerator.BeginExceptFilterBlock(); + ilGenerator.BeginCatchBlock(null); + ilGenerator.Emit(OpCodes.Pop); + ilGenerator.EmitWriteLine("Except filter block handled."); + ilGenerator.BeginCatchBlock(overflowEType); + ilGenerator.Emit(OpCodes.Stloc_S, myLocalBuilder2); + ilGenerator.Emit(OpCodes.Ldstr, "{0}"); + ilGenerator.Emit(OpCodes.Ldloc_S, myLocalBuilder2); + ilGenerator.EmitCall(OpCodes.Callvirt, myExToStrMI, null); + ilGenerator.EmitCall(OpCodes.Call, myWriteLineMI, null); + ilGenerator.Emit(OpCodes.Ldc_I4_M1); + ilGenerator.Emit(OpCodes.Stloc_S, myLocalBuilder1); + ilGenerator.BeginFinallyBlock(); + ilGenerator.EmitWriteLine("Finally block handled."); + ilGenerator.EndExceptionBlock(); + ilGenerator.MarkLabel(myEndOfMethodLabel); + ilGenerator.Emit(OpCodes.Ldloc_S, myLocalBuilder1); + ilGenerator.Emit(OpCodes.Ret); tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1352,6 +1398,9 @@ public void TryFaultBlock() tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1382,22 +1431,42 @@ public void NestedTryCatchBlocks() MethodBuilder method = tb.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new[] { typeof(int), typeof(int) }); Type exception = typeof(Exception); ILGenerator ilGenerator = method.GetILGenerator(); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); ilGenerator.BeginExceptionBlock(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.Emit(OpCodes.Div); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); ilGenerator.BeginExceptionBlock(); ilGenerator.EmitWriteLine("Try block nested in try"); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Ldarg_1); + ilGenerator.Emit(OpCodes.Ldc_I4_4); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); + Assert.Equal(3, getMaxStackMethod.Invoke(ilGenerator, null)); ilGenerator.BeginCatchBlock(exception); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EmitWriteLine("Catch block nested in try"); ilGenerator.EndExceptionBlock(); ilGenerator.EmitWriteLine("Outer try block ends"); ilGenerator.BeginCatchBlock(exception); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EmitWriteLine("Outer catch block starts"); ilGenerator.BeginExceptionBlock(); ilGenerator.EmitWriteLine("Try block nested in catch"); ilGenerator.BeginCatchBlock(exception); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Ldarg_1); + ilGenerator.Emit(OpCodes.Ldc_I4_4); + ilGenerator.Emit(OpCodes.Ldc_I4_4); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Pop); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EmitWriteLine("Catch block nested in catch"); + Assert.Equal(5, getMaxStackMethod.Invoke(ilGenerator, null)); // 5 including the exception object ilGenerator.EndExceptionBlock(); ilGenerator.EmitWriteLine("Outer catch block ends"); ilGenerator.EndExceptionBlock(); @@ -1405,6 +1474,8 @@ public void NestedTryCatchBlocks() tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + Assert.Equal(5, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1427,27 +1498,34 @@ public void DeeperNestedTryCatchFilterFinallyBlocks() MethodBuilder method = tb.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new[] { typeof(int), typeof(int) }); Type exception = typeof(Exception); ILGenerator ilGenerator = method.GetILGenerator(); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); LocalBuilder local = ilGenerator.DeclareLocal(typeof(int)); ilGenerator.Emit(OpCodes.Ldc_I4_0); - ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.BeginExceptionBlock(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.Emit(OpCodes.Div); ilGenerator.Emit(OpCodes.Stloc_0); - + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); ilGenerator.BeginExceptionBlock(); ilGenerator.EmitWriteLine("1st nested try block nested in try"); Label myLabel = ilGenerator.BeginExceptionBlock(); ilGenerator.EmitWriteLine("2nd nested try block starts"); ilGenerator.Emit(OpCodes.Ldc_I4_3); + ilGenerator.Emit(OpCodes.Stloc_0); + Assert.Equal(2, getMaxStackMethod.Invoke(ilGenerator, null)); + ilGenerator.BeginExceptionBlock(); + ilGenerator.Emit(OpCodes.Ldc_I4_3); + ilGenerator.Emit(OpCodes.Ldc_I4_3); + ilGenerator.Emit(OpCodes.Ldc_I4_3); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); ilGenerator.Emit(OpCodes.Stloc_0); - - ilGenerator.BeginExceptionBlock(); ilGenerator.EmitWriteLine("3rd nested try block"); + Assert.Equal(4, getMaxStackMethod.Invoke(ilGenerator, null)); ilGenerator.BeginFinallyBlock(); ilGenerator.EmitWriteLine("3rd nested finally block"); ilGenerator.EndExceptionBlock(); @@ -1455,29 +1533,46 @@ public void DeeperNestedTryCatchFilterFinallyBlocks() ilGenerator.EmitWriteLine("2nd nested try block ends"); ilGenerator.BeginExceptFilterBlock(); ilGenerator.EmitWriteLine("2nd nested filter block starts."); + Assert.Equal(4, getMaxStackMethod.Invoke(ilGenerator, null)); ilGenerator.BeginCatchBlock(null); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EmitWriteLine("2nd nested filter block handled."); ilGenerator.BeginCatchBlock(exception); ilGenerator.EmitWriteLine("2nd nested catch block handled."); + ilGenerator.Emit(OpCodes.Ldc_I4_3); + ilGenerator.Emit(OpCodes.Ldc_I4_3); + ilGenerator.Emit(OpCodes.Ldc_I4_3); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Add); + ilGenerator.Emit(OpCodes.Stloc_0); + Assert.Equal(5, getMaxStackMethod.Invoke(ilGenerator, null)); // including the exception object + ilGenerator.Emit(OpCodes.Pop); ilGenerator.BeginFinallyBlock(); ilGenerator.EmitWriteLine("2nd nested finally block handled."); ilGenerator.EndExceptionBlock(); ilGenerator.BeginCatchBlock(exception); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EmitWriteLine("Catch block nested in try"); ilGenerator.EndExceptionBlock(); ilGenerator.EmitWriteLine("Outer try block ends"); ilGenerator.BeginCatchBlock(exception); ilGenerator.EmitWriteLine("Outer catch block starts"); + ilGenerator.Emit(OpCodes.Pop); ilGenerator.EmitWriteLine("Outer catch block ends"); ilGenerator.EndExceptionBlock(); + ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ret); tb.CreateType(); saveMethod.Invoke(ab, new object[] { file.Path }); + Assert.Equal(5, getMaxStackMethod.Invoke(ilGenerator, null)); + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); MethodBody body = typeFromDisk.GetMethod("Method").GetMethodBody(); @@ -1497,12 +1592,13 @@ public void DeeperNestedTryCatchFilterFinallyBlocks() private static int Int32Sum(int a, int b) => a + b; [Fact] - public void TestEmitCalliBlittable() + public void EmitCalliBlittable() { int a = 1, b = 1, result = 2; using (TempFile file = TempFile.Create()) { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliBlittable"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); Type returnType = typeof(int); MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); @@ -1519,9 +1615,7 @@ public void TestEmitCalliBlittable() Type typeFromDisk = assemblyFromDisk.GetType("MyType"); var del = new Int32SumStdCall(Int32Sum); IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); - object resultValue = typeFromDisk - .GetMethod("F", BindingFlags.Public | BindingFlags.Static) - .Invoke(null, [funcPtr, a, b]); + object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, a, b]); GC.KeepAlive(del); Assert.IsType(returnType, resultValue); @@ -1530,12 +1624,13 @@ public void TestEmitCalliBlittable() } [Fact] - public void TestEmitCalliManagedBlittable() + public void EmitCalliManagedBlittable() { int a = 1, b = 1, result = 2; using (TempFile file = TempFile.Create()) { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliManagedBlittable"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); Type returnType = typeof(int); MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); @@ -1552,9 +1647,7 @@ public void TestEmitCalliManagedBlittable() Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); Type typeFromDisk = assemblyFromDisk.GetType("MyType"); - object resultValue = typeFromDisk - .GetMethod("F", BindingFlags.Public | BindingFlags.Static) - .Invoke(null, [funcPtr, a, b]); + object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, a, b]); Assert.IsType(returnType, resultValue); Assert.Equal(result, resultValue); @@ -1567,12 +1660,13 @@ public void TestEmitCalliManagedBlittable() private static string StringReverse(string a) => string.Join("", a.Reverse()); [Fact] - public void TestEmitCalliNonBlittable() + public void EmitCalliNonBlittable() { string input = "Test string!", result = "!gnirts tseT"; using (TempFile file = TempFile.Create()) { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliNonBlittable"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); Type returnType = typeof(string); MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(string)]); methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); @@ -1588,9 +1682,7 @@ public void TestEmitCalliNonBlittable() Type typeFromDisk = assemblyFromDisk.GetType("MyType"); var del = new StringReverseCdecl(StringReverse); IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); - object resultValue = typeFromDisk - .GetMethod("F", BindingFlags.Public | BindingFlags.Static) - .Invoke(null, [funcPtr, input]); + object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, input]); GC.KeepAlive(del); Assert.IsType(returnType, resultValue); @@ -1660,49 +1752,27 @@ public void EmitCall_VarArgsMethodInIL() Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); - MethodInfo varargMethodFromDisk = typeFromDisk.GetMethod("VarArgMethod"); - Assert.Equal(CallingConventions.VarArgs, varargMethodFromDisk.CallingConvention); - ParameterInfo[] parameters = varargMethodFromDisk.GetParameters(); + MethodInfo varArgMethodFromDisk = typeFromDisk.GetMethod("VarArgMethod"); + Assert.Equal(CallingConventions.VarArgs, varArgMethodFromDisk.CallingConvention); + ParameterInfo[] parameters = varArgMethodFromDisk.GetParameters(); Assert.Equal(1, parameters.Length); // TODO: how to get the vararg parameter? + IList locals = varArgMethodFromDisk.GetMethodBody().LocalVariables; + Assert.Equal(2, locals.Count); + Assert.Equal(typeof(ArgIterator).FullName, locals[0].LocalType.FullName); + Assert.Equal(typeof(bool).FullName, locals[1].LocalType.FullName); + + byte[] callingMethodBody = typeFromDisk.GetMethod("CallVarArgMethod").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Ldstr.Value, callingMethodBody[0]); + Assert.Equal(OpCodes.Ldstr.Value, callingMethodBody[5]); + Assert.Equal(OpCodes.Ldc_I4.Value, callingMethodBody[10]); + Assert.Equal(OpCodes.Call.Value, callingMethodBody[15]); } } - [Fact] - public void EmitCalli_CallFixedAndVarArgMethodsInIL() - { - using (TempFile file = TempFile.Create()) - { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); - MethodInfo getpid = typeof(AssemblySaveILGeneratorTests).GetMethod("getpid", BindingFlags.Static | BindingFlags.NonPublic); - MethodInfo print = typeof(AssemblySaveILGeneratorTests).GetMethod("Print", BindingFlags.Static | BindingFlags.NonPublic); - MethodBuilder mb = tb.DefineMethod("CallingMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); - ILGenerator il = mb.GetILGenerator(); - LocalBuilder local = il.DeclareLocal(typeof(int)); - il.EmitWriteLine("Calling native functions"); - il.Emit(OpCodes.Ldftn, getpid); - il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, typeof(int), Type.EmptyTypes); - il.Emit(OpCodes.Stloc_0); - il.Emit(OpCodes.Ldstr, "Hello "); - il.Emit(OpCodes.Ldstr, "world "); - il.Emit(OpCodes.Ldloc_0); - il.Emit(OpCodes.Ldftn, print); - il.EmitCalli(OpCodes.Calli, CallingConventions.VarArgs, typeof(void), [typeof(string)], [typeof(string), typeof(int)]); - il.Emit(OpCodes.Ret); - Type type = tb.CreateType(); - saveMethod.Invoke(ab, [file.Path]); - - Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); - Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); - MethodInfo methodFromDisk = typeFromDisk.GetMethod("CallingMethod"); - } - } - - [DllImport("libSystem.dylib")] - private static extern int getpid(); - - internal static void Print(string format, params object[] args) + private static FieldInfo GetDepthAdjustmentField() { - Console.WriteLine(format, args); + Type ilgType = Type.GetType("System.Reflection.Emit.ILGeneratorImpl, System.Reflection.Emit", throwOnError: true)!; + return ilgType.GetField("_depthAdjustment", BindingFlags.NonPublic | BindingFlags.Instance); } [Fact] @@ -1713,51 +1783,90 @@ public void Emit_CallBySignature() AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); MethodBuilder mb1 = tb.DefineMethod("VarArgMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.VarArgs, null, [typeof(string)]); ILGenerator il1 = mb1.GetILGenerator(); + FieldInfo maxStack = GetMaxStackDepthAndCurrentStackDepthField(out FieldInfo currentStack); + FieldInfo depthAdjustment = GetDepthAdjustmentField(); LocalBuilder locAi = il1.DeclareLocal(typeof(ArgIterator)); LocalBuilder locNext = il1.DeclareLocal(typeof(bool)); Label labelCheckCondition = il1.DefineLabel(); Label labelNext = il1.DefineLabel(); il1.Emit(OpCodes.Ldarg_0); + Assert.Equal(1, currentStack.GetValue(il1)); il1.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", [typeof(string)])); + Assert.Equal(0, currentStack.GetValue(il1)); il1.Emit(OpCodes.Ldloca_S, locAi); il1.Emit(OpCodes.Arglist); + Assert.Equal(2, currentStack.GetValue(il1)); il1.Emit(OpCodes.Call, typeof(ArgIterator).GetConstructor([typeof(RuntimeArgumentHandle)])); - il1.Emit(OpCodes.Br_S, labelCheckCondition); + Assert.Equal(2, currentStack.GetValue(il1)); + Assert.Equal(2, maxStack.GetValue(il1)); + il1.Emit(OpCodes.Br_S, labelCheckCondition); // uncleared currentStack value 2 will be kept in the LabelInfo._startDepth il1.MarkLabel(labelNext); + Assert.Equal(0, currentStack.GetValue(il1)); il1.Emit(OpCodes.Ldloca_S, locAi); il1.Emit(OpCodes.Call, typeof(ArgIterator).GetMethod("GetNextArg", Type.EmptyTypes)); il1.Emit(OpCodes.Call, typeof(TypedReference).GetMethod("ToObject")); il1.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", [typeof(object)])); + Assert.Equal(0, currentStack.GetValue(il1)); il1.MarkLabel(labelCheckCondition); + Assert.Equal(2, currentStack.GetValue(il1)); // LabelInfo._startDepth sets the currentStack + Assert.Equal(2, maxStack.GetValue(il1)); il1.Emit(OpCodes.Ldloca_S, locAi); + Assert.Equal(3, currentStack.GetValue(il1)); il1.Emit(OpCodes.Call, typeof(ArgIterator).GetMethod("GetRemainingCount")); + Assert.Equal(3, currentStack.GetValue(il1)); il1.Emit(OpCodes.Ldc_I4_0); + Assert.Equal(4, currentStack.GetValue(il1)); il1.Emit(OpCodes.Cgt); + Assert.Equal(3, currentStack.GetValue(il1)); il1.Emit(OpCodes.Stloc_1); + Assert.Equal(2, currentStack.GetValue(il1)); il1.Emit(OpCodes.Ldloc_1); - il1.Emit(OpCodes.Brtrue_S, labelNext); + Assert.Equal(3, currentStack.GetValue(il1)); + Assert.Equal(4, maxStack.GetValue(il1)); + Assert.Equal(0, depthAdjustment.GetValue(il1)); + il1.Emit(OpCodes.Brtrue_S, labelNext); // Backward branching, sets the adjustment to 2 + Assert.Equal(2, depthAdjustment.GetValue(il1)); il1.Emit(OpCodes.Ret); + Assert.Equal(0, currentStack.GetValue(il1)); + Assert.Equal(4, maxStack.GetValue(il1)); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(4 + 2, getMaxStackMethod.Invoke(il1, null)); + MethodBuilder mb2 = tb.DefineMethod("CallingMethod", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard); ILGenerator il2 = mb2.GetILGenerator(); il2.Emit(OpCodes.Ldstr, "Hello "); il2.Emit(OpCodes.Ldstr, "world "); il2.Emit(OpCodes.Ldc_I4, 2024); il2.Emit(OpCodes.Ldftn, mb1); + Assert.Equal(4, currentStack.GetValue(il2)); + Assert.Equal(4, maxStack.GetValue(il2)); + Assert.Equal(0, depthAdjustment.GetValue(il2)); SignatureHelper signature = SignatureHelper.GetMethodSigHelper(CallingConventions.VarArgs, typeof(void)); signature.AddArgument(typeof(string)); signature.AddSentinel(); signature.AddArgument(typeof(string)); signature.AddArgument(typeof(int)); il2.Emit(OpCodes.Calli, signature); + Assert.Equal(0, currentStack.GetValue(il2)); + Assert.Equal(4, maxStack.GetValue(il2)); il2.Emit(OpCodes.Ret); + Assert.Equal(0, depthAdjustment.GetValue(il2)); + Assert.Equal(4, getMaxStackMethod.Invoke(il2, null)); Type type = tb.CreateType(); saveMethod.Invoke(ab, [file.Path]); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); - MethodInfo varArgMethodFromDisk = typeFromDisk.GetMethod("VarArgMethod"); - MethodInfo methodFromDisk = typeFromDisk.GetMethod("CallingMethod"); + Assert.Equal(CallingConventions.VarArgs, typeFromDisk.GetMethod("VarArgMethod").CallingConvention); + + byte[] callingMethodBody = typeFromDisk.GetMethod("CallingMethod").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Ldstr.Value, callingMethodBody[0]); + Assert.Equal(OpCodes.Ldstr.Value, callingMethodBody[5]); + Assert.Equal(OpCodes.Ldc_I4.Value, callingMethodBody[10]); + Assert.Equal(0xFE, callingMethodBody[15]); // Ldftn = 0xfe06 + Assert.Equal(0x06, callingMethodBody[16]); + Assert.Equal(OpCodes.Calli.Value, callingMethodBody[21]); } } @@ -1805,10 +1914,10 @@ static void GetCode(int num) ilg.Emit(OpCodes.Ldloc, loc); ilg.Emit(OpCodes.Ret); - type.CreateTypeInfo(); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(4, getMaxStackSizeMethod.Invoke(ilg, [])); + + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(4, getMaxStackMethod.Invoke(ilg, null)); } } @@ -1824,45 +1933,38 @@ public void MaxStackNonEmptyForward() static void GetCode(int num) { - using (TempFile file = TempFile.Create()) - { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); - MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); - var ilg = method.GetILGenerator(); - - ilg.Emit(OpCodes.Ldc_I4_0); - for (int i = 0; i < num; i++) - { - ilg.Emit(OpCodes.Ldc_I4_1); - ilg.Emit(OpCodes.Ldc_I4_1); - ilg.Emit(OpCodes.Ldc_I4_1); - ilg.Emit(OpCodes.Ldc_I4_1); - ilg.Emit(OpCodes.Add); - ilg.Emit(OpCodes.Add); - - // Unconditional jump to next block. - var labNext = ilg.DefineLabel(); - ilg.Emit(OpCodes.Br, labNext); - ilg.MarkLabel(labNext); - } + AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _); + MethodBuilder method = type.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), null); + var ilg = method.GetILGenerator(); - // Each block leaves two values on the stack. Add them into the previous value. - for (int i = 0; i < num; i++) - { - ilg.Emit(OpCodes.Add); - ilg.Emit(OpCodes.Add); - } + ilg.Emit(OpCodes.Ldc_I4_0); + for (int i = 0; i < num; i++) + { + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Ldc_I4_1); + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); - ilg.Emit(OpCodes.Ret); + // Unconditional jump to next block. + var labNext = ilg.DefineLabel(); + ilg.Emit(OpCodes.Br, labNext); + ilg.MarkLabel(labNext); + } - type.CreateTypeInfo(); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(2 * num + 3, getMaxStackSizeMethod.Invoke(ilg, [])); + // Each block leaves two values on the stack. Add them into the previous value. + for (int i = 0; i < num; i++) + { + ilg.Emit(OpCodes.Add); + ilg.Emit(OpCodes.Add); + } - saveMethod.Invoke(ab, [file.Path]); + ilg.Emit(OpCodes.Ret); + type.CreateTypeInfo(); - Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); - } + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2 * num + 3, getMaxStackMethod.Invoke(ilg, null)); } } @@ -1873,10 +1975,10 @@ public void MaxStackNonEmptyBackward() // at the branch location is non-empty. GetCode(1 << 1); - GetCode(1 << 2); - GetCode(1 << 3); - GetCode(1 << 4); - GetCode(1 << 5); + GetCode(1 << 2); // n = 4 expected 16 was 12 + GetCode(1 << 3); // n = 8 exp 32 was 20 + GetCode(1 << 4); // n = 16 exp 64 was 36 + GetCode(1 << 5); // n = 32 exp 128 was 68 static void GetCode(int num) { @@ -1918,10 +2020,12 @@ static void GetCode(int num) ilg.Emit(OpCodes.Ret); type.CreateTypeInfo(); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - // It was (4 * num + 2) in the original test, not sure why old ILGenerator calculation produced this, - // but there should be no difference between forward and backward loops max stack calculation - Assert.Equal(2 * num + 3, getMaxStackSizeMethod.Invoke(ilg, [])); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + // The expected formula comes from (num - 1) * 2 + 1 + 5, + // ILs within the loop produces 2 adjustments for each, except 1st one + // last loop produce 2 + 1 adjustment because of the 1st instruction that loads 0 + // the max stack for 1st loop is 5, 4 for the all other loops + Assert.Equal(num * 2 + 4, getMaxStackMethod.Invoke(ilg, null)); } } @@ -1948,10 +2052,11 @@ static void GetCode() ilg.Emit(OpCodes.Ldc_I4_1); ilg.Emit(OpCodes.Add); ilg.Emit(OpCodes.Ret); - type.CreateTypeInfo(); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(2, getMaxStackSizeMethod.Invoke(ilg, [])); // TODO:it was 2 + 1, + 1 was for fixup + + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + // Observed depth of 2, with "adjustment" of 1. + Assert.Equal(2 +1, getMaxStackMethod.Invoke(ilg, null)); } } @@ -1981,8 +2086,8 @@ static void GetCode() ilg.Emit(OpCodes.Ret); type.CreateTypeInfo(); - MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); - Assert.Equal(2, getMaxStackSizeMethod.Invoke(ilg, [])); // TODO: was 3 + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(ilg, null)); } } @@ -2021,11 +2126,50 @@ public void SimpleForLoopTest() tb.CreateType(); saveMethod.Invoke(ab, [file.Path]); + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(il, null)); + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); Type typeFromDisk = assemblyFromDisk.GetType("MyType"); MethodInfo sumMethodFromDisk = typeFromDisk.GetMethod("SumMethod"); Assert.Equal(55, sumMethodFromDisk.Invoke(null, [10])); } } + + [Fact] + public void RecursiveSumTest() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder mb2 = tb.DefineMethod("RecursiveMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); + ILGenerator il = mb2.GetILGenerator(); + Label loopEnd = il.DefineLabel(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ble, loopEnd); // if (value1 <= value2) + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Sub); + il.Emit(OpCodes.Call, mb2); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Ret); + il.MarkLabel(loopEnd); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(3, getMaxStackMethod.Invoke(il, null)); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + MethodInfo recursiveMethodFromDisk = typeFromDisk.GetMethod("RecursiveMethod"); + Assert.NotNull(recursiveMethodFromDisk); + Assert.Equal(55, recursiveMethodFromDisk.Invoke(null, [10])); + } + } } } diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs index 6f92cc3a6e8550..69ec92d4b97a51 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs @@ -69,7 +69,10 @@ internal static AssemblyBuilder PopulateAssemblyBuilderTypeBuilderAndSaveMethod( typeBuilder = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); return ab; } - + + internal static AssemblyBuilder PopulateAssemblyBuilderAndSaveMethod(AssemblyName assemblyName, out MethodInfo saveMethod) => + PopulateAssemblyBuilderAndSaveMethod(assemblyName, null, typeof(string), out saveMethod); + internal static AssemblyBuilder PopulateAssemblyBuilderAndSaveMethod(AssemblyName assemblyName, List? assemblyAttributes, Type parameterType, out MethodInfo saveMethod) { From 7cf3c91cff25d5987362ba02f35fc0b72b3f1796 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 1 Jan 2024 17:24:20 -0800 Subject: [PATCH 4/4] Apply feedback, fix test failure, disable some tests on mono --- .../src/Resources/Strings.resx | 4 ++-- .../src/Resources/Strings.resx | 2 +- .../Reflection/Emit/AssemblyBuilderImpl.cs | 4 ++-- .../System/Reflection/Emit/ILGeneratorImpl.cs | 16 ++++++++-------- .../AssemblySaveILGeneratorTests.cs | 5 ++++- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index b7a2c2aa9d8667..53cc0c13d1abb6 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1523,7 +1523,7 @@ Recursive fallback not allowed for bytes {0}. - Label multiply defined. + Label defined multiple times. Token {0:x} is not a valid FieldInfo token in the scope of module {1}. @@ -4283,4 +4283,4 @@ This operation is not available because the reflection support was disabled at compile time. - + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index d8c595b9878ed0..84f91988b3ba99 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -265,6 +265,6 @@ Method '{0}' does not have a method body. - Label multiply defined. + Label defined multiple times. \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs index 2e829ddfa57433..8f252ace10fd7a 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/AssemblyBuilderImpl.cs @@ -47,8 +47,8 @@ internal static AssemblyBuilderImpl DefinePersistedAssembly(AssemblyName name, A private void WritePEImage(Stream peStream, BlobBuilder ilBuilder) { var peHeaderBuilder = new PEHeaderBuilder( - // When only Characteristics.Dll is set and try to load the generated assembly from file, - // runtime throws BadImageFormatException, had to add Characteristics.ExecutableImage. + // For now only support DLL, DLL files are considered executable files + // for almost all purposes, although they cannot be directly run. imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll); var peBuilder = new ManagedPEBuilder( diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index f78b3962d77824..6a424d04aad5b9 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -158,7 +158,7 @@ public override void BeginFaultBlock() currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = DefineLabel(); _cfBuilder.AddFaultRegion(GetMetaLabel(currentExBlock.TryStart), GetMetaLabel(currentExBlock.TryEnd), - GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd)); + GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd)); currentExBlock.State = ExceptionState.Fault; MarkLabel(currentExBlock.HandleStart); // Stack depth for "fault" starts at zero. @@ -189,7 +189,7 @@ public override void BeginFinallyBlock() currentExBlock.HandleStart = DefineLabel(); currentExBlock.HandleEnd = finallyEndLabel; _cfBuilder.AddFinallyRegion(GetMetaLabel(currentExBlock.TryStart), GetMetaLabel(currentExBlock.TryEnd), - GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd)); + GetMetaLabel(currentExBlock.HandleStart), GetMetaLabel(currentExBlock.HandleEnd)); currentExBlock.State = ExceptionState.Finally; MarkLabel(currentExBlock.HandleStart); // Stack depth for "finally" starts at zero. @@ -393,8 +393,8 @@ public override void Emit(OpCode opcode, ConstructorInfo con) { // Instruction must be one of call or callvirt. Debug.Assert(opcode.Equals(OpCodes.Call) || - opcode.Equals(OpCodes.Callvirt), - "Unexpected opcode encountered for StackBehaviour of VarPush."); + opcode.Equals(OpCodes.Callvirt), + "Unexpected opcode encountered for StackBehaviour of VarPush."); stackChange++; } @@ -402,9 +402,9 @@ public override void Emit(OpCode opcode, ConstructorInfo con) { // Instruction must be one of call, callvirt or newobj. Debug.Assert(opcode.Equals(OpCodes.Call) || - opcode.Equals(OpCodes.Callvirt) || - opcode.Equals(OpCodes.Newobj), - "Unexpected opcode encountered for StackBehaviour of VarPop."); + opcode.Equals(OpCodes.Callvirt) || + opcode.Equals(OpCodes.Newobj), + "Unexpected opcode encountered for StackBehaviour of VarPop."); if (con is ConstructorBuilderImpl builder) { @@ -425,7 +425,7 @@ private void WriteOrReserveToken(int token, object member) { if (token == -1) { - // The member is a `*BuilderImpl` and its token is not yet defined. + // The member is a `***BuilderImpl` and its token is not yet defined. // Reserve the token bytes and write them later when its ready _memberReferences.Add(new KeyValuePair (member, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index c47afe5778029e..45113f265eaae4 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -1691,6 +1691,7 @@ public void EmitCalliNonBlittable() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/96389", TestRuntimes.Mono)] public void EmitCall_VarArgsMethodInIL() { using (TempFile file = TempFile.Create()) @@ -1776,6 +1777,7 @@ private static FieldInfo GetDepthAdjustmentField() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/96389", TestRuntimes.Mono)] public void Emit_CallBySignature() { using (TempFile file = TempFile.Create()) @@ -2141,7 +2143,8 @@ public void RecursiveSumTest() { using (TempFile file = TempFile.Create()) { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("RecursiveSumTest"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); MethodBuilder mb2 = tb.DefineMethod("RecursiveMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); ILGenerator il = mb2.GetILGenerator(); Label loopEnd = il.DefineLabel();