Compare the choice of garbage collectors and high performance JVM implementations vs what the CLR offers. I don't have recent benchmarks for the MS implementation of the CLR, but Mono lags noticeably behind Java in terms of performance:
http://shootout.alioth.debian.org/u64q/benchmark.php?test=al...
I find it interesting that when folks want to claim that .NET is crossplatform they cite Mono, but when performance is discussed they are only prepared to discuss the performance of the MS Windows implementation provided by MS. The last time I benchmarked .NET (on Windows) it was significantly slower than java (with -server flag), but their EULA forbid publishing benchmark results at the time (was a long time ago, but since the parent is discussing .NET v1.0 and it's creation...)
And they can stop you through the EULA .NET gets distributed under, because no other license gives you the right to use .NET, so you have to do it under the terms of that license.
The situation is a bit more complicated than that.
To begin with, there are basically three CLR backends with sufficient maturity: Microsoft.NET, Mono, and Mono/LLVM, of which Mono is probably the slowest, because it is optimized for fast startup; that is matched against java -server, which (unlike -client) does extra optimizations (like mono --llvm) at the expense of a slower startup time. This works for short benchmarks, because compilation time is fairly minimal for these tight-inner-loop benchmarks.
Second, none of these benchmarks really touch on any of the JVM's weak spots. The classical example here is complex number arithmetic, where even an addition can potentially trigger a heap allocation (the JVM escape analysis only helps as long as functions don't actually store results anywhere). There are unfortunately quite a few such features where you get extra overhead to implement the workarounds.
Third, the JVM tends to be a bit of a memory guzzler. Combined with java -server's fixed-size heap, that can create problems in some scenarios (such as a system process that forks a few instances of itself).
In the end, few people will pick one or the other for the raw performance (which, in the end, is not very far apart), but for the ecosystem that comes with each backend.
It is worth noting that there are also other VM-related concerns, and overall, the CLR has the superior design as a general-purpose language backend.
For example, CLR functionality is a superset of JVM functionality. That makes the CLR far more pleasant to use as a compiler backend than the JVM (the reason why JVM-based languages use the JVM primarily is probably to hook into the JVM ecosystem, not because they love devising workarounds to implement closures or value types somewhat efficiently).
Similarly, P/Invoke is in practically all aspects superior to JNI. If you need to call native code a lot, P/Invoke is almost always the easier way to do it.
Library versioning is another issue where the CLR has the better story.
Again, what will almost certainly drive the use of one or the other for 99% of all programmers is what infrastructure (frameworks, libraries) they need. If they want to develop web applications using Scala/Lift, the JVM it is. If you want to implement games using Unity, you're going to use the CLR. The benefits you get from reusing code will almost certainly dwarf other concerns.
>>Mono/LLVM<< What a pain! -- "configure: error: Compiling with stock LLVM is not supported, please use the Mono LLVM repo at https://github.com/mono/llvm, with the GIT branch which matches this version of mono, i.e. 'mono-2-10' for Mono 2.10."
Not to mention that an out of the box .NET/CLR will just run fairly well. An out of the box JVM may or may choke, depending on the app you're trying to deploy. I've spent so much time trying to tune our JVMs for performance, that sometimes it's nice just to deploy a C# app on the CLR and have it just "work".
That makes the CLR far more pleasant to use as a
compiler backend than the JVM (the reason why
JVM-based languages use the JVM primarily is probably
to hook into the JVM ecosystem, not because they love
devising workarounds to implement closures or value
types somewhat efficiently).
That's not true.
The JVM has inherent advantages that the CLR does not have ...
(1) the JVM can inline virtual method calls and with some machinery attached (bytecode manipulation) you can get pretty close to zero overhead when invoking functions dynamically (the reason for why dynamic languages on top of the JVM have been pretty awesome)
On implementing closures efficiently ... you should check your facts, because delegate invocation on top of .NET always has overhead compared to plain method invocation, while on top of the JVM you can make it so that those invocations will be inlined at runtime (in server mode the JVM even inlines method calls made through plain reflection, which is considered extremely expensive).
Implementations such as JRuby successfully do this, but because of JRuby's nature, the call-stack sometimes gets too tall, beyond the capabilities of the JVM to inline, however this is addressed with the InvokeDynamic support from JDK 7, which will make dynamic method calls as efficient as normal calls.
Because the CLR is not capable of such optimizations, compiler authors have to do a lot of optimizations ahead-of-time, while on the JVM even a dumb compiler can produce good results ... for proof, compile 2 binaries from the same source-code, one with the Mono compiler, the other one with the official compiler, then run on the same platform and compare.
(2) compiler writers love the Jar/.class formats, being really easy to produce binaries for Java, coupled with the maturity of libraries such as ASM ... contrast this with the CLR .dll format, for which the .pdb format for attaching debugging symbols is proprietary and NOT documented, which pushed the people working on Mono to come up with their own format (.mdb). Of course, through reverse engineering and through some code that Microsoft published, the interoperability of Mono with .pdb has improved lately, but it's still painful.
(3) there are many tools available for the JVM that complement compiler authors, tools like ObjectWeb's ASM, which have poor substitutes for .NET. And if you want a good parser generator, there are plenty to choose from for Java, while for .NET the only reasonable choice being Antlr, which is primarily a Java tool that also supports .NET -- on the other hand if you wanted to express your grammar with PEGs I personally couldn't find an option for .NET, while there are several available for the JVM.
(4) the features in .NET that are not used in C# have received poor support. For instance tail calls were NOT guaranteed to be eliminated, even if you specified this behavior in the compiled bytecode. But more than this, tail-calls have severe overhead over plain method calls. And did I mention that tail-call optimizations, as produced by the F# compiler, simply won't work reliably on Mono?
On value types, this is not such a huge issue as many people think. In Scala there's the proposal SIP 15 (value classes) which are implemented in the latest Scala 2.10 milestone release. This will open the door to much needed primitives, like unsigned ints: http://docs.scala-lang.org/sips/pending/value-classes.html
Also, you're making a mistake if you think the CLR type system doesn't have flaws. For instance the CLR lacks a Union type, which makes it a bitch to implement lazy languages, such as Haskell.
The CLR also lacks anything that would aid in implementing continuation-passing-style. The JVM doesn't have anything for it either, however for the JVM exceptions can be used for unwinding the call-stack efficiently and compared with the CLR, exceptions on the JVM are very, very cheap.
This coupled with bytecode manipulation and library authors have been able to build libraries that make use of CPS efficiently.
First of all, let's clear some things up so we don't talk in circles. :)
Picking either the JVM or the CLR as your compiler backend is generally done because you either want (1) to hook into the respective infrastructure, (2) want a mature GC technology without having to develop your own, (3) need to generate reasonably fast code at runtime, or (4) all of the above. Most likely, it's a combination of (1) and (2).
If not, you're probably better off picking a more flexible backend that does not limit your object model or (if you're doing concurrency) your memory model. In short, either the JVM or the CLR forces you to compromise when emitting code.
In particular, both the JVM and the CLR are hostile environments for functional languages; if you're compiling a functional language to either (instead of, say, LLVM or MLRISC), you're almost certainly doing it for the JVM ecosystem, i.e. interoperability, vendor support, availability of tools, and such, not because it is a great fit for your needs.
So, when most of your points are about the superiority of the JVM ecosystem, I'm not disagreeing at all.
What I am still maintaining is that the CLR has the superior design. And yes, I am painfully aware of its limitations, too. Superiority is relative, not absolute.
All that said, there are a few of your points that merit a specific response.
(1) First of all, virtual call optimization (not just inlining) is something that the JVM does to solve what is primarily a JVM-specific problem. Simply put, while the CLR allows you to emit both call and callvirt instructions, the JVM has only invokevirtual (invokespecial, which was originally called invokenonvirtual, is more limited). The only option to tell the JVM that a call is non-virtual is to target a final method, which may require code duplication.
Second, nothing in Ecma 335 prohibits virtual call inlining when it can be proven to be safe. I don't know why Microsoft .NET/Mono still don't do it, but I suspect that they just don't see a big need for it, given that C# (unlike Java) uses non-virtual methods by default, and compilers for other languages with different semantics (e.g., Eiffel) can emit non-virtual calls at non-polymorphic call sites if they need to. Virtual call optimization could still be useful for inter-assembly calls (where the target assembly isn't strong-named), but that's probably a rare enough case that it's not certain whether the overhead is justified.
(2)/(3) Generating DLLs is pretty straightforward with ILASM or Mono.Cecil. Including debug information. You can only generate .mdb information on Windows, but then you only need it on Windows, too.
(4) The Ecma standard actually does make tail call elimination not optional for non-virtual calls. "CLI implementations are required to honor tail. call requests where caller and callee methods can be statically determined to lie in the same assembly; and where the caller is not in a synchronized region; and where caller and callee satisfy all conditions listed in the 'Verifiability' rules below."
With respect to value types, you probably misunderstood me. SIP-15 is about adding a layer of abstraction around existing value types. That's valuable, but it's also largely a compiler frontend, not backend issue.
What I was referring to was that the JVM makes heap allocations for tuple types all but necessary, especially if you're looking at collections of tuples. Sometimes you can work around it (such as mapping an array of N complex numbers to an array of 2*N real numbers) if you don't mind the extra work, but just as often that's not easily possible.
I'm curious as to what you mean by this; what part of the Java VM has evolved faster?