Why does CrossNet generate C++ instead of direct assembly code or JIT?

Let me talk about JIT first. CrossNet has been designed to be used in places where you cannot JIT assemblies.
There are multiple reasons where JIT cannot be used or may not be the best choice:
  • The platform doesn't allow you to execute code on writeable memory. JITed code is written in memory but some OS prevents you to execute code on this same memory. In those cases, the memory used to execute code is read only.
  • Your platform has very high memory constraints and as such you cannot afford the overhead of the JIT compiler in memory. CrossNet has a very low memory overhead, as it contains the bare minimum functionalities that you are using, nothing more. Dead code stripping from the C++ linker takes care of that.

So why using C++ with high level construct instead of replacing IL bytecode by corresponding assembly code?

Here are the PROS / CONS:

  • Reuse C++ optimizer / linker for faster / smaller code. No need to do an optimization pass. Better pipeline of the generated code / inlining...
  • Can use global optimizations or PGO without any development cost.
  • Able to interact directly with C++ user's code (either at the BCL level - like for STLs - at the C# level with extern keyword, or even manipulate unmanaged object in the C# script, or manipulate C# script from the unmanaged code).
  • Can use high-level constructs of the C++ (so while loop, switch cases, calls, etc.. are directly mapped, easy to understand, manipulate and debug...).
  • If it compiles, it has big chance to run as expected.
  • Inherently cross-platform. No need of platform specific code.
  • Complete understanding of the flow / class definition. Can change the behavior from a high level point of view (hugely beneficial for user's policy).
  • Generics become much simpler and efficient to emulate with C++ templates.
  • The whole application is in the same language, making it easier to maintain and see how things are working together (esp. when mixing BCL and user defined code).

  • Initial development is costly. A lot of stuff has to be handled correctly to handle the more complex codes:
    • Need to manage class dependencies correctly.
    • Nested classes don't work the same way between .NET and C++.
    • Class protections are not compatible.
    • Simple features are more complex to implement.
    • Have to get complete information on the whole assembly.
    • Have to work around C++ specific issues (in some cases, something that can be done in assembly code in 1 hour can take 1 day to solve with a proper C++ construct).
    • ...
  • Often difficult to make it compile in every single cases.
  • Sometime high level construct are difficult to convert in C++ world.
    • Foreach is a good example, it can actually have 4 different implementations (array, GetEnumerator(), IEnumerator and IEnumerator<>, there may be more...).
    • We actually have to find the exact case matching the .NET runtime behavior (to avoid possible discrepencies).
    • Assembly code from IL would work without any issue but would not be able to detect that's a foreach and do loop unrolling for example if we wanted to.

When you transform IL to assembly, you can almost completely invert the PROS and CONS.

To me, the most important criterias were:
  • Optimization
  • Cross-platform
  • Ease of debugging / maintainability
  • "Easy" Generics (it was actually all but easy ;) but i don't think I would have achieved the same level of emulation otherwise).

I was ready to spend more time on setting up the framework and have less rapid result at the beginning.
For the most complex cases, it definitively paid off as the parser is highly maintainable.

Last edited Sep 22, 2007 at 5:12 AM by OlivierNallet, version 5


No comments yet.