Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b2eb0c9
jit: compile into a shared module owned by JitEngine
youknowone Aug 24, 2026
5d91aa7
jit: add a Strict safety level that rejects diverging operations
youknowone Aug 24, 2026
31e83df
jit: add supports_code as a pre-filter for speculative callers
youknowone Aug 24, 2026
0cd8c40
vm: add an aot feature that compiles functions on their first call
youknowone Aug 24, 2026
dcedf97
jit: reject the compiled self-call under Strict
youknowone Aug 24, 2026
6d5811f
ci: build with aot and run the snippet in both modes
youknowone Aug 24, 2026
595717a
vm: let an interrupt out of a speculative annotation read
youknowone Aug 24, 2026
a425fa1
extra_tests: gate the aot snippet on a RustPython-only symbol
youknowone Aug 24, 2026
8c8ecdd
jit: build the libffi cif once per compiled function
youknowone Aug 24, 2026
b836043
jit: fold the two LoadGlobal rejections into one condition
youknowone Aug 24, 2026
69532d1
jit: call compiled code through a generated entry point
youknowone Aug 24, 2026
5009642
vm: trim the jit call path and its build warnings
youknowone Aug 24, 2026
1c88aa9
vm: compute f_lineno from the instruction pointer
youknowone Aug 25, 2026
11f9879
jit: pin down what a conditionally-defined local compiles to
youknowone Aug 30, 2026
8b40262
jit: pass a deopt buffer through the call boundary
youknowone Aug 30, 2026
eb40ea9
jit: spill locals and the value stack at a deopt site
youknowone Aug 30, 2026
5f3d697
jit: deoptimize instead of trapping or wrapping on int overflow
youknowone Aug 30, 2026
4982e31
jit: correct the bound-mask comment in the deopt buffer layout
youknowone Aug 30, 2026
f0dd84d
jit: floor int division and take the divisor's sign for the remainder
youknowone Aug 30, 2026
e812d68
jit: derive the floor-division remainder from the quotient
youknowone Aug 30, 2026
2b73190
jit: deoptimize where a power or a float division leaves the reals
youknowone Aug 30, 2026
cf22801
jit: close the float power trap and its remaining wrong answers
youknowone Aug 30, 2026
eb80261
jit: revive the 1e100 ** 1e50 crash as a deopt test
youknowone Aug 30, 2026
6fee874
jit: remove compile_fpow's edge cases now unreachable under the expon…
youknowone Aug 30, 2026
ade2a6a
jit: let Strict compile arithmetic now that it deoptimizes
youknowone Aug 30, 2026
d512846
jit: replace compile_fpow's double-double pow with a call to f64::powf
youknowone Aug 30, 2026
d6f289d
jit: give aot.py's third widening case its own compiled function, rai…
youknowone Aug 30, 2026
3f2b1c8
vm: resume the interpreter from a deopt record
youknowone Aug 30, 2026
9f586ea
jit: name the whole observer class for a non-resumable site
youknowone Aug 30, 2026
388ed1c
jit: document slot 0's third status and Permissive's resolution point
youknowone Aug 30, 2026
78862a2
extra_tests: assert compiled arithmetic answers what the interpreter …
youknowone Aug 30, 2026
fa4c09e
extra_tests: cover binary add and subtract in the equivalence table
youknowone Aug 30, 2026
2c7def5
jit: refuse a control-flow merge reached with a non-empty stack
youknowone Aug 30, 2026
9311270
vm: check a deopt record against the frame before resuming from it
youknowone Aug 30, 2026
d4bd40b
vm: interpret a call while a tracer or a monitoring tool is installed
youknowone Aug 30, 2026
10ef3a2
jit: document the safety of the checked invoke path
youknowone Aug 30, 2026
9628fe2
ci: build the aot step against the locked dependency versions
youknowone Aug 30, 2026
ec93de2
jit: make the pre-filter's merge rejection actually fire
youknowone Aug 31, 2026
3f6f728
jit: pin the pre-filter's rejection of a mid-expression merge
youknowone Aug 31, 2026
46802e3
vm: report the automatic compiler from sys._jit
youknowone Aug 31, 2026
15c1739
jit: move a doc comment onto the function it describes
youknowone Aug 31, 2026
713eab5
jit: keep a true-division operand of 1 << 53 compiled
youknowone Aug 31, 2026
9de0d3c
extra_tests: assert scale's value instead of a division it does not do
youknowone Aug 31, 2026
83d4ae1
ci: build the benchmarks with automatic compilation on
youknowone Aug 31, 2026
5a89f78
vm: record the traced line even where a frame carries no trace function
youknowone Aug 31, 2026
2fcd657
vm: attach the frame object to the caller when an escaped frame returns
youknowone Sep 2, 2026
32cf3a0
vm: resolve f_back from the thread still running the frame
youknowone Sep 2, 2026
a40ba85
vm: read a running frame's position from the thread running it
youknowone Sep 2, 2026
39fb1eb
ci: run part of the CPython suite with automatic compilation on
youknowone Sep 2, 2026
30ef72b
vm: set a resumed frame's offset through a method on the frame
youknowone Sep 2, 2026
8a8fa85
jit: poll the eval breaker at every backward jump
youknowone Sep 2, 2026
053f630
vm: decide a float power by its operands' values, not their sign bits
youknowone Sep 2, 2026
6fa0b7f
common: keep the sign of a zero quotient
youknowone Sep 2, 2026
c31469b
docs: describe what the automatic compiler takes and turns down
youknowone Sep 2, 2026
09b3583
vm: end the finalizing span with the shutdown that opened it
youknowone Sep 2, 2026
cb70c1d
ci: build the benchmarks without automatic compilation again
youknowone Sep 2, 2026
831b29a
vm: compile a function once it is warm, on the types its calls pass
youknowone Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cspell.dict/rust-more.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ cstring
datelike
deserializer
deserializers
fcmp
fdiv
flamescope
flate2
Expand All @@ -28,6 +29,8 @@ getres
hasher
hexf
hexversion
iabs
iconst
idents
illumos
ilog
Expand Down Expand Up @@ -79,6 +82,7 @@ thiserror
timelike
timsort
trai
uextend
ulonglong
unic
unistd
Expand Down
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"deduped",
"deoptimized",
"deoptimize",
"deoptimizes",
"emscripten",
"excs",
"fdigits",
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ jobs:
run: cargo build --locked --no-default-features --features ssl-openssl-vendor
if: runner.os == 'Linux'

