Skip to content
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

An arbitrary array/span of references #38488

Closed
IS4Code opened this issue Jun 27, 2020 · 9 comments
Closed

An arbitrary array/span of references #38488

IS4Code opened this issue Jun 27, 2020 · 9 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Reflection
Milestone

Comments

@IS4Code
Copy link

IS4Code commented Jun 27, 2020

Background and Motivation

In scenarios like reflection or interaction with other languages or platforms, there is not a generically usable and performant way to handle variable number of arbitrary arguments. C# provides syntax like params object[] or params T[] (and might support params Span<T> in the future), but that works best in cases where the parameters are homogenous. ref parameters are especially cumbersome to translate to params, as it requires copying the value back and forth in case the called method modifies it.

Prime targets are Delegate.DynamicInvoke or MethodInfo.Invoke. Here the call could be translated to invoking a method with any number of parameters, normal of ref. Calling a method with a single ref int argument requires 3 heap allocations – one for the array, one for boxing the argument as the input, and one for boxing the argument as the output.

This proposal tries to provide a way to solve this issue, without significant modifications to the runtime, and if that is deemed inappropriate, to instigate discussion about other and better options.

Proposed API

The core of this proposal is a type that is conceptually similar to Span<TypedReference*>, if that were a valid type.

namespace System.Runtime.CompilerServices
{
    public unsafe ref struct ByRefSpan
    {
        readonly void** data; readonly int length;
        public ByRefSpan(TypedReference** data, int length) { this.data = data; this.length = length; }
        public int Length => length;
        public TypedReference this[int index] => *(TypedReference*)data[index];
        public ref T Get<T>(int index) => ref __refvalue(this[index], T);
        public Type GetType(int index) => __reftype(this[index]);
    }
}

This is also an example implementation in C# (if returning TypedReference was possible in case of the indexer). Methods of Span<T> could also be ported to ByRefSpan.

Usage Examples

Cooperation from specific languages is needed to make the usage of this type more "beautiful", but this prepares the ground:

void DynamicMethod(ByRefSpan args)
{
    args.Get<int>(0) = 1;
    args.Get<bool>(1) = true;
    args.Get<string>(2) = "1";
}

unsafe void Caller()
{
    int arg1 = default; bool arg2 = default; string arg3 = default;

    TypedReference arg1ref = __makeref(arg1), arg2ref = __makeref(arg2), arg3ref = __makeref(arg3);
    var args = stackalloc void*[3] { &arg1ref, &arg2ref, &arg3ref};

    DynamicMethod(new ByRefSpan((TypedReference**)args, 3));
}

In a potential future where C# supports params ByRefSpan, the boilerplate code in Caller could easily be simplified to DynamicMethod(ref arg1, ref arg2, ref arg3);

Alternative Designs

At first I experimented with simple stackalloc TypedReference[3], but the runtime doesn't track stack space allocated this way (meaning GC ignores any references to objects in it), so until such a change is made (which would enable things like stackalloc (int, string)[10] and more), or until it is possible to have a fixed-size by-value array as a local variable or another way to statically group locals together as a span, individual TypedReference variables must be used and the double reference is needed. In essence, a tuple type like (TypedReference, TypedReference, TypedReference) would be needed.

There is also place for ByRefSpan<T> storing T** in a similar fashion. However, there is no way in C# I am aware of to use such a type, so first something like the internal ByReference<T> has to be exposed.

Another option is to resurrect and improve __arglist, RuntimeTypeHandle and ArgIterator, however those are less efficient and exist only for interaction with unmanaged code. Being able to construct ByRefSpan from RuntimeArgumentHandle would be nice though.

Risks

This type is used to store a pointer to potentially stack-allocated data, like Span<T>, but the risks of a value escaping the stack are solved by using ref struct. The only other risks stem from the way this type is used, which would, hopefully, be mitigated by improvements in the language.

@IS4Code IS4Code added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jun 27, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Memory untriaged New issue has not been triaged by the area owner labels Jun 27, 2020
@SingleAccretion
Copy link
Contributor

Is the core issue related to the language here? I do have to say, the concentration of unsafe code and undocumented keywords in the usage example is higher than most people are perhaps used to, even considering the advanced nature of the API.

@GrabYourPitchforks
Copy link
Member

Repathing this to the GC, because I'm pretty sure it would require a significant GC overhaul to support arrays / spans of ref structs. @Maoni0 would be able to provide a better analysis.

@ghost
Copy link

ghost commented Jun 27, 2020

Tagging subscribers to this area: @Maoni0
Notify danmosemsft if you want to be subscribed.

@IS4Code
Copy link
Author

IS4Code commented Jun 27, 2020

@SingleAccretion
Realistically, the language team would have to do the majority of the work to make this type widely usable and applicable, but the same could be said for Span<T>. I thought this would be good starting place, as this is a feature every .NET language could make eventually find use of.

Similarly to Span<T>, existing APIs could be extended to support ByRefSpan, like DynamicInvoke etc.

Edit: Note that spans of ref struct would be helpful to have in this case, but it could be implemented as a span of pointers to ref structs (TypedReferences) located on the stack.

@SingleAccretion
Copy link
Contributor

Realistically, the language team would have to do the majority of the work to make this type widely usable and applicable, but the same could be said for Span<T>

Oh, well, you probably already know this, but this makes it extremely unlikely for this to even be considered. The bar for language features is very high, and this one:

  • Requires runtime support (or at least it seems like it does, correct me here if I am wrong).
  • Requires language support.
  • Has limited use cases.

Also of note here is that as a platform .NET is kind of moving away from late binding/reflection, so...

@IS4Code
Copy link
Author

IS4Code commented Jun 27, 2020

Also of note here is that as a platform .NET is kind of moving away from late binding/reflection, so...

That is unfortunate to hear. The reflection API is basically stuck in .NET 1 in its capabilities, everything being either object, object[] or Type[]. More generics, more references, things like #23716 etc. are necessary if you want to do anything serious.

@jkotas
Copy link
Member

jkotas commented Jun 27, 2020

Repathing this to the GC, because I'm pretty sure it would require a significant GC overhaul to support arrays / spans of ref structs

As long as these Span<TypedReference>s live on stack, it does not require any special GC support.

the language team would have to do the majority of the work to make this type widely usable and applicable

Correct, this would need language support to be a safe, widely usable construct. It does not require any special language support if this was an low-level building block that requires you to know the rules to use it safely.

CoreRT has an internal building block like that: https://github.com/dotnet/corert/search?q=LocalVariableSet. Before he left, Ati looked into turning it into a public API as one of the possible strategies to make Reflection Invoke faster.

Related: #26186

@GrabYourPitchforks
Copy link
Member

Then we can spin up an internal prototype perhaps using #12832 as a test case. It'll look a bit rough without compiler support, but it should at least tell us whether or not the scenario works.

@steveharter
Copy link
Member

Closing this issue in favor of #75349 which will support a stack-based only approach.

@IllidanS4 thanks for the API suggestions. Those APIs are effectively block until we can get support for generic parameters for byref-like types.

Repository owner moved this from Future to Done in Triage POD for Reflection, META, etc. Dec 5, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Jan 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Reflection
Projects
No open projects
Development

No branches or pull requests

8 participants