@@ -42,9 +42,10 @@ sealed class PEAssemblyBuilder
4242 // Avoids creating duplicate __utf8_N types when multiple fields share the same size.
4343 readonly Dictionary < int , TypeDefinitionHandle > _sizedTypeCache = new ( ) ;
4444
45- // Deduplication cache for UTF-8 string RVA fields. Strings like "()V" that repeat across
46- // many proxy types are stored once and shared via the same FieldDefinitionHandle.
47- readonly Dictionary < string , FieldDefinitionHandle > _utf8FieldCache = new ( StringComparer . Ordinal ) ;
45+ // JNI signatures are owner-independent and can safely share one RVA field. JNI method names
46+ // are owner-specific after R8 rewriting, so each registration receives its own field.
47+ readonly Dictionary < string , FieldDefinitionHandle > _sharedUtf8FieldCache = new ( StringComparer . Ordinal ) ;
48+ readonly Dictionary < string , Queue < FieldDefinitionHandle > > _uniqueUtf8FieldCache = new ( StringComparer . Ordinal ) ;
4849 TypeDefinitionHandle _privateImplDetailsType ;
4950 int _utf8FieldCounter ;
5051
@@ -273,31 +274,57 @@ TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string manage
273274 }
274275
275276 /// <summary>
276- /// Emits deduplicated RVA fields containing the supplied null-terminated UTF-8 strings.
277+ /// Emits RVA fields containing the supplied null-terminated UTF-8 strings.
278+ /// <paramref name="sharedValues"/> are deduplicated, while every occurrence in
279+ /// <paramref name="uniqueValues"/> receives a separate field.
277280 /// Fields are grouped by size so each group is emitted contiguously on its sized helper
278281 /// type before any consuming types are emitted.
279282 /// </summary>
280- public void PrepareUtf8Fields ( IEnumerable < string > values )
283+ public void PrepareUtf8Fields ( IEnumerable < string > sharedValues , IEnumerable < string > uniqueValues )
281284 {
282- var valuesBySize = new SortedDictionary < int , SortedSet < string > > ( ) ;
283- foreach ( string value in values ) {
285+ var sharedValuesBySize = new SortedDictionary < int , SortedSet < string > > ( ) ;
286+ foreach ( string value in sharedValues ) {
284287 int size = System . Text . Encoding . UTF8 . GetByteCount ( value ) + 1 ;
285- if ( ! valuesBySize . TryGetValue ( size , out var valuesForSize ) ) {
288+ if ( ! sharedValuesBySize . TryGetValue ( size , out var valuesForSize ) ) {
286289 valuesForSize = new SortedSet < string > ( StringComparer . Ordinal ) ;
287- valuesBySize . Add ( size , valuesForSize ) ;
290+ sharedValuesBySize . Add ( size , valuesForSize ) ;
288291 }
289292 valuesForSize . Add ( value ) ;
290293 }
291294
292- foreach ( var group in valuesBySize ) {
293- var sizedType = GetOrCreateSizedType ( group . Key ) ;
294- foreach ( string value in group . Value ) {
295- AddUtf8Field ( value , sizedType ) ;
295+ var uniqueValuesBySize = new SortedDictionary < int , SortedDictionary < string , int > > ( ) ;
296+ foreach ( string value in uniqueValues ) {
297+ int size = System . Text . Encoding . UTF8 . GetByteCount ( value ) + 1 ;
298+ if ( ! uniqueValuesBySize . TryGetValue ( size , out var valuesForSize ) ) {
299+ valuesForSize = new SortedDictionary < string , int > ( StringComparer . Ordinal ) ;
300+ uniqueValuesBySize . Add ( size , valuesForSize ) ;
301+ }
302+ valuesForSize . TryGetValue ( value , out int count ) ;
303+ valuesForSize [ value ] = count + 1 ;
304+ }
305+
306+ var sizes = new SortedSet < int > ( sharedValuesBySize . Keys ) ;
307+ sizes . UnionWith ( uniqueValuesBySize . Keys ) ;
308+ foreach ( int size in sizes ) {
309+ var sizedType = GetOrCreateSizedType ( size ) ;
310+ if ( sharedValuesBySize . TryGetValue ( size , out var sharedForSize ) ) {
311+ foreach ( string value in sharedForSize ) {
312+ _sharedUtf8FieldCache . Add ( value , AddUtf8Field ( value , sizedType ) ) ;
313+ }
314+ }
315+ if ( uniqueValuesBySize . TryGetValue ( size , out var uniqueForSize ) ) {
316+ foreach ( var pair in uniqueForSize ) {
317+ var fields = new Queue < FieldDefinitionHandle > ( pair . Value ) ;
318+ for ( int i = 0 ; i < pair . Value ; i ++ ) {
319+ fields . Enqueue ( AddUtf8Field ( pair . Key , sizedType ) ) ;
320+ }
321+ _uniqueUtf8FieldCache . Add ( pair . Key , fields ) ;
322+ }
296323 }
297324 }
298325 }
299326
300- void AddUtf8Field ( string value , TypeDefinitionHandle sizedType )
327+ FieldDefinitionHandle AddUtf8Field ( string value , TypeDefinitionHandle sizedType )
301328 {
302329 // Encode to null-terminated UTF-8 (all JNI names/signatures are ASCII).
303330 _sigBlob . Clear ( ) ;
@@ -313,22 +340,33 @@ void AddUtf8Field (string value, TypeDefinitionHandle sizedType)
313340 Metadata . GetOrAddBlob ( _sigBlob ) ) ;
314341
315342 Metadata . AddFieldRelativeVirtualAddress ( fieldHandle , rva ) ;
316-
317- _utf8FieldCache [ value ] = fieldHandle ;
343+ return fieldHandle ;
318344 }
319345
320346 /// <summary>
321347 /// Returns a previously prepared UTF-8 RVA field.
322348 /// </summary>
323349 public FieldDefinitionHandle GetUtf8Field ( string value )
324350 {
325- if ( _utf8FieldCache . TryGetValue ( value , out var existing ) ) {
351+ if ( _sharedUtf8FieldCache . TryGetValue ( value , out var existing ) ) {
326352 return existing ;
327353 }
328354
329355 throw new InvalidOperationException ( $ "UTF-8 field '{ value } ' was not prepared before type emission.") ;
330356 }
331357
358+ /// <summary>
359+ /// Returns and consumes one previously prepared unique UTF-8 RVA field.
360+ /// </summary>
361+ public FieldDefinitionHandle GetUniqueUtf8Field ( string value )
362+ {
363+ if ( _uniqueUtf8FieldCache . TryGetValue ( value , out var fields ) && fields . Count > 0 ) {
364+ return fields . Dequeue ( ) ;
365+ }
366+
367+ throw new InvalidOperationException ( $ "Unique UTF-8 field '{ value } ' was not prepared before type emission.") ;
368+ }
369+
332370 void EnsurePrivateImplDetailsType ( )
333371 {
334372 if ( ! _privateImplDetailsType . IsNil ) {
0 commit comments