1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Diagnostics;
8 using System.Reflection.Internal;
9 using System.Runtime.CompilerServices;
10 using System.Text;
11 
12 namespace System.Reflection.Metadata.Ecma335
13 {
14     public sealed partial class MetadataBuilder
15     {
16         private sealed class HeapBlobBuilder : BlobBuilder
17         {
18             private int _capacityExpansion;
19 
HeapBlobBuilder(int capacity)20             public HeapBlobBuilder(int capacity)
21                 : base(capacity)
22             {
23             }
24 
AllocateChunk(int minimalSize)25             protected override BlobBuilder AllocateChunk(int minimalSize)
26             {
27                 return new HeapBlobBuilder(Math.Max(Math.Max(minimalSize, ChunkCapacity), _capacityExpansion));
28             }
29 
SetCapacity(int capacity)30             internal void SetCapacity(int capacity)
31             {
32                 _capacityExpansion = Math.Max(0, capacity - Count - FreeBytes);
33             }
34         }
35 
36         // #US heap
37         private const int UserStringHeapSizeLimit = 0x01000000;
38         private readonly Dictionary<string, UserStringHandle> _userStrings = new Dictionary<string, UserStringHandle>(256);
39         private readonly HeapBlobBuilder _userStringBuilder = new HeapBlobBuilder(4 * 1024);
40         private readonly int _userStringHeapStartOffset;
41 
42         // #String heap
43         private Dictionary<string, StringHandle> _strings = new Dictionary<string, StringHandle>(256);
44         private readonly int _stringHeapStartOffset;
45         private int _stringHeapCapacity = 4 * 1024;
46 
47         // #Blob heap
48         private readonly Dictionary<ImmutableArray<byte>, BlobHandle> _blobs = new Dictionary<ImmutableArray<byte>, BlobHandle>(1024, ByteSequenceComparer.Instance);
49         private readonly int _blobHeapStartOffset;
50         private int _blobHeapSize;
51 
52         // #GUID heap
53         private readonly Dictionary<Guid, GuidHandle> _guids = new Dictionary<Guid, GuidHandle>();
54         private readonly HeapBlobBuilder _guidBuilder = new HeapBlobBuilder(16); // full metadata has just a single guid
55 
56         /// <summary>
57         /// Creates a builder for metadata tables and heaps.
58         /// </summary>
59         /// <param name="userStringHeapStartOffset">
60         /// Start offset of the User String heap.
61         /// The cumulative size of User String heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata.
62         /// </param>
63         /// <param name="stringHeapStartOffset">
64         /// Start offset of the String heap.
65         /// The cumulative size of String heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata.
66         /// </param>
67         /// <param name="blobHeapStartOffset">
68         /// Start offset of the Blob heap.
69         /// The cumulative size of Blob heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata.
70         /// </param>
71         /// <param name="guidHeapStartOffset">
72         /// Start offset of the Guid heap.
73         /// The cumulative size of Guid heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata.
74         /// </param>
75         /// <exception cref="ImageFormatLimitationException">Offset is too big.</exception>
76         /// <exception cref="ArgumentOutOfRangeException">Offset is negative.</exception>
77         /// <exception cref="ArgumentException"><paramref name="guidHeapStartOffset"/> is not a multiple of size of GUID.</exception>
MetadataBuilder( int userStringHeapStartOffset = 0, int stringHeapStartOffset = 0, int blobHeapStartOffset = 0, int guidHeapStartOffset = 0)78         public MetadataBuilder(
79             int userStringHeapStartOffset = 0,
80             int stringHeapStartOffset = 0,
81             int blobHeapStartOffset = 0,
82             int guidHeapStartOffset = 0)
83         {
84             // -1 for the 0 we always write at the beginning of the heap:
85             if (userStringHeapStartOffset >= UserStringHeapSizeLimit - 1)
86             {
87                 Throw.HeapSizeLimitExceeded(HeapIndex.UserString);
88             }
89 
90             if (userStringHeapStartOffset < 0)
91             {
92                 Throw.ArgumentOutOfRange(nameof(userStringHeapStartOffset));
93             }
94 
95             if (stringHeapStartOffset < 0)
96             {
97                 Throw.ArgumentOutOfRange(nameof(stringHeapStartOffset));
98             }
99 
100             if (blobHeapStartOffset < 0)
101             {
102                 Throw.ArgumentOutOfRange(nameof(blobHeapStartOffset));
103             }
104 
105             if (guidHeapStartOffset < 0)
106             {
107                 Throw.ArgumentOutOfRange(nameof(guidHeapStartOffset));
108             }
109 
110             if (guidHeapStartOffset % BlobUtilities.SizeOfGuid != 0)
111             {
112                 throw new ArgumentException(SR.Format(SR.ValueMustBeMultiple, BlobUtilities.SizeOfGuid), nameof(guidHeapStartOffset));
113             }
114 
115             // Add zero-th entry to all heaps, even in EnC delta.
116             // We don't want generation-relative handles to ever be IsNil.
117             // In both full and delta metadata all nil heap handles should have zero value.
118             // There should be no blob handle that references the 0 byte added at the
119             // beginning of the delta blob.
120             _userStringBuilder.WriteByte(0);
121 
122             _blobs.Add(ImmutableArray<byte>.Empty, default(BlobHandle));
123             _blobHeapSize = 1;
124 
125             // When EnC delta is applied #US, #String and #Blob heaps are appended.
126             // Thus indices of strings and blobs added to this generation are offset
127             // by the sum of respective heap sizes of all previous generations.
128             _userStringHeapStartOffset = userStringHeapStartOffset;
129             _stringHeapStartOffset = stringHeapStartOffset;
130             _blobHeapStartOffset = blobHeapStartOffset;
131 
132             // Unlike other heaps, #Guid heap in EnC delta is zero-padded.
133             _guidBuilder.WriteBytes(0, guidHeapStartOffset);
134         }
135 
136         /// <summary>
137         /// Sets the capacity of the specified table.
138         /// </summary>
139         /// <param name="heap">Heap index.</param>
140         /// <param name="byteCount">Number of bytes.</param>
141         /// <exception cref="ArgumentOutOfRangeException"><paramref name="heap"/> is not a valid heap index.</exception>
142         /// <exception cref="ArgumentOutOfRangeException"><paramref name="byteCount"/> is negative.</exception>
143         /// <remarks>
144         /// Use to reduce allocations if the approximate number of bytes is known ahead of time.
145         /// </remarks>
SetCapacity(HeapIndex heap, int byteCount)146         public void SetCapacity(HeapIndex heap, int byteCount)
147         {
148             if (byteCount < 0)
149             {
150                 Throw.ArgumentOutOfRange(nameof(byteCount));
151             }
152 
153             switch (heap)
154             {
155                 case HeapIndex.Blob:
156                     // Not useful to set capacity.
157                     // By the time the blob heap is serialized we know the exact size we need.
158                     break;
159 
160                 case HeapIndex.Guid:
161                     _guidBuilder.SetCapacity(byteCount);
162                     break;
163 
164                 case HeapIndex.String:
165                     _stringHeapCapacity = byteCount;
166                     break;
167 
168                 case HeapIndex.UserString:
169                     _userStringBuilder.SetCapacity(byteCount);
170                     break;
171 
172                 default:
173                     Throw.ArgumentOutOfRange(nameof(heap));
174                     break;
175             }
176         }
177 
178         // internal for testing
SerializeHandle(ImmutableArray<int> map, StringHandle handle)179         internal int SerializeHandle(ImmutableArray<int> map, StringHandle handle) => map[handle.GetWriterVirtualIndex()];
180         internal int SerializeHandle(BlobHandle handle) => handle.GetHeapOffset();
181         internal int SerializeHandle(GuidHandle handle) => handle.Index;
182         internal int SerializeHandle(UserStringHandle handle) => handle.GetHeapOffset();
183 
184         /// <summary>
185         /// Adds specified blob to Blob heap, if it's not there already.
186         /// </summary>
187         /// <param name="value"><see cref="BlobBuilder"/> containing the blob.</param>
188         /// <returns>Handle to the added or existing blob.</returns>
189         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddBlob(BlobBuilder value)190         public BlobHandle GetOrAddBlob(BlobBuilder value)
191         {
192             if (value == null)
193             {
194                 Throw.ArgumentNull(nameof(value));
195             }
196 
197             // TODO: avoid making a copy if the blob exists in the index
198             return GetOrAddBlob(value.ToImmutableArray());
199         }
200 
201         /// <summary>
202         /// Adds specified blob to Blob heap, if it's not there already.
203         /// </summary>
204         /// <param name="value">Array containing the blob.</param>
205         /// <returns>Handle to the added or existing blob.</returns>
206         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddBlob(byte[] value)207         public BlobHandle GetOrAddBlob(byte[] value)
208         {
209             if (value == null)
210             {
211                 Throw.ArgumentNull(nameof(value));
212             }
213 
214             // TODO: avoid making a copy if the blob exists in the index
215             return GetOrAddBlob(ImmutableArray.Create(value));
216         }
217 
218         /// <summary>
219         /// Adds specified blob to Blob heap, if it's not there already.
220         /// </summary>
221         /// <param name="value">Array containing the blob.</param>
222         /// <returns>Handle to the added or existing blob.</returns>
223         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddBlob(ImmutableArray<byte> value)224         public BlobHandle GetOrAddBlob(ImmutableArray<byte> value)
225         {
226             if (value.IsDefault)
227             {
228                 Throw.ArgumentNull(nameof(value));
229             }
230 
231             BlobHandle handle;
232             if (!_blobs.TryGetValue(value, out handle))
233             {
234                 handle = BlobHandle.FromOffset(_blobHeapStartOffset + _blobHeapSize);
235                 _blobs.Add(value, handle);
236 
237                 _blobHeapSize += BlobWriterImpl.GetCompressedIntegerSize(value.Length) + value.Length;
238             }
239 
240             return handle;
241         }
242 
243         /// <summary>
244         /// Encodes a constant value to a blob and adds it to the Blob heap, if it's not there already.
245         /// Uses UTF16 to encode string constants.
246         /// </summary>
247         /// <param name="value">Constant value.</param>
248         /// <returns>Handle to the added or existing blob.</returns>
GetOrAddConstantBlob(object value)249         public unsafe BlobHandle GetOrAddConstantBlob(object value)
250         {
251             string str = value as string;
252             if (str != null)
253             {
254                 return GetOrAddBlobUTF16(str);
255             }
256 
257             var builder = PooledBlobBuilder.GetInstance();
258             builder.WriteConstant(value);
259             var result = GetOrAddBlob(builder);
260             builder.Free();
261             return result;
262         }
263 
264         /// <summary>
265         /// Encodes a string using UTF16 encoding to a blob and adds it to the Blob heap, if it's not there already.
266         /// </summary>
267         /// <param name="value">String.</param>
268         /// <returns>Handle to the added or existing blob.</returns>
269         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddBlobUTF16(string value)270         public BlobHandle GetOrAddBlobUTF16(string value)
271         {
272             var builder = PooledBlobBuilder.GetInstance();
273             builder.WriteUTF16(value);
274             var handle = GetOrAddBlob(builder);
275             builder.Free();
276             return handle;
277         }
278 
279         /// <summary>
280         /// Encodes a string using UTF8 encoding to a blob and adds it to the Blob heap, if it's not there already.
281         /// </summary>
282         /// <param name="value">Constant value.</param>
283         /// <param name="allowUnpairedSurrogates">
284         /// True to encode unpaired surrogates as specified, otherwise replace them with U+FFFD character.
285         /// </param>
286         /// <returns>Handle to the added or existing blob.</returns>
287         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddBlobUTF8(string value, bool allowUnpairedSurrogates = true)288         public BlobHandle GetOrAddBlobUTF8(string value, bool allowUnpairedSurrogates = true)
289         {
290             var builder = PooledBlobBuilder.GetInstance();
291             builder.WriteUTF8(value, allowUnpairedSurrogates);
292             var handle = GetOrAddBlob(builder);
293             builder.Free();
294             return handle;
295         }
296 
297         /// <summary>
298         /// Encodes a debug document name and adds it to the Blob heap, if it's not there already.
299         /// </summary>
300         /// <param name="value">Document name.</param>
301         /// <returns>
302         /// Handle to the added or existing document name blob
303         /// (see https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#DocumentNameBlob).
304         /// </returns>
305         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddDocumentName(string value)306         public BlobHandle GetOrAddDocumentName(string value)
307         {
308             if (value == null)
309             {
310                 Throw.ArgumentNull(nameof(value));
311             }
312 
313             char separator = ChooseSeparator(value);
314 
315             var resultBuilder = PooledBlobBuilder.GetInstance();
316             resultBuilder.WriteByte((byte)separator);
317 
318             var partBuilder = PooledBlobBuilder.GetInstance();
319 
320             int i = 0;
321             while (true)
322             {
323                 int next = value.IndexOf(separator, i);
324 
325                 partBuilder.WriteUTF8(value, i, (next >= 0 ? next : value.Length) - i, allowUnpairedSurrogates: true, prependSize: false);
326                 resultBuilder.WriteCompressedInteger(GetOrAddBlob(partBuilder).GetHeapOffset());
327 
328                 if (next == -1)
329                 {
330                     break;
331                 }
332 
333                 if (next == value.Length - 1)
334                 {
335                     // trailing separator:
336                     resultBuilder.WriteByte(0);
337                     break;
338                 }
339 
340                 partBuilder.Clear();
341                 i = next + 1;
342             }
343 
344             partBuilder.Free();
345 
346             var resultHandle = GetOrAddBlob(resultBuilder);
347             resultBuilder.Free();
348             return resultHandle;
349         }
350 
ChooseSeparator(string str)351         private static char ChooseSeparator(string str)
352         {
353             const char s1 = '/';
354             const char s2 = '\\';
355 
356             int count1 = 0, count2 = 0;
357             foreach (var c in str)
358             {
359                 if (c == s1)
360                 {
361                     count1++;
362                 }
363                 else if (c == s2)
364                 {
365                     count2++;
366                 }
367             }
368 
369             return (count1 >= count2) ? s1 : s2;
370         }
371 
372         /// <summary>
373         /// Adds specified Guid to Guid heap, if it's not there already.
374         /// </summary>
375         /// <param name="guid">Guid to add.</param>
376         /// <returns>Handle to the added or existing Guid.</returns>
GetOrAddGuid(Guid guid)377         public GuidHandle GetOrAddGuid(Guid guid)
378         {
379             if (guid == Guid.Empty)
380             {
381                 return default(GuidHandle);
382             }
383 
384             GuidHandle result;
385             if (_guids.TryGetValue(guid, out result))
386             {
387                 return result;
388             }
389 
390             result = GetNewGuidHandle();
391             _guids.Add(guid, result);
392             _guidBuilder.WriteGuid(guid);
393             return result;
394         }
395 
396         /// <summary>
397         /// Reserves space on the Guid heap for a GUID.
398         /// </summary>
399         /// <returns>
400         /// Handle to the reserved Guid and a <see cref="Blob"/> representing the GUID blob as stored on the heap.
401         /// </returns>
402         /// <exception cref="ImageFormatLimitationException">The remaining space on the heap is too small to fit the string.</exception>
ReserveGuid()403         public ReservedBlob<GuidHandle> ReserveGuid()
404         {
405             var handle = GetNewGuidHandle();
406             var content = _guidBuilder.ReserveBytes(BlobUtilities.SizeOfGuid);
407             return new ReservedBlob<GuidHandle>(handle, content);
408         }
409 
GetNewGuidHandle()410         private GuidHandle GetNewGuidHandle()
411         {
412             // Unlike #Blob, #String and #US streams delta #GUID stream is padded to the
413             // size of the previous generation #GUID stream before new GUIDs are added.
414             // The first GUID added in a delta will thus have an index that equals the number
415             // of GUIDs in all previous generations + 1.
416 
417             // Metadata Spec:
418             // The Guid heap is an array of GUIDs, each 16 bytes wide.
419             // Its first element is numbered 1, its second 2, and so on.
420             return GuidHandle.FromIndex((_guidBuilder.Count >> 4) + 1);
421         }
422 
423         /// <summary>
424         /// Adds specified string to String heap, if it's not there already.
425         /// </summary>
426         /// <param name="value">Array containing the blob.</param>
427         /// <returns>Handle to the added or existing blob.</returns>
428         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddString(string value)429         public StringHandle GetOrAddString(string value)
430         {
431             if (value == null)
432             {
433                 Throw.ArgumentNull(nameof(value));
434             }
435 
436             StringHandle handle;
437             if (value.Length == 0)
438             {
439                 handle = default(StringHandle);
440             }
441             else if (!_strings.TryGetValue(value, out handle))
442             {
443                 handle = StringHandle.FromWriterVirtualIndex(_strings.Count + 1); // idx 0 is reserved for empty string
444                 _strings.Add(value, handle);
445             }
446 
447             return handle;
448         }
449 
450         /// <summary>
451         /// Reserves space on the User String heap for a string of specified length.
452         /// </summary>
453         /// <param name="length">The number of characters to reserve.</param>
454         /// <returns>
455         /// Handle to the reserved User String and a <see cref="Blob"/> representing the entire User String blob (including its length and terminal character).
456         ///
457         /// Handle may be used in <see cref="InstructionEncoder.LoadString(UserStringHandle)"/>.
458         /// Use <see cref="BlobWriter.WriteUserString(string)"/> to fill in the blob content.
459         /// </returns>
460         /// <exception cref="ImageFormatLimitationException">The remaining space on the heap is too small to fit the string.</exception>
461         /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is negative.</exception>
ReserveUserString(int length)462         public ReservedBlob<UserStringHandle> ReserveUserString(int length)
463         {
464             if (length < 0)
465             {
466                 Throw.ArgumentOutOfRange(nameof(length));
467             }
468 
469             var handle = GetNewUserStringHandle();
470             int encodedLength = BlobUtilities.GetUserStringByteLength(length);
471             var reservedUserString = _userStringBuilder.ReserveBytes(BlobWriterImpl.GetCompressedIntegerSize(encodedLength) + encodedLength);
472             return new ReservedBlob<UserStringHandle>(handle, reservedUserString);
473         }
474 
475         /// <summary>
476         /// Adds specified string to User String heap, if it's not there already.
477         /// </summary>
478         /// <param name="value">String to add.</param>
479         /// <returns>
480         /// Handle to the added or existing string.
481         /// May be used in <see cref="InstructionEncoder.LoadString(UserStringHandle)"/>.
482         /// </returns>
483         /// <exception cref="ImageFormatLimitationException">The remaining space on the heap is too small to fit the string.</exception>
484         /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
GetOrAddUserString(string value)485         public UserStringHandle GetOrAddUserString(string value)
486         {
487             if (value == null)
488             {
489                 Throw.ArgumentNull(nameof(value));
490             }
491 
492             UserStringHandle handle;
493             if (!_userStrings.TryGetValue(value, out handle))
494             {
495                 handle = GetNewUserStringHandle();
496 
497                 _userStrings.Add(value, handle);
498                 _userStringBuilder.WriteUserString(value);
499             }
500 
501             return handle;
502         }
503 
GetNewUserStringHandle()504         private UserStringHandle GetNewUserStringHandle()
505         {
506             int offset = _userStringHeapStartOffset + _userStringBuilder.Count;
507 
508             // Native metadata emitter allows strings to exceed the heap size limit as long
509             // as the index is within the limits (see https://github.com/dotnet/roslyn/issues/9852)
510             if (offset >= UserStringHeapSizeLimit)
511             {
512                 Throw.HeapSizeLimitExceeded(HeapIndex.UserString);
513             }
514 
515             return UserStringHandle.FromOffset(offset);
516         }
517 
518         /// <summary>
519         /// Fills in stringIndexMap with data from stringIndex and write to stringWriter.
520         /// Releases stringIndex as the stringTable is sealed after this point.
521         /// </summary>
SerializeStringHeap( BlobBuilder heapBuilder, Dictionary<string, StringHandle> strings, int stringHeapStartOffset)522         private static ImmutableArray<int> SerializeStringHeap(
523             BlobBuilder heapBuilder,
524             Dictionary<string, StringHandle> strings,
525             int stringHeapStartOffset)
526         {
527             // Sort by suffix and remove stringIndex
528             var sorted = new List<KeyValuePair<string, StringHandle>>(strings);
529             sorted.Sort(SuffixSort.Instance);
530 
531             // Create VirtIdx to Idx map and add entry for empty string
532             int totalCount = sorted.Count + 1;
533             var stringVirtualIndexToHeapOffsetMap = ImmutableArray.CreateBuilder<int>(totalCount);
534             stringVirtualIndexToHeapOffsetMap.Count = totalCount;
535 
536             stringVirtualIndexToHeapOffsetMap[0] = 0;
537             heapBuilder.WriteByte(0);
538 
539             // Find strings that can be folded
540             string prev = string.Empty;
541             foreach (KeyValuePair<string, StringHandle> entry in sorted)
542             {
543                 int position = stringHeapStartOffset + heapBuilder.Count;
544 
545                 // It is important to use ordinal comparison otherwise we'll use the current culture!
546                 if (prev.EndsWith(entry.Key, StringComparison.Ordinal) && !BlobUtilities.IsLowSurrogateChar(entry.Key[0]))
547                 {
548                     // Map over the tail of prev string. Watch for null-terminator of prev string.
549                     stringVirtualIndexToHeapOffsetMap[entry.Value.GetWriterVirtualIndex()] = position - (BlobUtilities.GetUTF8ByteCount(entry.Key) + 1);
550                 }
551                 else
552                 {
553                     stringVirtualIndexToHeapOffsetMap[entry.Value.GetWriterVirtualIndex()] = position;
554                     heapBuilder.WriteUTF8(entry.Key, allowUnpairedSurrogates: false);
555                     heapBuilder.WriteByte(0);
556                 }
557 
558                 prev = entry.Key;
559             }
560 
561             return stringVirtualIndexToHeapOffsetMap.MoveToImmutable();
562         }
563 
564         /// <summary>
565         /// Sorts strings such that a string is followed immediately by all strings
566         /// that are a suffix of it.
567         /// </summary>
568         private sealed class SuffixSort : IComparer<KeyValuePair<string, StringHandle>>
569         {
570             internal static SuffixSort Instance = new SuffixSort();
571 
Compare(KeyValuePair<string, StringHandle> xPair, KeyValuePair<string, StringHandle> yPair)572             public int Compare(KeyValuePair<string, StringHandle> xPair, KeyValuePair<string, StringHandle> yPair)
573             {
574                 string x = xPair.Key;
575                 string y = yPair.Key;
576 
577                 for (int i = x.Length - 1, j = y.Length - 1; i >= 0 & j >= 0; i--, j--)
578                 {
579                     if (x[i] < y[j])
580                     {
581                         return -1;
582                     }
583 
584                     if (x[i] > y[j])
585                     {
586                         return +1;
587                     }
588                 }
589 
590                 return y.Length.CompareTo(x.Length);
591             }
592         }
593 
WriteHeapsTo(BlobBuilder builder, BlobBuilder stringHeap)594         internal void WriteHeapsTo(BlobBuilder builder, BlobBuilder stringHeap)
595         {
596             WriteAligned(stringHeap, builder);
597             WriteAligned(_userStringBuilder, builder);
598             WriteAligned(_guidBuilder, builder);
599             WriteAlignedBlobHeap(builder);
600         }
601 
WriteAlignedBlobHeap(BlobBuilder builder)602         private void WriteAlignedBlobHeap(BlobBuilder builder)
603         {
604             int alignment = BitArithmetic.Align(_blobHeapSize, 4) - _blobHeapSize;
605 
606             var writer = new BlobWriter(builder.ReserveBytes(_blobHeapSize + alignment));
607 
608             // Perf consideration: With large heap the following loop may cause a lot of cache misses
609             // since the order of entries in _blobs dictionary depends on the hash of the array values,
610             // which is not correlated to the heap index. If we observe such issue we should order
611             // the entries by heap position before running this loop.
612 
613             int startOffset = _blobHeapStartOffset;
614             foreach (var entry in _blobs)
615             {
616                 int heapOffset = entry.Value.GetHeapOffset();
617                 var blob = entry.Key;
618 
619                 writer.Offset = (heapOffset == 0) ? 0 : heapOffset - startOffset;
620                 writer.WriteCompressedInteger(blob.Length);
621                 writer.WriteBytes(blob);
622             }
623 
624             writer.Offset = _blobHeapSize;
625             writer.WriteBytes(0, alignment);
626         }
627 
WriteAligned(BlobBuilder source, BlobBuilder target)628         private static void WriteAligned(BlobBuilder source, BlobBuilder target)
629         {
630             int length = source.Count;
631             target.LinkSuffix(source);
632             target.WriteBytes(0, BitArithmetic.Align(length, 4) - length);
633         }
634     }
635 }
636