This project is read-only.

Indirect function call.

Topics: APIs, C# Language Design
Oct 18, 2014 at 5:41 PM
Edited Oct 18, 2014 at 5:42 PM
Hi Everybody,

In IL its possible to call a function indirect with it´s pointer.

Would it be possible to introduce this in roslyn?

Something like...
public WglWrapper
{
     public indirect WglIndirectFunc(IntPtr arg1, ref bool arg2);
     
     public WglWrapper()
     {
           IntPtr pointerToFunc;
           WglIndirectFunc = pointerToFunc
     }
}
I know I wouldnt have no longer type-safety... maybe someone has a better idea.
The delegate approch is to slow in most cases.

Are there tutorials for implementing a new keyword down to defining Il-Code?

Thanks.
Oct 19, 2014 at 1:43 AM
The delegate approch is to slow in most cases.
are you serious about that one?

http://en.wikipedia.org/wiki/Delegate_(CLI)#Performance
Performance of delegates used to be much slower than a virtual or interface method call (6 to 8 times slower in Microsoft's 2003 benchmarks),[1] but, since the .NET 2.0 CLR in 2005, it is about the same as interface calls
Oct 19, 2014 at 3:28 AM
My own benchmarking yielded the following results:
Direct Static:      00:00:02.5026562
Direct Instance:    00:00:02.8384719
Virtual:            00:00:07.5557671
Overriden:          00:00:07.5496939
Interface:          00:00:10.8050516
Delegate:           00:00:12.9558089
Indirect:           00:00:07.5553939
The test was Int32.MaxValue iterations of a call with a signature of int32(int32,int32) that does nothing but return 0. I stubbed the indirect call in C# and used ildasm/ilasm to insert the ldftn and calli instructions.

So, yeah, an indirect call is a little less than twice as fast as a call through a delegate. However, you're talking differences in the realm of esoteric.

Also, note that the rules around indirect calling is quite strict. From what I understand if the calli opcode is not immediately preceded by the ldftn opcode that the assembly is considered non-verifiable, so you wouldn't be able to save off the function pointer and pass it around like a delegate anyway.

If you really need that degree of performance I would say that you'd be much better off with c. Or maybe check out d.
Oct 19, 2014 at 11:34 AM
Edited Oct 19, 2014 at 11:45 AM
Take a look at SharpDX they are using a postprocessing step to generate very performant interop code (in fact pretty close to the most performant you can get with raw IL).

They analyzed the code which managed C++ generated and use IL postprocessing to reproduce similar IL from C# source, its a bit awkward but much more practical than extending the C# language itself with random features which probably won't go into the standard language anyways.

The idea is to identify the patterns MC++ generates, then in the C# source use stub methods with the proper signatures. In the postprocessing step you search for all calls to these stub methods and replace the calling IL by the MC++ IL pattern you identified.
Oct 24, 2014 at 8:29 AM
Sorry for the delay. Currently I do it via a postprocess, but I thought it would be a nice gadget. Is there any further informations about this topic?
Oct 28, 2014 at 12:01 PM
Edited Oct 28, 2014 at 12:01 PM
There is an old article from the SharpDX guys which explains a lot how they do stuff; but other than that, no, there is not much information around about it.
Nov 3, 2014 at 7:33 PM
First off, you are correct that in scenarios where every cycle matters, calls through delegates impose a significant penalty. For the vast majority of developers this penalty is so tiny compared to the amount of work that the method will actually do that it is not worth considering. However, if you imagine very low-level interop scenarios where you've got big tables of raw method pointers that you need to invoke, the expense in memory of building a delegate around the pointers, and then invoking each via the delegate, could be expensive. The proposed feature idea would be most useful in the sorts of scenarios that Joe Duffy describes here: http://joeduffyblog.com/2013/12/27/csharp-for-systems-programming/

You are also correct to note that there is no way in C# today to generate the calli instruction.

Many years ago when I was on the C# compiler team I investigated adding a feature to C# that would allow you to generate a calli instruction on a void* or IntPtr. The proposed feature involved creating a syntax which would allow the IntPtr to be marked as having a particular signature -- the jitter needs to know the calling convention and other details to ensure that the stack is not misaligned and so on.

Your question was "is it possible to introduce this feature?" Yes, it is possible; we know it is possible because we did it. Ultimately we decided to not put the feature into the mainstream C# language as the number of people who would actually benefit from the feature was very small. For most features the question is not "can we do it?" but rather "is this the best way to spend our limited time and effort?" It was a good enough feature that I spent some effort prototyping it, but not so good that we felt we could cut a better feature to get it in.
Nov 3, 2014 at 9:28 PM
Eric, thank you for detailed comments. I too would like to see "calli" support in C#, even though I would only use it indirectly via OpenTK and SharpDX. Have you ever looked at these projects? They're modifying the compiled assemblies post-build to add the calli instruction. These libraries would probably be the main users of such a feature.

You say that the feature had been designed at some point, but didn't make it because of time constraints. I understand that - there are many things to do that benefit a far larger audience of C# programmers. However, your argument is no longer necessarily true. After all, Roslyn in now open source, so the C# team could now rely on the community to implement such a feature. It seems to me that the feature is very limited in scope and should not have many interactions with other language features. It might be the perfect testbed for a first community-implemented feature.

So I'm wondering: Is there any chance that you could provide us with the spec that you came up with all these years ago? That could serve as a starting point for a potential prototypical implementation and since it is already known to the C# team (if they remember, of course), it might actually have a chance of being approved into the language.
Nov 3, 2014 at 9:51 PM
Expandable wrote:
Eric, thank you for detailed comments. I too would like to see "calli" support in C#, even though I would only use it indirectly via OpenTK and SharpDX. Have you ever looked at these projects? They're modifying the compiled assemblies post-build to add the calli instruction. These libraries would probably be the main users of such a feature.
How would the cost of performing an indirect call that way compare with the cost of making a call to an interface method? For example, instead of having code receive and invoke an Action<int>, have an interface
interface IAction<T1>
{
  void Invoke(T1 p1);
}
and have a class which will only need to export one method invoke that type.
Nov 3, 2014 at 10:09 PM
Edited Nov 3, 2014 at 10:10 PM
What's your point, supercat? According to Halo_Four's micro benchmarks above, indirect calls are faster than interface or delegate calls. Plus we're talking specifically about managed<->native transitions, so interfaces are irrelevant here.
Nov 4, 2014 at 1:18 AM
Expandable wrote:
What's your point, supercat? According to Halo_Four's micro benchmarks above, indirect calls are faster than interface or delegate calls. Plus we're talking specifically about managed<->native transitions, so interfaces are irrelevant here.
Rather than trying to extend the language to support raw function pointers in some fashion I think it would be more beneficial to see effort put into the JIT engine to optimize for those common case delegate scenarios. The majority of the time you are talking about one target per invocation and the compiler/IL likely gives enough information as a hint to the JITer. While probably more complicated to design and less directly exploitable by code I think it would be a bigger win given how much simple single-invocation delegates are utilized by LINQ. There is also precedent for the JIT attempting optimizations beyond what is specified by the IL such as with tail-calls.
Nov 4, 2014 at 7:51 PM
Expandable wrote:
What's your point, supercat? According to Halo_Four's micro benchmarks above, indirect calls are faster than interface or delegate calls. Plus we're talking specifically about managed<->native transitions, so interfaces are irrelevant here.
I didn't see mention of the managed/native issue prior to the above statement; I thought you were seeking to speed up behavior of common situations which presently use delegates; the speed advantage of interfaces over delegates isn't as great as the difference with function pointers, but I would think such advantage would be achievable in more situations.

Incidentally, when a delegate is invoked does the system spend any time checking whether it has multiple targets? If so, I would think efficiency of single-target delegates could be enhanced by having the target field of each multi-target delegate identify an array containing all the delegates, and have the method-pointer field identify a static method which would execute all the delegates in the array. To implement such a design now would require that delegate properties check whether the Method is that static method and--if so--interpret the array in such fashion as to emulate existing behaviors, but unless code uses Reflection to access delegate internals directly (which I would expect to be rare) I wouldn't expect compatibility problems.
Nov 12, 2014 at 12:48 PM
Edited Nov 12, 2014 at 12:54 PM
@EricLippert: I'd really like to see the solution you came up with. I've been thinking about the issue some more, and to me it seems that there is an even easier route to add support for indirect calls to C# that is very closely aligned to PInvoke and should be very simple to add to the compiler as it requires no new language constructs: It requires just some new code gen functionality to emit the calli instruction and it has to know about a new IndirectCallAttribute in addition to DllImportAttribute on extern methods:
class NativeMethods
{
    // Regular PInvoke, as always
    [DllImport("Lib", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Add(int a, int b);

    // Indirect Call similar to PInvoke
    [IndirectCall(CallingConvention.Cdecl)] 
    public static extern int AddIndirect(IntPtr funcPtr, int a, int b);
}

// PInvoke usage:
var result = NativeMethods.Add(1, 2);

// Indirect call usage:
var ptr = /* obtain the function pointer somehow */;
var result = NativeMethods.AddIndirect(ptr, 1, 2);
C# already supports extern methods, so we could reuse them here and apply a new IndirectCallAttribute (is there any other use of extern methods besides DllImport and internal CLR calls?). The compiler would add a body for the extern method marked with IndirectCallAttribute at compile time, generating the appropriate calli instruction. The first parameter of a function marked with IndirectCallAttribute must be of type System.IntPtr or void* and represents the target of the invocation, i.e., the function pointer to call. The calling convention specified in the attribute must of course match the calling convention of the invoked function, otherwise all sorts of bad things might happen (note that the CLR seems to do no stack validation whatsoever, in contrast to regular PInvoke calls).

I would be an error to mark a method with both DllImportAttribute and IndirectCallAttribute. Similar to DllImportAttribute, it would be an error to mark a non-static, non-extern method with IndirectCallAttribute.

Backwards compatibility with older versions of the framework (that of course miss the IndirectCallAttribute) could be achieved by allowing the user to specify his own version of the attribute.

Any comments?
Nov 13, 2014 at 2:01 PM
I think what you need something like Visual Studio on UserVoice, but for Roslyn.
As for the feature, I don't understand why I can't use feature that already exists in CLR.