feat: suspend managed calls on generic awaitable operands

This commit is contained in:
2026-10-23 20:14:15 +08:00
parent b33d322690
commit 16816c38c7
9 changed files with 90 additions and 10 deletions
+1 -1
View File
@@ -394,7 +394,7 @@ fn verify(
| Opcode::StoreDeref
| Opcode::PopJumpIfFalse
| Opcode::PopJumpIfTrue => (1, 1, 0),
Opcode::ToBool | Opcode::UnaryNot | Opcode::LoadAttr => (1, 1, 1),
Opcode::ToBool | Opcode::UnaryNot | Opcode::LoadAttr | Opcode::Await => (1, 1, 1),
Opcode::CompareOp => (2, 2, 1),
Opcode::StoreAttr => (2, 2, 0),
Opcode::Copy => (argument, 0, 1),
+1
View File
@@ -123,6 +123,7 @@ impl ObjectStore {
let argument = decoded.instruction.argument as usize;
let mut stack = states[index].clone().expect("reachable effect state");
match opcode {
Opcode::Await => return Ok(None),
Opcode::LoadConst => stack.push(Fact::value(code.constants()[argument].clone())),
Opcode::LoadFast => stack.push(if argument == 0 {
receiver.map(Fact::Object).unwrap_or(Fact::Unknown)
+4
View File
@@ -4,6 +4,8 @@ use std::fmt;
/// retained for embedders; defining it here avoids a dependency on the VM.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VmError {
AwaitFailed(String),
AwaitCancelled,
ForeignHandle,
InvalidHandle,
IdentityExhausted,
@@ -49,6 +51,8 @@ pub enum VmError {
impl fmt::Display for VmError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AwaitFailed(message) => write!(f, "await failed: {message}"),
Self::AwaitCancelled => f.write_str("await cancelled"),
Self::ForeignHandle => f.write_str("handle belongs to another VM"),
Self::InvalidHandle => f.write_str("handle does not identify a live object"),
Self::IdentityExhausted => f.write_str("process-local VM identities exhausted"),
+3
View File
@@ -35,6 +35,7 @@ pub enum Opcode {
BuildMap = 27,
MakeFunction = 28,
BuildClass = 29,
Await = 30,
ExtendedArg = 255,
}
@@ -71,6 +72,7 @@ impl Opcode {
27 => Self::BuildMap,
28 => Self::MakeFunction,
29 => Self::BuildClass,
30 => Self::Await,
255 => Self::ExtendedArg,
_ => return None,
})
@@ -85,6 +87,7 @@ impl Opcode {
| Self::ReturnValue
| Self::ToBool
| Self::UnaryNot
| Self::Await
| Self::BuildClass
)
}
+2 -2
View File
@@ -43,8 +43,8 @@ pub use number::{
EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational, EndorReal,
};
pub use object::{
CompareOp, EndorNativeCall, EndorNativeFunctionObject, EndorObject, NativeCall, NativeFunction,
ObjectHeader, ObjectPayload, Record, Value,
AwaitPoll, CompareOp, EndorNativeCall, EndorNativeFunctionObject, EndorObject, NativeCall,
NativeFunction, ObjectHeader, ObjectPayload, Record, Value,
};
pub use store::ObjectStore;
pub use types::{
+12
View File
@@ -42,6 +42,14 @@ impl CompareOp {
}
}
/// Non-blocking awaitable observation. Ready object values are borrowed from
/// the managed graph and must be retained by the consumer before dropping it.
#[derive(Debug)]
pub enum AwaitPoll {
Pending,
Ready(Value),
}
/// Rust-side operation interface, not a language type or metaclass.
///
/// Implementations must report every payload-owned object reference in trace(),
@@ -51,6 +59,10 @@ impl CompareOp {
/// Rust Drop releases host resources when the payload is destroyed.
/// Equal payloads must provide compatible hashes, or reject hashing explicitly.
pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
fn poll_await(&self) -> Result<AwaitPoll, VmError> {
Err(VmError::UnsupportedOperation("await"))
}
/// Explicit function category; callable Type objects are not functions.
fn is_function(&self) -> bool {
false