Roslyn: Proposal: Compiler intrinsics

Created on 21 May 2016  路  50Comments  路  Source: dotnet/roslyn

Background

C# and VB languages aren't able to express certain IL instructions and patterns (e.g. ldtoken, calli, ldftn, etc.). That's ok, it's not the job of a language to express all possible patterns of the underlying VM. However, there are scenarios where these patterns are very useful. In these cases users are currently left with options that are painful to use, break debugging, break IDE experience (refactorings etc.), are hard to maintain. These include various forms of IL rewriting, writing code in plain IL and compiling it to a separate assembly, runtime code generation, etc.

A few examples of such scenarios:

  • the CLR's tool for generating native interop stubs (MCG) would greatly benefit from the ability to emit IL instructions like calli, ldftn, etc.
  • infoof feature could be implemented as a library method if it was possible to directly emit ldtoken instruction
  • @joeduffy's implementation of slices currently needs some IL helpers compiled into a separate library.

If only Roslyn compilers could provide a way to emit these commonly needed IL patterns without changing the language specification ...

Proposal

A _compiler intrinsic_ is a static extern method declared in compilation source code that is marked with CompilerIntrinsicAttribute and its name is well-known to the compiler.

Each intrinsic provides a way to emit certain IL instruction or pattern that otherwise can鈥檛 be expressed in the C# language, using _existing syntax_ and _preserving standard evaluation stack behavior_. These intrinsics can thus be used in the middle of ordinary statements and expressions with no adverse effect on debugging, EnC, IntelliSense, refactorings, etc. Intrinsics have well-known names that determine the IL instruction pattern to emit and imply a signature pattern. The specific signature declared by the user along with the call-site arguments provide the compiler all the information it needs to emit the requested IL.

The declaration of an intrinsic won't be emitted to metadata. It can't be, as the type loader would not be able to load the containing type due to missing implementation for the intrinsics.

Additional constraints might need to be enforced on intrinsic call-sites and corresponding errors reported during binding phase. One of the constraints is that some arguments passed to the call-site of an intrinsic method must evaluate to a compile-time constant. This constraint is needed when the generated IL depends on the value of such arguments (e.g. mappedData in AddressOfMappedData).

Currently proposed intrinsics

```C#
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Method)]
internal class CompilerIntrinsicAttribute : Attribute { }
}

class Program
{
[CompilerIntrinsic]
static unsafe extern void* LoadFunctionPointer(DelegateType target); // e.g. DelegateType = Func

[CompilerIntrinsic]
static unsafe extern void* LoadVirtualFunctionPointer(DelegateType target);

[CompilerIntrinsic]
static unsafe extern ReturnType CallIndirect(Type1 p1, ..., TypeN pN, void* managedFunctionPointer);

[CompilerIntrinsic]
static unsafe extern ReturnType CallIndirectCDecl(Type1 p1, ..., TypeN pN, void* nativeFunctionPointer);

[CompilerIntrinsic]
static unsafe extern ReturnType CallIndirectStdCall(Type1 p1, ..., TypeN pN, void* nativeFunctionPointer);

[CompilerIntrinsic]
static unsafe extern ReturnType CallIndirectThisCall(Type1 p1, ..., TypeN pN, void* nativeFunctionPointer);

[CompilerIntrinsic]
static unsafe extern DataType* AddressOfMappedData(DataType[] mappedData); // adds entry to FieldRVA table

}

## Generic Intrinsics

It might be useful to allow the declarations to be generic in order to reduce the amount of declarations required in the code, for example

```C#
class Program
{
    [CompilerIntrinsic]
    static unsafe extern void* LoadFunctionPointer<T1, ..., TN, TRet>(Func<T1, ..., Tn, TRet> target);

    [CompilerIntrinsic]
    static unsafe extern TRet CallIndirect<T1, ..., TN, TRet>(T1 p1, ..., TN pN, void* managedFunctionPointer);
}

Local Intrinsics

