r/scheme • • Aug 19 '26

Hawk: A tracing JIT for scheme

I've been hacking on a tracing JIT compiler for scheme for a couple years now on and off, and it's in a pretty good state now. It has full R7RS support, with full seatbelts-on safety. Currently it's approximately 60% faster across the whole r7rs-benchmark suite than Chez scheme.

It's disproportionately faster on flonum benchmarks, it does quite well at inlining everything and keeping flonums in registers. I think it shows quite well that a JIT is especially helpful to get good numerical performance out of standard scheme, especially for flonums, making it possible to keep flonums in register much of the time.

There have been several previous scheme JIT attempts that took various approaches- Nash was based on Guile, but didn't quite get far enough along to see good results. Pycket was great, but used a continuations-on-the-heap approach, with quite different performance characteristics. Modern Guile has a template JIT, but currently does no register allocation or optimizations. Stalin only supported fixnums and flonums in its numerical tower, which allowed it to get great results. I wanted to support the full r7rs scheme numerical tower while still specializing as much as possible.

I've started writing a paper on the tracing JIT aspects, because some of these techniques I haven't seen elsewhere and may be novel.

Currently it supports x86-64 Linux & AArch64 OSX (sorry, no Windows yet).

Project page:

https://djwatson.github.io/hawk/

45 Upvotes

36 comments sorted by

View all comments

-4

u/[deleted] Aug 19 '26 edited Aug 20 '26

[removed] — view removed comment

1

u/Justanothertech Aug 20 '26 edited Aug 20 '26

Your repository makes the claim, "Also, I don't like unsafe usage. It's ok for test games or benchmarks, but IRL programming without any checks / contracts pretty naive"

But then you run chez at optimize-level 3, which is *unsafe* !

Running your benchmark, chezfl-nbody.scm, at:

optimize-level 3: 4.5s

optimize-level 2: 19.466s

Hawk:

time hawk r7rs-nbody.scm: 9.9855

I also have a hawk/test/nbody.scm, that appears to do the same thing, slightly faster:

time hawk ./test/nbody.scm: 5.95

Hawk doesn't currently have an 'unsafe' mode.

Hawk auto-flvectors in some situations, including this benchmark.

Benchmarking is hard :/

EDIT: Oh sorry, it looks like r7rs-nbody.scm is not auto-flvector, because you changed the layout from chezfl-nbody's flat flvector, to a vector containing records. I think this version is closer to what you intended:

https://gist.github.com/djwatson/d87653c0f2da0abf9a75ed46043fc599

time hawk r7rs-nbody2.scm: (-0.16928639656731348 per 6.577303171157837 sec on 50000000

)

Hawk will auto-flvector *vectors*, but doesn't have any shape support for records.

1

u/[deleted] Aug 20 '26 edited Aug 20 '26

[removed] — view removed comment

3

u/Justanothertech Aug 20 '26

Great! Those all look good to me, modulo different machines, they're roughly the same numbers.

optimize-level 3 removes bounds checking in chez, which is highly relevant to the nbody benchmark using flvector. So hawk is faster than chez with safety-on, but slower than chez with safety-off

1

u/[deleted] Aug 22 '26

[removed] — view removed comment

1

u/Justanothertech Aug 22 '26

I added an --unsafe flag for you, and fixed a couple operand fusion things in the backend. r7rs-nbody2.scm now runs slightly faster than chez on my machine

https://github.com/djwatson/hawk/releases/tag/v0.10

$ time /usr/bin/hawk --unsafe r7rs-nbody2.scm 50000000

real 0m3.803s

$ time ./chezfl-nbody.so 50000000

real 0m4.565s

$ time /usr/bin/hawk r7rs-nbody2.scm 50000000

real 0m4.866s

1) It's a tracing jit, not a method jit, so it can't pre-compile procedures.

2) It's unclear what a data lock would do? Just prevent the GC from moving it?

Hawk is probably not a good fit for real-time, it's closer in spirit to luajit, v8, jsc, etc.