dotnet / csharplang

The official repo for the design of the C# programming language
11.48k stars 1.02k forks source link

Champion "Compiler intrinsics (calli, ldftn, etc)" #191

Closed gafter closed 2 years ago

gafter commented 7 years ago

See also

Unknown6656 commented 7 years ago

What are the advantages of compiler intrinsics for specific IL-opcodes instead of directly supporting inline-IL?

orthoxerox commented 7 years ago

@Unknown6656 you don't have to modify the parser, only the emitter, so you get to reuse type checking.

tannergooding commented 7 years ago

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.

MadsTorgersen commented 7 years ago

Need more motivation to prioritize this

MichalStrehovsky commented 6 years ago

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?

4creators commented 6 years ago

@MichalStrehovsky @MadsTorgersen Without any beer I could volunteer to implement it, however, due to my time limitations it could be only free time job.

jaredpar commented 6 years ago

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.

4creators commented 6 years ago

@jaredpar Can you describe what kind of reservations C# team has about this proposal?

GeirGrusom commented 6 years ago

IL as a language feature is a little bit of a cop-out I think.

jaredpar commented 6 years ago

@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.

tannergooding commented 6 years ago

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#.

tmat commented 6 years ago

IIRC, the LDM approved the feature as proposed in https://github.com/dotnet/roslyn/issues/11475.

MichalStrehovsky commented 6 years ago

@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.

jaredpar commented 6 years ago

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".

tmat commented 6 years ago

@gafter @MadsTorgersen who might recall the LDM meeting.

svick commented 6 years ago

@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?

HaloFour commented 6 years ago

@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.

tannergooding commented 6 years ago

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.

tannergooding commented 6 years ago

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)

HaloFour commented 6 years ago

@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?

tannergooding commented 6 years ago

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)

HaloFour commented 6 years ago

@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.

MichalStrehovsky commented 6 years ago

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).

tmat commented 6 years ago

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.

tannergooding commented 6 years ago

FYI. @GrabYourPitchforks, who was asking about some of the proposed syntax (see the proposal.md that Jared merged in 1685)

agocke commented 6 years ago

Note that the design has changed and the new proposal has been added here: https://github.com/dotnet/csharplang/blob/master/proposals/intrinsics.md

pentp commented 6 years ago

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).

4creators commented 6 years ago

@gafter Has the team considered moving implementation timeline of this feature to AnyTime ?

bbarry commented 6 years ago

more LDM notes: https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-09-05.md

mjsabby commented 5 years ago

@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?

jaredpar commented 5 years ago

@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.

dzmitry-lahoda commented 5 years ago

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.

fschmied commented 4 years ago
  1. What's the status of this issue? https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md mentions that it has been merged into master, but this issue says it's only a proposal, not yet discussed or decided in LDM?
  2. Assuming the proposal has been accepted, isn't 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.
weltkante commented 4 years ago

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

Unknown6656 commented 4 years ago

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
    }
}

https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQxASwDYB8ACBmAAgFcA7KCAMzgJwEYA2GgJhtoHYBYAKAG9uCBrRgBM4GOAHMI8AFQAeNCRgA+AgAcA9ovgICAXgIAyALIRFAbn6CrA/EILaCpxQAoAlAT5dBPmmwIADJbeggC+3KFAA===

weltkante commented 4 years ago

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:

Unknown6656 commented 4 years ago

Fair point, I only mentioned it for completeness sake - not really as an answering comment.

jaredpar commented 4 years ago

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.

333fred commented 4 years ago
  • I don't know if ldvirtftn (part of this proposal) is covered by function pointers

It is not.

newtype-cybernetics commented 3 years ago

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?

333fred commented 3 years ago

I don't believe that has anything to do with function pointers, it looks like https://github.com/dotnet/runtime/issues/6924.

newtype-cybernetics commented 3 years ago

@333fred It's very likely that that is my bug. Thank you.

dellamonica commented 3 years ago

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)?

333fred commented 2 years ago

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.

fandrei commented 2 years ago

@333fred function pointers are only a small subset of possible use cases proposed here.

333fred commented 2 years ago

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.

jaredpar commented 2 years ago

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.

jerviscui commented 2 years ago

@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