Provided that C# 7 allows _local functions_ to be declared as extern and custom attributes applied to them it would be possible to declare intrinsics "inline" (see https://github.com/dotnet/roslyn/issues/11731), for example:

``` C#
unsafe void IL()
{
[CompilerIntrinsic]
extern int CallIndirectCDecl(int arg0, void* nativeFunctionPointer);

Console.WriteLine(CallIndirectCDecl(123, GetAddressOfNativeFunction()));

}

## Possible future intrinsics

The compiler can define any number of intrinsics and add more as needed. The following example demonstrates what would be possible. The exact set of intrinsics to introduce is up for a debate. 

Note that the following code compiles and works in the current version of C# compiler and IDE services. It just doesn't compile to the desired IL.

``` C#
class Program
{
    [CompilerIntrinsic]
    static unsafe extern int CallIndirect(string arg0, bool arg1, void* functionPointer);

    [CompilerIntrinsic]
    static unsafe extern int CallIndirectCDecl(int arg0, void* functionPointer);

    [CompilerIntrinsic]
    static unsafe extern int TailCallIndirect(string arg0, bool arg1, void* functionPointer);

    [CompilerIntrinsic]
    static unsafe extern void TailCallIndirectHasThis(void* functionPointer);

    [CompilerIntrinsic]
    static unsafe extern void* LoadFunctionPointer(Func<string, bool, int> target);

    [CompilerIntrinsic]
    static unsafe extern void* LoadFunctionPointer(Action target);

    [CompilerIntrinsic]
    static unsafe extern void* LoadVirtualFunctionPointer(Func<int, int> target);

    [CompilerIntrinsic]
    static extern RuntimeTypeHandle LoadTypeToken<T>();

    [CompilerIntrinsic]
    static extern RuntimeMethodHandle LoadMethodToken(Func<int, int> target);

    [CompilerIntrinsic]
    static extern RuntimeMethodHandle LoadGetterToken<T>(T targetProperty);

    [CompilerIntrinsic]
    static extern RuntimeMethodHandle LoadSetterToken<T>(T targetProperty);

    [CompilerIntrinsic]
    static extern RuntimeFieldHandle LoadFieldToken<T>(T targetField);

    [CompilerIntrinsic]
    static extern int LoadTypeTokenInt32<T>();

    [CompilerIntrinsic]
    static extern int LoadMethodTokenInt32(Func<int, int> target);

    [CompilerIntrinsic]
    static extern int LoadFieldTokenInt32<T>(T targetField);

    [CompilerIntrinsic]
    static extern int LoadGetterTokenInt32<T>(T targetProperty);

    [CompilerIntrinsic]
    static extern int LoadSetterTokenInt32<T>(T targetProperty);

    [CompilerIntrinsic]
    static extern string LoadMvidString();


    abstract class C<T>
    {
        public C(int a) { }
        public abstract int F(int a);
    }

    public void F() { }
    public static T G<T>(T a) => default(T);
    public static int H(string s, bool b) => 1;

    public static string fld;

    public static int P { get; set; }

    private unsafe void* GetAddressOfNativeFunction() => null;

    void IL()
    {
        // ldvirtftn C<string>.F
        // pop
        unsafe { LoadVirtualFunctionPointer(default(C<string>).F); }

        // ldstr "str"
        // ldc.i4.1
        // ldftn H
        // calli int(string, bool)
        // box
        // call Console.WriteLine(object)
        unsafe 
        {
            Console.WriteLine(CallIndirect("str", true, LoadFunctionPointer(H))); 
         }

        // ldc.i4 123
        // call GetAddressOfNativeFunction()
        // calli int(int)
        // box
        // call Console.WriteLine(object)
        unsafe 
        {
            Console.WriteLine(CallIndirectCDecl(123, GetAddressOfNativeFunction())); 
        }

        LoadTypeToken<Program>();                 // ldtoken Program
        LoadMethodToken(default(C<bool>).F);      // ldtoken C<bool>.F
        LoadMethodToken(G<int>);                  // ldtoken G<int>
        LoadFieldToken(fld);                      // ldtoken fld
        LoadGetterToken(P);                       // ldtoken get_P
        LoadSetterToken(P);                       // ldtoken set_P

        LoadTypeTokenInt32<Program>();            // ldc.i4 <token of Program>
        LoadMethodTokenInt32(default(C<bool>).F); // ldc.i4 <token of C<bool>.F>
        LoadFieldTokenInt32(fld);                 // ldc.i4 <token of fld>
        LoadGetterTokenInt32(P);                  // ldc.i4 <token of get_P>
        LoadSetterTokenInt32(P);                  // ldc.i4 <token of set_P>
        LoadMvidString();                         // ldstr "<containing module mvid>"

        // ldarg.0
        // ldftn F
        // tail.calli void()
        unsafe
        {
           TailCallIndirectHasThis(LoadFunctionPointer(F)); 
         }
    }
}
0 - Backlog Area-Language Design Feature Request

