I think it is probably a good time to give a couple of motivating examples for my idea of a rich intermediate language.  Both examples ultimately have to do with optimization, and are therefore more matters of performance than correctness.  However, I believe that my solution also provides a run-time model that is intuitively what we expect from certain types of program behavior.

Often, applications have behavior that is customizable by setting certain properties in a properties file (such as those ubiquitous .properties files that Java application often use, or XML-based configuration files), the Windows registry, environment variables, command-line args, etc.  These behavior customizations are semantically related to conditional compilation, except the customizations occur at run-time instead of compile-time.  Or so it seems.  While the case could be made that the customizations technically occur at run-time, in fact they often conceptually occur at load-time.  So for instance, when a Java class is loaded and its static initializer runs, a .properties file is read and the relevant properties in this file are stored as static variables of the class (or perhaps rather than attempting to pull out all relevant properties in the static initializer, the Properties object itself is stored).  From this point on in many applications, these properties never change.  Therefore, although the loading of properties occurs at run-time due to the limitations of the platform, it happens once-per-load and it happens before any other code in the class executes.  This is my justification for calling the process conceptually a load-time process.

Let's step back for a second and look at conditional compilation.  Why would conditional compilation be used at all, given that a similar but more flexible solution is available in setting properties in configuration files and the like?  One reason is that a company may produce two or more versions of a similar product with different functionality, but that share a large portion of their code bases.  It may be undesirable to make it easy for a customer to get functionality they didn't pay for by tweaking the configuration files.  This particular concern is orthogonal to the conclusion I am trying to get at, but I want to make sure I don't ignore it.  Another reason is that dynamic checks such as:

if (featureXEnabled) { ... }

can be a performance drain because

  1. The check itself takes time, albeit a small amount of time, that can nonetheless hurt performance significantly if it appears in a critical code path.
  2. The code for disabled features, while essentially dead code, still exists in the binary and therefore can increase the need for page swapping.
  3. Some potential for optimization is lost.  In unmanaged code, the potential is irrevocably lost.  In managed code, due to limitations of the optimizations that are done at load-time, because it is often difficult for the virtual machine to discover that these optimizations are possible, and because sometimes the code is written in such a way that it is impossible for the virtual machine to safely make the optimization, the potential is still usually lost.

Therefore, even if it would be beneficial from a business standpoint to make the behavior dynamically customizable, it is often necessary for performance reasons to use conditional compilation (or, in the case of languages such as Java that don't allow for conditional compilation, to develop in parallel two versions of the same application).

If there were a standard way for loading properties, one that the loader could be aware of, then it would be easy (easier at least) for the loader to make these kinds of optimizations as it loads the application/module.  To make the discovery feasible, it would be useful to have this information specified explicitly in the intermediate code that is loaded, so that instead of the "if" statement above, the code in the statement would be tagged with "this block of code gets executed if and only if feature X is enabled".  This requires some syntactic structure of the intermediate code.

My second example is dynamic class loading, used to select an implementation for a particular interface at run time.  I will admit, this is an area where managed languages excel.  My biggest complaint is that reflection, being based on calls to APIs using primarily strings to identify classes, provides little in the way of verification ability at either compile-time or load-time, but I will get back to this in a later post.  My point is that, much as in the case of behavior being determined by properties in a configuration file that don't change once they are loaded, often the choice of implementation for a particular interface is decided once per load and never changed.  In fact, Windows manifests (among their other uses) provide a rare example of such a mechanism that explicitly happens at load-time.  If the programmer has a way of signalling to the loader that this is the case, further optimizations become possible, including inlining, eliminating the need for vtables, etc.  Discovery of this too is made easier by having a structured intermediate language.