use std::mem;
use serde::de::DeserializeOwned;
use crate::{
ExcType, MontyException,
asyncio::CallId,
bytecode::{FrameExit, VM, VMSnapshot},
exception_private::{RunError, RunResult},
heap::{Heap, HeapReader},
io::PrintWriter,
object::MontyObject,
os::OsFunctionCall,
resource::ResourceTracker,
run::Executor,
};
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::de::DeserializeOwned"))]
pub enum RunProgress<T: ResourceTracker> {
FunctionCall(FunctionCall<T>),
OsCall(OsCall<T>),
ResolveFutures(ResolveFutures<T>),
NameLookup(NameLookup<T>),
Complete(MontyObject),
}
impl<T: ResourceTracker> RunProgress<T> {
#[must_use]
pub fn into_function_call(self) -> Option<FunctionCall<T>> {
match self {
Self::FunctionCall(call) => Some(call),
_ => None,
}
}
#[must_use]
pub fn into_os_call(self) -> Option<OsCall<T>> {
match self {
Self::OsCall(call) => Some(call),
_ => None,
}
}
#[must_use]
pub fn into_complete(self) -> Option<MontyObject> {
match self {
Self::Complete(value) => Some(value),
_ => None,
}
}
#[must_use]
pub fn into_resolve_futures(self) -> Option<ResolveFutures<T>> {
match self {
Self::ResolveFutures(state) => Some(state),
_ => None,
}
}
#[must_use]
pub fn into_name_lookup(self) -> Option<NameLookup<T>> {
match self {
Self::NameLookup(lookup) => Some(lookup),
_ => None,
}
}
}
impl<T: ResourceTracker + serde::Serialize> RunProgress<T> {
pub fn dump(&self) -> Result<Vec<u8>, postcard::Error> {
postcard::to_allocvec(self)
}
}
impl<T: ResourceTracker + DeserializeOwned> RunProgress<T> {
pub fn load(bytes: &[u8]) -> Result<Self, postcard::Error> {
postcard::from_bytes(bytes)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::de::DeserializeOwned"))]
pub struct FunctionCall<T: ResourceTracker> {
pub function_name: String,
pub args: Vec<MontyObject>,
pub kwargs: Vec<(MontyObject, MontyObject)>,
pub call_id: u32,
pub method_call: bool,
snapshot: Snapshot<T>,
}
impl<T: ResourceTracker> FunctionCall<T> {
fn new(
function_name: String,
args: Vec<MontyObject>,
kwargs: Vec<(MontyObject, MontyObject)>,
call_id: u32,
method_call: bool,
snapshot: Snapshot<T>,
) -> Self {
Self {
function_name,
args,
kwargs,
call_id,
method_call,
snapshot,
}
}
pub fn tracker_mut(&mut self) -> &mut T {
self.snapshot.heap.tracker_mut()
}
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress<T>, MontyException> {
self.snapshot.run(result, print)
}
pub fn resume_pending(self, print: PrintWriter<'_>) -> Result<RunProgress<T>, MontyException> {
self.snapshot.run(ExtFunctionResult::Future(self.call_id), print)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::de::DeserializeOwned"))]
pub struct OsCall<T: ResourceTracker> {
pub function_call: OsFunctionCall,
pub call_id: u32,
snapshot: Snapshot<T>,
}
impl<T: ResourceTracker> OsCall<T> {
fn new(function_call: OsFunctionCall, call_id: u32, snapshot: Snapshot<T>) -> Self {
Self {
function_call,
call_id,
snapshot,
}
}
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress<T>, MontyException> {
self.snapshot.run(result.into(), print)
}
#[must_use]
pub fn take_function_call(&mut self) -> OsFunctionCall {
mem::replace(&mut self.function_call, OsFunctionCall::Used)
}
pub fn tracker(&self) -> &T {
self.snapshot.heap.tracker()
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::de::DeserializeOwned"))]
pub struct NameLookup<T: ResourceTracker> {
pub name: String,
namespace_slot: u16,
is_global: bool,
snapshot: Snapshot<T>,
}
impl<T: ResourceTracker> NameLookup<T> {
fn new(name: String, namespace_slot: u16, is_global: bool, snapshot: Snapshot<T>) -> Self {
Self {
name,
namespace_slot,
is_global,
snapshot,
}
}
pub fn resume(
self,
result: impl Into<NameLookupResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress<T>, MontyException> {
let result = result.into();
let Snapshot {
mut heap,
executor,
vm_state: snapshot_vm_state,
} = self.snapshot;
let namespace_slot = self.namespace_slot;
let is_global = self.is_global;
let name = self.name;
let (converted, vm_state) =
HeapReader::with(&mut heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::restore(
snapshot_vm_state,
&executor.module_code,
reader,
&executor.interns,
print.reborrow(),
);
let vm_result = match result {
NameLookupResult::Value(obj) => {
let value = obj
.to_value(&mut vm)
.map_err(|e| MontyException::runtime_error(format!("invalid name lookup result: {e}")))?;
let slot_idx = namespace_slot as usize;
let cloned = value.clone_with_heap(&vm);
let slot = if is_global {
&mut vm.globals[slot_idx]
} else {
let stack_base = vm.current_stack_base();
&mut vm.stack[stack_base + slot_idx]
};
let old = mem::replace(slot, cloned);
old.drop_with_heap(&mut vm);
vm.push(value);
vm.run_external()
}
NameLookupResult::Undefined => {
let err = ExcType::name_error(&name);
vm.resume_with_exception(err.into())
}
};
let converted = convert_frame_exit(vm_result, &mut vm);
let vm_state = check_snapshot_from_converted(&converted, vm);
Ok((converted, vm_state))
})?;
build_run_progress(converted, vm_state, executor, heap)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::de::DeserializeOwned"))]
pub struct ResolveFutures<T: ResourceTracker> {
executor: Executor,
vm_state: VMSnapshot,
heap: Heap<T>,
pending_call_ids: Vec<u32>,
}
impl<T: ResourceTracker> ResolveFutures<T> {
fn new(executor: Executor, vm_state: VMSnapshot, heap: Heap<T>, pending_call_ids: Vec<u32>) -> Self {
Self {
executor,
vm_state,
heap,
pending_call_ids,
}
}
#[must_use]
pub fn pending_call_ids(&self) -> &[u32] {
&self.pending_call_ids
}
#[cfg(feature = "test-hooks")]
#[doc(hidden)]
#[must_use]
pub fn __force_gc_for_tests(self) -> Self {
let Self {
executor,
vm_state,
mut heap,
pending_call_ids,
} = self;
let vm_state = HeapReader::with(&mut heap, &mut &executor, |reader, executor| {
let mut vm = VM::restore(
vm_state,
&executor.module_code,
reader,
&executor.interns,
PrintWriter::Stdout,
);
vm.__force_gc_for_tests();
vm.snapshot()
});
Self::new(executor, vm_state, heap, pending_call_ids)
}
pub fn resume(
self,
results: Vec<(u32, ExtFunctionResult)>,
print: PrintWriter<'_>,
) -> Result<RunProgress<T>, MontyException> {
let Self {
executor,
vm_state,
mut heap,
pending_call_ids,
} = self;
let invalid_call_id = results
.iter()
.find(|(call_id, _)| !pending_call_ids.contains(call_id))
.map(|(call_id, _)| *call_id);
let (converted, vm_state) =
HeapReader::with(&mut heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::restore(
vm_state,
&executor.module_code,
reader,
&executor.interns,
print.reborrow(),
);
if let Some(call_id) = invalid_call_id {
return Err(MontyException::runtime_error(format!(
"unknown call_id {call_id}, expected one of: {pending_call_ids:?}"
)));
}
let result = vm.resume_with_resolved_futures(results);
let converted = convert_frame_exit(result, &mut vm);
let vm_state = check_snapshot_from_converted(&converted, vm);
Ok((converted, vm_state))
})?;
build_run_progress(converted, vm_state, executor, heap)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::de::DeserializeOwned"))]
pub(crate) struct Snapshot<T: ResourceTracker> {
pub(crate) executor: Executor,
pub(crate) vm_state: VMSnapshot,
pub(crate) heap: Heap<T>,
}
impl<T: ResourceTracker> Snapshot<T> {
pub(crate) fn run(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress<T>, MontyException> {
let ext_result = result.into();
let Self {
executor,
vm_state,
mut heap,
} = self;
let (converted, vm_state) =
HeapReader::with(&mut heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::restore(
vm_state,
&executor.module_code,
reader,
&executor.interns,
print.reborrow(),
);
let vm_result = match ext_result {
ExtFunctionResult::Return(obj) => vm.resume(obj),
ExtFunctionResult::Error(exc) => vm.resume_with_exception(exc.into()),
ExtFunctionResult::Future(raw_call_id) => {
let call_id = CallId::new(raw_call_id);
match vm.add_pending_call(call_id) {
Ok(()) => vm.run_external(),
Err(err) => vm.resume_with_exception(err),
}
}
ExtFunctionResult::NotFound(function_name) => {
vm.resume_with_exception(ExtFunctionResult::not_found_exc(&function_name))
}
};
let converted = convert_frame_exit(vm_result, &mut vm);
let vm_state = check_snapshot_from_converted(&converted, vm);
(converted, vm_state)
});
build_run_progress(converted, vm_state, executor, heap)
}
}
#[derive(Debug)]
pub enum NameLookupResult {
Value(MontyObject),
Undefined,
}
impl From<MontyObject> for NameLookupResult {
fn from(value: MontyObject) -> Self {
Self::Value(value)
}
}
#[derive(Debug)]
pub enum ExtFunctionResult {
Return(MontyObject),
Error(MontyException),
Future(u32),
NotFound(String),
}
impl ExtFunctionResult {
pub(crate) fn not_found_exc(function_name: &str) -> RunError {
let msg = format!("name '{function_name}' is not defined");
MontyException::new(ExcType::NameError, Some(msg)).into()
}
}
impl From<MontyObject> for ExtFunctionResult {
fn from(value: MontyObject) -> Self {
Self::Return(value)
}
}
impl From<MontyException> for ExtFunctionResult {
fn from(exception: MontyException) -> Self {
Self::Error(exception)
}
}
pub(crate) enum ConvertedExit {
Complete(MontyObject),
FunctionCall {
function_name: String,
args: Vec<MontyObject>,
kwargs: Vec<(MontyObject, MontyObject)>,
call_id: u32,
method_call: bool,
},
OsCall {
function_call: OsFunctionCall,
call_id: u32,
},
ResolveFutures(Vec<u32>),
NameLookup {
name: String,
namespace_slot: u16,
is_global: bool,
},
Error(RunError),
}
impl ConvertedExit {
pub(crate) fn needs_snapshot(&self) -> bool {
!matches!(self, Self::Complete(_) | Self::Error(_))
}
}
pub(crate) fn convert_frame_exit(result: RunResult<FrameExit>, vm: &mut VM<'_, impl ResourceTracker>) -> ConvertedExit {
match result {
Ok(FrameExit::Return(value)) => ConvertedExit::Complete(MontyObject::new(value, vm)),
Ok(FrameExit::ExternalCall {
function_name,
args,
call_id,
..
}) => {
let name = function_name.into_string(vm.interns);
let (args_py, kwargs_py) = args.into_py_objects(vm);
ConvertedExit::FunctionCall {
function_name: name,
args: args_py,
kwargs: kwargs_py,
call_id: call_id.raw(),
method_call: false,
}
}
Ok(FrameExit::OsCall { function_call, call_id }) => ConvertedExit::OsCall {
function_call,
call_id: call_id.raw(),
},
Ok(FrameExit::MethodCall {
method_name,
args,
call_id,
}) => {
let name = method_name.into_string(vm.interns);
let (args_py, kwargs_py) = args.into_py_objects(vm);
ConvertedExit::FunctionCall {
function_name: name,
args: args_py,
kwargs: kwargs_py,
call_id: call_id.raw(),
method_call: true,
}
}
Ok(FrameExit::ResolveFutures(pending_call_ids)) => {
ConvertedExit::ResolveFutures(pending_call_ids.iter().map(|id| id.raw()).collect())
}
Ok(FrameExit::NameLookup {
name_id,
namespace_slot,
is_global,
}) => {
let name = vm.interns.get_str(name_id).to_owned();
ConvertedExit::NameLookup {
name,
namespace_slot,
is_global,
}
}
Err(err) => ConvertedExit::Error(err),
}
}
pub(crate) fn check_snapshot_from_converted(
converted: &ConvertedExit,
vm: VM<'_, impl ResourceTracker>,
) -> Option<VMSnapshot> {
if converted.needs_snapshot() {
Some(vm.snapshot())
} else {
None
}
}
pub(crate) fn build_run_progress<T: ResourceTracker>(
converted: ConvertedExit,
vm_state: Option<VMSnapshot>,
executor: Executor,
heap: Heap<T>,
) -> Result<RunProgress<T>, MontyException> {
macro_rules! new_snapshot {
() => {
Snapshot {
executor,
vm_state: vm_state.expect("snapshot should exist"),
heap,
}
};
}
match converted {
ConvertedExit::Complete(obj) => Ok(RunProgress::Complete(obj)),
ConvertedExit::FunctionCall {
function_name,
args,
kwargs,
call_id,
method_call,
} => Ok(RunProgress::FunctionCall(FunctionCall::new(
function_name,
args,
kwargs,
call_id,
method_call,
new_snapshot!(),
))),
ConvertedExit::OsCall { function_call, call_id } => Ok(RunProgress::OsCall(OsCall::new(
function_call,
call_id,
new_snapshot!(),
))),
ConvertedExit::ResolveFutures(pending_call_ids) => Ok(RunProgress::ResolveFutures(ResolveFutures::new(
executor,
vm_state.expect("snapshot should exist for ResolveFutures"),
heap,
pending_call_ids,
))),
ConvertedExit::NameLookup {
name,
namespace_slot,
is_global,
} => Ok(RunProgress::NameLookup(NameLookup::new(
name,
namespace_slot,
is_global,
new_snapshot!(),
))),
ConvertedExit::Error(err) => {
Err(err.into_python_exception(&executor.interns, |_| Some(executor.code.as_str())))
}
}
}