Most helpful comment

After a few hours of work, here we go: http://xoofx.com/blog/2016/05/25/inline-il-asm-in-csharp-with-roslyn/ :tada:

All 50 comments

@jkotas @yizhang82 @smosier @stephentoub @MadsTorgersen @gafter @dotnet/roslyn-compiler

Looks exactly how I had imagined, @jaredpar and I were talking about this afternoon, so I'm super happy to see this!

How do we feel about not taking a dependency on CoreFX defining the CompilerIntrinsic attribute, instead allowing the compiler to make a decision about which attribute to look for via an argument, or (and this may be heavy handed) making a keyword out of this.

I'd hate for this feature to be gated on your need to upgrade the compiler AND your standard library.

There is no dependency on CoreFX defining the attribute. The attribute can be defined in source as it is demonstrated in the example above (notice the internal visibility). The compiler doesn't require any attribute to be defined in a specific assembly. It just looks for its namespace qualified name and particular signature of the attribute constructor.

In other words you can just define the attribute anywhere in your project.

Gotcha, thanks.

Event adders/removers seem to be missing from the example.

How would LoadMethodToken work with overload resolution? For example, if I wanted the tokens of both Console.WriteLine(object) and Console.WriteLine(string), would the following work?

``` c#
class Program
{
[CompilerIntrinsic]
static unsafe extern void* LoadFunctionPointer(Action target);

[CompilerIntrinsic]
static unsafe extern void* LoadFunctionPointer(Action<string> target);

unsafe static void Main()
{
    var writeLineObject = LoadFunctionPointer((Action<object>)Console.WriteLine);
    var writeLineString = LoadFunctionPointer((Action<string>)Console.WriteLine);
}

}
```

@svick Yes, exactly.

What are the reasons to prefer this over supporting inline assembly directly?

@miloush Because this approach doesn't need to change the language syntax and semantics.

@tmat Feels a little bit like hack to avoid changes in the language. I agree it's much easier to ship though than designing IL support if it had to offer comparable features. Do you expect all IL instructions to be available via such methods or only the "impossible" ones?

@miloush Only a few special purpose patterns as needed. The intrinsics are designed to be used in very rare cases. Most C# programmers won't ever see one. Embedding full IL sublanguage into C# would be a lot of effort and would make the language much more complex for something that should be used very rarely. That's not a good trade off to make.

What if we could define them as generic functions,

[CompilerIntrinsic]
static unsafe extern void* LoadFunctionPointer<T>(Action<T> target);

var writeLineObject = LoadFunctionPointer<object>(Console.WriteLine);
var writeLineString = LoadFunctionPointer<string>(Console.WriteLine);

Or with a different name.

[CompilerIntrinsic("LoadFunctionPointer")]
static unsafe extern void* LoadFunctionPointerStr(Action<string> target);
[CompilerIntrinsic("LoadFunctionPointer")]
static unsafe extern void* LoadFunctionPointerObj(Action<object> target);

Or a general one,

[CompilerIntrinsic]
static unsafe extern void* LoadFunctionPointer(Delegate target);

Because you will need to cast the argument anyways.

