Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Compiler intrinsics #11475

Closed
tmat opened this issue May 21, 2016 · 50 comments
Closed

Proposal: Compiler intrinsics #11475

tmat opened this issue May 21, 2016 · 50 comments

Comments

@tmat
Copy link
Member

tmat commented May 21, 2016

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’t 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

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<int, int>

    [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

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 #11731), for example:

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.

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)); 
         }
    }
}
@tmat
Copy link
Member Author

tmat commented May 21, 2016

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

@mjsabby
Copy link
Contributor

mjsabby commented May 21, 2016

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

@mjsabby
Copy link
Contributor

mjsabby commented May 21, 2016

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.

@tmat
Copy link
Member Author

tmat commented May 21, 2016

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.

@mjsabby
Copy link
Contributor

mjsabby commented May 21, 2016

Gotcha, thanks.

@0xd4d
Copy link

0xd4d commented May 21, 2016

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

@svick
Copy link
Contributor

svick commented May 21, 2016

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?

class Program
{
    [CompilerIntrinsic]
    static unsafe extern void* LoadFunctionPointer(Action<object> 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);
    }
}

@tmat
Copy link
Member Author

tmat commented May 21, 2016

@svick Yes, exactly.

@miloush
Copy link

miloush commented May 21, 2016

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

@tmat
Copy link
Member Author

tmat commented May 21, 2016

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

@miloush
Copy link

miloush commented May 21, 2016

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

@tmat
Copy link
Member Author

tmat commented May 22, 2016

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

@alrz
Copy link
Member

alrz commented May 22, 2016

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.

@xoofx
Copy link
Member

xoofx commented May 23, 2016

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

@tmat
Copy link
Member Author

tmat commented May 23, 2016

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

@miloush
Copy link

miloush commented May 23, 2016

@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
Copy link
Member

xoofx commented May 23, 2016

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

@tmat
Copy link
Member Author

tmat commented May 23, 2016

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

@xoofx
Copy link
Member

xoofx commented May 23, 2016

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.

@pebezo
Copy link

pebezo commented May 23, 2016

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

@tmat
Copy link
Member Author

tmat commented May 23, 2016

@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

@xoofx
Copy link
Member

xoofx commented May 25, 2016

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

@tmat
Copy link
Member Author

tmat commented May 25, 2016

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

@xoofx
Copy link
Member

xoofx commented May 25, 2016

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

@agocke
Copy link
Member

agocke commented May 25, 2016

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

@tmat
Copy link
Member Author

tmat commented May 25, 2016

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

@jamesqo
Copy link
Contributor

jamesqo commented Jun 5, 2016

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.

@GSPP
Copy link

GSPP commented Jun 5, 2016

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

@juliusfriedman
Copy link

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

@arogozine
Copy link

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

@HaloFour
Copy link

HaloFour commented Jun 8, 2016

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

@GSPP
Copy link

GSPP commented Jun 9, 2016

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.

@juliusfriedman
Copy link

juliusfriedman commented Jun 9, 2016

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

@mjsabby
Copy link
Contributor

mjsabby commented Nov 22, 2016

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

@bartdesmet
Copy link

dotnet/corefx#13561 has another case where "infoof" functionality will come in handy for the System.Linq.Queryable factory methods.

@tmat
Copy link
Member Author

tmat commented Nov 28, 2016

@mjsabby Updated the proposal.

@jkotas
Copy link
Member

jkotas commented Nov 28, 2016

static unsafe extern PointerType AddressOfMappedData(byte[] mappedData)

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

@tmat
Copy link
Member Author

tmat commented Nov 28, 2016

@jkotas Fixed

@MichalStrehovsky
Copy link
Member

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?

@ltrzesniewski
Copy link
Contributor

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.

@mjsabby
Copy link
Contributor

mjsabby commented Jun 18, 2018

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

@mjsabby
Copy link
Contributor

mjsabby commented Jun 28, 2018

@jkotas @MichalStrehovsky are you sufficiently satisfied with #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.

@jkotas
Copy link
Member

jkotas commented Jun 28, 2018

are you sufficiently satisfied with #24621 for AddressOfMappedData

Yes.

jaredpar added a commit to jaredpar/csharplang that referenced this issue Jul 2, 2018
This is an alternate proposal for the compiler intrinsicts feature:

dotnet#191
dotnet/roslyn#11475

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 @msjabby, @tmat and @jkotas.
jaredpar added a commit to jaredpar/csharplang that referenced this issue Jul 2, 2018
This is an alternate proposal for the compiler intrinsicts feature:

dotnet#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.
@agocke
Copy link
Member

agocke commented Aug 14, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests