mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Align specialization guards and caching with CPython (#7341)
* vm: complete specialized opcode dispatch paths * vm: cache LOAD_GLOBAL with dict entry hints * vm: align adaptive specialization counters with CPython backoff * vm: apply cooldown counter on specialization success paths * vm: retain LOAD_GLOBAL specializations on misses * vm: keep attr and call specializations on guard misses * vm: retain store-attr and store-subscr specializations on misses * vm: retain specialization opcodes on generic fallback paths * vm: align jump-backward specialization defaults with CPython * vm: retain exact-args call specializations on misses * vm: retain SEND_GEN specialization on non-coroutine sends * vm: specialize list.append calls like CPython CALL_LIST_APPEND * vm: set cooldown on LOAD_ATTR_CLASS specialization * vm: specialize bound method object CALL paths * vm: specialize CALL_KW for bound method objects * vm: use current-state function version for CALL_KW specialization * vm: align CALL/CALL_KW pyfunction specialization with CPython * vm: drop call-site identity caches in generic CALL specializations * vm: align builtin type call specializations with CPython guards * vm: align builtin CALL guards with CPython self_or_null semantics * vm: require exact list in CALL_LIST_APPEND fast path * vm: align CALL builtin/class specialization flow with CPython * vm: tighten len/isinstance CALL specializations to builtin guards * vm: gate CALL_BUILTIN_CLASS on type vectorcall like CPython * vm: run non-py CALL specializations via direct vectorcall * vm: align class-call specialization branching with CPython * Fix CI: disable ForIterGen, tighten CALL guards - Disable ForIterGen specialization (falls through to generic path) because inline generator frame resumption is needed for correct debugger StopIteration visibility (test_bdb) - Use downcast_ref_if_exact for PyNativeFunction in CALL specialization guards - Add can_specialize_call guard for class __init__ specialization - Remove expectedFailure for test_bad_newobj_args (now passing)
This commit is contained in:
1 parent
5c29074596
commit
9a0511be0b
9 files changed
+1224
-723
No files matched your search
Vendored
-4
@@ -85,10 +85,6 @@ class PyPicklingErrorTests(AbstractPicklingErrorTests, unittest.TestCase):
|
||||
f.seek(0)
|
||||
return bytes(f.read())
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_bad_newobj_args(self):
|
||||
return super().test_bad_newobj_args()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_buffer_callback_error(self):
|
||||
return super().test_buffer_callback_error()
|
||||
|
||||
@@ -349,9 +349,47 @@ pub struct CodeUnit {
|
||||
const _: () = assert!(mem::size_of::<CodeUnit>() == 2);
|
||||
|
||||
/// Adaptive specialization: number of executions before attempting specialization.
|
||||
pub const ADAPTIVE_WARMUP_VALUE: u8 = 50;
|
||||
/// Adaptive specialization: backoff counter after de-optimization.
|
||||
pub const ADAPTIVE_BACKOFF_VALUE: u8 = 250;
|
||||
///
|
||||
/// Matches CPython's `_Py_BackoffCounter` encoding.
|
||||
pub const ADAPTIVE_WARMUP_VALUE: u16 = adaptive_counter_bits(1, 1);
|
||||
/// Adaptive specialization: cooldown counter after a successful specialization.
|
||||
///
|
||||
/// Value/backoff = (52, 0), matching CPython's ADAPTIVE_COOLDOWN bits.
|
||||
pub const ADAPTIVE_COOLDOWN_VALUE: u16 = adaptive_counter_bits(52, 0);
|
||||
/// Initial JUMP_BACKWARD counter bits (value/backoff = 4095/12).
|
||||
pub const JUMP_BACKWARD_INITIAL_VALUE: u16 = adaptive_counter_bits(4095, 12);
|
||||
|
||||
const BACKOFF_BITS: u16 = 4;
|
||||
const MAX_BACKOFF: u16 = 12;
|
||||
const UNREACHABLE_BACKOFF: u16 = 15;
|
||||
|
||||
/// Encode an adaptive counter as `(value << 4) | backoff`.
|
||||
pub const fn adaptive_counter_bits(value: u16, backoff: u16) -> u16 {
|
||||
(value << BACKOFF_BITS) | backoff
|
||||
}
|
||||
|
||||
/// True when the adaptive counter should trigger specialization.
|
||||
#[inline]
|
||||
pub const fn adaptive_counter_triggers(counter: u16) -> bool {
|
||||
counter < UNREACHABLE_BACKOFF
|
||||
}
|
||||
|
||||
/// Decrement adaptive counter by one countdown step.
|
||||
#[inline]
|
||||
pub const fn advance_adaptive_counter(counter: u16) -> u16 {
|
||||
counter.wrapping_sub(1 << BACKOFF_BITS)
|
||||
}
|
||||
|
||||
/// Reset adaptive counter with exponential backoff.
|
||||
#[inline]
|
||||
pub const fn adaptive_counter_backoff(counter: u16) -> u16 {
|
||||
let backoff = counter & ((1 << BACKOFF_BITS) - 1);
|
||||
if backoff < MAX_BACKOFF {
|
||||
adaptive_counter_bits((1 << (backoff + 1)) - 1, backoff + 1)
|
||||
} else {
|
||||
adaptive_counter_bits((1 << MAX_BACKOFF) - 1, MAX_BACKOFF)
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeUnit {
|
||||
pub const fn new(op: Instruction, arg: OpArgByte) -> Self {
|
||||
@@ -370,12 +408,15 @@ impl TryFrom<&[u8]> for CodeUnit {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CodeUnits(UnsafeCell<Box<[CodeUnit]>>);
|
||||
pub struct CodeUnits {
|
||||
units: UnsafeCell<Box<[CodeUnit]>>,
|
||||
adaptive_counters: Box<[AtomicU16]>,
|
||||
}
|
||||
|
||||
// SAFETY: All cache operations use atomic read/write instructions.
|
||||
// - replace_op / compare_exchange_op: AtomicU8 store/CAS (Release)
|
||||
// - cache read/write: AtomicU16 load/store (Relaxed)
|
||||
// - adaptive counter: AtomicU8 load/store (Relaxed)
|
||||
// - adaptive counter: AtomicU16 load/store (Relaxed)
|
||||
// Ordering is established by:
|
||||
// - replace_op (Release) ↔ dispatch loop read_op (Acquire) for cache data visibility
|
||||
// - tp_version_tag (Acquire) for descriptor pointer validity
|
||||
@@ -385,15 +426,23 @@ impl Clone for CodeUnits {
|
||||
fn clone(&self) -> Self {
|
||||
// SAFETY: No concurrent mutation during clone — cloning is only done
|
||||
// during code object construction or marshaling, not while instrumented.
|
||||
let inner = unsafe { &*self.0.get() };
|
||||
Self(UnsafeCell::new(inner.clone()))
|
||||
let units = unsafe { &*self.units.get() }.clone();
|
||||
let adaptive_counters = self
|
||||
.adaptive_counters
|
||||
.iter()
|
||||
.map(|c| AtomicU16::new(c.load(Ordering::Relaxed)))
|
||||
.collect();
|
||||
Self {
|
||||
units: UnsafeCell::new(units),
|
||||
adaptive_counters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CodeUnits {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// SAFETY: Debug formatting doesn't race with replace_op
|
||||
let inner = unsafe { &*self.0.get() };
|
||||
let inner = unsafe { &*self.units.get() };
|
||||
f.debug_tuple("CodeUnits").field(inner).finish()
|
||||
}
|
||||
}
|
||||
@@ -406,29 +455,37 @@ impl TryFrom<&[u8]> for CodeUnits {
|
||||
return Err(Self::Error::InvalidBytecode);
|
||||
}
|
||||
|
||||
let units: Self = value
|
||||
let units = value
|
||||
.chunks_exact(2)
|
||||
.map(CodeUnit::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(units)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(units.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[CodeUnit; N]> for CodeUnits {
|
||||
fn from(value: [CodeUnit; N]) -> Self {
|
||||
Self(UnsafeCell::new(Box::from(value)))
|
||||
Self::from(Vec::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<CodeUnit>> for CodeUnits {
|
||||
fn from(value: Vec<CodeUnit>) -> Self {
|
||||
Self(UnsafeCell::new(value.into_boxed_slice()))
|
||||
let units = value.into_boxed_slice();
|
||||
let adaptive_counters = (0..units.len())
|
||||
.map(|_| AtomicU16::new(0))
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
Self {
|
||||
units: UnsafeCell::new(units),
|
||||
adaptive_counters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<CodeUnit> for CodeUnits {
|
||||
fn from_iter<T: IntoIterator<Item = CodeUnit>>(iter: T) -> Self {
|
||||
Self(UnsafeCell::new(iter.into_iter().collect()))
|
||||
Self::from(iter.into_iter().collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +496,7 @@ impl Deref for CodeUnits {
|
||||
// SAFETY: Shared references to the slice are valid even while replace_op
|
||||
// may update individual opcode bytes — readers tolerate stale opcodes
|
||||
// (they will re-read on the next iteration).
|
||||
unsafe { &*self.0.get() }
|
||||
unsafe { &*self.units.get() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +509,7 @@ impl CodeUnits {
|
||||
/// - `index` must be in bounds.
|
||||
/// - `new_op` must have the same arg semantics as the original opcode.
|
||||
pub unsafe fn replace_op(&self, index: usize, new_op: Instruction) {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let units = unsafe { &*self.units.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU8;
|
||||
unsafe { &*ptr }.store(new_op.into(), Ordering::Release);
|
||||
}
|
||||
@@ -468,7 +525,7 @@ impl CodeUnits {
|
||||
expected: Instruction,
|
||||
new_op: Instruction,
|
||||
) -> bool {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let units = unsafe { &*self.units.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU8;
|
||||
unsafe { &*ptr }
|
||||
.compare_exchange(
|
||||
@@ -483,7 +540,7 @@ impl CodeUnits {
|
||||
/// Atomically read the opcode at `index` with Acquire ordering.
|
||||
/// Pairs with `replace_op` (Release) to ensure cache data visibility.
|
||||
pub fn read_op(&self, index: usize) -> Instruction {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let units = unsafe { &*self.units.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU8;
|
||||
let byte = unsafe { &*ptr }.load(Ordering::Acquire);
|
||||
// SAFETY: Only valid Instruction values are stored via replace_op/compare_exchange_op.
|
||||
@@ -492,7 +549,7 @@ impl CodeUnits {
|
||||
|
||||
/// Atomically read the arg byte at `index` with Relaxed ordering.
|
||||
pub fn read_arg(&self, index: usize) -> OpArgByte {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let units = unsafe { &*self.units.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const u8;
|
||||
let arg_ptr = unsafe { ptr.add(1) } as *const AtomicU8;
|
||||
OpArgByte::from(unsafe { &*arg_ptr }.load(Ordering::Relaxed))
|
||||
@@ -505,7 +562,7 @@ impl CodeUnits {
|
||||
/// # Safety
|
||||
/// - `index` must be in bounds and point to a CACHE entry.
|
||||
pub unsafe fn write_cache_u16(&self, index: usize, value: u16) {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let units = unsafe { &*self.units.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU16;
|
||||
unsafe { &*ptr }.store(value, Ordering::Relaxed);
|
||||
}
|
||||
@@ -516,7 +573,7 @@ impl CodeUnits {
|
||||
/// # Panics
|
||||
/// Panics if `index` is out of bounds.
|
||||
pub fn read_cache_u16(&self, index: usize) -> u16 {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let units = unsafe { &*self.units.get() };
|
||||
assert!(index < units.len(), "read_cache_u16: index out of bounds");
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU16;
|
||||
unsafe { &*ptr }.load(Ordering::Relaxed)
|
||||
@@ -564,25 +621,19 @@ impl CodeUnits {
|
||||
lo | (hi << 32)
|
||||
}
|
||||
|
||||
/// Read the adaptive counter from the CACHE entry's `arg` byte at `index`.
|
||||
/// Read adaptive counter bits for instruction at `index`.
|
||||
/// Uses Relaxed atomic load.
|
||||
pub fn read_adaptive_counter(&self, index: usize) -> u8 {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const u8;
|
||||
let arg_ptr = unsafe { ptr.add(1) } as *const AtomicU8;
|
||||
unsafe { &*arg_ptr }.load(Ordering::Relaxed)
|
||||
pub fn read_adaptive_counter(&self, index: usize) -> u16 {
|
||||
self.adaptive_counters[index].load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Write the adaptive counter to the CACHE entry's `arg` byte at `index`.
|
||||
/// Write adaptive counter bits for instruction at `index`.
|
||||
/// Uses Relaxed atomic store.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index` must be in bounds and point to a CACHE entry.
|
||||
pub unsafe fn write_adaptive_counter(&self, index: usize, value: u8) {
|
||||
let units = unsafe { &*self.0.get() };
|
||||
let ptr = units.as_ptr().wrapping_add(index) as *const u8;
|
||||
let arg_ptr = unsafe { ptr.add(1) } as *const AtomicU8;
|
||||
unsafe { &*arg_ptr }.store(value, Ordering::Relaxed);
|
||||
/// - `index` must be in bounds.
|
||||
pub unsafe fn write_adaptive_counter(&self, index: usize, value: u16) {
|
||||
self.adaptive_counters[index].store(value, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Produce a clean copy of the bytecode suitable for serialization
|
||||
@@ -611,7 +662,7 @@ impl CodeUnits {
|
||||
|
||||
/// Initialize adaptive warmup counters for all cacheable instructions.
|
||||
/// Called lazily at RESUME (first execution of a code object).
|
||||
/// Uses the `arg` byte of the first CACHE entry, preserving `op = Instruction::Cache`.
|
||||
/// Counters are stored out-of-line to preserve `op = Instruction::Cache`.
|
||||
/// All writes are atomic (Relaxed) to avoid data races with concurrent readers.
|
||||
pub fn quicken(&self) {
|
||||
let len = self.len();
|
||||
@@ -625,8 +676,13 @@ impl CodeUnits {
|
||||
if !op.is_instrumented() {
|
||||
let cache_base = i + 1;
|
||||
if cache_base < len {
|
||||
let initial_counter = if matches!(op, Instruction::JumpBackward { .. }) {
|
||||
JUMP_BACKWARD_INITIAL_VALUE
|
||||
} else {
|
||||
ADAPTIVE_WARMUP_VALUE
|
||||
};
|
||||
unsafe {
|
||||
self.write_adaptive_counter(cache_base, ADAPTIVE_WARMUP_VALUE);
|
||||
self.write_adaptive_counter(cache_base, initial_counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,9 +210,18 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
|
||||
func_ref: FuncRef,
|
||||
bytecode: &CodeObject<C>,
|
||||
) -> Result<(), JitCompileError> {
|
||||
// JIT should consume a stable instruction stream: de-specialized opcodes
|
||||
// with zeroed CACHE entries, not runtime-mutated quickened code.
|
||||
let clean_instructions: bytecode::CodeUnits = bytecode
|
||||
.instructions
|
||||
.original_bytes()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| JitCompileError::BadBytecode)?;
|
||||
|
||||
let mut label_targets = BTreeSet::new();
|
||||
let mut target_arg_state = OpArgState::default();
|
||||
for (offset, &raw_instr) in bytecode.instructions.iter().enumerate() {
|
||||
for (offset, &raw_instr) in clean_instructions.iter().enumerate() {
|
||||
let (instruction, arg) = target_arg_state.get(raw_instr);
|
||||
if let Some(target) = Self::instruction_target(offset as u32, instruction, arg)? {
|
||||
label_targets.insert(target);
|
||||
@@ -223,7 +232,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
|
||||
// Track whether we have "returned" in the current block
|
||||
let mut in_unreachable_code = false;
|
||||
|
||||
for (offset, &raw_instr) in bytecode.instructions.iter().enumerate() {
|
||||
for (offset, &raw_instr) in clean_instructions.iter().enumerate() {
|
||||
let label = Label(offset as u32);
|
||||
let (instruction, arg) = arg_state.get(raw_instr);
|
||||
|
||||
|
||||
@@ -668,6 +668,33 @@ impl Py<PyDict> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a cached-entry hint for exact dict fast paths.
|
||||
pub(crate) fn hint_for_key<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
key: &K,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<u16>> {
|
||||
if self.exact_dict(vm) {
|
||||
self.entries.hint_for_key(vm, key)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast lookup using a cached entry index hint.
|
||||
pub(crate) fn get_item_opt_hint<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
key: &K,
|
||||
hint: u16,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
if self.exact_dict(vm) {
|
||||
self.entries.get_hint(vm, key, usize::from(hint))
|
||||
} else {
|
||||
self.get_item_opt(key, vm)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_item<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> PyResult {
|
||||
if self.exact_dict(vm) {
|
||||
self.inner_getitem(key, vm)
|
||||
|
||||
@@ -727,10 +727,12 @@ impl PyFunction {
|
||||
|
||||
#[pygetset(setter)]
|
||||
fn set___code__(&self, code: PyRef<PyCode>, vm: &VirtualMachine) {
|
||||
#[cfg(feature = "jit")]
|
||||
let mut jit_guard = self.jitted_code.lock();
|
||||
self.code.swap_to_temporary_refs(code, vm);
|
||||
#[cfg(feature = "jit")]
|
||||
{
|
||||
*self.jitted_code.lock() = None;
|
||||
*jit_guard = None;
|
||||
}
|
||||
self.func_version.store(0, Relaxed);
|
||||
}
|
||||
@@ -968,7 +970,8 @@ impl PyFunction {
|
||||
#[cfg(feature = "jit")]
|
||||
#[pymethod]
|
||||
fn __jit__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if zelf.jitted_code.lock().is_some() {
|
||||
let mut jit_guard = zelf.jitted_code.lock();
|
||||
if jit_guard.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
let arg_types = jit::get_jit_arg_types(&zelf, vm)?;
|
||||
@@ -976,7 +979,7 @@ impl PyFunction {
|
||||
let code: &Py<PyCode> = &zelf.code;
|
||||
let compiled = rustpython_jit::compile(&code.code, &arg_types, ret_type)
|
||||
.map_err(|err| jit::new_jit_error(err.to_string(), vm))?;
|
||||
*zelf.jitted_code.lock() = Some(compiled);
|
||||
*jit_guard = Some(compiled);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1149,6 +1152,16 @@ impl PyBoundMethod {
|
||||
Self { object, function }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn function_obj(&self) -> &PyObjectRef {
|
||||
&self.function
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn self_obj(&self) -> &PyObjectRef {
|
||||
&self.object
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use `Self::new(object, function).into_ref(ctx)` instead")]
|
||||
pub fn new_ref(object: PyObjectRef, function: PyObjectRef, ctx: &Context) -> PyRef<Self> {
|
||||
Self::new(object, function).into_ref(ctx)
|
||||
|
||||
@@ -327,7 +327,13 @@ impl PyTuple {
|
||||
|
||||
fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
|
||||
match SequenceIndex::try_from_borrowed_object(vm, needle, "tuple")? {
|
||||
SequenceIndex::Int(i) => self.elements.getitem_by_index(vm, i),
|
||||
SequenceIndex::Int(i) => {
|
||||
let index = self
|
||||
.elements
|
||||
.wrap_index(i)
|
||||
.ok_or_else(|| vm.new_index_error("tuple index out of range"))?;
|
||||
Ok(self.elements[index].clone())
|
||||
}
|
||||
SequenceIndex::Slice(slice) => self
|
||||
.elements
|
||||
.getitem_by_slice(vm, slice)
|
||||
|
||||
@@ -337,6 +337,50 @@ impl<T: Clone> Dict<T> {
|
||||
self._get_inner(vm, key, hash)
|
||||
}
|
||||
|
||||
/// Return a stable entry hint for `key` if present.
|
||||
///
|
||||
/// The hint is the internal entry index and can be used with
|
||||
/// [`Self::get_hint`]. It is invalidated by dict mutations.
|
||||
pub fn hint_for_key<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
vm: &VirtualMachine,
|
||||
key: &K,
|
||||
) -> PyResult<Option<u16>> {
|
||||
let hash = key.key_hash(vm)?;
|
||||
let (entry, _) = self.lookup(vm, key, hash, None)?;
|
||||
let Some(index) = entry.index() else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(u16::try_from(index).ok())
|
||||
}
|
||||
|
||||
/// Fast path lookup using a cached entry index (`hint`).
|
||||
///
|
||||
/// Returns `None` if the hint is stale or the key no longer matches.
|
||||
pub fn get_hint<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
vm: &VirtualMachine,
|
||||
key: &K,
|
||||
hint: usize,
|
||||
) -> PyResult<Option<T>> {
|
||||
let (entry_key, entry_value) = {
|
||||
let inner = self.read();
|
||||
let Some(Some(entry)) = inner.entries.get(hint) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if key.key_is(&entry.key) {
|
||||
return Ok(Some(entry.value.clone()));
|
||||
}
|
||||
(entry.key.clone(), entry.value.clone())
|
||||
};
|
||||
// key_eq may run Python __eq__, so must be outside the lock.
|
||||
if key.key_eq(vm, &entry_key)? {
|
||||
Ok(Some(entry_value))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn _get_inner<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
vm: &VirtualMachine,
|
||||
|
||||
+1026
-676
File diff suppressed because it is too large.
Load diff
@@ -322,7 +322,7 @@ pub fn instrument_code(code: &PyCode, events: u32) {
|
||||
.code
|
||||
.instructions
|
||||
.iter()
|
||||
.position(|u| matches!(u.op, Instruction::Resume { .. }))
|
||||
.position(|u| matches!(u.op, Instruction::Resume { .. } | Instruction::ResumeCheck))
|
||||
.unwrap_or(0);
|
||||
|
||||
// Phase 4: Place regular INSTRUMENTED_* opcodes
|
||||
|
||||
Reference in new issue
Block a user