I think the main point of libgccjit is actually to provide a cleaner interface than GIMPLE to gcc.
I think the main point of libgccjit is actually to provide a cleaner interface than GIMPLE to gcc.
Well the most high-profile project using libgccjit right now is Elisp (feature/native-comp GNU Emacs branch, which by all indications will eventually make it into the official release), and using GCC as a JIT compiler is how Embeddable Common Lisp has always worked.
https://gitlab.com/embeddable-common-lisp/ecl/-/blob/develop...
What is the difference?
(compile (lambda (x) x))
Compiles the lambda immediately.[edit]
https://en.wikipedia.org/wiki/Dynamic_compilation discusses Dynamic Compilation vs Incremental Compilation.
By that definition using the REPL or loading source files in SBCL qualifies as a JIT.
> https://en.wikipedia.org/wiki/Dynamic_compilation discusses Dynamic Compilation vs Incremental Compilation.
That article just seems to perpetuate the confusion: "Runtime environments using dynamic compilation typically have programs run slowly for the first few minutes, and then after that, most of the compilation and recompilation is done and it runs quickly."
This could be talking about adaptive optimization, or dynamic recompilation, or even dispatch caching. You can do adaptive optimization in an interpreter or bytecode VM. Dispatch caching does not involve anything that looks like a compiler at all unless you are doing inlining.
Most importantly, none of those things is what GNU Lightning or libgccjit does. All they do is generate machine code, at run-time. That is what a JIT is.
When SBCL compiles rather than interprets at the REPL, that arguably qualifies as a JIT. Same for loading top-level forms that aren't DEFUN. I'm pretty sure that ECL uses an interpreter in those cases though.
Again, look at the code and explain how that is not JIT: https://gitlab.com/embeddable-common-lisp/ecl/-/blob/develop...
When you explicitly compile (e.g. via ASDF which separates the compile and load phase with FASLs between) in any lisp, that doesn't meet the bar for being a JIT anymore than a C program that distributes plugins as source and compiles them to load the .so is.
You have machine code generation at run-time, you can then make arbitrary decisions for when to do the code generation. Explain how this is not a JavaScript JIT compiler when you run it in ECL: https://github.com/akapav/js/blob/master/js.lisp#L72
> That's why I said SBCL's LOAD and REPL (which decides between interpreting and compiling) is arguably JIT.
SBCL does not really have an interpreter (it started out without any kind of interpreter at all), it only has a compiler. No wonder you misunderstood my point about SBCL.
> that doesn't meet the bar for being a JIT anymore than a C program that distributes plugins as source and compiles them to load the .so is.
Let's try to rephrase that a little bit: "that doesn't meet the bar for being a JIT anymore than a [web browser] program that distributes [loads] plugins [JavaScript] as source and compiles them to load the .so [machine code] is."
But neither SBCL nor ECL has a JIT. A JIT is a subsystem which on its own decides to compile an intermediate representation (usually some compiled byte code) to native code. Typically the JIT decides when and how to compile it, often using runtime information to compile runtime optimized code.
In both SBCL and ECL, the Lisp is either configured to compile AOT or the user calls the compiler. If there is a source interpreter running the source, the user has to explicitly call COMPILE and COMPILE will not use any runtime information on its own - like using statistics how often functions are being use with which argument types and compile code versions for different argument types. It will also not compile new optimized versions on additional runtime information. The current Lisp compiler will only use the static information which is in the source.
JIT systems OTOH watch the byte code execution and compile code on its own to optimized/native versions, based on the runtime information the JIT system observes.
In basically most Common Lisp implementation, the decision to what kind of code and when it is compiled/executed is left to the user.
Though I think there was a CLISP experiment with compiling CLISP byte codes to native with a JIT. https://sourceforge.net/p/clisp/mailman/clisp-devel/thread/e...
ECL has three ways to run code: an interpreter, a byte code engine and a Lisp-to-C compiler (files, and incremental via files). In theory one could use a JIT to compile the byte codes to native code at runtime. I don't know if this has been tried.
Some experiments of improving CLOS speed at runtime (similar to what Julia does) might be considered to be a moral equivalent of a JIT - but I haven't checked it out.
SBCL's original "interpreter" is %simple-eval, which just calls the compiler. SB!EVAL was added sometime around 2008, but as you point out it is not used unless you set evaluator-mode to something other than :compile. The default code path goes through %simple-eval and the compiler. I have been using SBCL since before SB!EVAL was added, and I have never used the interpreter.
> ECL has three ways to run code: an interpreter, a byte code engine and a Lisp-to-C compiler
The confusing thing about ECL is that someone decided to call their bytecode VM an "interpreter." ECL has no interpreter - eval_nontrivial_form just calls the bytecode compiler.
All that is irrelevant to the fact that ECL uses GCC as a JIT.
> In theory one could use a JIT to compile the byte codes to native code at runtime.
Which would be completely pointless because GCC (or clang or MVCC, but the original question was about libgccjit) is already there and does better optimization.
> Some experiments of improving CLOS speed at runtime (similar to what Julia does) might be considered to be a moral equivalent of a JIT
Like half of the PCL is devoted to various kinds of caching that are in fact adaptive optimization. It is not a big step from that to inline caching. But again, why would you do that when you don't need to?
https://github.com/guicho271828/inlined-generic-function https://github.com/marcoheisig/fast-generic-functions
There is a reason Mozilla went from tracing (TraceMonkey) to inline caching (JägerMonkey) to type specialization (IonMonkey). JavaScript JITs have moved closer to Lisp compilers, not vice-versa.
I have covered other misconceptions that you have listed elsewhere in this thread.
There would only be a reason to use it, if it were providing added functionality. In SBCL I don't use the interpreter, but in other implementations I might use it, for example when a source stepper uses an interpreter.
> ECL has no interpreter
I missed that - actually ECL had an interpreter, but it was removed long time ago.
> Which would be completely pointless because GCC (or clang or MVCC, but the original question was about libgccjit) is already there and does better optimization.
That's not clear. If one would do runtime JIT compilation with runtime statistical information, it could very well a speed improvement. There are a bunch of things which could (!) be useful:
Typically functions in Lisp have only one compiled version, depending on a fixed amount of declarations and optimization settings. A JIT could compile several optimized versions for different argument types and choose the best fit one at compile time.
A JIT could do inlining code based on statistical information and select at runtime optimized functions with additional inlining.
A JIT could remove runtime dispatch for often used code parts and provide versions for that.
> PCL is devoted to various kinds of caching
And a bunch of implementations were trying to make sure that no compilation takes place during CLOS calls -> thus making sure that a Lisp compiler at runtime is not necessary.
> There is a reason Mozilla went from tracing (TraceMonkey) to inline caching (JägerMonkey) to type specialization (IonMonkey). JavaScript JITs have moved closer to Lisp compilers, not vice-versa.
Lisp compilers are usually AOT native code generating compilers, like ECL's Lisp->C compiler.
> I have covered other misconceptions that you have listed elsewhere in this thread.
Like you claim that ECL's version of the Common Lisp COMPILE is a JIT. Then basically most COMPILE implementations in Common Lisp implementations would be a JIT. Is there anything that ECL's COMPILE function makes it special?
Otherwise Lisp I of 1960 had a JIT. The function comdef (page 53 in the Lisp I Programmer's Manual) would compile named functions at runtime to machine code.
Or you could just be wasting cycles on profiling overhead and recompiling things for no reason.
> A JIT could compile several optimized versions for different argument types and choose the best fit one at compile time.
That's... a compile time optimization.
> A JIT could do inlining code based on statistical information and select at runtime optimized functions with additional inlining.
Tracing is very close to self-modifying code. What happens when you redefine a function? What happens when you redefine a function at a breakpoint? There is a lot of runtime overhead (profiling, bookkeeping to back out of inlining) and it is very hard to debug. And it is never going to be as fast as static inlining and dispatching (sealing) at compile time.
> A JIT could remove runtime dispatch for often used code parts and provide versions for that.
That's what dispatch caching already does.
I think you and other people in this thread bought into the marketing hype for 10 year old tracing JavaScript implementations and now think that "JIT" is some kind of magic runtime optimization. The optimizations are all orthogonal to JIT (in fact GNU Lightning does none of the ones you alluded to). Adding tracing to any of the existing Common Lisp implementations would be a huge waste of time when there are easier and more effective things that can be done (like say converting an existing implementation to use SSA).
> Then basically most COMPILE implementations in Common Lisp implementations would be a JIT.
Yes.
> Is there anything that ECL's COMPILE function makes it special?
ECL uses GCC. If you read the original question, this was asking about libgccjit.
> Embeddable Common Lisp
ECL compiles to c.
And JavaScript programs almost never change after a website is loaded. That has nothing to do with whether the programs are compiled.
> you only have to recompile a few files. The elisp isn't being JITed, it's being compiled into shared objects which are cached aggressively
You do not need any files to compile Elisp code, and you do not have to save the bytecode anywhere.
> Completely different kettle of fish.
What exactly is your definition of JIT? I think you might be confusing "JIT" with dynamic recompilation.
> ECL compiles to c.
Ok, and what do you think happens to C?
> And JavaScript programs almost never change after a website is loaded
No, but new javascript programs are frequently loaded.
> You do not need any files to compile Elisp code, and you do not have to save the bytecode anywhere.
You do not need to do any of this by hand, but it still happens; emacs does it automatically.
> What exactly is your definition of JIT? I think you might be confusing "JIT" with dynamic recompilation.
The term is somewhat nebulous. I don't think it's appropriate to apply it to the result of a traditional compilation process that occurs immediately prior to execution. If (for instance) I write a script in c and run it with tcc -run, what happens is that that file is compiled—very similarly to a ‘traditional’ AOT, though the dynamic linker is bypassed—and the result is run.
Essential to JIT, in my view, is not dynamic recompilation, but plain dynamic compilation. Compilation should occur concurrently with execution, and is usually accompanied by some heuristic for deciding what to compile first (e.g. tracing) as well as an interpreter. Wikipedia agrees:
> JIT is a way of executing computer code that involves compilation during execution of a program
______________________________________
> what do you think happens to C?Under most implementations, it is statically compiled to a native binary. Even if it is not, however, ECL does not JIT c, but produces it statically.
This is probably what confuses a lot of people into thinking that program distribution has anything to do with JIT. JavaScript is the only programming language for which the main method of program distribution is source-code-only, and why all the trade-offs of adaptive optimization seem necessary, to the point that now people are making its necessity into a virtue. Likewise for Java stealing credit for popularizing portable bytecode as the main method of program distribution - although Gosling coined the term JIT years before Java had a JIT compiler.
> If (for instance) I write a script in c and run it with tcc -run
And what happens when you use libtcc?
> Compilation should occur concurrently with execution
Execution of what? You do not need an interpreter or a bytecode VM to have a JIT compiler.
> usually accompanied by some heuristic for deciding what to compile first (e.g. tracing)
That is adaptive optimization, and is orthogonal to compiling (you can have adaptive optimizations in an interpreter or a bytecode VM). Tracing (which is not a heuristic btw) is a variation of profile-guided optimization, which is orthogonal to JIT.
> Even if it is not, however, ECL does not JIT c, but produces it statically.
I posted this elsewhere in this thread:
https://gitlab.com/embeddable-common-lisp/ecl/-/blob/develop...
Can you explain why you think that is not a JIT.