-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better BlobBuilder pooling #72383
Better BlobBuilder pooling #72383
Changes from 7 commits
03a61be
4351f58
06b1763
d2e8a20
fdcf80e
8f30134
1101c64
5e43343
fc23dec
814cd23
e08abbd
869a2eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,14 +33,14 @@ public ExtendedPEBuilder( | |
PEHeaderBuilder header, | ||
MetadataRootBuilder metadataRootBuilder, | ||
BlobBuilder ilStream, | ||
BlobBuilder mappedFieldData, | ||
BlobBuilder managedResources, | ||
ResourceSectionBuilder nativeResources, | ||
DebugDirectoryBuilder debugDirectoryBuilder, | ||
BlobBuilder? mappedFieldData, | ||
BlobBuilder? managedResources, | ||
ResourceSectionBuilder? nativeResources, | ||
DebugDirectoryBuilder? debugDirectoryBuilder, | ||
int strongNameSignatureSize, | ||
MethodDefinitionHandle entryPoint, | ||
CorFlags flags, | ||
Func<IEnumerable<Blob>, BlobContentId> deterministicIdProvider, | ||
Func<IEnumerable<Blob>, BlobContentId>? deterministicIdProvider, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These changes all line up with the declarations on the base type ctor. |
||
bool withMvidSection) | ||
: base(header, metadataRootBuilder, ilStream, mappedFieldData, managedResources, nativeResources, | ||
debugDirectoryBuilder, strongNameSignatureSize, entryPoint, flags, deterministicIdProvider) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Reflection.Metadata; | ||
using System.Reflection.Metadata.Ecma335; | ||
using Microsoft.Cci; | ||
|
||
namespace Microsoft.CodeAnalysis; | ||
|
||
internal static class MetadataBuilderExtensions | ||
{ | ||
internal static BlobHandle GetOrAddBlobAndFree(this MetadataBuilder metadataBuilder, PooledBlobBuilder builder) | ||
{ | ||
var handle = metadataBuilder.GetOrAddBlob(builder); | ||
builder.Free(); | ||
return handle; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,6 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
#nullable disable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
|
@@ -35,13 +33,69 @@ public PeWritingException(Exception inner) | |
|
||
internal static class PeWriter | ||
{ | ||
internal struct EmitBuilders | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Know there was, rightfully, confusion in previous iterations of the change on whose responsibility it was to free values and whether or not that was handled correctly in the code. Tried to respond to that feedback by moving all of the responsibility for the builder ownership into this type. Hoping this makes everything clearer. |
||
{ | ||
internal BlobBuilder IlBlobBuilder; | ||
internal PooledBlobBuilder MappedFieldDataBlobBuilder; | ||
internal PooledBlobBuilder ManagedResourceBlobBuilder; | ||
internal PooledBlobBuilder? PortableExecutableBlobBuilder; | ||
internal PooledBlobBuilder? PortablePdbBlobBuilder; | ||
|
||
public EmitBuilders() | ||
{ | ||
IlBlobBuilder = new BlobBuilder(32 * 1024); | ||
MappedFieldDataBlobBuilder = PooledBlobBuilder.GetInstance(); | ||
ManagedResourceBlobBuilder = PooledBlobBuilder.GetInstance(); | ||
PortableExecutableBlobBuilder = null; | ||
PortablePdbBlobBuilder = null; | ||
} | ||
|
||
internal void Free() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe make this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of our pool freeing in the compiler is not via |
||
{ | ||
if (PortableExecutableBlobBuilder is null) | ||
{ | ||
MappedFieldDataBlobBuilder.Free(); | ||
ManagedResourceBlobBuilder.Free(); | ||
} | ||
else | ||
{ | ||
var mappedCount = MappedFieldDataBlobBuilder.Count; | ||
var resourceCount = ManagedResourceBlobBuilder.Count; | ||
|
||
PortableExecutableBlobBuilder.Free(); | ||
|
||
// Once PortableExecutableBuilder is created it becomes the owner of the | ||
// MappedFieldDataBuilder and ManagedResourceBuilder instances. Freeing | ||
// it is sufficient to free both of them. | ||
// | ||
// However there is a bug in LinkSuffix / LinkPrefix which causes the | ||
// ownership to not happen when these are length 0. Need to handle that | ||
// specially until this is fixed. | ||
// https://github.com/dotnet/runtime/issues/99266 | ||
if (mappedCount == 0) | ||
{ | ||
MappedFieldDataBlobBuilder.AssertAlive(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a previous iteration of this change I had a similar method called |
||
MappedFieldDataBlobBuilder.Free(); | ||
} | ||
|
||
if (resourceCount == 0) | ||
{ | ||
ManagedResourceBlobBuilder.AssertAlive(); | ||
ManagedResourceBlobBuilder.Free(); | ||
} | ||
} | ||
|
||
PortablePdbBlobBuilder?.Free(); | ||
} | ||
} | ||
|
||
internal static bool WritePeToStream( | ||
EmitContext context, | ||
CommonMessageProvider messageProvider, | ||
Func<Stream> getPeStream, | ||
Func<Stream> getPortablePdbStreamOpt, | ||
PdbWriter nativePdbWriterOpt, | ||
string pdbPathOpt, | ||
Func<Stream?> getPeStream, | ||
Func<Stream?>? getPortablePdbStreamOpt, | ||
PdbWriter? nativePdbWriterOpt, | ||
string? pdbPathOpt, | ||
bool metadataOnly, | ||
bool isDeterministic, | ||
bool emitTestCoverageData, | ||
|
@@ -64,16 +118,13 @@ internal static bool WritePeToStream( | |
// based on the contents of the generated stream. | ||
Debug.Assert(properties.PersistentIdentifier == default(Guid)); | ||
|
||
var ilBuilder = new BlobBuilder(32 * 1024); | ||
var mappedFieldDataBuilder = new BlobBuilder(); | ||
var managedResourceBuilder = new BlobBuilder(1024); | ||
|
||
var emitBuilders = new EmitBuilders(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Last night I was considering going ahead and fixing the LinkSuffix / LinkPrefix bug and realized my current approach is flawed. Once that bug is fixed and a new version of SRM is used with Roslyn then my code will start throwing cause After some thought I decided a more durable fix is to pass
Ended up going with approach (2) here. If you feel churn is too big can use approach (1) instead. |
||
Blob mvidFixup, mvidStringFixup; | ||
mdWriter.BuildMetadataAndIL( | ||
nativePdbWriterOpt, | ||
ilBuilder, | ||
mappedFieldDataBuilder, | ||
managedResourceBuilder, | ||
emitBuilders.IlBlobBuilder, | ||
emitBuilders.MappedFieldDataBlobBuilder, | ||
emitBuilders.ManagedResourceBlobBuilder, | ||
out mvidFixup, | ||
out mvidStringFixup); | ||
|
||
|
@@ -113,9 +164,10 @@ internal static bool WritePeToStream( | |
nativePdbWriterOpt.WriteCompilerVersion(context.Module.CommonCompilation.Language); | ||
} | ||
|
||
Stream peStream = getPeStream(); | ||
Stream? peStream = getPeStream(); | ||
if (peStream == null) | ||
{ | ||
emitBuilders.Free(); | ||
return false; | ||
} | ||
|
||
|
@@ -155,7 +207,7 @@ internal static bool WritePeToStream( | |
// We need to calculate the PDB checksum, so we may as well use the calculated hash for PDB ID regardless of whether deterministic build is requested. | ||
var portablePdbContentHash = default(ImmutableArray<byte>); | ||
|
||
BlobBuilder portablePdbToEmbed = null; | ||
PooledBlobBuilder? portablePdbToEmbed = null; | ||
if (mdWriter.EmitPortableDebugMetadata) | ||
{ | ||
mdWriter.AddRemainingDebugDocuments(mdWriter.Module.DebugDocumentsBuilder.DebugDocuments); | ||
|
@@ -167,25 +219,25 @@ internal static bool WritePeToStream( | |
new Func<IEnumerable<Blob>, BlobContentId>(content => BlobContentId.FromHash(portablePdbContentHash = CryptographicHashProvider.ComputeHash(context.Module.PdbChecksumAlgorithm, content))) : | ||
null; | ||
|
||
var portablePdbBlob = new BlobBuilder(); | ||
emitBuilders.PortablePdbBlobBuilder = PooledBlobBuilder.GetInstance(zero: true); | ||
var portablePdbBuilder = mdWriter.GetPortablePdbBuilder(metadataRootBuilder.Sizes.RowCounts, debugEntryPointHandle, portablePdbIdProvider); | ||
pdbContentId = portablePdbBuilder.Serialize(portablePdbBlob); | ||
pdbContentId = portablePdbBuilder.Serialize(emitBuilders.PortablePdbBlobBuilder); | ||
portablePdbVersion = portablePdbBuilder.FormatVersion; | ||
|
||
if (getPortablePdbStreamOpt == null) | ||
{ | ||
// embed to debug directory: | ||
portablePdbToEmbed = portablePdbBlob; | ||
portablePdbToEmbed = emitBuilders.PortablePdbBlobBuilder; | ||
} | ||
else | ||
{ | ||
// write to Portable PDB stream: | ||
Stream portablePdbStream = getPortablePdbStreamOpt(); | ||
Stream? portablePdbStream = getPortablePdbStreamOpt(); | ||
if (portablePdbStream != null) | ||
{ | ||
try | ||
{ | ||
portablePdbBlob.WriteContentTo(portablePdbStream); | ||
emitBuilders.PortablePdbBlobBuilder.WriteContentTo(portablePdbStream); | ||
} | ||
catch (Exception e) when (!(e is OperationCanceledException)) | ||
{ | ||
|
@@ -195,7 +247,7 @@ internal static bool WritePeToStream( | |
} | ||
} | ||
|
||
DebugDirectoryBuilder debugDirectoryBuilder; | ||
DebugDirectoryBuilder? debugDirectoryBuilder; | ||
if (pdbPathOpt != null || isDeterministic || portablePdbToEmbed != null) | ||
{ | ||
debugDirectoryBuilder = new DebugDirectoryBuilder(); | ||
|
@@ -209,7 +261,7 @@ internal static bool WritePeToStream( | |
// Emit PDB Checksum entry for Portable and Embedded PDBs. The checksum is not as useful when the PDB is embedded, | ||
// however it allows the client to efficiently validate a standalone Portable PDB that | ||
// has been extracted from Embedded PDB and placed next to the PE file. | ||
debugDirectoryBuilder.AddPdbChecksumEntry(context.Module.PdbChecksumAlgorithm.Name, portablePdbContentHash); | ||
debugDirectoryBuilder.AddPdbChecksumEntry(context.Module.PdbChecksumAlgorithm.Name!, portablePdbContentHash); | ||
} | ||
} | ||
|
||
|
@@ -234,9 +286,9 @@ internal static bool WritePeToStream( | |
var peBuilder = new ExtendedPEBuilder( | ||
peHeaderBuilder, | ||
metadataRootBuilder, | ||
ilBuilder, | ||
mappedFieldDataBuilder, | ||
managedResourceBuilder, | ||
emitBuilders.IlBlobBuilder, | ||
emitBuilders.MappedFieldDataBlobBuilder, | ||
emitBuilders.ManagedResourceBlobBuilder, | ||
CreateNativeResourceSectionSerializer(context.Module), | ||
debugDirectoryBuilder, | ||
CalculateStrongNameSignatureSize(context.Module, privateKeyOpt), | ||
|
@@ -245,29 +297,36 @@ internal static bool WritePeToStream( | |
peIdProvider, | ||
metadataOnly && !context.IncludePrivateMembers); | ||
|
||
var peBlob = new BlobBuilder(); | ||
var peContentId = peBuilder.Serialize(peBlob, out Blob mvidSectionFixup); | ||
// This needs to force the backing builder to zero due to the issue writing COFF | ||
// headers. Can remove once this issue is fixed and we've moved to SRM with the | ||
// fix | ||
// https://github.com/dotnet/runtime/issues/99244 | ||
emitBuilders.PortableExecutableBlobBuilder = PooledBlobBuilder.GetInstance(zero: true); | ||
var peContentId = peBuilder.Serialize(emitBuilders.PortableExecutableBlobBuilder, out Blob mvidSectionFixup); | ||
|
||
PatchModuleVersionIds(mvidFixup, mvidSectionFixup, mvidStringFixup, peContentId.Guid); | ||
|
||
if (privateKeyOpt != null && corFlags.HasFlag(CorFlags.StrongNameSigned)) | ||
{ | ||
strongNameProvider.SignBuilder(peBuilder, peBlob, privateKeyOpt.Value); | ||
Debug.Assert(strongNameProvider != null); | ||
strongNameProvider.SignBuilder(peBuilder, emitBuilders.PortableExecutableBlobBuilder, privateKeyOpt.Value); | ||
} | ||
|
||
try | ||
{ | ||
peBlob.WriteContentTo(peStream); | ||
emitBuilders.PortableExecutableBlobBuilder.WriteContentTo(peStream); | ||
} | ||
catch (Exception e) when (!(e is OperationCanceledException)) | ||
{ | ||
throw new PeWritingException(e); | ||
} | ||
|
||
emitBuilders.Free(); | ||
return true; | ||
} | ||
|
||
private static MethodInfo s_calculateChecksumMethod; | ||
private static MethodInfo? s_calculateChecksumMethod; | ||
|
||
// internal for testing | ||
internal static uint CalculateChecksum(BlobBuilder peBlob, Blob checksumBlob) | ||
{ | ||
|
@@ -282,7 +341,7 @@ internal static uint CalculateChecksum(BlobBuilder peBlob, Blob checksumBlob) | |
{ | ||
peBlob, | ||
checksumBlob, | ||
}); | ||
})!; | ||
} | ||
|
||
private static void PatchModuleVersionIds(Blob guidFixup, Blob guidSectionFixup, Blob stringFixup, Guid mvid) | ||
|
@@ -318,7 +377,7 @@ private static string PadPdbPath(string path) | |
return path + new string('\0', Math.Max(0, minLength - Encoding.UTF8.GetByteCount(path) - 1)); | ||
} | ||
|
||
private static ResourceSectionBuilder CreateNativeResourceSectionSerializer(CommonPEModuleBuilder module) | ||
private static ResourceSectionBuilder? CreateNativeResourceSectionSerializer(CommonPEModuleBuilder module) | ||
{ | ||
// Win32 resources are supplied to the compiler in one of two forms, .RES (the output of the resource compiler), | ||
// or .OBJ (the output of running cvtres.exe on a .RES file). A .RES file is parsed and processed into | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allowed me to avoid a nullable suppression on
getMetadataPeStreamOpt
below.