+1 I like the general idea as it would obviate almost the need for IL patching in SharpDX interop codegen (similar to MCG), though, ideally, I would prefer a generic solution like (in between the proposal and @miloush concerns):

[CompilerIntrinsic] static extern void ilemit(string emit);

...

ilemit("ldtoken Program");
ilemit("ldtoken fld");

The underlying ilemit would have to be decoded by Roslyn and understand a simplified ILDASM syntax. Imho, much easier to author and roundtrip with an ILDASM output.

In addition to calli I also needed:

  • sizeof T
  • pin blittable struct T or T[] argument to pass it to a native function param.

@xoofx I don't understand how adding quotes around intrinsics adds any value. As I already mentioned we'd only support very small subset of IL patterns, which is expressed by the set of supported intrinsics. So the amount of IL you could express would be the same.

Imho, much easier to author and roundtrip with an ILDASM output.

Have you tried to prototype such approach? I believe the opposite is true. It'd be much more complicated.

@xoofx that seems to me as complicated as supporting inline assembly

The proposal of @tmat has at least some type safety, which is nice.

@xoofx I don't understand how adding quotes around intrinsics adds any value. As I already mentioned we'd only support very small subset of IL patterns, which is expressed by the set of supported intrinsics. So the amount of IL you could express would be the same.

The benefits are:

  • You need to declare just one intrinsic
  • You can use an IL ASM syntax with which we are much more familiar. It allows to almost copy/paste directly from ILDASM, which is very handy. The syntax is familiar, we have some documentation for it, we don't have to look for another syntax declaration.
  • It would be compatible with all .NET languages (e.g unsafe is not supported by VB, so the original proposal would only work for C#)

Have you tried to prototype such approach? I believe the opposite is true. It'd be much more complicated.

I have almost always started to develop my IL in IL ASM, compile an assembly from it and linked to it. Then I had to go to the Mono.Cecil route to avoid having another assembly (ILMerge has not been always safe to use on some assemblies). Exactly as you can see it in PtrUtils.il by @joeduffy

It has been somewhat prototyped in "Tool to allow inline IL in C# / VB.Net", although it was of course not done at compilation time but as a ILDASM post-processing injection trick.

@xoofx that seems to me as complicated as supporting inline assembly

Not sure what you both mean by "complicated". Do you mean complicated to implement in Roslyn? (in that case, knowing a bit of Roslyn, I know for sure that it is quite easy to hook a prototype into it for this kind of things) Or do you mean complicated to read for someone that doesn't know IL ASM?

So yes, I prefer a well known syntax like on the right side, rather than the left side which is a bit more cumbersome to use and author:

        LoadTypeTokenInt32<Program>();            =>  ilemit("ldc.i4 Program");
        LoadMethodTokenInt32(default(C<bool>).F); =>  ilemit("ldc.i4 C<bool>.F");
        LoadFieldTokenInt32(fld);                 =>  ilemit("ldc.i4 fld");
        LoadGetterTokenInt32(P);                  =>  ilemit("ldc.i4 get_P");
        LoadSetterTokenInt32(P);                  =>  ilemit("ldc.i4 set_P");

The proposal of @tmat has at least some type safety, which is nice.

My proposal has type safety as well. Roslyn would parse the asm string in exactly the same way it would do it if it was part of the language. It means that a reference inside the string would be evaluated (you could navigate to it), you would have a SyntaxToken for it, and it would be part of the SyntaxTree. It is just that by escaping the IL ASM in a string, you don't introduce any breaking changes to the language(s) and it is compatible typically with any .NET language.

@xoofx

Do you mean complicated to implement in Roslyn?

Yes.

Writing code in IL is an explicit non-goal of this feature. We want our users to write code in C#.

Writing code in IL is an explicit non-goal of this feature. We want our users to write code in C#.

Fair enough if it is a requirement.

Too bad, we could've had an ASM (maybe il { }) for C# http://wiki.freepascal.org/Lazarus_Inline_Assembler

@pebezo There are million things we could potentially have in C#. Adding all of them won't make the language better. https://i.kinja-img.com/gawker-media/image/upload/s--BdEb-Dl5--/c_scale,fl_progressive,q_80,w_800/185xbqdcr7fgmjpg.jpg

After a few hours of work, here we go: http://xoofx.com/blog/2016/05/25/inline-il-asm-in-csharp-with-roslyn/ :tada:

@xoofx Nice! Certainly better than strings ;) However a few issues you might want to think about:

a) The IL validation you mention in the blog post (balanced stack, etc.), especially when combined with all other C# language features -- like using il(...) in the middle of expression, etc. Perhaps you could do the final validation once everything is on IL level, but then you won't be able to report diagnostics to the user as they are typing.

b) Debugging -- il(...) looks like a statement but it is not a statement. How are you gonna place sequence points? Is EnC gonna work in a method that contains il() calls?

c) How many IDE features (e.g. refactorings, rename, etc.) need to understand il(...) calls in order to not produce incorrect results?

Complexity is hidden in the detail...

@tmat The points you mention are of course things that would have to be handled/crafted carefully for a non-prototype code. That would require certainly more work than the original proposal. I never claimed the opposite. but...

Complexity is hidden in the detail...

first, I'm making a large difference in nature (and not in levels) between the word "complexity" and "complicated"...
So I don't think that neither the problem nor my solution are complex. Regarding the "complicated" part of the implem, considering that I only worked a few hours on this, with a prototype that is already able to validate many things (while being able to cover almost the whole IL instruction set), it gives already a good tendency about the problems ahead.

But I definitely agree that a clean work would require more work for sure! Though, nothing complicated here, just laborious work. :wink:

@xoofx Cool prototype! Looking at it, though, I think I like @tmat's proposal more. In order to incorporate your changes we would basically have to create an entire IL sub-language inside C# and validate it all in the compiler. I would prefer to separate concerns here and let an IL compiler handle IL and the C# compiler handle C#.

@xoofx I understand you haven't spent too much time on your prototype so it doesn't cover everything.
However, the points I raised are important to consider and explain how they would be addressed. It's not just extra laborious work. There is additional design work to be made for all of them. These are the reasons why I said that support for arbitrary IL would be complicated.

The original proposal is designed to avoid these problems and thus the complete implementation of it would be a simple local change in the compiler. Your proposal adds more features but they come with a price. The features are inherently non-local, they don't only affect the compiler but all language services need to deal with them.

@xoofx by 'complicated' I meant

we would basically have to create an entire IL sub-language inside C# and validate it all in the compiler

And by type safety I meant including refactoring. If someone renames Program, you don't want to keep the IL string behind.

I am all for having decent IL support (and debugging story), but I understand that this is not the goal of this feature.

In either case if this is intended for public consumption, we will be stuck to supporting this syntax even if better solutions arise. @tmat suggests the scenarios are for exceptional cases and indeed, the provided list of examples seems pretty exceptional. Are they worth extra support in the compiler then?

Where there any other syntax options considered?

agree with @miloush and @tmat. The scope of this proposal is to support scenarios that are not possible today in C# and tools such as MCG (interop static codegen in .NET native and eventually CoreRT) need to resort to IL rewriting techniques using CCI, which is complicated (PDB, handling type references correctly, generics) and not performant (two pass >> one pass). Supporting all IL syntax seems nice but is out of scope for this particular proposal and is certainly much more involved to design and test to cover all possible combinations of valid and invalid scenarios.

No problem guys, as I said in the end of my post, I would still be happy with this proposal. :wink:

Why is there a need to allow users to define such functions? Could they not be defined once in the CompilerServices namespace? The functions are useful for other CLR languages as well. Also, it's not possible to define anything custom anyway. It's a way of _referencing_ an intrinsic, not defining one.

There's a semantic gap for LoadGetterTokenInt32. What would LoadGetterTokenInt32(P + P) mean? This breaks the concept of expressions in the language. Expressions are supposed to be substitutable for one another.

