From db50ff6c94146c04476ffccdcdb82c33544efd73 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 31 Aug 2026 14:43:34 +0200 Subject: [PATCH 1/2] Reduce typemap signature encoder allocations Cache fixed activation and CreateInstance signatures so repeated PE emission avoids nested signature encoder delegates while preserving emitted bytes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Generator/PEAssemblyBuilder.cs | 24 +++++++- .../Generator/TypeMapAssemblyEmitter.cs | 56 ++++++++++++------- .../TypeMapAssemblyGeneratorTests.cs | 43 ++++++++++++++ 3 files changed, 101 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs index 6434c515e27..001a834a201 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs @@ -162,9 +162,12 @@ public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, Act { _sigBlob.Clear (); encodeSig (new BlobEncoder (_sigBlob)); - return Metadata.AddMemberReference (parent, Metadata.GetOrAddString (name), Metadata.GetOrAddBlob (_sigBlob)); + return AddMemberRef (parent, name, Metadata.GetOrAddBlob (_sigBlob)); } + public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, BlobHandle signature) + => Metadata.AddMemberReference (parent, Metadata.GetOrAddString (name), signature); + /// /// Resolves a to a TypeReference/TypeSpecification handle, with caching. /// @@ -382,6 +385,15 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, Action encodeSig, Action emitIL) => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null, useBranches: false); + public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, + BlobHandle signature, Action emitIL) + => EmitBody (name, attrs, signature, emitIL, encodeLocals: null, useBranches: false); + + public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, + BlobHandle signature, Action emitIL, + Action? encodeLocals) + => EmitBody (name, attrs, signature, emitIL, encodeLocals, useBranches: false); + /// /// Emits a method body and definition with optional local variable declarations. /// @@ -408,7 +420,13 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, encodeSig (new BlobEncoder (_sigBlob)); // Capture the sig blob handle before emitIL, because emitIL callbacks // may call AddMemberRef which clears and repopulates _sigBlob. - var sigBlobHandle = Metadata.GetOrAddBlob (_sigBlob); + return EmitBody (name, attrs, Metadata.GetOrAddBlob (_sigBlob), emitIL, encodeLocals, useBranches); + } + + MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, + BlobHandle signature, Action emitIL, + Action? encodeLocals, bool useBranches) + { StandaloneSignatureHandle localSigHandle = default; if (encodeLocals != null) { @@ -433,7 +451,7 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, return Metadata.AddMethodDefinition ( attrs, MethodImplAttributes.IL, Metadata.GetOrAddString (name), - sigBlobHandle, + signature, bodyOffset, MetadataTokens.ParameterHandle (Metadata.GetRowCount (TableIndex.Param) + 1)); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 56a160a7914..2aa48fb0e0f 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -137,6 +137,9 @@ sealed class TypeMapAssemblyEmitter MemberReferenceHandle _jniEnvTypesRegisterNativesRef; MemberReferenceHandle _readOnlySpanOfJniNativeMethodCtorRef; + BlobHandle _activationCtorSignature; + BlobHandle _createInstanceSignature; + EntityHandle _anchorTypeHandle; ExportMethodDispatchEmitter? _exportMethodDispatchEmitter; @@ -950,12 +953,7 @@ void EmitCreateInstanceBody (Action emitIL) { _pe.EmitBody ("CreateInstance", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, - rt => rt.Type ().Type (_iJavaPeerableRef, false), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); - }), + GetCreateInstanceSignature (), emitIL); } @@ -963,25 +961,45 @@ void EmitCreateInstanceBodyWithLocals (Action encodeLocals, Action< { _pe.EmitBody ("CreateInstance", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, - rt => rt.Type ().Type (_iJavaPeerableRef, false), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); - }), + GetCreateInstanceSignature (), emitIL, encodeLocals); } MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef) { - return _pe.AddMemberRef (declaringTypeRef, ".ctor", - sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); - })); + return _pe.AddMemberRef (declaringTypeRef, ".ctor", GetActivationCtorSignature ()); + } + + BlobHandle GetActivationCtorSignature () + { + if (_activationCtorSignature.IsNil) { + var blob = new BlobBuilder (8); + blob.WriteByte ((byte) SignatureAttributes.Instance); + blob.WriteCompressedInteger (2); + blob.WriteByte ((byte) SignatureTypeCode.Void); + blob.WriteByte ((byte) SignatureTypeCode.IntPtr); + blob.WriteByte ((byte) SignatureTypeKind.ValueType); + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniHandleOwnershipRef)); + _activationCtorSignature = _pe.Metadata.GetOrAddBlob (blob); + } + return _activationCtorSignature; + } + + BlobHandle GetCreateInstanceSignature () + { + if (_createInstanceSignature.IsNil) { + var blob = new BlobBuilder (8); + blob.WriteByte ((byte) SignatureAttributes.Instance); + blob.WriteCompressedInteger (2); + blob.WriteByte ((byte) SignatureTypeKind.Class); + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_iJavaPeerableRef)); + blob.WriteByte ((byte) SignatureTypeCode.IntPtr); + blob.WriteByte ((byte) SignatureTypeKind.ValueType); + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniHandleOwnershipRef)); + _createInstanceSignature = _pe.Metadata.GetOrAddBlob (blob); + } + return _createInstanceSignature; } MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnlyList parameterTypes) diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 48e55b3edc5..f205fc5236a 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -393,6 +393,49 @@ public void EmitBody_ILCallbackCallsAddMemberRef_SignatureNotCorrupted () Assert.Equal ("System.Int32", paramType); } + [Fact] + public void EmitBody_PreencodedSignature_PreservesMethodSignature () + { + var pe = new PEAssemblyBuilder (new Version (11, 0, 0, 0)); + pe.EmitPreamble ("PreencodedSigTest", "PreencodedSigTest.dll"); + var objectRef = pe.Metadata.AddTypeReference (pe.SystemRuntimeRef, + pe.Metadata.GetOrAddString ("System"), pe.Metadata.GetOrAddString ("Object")); + pe.Metadata.AddTypeDefinition ( + TypeAttributes.Public | TypeAttributes.Class, + pe.Metadata.GetOrAddString ("Test"), + pe.Metadata.GetOrAddString ("MyType"), + objectRef, + MetadataTokens.FieldDefinitionHandle (pe.Metadata.GetRowCount (TableIndex.Field) + 1), + MetadataTokens.MethodDefinitionHandle (pe.Metadata.GetRowCount (TableIndex.MethodDef) + 1)); + var signature = new BlobBuilder (); + signature.WriteByte ((byte) SignatureAttributes.Instance); + signature.WriteCompressedInteger (1); + signature.WriteByte ((byte) SignatureTypeCode.String); + signature.WriteByte ((byte) SignatureTypeCode.Int32); + + pe.EmitBody ( + "PreencodedMethod", + MethodAttributes.Public, + pe.Metadata.GetOrAddBlob (signature), + encoder => { + encoder.OpCode (ILOpCode.Ldnull); + encoder.Return (returnsValue: true); + }); + using var stream = new MemoryStream (); + pe.WritePE (stream); + stream.Position = 0; + using var peReader = new PEReader (stream); + var reader = peReader.GetMetadataReader (); + var method = reader.TypeDefinitions + .SelectMany (handle => reader.GetTypeDefinition (handle).GetMethods ()) + .Select (handle => reader.GetMethodDefinition (handle)) + .Single (method => reader.GetString (method.Name) == "PreencodedMethod"); + var decoded = method.DecodeSignature (SignatureTypeProvider.Instance, null); + + Assert.Equal ("System.String", decoded.ReturnType); + Assert.Equal ("System.Int32", Assert.Single (decoded.ParameterTypes)); + } + [Fact] public void Generate_JiStyleInvoker_FirstParamIsByRef () { From 8608a908d24b534bf90700c1809ad4dbbeb551a4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 31 Aug 2026 14:45:20 +0200 Subject: [PATCH 2/2] Reuse metadata signature encoder Build the cached signatures through the existing SRM encoder so the optimization stays small and preserves the established encoding path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Generator/PEAssemblyBuilder.cs | 15 ++++----- .../Generator/TypeMapAssemblyEmitter.cs | 31 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs index 001a834a201..ec4da021cf7 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs @@ -159,15 +159,18 @@ public AssemblyReferenceHandle FindOrAddAssemblyRef (string assemblyName) /// Adds a member reference using the reusable signature blob builder. /// public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, Action encodeSig) + => AddMemberRef (parent, name, GetOrAddSignature (encodeSig)); + + public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, BlobHandle signature) + => Metadata.AddMemberReference (parent, Metadata.GetOrAddString (name), signature); + + public BlobHandle GetOrAddSignature (Action encodeSig) { _sigBlob.Clear (); encodeSig (new BlobEncoder (_sigBlob)); - return AddMemberRef (parent, name, Metadata.GetOrAddBlob (_sigBlob)); + return Metadata.GetOrAddBlob (_sigBlob); } - public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, BlobHandle signature) - => Metadata.AddMemberReference (parent, Metadata.GetOrAddString (name), signature); - /// /// Resolves a to a TypeReference/TypeSpecification handle, with caching. /// @@ -416,11 +419,9 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, Action encodeSig, Action emitIL, Action? encodeLocals, bool useBranches) { - _sigBlob.Clear (); - encodeSig (new BlobEncoder (_sigBlob)); // Capture the sig blob handle before emitIL, because emitIL callbacks // may call AddMemberRef which clears and repopulates _sigBlob. - return EmitBody (name, attrs, Metadata.GetOrAddBlob (_sigBlob), emitIL, encodeLocals, useBranches); + return EmitBody (name, attrs, GetOrAddSignature (encodeSig), emitIL, encodeLocals, useBranches); } MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 2aa48fb0e0f..812a533653e 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -974,14 +974,13 @@ MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef) BlobHandle GetActivationCtorSignature () { if (_activationCtorSignature.IsNil) { - var blob = new BlobBuilder (8); - blob.WriteByte ((byte) SignatureAttributes.Instance); - blob.WriteCompressedInteger (2); - blob.WriteByte ((byte) SignatureTypeCode.Void); - blob.WriteByte ((byte) SignatureTypeCode.IntPtr); - blob.WriteByte ((byte) SignatureTypeKind.ValueType); - blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniHandleOwnershipRef)); - _activationCtorSignature = _pe.Metadata.GetOrAddBlob (blob); + _activationCtorSignature = _pe.GetOrAddSignature ( + sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + })); } return _activationCtorSignature; } @@ -989,15 +988,13 @@ BlobHandle GetActivationCtorSignature () BlobHandle GetCreateInstanceSignature () { if (_createInstanceSignature.IsNil) { - var blob = new BlobBuilder (8); - blob.WriteByte ((byte) SignatureAttributes.Instance); - blob.WriteCompressedInteger (2); - blob.WriteByte ((byte) SignatureTypeKind.Class); - blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_iJavaPeerableRef)); - blob.WriteByte ((byte) SignatureTypeCode.IntPtr); - blob.WriteByte ((byte) SignatureTypeKind.ValueType); - blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniHandleOwnershipRef)); - _createInstanceSignature = _pe.Metadata.GetOrAddBlob (blob); + _createInstanceSignature = _pe.GetOrAddSignature ( + sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, + rt => rt.Type ().Type (_iJavaPeerableRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + })); } return _createInstanceSignature; }