RyanLamansky / dotnet-webassembly

Create, read, modify, write and execute WebAssembly (WASM) files from .NET-based applications.
Apache License 2.0
791 stars 74 forks source link

Ahead-of-Time Compiler using IKVM #10

Open mccoyp opened 5 years ago

mccoyp commented 5 years ago

Hello! I was wondering if there were plans to incorporate AOT compilation of wasm into this project. I'm a student who's spent some time building a prototype for an AOT compiler based off of this library (my fork is here).

My approach uses IKVM.Reflection to allow assembly saving as a DLL on .NET Standard 2.0 - the version of IKVM used is derived from Mono's fork of IKVM reflection (here). The prototype isn't fully fleshed out, but it does show for a number of examples that AOT wasm compilation can work for .NET Core (and Mono). It required some pretty heavy refactoring of the Compiler and CompilationContext to switch to an IKVM.Reflection dependency, but I think it could be a very useful addition to the project.

Either I or @lambdageek (who helped me with the development) can help to explain the approach a bit more and upstream changes if you're interested in this approach.

ericsink commented 5 years ago

+1

My primary interest in this project is in using it to convert WASM into .NET assembly files. I've done some fiddling around in this area, but not as far as @mccoyp has gone.

I found myself wanting the guts of this library to be a bit more abstracted, so that it could be used for its current use case (generating IL on the fly in memory) or with one of the various libraries for writing an assembly to a file, such as the IKVM stuff mentioned above, or even System.Reflection.Metadata.

But I recognized that my particular interests may not be the same as the goals for this project.

lambdageek commented 5 years ago

System.Reflection.Metadata

Just want to mention that the reason @mccoyp and I ended up using IKVM.Reflection was because it follows the API and semantics of System.Reflection pretty closely - so it was fairly straightforward to retarget dotnet-webassembly for a proof of concept without having to re-architect the internals of dotnet-webassembly too much.

We wanted to see if we could build something useful in a short amount of time. With more time and more serious intent there are more options for the backend reflection library.

ericsink commented 5 years ago

@lambdageek Yep, that all makes sense. I mention S.R.M simply because it is another alternative, but going down that path would open a big can of worms, as the impedance mismatch is fairly high.

RyanLamansky commented 5 years ago

Well, the original purpose of the library is simply the outcome of me reading through the WASM spec, realizing how close it was to the .NET CIL spec, and deciding that a project like this should exist, if only because it can exist.

I'm not looking to add any more dependencies than it has now (which is just System.Reflection.Emit on .NET Standard and nothing at all on .NET 4.5+), but I'm happy to make it more extensible so that project like yours have can integrate with the main library instead of needing to fork.

AoT compilation is something I've definitely considered. In fact, it's very easy to achieve on .NET 4.5+ since this capability is built right into AssemblyBuilder via AssemblyBuilder.RunAndSave/Save. .NET Core does not have this capability, but it's been a known issue for years (which you may want to add to your watch list): https://github.com/dotnet/corefx/issues/4491 . I've been hoping they'd get to it eventually...

lambdageek commented 5 years ago

Happy to hear that extensibility is an option, @RyanLamansky - not sure what you have in mind, but I think you've built a pretty great general foundation for working with wasm modules from .NET - transpiling is one use-case, but maybe it doesn't have to be the only one.

As for .NET Core and RunAndSave... comments like https://github.com/dotnet/corefx/issues/4491#issuecomment-189756092 or https://github.com/dotnet/corefx/issues/4491#issuecomment-305010239 really don't offer a lot of hope that it will happen any time soon.

NicolasDorier commented 5 years ago