LoadGetterToken(infoof(P)) would fix that. infoof(P) returns a PropertyInfo. LoadGetterToken can be defined as IntPtr LoadGetterToken(PropertyInfo pi) => pi.Getter.Token. This would work without any new intrinsic. Then, a C# compiler or JIT optimization could turn LoadGetterToken(infoof(P)) into the appropriate efficient IL sequence.

That way the language is not polluted with any more concepts than necessary. It's just an optimization that does not need to be specified.

Just wanted to chime in and express my support for some variation of this concept.

There is already a potentially better definition via ICallIndirect
See MSDN)

I implemented a variation of this on my project to call intrinsic functions, you can see the result @ CodePlex

It works fine in its current form, the only thing which it leaves to be desired [to me] is the ability to call multiple functions without having to specify the arguments of each type of call.

Example

Why not have an attribute instead that allows people to write inline IL as a string? Something like this:

[ILSub(@"
    sizeof !!T
    ret")]
public static extern int SizeOf<T>();

That way we don't have to make people remember the intrinsic function names _as well_ as the corresponding IL ones, and we won't need to worry if new opcodes are added to IL.

Seems like a good thing to have. On the other hand that does not allow for obtaining tokens to methods.

I moved further along in my implementation..

See changes

There is also extension methods which are close as I can seem to get for getting MethodTokens or TypeRef using Expression.

I will be making more updates and refactoring the Managed Intrinsic to completely replace Intrinsic which I previously implemented.

The only thing I can think of in relation to use via Emit would be to use the combination of Expressions to allow OpCodes to be created from either a byte[] or otherwise.

Thus a syntax like you wanted as seen above could be possible using a combination of the byte[] changed into op codes and some type of IL parser..

I have a old implementation of the an IL Parser if you wanted to play with it, I have linked to it below.

IL Parsing from String

@jamesqo
Instead of having inline IL as a string, why not something like __asm { } from C++?

Also, thanks to @xoofx and .NET Blog for pointing out this proposal.

@arogozine

IIRC, even if this is something that would be desirable in C#, the CLR doesn't support mixed mode methods. A method may either be IL or native, but not some combination of the two.

It's also too much work. This is a fairly esoteric feature. It does not have to be "nice". It's an escape hatch for power users. The team can spend the time better on other things rather than building extensive support for esoteric features.

@ HaloFour, after JIT it's not IL anymore so there's no mixing to worry about.

The bigger question no one seems to want to ask is what intrinsic are you going to invoke or represent in IL which has any meaningful benefits? The JIT is where these improvements need to made not the compiler.

You can already dynamically emit methods or compile them manually, JIT the result and replace amother method.

The bottom line is that this is an esoteric feature which is mostly desired by power users as GSPP indicates but also has useful applications outside of using the DLR.

Is it worth it to have native compiler support now that there is the DLR is the real question.

Since it offers such possibilities and with higher performance potential I desire it but then again I don't use the DLR much at all. (I had already cooked up a Dictionary based approach which would use expressions almost just before it's release)

Maybe it's better suited as an extension or replacement to the DLR itself with a special type 'il' just like we have 'dynamic' for the for CLR where it could also be used by any CLR language.

@tmat We should update the proposal to modify the parameter passing of the calling convention to instead be name suffixed.

[CompilerIntrinsic]
static unsafe extern int CallIndirect(arg0, void* managedMethodPointer); // managed cc

[CompilerIntrinsic]
static unsafe extern int CallIndirectCDecl(arg0, void* functionPointer); // unmanaged

[CompilerIntrinsic]
static unsafe extern int CallIndirectStdCall(arg0, void* functionPointer); // unmanaged

[CompilerIntrinsic]
static unsafe extern int TailCallIndirectFastCall(arg0, void* functionPointer);  // unmanaged

I have some of these intrinsics implemented here (namely CallIndirect and friends): https://github.com/mjsabby/roslyn/tree/intrinsic_backup

The implementation is a prototype and the mechanics of how it generates the code are likely to change, but there is correctness. I intend to keep my fork around until we can get this proposal landed. There is also a nuget package of this that is much like the Microsoft.Net.Compilers package in that installing it in your VS project will make the project build against this version of the compiler with intrinsics support (https://www.nuget.org/packages/CSCWithCompilerIntrinsics).

https://github.com/dotnet/corefx/pull/13561 has another case where "infoof" functionality will come in handy for the System.Linq.Queryable factory methods.

@mjsabby Updated the proposal.

static unsafe extern PointerType AddressOfMappedData(byte[] mappedData)

The return type should match the argument type, e.g. byte* for byte[] mappedData.

@jkotas Fixed

static unsafe extern DataType* AddressOfMappedData(DataType[] mappedData)

I assume there will be restrictions on how much data flow analysis Roslyn can do to get the mapped data array - can I do:

var myArray = new byte[] { 1, 2, 3 };
var data = AddressOfMappedData(myArray);
UseData(data, myArray.Length /* constprop optimized to 3 at compile time */);

Or do I have to do:

var data = AddressOfMappedData(new byte[] { 1, 2, 3 });
UseData(data, 3);

If it's the latter, any chance we can make the intrinsic return a Span<DataType> so that the user can safely get the length?

Here's another approach for those interested in embedded IL code support:

I also wanted to be able to write IL directly in my C# code, so I wrote a Fody addin which lets you do just that: InlineIL.Fody. Just add the NuGet package and now you have a compile-time ILGenerator.

For those who don't know what Fody is, it's an extensible weaver which adds a build step that modifies assemblies generated by Roslyn.

I've implemented most of the Compiler Intrinsics according to this proposal and put them in a nuget package CSCWithCompilerIntrinsics. Adding this nuget package overrides the C# Compiler and gives you the C# 2.8.2 (as of this writing) compiler + the intrinsics. (https://github.com/mjsabby/roslyn/tree/compilerintrinsics_2_8_2)

dotnet run should produce the following output: Hello World from Compiler Intrinsics. 42.

intrinsictest.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="CSCWithCompilerIntrinsics" Version="2.8.2" />
  </ItemGroup>

</Project>

Program.cs

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method)]
    internal class CompilerIntrinsicAttribute : Attribute { }
}

