-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is an alternate proposal for the compiler intrinsicts feature: #191 dotnet/roslyn#11475 This alternate design proposal comes after reviewing a prototype implementation of the original proposal by @mjsabby as well as the use throughout a significant code base. This design was done with significant input from @mjsabby, @tmat and @jkotas.
- Loading branch information
Showing
1 changed file
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
# Compiler Intrinsics | ||
|
||
* [x] Proposed | ||
* [ ] Prototype: Not Started | ||
* [ ] Implementation: Not Started | ||
* [ ] Specification: Below | ||
|
||
## 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 op | ||
codes 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 isssue (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 static | ||
- ldvirtftn: when the method is an instance method. | ||
|
||
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` using the `ldtoken` instruction. | ||
|
||
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 `handelof` name in scope. For | ||
example a type, namespace, etc ... | ||
|
||
### calli | ||
|
||
The compiler will add support for a new type of `extern` function that efficients translates into | ||
a `.calli` instruction. The exten attribute will be marked with an attribute of the following | ||
shape: | ||
|
||
``` csharp | ||
[AttributeUsage(AttributeTargets.Method)] | ||
public sealed class CallIndirectAttribute : Attribute | ||
{ | ||
public CallingConvention CallingConvention { get; set; } | ||
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); | ||
} | ||
``` | ||
|
||
## Considerations | ||
|
||
### Disambiguating method groups | ||
|
||
There was some discussion around features that would make it easier to disambiguate method groups | ||
passed to an addressof 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, possible local function, that is unambiguous and uses C# code to call into the | ||
desired function. | ||
|
||
``` csharp | ||
unsafe { | ||
static void LocalLog() => Util.Log(); | ||
void* ptr = &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. | ||
|