Live data from Hacker News

What I Learned Making My Own JIT Language

mikedrivendevelopment.com

101–110 of 120 posts

Re: What I Learned Making My Own JIT Language

#102
post #64

Earlier quoted context omitted.

How about a language that loses the ability to redefine structures and methods after a certain point? I've been thinking of making a scripting language where you can freely redefine things until you return the main function to the script's caller. At that point main gets compiled / JITed / otherwise locked and any redefinitions done by calling it are errors.

> How about a language that loses the ability to redefine structures and methods after a certain point? Sounds awesome! We could call the phases before that point "compile time" and "preprocessor time", and the phase after that point "run time". /sarc

Most languages lack one of these phases. C/C++ and the C preprocessor are completely different languages, you can't execute C code during preprocessing. That's annoying and the reason for the introduction of constexpr in C++11. Other languages are too dynamic to be handled by a static compiler, but might benefit if you could transition to optimized machine code at runtime. Except for Lisp, I don't think there are many other languages where such a thing is possible.

Re: What I Learned Making My Own JIT Language

#103
post #69
post #30

Earlier quoted context omitted.

OMR is amazing. It's essentially IBM's J9 virtual machine, with all the man-decades of effort that went into that. However, if you're writing your own JIT language and you've never written one before, you probably want to learn something and write your own GC, JIT, etc. It's really not that hard. Then once you get an idea of how everything works you can use OMR and make it fast.

How does it compare to PyPy?

Don't know yet. IBM has a CPython-based JIT using OMR but it hasn't been publicly released yet.

Ruby OMR is a little more public and from what I can tell gets about a 30%-200% speedup. They basically just stapled OMR onto the regular Ruby interpreter, so there's nothing fancy going on yet and those numbers will definitely get higher.

Re: What I Learned Making My Own JIT Language

#104

Earlier quoted context omitted.

> I think "unknown type at compile time" and "JIT-able constructs" are opposites. Not opposites exactly. Types enforce a phase separation between compile time and runtime, but there's no reason you can't have a compilation phase at runtime too. This leads to staged programming, like MetaOCaml.

Well, to phrase it simply, in my mind a dynamic type is not a very JIT-able construct. I suppose it comes down to what people consider "very JIT-able", but I don't consider the same code possibly JIT'ing in separate ways in the same execution as very JIT-able.

Dynamic types are very JIT-able. The first JITs ever were for a dynamically-typed language (Smalltalk).

Re: What I Learned Making My Own JIT Language

#105
post #73

This is cool, I used to really like this sort of thing. At some point I transformed into a bitter sad man who can't stand reading articles like this because I feel like such a worthless piece of crap.

> bitter sad

This can be your muse, channel it into something!

Re: What I Learned Making My Own JIT Language

#106
post #21
post #11

Earlier quoted context omitted.

I guess you mean Julia, or to pick three from the past Common Lisp, Dylan and Strongtalk.