namespace CompilerIntrinsicsTest
{
    using System;
    using System.Runtime.CompilerServices;

    interface IFoo
    {
        void Bar<T>(T j);
    }

    class Foo : IFoo
    {
        public void Bar<T>(T j)
        {
            Console.WriteLine($"{j}.");
        }
    }

    class Program
    {
        [CompilerIntrinsic]
        private static unsafe extern void* LoadFunctionPointer(Action<string> target);

        [CompilerIntrinsic]
        private static unsafe extern void* LoadVirtualFunctionPointer(Action<int> bar);

        [CompilerIntrinsic]
        static unsafe extern void CallIndirect(IFoo arg0, int arg1, void* functionPointer);

        [CompilerIntrinsic]
        static unsafe extern void CallIndirect(string arg0, void* functionPointer);

        static void Main(string[] args)
        {
            unsafe
            {
                CallIndirect("Hello World from Compiler Intrinsics.", LoadFunctionPointer(PrintMessage));
                var foo = new Foo();
                CallIndirect(foo, 42, LoadVirtualFunctionPointer(foo.Bar<int>));
            }
        }

        private static void PrintMessage(string message)
        {
            Console.WriteLine(message);
        }
    }
}

@jkotas @MichalStrehovsky are you sufficiently satisfied with https://github.com/dotnet/roslyn/pull/24621 for AddressOfMappedData. I think it's better than what was proposed and I'm using it for my projects. If yes @tmat we can probably remove that from this proposal.

Now only the CallIndirect intrinsic has metadata impact.

are you sufficiently satisfied with #24621 for AddressOfMappedData

Yes.

Closing because LDM has decided to not take this design and instead adopt a more scoped approach.

https://github.com/dotnet/csharplang/blob/master/proposals/intrinsics.md

Was this page helpful?
0 / 5 - 0 ratings