# Automatic compilation must not change what a program does, so the
# snippet has to pass identically with it on and off.
- name: Test aot build
run: |
cargo build --locked --features aot
target/debug/rustpython -X aot=1 extra_tests/snippets/aot.py
target/debug/rustpython -X aot=0 extra_tests/snippets/aot.py
target/debug/rustpython -X aot=1 extra_tests/snippets/jit.py
if: runner.os == 'Linux'

# The snippets cover arithmetic. These cover the rest of what the
# automatic call path moves through: frames, tracebacks, tracing, and
# the threads that have to be able to leave a compiled loop.
- name: Test aot against the CPython suite
run: |
target/debug/rustpython -X aot=1 -m test \
test_sys test_traceback test_sys_settrace test_monitoring \
test_bdb test_trace test_exceptions test_generators test_threading
if: runner.os == 'Linux'

# - name: Install tk-dev for tkinter build
# run: sudo apt-get update && sudo apt-get install -y tk-dev
# if: runner.os == 'Linux'
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"]
flame-it = ["rustpython-vm/flame-it", "rustpython-stdlib/flame-it", "flame", "flamescope"]
freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
jit = ["rustpython-vm/jit"]
aot = ["rustpython-vm/aot"]
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
sqlite = ["rustpython-stdlib/sqlite"]
ssl = ["host_env"]
Expand Down
59 changes: 54 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,21 +145,70 @@ cargo build --release --target wasm32-wasip1 --features="freeze-stdlib"

### JIT (Just in time) compiler

RustPython has a **very** experimental JIT compiler that compile python functions into native code.
RustPython has a **very** experimental JIT compiler that compiles python functions into native code.
It comes in two forms: an automatic one that compiles a function once it has been called
often enough to be worth it, and an explicit `__jit__()` that compiles the one function
it is called on.

#### Building

By default the JIT compiler isn't enabled, it's enabled with the `jit` cargo feature.
Neither is built by default.

