use crate::{
builtins::{PyBoundMethod, PyFunction},
function::{FuncArgs, IntoFuncArgs},
types::{GenericMethod, VectorCallFunc},
{PyObject, PyObjectRef, PyResult, VirtualMachine},
};
impl PyObject {
#[inline]
#[must_use]
pub fn to_callable(&self) -> Option<PyCallable<'_>> {
PyCallable::new(self)
}
#[inline]
#[must_use]
pub fn is_callable(&self) -> bool {
self.to_callable().is_some()
}
#[inline]
pub fn call(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
let args = args.into_args(vm);
self.call_with_args(args, vm)
}
pub fn call_with_args(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let Some(callable) = self.to_callable() else {
return Err(vm.new_type_error(format!(
"'{}' object is not callable",
self.class().slot_name()
)));
};
vm_trace!("Invoke: {:?} {:?}", callable, args);
callable.invoke(args, vm)
}
#[inline]
pub fn vectorcall(
&self,
args: Vec<PyObjectRef>,
nargs: usize,
kwnames: Option<&[PyObjectRef]>,
vm: &VirtualMachine,
) -> PyResult {
let Some(callable) = self.to_callable() else {
return Err(vm.new_type_error(format!(
"'{}' object is not callable",
self.class().slot_name()
)));
};
callable.invoke_vectorcall(args, nargs, kwnames, vm)
}
}
#[derive(Debug)]
pub struct PyCallable<'a> {
pub obj: &'a PyObject,
pub call: GenericMethod,
pub vectorcall: Option<VectorCallFunc>,
}
impl<'a> PyCallable<'a> {
pub fn new(obj: &'a PyObject) -> Option<Self> {
let slots = obj.class().slots();
let call = slots.call.load()?;
let vectorcall = slots.vectorcall.load();
Some(PyCallable {
obj,
call,
vectorcall,
})
}
pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
let args = args.into_args(vm);
if !vm.use_tracing.get() {
return (self.call)(self.obj, args, vm);
}
let is_python_callable = self.obj.downcast_ref::<PyFunction>().is_some()
|| self.obj.downcast_ref::<PyBoundMethod>().is_some();
if is_python_callable {
(self.call)(self.obj, args, vm)
} else {
let callable = self.obj.to_owned();
vm.trace_event(TraceEvent::CCall, Some(callable.clone()))?;
let result = (self.call)(self.obj, args, vm);
if result.is_ok() {
vm.trace_event(TraceEvent::CReturn, Some(callable))?;
} else {
let _ = vm.trace_event(TraceEvent::CException, Some(callable));
}
result
}
}
#[inline]
pub fn invoke_vectorcall(
&self,
args: Vec<PyObjectRef>,
nargs: usize,
kwnames: Option<&[PyObjectRef]>,
vm: &VirtualMachine,
) -> PyResult {
if let Some(vc) = self.vectorcall {
if !vm.use_tracing.get() {
return vc(self.obj, args, nargs, kwnames, vm);
}
let is_python_callable = self.obj.downcast_ref::<PyFunction>().is_some()
|| self.obj.downcast_ref::<PyBoundMethod>().is_some();
if is_python_callable {
vc(self.obj, args, nargs, kwnames, vm)
} else {
let callable = self.obj.to_owned();
vm.trace_event(TraceEvent::CCall, Some(callable.clone()))?;
let result = vc(self.obj, args, nargs, kwnames, vm);
if result.is_ok() {
vm.trace_event(TraceEvent::CReturn, Some(callable))?;
} else {
let _ = vm.trace_event(TraceEvent::CException, Some(callable));
}
result
}
} else {
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
self.invoke(func_args, vm)
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum TraceEvent {
Call,
Return,
Exception,
Line,
Opcode,
CCall,
CReturn,
CException,
}
impl TraceEvent {
#[must_use]
const fn is_trace_event(self) -> bool {
matches!(
self,
Self::Call | Self::Return | Self::Exception | Self::Line | Self::Opcode
)
}
#[must_use]
const fn is_profile_event(self) -> bool {
matches!(
self,
Self::Call | Self::Return | Self::CCall | Self::CReturn | Self::CException
)
}
#[must_use]
pub(crate) const fn is_opcode_event(self) -> bool {
matches!(self, Self::Opcode)
}
#[must_use]
const fn default_what(self) -> crate::stdlib::sys::monitoring::MonitoringEvent {
use crate::stdlib::sys::monitoring::MonitoringEvent as Ev;
match self {
Self::Call => Ev::PyStart,
Self::Return => Ev::PyReturn,
Self::Exception => Ev::Raise,
Self::Line => Ev::Line,
Self::Opcode => Ev::Instruction,
Self::CCall => Ev::Call,
Self::CReturn => Ev::CReturn,
Self::CException => Ev::CRaise,
}
}
}
impl core::fmt::Display for TraceEvent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Call => write!(f, "call"),
Self::Return => write!(f, "return"),
Self::Exception => write!(f, "exception"),
Self::Line => write!(f, "line"),
Self::Opcode => write!(f, "opcode"),
Self::CCall => write!(f, "c_call"),
Self::CReturn => write!(f, "c_return"),
Self::CException => write!(f, "c_exception"),
}
}
}
impl VirtualMachine {
#[inline]
pub(crate) fn trace_event(
&self,
event: TraceEvent,
arg: Option<PyObjectRef>,
) -> PyResult<Option<PyObjectRef>> {
self.trace_event_what(event, event.default_what(), arg)
}
#[inline]
pub(crate) fn trace_event_what(
&self,
event: TraceEvent,
what: crate::stdlib::sys::monitoring::MonitoringEvent,
arg: Option<PyObjectRef>,
) -> PyResult<Option<PyObjectRef>> {
if self.use_tracing.get() && !self.tracing_is_suppressed() {
let old = self.what_event.replace(Some(what));
let result = self._trace_event_inner(event, arg);
self.what_event.set(old);
result
} else {
Ok(None)
}
}
fn _trace_event_inner(
&self,
event: TraceEvent,
arg: Option<PyObjectRef>,
) -> PyResult<Option<PyObjectRef>> {
let trace_func = self.trace_func.borrow().to_owned();
let profile_func = self.profile_func.borrow().to_owned();
if self.is_none(&trace_func) && self.is_none(&profile_func) {
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();
let Some(frame_ref) = crate::frame::current_thread_frame_materialize(self) else {
return Ok(None);
};
if is_opcode_event
&& !frame_ref
.iframe()
.cold_opt()
.is_some_and(|c| *c.trace_opcodes.lock())
{
return Ok(None);
}
let callback = if event == TraceEvent::Call {
if self.is_none(&trace_func) {
None
} else {
Some(trace_func)
}
} else {
frame_ref
.iframe()
.cold_opt()
.and_then(|c| c.trace.lock().clone())
};
let frame: PyObjectRef = frame_ref.to_owned().into();
let event_str: PyObjectRef = self.ctx.new_str(event.to_string()).into();
let args = vec![frame, event_str, arg.unwrap_or_else(|| self.ctx.none())];
let mut trace_result = None;
if is_trace_event && let Some(callback) = callback {
self.use_tracing.set(false);
self.enter_tracing();
let res = callback.call(args.clone(), self);
self.leave_tracing();
self.use_tracing.set(true);
match res {
Ok(result) => {
if !self.is_none(&result) {
*frame_ref.iframe().cold().trace.lock() = Some(result.clone());
trace_result = Some(result);
}
}
Err(e) => {
*self.trace_func.borrow_mut() = self.ctx.none();
*frame_ref.iframe().cold().trace.lock() = None;
let profile_is_none = self.is_none(&self.profile_func.borrow());
self.use_tracing.set(!profile_is_none);
return Err(e);
}
}
}
if is_profile_event && !self.is_none(&profile_func) {
self.use_tracing.set(false);
self.enter_tracing();
let res = profile_func.call(args, self);
self.leave_tracing();
self.use_tracing.set(true);
if res.is_err() {
*self.profile_func.borrow_mut() = self.ctx.none();
}
}
Ok(trace_result)
}
}