@lambdageek @mccoyp I heard about your current work thanks to this tweet, and I am so happy I found this project. This open huge interoperability perspective. (Some cryptographic libraries in C are too hard for normal human to port into C#, and I don't want to spend time cross compiling because this is a PITA)

Forgive me my ignorance as I am still learning about what is being done in this area, but why using IKVM instead of Mono.Cecil?

RyanLamansky commented 5 years ago

@lambdageek My current thought is to modify the compiler to emit instructions more abstractly. The built-in process would still default to wrapping AssemblyBuilder, but it could be swapped out with another service. I could leverage this internally to, for example, disable streaming compilation so that the WASM "names" section at the end of the file can be used to set accurate function names for easier debugging, or provide a clean way to do AoT on .NET 4.5 where this capability is already part of the platform.

I realize it's unlikely that Microsoft is going to fix https://github.com/dotnet/corefx/issues/4491 any time soon, but the long-term success of this project will require some effort from them. A growing problem is that .NET doesn't offer access to all the same raw CPU instructions that WebAssembly can use. I've been able to use combinations of other instructions to work around this so far (see https://github.com/RyanLamansky/dotnet-webassembly/blob/9599f9e122ab15ee7c91f905303f5d696f9f25b4/WebAssembly/Instructions/Int32CountOneBits.cs for one of the uglier examples) at the expense of performance, but some of the proposals I've seen for SIMD will be problematic and threads may be impossible.

With @migueldeicaza seeming excited about the potential here, there's hope for a path to clear these approaching roadblocks. Ultimately, this requires Microsoft to decide that the ability to run any WASM directly on .NET is of strategic interest, because opening up threading in a way WebAssembly will need would be complicated project.

NicolasDorier commented 5 years ago

@RyanLamansky for your particular example of Int32CountOneBits, if I am not wrong, you can implement it through by just calling some methods on System.Runtime.Intrinsic see doc here.

If I understand how it works, .NET Core JIT on encountering a call to Popcnt of this library, will properly generate the instructions on the target architecture.

NicolasDorier commented 5 years ago

So playing with intrinsic, I found out that it would be very hard to leverage this in a ".wasm to .dll" feature:

The reason is that we don't know if popcnt is available ahead of time on the target platform. And if not, calling System.Runtime.Intrinsics.X86.Popcnt.PopCount(4) will just throw an exception. (I was expecting the JIT to generate a fallback implementation if the target environment was not supporting it :( )

However, if wasm to IL compiler run in the target environment, then this is a quite nice optimization.

Suchiman commented 5 years ago

The idea of using System.Runtime.Intrinsics is always to check

int result;
if (System.Runtime.Intrinsics.X86.Popcnt.IsSupported)
{
    result = System.Runtime.Intrinsics.X86.Popcnt.PopCount(x);
}
else
{
    result = SoftwarePopCount(x);
}

IsSupported is a JIT time constant so the JIT will eliminate the check and the unreachable branches at JIT time, leading runtime code equivalent to

int result = System.Runtime.Intrinsics.X86.Popcnt.PopCount(x);

if it's supported, otherwise

int result = SoftwarePopCount(x);
NicolasDorier commented 5 years ago

@Suchiman this is amazing and exciting. This is sad that this is not more documented. It is a feature of RyuJIT? So this would work on Mono and .NET Framework as well?

Because if the JIT does not, the implementation would be a recursive call.

Second question, does the generated assembly need System.Runtime.Intrinsic.Experimental assembly loadable at runtime?

Suchiman commented 5 years ago

@NicolasDorier .NET Framework won't be getting this (don't have any hopes for new features in there), mono is thinking about adapting them IIRC

NicolasDorier commented 5 years ago

I am surprised, I thought .NET Framework was using RyuJIT by now so that would just work.

Suchiman commented 5 years ago

Yes but this also requires support from the VM side and the VM is not shared with NETFX so that part is #ifdef'd out for NETFX

Suchiman commented 5 years ago

Regarding System.Runtime.Intrinsic.Experimental, this the preview package that contains the reference assemblies for .NET Core 2.1. This package won't be used as of .NET Core 3 where this API is getting officially shipped

RyanLamansky commented 5 years ago

I'll definitely leverage any intrinsics shipped! I'm still concerned about how I'm going to deal with further out features like atomic memory instructions...

RyanLamansky commented 5 years ago

Anyway, back on topic, adding extensibility to make AoT compilation easier to achieve is now on the Potential Future Features list. I looked over all the work you (@mccoyp @lambdageek) did and feel bad that it was so difficult, I'm sure I can make it better!

Doing this is lower priority than getting 1.0 shipped. I'm so close to having 100% coverage of the base WASM spec that it's all I want to think about right now...

I'll leave this issue open until I have something that I think is good enough to meet your needs.