JSIL: Compile .NET to Javascript

As if compiling LLVM bytecode to Javascript wasn’t crazy enough there is now the JSIL, a project by Mozilla engineer Kevin Gadd. JSIL compiles CIL (.NET’s bytecode format) to readable Javascript.

For instance, after compiling the following C# code using the C# compiler to CIL:

using System;
using System.Collections.Generic;

public static class Program {
 public static void Main (string[] args) {
 var array = new[] { 1, 2, 4, 8, 16 };

foreach (var i in array)
 Console.WriteLine(i);

var list = new List(array);

foreach (var j in list)
 Console.WriteLine(j);
 }
}

JSIL compiles it to the following Javascript (which we had to somewhat awkwardly reformat to fit the width of this site):

JSIL.MakeStaticClass(“Program”, true);

Program.Main = function (args) {
 var array = JSIL.Array.New(System.Int32,
 [1, 2, 4, 8, 16]);
 var array2 = array;
__loop0__: 
 for (var k = 0; k < array2.length; ++k) {
 var i = array2[k];
 System.Console.WriteLine(i);
 }
 var list = new (System.Collections.Generic
 .List$b1.Of(System.Int32))
 (array);
 var enumerator = JSIL.Cast(list.GetEnumerator(), 
 System.Collections.Generic
 .List$b1.Enumerator.Of
 (System.Int32));
 try {
 __loop1__: 
 while (enumerator.MoveNext()) {
 var j = enumerator.Current;
 System.Console.WriteLine(j);
 }
 } finally {
 enumerator.IDisposable_Dispose();
 }
};

The site contains a couple of demo applications, including a version of Tetrisoriginally developed using Microsoft XNA 4.

We asked its creator, Kevin Gadd, a few questions.

You work for Mozilla, is this a personal project or part of Mozilla’s strategy?

This is a personal project. I believe it’s in line with the general principles of the Mozilla Corporation but it’s not currently being funded by the company. Emscripten is, as I understand it, also a personal side project, not funded work.

Why did you start the project, just for fun or do you have actual code or applications you want to port?

I have a bunch of code written in C# that I want to make available to people running web browsers (particularly on mobile devices, where a good port is difficult or otherwise impossible). I also think that .NET provides a good way to build portable applications while still delivering a relatively good experience on each platform, because it’s expressive enough to let you get close to the metal in a way that’s harder with other, more portable languages.

How does this compare to Emscripten, which compiles LLVM to Javascript. CIL is probably higher level, does that make it easier to port or faster?

Emscripten is, to a certain degree, both faster and easier to port. However, this is because it ends up operating at a different level – effectively, you’re using the JS runtime as a virtual machine to run LLVM bytecode. The bytecode is translated into JS, and in some cases it’s possible to translate the bytecode into higher level constructs, but in many cases it ends up being very low-level. This makes it very difficult to leverage any browser features that aren’t integrated directly into Emscripten, and maintenance and debugging are a significant challenge because the resulting code has little resemblance to the original source. I think in the long run it might be possible to get better performance out of JSIL because it will be easier to detect higher level patterns and features in the original .NET code and translate them to efficient JS equivalents.

To provide a contrived example, most of the existing ports of games like doom or quake using Emscripten are still using a software rasterizer, and basically generating pixel data in JS and blitting it to the screen. For the XNA games I’ve translated so far, JSIL is able to trivially adapt uses of the XNA graphics APIs into equivalent canvas-based code, which can then be hardware accelerated. It’s possible to do something like that with Emscripten, but the type information provided by .NET makes it much simpler.

Emscripten pretty much kicks ass though :) A counter-example is that structs are considerably faster in Emscripten, because they’re translated into raw memory accesses, just like singular variables of primitive types. JSIL has to emulate the semantics of structs, so they end up becoming reference types just like classes, and that produces tremendous garbage collection overhead.

Readability of generated code is a goal. Can this be used as a migration tool?

Yes, I believe that it is possible to use JSIL as a migration tool, where you run it once and then maintain the resulting JS by hand and make improvements there. However, parts of the JSIL framework are not currently friendly to authoring JS by hand — so anyone attempting to migrate a large project to JS and do further maintenance/development in JS would probably find themselves having to fix up the output from JSIL. A lot of this is because I can’t make many assumptions about what specific language features an application needs, so I end up emulating language semantics consistently even if they make the resulting code noisier.

A good example of this is how the ‘foreach’ construct in C# implicitly involves a try { } finally { } block, so that if the enumerator is Disposable, it is disposed upon the termination of the loop. This makes the resulting JS a lot uglier than it would be if it were written as native JavaScript, and worse still, try/finally has severe performance consequences in some runtimes. A developer can easily come in and fix these by hand when they show up in a profiler, of course, but it is a downside.

What about the .NET framework libraries. To make JSIL work, you need a considerable amount of framework code. Do you cross compile Microsoft’s .NET .dll’s, do you use Mono’s open source implementation, a custom Javascript implementation?

Currently I use a custom JavaScript implementation. Given a minimal set of stuff that can’t be machine translated, though, it should be possible to cross compile the Mono/Microsoft mscorlibs. I’ve compiled the Microsoft mscorlibs before and gotten them to work, but shipping those with an app poses significant IP issues. My long term plan is to cross-compile the mono libraries and use those to supplement a small set of core JS stubs.

Links  — — –