Skip to content
Prev Previous commit
Next Next commit
Fix jit
  • Loading branch information
ShaharNaveh committed Jan 5, 2026
commit fd3ef1dd64d3530ef2d228bd35b3104b38bd99a9
28 changes: 11 additions & 17 deletions crates/jit/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cranelift::prelude::*;
use num_traits::cast::ToPrimitive;
use rustpython_compiler_core::bytecode::{
self, BinaryOperator, BorrowedConstant, CodeObject, ComparisonOperator, Instruction, Label,
OpArg, OpArgState, UnaryOperator,
OpArg, OpArgState,
};
use std::collections::HashMap;

Expand Down Expand Up @@ -620,28 +620,22 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
self.stack.swap(i, j);
Ok(())
}
Instruction::UnaryOperation { op, .. } => {
let op = op.get(arg);
Instruction::UnaryNot => {
let a = self.stack.pop().ok_or(JitCompileError::BadBytecode)?;
match (op, a) {
(UnaryOperator::Minus, JitValue::Int(val)) => {
// Compile minus as 0 - a.
let boolean = self.boolean_val(a)?;
let not_boolean = self.builder.ins().bxor_imm(boolean, 1);
self.stack.push(JitValue::Bool(not_boolean));
Ok(())
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Instruction::UnaryInvert => {
match self.stack.pop().ok_or(JitCompileError::BadBytecode)? {
JitValue::Int(val) => {
// Compile minus as 0 - val.
let zero = self.builder.ins().iconst(types::I64, 0);
let out = self.compile_sub(zero, val);
self.stack.push(JitValue::Int(out));
Ok(())
}
(UnaryOperator::Plus, JitValue::Int(val)) => {
// Nothing to do
self.stack.push(JitValue::Int(val));
Ok(())
}
(UnaryOperator::Not, a) => {
let boolean = self.boolean_val(a)?;
let not_boolean = self.builder.ins().bxor_imm(boolean, 1);
self.stack.push(JitValue::Bool(not_boolean));
Ok(())
}
_ => Err(JitCompileError::NotSupported),
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Incorrect implementation of bitwise invert operator.

The UnaryInvert instruction implements Python's ~ operator (bitwise NOT), which should compute -x - 1 for integers, not -x. The current implementation computes 0 - val, which is negation, producing incorrect results.

Examples of the bug:

  • ~5 should return -6, but this returns -5
  • ~0 should return -1, but this returns 0
  • ~(-3) should return 2, but this returns 3
🔎 Proposed fix for bitwise invert
 Instruction::UnaryInvert => {
     match self.stack.pop().ok_or(JitCompileError::BadBytecode)? {
         JitValue::Int(val) => {
-            // Compile minus as 0 - val.
+            // Bitwise invert: ~x = -x - 1
             let zero = self.builder.ins().iconst(types::I64, 0);
-            let out = self.compile_sub(zero, val);
+            let neg_val = self.compile_sub(zero, val);
+            let one = self.builder.ins().iconst(types::I64, 1);
+            let out = self.compile_sub(neg_val, one);
             self.stack.push(JitValue::Int(out));
             Ok(())
         }
         _ => Err(JitCompileError::NotSupported),
     }
 }

Alternatively, if Cranelift supports bitwise NOT directly (e.g., bnot), that would be more efficient and clearer:

 Instruction::UnaryInvert => {
     match self.stack.pop().ok_or(JitCompileError::BadBytecode)? {
         JitValue::Int(val) => {
-            // Compile minus as 0 - val.
-            let zero = self.builder.ins().iconst(types::I64, 0);
-            let out = self.compile_sub(zero, val);
+            // Bitwise invert using NOT instruction
+            let out = self.builder.ins().bnot(val);
             self.stack.push(JitValue::Int(out));
             Ok(())
         }
         _ => Err(JitCompileError::NotSupported),
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Instruction::UnaryInvert => {
match self.stack.pop().ok_or(JitCompileError::BadBytecode)? {
JitValue::Int(val) => {
// Compile minus as 0 - val.
let zero = self.builder.ins().iconst(types::I64, 0);
let out = self.compile_sub(zero, val);
self.stack.push(JitValue::Int(out));
Ok(())
}
(UnaryOperator::Plus, JitValue::Int(val)) => {
// Nothing to do
self.stack.push(JitValue::Int(val));
Ok(())
}
(UnaryOperator::Not, a) => {
let boolean = self.boolean_val(a)?;
let not_boolean = self.builder.ins().bxor_imm(boolean, 1);
self.stack.push(JitValue::Bool(not_boolean));
Ok(())
}
_ => Err(JitCompileError::NotSupported),
}
}
Instruction::UnaryInvert => {
match self.stack.pop().ok_or(JitCompileError::BadBytecode)? {
JitValue::Int(val) => {
// Bitwise invert: ~x = -x - 1
let zero = self.builder.ins().iconst(types::I64, 0);
let neg_val = self.compile_sub(zero, val);
let one = self.builder.ins().iconst(types::I64, 1);
let out = self.compile_sub(neg_val, one);
self.stack.push(JitValue::Int(out));
Ok(())
}
_ => Err(JitCompileError::NotSupported),
}
}
🤖 Prompt for AI Agents
In @crates/jit/src/instructions.rs around lines 630-641, The UnaryInvert arm
incorrectly implements bitwise NOT by computing 0 - val (negation); change the
logic in Instruction::UnaryInvert handling for JitValue::Int so it produces
bitwise complement: either emit a bnot on the integer value via the builder (if
Cranelift supports a bnot op) or compute -val - 1 by calling compile_sub twice
(first compute neg = compile_sub(zero, val) then compute out = compile_sub(neg,
one)) where one is an iconst types::I64 value of 1; push JitValue::Int(out) and
keep the NotSupported error path unchanged.

Comment on lines +653 to 664
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for fneg and Float handling in unary operations
rg -nP --type=rust -C3 'fneg|UnaryNegative.*Float|Float.*UnaryNegative' crates/jit/src/

Repository: RustPython/RustPython

Length of output: 609


🏁 Script executed:

#!/bin/bash
# Check the full context around UnaryNegative and look for Float handling patterns
rg -nP --type=rust -B5 -A10 'Instruction::UnaryNegative' crates/jit/src/

Repository: RustPython/RustPython

Length of output: 1435


🏁 Script executed:

#!/bin/bash
# Search for UnaryPositive to see if it has similar patterns
rg -nP --type=rust -B2 -A8 'Instruction::UnaryPositive' crates/jit/src/

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look for Float handling in other unary or binary operations for reference
rg -nP --type=rust 'JitValue::Float.*=>' crates/jit/src/ | head -20

Repository: RustPython/RustPython

Length of output: 556


Add Float support to UnaryNegative handler.

The implementation handles Int correctly but should also support JitValue::Float. Since the codebase uses fneg for float negation and Float is handled in other binary operations (e.g., line 529), add a case for JitValue::Float:

JitValue::Float(val) => {
    let out = self.builder.ins().fneg(val);
    self.stack.push(JitValue::Float(out));
    Ok(())
}
🤖 Prompt for AI Agents
In @crates/jit/src/instructions.rs around lines 650-661, The UnaryNegative
handler currently only handles JitValue::Int by compiling 0 - val; add a branch
for JitValue::Float in the match inside Instruction::UnaryNegative to call
self.builder.ins().fneg on the popped float value and push JitValue::Float(out)
onto self.stack, returning Ok(()); keep the existing Int branch (using
compile_sub) and the fallback error branch unchanged.

Expand Down