mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Refine specialization caches and extend binary-op coverage (#7386)
* Align BINARY_OP_EXTEND with CPython descriptor cache model * Align type _spec_cache and latin1 singleton string paths * Add specialization differential harness and align init error text * Tighten CALL_ALLOC_AND_ENTER_INIT stack-space guard * Align call-init frame flow and spec cache atomic ordering * Refine call-init recursion guard and cache swap lifetime handling * Align spec cache write locking with CPython contract * Align load attr miss cooldown with CPython * Align CALL descriptor and class-call specialization with CPython * Extract datastack_frame_size_bytes_for_code, skip monitoring for init_cleanup frames, guard trace dispatch - Extract datastack_frame_size_bytes_for_code as free function, use it to compute init_cleanup stack bytes instead of hardcoded constant - Add monitoring_disabled_for_code to skip instrumentation for synthetic init_cleanup code object in RESUME and execute_instrumented - Add is_trace_event guard so profile-only events skip trace_func dispatch
This commit is contained in:
@@ -13,7 +13,7 @@ use crate::{
|
||||
bytecode,
|
||||
class::PyClassImpl,
|
||||
common::wtf8::{Wtf8Buf, wtf8_concat},
|
||||
frame::Frame,
|
||||
frame::{Frame, FrameRef},
|
||||
function::{FuncArgs, OptionalArg, PyComparisonValue, PySetterValue},
|
||||
scope::Scope,
|
||||
types::{
|
||||
@@ -673,27 +673,70 @@ impl Py<PyFunction> {
|
||||
/// Returns `None` for generator/coroutine code paths that do not push a
|
||||
/// regular datastack-backed frame in the fast call path.
|
||||
pub(crate) fn datastack_frame_size_bytes(&self) -> Option<usize> {
|
||||
let code: &Py<PyCode> = &self.code;
|
||||
if code
|
||||
.flags
|
||||
.intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE)
|
||||
datastack_frame_size_bytes_for_code(&self.code)
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_exact_args_frame(
|
||||
&self,
|
||||
mut args: Vec<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> FrameRef {
|
||||
let code: PyRef<PyCode> = (*self.code).to_owned();
|
||||
|
||||
debug_assert_eq!(args.len(), code.arg_count as usize);
|
||||
debug_assert!(code.flags.contains(bytecode::CodeFlags::OPTIMIZED));
|
||||
debug_assert!(
|
||||
!code
|
||||
.flags
|
||||
.intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
|
||||
);
|
||||
debug_assert_eq!(code.kwonlyarg_count, 0);
|
||||
debug_assert!(
|
||||
!code
|
||||
.flags
|
||||
.intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE)
|
||||
);
|
||||
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
None
|
||||
} else {
|
||||
Some(ArgMapping::from_dict_exact(self.globals.clone()))
|
||||
};
|
||||
|
||||
let frame = Frame::new(
|
||||
code.clone(),
|
||||
Scope::new(locals, self.globals.clone()),
|
||||
self.builtins.clone(),
|
||||
self.closure.as_ref().map_or(&[], |c| c.as_slice()),
|
||||
Some(self.to_owned().into()),
|
||||
true, // Exact-args fast path is only used for non-gen/coro functions.
|
||||
vm,
|
||||
)
|
||||
.into_ref(&vm.ctx);
|
||||
|
||||
{
|
||||
return None;
|
||||
let fastlocals = unsafe { frame.fastlocals_mut() };
|
||||
for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..)) {
|
||||
*slot = Some(arg);
|
||||
}
|
||||
}
|
||||
let nlocalsplus = code
|
||||
.varnames
|
||||
.len()
|
||||
.checked_add(code.cellvars.len())?
|
||||
.checked_add(code.freevars.len())?;
|
||||
let capacity = nlocalsplus.checked_add(code.max_stackdepth as usize)?;
|
||||
capacity.checked_mul(core::mem::size_of::<usize>())
|
||||
|
||||
if let Some(cell2arg) = code.cell2arg.as_deref() {
|
||||
let fastlocals = unsafe { frame.fastlocals_mut() };
|
||||
for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
|
||||
let x = fastlocals[*arg_idx as usize].take();
|
||||
frame.set_cell_contents(cell_idx, x);
|
||||
}
|
||||
}
|
||||
|
||||
frame
|
||||
}
|
||||
|
||||
/// Fast path for calling a simple function with exact positional args.
|
||||
/// Skips FuncArgs allocation, prepend_arg, and fill_locals_from_args.
|
||||
/// Only valid when: CO_OPTIMIZED, no VARARGS, no VARKEYWORDS, no kwonlyargs,
|
||||
/// and nargs == co_argcount.
|
||||
pub fn invoke_exact_args(&self, mut args: Vec<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
|
||||
pub fn invoke_exact_args(&self, args: Vec<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
|
||||
let code: PyRef<PyCode> = (*self.code).to_owned();
|
||||
|
||||
debug_assert_eq!(args.len(), code.arg_count as usize);
|
||||
@@ -714,40 +757,7 @@ impl Py<PyFunction> {
|
||||
{
|
||||
return self.invoke(FuncArgs::from(args), vm);
|
||||
}
|
||||
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
None
|
||||
} else {
|
||||
Some(ArgMapping::from_dict_exact(self.globals.clone()))
|
||||
};
|
||||
|
||||
let frame = Frame::new(
|
||||
code.clone(),
|
||||
Scope::new(locals, self.globals.clone()),
|
||||
self.builtins.clone(),
|
||||
self.closure.as_ref().map_or(&[], |c| c.as_slice()),
|
||||
Some(self.to_owned().into()),
|
||||
true, // Always use datastack (invoke_exact_args is never gen/coro)
|
||||
vm,
|
||||
)
|
||||
.into_ref(&vm.ctx);
|
||||
|
||||
// Move args directly into fastlocals (no clone/refcount needed)
|
||||
{
|
||||
let fastlocals = unsafe { frame.fastlocals_mut() };
|
||||
for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..)) {
|
||||
*slot = Some(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cell2arg
|
||||
if let Some(cell2arg) = code.cell2arg.as_deref() {
|
||||
let fastlocals = unsafe { frame.fastlocals_mut() };
|
||||
for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
|
||||
let x = fastlocals[*arg_idx as usize].take();
|
||||
frame.set_cell_contents(cell_idx, x);
|
||||
}
|
||||
}
|
||||
let frame = self.prepare_exact_args_frame(args, vm);
|
||||
|
||||
let result = vm.run_frame(frame.clone());
|
||||
unsafe {
|
||||
@@ -759,6 +769,22 @@ impl Py<PyFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn datastack_frame_size_bytes_for_code(code: &Py<PyCode>) -> Option<usize> {
|
||||
if code
|
||||
.flags
|
||||
.intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let nlocalsplus = code
|
||||
.varnames
|
||||
.len()
|
||||
.checked_add(code.cellvars.len())?
|
||||
.checked_add(code.freevars.len())?;
|
||||
let capacity = nlocalsplus.checked_add(code.max_stackdepth as usize)?;
|
||||
capacity.checked_mul(core::mem::size_of::<usize>())
|
||||
}
|
||||
|
||||
impl PyPayload for PyFunction {
|
||||
#[inline]
|
||||
fn class(ctx: &Context) -> &'static Py<PyType> {
|
||||
@@ -1351,6 +1377,7 @@ pub(crate) fn vectorcall_function(
|
||||
|
||||
let has_kwargs = kwnames.is_some_and(|kw| !kw.is_empty());
|
||||
let is_simple = !has_kwargs
|
||||
&& code.flags.contains(bytecode::CodeFlags::OPTIMIZED)
|
||||
&& !code.flags.contains(bytecode::CodeFlags::VARARGS)
|
||||
&& !code.flags.contains(bytecode::CodeFlags::VARKEYWORDS)
|
||||
&& code.kwonlyarg_count == 0
|
||||
@@ -1361,37 +1388,8 @@ pub(crate) fn vectorcall_function(
|
||||
if is_simple && nargs == code.arg_count as usize {
|
||||
// FAST PATH: simple positional-only call, exact arg count.
|
||||
// Move owned args directly into fastlocals — no clone needed.
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
None // lazy allocation — most frames never access locals dict
|
||||
} else {
|
||||
Some(ArgMapping::from_dict_exact(zelf.globals.clone()))
|
||||
};
|
||||
|
||||
let frame = Frame::new(
|
||||
code.to_owned(),
|
||||
Scope::new(locals, zelf.globals.clone()),
|
||||
zelf.builtins.clone(),
|
||||
zelf.closure.as_ref().map_or(&[], |c| c.as_slice()),
|
||||
Some(zelf.to_owned().into()),
|
||||
true, // Always use datastack (is_simple excludes gen/coro)
|
||||
vm,
|
||||
)
|
||||
.into_ref(&vm.ctx);
|
||||
|
||||
{
|
||||
let fastlocals = unsafe { frame.fastlocals_mut() };
|
||||
for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..nargs)) {
|
||||
*slot = Some(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cell2arg) = code.cell2arg.as_deref() {
|
||||
let fastlocals = unsafe { frame.fastlocals_mut() };
|
||||
for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
|
||||
let x = fastlocals[*arg_idx as usize].take();
|
||||
frame.set_cell_contents(cell_idx, x);
|
||||
}
|
||||
}
|
||||
args.truncate(nargs);
|
||||
let frame = zelf.prepare_exact_args_frame(args, vm);
|
||||
|
||||
let result = vm.run_frame(frame.clone());
|
||||
unsafe {
|
||||
|
||||
@@ -1691,7 +1691,7 @@ impl ToPyObject for char {
|
||||
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
let cp = self as u32;
|
||||
if cp <= u8::MAX as u32 {
|
||||
vm.ctx.latin1_char_cache[cp as usize].clone().into()
|
||||
vm.ctx.latin1_char(cp as u8).into()
|
||||
} else {
|
||||
vm.ctx.new_str(self).into()
|
||||
}
|
||||
@@ -1702,7 +1702,7 @@ impl ToPyObject for CodePoint {
|
||||
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
let cp = self.to_u32();
|
||||
if cp <= u8::MAX as u32 {
|
||||
vm.ctx.latin1_char_cache[cp as usize].clone().into()
|
||||
vm.ctx.latin1_char(cp as u8).into()
|
||||
} else {
|
||||
vm.ctx.new_str(self).into()
|
||||
}
|
||||
@@ -1747,7 +1747,7 @@ impl ToPyObject for AsciiString {
|
||||
|
||||
impl ToPyObject for AsciiChar {
|
||||
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.ctx.new_str(self).into()
|
||||
vm.ctx.latin1_char(u8::from(self)).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use super::{
|
||||
PyUtf8StrRef, PyWeak, mappingproxy::PyMappingProxy, object, union_,
|
||||
};
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
|
||||
VirtualMachine,
|
||||
AsObject, Context, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
TryFromObject, VirtualMachine,
|
||||
builtins::{
|
||||
PyBaseExceptionRef,
|
||||
descriptor::{
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
common::{
|
||||
ascii,
|
||||
borrow::BorrowedValue,
|
||||
lock::{PyRwLock, PyRwLockReadGuard},
|
||||
lock::{PyMutex, PyRwLock, PyRwLockReadGuard},
|
||||
},
|
||||
function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue},
|
||||
object::{Traverse, TraverseFn},
|
||||
@@ -228,8 +228,7 @@ unsafe impl crate::object::Traverse for PyType {
|
||||
.map(|(_, v)| v.traverse(tracer_fn))
|
||||
.count();
|
||||
if let Some(ext) = self.heaptype_ext.as_ref() {
|
||||
ext.specialization_init.read().traverse(tracer_fn);
|
||||
ext.specialization_getitem.read().traverse(tracer_fn);
|
||||
ext.specialization_cache.traverse(tracer_fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,18 +258,7 @@ unsafe impl crate::object::Traverse for PyType {
|
||||
}
|
||||
}
|
||||
if let Some(ext) = self.heaptype_ext.as_ref() {
|
||||
if let Some(mut guard) = ext.specialization_init.try_write()
|
||||
&& let Some(init) = guard.take()
|
||||
{
|
||||
out.push(init.into());
|
||||
}
|
||||
if let Some(mut guard) = ext.specialization_getitem.try_write()
|
||||
&& let Some(getitem) = guard.take()
|
||||
{
|
||||
out.push(getitem.into());
|
||||
ext.specialization_getitem_version
|
||||
.store(0, Ordering::Release);
|
||||
}
|
||||
ext.specialization_cache.clear_into(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,9 +269,99 @@ pub struct HeapTypeExt {
|
||||
pub qualname: PyRwLock<PyStrRef>,
|
||||
pub slots: Option<PyRef<PyTuple<PyStrRef>>>,
|
||||
pub type_data: PyRwLock<Option<TypeDataSlot>>,
|
||||
pub specialization_init: PyRwLock<Option<PyRef<PyFunction>>>,
|
||||
pub specialization_getitem: PyRwLock<Option<PyRef<PyFunction>>>,
|
||||
pub specialization_getitem_version: AtomicU32,
|
||||
pub specialization_cache: TypeSpecializationCache,
|
||||
}
|
||||
|
||||
pub struct TypeSpecializationCache {
|
||||
pub init: PyAtomicRef<Option<PyFunction>>,
|
||||
pub getitem: PyAtomicRef<Option<PyFunction>>,
|
||||
pub getitem_version: AtomicU32,
|
||||
// Serialize cache writes/invalidation similar to CPython's BEGIN_TYPE_LOCK.
|
||||
write_lock: PyMutex<()>,
|
||||
retired: PyRwLock<Vec<PyObjectRef>>,
|
||||
}
|
||||
|
||||
impl TypeSpecializationCache {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
init: PyAtomicRef::from(None::<PyRef<PyFunction>>),
|
||||
getitem: PyAtomicRef::from(None::<PyRef<PyFunction>>),
|
||||
getitem_version: AtomicU32::new(0),
|
||||
write_lock: PyMutex::new(()),
|
||||
retired: PyRwLock::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn retire_old_function(&self, old: Option<PyRef<PyFunction>>) {
|
||||
if let Some(old) = old {
|
||||
self.retired.write().push(old.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn swap_init(&self, new_init: Option<PyRef<PyFunction>>, vm: Option<&VirtualMachine>) {
|
||||
if let Some(vm) = vm {
|
||||
// Keep replaced refs alive for the currently executing frame, matching
|
||||
// CPython-style "old pointer remains valid during ongoing execution"
|
||||
// without accumulating global retired refs.
|
||||
self.init.swap_to_temporary_refs(new_init, vm);
|
||||
return;
|
||||
}
|
||||
// SAFETY: old value is moved to `retired`, so it stays alive while
|
||||
// concurrent readers may still hold borrowed references.
|
||||
let old = unsafe { self.init.swap(new_init) };
|
||||
self.retire_old_function(old);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn swap_getitem(&self, new_getitem: Option<PyRef<PyFunction>>, vm: Option<&VirtualMachine>) {
|
||||
if let Some(vm) = vm {
|
||||
self.getitem.swap_to_temporary_refs(new_getitem, vm);
|
||||
return;
|
||||
}
|
||||
// SAFETY: old value is moved to `retired`, so it stays alive while
|
||||
// concurrent readers may still hold borrowed references.
|
||||
let old = unsafe { self.getitem.swap(new_getitem) };
|
||||
self.retire_old_function(old);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn invalidate_for_type_modified(&self) {
|
||||
let _guard = self.write_lock.lock();
|
||||
// _spec_cache contract: type modification invalidates all cached
|
||||
// specialization functions.
|
||||
self.swap_init(None, None);
|
||||
self.swap_getitem(None, None);
|
||||
}
|
||||
|
||||
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
|
||||
if let Some(init) = self.init.deref() {
|
||||
tracer_fn(init.as_object());
|
||||
}
|
||||
if let Some(getitem) = self.getitem.deref() {
|
||||
tracer_fn(getitem.as_object());
|
||||
}
|
||||
self.retired
|
||||
.read()
|
||||
.iter()
|
||||
.map(|obj| obj.traverse(tracer_fn))
|
||||
.count();
|
||||
}
|
||||
|
||||
fn clear_into(&self, out: &mut Vec<PyObjectRef>) {
|
||||
let _guard = self.write_lock.lock();
|
||||
let old_init = unsafe { self.init.swap(None) };
|
||||
if let Some(old_init) = old_init {
|
||||
out.push(old_init.into());
|
||||
}
|
||||
let old_getitem = unsafe { self.getitem.swap(None) };
|
||||
if let Some(old_getitem) = old_getitem {
|
||||
out.push(old_getitem.into());
|
||||
}
|
||||
self.getitem_version.store(0, Ordering::Release);
|
||||
out.extend(self.retired.write().drain(..));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PointerSlot<T>(NonNull<T>);
|
||||
@@ -412,10 +490,7 @@ impl PyType {
|
||||
/// Invalidate this type's version tag and cascade to all subclasses.
|
||||
pub fn modified(&self) {
|
||||
if let Some(ext) = self.heaptype_ext.as_ref() {
|
||||
*ext.specialization_init.write() = None;
|
||||
*ext.specialization_getitem.write() = None;
|
||||
ext.specialization_getitem_version
|
||||
.store(0, Ordering::Release);
|
||||
ext.specialization_cache.invalidate_for_type_modified();
|
||||
}
|
||||
// If already invalidated, all subclasses must also be invalidated
|
||||
// (guaranteed by the MRO invariant in assign_version_tag).
|
||||
@@ -470,9 +545,7 @@ impl PyType {
|
||||
qualname: PyRwLock::new(name),
|
||||
slots: None,
|
||||
type_data: PyRwLock::new(None),
|
||||
specialization_init: PyRwLock::new(None),
|
||||
specialization_getitem: PyRwLock::new(None),
|
||||
specialization_getitem_version: AtomicU32::new(0),
|
||||
specialization_cache: TypeSpecializationCache::new(),
|
||||
};
|
||||
let base = bases[0].clone();
|
||||
|
||||
@@ -831,6 +904,7 @@ impl PyType {
|
||||
&self,
|
||||
init: PyRef<PyFunction>,
|
||||
tp_version: u32,
|
||||
vm: &VirtualMachine,
|
||||
) -> bool {
|
||||
let Some(ext) = self.heaptype_ext.as_ref() else {
|
||||
return false;
|
||||
@@ -838,11 +912,14 @@ impl PyType {
|
||||
if tp_version == 0 {
|
||||
return false;
|
||||
}
|
||||
let mut guard = ext.specialization_init.write();
|
||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||
return false;
|
||||
}
|
||||
*guard = Some(init);
|
||||
let _guard = ext.specialization_cache.write_lock.lock();
|
||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||
return false;
|
||||
}
|
||||
ext.specialization_cache.swap_init(Some(init), Some(vm));
|
||||
true
|
||||
}
|
||||
|
||||
@@ -855,11 +932,12 @@ impl PyType {
|
||||
if tp_version == 0 {
|
||||
return None;
|
||||
}
|
||||
let guard = ext.specialization_init.read();
|
||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||
return None;
|
||||
}
|
||||
guard.as_ref().map(|init| init.to_owned())
|
||||
ext.specialization_cache
|
||||
.init
|
||||
.to_owned_ordering(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Cache __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
|
||||
@@ -868,6 +946,7 @@ impl PyType {
|
||||
&self,
|
||||
getitem: PyRef<PyFunction>,
|
||||
tp_version: u32,
|
||||
vm: &VirtualMachine,
|
||||
) -> bool {
|
||||
let Some(ext) = self.heaptype_ext.as_ref() else {
|
||||
return false;
|
||||
@@ -875,34 +954,38 @@ impl PyType {
|
||||
if tp_version == 0 {
|
||||
return false;
|
||||
}
|
||||
let _guard = ext.specialization_cache.write_lock.lock();
|
||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||
return false;
|
||||
}
|
||||
let func_version = getitem.get_version_for_current_state();
|
||||
if func_version == 0 {
|
||||
return false;
|
||||
}
|
||||
let mut guard = ext.specialization_getitem.write();
|
||||
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
|
||||
return false;
|
||||
}
|
||||
*guard = Some(getitem);
|
||||
ext.specialization_getitem_version
|
||||
.store(func_version, Ordering::Release);
|
||||
ext.specialization_cache
|
||||
.swap_getitem(Some(getitem), Some(vm));
|
||||
ext.specialization_cache
|
||||
.getitem_version
|
||||
.store(func_version, Ordering::Relaxed);
|
||||
true
|
||||
}
|
||||
|
||||
/// Read cached __getitem__ for BINARY_OP_SUBSCR_GETITEM specialization.
|
||||
pub(crate) fn get_cached_getitem_for_specialization(&self) -> Option<(PyRef<PyFunction>, u32)> {
|
||||
let ext = self.heaptype_ext.as_ref()?;
|
||||
let cached_version = ext.specialization_getitem_version.load(Ordering::Acquire);
|
||||
// Match CPython check order: pointer (Acquire) then function version.
|
||||
let getitem = ext
|
||||
.specialization_cache
|
||||
.getitem
|
||||
.to_owned_ordering(Ordering::Acquire)?;
|
||||
let cached_version = ext
|
||||
.specialization_cache
|
||||
.getitem_version
|
||||
.load(Ordering::Relaxed);
|
||||
if cached_version == 0 {
|
||||
return None;
|
||||
}
|
||||
let guard = ext.specialization_getitem.read();
|
||||
if self.tp_version_tag.load(Ordering::Acquire) == 0 {
|
||||
return None;
|
||||
}
|
||||
guard
|
||||
.as_ref()
|
||||
.map(|getitem| (getitem.to_owned(), cached_version))
|
||||
Some((getitem, cached_version))
|
||||
}
|
||||
|
||||
pub fn get_direct_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
@@ -2001,9 +2084,7 @@ impl Constructor for PyType {
|
||||
qualname: PyRwLock::new(qualname),
|
||||
slots: heaptype_slots.clone(),
|
||||
type_data: PyRwLock::new(None),
|
||||
specialization_init: PyRwLock::new(None),
|
||||
specialization_getitem: PyRwLock::new(None),
|
||||
specialization_getitem_version: AtomicU32::new(0),
|
||||
specialization_cache: TypeSpecializationCache::new(),
|
||||
};
|
||||
(slots, heaptype_ext)
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -355,11 +355,19 @@ impl<T: PyPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> {
|
||||
|
||||
impl<T: PyPayload> PyAtomicRef<Option<T>> {
|
||||
pub fn deref(&self) -> Option<&Py<T>> {
|
||||
unsafe { self.inner.load(Ordering::Relaxed).cast::<Py<T>>().as_ref() }
|
||||
self.deref_ordering(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn deref_ordering(&self, ordering: Ordering) -> Option<&Py<T>> {
|
||||
unsafe { self.inner.load(ordering).cast::<Py<T>>().as_ref() }
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> Option<PyRef<T>> {
|
||||
self.deref().map(|x| x.to_owned())
|
||||
self.to_owned_ordering(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn to_owned_ordering(&self, ordering: Ordering) -> Option<PyRef<T>> {
|
||||
self.deref_ordering(ordering).map(|x| x.to_owned())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
@@ -441,16 +449,19 @@ impl From<Option<PyObjectRef>> for PyAtomicRef<Option<PyObject>> {
|
||||
|
||||
impl PyAtomicRef<Option<PyObject>> {
|
||||
pub fn deref(&self) -> Option<&PyObject> {
|
||||
unsafe {
|
||||
self.inner
|
||||
.load(Ordering::Relaxed)
|
||||
.cast::<PyObject>()
|
||||
.as_ref()
|
||||
}
|
||||
self.deref_ordering(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn deref_ordering(&self, ordering: Ordering) -> Option<&PyObject> {
|
||||
unsafe { self.inner.load(ordering).cast::<PyObject>().as_ref() }
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> Option<PyObjectRef> {
|
||||
self.deref().map(|x| x.to_owned())
|
||||
self.to_owned_ordering(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn to_owned_ordering(&self, ordering: Ordering) -> Option<PyObjectRef> {
|
||||
self.deref_ordering(ordering).map(|x| x.to_owned())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
||||
@@ -146,6 +146,14 @@ pub(crate) enum TraceEvent {
|
||||
}
|
||||
|
||||
impl TraceEvent {
|
||||
/// Whether sys.settrace receives this event.
|
||||
fn is_trace_event(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Call | Self::Return | Self::Exception | Self::Line | Self::Opcode
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether sys.setprofile receives this event.
|
||||
/// In legacy_tracing.c, profile callbacks are only registered for
|
||||
/// PY_RETURN, PY_UNWIND, C_CALL, C_RETURN, C_RAISE.
|
||||
@@ -211,6 +219,7 @@ impl VirtualMachine {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let is_trace_event = event.is_trace_event();
|
||||
let is_profile_event = event.is_profile_event();
|
||||
let is_opcode_event = event.is_opcode_event();
|
||||
|
||||
@@ -231,7 +240,7 @@ impl VirtualMachine {
|
||||
|
||||
// temporarily disable tracing, during the call to the
|
||||
// tracing function itself.
|
||||
if !self.is_none(&trace_func) {
|
||||
if is_trace_event && !self.is_none(&trace_func) {
|
||||
self.use_tracing.set(false);
|
||||
let res = trace_func.call(args.clone(), self);
|
||||
self.use_tracing.set(true);
|
||||
|
||||
@@ -998,9 +998,7 @@ mod builtins {
|
||||
};
|
||||
let write = |obj: PyStrRef| vm.call_method(&file, "write", (obj,));
|
||||
|
||||
let sep = options
|
||||
.sep
|
||||
.unwrap_or_else(|| PyStr::from(" ").into_ref(&vm.ctx));
|
||||
let sep = options.sep.unwrap_or_else(|| vm.ctx.new_str(" "));
|
||||
|
||||
let mut first = true;
|
||||
for object in objects {
|
||||
@@ -1013,9 +1011,7 @@ mod builtins {
|
||||
write(object.str(vm)?)?;
|
||||
}
|
||||
|
||||
let end = options
|
||||
.end
|
||||
.unwrap_or_else(|| PyStr::from("\n").into_ref(&vm.ctx));
|
||||
let end = options.end.unwrap_or_else(|| vm.ctx.new_str("\n"));
|
||||
write(end)?;
|
||||
|
||||
if options.flush.into() {
|
||||
|
||||
@@ -614,7 +614,7 @@ fn init_wrapper(obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResu
|
||||
let res = vm.call_special_method(&obj, identifier!(vm, __init__), args)?;
|
||||
if !vm.is_none(&res) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"__init__ should return None, not '{:.200}'",
|
||||
"__init__() should return None, not '{:.200}'",
|
||||
res.class().name()
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::{
|
||||
object, pystr,
|
||||
type_::PyAttributes,
|
||||
},
|
||||
bytecode::{self, CodeFlags, CodeUnit, Instruction},
|
||||
class::StaticType,
|
||||
common::rc::PyRc,
|
||||
exceptions,
|
||||
@@ -29,6 +30,7 @@ use malachite_bigint::BigInt;
|
||||
use num_complex::Complex64;
|
||||
use num_traits::ToPrimitive;
|
||||
use rustpython_common::lock::PyRwLock;
|
||||
use rustpython_compiler_core::{OneIndexed, SourceLocation};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
@@ -49,6 +51,7 @@ pub struct Context {
|
||||
pub int_cache_pool: Vec<PyIntRef>,
|
||||
pub(crate) latin1_char_cache: Vec<PyRef<PyStr>>,
|
||||
pub(crate) ascii_char_cache: Vec<PyRef<PyStr>>,
|
||||
pub(crate) init_cleanup_code: PyRef<PyCode>,
|
||||
// there should only be exact objects of str in here, no non-str objects and no subclasses
|
||||
pub(crate) string_pool: StringPool,
|
||||
pub(crate) slot_new_wrapper: PyMethodDef,
|
||||
@@ -353,6 +356,7 @@ impl Context {
|
||||
PyMethodFlags::METHOD,
|
||||
None,
|
||||
);
|
||||
let init_cleanup_code = Self::new_init_cleanup_code(&types, &names);
|
||||
|
||||
let empty_str = unsafe { string_pool.intern("", types.str_type.to_owned()) };
|
||||
let empty_bytes = create_object(PyBytes::from(Vec::new()), types.bytes_type);
|
||||
@@ -379,6 +383,7 @@ impl Context {
|
||||
int_cache_pool,
|
||||
latin1_char_cache,
|
||||
ascii_char_cache,
|
||||
init_cleanup_code,
|
||||
string_pool,
|
||||
slot_new_wrapper,
|
||||
names,
|
||||
@@ -388,6 +393,51 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_init_cleanup_code(types: &TypeZoo, names: &ConstName) -> PyRef<PyCode> {
|
||||
let loc = SourceLocation {
|
||||
line: OneIndexed::MIN,
|
||||
character_offset: OneIndexed::from_zero_indexed(0),
|
||||
};
|
||||
let instructions = [
|
||||
CodeUnit {
|
||||
op: Instruction::ExitInitCheck,
|
||||
arg: 0.into(),
|
||||
},
|
||||
CodeUnit {
|
||||
op: Instruction::ReturnValue,
|
||||
arg: 0.into(),
|
||||
},
|
||||
CodeUnit {
|
||||
op: Instruction::Resume {
|
||||
context: bytecode::Arg::marker(),
|
||||
},
|
||||
arg: 0.into(),
|
||||
},
|
||||
];
|
||||
let code = bytecode::CodeObject {
|
||||
instructions: instructions.into(),
|
||||
locations: vec![(loc, loc); instructions.len()].into_boxed_slice(),
|
||||
flags: CodeFlags::OPTIMIZED,
|
||||
posonlyarg_count: 0,
|
||||
arg_count: 0,
|
||||
kwonlyarg_count: 0,
|
||||
source_path: names.__init__,
|
||||
first_line_number: None,
|
||||
max_stackdepth: 2,
|
||||
obj_name: names.__init__,
|
||||
qualname: names.__init__,
|
||||
cell2arg: None,
|
||||
constants: core::iter::empty().collect(),
|
||||
names: Vec::new().into_boxed_slice(),
|
||||
varnames: Vec::new().into_boxed_slice(),
|
||||
cellvars: Vec::new().into_boxed_slice(),
|
||||
freevars: Vec::new().into_boxed_slice(),
|
||||
linetable: Vec::new().into_boxed_slice(),
|
||||
exceptiontable: Vec::new().into_boxed_slice(),
|
||||
};
|
||||
PyRef::new_ref(PyCode::new(code), types.code_type.to_owned(), None)
|
||||
}
|
||||
|
||||
pub fn intern_str<S: InternableString>(&self, s: S) -> &'static PyStrInterned {
|
||||
unsafe { self.string_pool.intern(s, self.types.str_type.to_owned()) }
|
||||
}
|
||||
@@ -458,9 +508,28 @@ impl Context {
|
||||
PyComplex::from(value).into_ref(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn latin1_char(&self, ch: u8) -> PyRef<PyStr> {
|
||||
self.latin1_char_cache[ch as usize].clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn latin1_singleton_index(s: &PyStr) -> Option<u8> {
|
||||
let mut cps = s.as_wtf8().code_points();
|
||||
let cp = cps.next()?;
|
||||
if cps.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
u8::try_from(cp.to_u32()).ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_str(&self, s: impl Into<pystr::PyStr>) -> PyRef<PyStr> {
|
||||
s.into().into_ref(self)
|
||||
let s = s.into();
|
||||
if let Some(ch) = Self::latin1_singleton_index(&s) {
|
||||
return self.latin1_char(ch);
|
||||
}
|
||||
s.into_ref(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -1550,7 +1550,7 @@ impl VirtualMachine {
|
||||
frame: FrameRef,
|
||||
f: F,
|
||||
) -> PyResult<R> {
|
||||
self.with_frame_exc(frame, None, f)
|
||||
self.with_frame_impl(frame, None, true, f)
|
||||
}
|
||||
|
||||
/// Like `with_frame` but allows specifying the initial exception state.
|
||||
@@ -1559,6 +1559,24 @@ impl VirtualMachine {
|
||||
frame: FrameRef,
|
||||
exc: Option<PyBaseExceptionRef>,
|
||||
f: F,
|
||||
) -> PyResult<R> {
|
||||
self.with_frame_impl(frame, exc, true, f)
|
||||
}
|
||||
|
||||
pub(crate) fn with_frame_untraced<R, F: FnOnce(FrameRef) -> PyResult<R>>(
|
||||
&self,
|
||||
frame: FrameRef,
|
||||
f: F,
|
||||
) -> PyResult<R> {
|
||||
self.with_frame_impl(frame, None, false, f)
|
||||
}
|
||||
|
||||
fn with_frame_impl<R, F: FnOnce(FrameRef) -> PyResult<R>>(
|
||||
&self,
|
||||
frame: FrameRef,
|
||||
exc: Option<PyBaseExceptionRef>,
|
||||
traced: bool,
|
||||
f: F,
|
||||
) -> PyResult<R> {
|
||||
self.with_recursion("", || {
|
||||
// SAFETY: `frame` (FrameRef) stays alive for the entire closure scope,
|
||||
@@ -1594,7 +1612,11 @@ impl VirtualMachine {
|
||||
crate::vm::thread::pop_thread_frame();
|
||||
}
|
||||
|
||||
self.dispatch_traced_frame(&frame, |frame| f(frame.to_owned()))
|
||||
if traced {
|
||||
self.dispatch_traced_frame(&frame, |frame| f(frame.to_owned()))
|
||||
} else {
|
||||
f(frame.to_owned())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user