1. 21
  1.  

  2. 5

    I’m really amused they generate a C file and call out to gcc / clang. I wonder if they plan to move away from that strategy.

    1. 3

      This is a draft implementation of the concept, so probably yes.

      1. 2

        What would be gained by moving away from that?

        1. 6

          For one, no runtime dependency on a C compiler.

          C compilers are also fairly expensive to run, compared to other more targeted JIT strategies. And it’s more difficult to make the JIT code work nicely with the regular uncompiled VM code.

          Take LuaJIT. It starts by compiling the Lua code to VM bytecode. Then instead of interpreting the bytecode, it “compiles” the bytecode into native machine code that calls the interpreter functions that would be called by a loop { switch (opcode) { ... } }. That way when the JIT compiles a hot path, it directly encodes all entry points as jumps directly into the optimized code, and all exit conditions as jumps directly back to the interpreter code.

          Compare this to a external compiled object, which can only exit wholesale, leaving the VM to clean up and figure out the next step. A fully external object—C compiled or not—can’t thread into the rest of the execution, so its scope is limited to pretty isolated functions that only call similarly isolated functions, or functions with very consistent output.

          1. 2

            Compare this to a external compiled object, which can only exit wholesale, leaving the VM to clean up and figure out the next step. A fully external object—C compiled or not—can’t thread into the rest of the execution, so its scope is limited to pretty isolated functions that only call similarly isolated functions, or functions with very consistent output.

            This doesn’t seem to be related to the approach Ruby is taking, though? They’re callng out to the compiler to build a shared library, and then dynamically linking it in. There shouldn’t be anything stopping the code in the shared object from calling back into the rest of the Ruby runtime.

            1. 2

              Right, it can use the Ruby runtime, but it can’t jump directly to a specific location in the VM bytecode. It has to call a function that can execute for it, and will return back into the compiled code when that execution finishes. It’s very limited, compared to all types of code being able to jump between each other at any time.

          2. 4

            exec’ing a new process each time probably gets expensive.

            1. 3

              Typically any kind of JIT startup cost is quite expensive, but as long as you JIT the right code, the cost of exec’ing clang over the life of a long running process should amortize out to basically nothing.

              I’d expect that the bare exec cost would only become a significant factor if you were stuck flip-flopping between JITing a code section and deoptimizing it, and at that point you’d gain more from improving your JIT candidate heuristics rather than folding the machine code generator in process and continuing to let it flip-flop.

              There are other reasons they may want to move away from this C-intermediary approach, but exec cost doesn’t strike me as one of them.