2

I'm working on a VM (and a scripting language for it) that I plan to implement JITing for. I'm only working on the "plumbing" of it now, but I don't want the JIT compiler to be an afterthought. However, while I understand the fundamentals of it, I'm a bit confused on what exactly the JIT should do.

There are two ways I could think of it being implemented:

  1. Translate the bytecode into "proper" x86 just like any compiler would, thus eliminating the interpreter/VM part.
  2. Translate the bytecode into x86 that calls VM functions to tell it what to do thus eliminating the interpreter's opcode decoding step and going straight to calling the internal VM functions.

The first method would be difficult to implement simply because it requires knowledge on not only building a full-blown compiler, but being able to compile a high-level language that relies normally on VM functionality to be compiled into native code.

The second method would be much simpler to implement as you're not actually compiling the program, you're just dynamically creating a list of C function calls (to the internal VM) with corresponding operands with x86 instructions to call in the same order that would've otherwise require an interpreter to "decode".

However, while the second clearly seems more sane to implement, I'm not too sure how much more (or less) it would effect the performance of the program. What direction should I aim for? Any notable pros and cons?

  • FYI the second is called threaded code. – user7043 Jul 25 '15 at 12:15
  • Note that threaded Forth interpreters are in widespread use because they often can perform faster and use less memory (and that is for the program plus the interpreter) than an equivalent hand-optimized C program! Granted, part of this is due to Forth and not the threaded interpreter, but still, you should definitely test your assumption that it would be automatically slow! – Jörg W Mittag Jul 25 '15 at 12:42
  • @JörgWMittag Perhaps some Forth programs are faster than a C program that you spent the same amount of time writing and optimizing, but often faster than hand-optimized C? You'll excuse me if I am massively skeptical. In fact, if I didn't associate your user name with quite a few good answers, I'd simply dismiss this as yet another instance of the flat-out wrong propaganda that plagues programming language advocacy. – user7043 Jul 25 '15 at 13:33
  • @delnan - Forth is extremely cache friendly, so I would not be surprised at that claim. Whether you could write (and maintain) a Forth program that's equivalent to an arbitrary C program is another matter (caveat: it's been 30 years since I used Forth professionally, so take that comment with a grain of salt). – kdgregory Jul 26 '15 at 11:27
  • Another geezer data point: the DEC PDP-11 Fortran compiler from the 1970s also used a threaded interpreter. They might have done it for ease of implementation, but I strongly suspect that they wouldn't have done it if the performance was bad (and that was pre-cache, with a machine architecture that was flexible enough to run the processor backwards). – kdgregory Jul 26 '15 at 11:29
2

You're right that method 2 won't give you a huge performance boost over a simple bytecode interpreter loop. The real gains are to be made by using method 1. That said, method 1 isn't as hard these days as it used to be, as there are libraries that can help.

One interesting approach that should be relatively east and might give good performance is to use LLVM to implement method 2, and use an existing LLVM compiler (eg clang) to compile the functions you're calling to LLVM code rather than native - then run its optimization steps over the result, particularly the function call inliner. This should result in reasonably good native code without much in the way of complexity.

0

I would recommend using some existing JIT library, like GCCJIT, libjit, LLVM, asmjit, ... You could even consider translating your bytecode to C, dynamically compiling that C code into a shared library at runtime, and dlopen-ing that plugin etc...

You'll need to understand some compilation and optimization techniques to do so (in particular because your bytecode might be semantically far from the internal representations needed by the JIT library that you'll use).

  • The last point cannot be overstated. All of these libraries take code that's almost at the level of machine code, and turn it into actual machine code. The tricky part in compiling most languages that are called "scripting languages" is getting from the dynamically typed, late-bound, huge, complicated source language to something that is even remotely efficient. – user7043 Jul 25 '15 at 13:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.