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

Feature request: New IL instruction or new IL pattern to represent an invocation of a method implementation found in specific base type #12886

Open
AlekseyTs opened this issue Jun 13, 2019 · 11 comments

Comments

@AlekseyTs
Copy link
Contributor

In C# and VB we are planning to add support for a special expression representing an invocation of an implementation of a particular virtual method from a specific base type. This includes virtual methods declared in classes as well as in interfaces.

Compilers have to make sure that there is a most specific implementation of the method in the specified base type, and, in that case, emit an instruction that “says”: “Runtime, call the most specific implementation of this method in the type specified. If the method is an interface method, the implementation method should be called with a virtual dispatch. If the method is a class method, the implementation method should be called non virtually.”

The IL instruction or the pattern should capture the following information about the target of the invocation:

  • A receiver
  • A method which implementation should be found and invoked. The receiver should derive (including interface implementations) from the type declaring the method.
  • A type in which to look for the implementation. Receiver should derive from this type, this type should be the same or derive from the type declaring the method.

Possibly a usage of constrained prefix can be expanded. Or a new IL instruction added. Also, we should make sure that it is possible to create function pointers that capture the same semantics, primary usage scenarios - creation/invocation of delegates.

@jzabroski
Copy link
Contributor

jzabroski commented Jun 20, 2019

Something to think about before diving too deep into a new IL instruction: .NET currently does not support the composition of open instance delegates to generic interface methods.

Some background: http://higherlogics.blogspot.com/2011/08/open-instance-delegate-for-generic.html

While the linked blog post was written in 2011, it remains true on netcoreapp2.2 today. - Just tested to make sure.

Similarly, before adding a bunch of magic, maybe add a regression suite on this stuff. Developers hate when syntactically valid programs are semantically not supported.

@mburbea
Copy link

mburbea commented Jul 5, 2019

@jzabroski,
This is true. But my awful solution suggested on stackoverflow still does work.

public class C
{
    public virtual void DoNothing<T>()=> Console.WriteLine(typeof(T));
}

var dynamicMethod = new DynamicMethod("a", typeof(void), new[] { typeof(C) }, typeof(object), true);
var ilgen = dynamicMethod.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldvirtftn, typeof(C).GetMethod("DoNothing").MakeGenericMethod(typeof(int)));
ilgen.Emit(OpCodes.Calli, SignatureHelper.GetMethodSigHelper(CallingConventions.HasThis, typeof(void)));
ilgen.Emit(OpCodes.Ret);
(dynamicMethod.CreateDelegate(typeof(Action<C>)) as Action<C>).Invoke(new C())

Now why does calli support it and not delegates? Who knows.

@jzabroski
Copy link
Contributor

@mburbea What SO post are you referring to? Thanks.

@mburbea
Copy link

mburbea commented Jul 5, 2019 via email

@jzabroski
Copy link
Contributor

jzabroski commented Jul 5, 2019

OK, my point is that open delegates should use the same dispatch logic as the rest of the CLR, unless there is a good reason not to.

@AlekseyTs said at the end of his request:

Also, we should make sure that it is possible to create function pointers that capture the same semantics, primary usage scenarios - creation/invocation of delegates.

Rather than add new instructions or patterns, we should probably first revisit what and why the CLR does what it does today. The only clue is this paragraph from the ECMA-335 CLI specification (emphasis mine):

Delegates shall be declared sealed, and the only members a delegate shall have are either the first two or all four methods as specified here. These methods shall be declared runtime and managed (§II.15.4.3). They shall not have a body, since that body shall be created automatically by the VES. Other methods available on delegates are inherited from the class System.Delegate in the Base Class Library (see Partition IV).

My hunch has always been that the constraint that the Virtual Execution System (VES) causes this to not be possible, presumably because it is too low-level to know how to handle such things. But I've not investigated. I guess now that it's open source it's possible to investigate. My second hunch is that calli bypasses the VES entirely.

@mattwarren
Copy link
Contributor

But I've not investigated. I guess now that it's open source it's possible to investigate.

@jzabroski This post I wrote How do .NET delegates work? might help you get started if you want to investigate (I don't think the post on its own answers your question, but it might help)

@davidwrighton
Copy link
Member

davidwrighton commented Dec 16, 2019

These are my thoughts on the additional runtime support needed for the base(T) feature request.

We should be able to support this with a variation on the constrained prefix. This is my idea of what the spec would generally look like.

.constrained <type> call <method>
Stack Transition:
..., this reference, arg1, arg2, arg3, argN -> ..., retVal (not always returned)

Description:
The .constrained <type> call <method> instruction calls implementation of the method indicated by the descriptor method on the type <type>. This sequence may be preceded by the .tail prefix to specify that the current method state should be released before transferring control. If <type> is an interface, the target method is chosen using the default interface method resolution rules where <type> is the most derived interface. If <type> is not an interface type, the target method is resolved by computing the normal virtual resolution algorithm as if it was called on an object of runtime type <type>, and calling that method. If <method> is an interface method, and the resolution would be ambiguous, throw an AmbiguousMatchException (as with other failed Default Interface Method resolution failures). If the resolution does not find a target method body, throw an System.EntryPointNotFoundException.

If <type> is an interface type, <method> must be defined on an interface type.
If <type> is not an interface type, <method> must be defined on a class.

Add note about how <method> resolves that matches the details of method token resolution as specified in the call instruction

Correctness:
Correct CIL ensures that the <method> is defined on <type> or on a superclass of <type>. <type> must be a reference type. <method> must be a virtual or abstract function. <method> must be an instance function.

Verifiability:
Verification checks to ensure that <type> is a superclass of the type that defines the method being compiled, and that <method> is accessible to the method providing IL. and the types of the objects on the stack are consistent with the types expected by the method call. It also verifies that the first parameter is this pointer if the current IL method body is defined on a reference type, or a boxed instance of the current method bodie's type.

@jzabroski
Copy link
Contributor

@davidwrighton Can you edit your post to fix the <T> references? It makes it hard to grok what you were saying, and took me awhile to figure out what you were saying. You can use the backtick operator for inline code on Github. Thanks in advance.

@IS4Code
Copy link

IS4Code commented Jan 16, 2020

How is this different from simple call?

@davidwrighton
Copy link
Member

The difference is that the actual method called may not be the same as the method referenced by the instruction, if the method is overrided by the type it is constrained to. The primary purpose for the new instruction is around behavior of default interface methods, and the multiple-inheritance style overriding that is available through their use. (Although there are some more subtle benefits for base calls on classes.)

@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@msftgits msftgits added this to the Future milestone Jan 31, 2020
@hamarb123
Copy link
Contributor

What is the state of this feature currently? I'm really looking forward to it!

cc @davidwrighton @MichalStrehovsky

@MichalStrehovsky MichalStrehovsky removed their assignment Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants