Closed gafter closed 2 years ago
What are the advantages of compiler intrinsics for specific IL-opcodes instead of directly supporting inline-IL?
@Unknown6656 you don't have to modify the parser, only the emitter, so you get to reuse type checking.
At least for calli
and ldftn
, the static delegate
proposal (https://github.com/dotnet/csharplang/issues/302) would cover a way of using it outside of intrinsic support. However, the intrinsic support would still be useful for cases where users can't or don't want to change their API surface.
Need more motivation to prioritize this
Need more motivation to prioritize this
@MadsTorgersen What kind of motivation? I can buy beers to anyone who implements it if they need to motivate that way. There's plenty of use cases for this - see all the people referencing the issue at dotnet/roslyn#11475.
Could implementing this be an up for grabs issue in the Roslyn repo?
@MichalStrehovsky @MadsTorgersen Without any beer I could volunteer to implement it, however, due to my time limitations it could be only free time job.
Could implementing this be an up for grabs issue in the Roslyn repo?
Unlikely. We are still uncertain this is the design we want to take. I personally have a lot of reservations about the approach.
@jaredpar Can you describe what kind of reservations C# team has about this proposal?
IL as a language feature is a little bit of a cop-out I think.
@4creators the LDM notes capture most of the push back. Mostly though it's a feature that essentially lets you write IL in C#. If we want that as a feature I'd rather just support inline IL than almost IL for a few small cases.
But more importantly the main use cases for these features can be done with firstn class language features which are already under consideration. Would rather support static delegates
for instance and get the calli
support + have an allocation free delegate experience.
I would also think that these would be more useful as a general API (exposed in CoreFX and powered by CoreCLR, just like the hardware intrinsics), rather than as a language specific feature.
Being able to write IL code that isn't supported by the language in a easy to use/performant manner isn't just limited to C#.
IIRC, the LDM approved the feature as proposed in https://github.com/dotnet/roslyn/issues/11475.
@jaredpar If this has LDM approval, can we mark this as up for grabs? I also remember it being LDM approved from hallway conversations. The LDM meeting notes discussing this stop mid sentence, unfortunately.
I don't remember that we approved it directly. Instead we wanted to see if other features were going to get done and then come back to this if not. It's been a while since we discussed this and possible I'm misremembering.
If this has LDM approval, can we mark this as up for grabs?
The process for contributing language features is described here: Developing a Language Feature. In summary though language features are done with oversight / in conjunction with the compiler team. They are sufficiently complex, and must update so many parts of the language experience, that it's not realistic to have an "up for grabs model".
@gafter @MadsTorgersen who might recall the LDM meeting.
@tannergooding
I would also think that these would be more useful as a general API (exposed in CoreFX and powered by CoreCLR, just like the hardware intrinsics), rather than as a language specific feature.
As I understand it, the main point of this proposal is to support IL opcodes that can't be reasonably expressed as library methods.
Specifically, how do you imagine the API for calli
could look like?
@svick
Specifically, how do you imagine the API for
calli
could look like?
Like this: Static Delegates
Seems the point is that rather than providing awkward facilities to access the specific IL opcodes directly that the use cases for those opcodes would be determined and proper language features offered instead. That largely makes sense, although there will likely always be weird edge cases that couldn't be handled. Not to mention the burden would be on the team to identify those cases and design APIs around them.
calli
is effectively a method call on a function ptr with a specific signature, so
TResult CallIndirect<TArg1, TArg2, TResult>(T1 arg1, T2 arg2, void* functionPtr)
It would effectively have the same signature as however C# would expose it if it had an intrinsic for it.
But yes, for the relatively few keywords C# doesn't support, I would personally prefer language features (Static Delegates
, rather than calli
and ldftn
, for example).
And for il intrinsics
in general, I feel like a managed API that has runtime support would be better suited.
We can currently emit IL dynamically, but it is expensive and (AFAIK) can't readily be used inline with existing methods. Having an API where the JIT sees an API call and interprets it as the equivalent IL
instruction should be reasonably fast, output the same code, and work on all languages. (it would just require justification as to why doing so is worthwhile)
@tannergooding
Having an API where the JIT sees an API call and interprets it as the equivalent IL instruction should be reasonably fast
I assumed that the interpreting and replacement would be done by the compiler? Why put the onus onto the JIT to identify, validate and replace IL intrinsics?
In the same vein, why put any burden on the compiler to implement this when the corresponding language proposals to expose the functionality would likely be better.
I think this would be better as a runtime feature because:
That being said, I think there are better investments for the runtime as well and that this wouldn't be very high on the priority list (if it wasn't rejected right away anyways)
@tannergooding
Maybe. I think it depends a great deal on the feature, especially since unless the API is shaped very specifically following CLS it would be up in the air as to whether or not other CLR-based languages could exploit it anyway. And even then it might not feel natural in those languages. I think that approach makes significantly more sense when dealing with instrinsics specific to machine language for specific architectures and less with IL. As a compiler feature it skips an unnecessary step and could have better integration and validation within the language.
C# already has several of these "IL intrinsics" that are not part of the official language, but are there for people who need them for advanced scenarios. Think __arglist
, __makeref
, and friends. The proposal is not much different from __addrof
, __call
, __stdcall
, etc. (an unadvertised feature for power scenarios that is not expected to be used by 99% of developers).
We keep coming back to the "let's make this a JIT feature instead", but that would require JIT to pattern match delegate creation sequence with the intrinsic (for the addrof scenario) and those things are not great (e.g. it took several attempts to get RuntimeHelpers.InitializeArray
an intrinsic that would be guaranteed to expand because it goes against the single pass nature of the IL importer). I don't see why JIT would have to concern itself with it, especially since there's perfectly fine pure IL representation for the concept. Also, as a JIT intrinsic, it would have to be implemented by half a dozen codegen backends we have in .NET (CLR JIT, .NET Native UTC, Mono's JIT, Mono's LLVM backend, and experimental CppCodegen and WebAssembly backends in CoreRT).
Agreed. We have discussed all the options and their pros and cons presented on this thread already at the said LDM and decided that the proposal https://github.com/dotnet/roslyn/issues/11475 is the optimal in terms of complexity (low) and amount of impact (no impact) on the rest of the stack (IDE, analyzers, tools, JIT, runtime) etc. Any feature that requires runtime changes will take very long time to get implemented in all relevant runtimes -- Desktop .NET, Core CLR, Mono, LLVM, etc. and it will take even longer for a significant amount of customers to upgrade to the new versions of these runtimes. Besides not all the intrinsics are covered by proper language/runtime features like static delegates so the tools that need these intrinsics will need them even if static delegates are implemented everywhere.
FYI. @GrabYourPitchforks, who was asking about some of the proposed syntax (see the proposal.md that Jared merged in 1685)
Note that the design has changed and the new proposal has been added here: https://github.com/dotnet/csharplang/blob/master/proposals/intrinsics.md
I would like to have the ability to use ldftn
on virtual instance methods, either by omitting an instance value or by class name (like static methods). The primary use case would be to detect if a particular instance has overridden a method: if(&this.VirtMethod != &VirtMethod) ...
handleof
should support properties as well, e.g. by mandating a postfix: handleof(SomeProp.get)
, maybe even handleof(SomeProp.field)
(to get an auto-prop backing field).
Actually, address-of should also support properties (in some similar way).
@gafter Has the team considered moving implementation timeline of this feature to AnyTime
?
@jaredpar Where are we with the handleof/tokenof part of this proposal. Given calli/ldftn have migrated over to the new proposal.
Are we just looking for an implementation at this time?
@mjsabby that part of the proposal is pretty much the same: add handleof
and tokenof
that have roughly the semantics of nameof
. There wasn't much push back on that.
Is anybody aware of calli
util which hacks dotnet during runtime and swaps some call into calli
? I want to use that before feature will be released as part of csharp. I know about https://github.com/SharpGenTools/SharpGenTools, but it look like could be to heavy - as it seems build time utility.
handleof
, when combined with MethodBase.GetMethodFromHandle
, Type.GetTypeFromHandle
, and FieldInfo.GetFieldFromHandle
, effectively the infoof
(aka memberof
) operator? (See also #712, https://github.com/dotnet/roslyn/issues/1653, https://github.com/dotnet/roslyn/issues/128.) I'm surprised, but not unhappy, that (judging from the LDM notes linked in the OP) this was accepted without all the discussion that usually goes along with infoof
.your link (1) only says the function pointer aspect has been merged (that would mean calli
and ldftn
are no longer needed as "compiler intrinsics" since they now have other low level representations)
I think there were other discussions for function pointers, maybe linking here was just a mistake
The following code defintily works:
public unsafe static class Program
{
static delegate*<int> pointer = &Main;
public static int Main() {
return 0;
}
}
and it correctly outputs:
.class public auto ansi abstract sealed beforefieldinit Program extends [System.Private.CoreLib]System.Object
{
.field private static method int32 *() pointer
.method public hidebysig static int32 Main () cil managed
{
.maxstack 8
ldc.i4.0
ret
}
.method private hidebysig specialname rtspecialname static void .cctor () cil managed
{
.maxstack 8
ldftn int32 Program::Main()
stsfld method int32 *() Program::pointer
ret
}
}
The following code defintily works:
@Unknown6656 yes but that wasn't the question, nor is that what was described in this proposal. There were other proposals for function pointers that got implemented as you described them.
net effect:
calli
/ldftn
(or can be updated to match what was implemented)ldtoken
which is not covered by function pointers, so this proposal is not redundant or completed yetldvirtftn
(part of this proposal) is covered by function pointersFair point, I only mentioned it for completeness sake - not really as an answering comment.
What's the status of this issue?
The feature function pointers is now merged into the main branch but remains under preview. The plan is to ship it with C# 9. This issue is separate from function pointers but has significant overlap. My expectation is that when we ship function pointers we will resolve this issue at the same time we close down function pointers.
There are a few items, like tokenof
, that won't be implemented. At the point function pointers ships though if there is still significant need for them we can track them as a separate issue.
- I don't know if
ldvirtftn
(part of this proposal) is covered by function pointers
It is not.
Please help me get this to the right place.
I am experiencing a bug with function pointers.
Steps to repro: Create console app. Switch build to .net 5 and enable unsafe code. Replace contents of Program.cs with the following:
public struct Host { Extractor<Host> uhoh; }
public unsafe struct Extractor<T> { delegate*<ref T, ref int> extract; }
class Program { static void Main( string[] args ) { Host h; } }
Run.
Result: An unhandled exception of type 'System.TypeLoadException' occurred in Unknown Module. Could not load type 'bug.Host' from assembly 'bug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I am guessing this has something to do with Extractor uhoh being a member of Host, and also using Host as an argument for it's function pointer?
Love the new feature otherwise :D. Is full documentation in the works?
I don't believe that has anything to do with function pointers, it looks like https://github.com/dotnet/runtime/issues/6924.
@333fred It's very likely that that is my bug. Thank you.
In my code I have something like this in a very hot path:
interface IFoo { int DoSomething(args); }
class FooGroup
{
private readonly ImmutableArray<IFoo> _foos;
public int HotMethod(args)
{
int local = 0;
foreach (var foo in _foos)
{
local += foo.DoSomething(args)
}
return local
}
}
The IFoo's are all distinct types, so I guess the JIT can't do much with respect to guarded devirtualization
Every time HotMethod gets called, callvirt is used to invoke each of the DoSomething's implementations. If the function pointer could be assigned an instance method, I could then cache those pointers in FooGroup's constructor and HotMethod would use them instead of a virtual call.
Is there any plans to support this (function pointer to instance/interface methods)?
What's the status of this issue?
The feature function pointers is now merged into the main branch but remains under preview. The plan is to ship it with C# 9. This issue is separate from function pointers but has significant overlap. My expectation is that when we ship function pointers we will resolve this issue at the same time we close down function pointers.
There are a few items, like
tokenof
, that won't be implemented. At the point function pointers ships though if there is still significant need for them we can track them as a separate issue.
As Jared indicated, I'm closing this out.
@333fred function pointers are only a small subset of possible use cases proposed here.
Yes, I'm aware. As Jared said, if there is significant need we can track those as separate issues/discussions. As of right now, there is no LDM champion for future intrinsics work.
I personally viewed the other aspects of this proposal as add-ons. The primary motivation of the proposal was function pointers and the suggestion was il-intrinsics as a solution. If we chose to go that route then it made sense to consider a few other intrinsics as they were essentially incremental cost that point. The team would already be doing the heavy lifting to expose a few of the instructions, it made sense to take a bigger view and grab the other missing pieces. I didn't view them as standing on their own here.
I'm more than happy to help review a proposal for the work that didn't get done here and the motivations for adding them in. But I do think it should be tracked as a new issue.
@svick
Specifically, how do you imagine the API for
calli
could look like?Like this: Static Delegates
Seems the point is that rather than providing awkward facilities to access the specific IL opcodes directly that the use cases for those opcodes would be determined and proper language features offered instead. That largely makes sense, although there will likely always be weird edge cases that couldn't be handled. Not to mention the burden would be on the team to identify those cases and design APIs around them.
Static Delegates link has changed. Static Delegates superseded by function-pointers.md
See also