Why Is No One Writing Language Runtimes?
acorwin.com
acorwin.com
1. There is no such thing as a universal runtime. All runtimes are coupled to some language's semantics. The idea of a universal runtime has been tried several times and has always failed, or in the best case been limited to languages that are basically skins on top of the same underlying semantics (e.g. C# and VB.NET).
2. The IRs in the Go compiler are especially strongly coupled to Go semantics. Your language would have to replicate many idiosyncrasies of Go to run on its runtime. For example, you would be unable to benefit from performant lexically scoped cleanup the way e.g. Java (or basically any other language) can, because the Go language "defer" construct has extremely dynamic semantics and there is no support in the compiler for C++-style unwind tables (at least, I don't see why there would be, given that nothing in Go needs them).
3. Go is not a good candidate for this universal runtime in 2016 due its lack of a mature optimization pipeline. Maybe in a few years, but in the meantime you'd be better off using HotSpot or .NET for this purpose.
Go 1.7 on amd64 is looking to be pretty competitive (ranging to better than) with HotSpot or .NET in micro-benchmarks. Most of this is due to the new SSA compiler back-end. "Years" might be a pessimistic appraisal.
Microbenchmarks are one thing; breadth and depth are another. It doesn't take much to compete with GCC in a loop summing integers (unless GCC vectorizes that loop or precomputes the value, of course!) but competing on SPECINT is quite another.
Why oh why has no one written a runtime for the major browsers for others, say "text/python", "txt/golang", ... Anything other than javascript?
Once an alternative engine were in the major browsers they would hopefully take off as the world's hackers enhanced them.. and we could finally be freed from the shackles of a language designed in a weekend and bandaid-ed ever since.
The problem is getting multiple browsers to agree, and no real standard has emerged there. Even if one did, you're either relying on browser creators to include a runtime, or you have to patch one in, Flash-style. I think Google was trying to do this with Dart but failed.
A modern workaround I've seen a few times is to write/compile an interpreter in JavaScript that looks for script tags with different types and runs them, but that leads to yet more JS bloat when loading pages.
This would actually be the perfect use-case for Google's Native Client, if other browsers would bother to support it.
The arguments against webapp authors writing their app in NaCl are probably valid. But those arguments don't apply to the case where the "apps" are run times, there are only a common ones floating around, and people pull them in by URL in order to script against them.
Interestingly, this would solve one additional pain-point: every script would have the particular ABI version of the runtime it was coded against "locked" into it, in the same sense as a Ruby/Node dependency-lock file. So the runtimes themselves could be in very heavy flux version-to-version, as long as they kept old ABI-lines around and patched with security-updates.
Other script types have been supported for a while. Some compile-to-javascript languages can be setup to use script tags in their language. For example, CoffeeScript can be setup[1] to compile <script type="text/coffeescript"> tags at runtime.
Also, WebGL can load shaders[2] from script tags of type "x-shader/x-vertex" and "x-shader/x-fragment".
[1] http://coffeescript.org/#scripts
[2] see the source of http://mdn.github.io/webgl-examples/tutorial/sample3/ for a basic example.
http://almaer.com/blog/running-ruby-in-the-browser-via-scrip...
I remember hearing about it initially, but it doesn't appear to have gotten much traction.
A nitpick: Rust doesn't guarantee you won't leak memory, although it makes it much less likely than in C. Leaking memory is actually officially Safe (in the sense of Rust's safe/unsafe division) since it can't cause a program to access not-yet-allocated memory or already-freed memory.
That said the language does guide you away from things like that much more than C/C++.
To answer the question in the title, maybe the reason nobody's writing runtimes is that the JVM is just too good? What impact does all the "baggage" have in practice?
Suppose I want to write a new language that uses Python's vm as the backend.
* What files do I need to copy to get just the vm parts?
* How do I prepare functions and objects for the vm to use?
* What advantages do I get to targeting Python/Ruby/etc's vm?
* Will I be able to embed the vm, or is the vm defined in a way that it depends on global data. Lua, for example, wraps everything into a state object. So if I extend Lua (which, if I recall correctly, Terra does), I can provide the same functionality upward. But if the vm relies on global data, then I can't provide an embeddable language that extends off of that.
The problem is that there isn't really any immediate gain for the organization doing this. The benefit is for the community as a whole. So, we end up in a Tragedy of the Commons-like scenario with disparate pieces that disregard each other.
Going down a few turtles, my understanding of RISC is such that it does something similar (providing a much simpler set of possibilities, leaving more to the next level above), but at the machine level. What would 1 or 2 steps up from RISC look like, if it were built with some of the same principles?
The parrot story was a bit different. The tragic element was new parrot maintainership who destroyed it, and not perl6. perl6 was always fine and never tragic. And perl6 always kept the lead on board, in contrast to perl5 and parrot, where they left.
To the point: A lot of people are writing language runtimes. And some of them even target multiple languages. But no one really cares about pluggable bytecode, as parrot did. rpython the only comparable one, standing against the jvm, clr and llvm. Most people care about an efficient compiler and run-time for their language. And that's what is being done. Even more so in the last decade. Esp. with llvm getting more and more usable for those.
Nowadays lua influences a lot of the smaller runtimes, btw. the spot which used to be Scheme/your tiny lisp in the previous decades.
It also has to be said that even their JVM implementation fell behind MoarVM now, so the second and most important reason would be lack of time and manpower.
*Yes, I know about Niecza, not the same.
There's obviously nothing impossible about it, since it has been done, it's just that it takes a lot more resources than you might have initially thought to do it well. And I think from an economic perspective, the vast bulk of open source projects and similarly-staffed projects are way better off putting that "lot more resources" into becoming better on their native platform than being not-quite-as-good on a lot more platforms. (I had a lengthy defensive discussion of the differences between commercial and open source resource allocation, which grew to be more than half this post, so let's just pretend I proved that I understand the difference, and I still think this paragraph is true even given those differences.)
What's more rare is a single implementation of a language targeting multiple runtimes. There's Haxe, which has like 10 different targets (and those different targets seem to be the main selling point). Unity uses C# as it's programming language, but also has HTML5 + Javascript export.
The general downside with either of these is that the runtime should actually inform the language design, and vice versa. When you target multiple runtimes, you either have to design the language conservatively such that it only takes advantage of the smalles common subset of features in the runtimes. Or you have to accept that some operations the language nominally supports will be uncomfortable on some of the target runtimes.
As a classic example of the last one, a lot of scripting languages start off with memory management based on reference counting (possibly with some kind of facility for breaking loops automatically). This means that it's very natural for the language to provide deterministic object destruction as soon as the object goes out of scope, and for the library ecosystem to take advantage of that. (Like closing file descriptors when the matching file object goes out of scope). Move to a runtime with a garbage collector, and the finalization becomes non-deterministic. And you either accept the loss of this very convenient feature, you completely cripple the GC based implementation by adding this very expensive extra constraint on top of it, or you allow the two implementations to diverge.
Then there's fracture. Imagine reading that language X is at version 3 for one runtime, but 2.5 for another runtime because "the guy who did the support of runtime B lost interest, and nobody has taken it up again".
That, and almost everything has support compiling to its original target and JavaScript these days.
Nonetheless I agree with the sentiment, perhaps because I'd love to see Scala on .NET and F# on JVM in particular. I don't know why, of all the things that have happened, neither of these ever did. Maybe too much overlap to be worth it. I'd also have to imagine that strongly-typed languages are a bit more difficult since you have to match the underlying runtime.
Curious if there were any languages and targets in particular you had in mind.
FWIW ClojureCLR is still going strong; seems to track the JVM version within a couple weeks: https://github.com/clojure/clojure-clr/commits/master It does have some warts, but I've played with it and it works fine.
Functions are incrementally compiled into byte code instructions for a virtual stack machine I designed specifically to run Lisp. Compiled functions are added to the Lisp image, instead of being run directly in a Unix shell. The virtual machine is written in C++. Using the read function as the parser and Lisp-specific byte code as the target simplifies the compiler, which is only 1000 lines long. It also reduces the size of the object code. This does come at the expense of speed, but it's fast enough for my requirements.
And you started a visual dataflow language on the same runtime http://web.onetel.com/~hibou/Full%20Metal%20Jacket.html
Full Metal Jacket is implemented in Emblem.
Instead of trying to provide a superset of semantics which encompasses all languages, it provides a single runtime which supports a subset of features, which then lives inside the "host" language environment.
It would be so nice to have a universal VM that stripped out all the unnecessary non-64 bit data types, cleaned out all the crazy method-overloading related overhead, dropped the security model and added a nice runtime reflection model and, dare I dream, a sane isolation mechanism between modules...
" The JVM is very powerful, but it has its own potential issues (type erasure and garbage collection come to mind)."
Can someone explain why type erasures would be an issue? GC is a loaded subject I know but don't type erasures allow Java to have generics? What is the negative here?
In java, the compiler would instead (I'm simplifying here), replace List<int> and List<bool> with List<object>, and at runtime, you wouldn't be able to tell a List<int> from List<bool> because their types were "erased" and became List<object>.
If type is preserved, in certain scenario it could be handy, like dynamically checking whether certain class conforms to generic type(better instanceof operator).
One example is the Gson library, looks at what twisted hack it uses to preserve the generics information at runtime(hints: TypeToken class).
As others have said, the JVM has been a popular runtime target for the last few years. But it's been in constant development for a quarter century, and it powers countless enterprise systems, so the amount of time and resources that have gone into it are enormous. It's hard to get that level of investment in any software, period.
Isn't type erasure one of the very reasons dynamic languages are so plentiful and abundant on the JVM?
Notably LuaJit, Golang (and before Plan9 and Inferno), Erlang, Haskell, Swift, libc++, etc are writing runtimes. The problem is that it becomes a mess very quickly, unless you are Mike Pall.
Also, the idea of a VM isolated from an OS is a marketing meme (how could a mere OS process be isolated from an OS?) So, smallest, thin-layer-over-an-OS runtime - approach pioneered by Inferno, is a much saner and efficient one.
Compilation to machine code + thin layer runtime + native ABI based FFI is still the hardest and still the best way to make a runtime since times of Common Lisp.
I am not writing runtimes because I am not Mike Pall, not a Google employee and no one pays me to do so.))
BTW, marrying, say, Arc (or femtolisp, or a sane subset of scheme) to LuaJIT runtime could be a nice project, to have a really powerful scripting language.
"Compilation to machine code + thin layer runtime + native ABI based FFI is still the hardest and still the best way to make a runtime since times of Common Lisp."
Also what is the thin layer, in this context? I don't know much about Lisp runtimes, I'm sure its fascinating as its both compiled and interpreted. Thanks.
Compare Golang or LuaJIT FFI with what Java does. Java uses libffi, but it does unnecessary copying and type and encoding conversions. Golang and LuaJIT has zero-copy FFI due to reusing of OS types (for UNIX-like systems it is C) - just dlload-and-call.
Thin layer (of abstraction) is how Golang's runtime is organized - delegating to an OS instead of reimplementing inside a VM.