Copy-and-Patch: Fast compilation for high-level languages and bytecode (2020)
arxiv.org
arxiv.org
- Copy and Micropatch: Writing Binary Patches in C with Clang preserve_none https://www.philipzucker.com/permutation_compile/
- https://github.com/purseclab/Patcherex2/pull/31 A pull request to the PatcherEx2 library
I stuck my paper on it under Prof Phil Wadler's office door and ran away. I have no idea if he ever read it :D
A copy-and-patch JIT compiler for CPython - https://news.ycombinator.com/item?id=38769874 - Dec 2023 (68 comments)
Copy-and-Patch: Fast JIT Compilation for SQL, WebAssembly, and Others - https://news.ycombinator.com/item?id=28547057 - Sept 2021 (7 comments)
And back in the 70s, when the microcomputers and interactive computing were becoming the norm, and people really cared about the compilation times — still nobody bothered to implement that kind caching, AFAIK, even when the compiler performed way less complex kinds optimizations than they do today (heck, even register allocation was not done as graph colouring back then).
int f(double x, int y) {
return x * y;
}
int g(int x, int y) {
return x * y;
}
These two functions differ by a single token, yet their assembly translations have only one common instruction, and this instruction is "ret": f:
movapd xmm1, xmm0
pxor xmm0, xmm0
cvtsi2sd xmm0, edi
mulsd xmm0, xmm1
cvttsd2si eax, xmm0
ret
g:
mov eax, edi
imul eax, esi
retThat's... not exactly true? Yes, memory was scarce but that's precisely why the compilers in the 70s used disk I/O for temporary storage way more extensively than the modern ones do. Let's take Unix V6, for example: the C compiler used two temporary disk files to keep data between its three passes, the assembler used three temporary files to keep data between its two passes, and I can't quite wrap my head around how many temporaries its linker used but it's at least 4. Nobody bothered to try to keep and reuse those temporaries, however.
Compilers today, instead, keep almost everything in memory and don't generally bother with temporary files at all because why would they? The growth of the computing speed has outpaced the memory/disk speeds anyhow.
I feel like a lot of these sorts of optimization are best done at the language level, before it gets to the instruction level, eg. fusion, supercompilation, partial evaluation are all more general than and far more effective optimizations than these low-level rewrites. If you've already done those optimizations, then you just want to spit out code as fast as possible.
The low-level transformation that matters the most is probably register allocation. Good register allocation is responsible for a lot of OCaml's speed. I'm not clear whether that would be significantly impacted with copy and patch.
From what I understand, Cranelift's current instruction selector is very close to copy-and-patch conceptually (it maps patterns of bytecode instructions to sequences of machine instructions), and the pass Cranelift spends the most time in, register allocation, still has to happen in copy-and-patch.
So if copy-and-patch still has a meaningful advantage over Cranelift, I'm very curious to know where it comes from.
how would this work on OSs under hardened runtime rules?
What OSes prohibit that? Linux doesn't (well, I think it can with SeLinux maybe?). OpenBSD might?
https://developer.apple.com/documentation/bundleresources/en...
From what I gather, Deegen is independent of the VM in that repo, but lives there afaik.
The repo I link to here is by Haoran Xu, one of the authors of the paper.
Y'all need to chill out.
There is a prior, very similar approach in GNU Jitter, but it uses only the compiler and some magic rather than the linker for marking spots to replace. I read about it by mention of moonchild in a thread[0] linked by foota here.
I say possible in the sense that hardware supports it. The standard C language doesn't support this, but GCC's GNU C does support it as one of its extensions.
The GNU Jitter author wrote about this, see page 48, referred to as page '17', of this PDF: https://ageinghacker.net/talks/jitter-slides--saiu--ghm2017-...
See also Gforth writing about the same technique: https://gforth.org/manual/Threading.html
The term context threading refers to generation of native code that behaves much like conventional threaded-code interpretation, except specialised to the particular input code. [0][1]
See also dynamic superinstructions, a term used by gforth to refer to its runtime generation of native code for input Forth code. [2]
Disclaimer: I'm no Forth expert. I should give [1] a proper read, it looks interesting.
[0] https://webkit.org/blog/214/introducing-squirrelfish-extreme...
[1] (PDF) https://csng.cs.toronto.edu/publication_files/0000/0162/demk...
[2] https://www.complang.tuwien.ac.at/forth/gforth/Docs-html-his...
The creator of Jitter has read a lot about Gforth, as evidenced in [0][1].
You're not the only one to suspect there's some reinvention going on here somewhere though. [2] I'm not familiar enough with these topics (copy-and-patch and Jitter) to weigh in.
[0] (PDF) https://ageinghacker.net/talks/jitter-slides--saiu--ghm2017-...
[1] (PDF) https://ageinghacker.net/talks/jitter-slides--saiu--bts-2022...