Is Julia really a Python competitor, I mean, for the same sort of niches? I had the impression that it was more specialized than that. (Bearing in mind of course that once you're Turing Complete and have a C binding you can technically do anything. Not that either of those two is necessary, but they are sufficient.) But as my phrasing implies, it was just an impression, and two people have mentioned it now. Common Li…

Common Lisp (CL) as a language is highly dynamic. However the typical implementation is incrementally AOT compiled (when you define a function, it is compiled, but you can redefine functions with few limitations).

CL as a language is essentially untyped[1], but does provide some provision for type hints in the standard. Some implementations extend this to the point where they can generate code as efficient as a typed language. Most implementations can also use these to detect some fraction of type errors when compiling.

Some features in lisp were designed with less flexibility to allow for improved performance while others with flexibility at the expense of performance.

I can't imagine how anyone could ever consider CL to be a static language without horribly misunderstanding the term, the language, or both.

1: You can declare types of variables, but the specification clearly intends this to be for optimization purposes: " Type declarations present in the compilation environment must accurately describe the corresponding values at run time;..." the fact that some implementations use these type hints for more does not IMO change the nature of the language.

Re: What I Learned Making My Own JIT Language

#107
I'm impressed that people didn't mention https://www.gnu.org/software/libjit/ it's a small well designed library with a very good interpreter. I wish more people put some effort on improving it and all of us could benefit form it without reinventing the whell again and again.

Re: What I Learned Making My Own JIT Language

#108
post #21

Earlier quoted context omitted.

Is Julia really a Python competitor, I mean, for the same sort of niches? I had the impression that it was more specialized than that. (Bearing in mind of course that once you're Turing Complete and have a C binding you can technically do anything. Not that either of those two is necessary, but they are sufficient.) But as my phrasing implies, it was just an impression, and two people have mentioned it now. Common Li…

Common Lisp (CL) as a language is highly dynamic. However the typical implementation is incrementally AOT compiled (when you define a function, it is compiled, but you can redefine functions with few limitations). CL as a language is essentially untyped[1], but does provide some provision for type hints in the standard. Some implementations extend this to the point where they can generate code as efficient as a typed…

> consider CL to be a static language without horribly misunderstanding the term

If you look at the file compilation spec in the standard, the compiler is allowed to inline functions and it can assume that a function in the same file can't be redefined later. -> no late binding for the calls in the file. A function declared by the developers to be inlined, makes them also no longer use late-binding for calls.

Specific implementations may also remove the compiler (see Allegro CL, LispWorks and some others), parts of an interpreter and other stuff of a typical Lisp runtime from a delivered applications. Generally function calls then might no longer use late-binding.

The CL standard might mention some dynamic facilities, but actual implementations might remove parts of that from a delivered application, too.

Example for more static facilities were the block compilation mode of Lucid CL, IIRC CMU CL also had a block compilation mode, some compilers do their own inlining (Allegro CL), mocl/clicc (and some others) do 'static' compilation of whole-programs for a large CL subset to C applications, LispWorks has an extensive delivery system which removes various unused Lisp facilities from an application ( http://www.lispworks.com/documentation/lw71/DV/html/delivery... ) and which can drastically reduce dynamic features.

Re: What I Learned Making My Own JIT Language

#109
post #86

Earlier quoted context omitted.

> In prototype-based languages like JavaScript and Lua redefining methods on an object is common at runtime, which makes them quite challenging to JIT. It may be most common early in execution, but there's no way to semantically define that boundary. It is common, but I do not think it is at all necessary. It always looks nasty whenever I see it in dynamic languages, and I never feel a need to do so in static languag…

> Properly optimized JIT output should be orders of magnitude faster than interpreted bytecode Say we had code like, a + b Statically compiled code might look like load a from stack to register load b from stack to register add a and b A VM would look like load opcode from state to register compute opcode address # nullop or two loads and index jump to opcode block load a from state to register load b from state to r…

> A VM would look like

    load opcode from state to register
    compute opcode address # nullop or two loads and index
    jump to opcode block
    load a from state to register
    load b from state to register
    add a and b
This example means that a simple addition requires several loads, a jump, and due to the nature of such a VM, also a store that you did not mention so that the next opcode can use the result.

The JIT'ed version might end up as simple as:

    ADD ECX, EDX
Why? The JIT version will take care of making the input simple platform integers, and can ensure that the values are kept in registers between all computations within the function. for fully JIT'd methods, args and return values can be passed as registers, never needing a load, and if a parameter is a small constant, it can be inlined into the instruction (e.g. ADD EAX, 3). Function calls can also be inlined, and depending on things, the arguments may be passed as registers to non-inlined JIT'd functions as well.

While the (extremely simplified!) VM example you gave can surely be pipelined well, it still has extremely suboptimal performance compared to a single "add" instruction (long jumping around is not free!), and wastes resources that could have been used to run the rest of the code. Instruction cache, micro-op cache and instruction decoder time are all very limited and valuable resources that would be wasted by such a VM.

Furthermore, in a real-world scenario, the VM version will be much worse, with potentially overloadable add operators and the likes, effectively making "add a + b" into a madness that potentially involves looking up inheritance chains to find an implementation of an "add" method.

The assumption that the JIT will need to have checks as a significant portion of the runtime for something like this would normally be false, unless the function indeed only implements "a+b". For a method-based JIT (i.e. jitting methods rather than arbitrary loops), any necessary checks will be at the beginning of the method as a one-time cost. These checks are usually quite basic, such as checking that an input is a primitive number (meaning no overloaded functionality or otherwise unexpected behavior), which is a simple compare. Assuming you do some longer math, the check becomes a very insignificant portion of the execution time.

I agree that data structures is a big part of performance (less wasted memory access), and that dynamic languages can't get quite as good as static languages in this regard, but V8 certainly does quite well in this regard. In JIT'd methods, it will even throw away JS types entirely and just operate in proper primitives if it can (V8's hidden classes are also quite robust, as long as you stick somewhat to the initial layout created by the method initially returning the object, such as a prototype constructor). However, I think it is incorrect to consider the better performance to not also be due to much better use of instructions and registers. With what I do for work, a single branch in the wrong place can lead to a 15% drop in performance, so I certainly do not think that CPU time should be tossed around.

I agree that things like navigating a large tree structure is mostly memory bound, but even then, a VM will show some amount of performance degradation over natively compiled code.

Re: What I Learned Making My Own JIT Language

#110
post #69
post #30

Earlier quoted context omitted.

OMR is amazing. It's essentially IBM's J9 virtual machine, with all the man-decades of effort that went into that. However, if you're writing your own JIT language and you've never written one before, you probably want to learn something and write your own GC, JIT, etc. It's really not that hard. Then once you get an idea of how everything works you can use OMR and make it fast.

How does it compare to PyPy?

Different technology. PyPy/RPython is a more "high level" approach built on top of metatracing while OMR is more low level and geared towards method-at-a-time JIT. (I might be wrong. I worked more with pypy but only know OMR superficially)
Post reply on HN