224 lines
7.2 KiB
Markdown
224 lines
7.2 KiB
Markdown
# 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*`.
|
|
|
|
``` csharp
|
|
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.
|
|
|
|
``` csharp
|
|
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:
|
|
|
|
``` csharp
|
|
[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:
|
|
|
|
``` csharp
|
|
[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:
|
|
|
|
``` csharp
|
|
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.
|
|
|
|
``` csharp
|
|
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:
|
|
|
|
``` csharp
|
|
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](https://github.com/dotnet/csharplang/issues/1565) 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.
|
|
|
|
|
|
|
|
|