4 ms·
There are practical applications for this. Perhaps most importantly for regexp compilation, as it is a fairly common use case [1]. Some other examples I can t
by vardump 6y ago
There are practical applications for this.
Perhaps most importantly for regexp compilation, as it is a fairly common use case [1].
Some other examples I can think of: JIT compiling an eval-statement. Constructing a filter for example for a database scan. CPU based pixel shader. Etc.
A lot of code is JIT compiled, but executed only once. So this was a pretty interesting article with practical performance implications in some scenarios.
[1]: See for example https://www.pcre.org/original/doc/html/pcrejit.html https://www.pcre.org/original/doc/html/pcrejit.html
- Nyan 6y agoThanks for the info. I'm not particularly familiar with common JIT applications, but I suspect that this use-case is actually more niche than may think. The problem is that the example presented requires a memory page with write + execute permissions (at the same time). I suspect many JITs don't do this for security reasons (and to deal with OSes which don't allow it), as it may make it easier for an attacker to gain arbitrary code execution. It's likely that many JITs toggle between write and execute permissions, rather than have both enabled at the same time. Whilst this reduces attack surface, changing permissions on allocated memory requires syscalls, which are quite expensive in terms of performance. The scenario presented in the article avoids the impact of syscalls, to maximize performance, leaving only the impact caused by the processor itself. If a JIT isn't overly concerned with this type of security, using write+execute memory could be a way to avoid syscall overhead. On the other hand, if a JIT does toggle permissions, the syscall overhead is likely much more significant than overheads caused by the processor (although the techniques shown might still help depending on how the JIT engine works).
- vardump 6y agoYeah, the security implications are obvious, R+W+X should not be used with untrusted inputs. Not that I'd recommend this, but alternatively you could also map exact same memory twice, one with R+X and the other with R+W. The attacker would need to figure out the writable address. Unfortunately there are probably a lot of ways to accidentally leak this information to the attacker... There are still plenty of use cases where inputs can be trusted.
- deleted 6y ago[deleted]
- saagarjha 6y agoFor performance I think this scheme is fairly common for W^X JITs.
- cat199 6y ago> many JITs don't do this for security reasons would be nice, but historically it's more been: - all JITs do this - OpenBSD creates W^X - open source JITs and other OS's start to incorporate W^X - things are now either W^X compatible or haven't been ported to a W^X OS yet.
- Nyan 6y agoAh good point, thanks for the info!
- MaxBarraclough 6y agoRegex is a good example of where JIT can be a good idea for performance, but is it really 'single use'? A compiled regex object would presumably obey the lifetime rules of the host language. It might live as long as the host process, and be reused many times. I wasn't very clear but I was really thinking of 'self-modifying' code, in contrast to conventional JIT as with regex (once generated, the binary is presumably never modified).
- vardump 6y ago> Regex is a good example of where JIT can be a good idea for performance, but is it really 'single use'? Ideally not, but take a look at real life code bases... Just like with SQL prepared statements. This kind of waste happens often due to reduced visibility. Abstraction layers are good at hiding details like construction cost. Repeated construction happens accidentally pretty easily.
- chrisseaton 6y agoSelf-modifying code is sometimes used to allow you to update an inline cache for a method dispatch as the program is running. Rather than having to recompile and produce new machine code to update an inline cache.
- MaxBarraclough 6y agoNeat idea. Presumably a dynamic JIT like HotSpot would use this technique. In C you can have a global function pointer, and mutate it, but presumably that still has an additional indirection compared to the self-modifying approach.
- chrisseaton 6y ago> Presumably a dynamic JIT like HotSpot would use this technique. Not in practice in HotSpot no, but it could do and similar VMs do. execute|write is not fashionable these days! Some systems enforce execute^write, so you can't do it.
- pizlonator 6y agoUsually if the code is really single use then the optimal solution is to interpret before JITing. The more severe case of this in modern JITs is inline cache repatching. That’s super frequent.
- Jweb_Guru 6y ago> Usually if the code is really single use then the optimal solution is to interpret before JITing. This is the conventional wisdom, yes, but recently (for very long-running database queries at least) it seems to be less true.
- saagarjha 6y agoIf you know something is long running, then yes it makes sense to compile it early. If you don’t, then interpreting until you have it compiled and then OSRing usually does a good job.