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