Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
225 lines (161 sloc) 7.22 KB

Compiler Intrinsics

Summary

This proposal provides language constructs that expose low level IL opcodes that cannot currently be accessed efficiently, or at all: ldftn, ldvirtftn, ldtoken and calli. These low level opcodes can be important in high performance code and developers need an efficient way to access them.

Motivation

The motivations and background for this feature are described in the following issue (as is a potential implementation of the feature):

https://github.com/dotnet/csharplang/issues/191

This alternate design proposal comes after reviewing a prototype implementation of the original proposal by @msjabby as well as the use throughout a significant code base. This design was done with significant input from @mjsabby, @tmat and @jkotas.

Detailed Design

Allow address of to target methods

Method groups will now be allowed as arguments to an address-of expression. The type of such an expression will be void*.

class Util { 
    public static void Log() { } 
}

// ldftn Util.Log
void* ptr = &Util.Log; 

Given there is no delegate conversion here the only mechanism for filtering members in the method group is by static / instance access. If that cannot distinguish the members then a compile time error will occur.

class Util { 
    public void Log() { } 
    public void Log(string p1) { } 
    public static void Log(int i) { };
}

unsafe {
    // Error: Method group Log has more than one applicable candidate.
    void* ptr1 = &Log; 

    // Okay: only one static member to consider here.
    void* ptr2 = &Util.Log;
}

The addressof expression in this context will be implemented in the following manner:

  • ldftn: when the method is non-virtual.
  • ldvirtftn: when the method is virtual.

Restrictions of this feature:

  • Instance methods can only be specified when using an invocation expression on a value
  • Local functions cannot be used in &. The implementation details of these methods are deliberately not specified by the language. This includes whether they are static vs. instance or exactly what signature they are emitted with.

handleof

The handleof contextual keyword will translate a field, member or type into their equivalent RuntimeHandle type using the ldtoken instruction. The exact type of the expression will depend on the kind of the name in handleof:

  • field: RuntimeFieldHandle
  • type: RuntimeTypeHandle
  • method: RuntimeMethodHandle

The arguments to handleof are identical to nameof. It must be a simple name, qualified name, member access, base access with a specified member, or this access with a specified member. The argument expression identifies a code definition, but it is never evaluated.

The handleof expression is evaluated at runtime and has a return type of RuntimeHandle. This can be executed in safe code as well as unsafe.

RuntimeHandle stringHandle = handleof(string);

Restrictions of this feature:

  • Properties cannot be used in a handleof expression.
  • The handleof expression cannot be used when there is an existing handleof name in scope. For example a type, namespace, etc ...

calli

The compiler will add support for a new type of extern function that efficiently translates into a .calli instruction. The extern attribute will be marked with an attribute of the following shape:

[AttributeUsage(AttributeTargets.Method)]
public sealed class CallIndirectAttribute : Attribute
{
    public CallingConvention CallingConvention { get; }
    public CallIndirectAttribute(CallingConvention callingConvention)
    {
        CallingConvention = callingConvention;
    }
}

This allows developers to define methods in the following form:

[CallIndirect(CallingConvention.Cdecl)]
static extern int MapValue(string s, void *ptr);

unsafe {
    var i = MapValue("42", &int.Parse);
    Console.WriteLine(i);
}

Restrictions on the method which has the CallIndirect attribute applied:

  • Cannot have a DllImport attribute.
  • Cannot be generic.

Open Issues

CallingConvention

The CallIndirectAttribute as designed uses the CallingConvention enum which lacks an entry for managed calling conventions. The enum either needs to be extended to include this calling convention or the attribute needs to take a different approach.

Considerations

Disambiguating method groups

There was some discussion around features that would make it easier to disambiguate method groups passed to an address-of expression. For instance potentially adding signature elements to the syntax:

class Util {
    public static void Log() { ... }
    public static void Log(string) { ... }
}

unsafe {
    // Error: ambiguous Log
    void *ptr1 = &Util.Log;

    // Use Util.Log();
    void *ptr2 = &Util.Log();
}

This was rejected because a compelling case could not be made nor could a simple syntax be envisioned here. Also there is a fairly straight forward work around: simple define another method that is unambiguous and uses C# code to call into the desired function.

class Workaround {
    public static void LocalLog() => Util.Log();
}
unsafe { 
    void* ptr = &Workaround.LocalLog;
}

This becomes even simpler if static local functions enter the language. Then the work around could be defined in the same function that used the ambiguous address-of operation:

unsafe { 
    static void LocalLog() => Util.Log();
    void* ptr = &Workaround.LocalLog;
}

LoadTypeTokenInt32

The original proposal allowed for metadata tokens to be loaded as int values at compile time. Essentially have tokenof that has the same arguments as handleof but is evaluated at compile time to an int constant.

This was rejected as it causes significant problem for IL rewrites (of which .NET has many). Such rewriters often manipulate the metadata tables in a way that could invalidate these values. There is no reasonable way for such rewriters to update these values when they are stored as simple int values.

The underlying idea of having an opaque handle for metadata entries will continue to be explored by the runtime team.

Future Considerations

static local functions

This refers to the proposal to allow the static modifier on local functions. Such a function would be guaranteed to be emitted as static and with the exact signature specified in source code. Such a function should be a valid argument to & as it contains none of the problems local functions have today.

NativeCallableAttribute

The CLR has a feature that allows for managed methods to be emitted in such a way that they are directly callable from native code. This is done by adding the NativeCallableAttribute to methods. Such a method is only callable from native code and hence must contain only blittable types in the signature. Calling from managed code results in a runtime error.

This feature would pattern well with this proposal as it would allow:

  • Passing a function defined in managed code to native code as a function pointer (via address-of) with no overhead in managed or native code.
  • Runtime can introduce use site errors for such functions in managed code to prevent them from being invoked at compile time.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.