-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Developers using reflection invoke should be able to use ref struct #45152
Comments
This isn't just for JSON, what we've we doing should work for any framework that uses reflection today I assume. I'm hoping to see a deep analysis here of the scenarios that Would use these APIs |
PowerShell could be benefit from this - it is based on reflection, emit and dynamic. |
As mentioned in some of the previous issues, and to add a note on the requirements as there is a reference to Reflection.Emit that might make this not clear: the solution should not involve any managed objects from |
We have discussed that these APIs should be efficient and avoid allocating lots of managed memory: #28001 (comment) . Avoiding any managed objects from |
Fair enough. I don't know the cases that would involve reflection, but as long as simple cases like accessing statically known e.g fields directly from IL via ldtoken, are working without, that's ok. |
How much work is this? Is there any way to make incremental progress in 6.0 towards it? I'm writing up something on the code generation techniques used in ASP.NET Core and for the future reference I'd like an idea of how our APIs would need to evolve. The closest I've found is @jkotas 's comment here #10057 (comment). |
We may be able to make some incremental progress on faster reflection in .NET by refactoring the internal implementation. We won't be able to expose the new faster reflection APIs for .NET 6. For that, we need #26186 that depends on https://github.com/dotnet/csharplang/blob/master/proposals/low-level-struct-improvements.md that was cut for .NET 6. |
Got it. For .NET 7 then. I think I understand how to model the APIs we need to take advantage of this. Unfortunately this and async are at odds, but we can probably optimize the sync path with this and box in the async path. Still that would mean we need a way to create a TypedReference from this boxed value in a way that won't explode because the new API won't handle type coercion i.e a mix of this API needs to support a mix of boxed and unboxed values somehow. I can provide examples if needed. |
Bumping again on the topic, more precisely for the support of A scenario example for cc: @jaredpar (for the question about Language + Roslyn support) |
I'm not sure how the language can implement |
It would probably have to be through ldflda and managed pointer difference. There's an issue discussing that if we were to allow |
Yes, at native compile time (so runtime in the case of JIT) the value can be computed easily (it is part of the struct layout computation). So having an intrinsic like
Right the problem is that this is something we can do efficiently at the IL level already today (ldtoken) but I fail to see how we can express it with an API. We could maybe do something like
Not sure, if ldflda is necessary here while we could implement it with an intrinsic directly. |
This was prototyped for V6 but due to lack of language support around The prototype is based on |
@steveharter if you start it now, we can finish it before .NET 7 preview 1 😄 |
Oh, right, I found that I have actually access to |
hehe, yep, that's exactly I found! 😅 |
My apologies for this very basic question: I have modified the
does not contain So I'm not sure how to run the test easily. Any tip on how to run it? |
The Core Root needs to be created. You can do this quickly by running the following:
|
@xoofx It is in the referenced file, but it is a term that isn't obvious - https://github.com/dotnet/runtime/blob/main/docs/workflow/testing/coreclr/testing.md#building-the-core_root |
Created a proof of concept PR #93946 with the (Edit: The struggle I had above running the tests is likely that originally, I compiled clr+libs in release while I took the default - debug when compiling the tests 🤦) |
I feel like adding a property called |
We added JIT support for some of these patterns in .NET 8 in #81998. See e.g. this example: https://godbolt.org/z/or76frsWs |
Shouldn't Without |
Actually, we could add UnsafeAccessor for Field and Method handles then instead to solve the lack of language features for fieldof and methodof and just expose the Offset as an intrinsic. |
We were talking the other day about how IL allows overloading fields, and hence how you couldn't have an unsafe accessor returning eg. a |
It could be solved by adding a fake parameter for return type just like there is one for declaring type. |
Yes, this would need to be thought through if the raw offsets are enabled for classes, and not just structs. Another problem with classes is that field offset is not always available. #28001 has related discussion. |
Couldn't we add instead a new intrinsic to public static ref T AddByteOffset<T>(object obj, nint offset);
// ldarg.0, ldarg.1, add, ret |
I would think it couldn't be implemented like that, since it's invalid IL, but if there was a way to get the reference to field 0 from and object, and we did that after If we got this API (which I personally would like to get), it would be great if we also got the corresponding inverse API (should both of these be public static object? GetObject<T>(ref T reference, out nuint offset); |
Just tested and calling the following code is working with .NET 7 JIT, but I haven't checked the specs in a while for such operation. One unknown I have with CoreCLR JIT/GC is what is seen after the add: is it an object ref, or is it a ref T. If it is the former, that could create GC corruption.
|
As far as I know, the type you declare in C#/IL doesn't matter. As far as the GC is concerned, that is simply "some GC pointer". In this case, it'll fall inside the data of an object, hence it's an interior pointer, so during mark the GC will consider the entire object as reachable, that's it. Shouldn't really cause any problems. In fact, reinterpreting the type of interior pointers (or GC refs in general) is quite common for various reasons. |
An additional benefit of providing |
This is invalid IL. If you run it on checked JIT, you will see all sorts of asserts. The first one is:
It is likely that that the JIT can either crash or produce bad code when this gets method inlined into a callsite of a particular shape. |
Fair enough. We definitely don't want invalid IL 😅 So, exposing I'm ok with |
My opinion is that the following set of APIs makes the most sense: namespace System.Runtime.CompilerServices
{
public static class RuntimeHelpers
{
public static ref byte GetRawData(object? o);
public static object? GetObject(ref byte reference, out nuint offset);
}
} This provides the forward and reverse API to convert between byrefs and objects. The lack of Note When the documentation is written for these pair of APIs, we will need to consider their behaviour with strings and arrays - currently they would probably return a ref to the start of the length field - if this is how we want it to work (which probably makes sense), then we should document the offset from the length field to the first entry in these cases so people can use it correctly for these OR we could document that it's undefined to use these APIs and then try to determine/select what specific index it's at (this would allow us to change them to be 64 bit length in the future if needed), which could make sense since there are other APIs for working with these special cases in the "intended" way when indexing is desired. |
Linking Roslyn issue dotnet/roslyn#68000 where you can't get an offset of a |
We now have the PR at #93946 which adds But we need a champion to create the API issue for the |
I simply reopened #28001 since it already has a bunch of discussion and it's exactly that proposal. I cleared the milestone and marked untriaged. |
Support
ref struct
and "fast invoke" by adding new reflection APIs. The new APIs will be faster than the currentobject[]
boxing approach by leveraging the existingSystem.TypedReference<T>
.TypedReference
is a special type and is super-fast because since it is aref struct
with its own opcodes. By extending it with this feature, it provides alloc-free, stack-based “boxing” with support for all argument types (reference types, value types, pointers and ref structs [pending]) along with all modifiers (byval, in, out, ref, ref return). Currently reflection does not support passing or invoking aref struct
since it can’t be boxed toobject
; the new APIs are to supportref struct
with new language features currently being investigated.Example syntax (actual TBD):
Dependencies
The Roslyn and runtime dependencies below are required for the programming model above. These are listed in the order in which they need to be implemented.
ref
field support in .NET runtimes #63768ref struct
as a generic argument. This would be used when adding the static factory methodpublic static TypedReference CreateFromRefStruct<T>(ref T myRefStruct) where T : ref struct
which basically wraps__makeref(myrefstruct)
. It could also be used to enableSpan<T> where T : ref struct
which is the ideal stack-based collection implementation that can containTypedReference
s.params
. The new invoke APIs require a collection ofTypeReference
s. There are several possible implementations; the most normalized solution would be supportingSpan<T> where T : ref struct
thus enabling anyref struct
(not justTypedReference
) to be used in a container. UPDATE: done in prototype per [API Proposal]: byref parameter collection for invoke #75349TypedReference
. (Roslyn link TBD). Ideally,TypedReference
is a normalref struct
. Note thatTypedReference
currently hasByReference<byte>
to contain an interior pointer to the value so this will likely need a different type. Also,TypedReference
has several compile-time limitations including not be able to be passed to another method that should be removed resulting in only standardref struct
semantics.ref
fields.Motivation
Reflection is ~20x slower than a Delegate call for a typical method. Many users including our own libraries use IL Emit instead which is non-trivial and error-prone. The expected gains are ~10x faster with no allocs; verified with a prototype. Internally, IL Emit is used but with a proposed slow-path fallback for AOT (non-emit) cases. The existing reflection invoke APIs may also layer on this.
In Scope
APIs to invoke methods using
TypedReference
including passing aTypedReference
collection.TypedReference
must be treated as a normalref struct
(today it has nuances and special cases).Support ref struct (passing and invoking).
Performance on par with existing ref emit scenarios:
To scope this feature, the minimum functionality that results in a win by allowing
System.Text.Json
to remove its dependency to System.Reflection.Emit for inbox scenarios.Out of Scope
This issue is an incremental improvement of reflection by adding new Invoke APIs and leveraging the existing
TypedReference
while requiring some runtime\Roslyn changes. Longer-term we should consider a more holistic runtime and Roslin support for reflection including JIT intrinsics and\or new "dynamic invoke" opcodes for performance along with perhaps C# auto-stack-boxing to\from aref TypedReference
.Implementation
A design doc is forthcoming.
The implementation will likely cache the generated method on the corresponding
MethodBase
andMemberInfo
objects.100% backwards compat with the existing object[]-based Invoke APIs is not necessary but will be designed with laying in mind (e.g. parameter validation, special types like ReflectionPointer, the
Binder
pattern, CultureInfo for culture-aware methods) so that in theory the existing object[]-based Invoke APIs could layer on this new work.This issue supersedes other reflection performance issues that overlap:
The text was updated successfully, but these errors were encountered: