Rendered at 22:20:38 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
MaxBarraclough 14 hours ago [-]
Reminds me of the 2024 blog post Look ma, I wrote a new JIT compiler for PostgreSQL [0]. Both articles lament that Postgres's LLVM-based JIT [1] takes a while to generate code.
> The rarity of JIT compilers makes me believe that implementing a JIT compiler historically was too difficult for it to be worthwhile.
That's only true of writing a JIT from scratch. There's no rarity of JITs, it's just that LLVM (and other frameworks) are often used. Every major interpreter has a JIT compiler. PCRE2 has a JIT compiler. There are JIT frameworks out there with much faster code-generation than LLVM: Cranelift, GNU Lightning, Mir. I doubt they could do code-generation faster than a custom copy-and-patch JIT, but they'd be much faster than LLVM.
Not sure where the idea comes from that Cranelift is much faster than LLVM -O0, at least in our experiments in 2024 it wasn't, see [1] Fig. 6.
Template-based code generators suffer from bad code quality due to missing register allocation.
Our TPDE-based compilers compile a bit slower than template-based code generation but the generated code is much smaller and faster ([2] Fig. 2). Also for database workloads ([2] Fig. 6).
All that said, Postgres' main limitation is that it (IIRC) only compiles single expressions from operators, not pipelines. This fundamentally limits the achievable performance improvement compared to databases that perform more extensive query compilation.
PS: sorry for the promotion of my own research here, just couldn't resist.
MaxBarraclough 3 hours ago [-]
Always good to have proper researchers in the thread.
> Not sure where the idea comes from that Cranelift is much faster than LLVM -O0
Cranelift describes itself as a fast, secure, relatively simple and innovative compiler backend. [0] Interesting that LLVM can compete there, with its optimisations dialed down.
> Postgres' main limitation is that it (IIRC) only compiles single expressions from operators, not pipelines. This fundamentally limits the achievable performance improvement compared to databases that perform more extensive query compilation.
That sounds pretty limiting. That's separate from query optimisation though, right? The query optimiser is presumably able to reason 'broadly' and not just at the level of individual expressions? High-level query-plan optimisation must be much more consequential than effective use of JIT compilation.
> That's separate from query optimisation though, right? The query optimiser is presumably able to reason 'broadly' and not just at the level of individual expressions? High-level query-plan optimisation must be much more consequential than effective use of JIT compilation.
Yes, yes, and yes. For databases, query optimization (esp. join ordering for larger queries, which heavily depends on estimates) is fundamental. Query optimization happens at the level of the query plan, JIT compilation is only relevant afterwards. A bad query plan leads to asymptotically worse performance (e.g., bad join ordering with huge intermediate results).
On query plan execution: The "classical" model as used in e.g. Postgres is a pull-based iterator model, where operators implement a next() method yielding the next tuple and in there recursively call next() on their child operators (e.g., a next() of a select operator calls next() on its child operator, then applies the predicate [what Postgres JIT-compiles], and returns the tuple if the predicate was true). This can happen one tuple at a time (Postgres) or "vectorized" where multiple tuples are processed at once (e.g. DuckDB). A query-compiling database will split the tree into pipelines and compile each pipeline as one function (e.g., a pipeline will iterate over all the tuples from a source (e.g. tablescan) and a select operator then becomes an if statement inside that loop). This results in pretty tight loops, avoids per-tuple dispatch overhead, and enables more optimizations inside the JIT-ted code (e.g., tuple values don't need to be reloaded from memory all the time). (I find the original paper on query compilation [1] to be well readable.)
Interesting, thanks for the link. LLVM isn't the only game in town though, nobody using it (or libgccjit) for JIT should be surprised to see relatively long compile times. I wonder if the postgres project will try a different backend.
There's a strong 'diminishing returns' effect in striking a balance between compile time and the performance of the generated code. I'd expect a more lightweight (less optimising) JIT engine to be able to produce code with pretty respectable performance while taking only a fraction of the time that LLVM takes. There's a follow-up to the blog post I linked above, which bears this out. [0] (I don't know if that JIT solution is production-ready or viable for merging into postgres, mind.)
The blog post [0] gives this performance comparison:
> So, on our stupid benchmark, doing 10 times a simple SELECT * FROM demo WHERE a = 42 on a 10 million rows table...
Thanks, sljit looks somewhat similar to GNU lightning.
On reflection I wonder if I overstated the widespread use of JIT and of JIT compiler frameworks. All the 'major' well-resourced high-profile JIT-based interpreters I can think of don't use an off-the-shelf JIT framework for their backend, which makes sense as they want to carefully tune the code-generation. OpenJDK, OpenJ9, .Net, V8, SpiderMonkey, JavaScriptCore. LuaJIT and Python's new JIT don't use one either, nor does the Linux kernel's BPF engine.
The Guile Scheme interpreter uses a fork of the GNU Lightning JIT library. [0] Julia and (as mentioned) Postgres use LLVM for their JITs. I'm trying to think of other projects that use a JIT framework/library.
Similarly, I can't think of many problem domains where it makes sense to use JIT. The ones that spring to mind are interpreters (of course), regex engines, and DBMSs. JIT can also help in high-performance computing, to tailor the code to the particular problem and the particular CPU. [1] I don't think there are many other contexts where it makes sense to use JIT though.
JIT compilation brings its own drawbacks in portability (both between hardware platforms and operating systems), complexity, and perhaps cybersecurity, which might also limit its adoption, even if a good JIT framework could help with all three.
I think that the "manual JIT compilation" that Common Lisp provide is the most practical compromise here. Sure, you don't have the automated switching between bytecode execution and progressively optimized compilation and you need to manually track runtime typing information to feed to the compiler, but the machinery is so much simpler and builtin!
This is perhaps a little meta, but this was a pleasant read. It’s refreshing to read an article about using an LLM that doesn’t read like it was also written by that LLM.
I might use this approach to generate the stencils for a JIT firewall I’ve been experimenting with.
It also occurs to me that this could be used to generate eBPF byte code on the fly as well
glum64 14 hours ago [-]
Uhm, Common Lisp, where JIT is not only available but is also manageable: the programmer can decide what deserves to be compiled and what does not.
Besides run time, JIT is available also when the code is compiled or loaded for execution (i.e., do you have a compilation or loading speed-up in mind? no problem, you can also compile that speed-up into native machine code, and so ad infinitum...).
clbrmbr 12 hours ago [-]
is an xtensa lx7 (esp32-s3) target available that does not use llvm?
glum64 8 hours ago [-]
I am told there is http://www.ulisp.com/show?2AJI
Never used it myself; I cannot attest to the completeness of the implementation.
fweimer 12 hours ago [-]
Not in typical builds of SBCL: all code is compiled before evaluation.
pfdietz 8 hours ago [-]
You can turn that off by setting or binding sb-ext:*evaluator-mode* to :interpret.
Also, on my machine, compiling the identity lambda form takes about 200 usec with (optimize (compilation-speed 3) (debug 0)). SBCL could use a faster JIT mode, perhaps at compilation-speed 3/speed 0. Perhaps there are some other internal special variables that could be tweaked to reduce compile time.
fweimer 7 hours ago [-]
Are you sure this is actually makes a difference in your build?
Mine doesn't have the SB-INTERPRETER package, so I doubt binding the variable has an effect.
pfdietz 7 hours ago [-]
When calling EVAL it can be much faster to use the interpreter than to go through the compiler. This has bitten me in the past. A faster compiler would get around that.
One of the competing open Common Lisp implementations, CCL, has a much faster compiler (albeit one that produces worse code). This can be useful in development.
It depends on the implementation. CLISP compiles when it is told to.
pfdietz 7 hours ago [-]
Last I checked, CLISP compiles to byte code. Did they add a JITter for the byte code?
fweimer 7 hours ago [-]
Compiling to bytecode is still compilation. An interpreter would operate directly on the S-expressions. Obviously, this makes some constructs (such as TAGBODY) very inefficient.
mgaunard 12 hours ago [-]
The problem with the approach is that it's not real JIT-compilation, it's just assembly templates with basic substitutions.
By not using LLVM, you're missing all the optimizations it does.
the-lazy-guy 11 hours ago [-]
This is absolutely a JIT-compiler. It compiles code into machine code. This is a surprisingly efficient way to get noticeable speedup relative to interpretation. Also it is much safer than proper optimising compiler. Say ebpf jit-compiler functions very similarly, because it is fast and _secure_ way to jit. (well, there's a bit of cheating because before emitting bpf bytecode it goes through gcc/clang pipeline).
LLVM is a large dependency if you need to JIT. There are plenty of smaller (and much faster) alternatives which are much better fit for smaller projects. Larger projects usually roll out their own jit-pipeline because they can integrate better with the source language/interpreter and apply tricks LLVM is not well suited to (say, LLVM is not great at deoptimisation). I think only Julia is really a heavy user of LLVM JIT, also it is known for extremely slow repl from time to time.
mort96 10 hours ago [-]
To back up the "surprisingly efficient way to get a speed-up" thing: I once wrote a toy compiler, without an optimizer and without even a register allocator (so all variables lived in stack memory). In my test benchmarks it was roughly 4x slower than Clang at -O3, IIRC.
That's not exactly blazing fast for a low level C-like language, but it's not bad. It's infinitely faster than what I've ever gotten a toy interpreter to be.
jagged-chisel 4 hours ago [-]
Sounds like a "no true Scotsman" statement. How are you defining "real"?
Some human, somewhere, has to describe how to turn high-level language constructs into machine code. "When you see this pattern, emit this sequence of bytes." That's just templates and stencils. There's no magic for turning source code into machine code by divining the ISA at compile time.
Anything that's taking source code and, at the time of execution, is compiling it to machine code on-the-fly is JIT compilation. Regardless how long it takes, lack of optimization, or which machine is the target (x86, ARM32/64, RISC-V, JVM, WebAssembly), it's JIT.
mort96 11 hours ago [-]
A non-optimizing compiler is a real compiler.
bastawhiz 8 hours ago [-]
By choosing LLVM you're also taking a serious latency hit, and potentially burning a ton of CPU cycles on optimizations that will never apply. JSC, for instance, implemented LLVM for FTLJIT (which is where many of the JS bits jangling around in LLVM originated from), but it was only useful for code that was highly likely to benefit from the optimizations because of the high cost of compilation. The webkit folks have since ripped out LLVM and replaced it with their own specialized JIT, which is essentially just what's demonstrated here (with some optimization passes).
IshKebab 11 hours ago [-]
This absolutely is real JIT compilation. Copy and patch is a very well known JIT compilation technique.
RetroTechie 8 hours ago [-]
> By not using LLVM, you're missing all the optimizations it does
And yet, a good portion of software that runs today's world is written in scripting languages & executed using interpreters.
Which is okay! Imho: multiply [# of users] with [how often each user sees that software's effect] and [how much that contributes to the overall user experience], then you get a ballpark idea of how much $$/effort is worth spending on optimization.
In other words: for a one-off, don't bother. But as usercount, frequency of use by individual users, poor UX or RAM/CPU consumption goes up, progress from script -> compiled -> optimizing compiler -> (if necessary) hand-optimized assembly as needed. And of course consider high-level design, data structures, algorithms etc in that process. A change there might be more effective than a switch from interpreted -> optimizing compiler.
"Developer time" should not factor into that much (again: imho) unless users=developers.
Thoughtlessly putting every change through a (slow?) pipeline that does 'random' toolbox-of-optimizations without need, is wasteful. Apply that toolbox as needed while keeping the above in mind.
malisper 8 hours ago [-]
Author here. Let me know if you have any questions about the post or about pgrust.
glenjamin 15 hours ago [-]
pgrust sounds very interesting, but with the deep changes there’s no viable path to upstream it - is the end goal to be robust enough that it’ll get wide adoption?
FiberBundle 13 hours ago [-]
Is it really interesting though? It's essentially just vibe-coded by people who are unqualified for this kind of work. One of the authors claimed that what qualified them was having worked on a large-scale postgres cluster; they never actually worked on databases or compilers.
genxy 9 hours ago [-]
They are going to lose their coding driver's license.
hamilyon2 13 hours ago [-]
It uses copy-and-patch compilation to archive that
hnc3yfnu6f 8 hours ago [-]
Didn't know that
varjag 13 hours ago [-]
There’s been a meme circulating about how AI doesn’t help because “code was never the hard part.” I think that’s true in some domains, but in others, writing the code absolutely was the hard part. JIT compilers are a great example of that.
asdfsa32 13 hours ago [-]
Anyone who thinks AI is good with writing code that is hard to write for the operator, not due to lack of basic software engineering know how but complexity of the domain, either has access to models beyond what is available to the public or is completely lost.
I believe this because every time I use AI for domains that I consider myself above competent, if it is anything beyond UI components or a simple CRUD endpoints, I cringe at the quality of what it generates.
This has made me to be extremely cautious of starting working in a new domain with AI if I want anything beyond throw away quick hacks or junk, shy of quick bug fixes perhaps.
vouwfietsman 11 hours ago [-]
> I cringe at the quality of what it generates.
Besides all the other mentioned points, I think a good remaining less-discussed point is that quality in software has always been in the eye of the beholder. You may very well see the AI output as low quality, I may not, and its not necessarily clear who is right or wrong, because there was always little precedent in objectively evaluating code quality.
This is a long standing issue, and was never resolved before AI happened, and the coming of AI has not really changed things, except that AI is under a magnifying glass obviously. How do we objectively measure the quality of code? There's some general consensus on things, but surprisingly little is true professional agreed upon consensus.
If I can make up a figure, I would guess 95% of software engineering quality rhetoric, and craftmanship advice, is just strongly held opinions.
This is not something I can prove, but if I look at the (still ongoing.....) debates on very basic ideas like clean code, and the reactions from also-great programmers like Carmack, Blow & Muratori, it is clear to me that there is little consensus on even the fundamentals of software design.
If all these people can produce excellent working software while disagreeing on these fundamentals (of quality), it means we do not yet understand what the fundamentals are.
foolswisdom 9 hours ago [-]
You're talking about different measures / types of quality.
> Anyone who thinks AI is good with writing code that is hard to write for the operator, not due to lack of basic software engineering know how but complexity of the domain, either has access to models beyond what is available to the public or is completely lost.
vouwfietsman 9 hours ago [-]
I see what you mean.
You could read it as quality in the operational correctness sense, but just as well in the software architectural design sense. My comment indeed applies to only one of those.
However, why judge correctness as a "cringe on quality", rather than just objectively saying its producing errors. This is why my response is in the software direction.
He’s the obviously and exactly saying code quality is not objective fact. And replying with a fallacy link is just embarrassing.
rfgplk 8 hours ago [-]
> If I can make up a figure, I would guess 95% of software engineering quality rhetoric, and craftmanship advice, is just strongly held opinions.
Absolutely, at the end of the day the only metrics that matter are performance and code validity. A lot of the "this code is awful" arguments I hear just boil down to "this code is stylistically awful" and never talk about it's performance.
mgaunard 12 hours ago [-]
Fable (and even Opus if kept tightly under reigns) does generate high-quality code even for highly complex tasks.
It generally performs better if the tasks are broken done into small manageable pieces, and the person is actually reviewing and calling out problems, which usually requires the person to be a competent engineer in the problem domain to begin with.
But yes, I have personally used it to build what the OP calls a JIT. I would usually write that by hand and it would take me one week. The AI does it in an hour.
asdfsa32 11 hours ago [-]
You can't make assertions about "quality" of code that was generated under an hour while it would have taken a human 40 hours, unless you put substantial amount of work into reviewing it.
I used Fable on a Zephyr project with time sensitive code for LR-WPAN and it broke everything. Literally made the code worst to the point that the devices stopped connecting.
Mawr 11 hours ago [-]
Your second paragraph invalidates your first.
If I need to be a domain expert anyway, the value of the tool goes down by orders of magnitude. Same if I need to first break the task down into pieces and keep reviewing all the output. That sounds to me like >80% of the work I'd need to do anyway.
If I need to design and understand all of the code anyway, I might as well skip the whole process of repeatedly fixing the subpar-at-every-level LLM output and write it all myself.
Personally, I've found the greatest value in asking for simple tasks, like wiring up APIs, generating boilerplate, bug finding etc. Anything that requires effort to do but results in either very little or very simple output, so that I can easily verify its correctness.
But give the LLM anything remotely complex to generate and it cakes its pants.
rfgplk 8 hours ago [-]
> If I need to be a domain expert anyway, the value of the tool goes down by orders of magnitude. Same if I need to first break the task down into pieces and keep reviewing all the output. That sounds to me like >80% of the work I'd need to do anyway.
You absolutely don't. You only need to be roughly aware of what the code needs to be doing. Similar to how a software architect historically didn't personally oversee every line of code in an org, only it's overall structure. The implementation specific details can be left to the AI.
cseleborg 10 hours ago [-]
That is my experience as well. I get a (subjective) speedup between 1 and 3 for parts of the code I'd consider critical and where I check the output tightly, and 5-20 for menial work OR for important code that's well isolated into its own module such that its quality doesn't matter because I can have it rewritten easily if it doesn't work as expected.
mgaunard 2 hours ago [-]
You missed the part where I spelled out the advantage: it accelerates things significantly (one hour vs one week).
ultrahax 6 hours ago [-]
I find most models generate what I would consider not ideal C++, especially in a vacuum. However, I’ve found recently it’s very good at porting existing code, as it’s largely just moving code around that I’d written previously. I had it take a Linux build system and port I had implemented on a more recent project and apply it to a much older release of that project; it executed it more or less flawlessly. It’s also pretty good at debugging, as it can spam the debug loop _much_ faster than a human can.
bastawhiz 8 hours ago [-]
> anything beyond UI components
In fairness, UI components are probably one of the hardest things to do completely correctly, even with just HTML. As soon as you start thinking about i18n, screen reader support, color contrast, keyboard controls, and all of the layout and positioning you're trying to achieve at different viewport sizes, it's extremely hard for a "just competent" engineer to do an S-tier job. Even with the most vanilla default built in components it's not easy to get this correct, and I think we all cringe at what competent engineers create by hand in this domain.
grebc 12 hours ago [-]
Three quarters of my CS class at university could barely code and/or understand code.
That’s not a joke. A lot went on to be programmers professionally. And judging by the quality of closed & open source code I witness daily those figures from university accurately depict people’s capabilities.
Now that said, if you can’t really code then using AI will be a godsend to said individuals.
mgaunard 12 hours ago [-]
I'd say that is the problem we're observing. A lot of decent code is being written with weird inconsistencies, because it's actually written by AI driven by people who don't really understand what they're doing.
the tell-tale mark of AI code is highly over-engineered local solutions to trivial problems that don't matter, or that were already solved better elsewhere and that no sane human would ever duplicate.
asdfsa32 12 hours ago [-]
Fair, but I fear that now even more people who can't code will code, and code that is not any better than what people who could barely code write. Growing cabbages starting to look more and more interesting.
varjag 6 hours ago [-]
Mixtral (a small local model) was churning out better code in fall 2023 that what an incompetent programmer would produce. Am sure others models could as well. It had severe limitations with tiny context and blind spots but still you'd never see it doing the kind of terrors a hack of a developer would do.
rfgplk 8 hours ago [-]
> I believe this because every time I use AI for domains that I consider myself above competent, if it is anything beyond UI components or a simple CRUD endpoints, I cringe at the quality of what it generates.
In a long run session Fable 5 generated a Disney principled (physically based) shading/lighting engine from scratch, both with a CPU (SIMD accelerated) backend _and_ a full GPU Vulkan backend. Exceptional performance too; the CPU backend runs almost realtime and literally looks better than some AAA games outright. Took it about ~8 hours wall time total time to achieve this.
IshKebab 11 hours ago [-]
When did you last try? They've improved a lot.
Also often the difficulty with writing code is simply knowing where to start - getting past the blank page. AI can help a lot with that. Often there's a task where I've got kind of writers block, but you can ask AI to do it and suddenly it's like "ah yeah, sort of but actually that's not quite right we should do it this way".
ligarota 11 hours ago [-]
Tcc be like
Gamer_S4lyer 11 hours ago [-]
Nice
promptspheree 8 hours ago [-]
[flagged]
paidx 8 hours ago [-]
[dead]
uygar 10 hours ago [-]
[dead]
roschdal 15 hours ago [-]
JIT compilation is unsecure.
stevefan1999 12 hours ago [-]
So what, are you willing to go away from von-neumann architecture where instructions are data and data are instructions, i.e. the instruction-data hominocity that underpins JIT compilation? Are you willing to go to a pseudo-Harvard architecture where the ability of JIT compiling is soft locked by other means like VM or strong code authentication or policy protection, which is what Apple is doing.
Fun fact: even Apple themselves have JIT. JavaScriptCore on iOS has JIT, it's just that the App Store policies forbid any application submissions with JIT or trying to mmap/mprotect an executable region. There used to be apps on TrollStore that runs JIT
asdfsa32 14 hours ago [-]
You're entirely correct because JIT requires violating Write xor Execute security policy. This is the reason on iOS, it is limited to Apple shipped software.
> - Android Runtime Just-In-Time (JIT) compilation/profiling is fully disabled and replaced with full ahead-of-time (AOT) compilation. The only JIT compilation in the base OS is the V8 JavaScript JIT which is disabled by default for the Vanadium browser with per-site exception support.
> - Dynamic code loading for both native code or Java/Kotlin classes is blocked for nearly the entire base OS. […]
> - Dynamic code loading for both native code or Java/Kotlin classes can be disabled for user installed apps via 3 exploit protection toggles: […]
As someone who has written a jit compiler, I am puzzled by the claim that jitting requires write/execute permissions. When I have written a jit, I loaded some memory with read/write permissions using mmap. Once I filled in the generated code, I mprotected the region to read/execute before executing.
The drawback to this approach is there can be some bloat because you can only mprotect at page granulariy so a jitted function that only takes say 10 bytes to represent would take up a full page in memory, but this is extreme and in practice, the overhead is unlikely to be worth worrying about.
shakna 14 hours ago [-]
The wiki page mentions this is only a minor problem. Because everyone just writes, then switches and executes.
asdfsa32 13 hours ago [-]
But this means that you have to decide who is allowed to switch.
shakna 2 hours ago [-]
VirtualProtect/mprotect aren't privileged calls on any modern OS.
It's how you do JIT on macOS, where W^X is enforced, for example.
kllrnohj 9 hours ago [-]
No, it isn't. JITs don't grant capabilities abilities an equivalent interpreter doesn't already have.
Allowing code execution allows code execution, that's it, that's the entirety of it.
MaxBarraclough 7 hours ago [-]
There's more to it than that though. Defects in JIT logic can lead to nasty low-level bugs. Plain old interpreters, especially if written in a safe language, are unlikely to have similar issues. This is important when the input code is untrusted. JIT bugs are a major source of browser vulnerabilities.
kllrnohj 7 hours ago [-]
JITs are an attack surface in that process. They are still restricted to things that process was already allowed to do, no matter how badly implemented the JIT is.
In this example usage, one would hope that authentication already happened before the JIT processed the command. So an authorized user can attack themselves is the only realistic risk, which is hardly significant
MaxBarraclough 6 hours ago [-]
> JITs are an attack surface in that process
There's plenty of scope for harm just within the process, even ignoring the possibility of escaping the process. In the case of a database server, essentially everything of value takes place within the database process (or processes). That process presumably has both access to the raw database data, and network access. We wouldn't want it sending data to an attacker's server.
> one would hope that authentication already happened before the JIT processed the command
We'd hope, yes, but SQL injection issues are still somewhat common. Also, an organisation might trust their DBMS to enforce permissions, and a JIT bug is the kind of thing that might allow non-permissioned data access. A DBMS should be hardened against malicious queries, just as a browser should be hardened against malicious JavaScript.
In web browsers, the numbers show JIT compilers are a major cause of security issues. I don't know if there are hard numbers on JIT engines causing security issues in DBMSs though.
kllrnohj 5 hours ago [-]
> There's plenty of scope for harm just within the process,
Of course, but the process has every right to decide for itself if it wants to take that risk. Just like it decides if it wants to take the risk of a memory unsafe language, forgoing fuzzing, or going all out with formal verification.
Ohentis 8 hours ago [-]
[dead]
sebzim4500 14 hours ago [-]
Maybe, but surely there are users who are willing to trust all users of their db instance.
asdfsa32 14 hours ago [-]
The issue is that it restricts from locking-down and securing the system with Write xor Execute memory. So it has system wide implication.
W^X is typically per mapping, not per memory page and does not interfere with JIT compilation.
asdfsa32 13 hours ago [-]
Sure, but it still means that the OS has to decide who is allowed to do it and to what extent. Sophisticated worms like Stuxnet would be much harder with strict W^X for example, since CVE-2010-2568 and the like would be much harder to execute.
orf 13 hours ago [-]
> Sure, but it still means that the OS has to decide who is allowed to do it and to what extent
It has to do that anyway?
asdfsa32 12 hours ago [-]
Only if it wants to allow Writable Memory to become Executable, or basically, allow JIT.
pjmlp 13 hours ago [-]
Signed binaries with the proper assigned OS capabilities.
asdfsa32 12 hours ago [-]
Yes, but with JIT, you can't really verify what the application does upfront. That is the entire point.
pjmlp 12 hours ago [-]
Capabilities are a way to control that, and the point being that only responsible proven applications get the certificate, hence how it all goes on iOS.
asdfsa32 11 hours ago [-]
You're making the assumption that "responsible" is something provable, but that is not the case, it is specially not easy to prove software is secure from tampering its behaviour.
pjmlp 10 hours ago [-]
For that there is bytecode verification as intermediate step, and if you want to go crazy with security, hardware memory tagging with capabilities.
Which at this point most companies would rather save money and forbid JIT altogether.
Note that mainframes and micros have JIT environments that aren't at the same safety level as regular desktop PCs.
Nonsense, there's no "system wide implications". Mappings are per process, and W^X is just a strategy to help harden individual processes, not the entire system. There's no herd immunity here.
JITs do not grant the ability to bypass any OS/system sandboxes. The lack of W^X doesn't do that, either. If a process opts out of W^X, such as to enable a JIT, it's voluntarily making itself less hardened, but at the end of the day this isn't any more meaningful than the program being allowed to be written in, say, C, which also voluntarily reduces the processes security hardening.
asdf88990 7 hours ago [-]
You don’t understand OP’s point because you’re assuming vulnerabilities don’t exist. That is utter nonsense.
kllrnohj 7 hours ago [-]
No, you're not understanding mine. Nobody builds an OS/system expecting that every executable is perfectly well behaved with zero bugs and zero ill intent. Applications are allowed to run code. JITs just run code in that same process. They are already limited to what the process was already allowed to do in the first place.
And my point about C is literally that even without a JIT, applications can still have arbitrary execution vulnerabilities.
A JIT intended to run untrusted code as part of a sandbox, like a browser, is a big risk. But that's because of the untrusted code part, not the JIT. By comparison, something like a Python or Java JIT is as near as makes no difference completely risk free. The JIT is working on exclusively "trusted" code. Same basic concept applies here with this database usage.
dennis16384 14 hours ago [-]
It is the core of ClickHouse for example, for many years. Is it secure enough in your opinion?
JackSlateur 13 hours ago [-]
In rust, is jit equivalent to an "unsafe" block ?
brabel 11 hours ago [-]
Read the code in the post. Everything is written in unsafe Rust. The assembly itself knows no memory safety at all and is completely up to the programmer skill whether it can be trusted to not mess up.
Ohentis 8 hours ago [-]
[dead]
pjmlp 13 hours ago [-]
Machine code is insecure, we should all run interpreted code in a formally verified interpreter.
Alternatively, only allow for the execution of cryptographly signed static linked binaries, this naturally includes the interpreter above.
genxy 8 hours ago [-]
If we are sprinkling formal verification on things, we can sprinkle it on a JIT.
bastawhiz 8 hours ago [-]
But then we'd have to admit that it's not the JIT that's a problem, it's the lack of guardrails and analysis features in the machine code interfaces that higher level languages expose!
pjmlp 4 hours ago [-]
Only if it goes through verified bytecode, and the set of instructions is provable.
The JIT must also only be allowed to call into specific code, controlled by the runtime, and nothing else.
> The rarity of JIT compilers makes me believe that implementing a JIT compiler historically was too difficult for it to be worthwhile.
That's only true of writing a JIT from scratch. There's no rarity of JITs, it's just that LLVM (and other frameworks) are often used. Every major interpreter has a JIT compiler. PCRE2 has a JIT compiler. There are JIT frameworks out there with much faster code-generation than LLVM: Cranelift, GNU Lightning, Mir. I doubt they could do code-generation faster than a custom copy-and-patch JIT, but they'd be much faster than LLVM.
[0] https://www.pinaraf.info/2024/03/look-ma-i-wrote-a-new-jit-c... , discussed: https://news.ycombinator.com/item?id=39742916
[1] https://www.postgresql.org/docs/current/jit-reason.html
Template-based code generators suffer from bad code quality due to missing register allocation.
Our TPDE-based compilers compile a bit slower than template-based code generation but the generated code is much smaller and faster ([2] Fig. 2). Also for database workloads ([2] Fig. 6).
All that said, Postgres' main limitation is that it (IIRC) only compiles single expressions from operators, not pipelines. This fundamentally limits the achievable performance improvement compared to databases that perform more extensive query compilation.
[1]: https://aengelke.net/pubs/2403-cgo.pdf [2]: https://aengelke.net/pubs/2602-cgo1.pdf
PS: sorry for the promotion of my own research here, just couldn't resist.
> Not sure where the idea comes from that Cranelift is much faster than LLVM -O0
Cranelift describes itself as a fast, secure, relatively simple and innovative compiler backend. [0] Interesting that LLVM can compete there, with its optimisations dialed down.
> Postgres' main limitation is that it (IIRC) only compiles single expressions from operators, not pipelines. This fundamentally limits the achievable performance improvement compared to databases that perform more extensive query compilation.
That sounds pretty limiting. That's separate from query optimisation though, right? The query optimiser is presumably able to reason 'broadly' and not just at the level of individual expressions? High-level query-plan optimisation must be much more consequential than effective use of JIT compilation.
[0] https://cranelift.dev/
Yes, yes, and yes. For databases, query optimization (esp. join ordering for larger queries, which heavily depends on estimates) is fundamental. Query optimization happens at the level of the query plan, JIT compilation is only relevant afterwards. A bad query plan leads to asymptotically worse performance (e.g., bad join ordering with huge intermediate results).
On query plan execution: The "classical" model as used in e.g. Postgres is a pull-based iterator model, where operators implement a next() method yielding the next tuple and in there recursively call next() on their child operators (e.g., a next() of a select operator calls next() on its child operator, then applies the predicate [what Postgres JIT-compiles], and returns the tuple if the predicate was true). This can happen one tuple at a time (Postgres) or "vectorized" where multiple tuples are processed at once (e.g. DuckDB). A query-compiling database will split the tree into pipelines and compile each pipeline as one function (e.g., a pipeline will iterate over all the tuples from a source (e.g. tablescan) and a select operator then becomes an if statement inside that loop). This results in pretty tight loops, avoids per-tuple dispatch overhead, and enables more optimizations inside the JIT-ted code (e.g., tuple values don't need to be reloaded from memory all the time). (I find the original paper on query compilation [1] to be well readable.)
[1]: https://www.vldb.org/pvldb/vol4/p539-neumann.pdf
Except that using LLVM has high latency limitting it's applicability. Postgres just disabled LLVM by default because of this[0].
[0] https://www.postgresql.org/message-id/E1w8GWU-002bSL-31%40ge...
There's a strong 'diminishing returns' effect in striking a balance between compile time and the performance of the generated code. I'd expect a more lightweight (less optimising) JIT engine to be able to produce code with pretty respectable performance while taking only a fraction of the time that LLVM takes. There's a follow-up to the blog post I linked above, which bears this out. [0] (I don't know if that JIT solution is production-ready or viable for merging into postgres, mind.)
The blog post [0] gives this performance comparison:
> So, on our stupid benchmark, doing 10 times a simple SELECT * FROM demo WHERE a = 42 on a 10 million rows table...
[0] https://www.pinaraf.info/2025/12/jit-episode-iii-warp-speed-...It was the limits of 8 bit home computers hardware that made the interpreter version be more widely known.
Same to Lisp, Smalltalk, and many other languages.
Fully agree with you.
On reflection I wonder if I overstated the widespread use of JIT and of JIT compiler frameworks. All the 'major' well-resourced high-profile JIT-based interpreters I can think of don't use an off-the-shelf JIT framework for their backend, which makes sense as they want to carefully tune the code-generation. OpenJDK, OpenJ9, .Net, V8, SpiderMonkey, JavaScriptCore. LuaJIT and Python's new JIT don't use one either, nor does the Linux kernel's BPF engine.
The Guile Scheme interpreter uses a fork of the GNU Lightning JIT library. [0] Julia and (as mentioned) Postgres use LLVM for their JITs. I'm trying to think of other projects that use a JIT framework/library.
Similarly, I can't think of many problem domains where it makes sense to use JIT. The ones that spring to mind are interpreters (of course), regex engines, and DBMSs. JIT can also help in high-performance computing, to tailor the code to the particular problem and the particular CPU. [1] I don't think there are many other contexts where it makes sense to use JIT though.
JIT compilation brings its own drawbacks in portability (both between hardware platforms and operating systems), complexity, and perhaps cybersecurity, which might also limit its adoption, even if a good JIT framework could help with all three.
[0] https://doc.guix.gnu.org/guile/latest/en/html_node/Just_002d...
[1] https://www.intel.com/content/www/us/en/developer/articles/t...
See https://github.com/marcoheisig/Petalisp#why-is-petalisp-writ...
It is very relevant
I might use this approach to generate the stencils for a JIT firewall I’ve been experimenting with.
It also occurs to me that this could be used to generate eBPF byte code on the fly as well
Besides run time, JIT is available also when the code is compiled or loaded for execution (i.e., do you have a compilation or loading speed-up in mind? no problem, you can also compile that speed-up into native machine code, and so ad infinitum...).
Also, on my machine, compiling the identity lambda form takes about 200 usec with (optimize (compilation-speed 3) (debug 0)). SBCL could use a faster JIT mode, perhaps at compilation-speed 3/speed 0. Perhaps there are some other internal special variables that could be tweaked to reduce compile time.
Mine doesn't have the SB-INTERPRETER package, so I doubt binding the variable has an effect.
One of the competing open Common Lisp implementations, CCL, has a much faster compiler (albeit one that produces worse code). This can be useful in development.
[0] https://www.sbcl.org/manual/#compiler-only-implementation
By not using LLVM, you're missing all the optimizations it does.
LLVM is a large dependency if you need to JIT. There are plenty of smaller (and much faster) alternatives which are much better fit for smaller projects. Larger projects usually roll out their own jit-pipeline because they can integrate better with the source language/interpreter and apply tricks LLVM is not well suited to (say, LLVM is not great at deoptimisation). I think only Julia is really a heavy user of LLVM JIT, also it is known for extremely slow repl from time to time.
That's not exactly blazing fast for a low level C-like language, but it's not bad. It's infinitely faster than what I've ever gotten a toy interpreter to be.
Some human, somewhere, has to describe how to turn high-level language constructs into machine code. "When you see this pattern, emit this sequence of bytes." That's just templates and stencils. There's no magic for turning source code into machine code by divining the ISA at compile time.
Anything that's taking source code and, at the time of execution, is compiling it to machine code on-the-fly is JIT compilation. Regardless how long it takes, lack of optimization, or which machine is the target (x86, ARM32/64, RISC-V, JVM, WebAssembly), it's JIT.
And yet, a good portion of software that runs today's world is written in scripting languages & executed using interpreters.
Which is okay! Imho: multiply [# of users] with [how often each user sees that software's effect] and [how much that contributes to the overall user experience], then you get a ballpark idea of how much $$/effort is worth spending on optimization.
In other words: for a one-off, don't bother. But as usercount, frequency of use by individual users, poor UX or RAM/CPU consumption goes up, progress from script -> compiled -> optimizing compiler -> (if necessary) hand-optimized assembly as needed. And of course consider high-level design, data structures, algorithms etc in that process. A change there might be more effective than a switch from interpreted -> optimizing compiler.
"Developer time" should not factor into that much (again: imho) unless users=developers.
Thoughtlessly putting every change through a (slow?) pipeline that does 'random' toolbox-of-optimizations without need, is wasteful. Apply that toolbox as needed while keeping the above in mind.
I believe this because every time I use AI for domains that I consider myself above competent, if it is anything beyond UI components or a simple CRUD endpoints, I cringe at the quality of what it generates.
This has made me to be extremely cautious of starting working in a new domain with AI if I want anything beyond throw away quick hacks or junk, shy of quick bug fixes perhaps.
Besides all the other mentioned points, I think a good remaining less-discussed point is that quality in software has always been in the eye of the beholder. You may very well see the AI output as low quality, I may not, and its not necessarily clear who is right or wrong, because there was always little precedent in objectively evaluating code quality.
This is a long standing issue, and was never resolved before AI happened, and the coming of AI has not really changed things, except that AI is under a magnifying glass obviously. How do we objectively measure the quality of code? There's some general consensus on things, but surprisingly little is true professional agreed upon consensus.
If I can make up a figure, I would guess 95% of software engineering quality rhetoric, and craftmanship advice, is just strongly held opinions.
This is not something I can prove, but if I look at the (still ongoing.....) debates on very basic ideas like clean code, and the reactions from also-great programmers like Carmack, Blow & Muratori, it is clear to me that there is little consensus on even the fundamentals of software design.
If all these people can produce excellent working software while disagreeing on these fundamentals (of quality), it means we do not yet understand what the fundamentals are.
> Anyone who thinks AI is good with writing code that is hard to write for the operator, not due to lack of basic software engineering know how but complexity of the domain, either has access to models beyond what is available to the public or is completely lost.
You could read it as quality in the operational correctness sense, but just as well in the software architectural design sense. My comment indeed applies to only one of those.
However, why judge correctness as a "cringe on quality", rather than just objectively saying its producing errors. This is why my response is in the software direction.
Absolutely, at the end of the day the only metrics that matter are performance and code validity. A lot of the "this code is awful" arguments I hear just boil down to "this code is stylistically awful" and never talk about it's performance.
It generally performs better if the tasks are broken done into small manageable pieces, and the person is actually reviewing and calling out problems, which usually requires the person to be a competent engineer in the problem domain to begin with.
But yes, I have personally used it to build what the OP calls a JIT. I would usually write that by hand and it would take me one week. The AI does it in an hour.
I used Fable on a Zephyr project with time sensitive code for LR-WPAN and it broke everything. Literally made the code worst to the point that the devices stopped connecting.
If I need to be a domain expert anyway, the value of the tool goes down by orders of magnitude. Same if I need to first break the task down into pieces and keep reviewing all the output. That sounds to me like >80% of the work I'd need to do anyway.
If I need to design and understand all of the code anyway, I might as well skip the whole process of repeatedly fixing the subpar-at-every-level LLM output and write it all myself.
Personally, I've found the greatest value in asking for simple tasks, like wiring up APIs, generating boilerplate, bug finding etc. Anything that requires effort to do but results in either very little or very simple output, so that I can easily verify its correctness.
But give the LLM anything remotely complex to generate and it cakes its pants.
You absolutely don't. You only need to be roughly aware of what the code needs to be doing. Similar to how a software architect historically didn't personally oversee every line of code in an org, only it's overall structure. The implementation specific details can be left to the AI.
In fairness, UI components are probably one of the hardest things to do completely correctly, even with just HTML. As soon as you start thinking about i18n, screen reader support, color contrast, keyboard controls, and all of the layout and positioning you're trying to achieve at different viewport sizes, it's extremely hard for a "just competent" engineer to do an S-tier job. Even with the most vanilla default built in components it's not easy to get this correct, and I think we all cringe at what competent engineers create by hand in this domain.
That’s not a joke. A lot went on to be programmers professionally. And judging by the quality of closed & open source code I witness daily those figures from university accurately depict people’s capabilities.
Now that said, if you can’t really code then using AI will be a godsend to said individuals.
the tell-tale mark of AI code is highly over-engineered local solutions to trivial problems that don't matter, or that were already solved better elsewhere and that no sane human would ever duplicate.
In a long run session Fable 5 generated a Disney principled (physically based) shading/lighting engine from scratch, both with a CPU (SIMD accelerated) backend _and_ a full GPU Vulkan backend. Exceptional performance too; the CPU backend runs almost realtime and literally looks better than some AAA games outright. Took it about ~8 hours wall time total time to achieve this.
Also often the difficulty with writing code is simply knowing where to start - getting past the blank page. AI can help a lot with that. Often there's a task where I've got kind of writers block, but you can ask AI to do it and suddenly it's like "ah yeah, sort of but actually that's not quite right we should do it this way".
Fun fact: even Apple themselves have JIT. JavaScriptCore on iOS has JIT, it's just that the App Store policies forbid any application submissions with JIT or trying to mmap/mprotect an executable region. There used to be apps on TrollStore that runs JIT
https://en.wikipedia.org/wiki/W%5EX
> - Android Runtime Just-In-Time (JIT) compilation/profiling is fully disabled and replaced with full ahead-of-time (AOT) compilation. The only JIT compilation in the base OS is the V8 JavaScript JIT which is disabled by default for the Vanadium browser with per-site exception support.
> - Dynamic code loading for both native code or Java/Kotlin classes is blocked for nearly the entire base OS. […]
> - Dynamic code loading for both native code or Java/Kotlin classes can be disabled for user installed apps via 3 exploit protection toggles: […]
https://grapheneos.org/features
The drawback to this approach is there can be some bloat because you can only mprotect at page granulariy so a jitted function that only takes say 10 bytes to represent would take up a full page in memory, but this is extreme and in practice, the overhead is unlikely to be worth worrying about.
It's how you do JIT on macOS, where W^X is enforced, for example.
Allowing code execution allows code execution, that's it, that's the entirety of it.
In this example usage, one would hope that authentication already happened before the JIT processed the command. So an authorized user can attack themselves is the only realistic risk, which is hardly significant
There's plenty of scope for harm just within the process, even ignoring the possibility of escaping the process. In the case of a database server, essentially everything of value takes place within the database process (or processes). That process presumably has both access to the raw database data, and network access. We wouldn't want it sending data to an attacker's server.
> one would hope that authentication already happened before the JIT processed the command
We'd hope, yes, but SQL injection issues are still somewhat common. Also, an organisation might trust their DBMS to enforce permissions, and a JIT bug is the kind of thing that might allow non-permissioned data access. A DBMS should be hardened against malicious queries, just as a browser should be hardened against malicious JavaScript.
In web browsers, the numbers show JIT compilers are a major cause of security issues. I don't know if there are hard numbers on JIT engines causing security issues in DBMSs though.
Of course, but the process has every right to decide for itself if it wants to take that risk. Just like it decides if it wants to take the risk of a memory unsafe language, forgoing fuzzing, or going all out with formal verification.
https://en.wikipedia.org/wiki/W%5EX
It has to do that anyway?
Which at this point most companies would rather save money and forbid JIT altogether.
Note that mainframes and micros have JIT environments that aren't at the same safety level as regular desktop PCs.
For example,
https://medium.com/@dhemanthc/ibm-i-architecture-how-timi-an...
JITs do not grant the ability to bypass any OS/system sandboxes. The lack of W^X doesn't do that, either. If a process opts out of W^X, such as to enable a JIT, it's voluntarily making itself less hardened, but at the end of the day this isn't any more meaningful than the program being allowed to be written in, say, C, which also voluntarily reduces the processes security hardening.
And my point about C is literally that even without a JIT, applications can still have arbitrary execution vulnerabilities.
A JIT intended to run untrusted code as part of a sandbox, like a browser, is a big risk. But that's because of the untrusted code part, not the JIT. By comparison, something like a Python or Java JIT is as near as makes no difference completely risk free. The JIT is working on exclusively "trusted" code. Same basic concept applies here with this database usage.
Alternatively, only allow for the execution of cryptographly signed static linked binaries, this naturally includes the interpreter above.
The JIT must also only be allowed to call into specific code, controlled by the runtime, and nothing else.