Posts

Retrospective of the MoarVM JIT

Hi hackers! Today the MoarVM JIT project is nearly 9 years old. I was inspired by Jonathan's presentation reflecting on the development of MoarVM, to do the same for the MoarVM JIT, for which I have been responsible. For those who are unfamiliar, what is commonly understood as 'JIT compilation' for virtual machines is performed by two components in MoarVM. A framework for runtime type specialization (' spesh ') A native code generation backend for the specialized code (the 'JIT'). This post refers only to the native code generation backend component. It, too, is split into two mostly-independent systems: A backend that emits code directly from MoarVM instructions from machine code templates (the 'lego' JIT compiler). Another backend that transforms MoarVM instructions into an expression-based intermediate representation and compiles machine code based on that (the 'expression' compiler). Things that worked well Using DynASM for code generat...

Why bother with Scripting?

Many years back, Larry Wall shared his thesis on the nature of scripting. Since recently even Java gained 'script' support I thought it would be fitting to revisit the topic, and hopefully relevant to the perl and raku language community. The weakness of Larry's treatment (which, to be fair to the author, I think is more intended to be enlightening than to be complete) is the contrast of scripting with programming . This contrast does not permit a clear separation because scripts are programs . That is to say, no matter how long or short, scripts are written commands for a machine to execute, and I think that's a pretty decent definition of a program in general. A more useful contrast - and, I think, the intended one - is between scripts and other sorts of programs , because that allows us to compare scripting (writing scripts) with 'programming' (writing non-script programs). And to do that we need to know what other sorts of programs there are. The short...

Reverse Linear Scan Allocation is probably a good idea

Hi hackers! Today First of all, I want to thank everybody who gave such useful feedback on my last post.  For instance, I found out that the similarity between the expression JIT IR and the Testarossa Trees IR  is quite remarkable, and that they have a fix for the problem that is quite different from what I had in mind. Today I want to write something about register allocation, however. Register allocation is probably not my favorite problem, on account of being both messy and thankless. It is a messy problem because - aside from being NP-hard to solve optimally - hardware instruction sets and software ABI's introduce all sorts of annoying constraints. And it is a thankless problem because the case in which a good register allocator is useful - for instance, when there's lots of intermediate values used over a long stretch of code - are fairly rare. Much more common are the cases in which either there are trivially sufficient registers, or ABI constraints force a spill to m...

Something about IR optimization

Afbeelding
Hi hackers! Today I want to write about optimizing IR in the MoarVM JIT, and also a little bit about IR design itself. One of the (major) design goals for the expression JIT was to have the ability to optimize code over the boundaries of individual MoarVM instructions. To enable this, the expression JIT first expands each VM instruction into a graph of lower-level operators. Optimization then means pattern-matching those graphs and replacing them with more efficient expressions. As a running example, consider the idx operator. This operator takes two inputs ( base and element ) and a constant parameter scale and computes base+element*scale . This represents one of the operands of an  'indexed load' instruction on x86, typically used to process arrays. Such instructions allow one instruction to be used for what would otherwise be two operations (computing an address and loading a value). However, if the element of the idx operator is a constant, we can replace it instead...

A short post about types and polymorphism

Hi all. I usually write somewhat long-winded posts, but today I'm going to try and make an exception. Today I want to talk about the expression template language used to map the high-level MoarVM instructions to low-level constructs that the JIT compiler can easily work with: This 'language' was designed back in 2015 subject to three constraints: It should make it easy to develop 'templates' for MoarVM instructions, so we can map the ~800 or so different instructions supported by the interpreter to something the JIT compiler can work with. It should be simple to process and analyze; specifically, it should be suitable as input to the instruction selection process (the tiler). It should be simple to implement, both from the frontend (meaning the perl program that compiles a template file to a C header) and the backend (meaning the C code that combines templates into the IR that is compiled). Recently I've been working on adding support for floating point ...

New years post

Hi everybody! I recently read jnthn s Perl 6 new years resolutions post, and I realized that this was an excellent example to emulate. So here I will attempt to share what I've been doing in 2018 and what I'll be doing in 2019. In 2018, aside from the usual refactoring, bugfixing and the like: I added support for the fork() system call in the MoarVM backend. I removed the ' invokish ' control flow mechanism and replaced it with controlled return address modification. I requested a grant from the Perl Foundation aiming to complete the expression JIT compiler with floating point support, irregular registers and the like. So 2019 starts with me trying to complete the goals specified in that grant request. I've already partially completed one goal (as explained in the interim report ) - ensuring that register encoding works correctly for SSE registers in DynASM. Next up is actually ensuring support for SSE (and floating point) registers in the JIT, which is su...

A future for fork(2)

Hi hackers. Today I want to write about a new functionality that I've been developing for MoarVM that has very little to do with the JIT compiler. But it is still about VM internals so I guess it will fit. Many months ago, jnthn wrote a blog post on the relation between perl 5 and perl 6. And as a longtime and enthusiastic perl 5 user - most of the JIT's compile time support software is written in perl 5 for a reason - I wholeheartedly agree with the 'sister language' narrative. There is plenty of room for all sorts of perls yet, I hope. Yet one thing kept itching me: Moreover, it’s very much the case that Perl 5 and Perl 6 make different trade-offs. To pick one concrete example, Perl 6 makes it easy to run code across multiple threads, and even uses multiple threads internally (for example, performing optimization and JIT compilation on a background thread). Which is great…except the only winning move in a game involving both threads and  fork()  is not to pl...

Template Compiler Update

Hi everybody. After samcv++ released MoarVM again, it was finally time to introduce some updates to the expression template compiler. As you may or may not know, the expression JIT backend maps MoarVM opcodes to its internal representation via expression templates . At runtime (during the JIT compilation phase), these templates are combined to form an expression tree. The expression template language remains somewhat underdocumented, but quite a few brave developers have still ventured to add templates. With the new template compiler, a few things will change: Template syntax and type checking is much stricter. This means that, for example, it is no longer possible to place a C-macro expression (which evaluates to a constant value) where an expression operand (i.e. code) would be expected. Templates can contain references to MoarVM opcode operands (written as $0, $1 , $2 etc.) At runtime, read operand references are substituted with the value of the operand, and write operand...

A Curious Benchmark

Hi hackers! I recently saw a curious benchmark passed round on the #moarvm channel. (I understand that this originates from Ovid during his Future of Perl 5 and 6 presentation, but I haven't seen it myself, so if I'm wrong, don't hesitate to correct me). It's curious because it runs on perl5 and perl6, and because the difference is… significant: # reciprocal.pl my $x = 0; $x += 1/$_ for 1..50_000_000; print "$x\n" ; Perl 5 (5.24.1), on my laptop (Intel i7-4700HQ CPU @ 2.40GHz, 16GB ram), takes approximately 2.7s to print the value 18.3047492382933 . Perl 6, on the same script, takes just shy of 2m18s , or nearly 140s. This is 51 times as much. Perl 5 is not known as a particularly fast language, but in this case perl 6 is really very slow indeed. Let's try and do a little better. As pointed out by timotimo , the benchmark above is a pessimal case for rationals numbers, which perl 6 uses by default. Perl 5 uses floating point throughout. So...

Perl 6 on MoarVM has had a JIT for a few years

Dear readers, I recently came across the following comment on the internet (via Perl 6 weekly ): perl6 on MoarVM is starting to grow a JIT  Which is very interesting because: Someone had at least heard of our efforts, so that's a win. But they had left with the impression that it was still in a beginning phase. Clearly, we have some PR work to do. Consider this my attempt. When people are talking about a 'JIT' colloquially and especially in the context of dynamic languages, they usually refer to a system that has both a dynamic specialization functionality, as well as a machine-code emitting functionality. MoarVM has had support for both since 2014. Historically we've called the specializer 'spesh' and the machine code emitter 'jit'. Maybe for communicating with the rest of the world it is better to call both 'the JIT' frontend and backend, respectively. Without further ado, let me list the history of the MoarVM JIT compiler: The ...

Controlled Stack Hacking for the MoarVM JIT Compiler

Hi readers! Today I have a story about a recently-merged set of patches that allows  MoarVM to use the on-stack return pointer to reduce the overhead of exception handling and other VM features for JIT compiled code. Maybe you'll find it interesting. As you might know, MoarVM Is a bytecode interpreter. In some situations, MoarVM internals need to know the current position in the execution of the program. For instance in exception handling , all exception thrown within a block are caught by the associated CATCH block or propagated if no such block exists. Such blocks are indicated as a range within the bytecode, and we find the associated CATCH block by comparing the current position with the known ranges. This is relatively straightforward to implement for the interpreter, because the interpreter must maintain a 'current position' pointer simply to function. (MoarVM stores a pointer to this pointer in a thread context object so that it is available throughout the VM)...

Some things I want

Lately I've been fixing a few bugs here and there in the JIT compiler, as well as trying to advance the JIT expression optimizer. The story of those bugs is interesting but in this post I want to focus on something else, namely some support infrastructure that I think we should have that would make working on MoarVM and spesh/jit in particular much nicer. There are a bunch of things related to runtime control of spesh and the JIT: jit-bisect.pl  is an awesome tool (if I say so myself) that deserves it's own blog post. It can figure out which frame is the first to be miscompiled and cause an execution failure. (Or to be specific, the least miscompile to cause a failure). When a JIT issue is brought  to me, I reach for it directly. It should be more powerful: Because it does not know in advance how many frames are compiled in a program, it uses an 'exponential probing' strategy to find the first breaking block, and then binary search between the last two probes. Th...