|
|
|
+1 for LLVM ORC, it's really powerful but also very easy to use. There is a simple tutorial in C++ which is a great intro to it.
|
|
|
|
And the Rust ecosystem has good tooling for working with llvm already since it uses llvm itself.
|
|
|
|
As folks have mentioned, LLVM's JIT produces great code and is (relatively) easy to use. However, LLVM is extremely heavyweight. Which "latency" did you mean? Are you going to run these functions 1M times, so that the quality of the generated code is paramount (and you can afford really long compile time) or do you care more about "I hit enter and get the answer"? You can tune LLVM (disable almost all passes, use fast instruction selection) but it's really not focused on millisecond-ish compilation. There are a lot of "simple JIT" libraries for the latter case (you just want to feed in a simple IR and get machine code out, do an okay job at register allocation, but nothing fancy). None of them has "won" and most only have C bindings (to my knowledge).
|
|
|
|
Haven't used the RPython toolchain, but it's worth looking into. You write an interpreter in a restricted subset of Python2 called RPython and make your interpreter report the start of a loop in the guest language, and the end of a loop. The interpreter then gets transformed effectively into a JIT compiler. The underlying principle is something called meta-tracing. [edit: Changed "host" to "guest"; Python to Python2] I have a question to the experts though: The principle of meta-tracing suggests you might be able to write your guest language in Python. Is that currently possible with RPython/PyPy?
|
|
|
|
Basically the concept of rPython is you write your interpreter in rPython you get a jit for “free”. However your target language doesn’t have to be Python. A list of rPython projects is here: https://rpython.readthedocs.io/en/latest/examples.html I suppose you could do something similar like writing your interpreter in luaJIT. It’s not clear if rPython is doing anything special to enable jit in the target language but it is designed for this purpose.
|
|
|
|
GNU lightning is a simple and portable JIT library written in C, it might be a good fit. Its engine is fast and minimal, and does not perform much in the way of optimisations for you. (For what it's worth I'm a very minor contributor.) * https://en.wikipedia.org/wiki/GNU_lightning * https://www.gnu.org/software/lightning/ If you want a more sophisticated JIT engine, others have already mentioned libgccjit and LLVM (heavyweight compiler solutions), as well as Cranelift and Mir (more lightweight). Of these, only Cranelift is written in Rust.
|
|
|
|
I would personally try cranelift for this. Its goals are around producing executable code quickly rather than optimally, which is pretty much the opposite of llvm. There are lots of things out there that use llvm for jit, but even its jit library layer is pretty heavy and you’ll probably spend more time generating code than you’d like. That said, cranelift is still experimental.
|
|
|
|
I haven't used it, but cranelift is also my first thought for this. If you want a stable interface though, I might use wasm on top of it via wasmtime. I'm not sure how stable the API for wasmtime is, but at least the IR (wasm) is, and there's an ecosystem of tools around it. https://github.com/bytecodealliance/wasmtime
|
|
|
|
If your DSL is statically typed then I recommend that you have a look at the Mono CLR; it's compatible with the ECMA-335 standard and the IR (CIL) is well documented, even with secondary literature. If your DSL is dynamically typed I recommend LuaJIT; the bytecode is lean and documented (not as good as CIL though). LuaJIT also works well with statically typed languages, but Mono is faster in the latter case. Even if it was originally built for Lua any compiler can generate LuaJIT bytecode. Both approaches are lean (Mono about 8 MB, LuaJIT about 1 MB, much leaner and less complex than e.g. LLVM), general purpose, available on many platforms (especially the ones you're mentioning) and work well (see e.g. https://github.com/rochus-keller/Oberon/ and https://github.com/rochus-keller/Som/).
|
|
|
|
LLVM (in ORC mode) is very powerful. I wrote a JIT compiler for a DSL with it. It takes a fair bit of poring over the IR manual to figure out basic things. The optimization is as good as for C, including automatic unrolling of loops to generate SIMD instructions. A very useful feature is that you can write C or C++ and run it through LLVM to see the IR it generates, and adapt it to your needs. You can even do it in Godbolt. If your generated code is crunching over large amounts of data, an alternative to a JIT is to make the interpreter implicitly parallel. So each interpreter dispatch operation does N (say, 16) parallel operations, effectively cutting interpreter overhead by N. It works if there isn't data-dependent branching, which is often true for numerical operations.
|
|
|
|
It is fairly easy to use and can produce machine code for any architecture that LLVM supports (and this includes x86, x86_64, ARM, MIPS, PowerPC and SPARC).
|
|
|
|
|
|
|
|
|
There is Graal's Truffle [0] You'll have to rewrite your parser/interpreter in Truffle, but you get everything else "for free". Not in Rust. I wouldn't call it at all lightweight. It is cross-arch in the sense that the Graal JVM is cross-arch, which may or may not be sufficient for your purposes. [0] https://www.graalvm.org/22.0/graalvm-as-a-platform/language-...
|
|
|
|
You could write JVM byte code based on some language that isn’t Java but then you get the garbage collector and the rest of the runtime which you may or may not like.
|
|
|
|
|
|
I would suggest generating webassembly instead and use wasmtime.
|
|