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:
Jeong, YunWon
2026-03-10 15:39:55 +09:00
committed by GitHub
parent a854ef2a2b
commit d248a04cae
10 changed files with 812 additions and 490 deletions

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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()
)));
}

View File

@@ -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]

View File

@@ -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())
}
})
}