```bash
cargo run --features jit
cargo run --features aot # automatic, and the explicit one with it
cargo run --features jit # explicit `__jit__()` alone
```

This requires autoconf, automake, libtool, and clang to be installed.

#### Using
#### Using the automatic compiler

To compile a function, call `__jit__()` on it.
A build that has it still has to be switched on, with `-X aot=1`, `RUSTPYTHON_AOT=1`
or `PYTHON_JIT=1`; `-X aot=0` and `=0` switch it back off. A function is then counted
as it is called and offered to the compiler once it has been called enough times to
repay one. The attempt happens once, so a function it turns down costs that one
attempt and is interpreted from then on; a function called only a handful of times
is never offered at all.

`sys._jit.is_available()` reports whether the compiler was built in, `is_enabled()`
whether it is switched on, and `_stats()` returns `(compiled, rejected, deoptimized)`
for the functions it has looked at so far — a RustPython extension.

#### What it compiles

Scalar functions, and nothing else. The two forms differ in where the types come
from: `__jit__()` reads them off the annotations, and turns down a function without
them; the automatic path takes them from the arguments of the call that made the
function warm, so an unannotated function compiles as readily as an annotated one.
Nothing about that reads a `__annotations__` or runs an `__annotate__`.

A guess about types is a guess: a later call whose arguments do not fit the compiled
signature is run by the interpreter instead.

Taken: `int`, `float` and `bool` arguments, locals and return values; arithmetic,
comparison and boolean operators; `if`, `while`, and the assignments between them.

Turned down: arguments of any other type, `*args`/`**kwargs`, closures, generators
and coroutines, `try`/`except`, attributes and methods, containers, `for`, calls to
anything but the function itself, and expressions that merge with an operand still
on the stack, such as a conditional expression. A call a function makes to itself
is compiled only by `__jit__()`; the automatic path turns those down too, because
the global it goes through can be rebound between one call and the next.

Where a machine word runs out — an overflow, a division by zero, a shift past the
width, a power with no real answer — the compiled code hands the frame back at the
instruction it could not do, with the values it had, and the interpreter carries on
from there. The native code is dropped at that point, and the function is
interpreted afterwards.

Compiled code runs with no python frame. That is why `sys._jit.is_active()` is
always `False`, why such a call reports no line and no return to `sys.settrace` or
`sys.monitoring`, and why a call is interpreted, and left uncompiled, while either
of those is installed.

#### Using `__jit__()`

To compile a single function, call `__jit__()` on it. This needs only the `jit` feature.

```python
def foo():
Expand Down
4 changes: 2 additions & 2 deletions crates/capi/src/pyframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ pub unsafe extern "C" fn PyFrame_GetCode(frame: *mut PyFrameObject) -> *mut PyCo

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyFrame_GetLineNumber(frame: *mut PyFrameObject) -> c_int {
with_vm(|_vm| {
let lineno = unsafe { &*frame }.f_lineno();
with_vm(|vm| {
let lineno = unsafe { &*frame }.f_lineno(vm);
Ok(lineno.try_into().unwrap_or(c_int::MAX))
})
}
7 changes: 7 additions & 0 deletions crates/common/src/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ use num_traits::{One, ToPrimitive, Zero};

#[must_use]
pub fn true_div(numerator: &BigInt, denominator: &BigInt) -> f64 {
// A rational carries no signed zero, so `0 / -1` would round to `0.0`. A
// quotient of two differently signed operands is negative down to and
// including its zero, and only an exactly zero numerator loses that here:
// a quotient too small to represent still rounds to `-0.0` on its own.
if numerator.is_zero() && denominator.sign() == Sign::Minus {
return -0.0;
}
let rational = Rational::from_integers_ref(numerator.into(), denominator.into());
match rational.rounding_into(RoundingMode::Nearest) {
// returned value is $t::MAX but still less than the original
Expand Down
1 change: 0 additions & 1 deletion crates/jit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ rustpython-compiler-core = { workspace = true }

num-traits = { workspace = true }
thiserror = { workspace = true }
libffi = { workspace = true }

cranelift = { workspace = true }
cranelift-jit = { workspace = true }
Expand Down
Loading