-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[API Proposal]: Embrace spans in System.Reflection.Metadata. #85280
Comments
Tagging subscribers to this area: @dotnet/area-system-reflection-metadata Issue DetailsBackground and motivation
API Proposalnamespace System.Reflection.Metadata;
public class BlobBuilder
{
public void WriteBytes(ReadOnlySpan<byte> buffer);
}
public class BlobContentId
{
public BlobContentId(ReadOnlySpan<byte> id);
public static BllobContentId FromHash(ReadOnlySpan<byte> hashCode);
}
public struct BlobReader
{
public void ReadBytes(Span<byte> buffer);
public void ReadUTF8(int byteCount, Span<char> buffer);
public bool TryReadUTF8(int byteCount, Span<char> buffer, out int charsWritten);
public void ReadUTF16(int byteCount, Span<char> buffer);
public bool TryReadUTF16(int byteCount, Span<char> buffer, out int charsWritten);
}
public struct BlobWriter
{
public void WriteBytes(ReadOnlySpan<byte> buffer);
}
public struct MetadataReader
{
public int GetBlobLength(BlobHandle handle);
public void GetBlobBytes(BlobHandle handle, Span<byte> buffer, out int bytesWritten);
public int GetStringLength(StringHandle handle);
public void GetStringBytes(StringHandle handle, Span<char> buffer, out int bytesWritten);
public int GetStringLength(NamespaceDefinitionHandle handle);
public void GetStringBytes(NamespaceDefinitionHandle handle, Span<char> buffer, out int bytesWritten);
public int GetStringLength(DocumentNameBlobHandle handle);
public void GetStringBytes(DocumentNameBlobHandle handle, Span<char> buffer, out int bytesWritten);
public int GetUserStringLength(UserStringHandle handle);
public void GetUserStringBytes(UserStringHandle handle, Span<char> buffer, out int bytesWritten);
// Or what about we add the following APIs instead of the above?
// The memory the span points to can be freed under our feet but
// this is an existing problem with many SRM APIs that safely wrap pointers.
// The library is expert-level either way.
public ReadOnlySpan<byte> GetBlobSpan(BlobHandle handle);
public ReadOnlySpan<byte> GetRawStringBytes(StringHandle handle);
public ReadOnlySpan<byte> GetRawStringBytes(NamespaceDefinitionHandle handle);
public ReadOnlySpan<byte> GetRawStringBytes(DocumentNameBlobHandle handle);
public ReadOnlySpan<byte> GetRawUserStringBytes(UserStringHandle handle);
}
namespace System.Reflection.Metadata.Ecma335;
public sealed class MetadataBuilder
{
// These APIs will not allocate if the blob/string already exists.
public BlobHandle GetOrAddBlob(ReadOnlySpan<byte> value);
public BlobHandle GetOrAddBlobUTF8(ReadOnlySpan<byte> value);
public BlobHandle GetOrAddBlobUTF16(ReadOnlySpan<byte> value);
public BlobHandle GetOrAddDocumentName(ReadOnlySpan<char> value);
public StringHandle GetOrAddString(ReadOnlySpan<char> value);
public UserStringHandle GetOrAddUserString(ReadOnlySpan<char> value);
} API UsageThe proposed APIs correspond to existing APIs that work with strings and (immutable) byte arrays. Their usage will be similar. Alternative DesignsDo nothing and either let users implement the span APIs on top of pointers if they are available, or accept the memory allocations if they aren't. There are also more possible APIs to be spanified (such as methods in the RisksNo response
|
@teo-tsirpanis can you provider an example of the caller using buffers (not backed by a simple array) where these APIs are advantageous? Thanks |
One use case would be #84580 (comment). |
@teo-tsirpanis the API proposals for BlobBuilder, BlobContentId, BlobWriter and MetadataBuilder looks good to me, for others more info needed, I left some questions as a comment. In general, a usage scenarios for them would be helpful for review. Also, I would prefer to split the proposals for public struct BlobReader
{
public byte[] ReadBytes(int byteCount)
public void ReadBytes(int byteCount, byte[] buffer, int bufferOffset)
+ public void ReadBytes(Span<byte> buffer);
public string ReadUTF8(int byteCount)
+ public void ReadUTF8(int byteCount, Span<char> buffer); // Why this passes byteCount but ReadBytes(Span<byte> buffer) not?
+ public bool TryReadUTF8(int byteCount, Span<char> buffer, out int charsWritten); // A uage scenarios will be helpful
public string ReadUTF16(int byteCount)
+ public void ReadUTF16(int byteCount, Span<char> buffer);
+ public bool TryReadUTF16(int byteCount, Span<char> buffer, out int charsWritten); // Usage scenarios will be helpful
} public struct MetadataReader
{
+ public int GetBlobLength(BlobHandle handle); // Why this needed? I guess for determining span size, anyway a user scenario will be useful
public byte[] GetBlobBytes(BlobHandle handle);
+ public void GetBlobBytes(BlobHandle handle, Span<byte> buffer, out int bytesWritten); // why do you need bytesWritten?
public string GetString(StringHandle handle);
+ public int GetStringLength(StringHandle handle); // usage scenarios
+ public void GetStringBytes(StringHandle handle, Span<char> buffer, out int bytesWritten);
+ public int GetStringLength(NamespaceDefinitionHandle handle);
+ public void GetStringBytes(NamespaceDefinitionHandle handle, Span<char> buffer, out int bytesWritten);
+ public int GetStringLength(DocumentNameBlobHandle handle);
+ public void GetStringBytes(DocumentNameBlobHandle handle, Span<char> buffer, out int bytesWritten);
+ public int GetUserStringLength(UserStringHandle handle);
+ public void GetUserStringBytes(UserStringHandle handle, Span<char> buffer, out int bytesWritten);
// Or what about we add the following APIs instead of the above?
// The memory the span points to can be freed under our feet but
// this is an existing problem with many SRM APIs that safely wrap pointers.
// The library is expert-level either way.
+ public ReadOnlySpan<byte> GetBlobSpan(BlobHandle handle);
+ public ReadOnlySpan<byte> GetRawStringBytes(StringHandle handle);
+ public ReadOnlySpan<byte> GetRawStringBytes(NamespaceDefinitionHandle handle);
+ public ReadOnlySpan<byte> GetRawStringBytes(DocumentNameBlobHandle handle);
+ public ReadOnlySpan<byte> GetRawUserStringBytes(UserStringHandle handle);
} public sealed class MetadataBuilder
{
public BlobHandle GetOrAddBlob(byte[] value)
// These APIs will not allocate if the blob/string already exists.
+ public BlobHandle GetOrAddBlob(ReadOnlySpan<byte> value);
public BlobHandle GetOrAddBlobUTF8(string value, bool allowUnpairedSurrogates = true);
+ public BlobHandle GetOrAddBlobUTF8(ReadOnlySpan<byte> value); // should it have allowUnpairedSurrogates parameter too?
public System.Reflection.Metadata.BlobHandle GetOrAddBlobUTF16(string value);
+ public BlobHandle GetOrAddBlobUTF16(ReadOnlySpan<byte> value);
public BlobHandle GetOrAddDocumentName(string value);
+ public BlobHandle GetOrAddDocumentName(ReadOnlySpan<char> value);
public StringHandle GetOrAddString(string value);
+ public StringHandle GetOrAddString(ReadOnlySpan<char> value);
public UserStringHandle GetOrAddUserString(string value);
+ public UserStringHandle GetOrAddUserString(ReadOnlySpan<char> value);
} Adding future milestone for now, I don't think all of these APIs ready for 8.0, we could change milestone if |
Missing the simplest one :) public sealed class MetadataReader
{
public unsafe byte* MetadataPointer { get; }
public int MetadataLength { get; }
+ public ReadOnlySpan<byte> MetadataSpan { get; }
} Not sure if this is that useful given the whole type is unsafe, but perhaps: public unsafe struct BlobReader
{
+ public BlobReader(ReadOnlySpan<byte> buffer);
} |
ILCompiler and its components could benefit from more |
My thought was that it would allow the users to get the buffer's size and prepare an appropriately sized buffer to pass to
On second thought |
public void GetStringChars(NamespaceDefinitionHandle handle, Span<char> buffer, out int bytesWritten); The out parameter should be named |
Updated, thanks. |
IMO I prefer the single |
Background and motivation
System.Reflection.Metadata
is a pretty perormance-oriented library but its API lacks methods that accept spans. I propose additional span overloads for methods that work with memory buffers. Some of these APIs can now be implemented efficiently thanks to the work started in #81059.API Proposal
API Usage
The proposed APIs correspond to existing APIs that work with strings and (immutable) byte arrays. Their usage will be similar.
Alternative Designs
Do nothing and either let users implement the span APIs on top of pointers if they are available, or accept the memory allocations if they aren't.
There are also more possible APIs to be spanified (such as methods in the
System.Reflection.Metadata.Ecma335.***Encoder
s), but the proposed APIs are the most foundational; the rest can be implemented by user code on top of these with little effort.Risks
No response
The text was updated successfully, but these errors were encountered: