89a4e1bb86
- emit 识别 generator+async 组合,模块同时标记 is_generator/is_async - 新增 AsyncGeneratorState:帧快照 + 请求队列 + 当前请求 - next/return/throw 返回 Promise,请求入队逐条处理 - YIELD 让出经 promise unwrap 后交付(yield rejected promise 关闭生成器) - AWAIT 挂起保留当前请求,微任务恢复后继续执行 - %AsyncGeneratorPrototype% / %AsyncGeneratorFunction.prototype% 内建 - session GC / promote 支持异步生成器状态盒 - 普通 async 函数嵌套调用隔离外层异步生成器上下文
277 lines
11 KiB
Rust
277 lines
11 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use crate::vm_debug;
|
|
use oxide_types::object::JsObject;
|
|
use oxide_types::value::JsValue;
|
|
use rustc_hash::FxBuildHasher;
|
|
|
|
use crate::vm::Vm;
|
|
use oxide_builtins::{data_view, map, set, typed_array};
|
|
|
|
impl Vm {
|
|
pub(crate) fn is_session_escape_root_ptr(&self, target_ptr: *mut JsObject) -> bool {
|
|
if target_ptr.is_null() {
|
|
return false;
|
|
}
|
|
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
|
|
std::ptr::eq(target_ptr, global_ptr) || unsafe { (&*target_ptr).is_session_epoch() }
|
|
}
|
|
|
|
pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject {
|
|
vm_debug!("promote_object: src={:p}", src);
|
|
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
|
let result = self.promote_object_inner(src, &mut forwarding);
|
|
forwarding.clear();
|
|
self.gc_state.forwarding = forwarding;
|
|
result
|
|
}
|
|
|
|
pub(crate) fn promote_object_inner(
|
|
&mut self, src: *mut JsObject, forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
|
) -> *mut JsObject {
|
|
vm_debug!("promote_object_inner: src={:p}", src);
|
|
if src.is_null() {
|
|
return src;
|
|
}
|
|
let src_ref = unsafe { &*src };
|
|
if src_ref.is_session_epoch() || !self.epoch.is_epoch_ptr(src.cast::<u8>()) {
|
|
return src;
|
|
}
|
|
if let Some(dst) = forwarding.get(&src).copied() {
|
|
return dst;
|
|
}
|
|
|
|
let clone = src_ref.clone_for_session_epoch();
|
|
let dst = self.gc_state.session_epoch.alloc(clone) as *mut JsObject;
|
|
forwarding.insert(src, dst);
|
|
self.gc_state.session_object_ptrs.push(dst);
|
|
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsObject>();
|
|
|
|
let dst_ref = unsafe { &mut *dst };
|
|
dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding));
|
|
if src_ref.is_map() {
|
|
map::clone_map_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_set() {
|
|
set::clone_set_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_typed_array_obj() {
|
|
typed_array::clone_typed_array_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_data_view_obj() {
|
|
data_view::clone_data_view_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_generator_obj() {
|
|
// 生成器状态盒为堆分配、promote 后与源共享同一盒:就地改写其 JsValue。
|
|
crate::generator::rewrite_generator_native(dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_promise_obj() {
|
|
// Promise 状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
|
crate::promise::clone_promise_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_async_obj() {
|
|
// 异步状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
|
crate::async_func::clone_async_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
} else if src_ref.is_async_generator_obj() {
|
|
// 异步生成器状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
|
crate::async_generator::clone_async_generator_native_with_rewrite(src_ref, dst_ref, |value| {
|
|
self.promote_value_if_epoch_object(value, forwarding)
|
|
});
|
|
}
|
|
dst
|
|
}
|
|
|
|
pub(crate) fn promote_value_if_epoch_object(
|
|
&mut self, value: JsValue, forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
|
) -> JsValue {
|
|
if !value.is_object() {
|
|
return value;
|
|
}
|
|
let ptr = value.as_js_object_ptr();
|
|
if ptr.is_null() || !self.epoch.is_epoch_ptr(ptr.cast::<u8>()) {
|
|
return value;
|
|
}
|
|
JsValue::from_js_object(self.promote_object_inner(ptr, forwarding))
|
|
}
|
|
|
|
pub(crate) fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue {
|
|
if !value.is_object() || !self.is_session_escape_root_ptr(target_ptr) {
|
|
return value;
|
|
}
|
|
let value_ptr = value.as_js_object_ptr();
|
|
if value_ptr.is_null() || !self.epoch.is_epoch_ptr(value_ptr.cast::<u8>()) {
|
|
return value;
|
|
}
|
|
JsValue::from_js_object(self.promote_object(value_ptr))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
|
use oxide_types::object::{PropAttributes, PropMetaEntry};
|
|
|
|
fn plain_object(vm: &mut Vm) -> *mut JsObject {
|
|
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
|
vm.epoch
|
|
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)))
|
|
}
|
|
|
|
fn is_epoch_object(vm: &Vm, value: JsValue) -> bool {
|
|
value.is_object() && vm.epoch.is_epoch_ptr(value.as_js_object_ptr().cast::<u8>())
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_promotion_preserves_cycle_identity() {
|
|
let mut vm = Vm::new();
|
|
let root = plain_object(&mut vm);
|
|
unsafe {
|
|
(*root).set_prop_at(0, JsValue::from_js_object(root));
|
|
}
|
|
|
|
let promoted = vm.promote_object(root);
|
|
let promoted_obj = unsafe { &*promoted };
|
|
|
|
assert!(promoted_obj.is_session_epoch());
|
|
assert!(std::ptr::eq(promoted_obj.get_prop_at(0).as_js_object_ptr(), promoted));
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_promotion_preserves_duplicate_child_reference() {
|
|
let mut vm = Vm::new();
|
|
let root = plain_object(&mut vm);
|
|
let child = plain_object(&mut vm);
|
|
unsafe {
|
|
(*root).set_prop_at(0, JsValue::from_js_object(child));
|
|
(*root).set_prop_at(1, JsValue::from_js_object(child));
|
|
}
|
|
|
|
let promoted = vm.promote_object(root);
|
|
let promoted_obj = unsafe { &*promoted };
|
|
let left = promoted_obj.get_prop_at(0).as_js_object_ptr();
|
|
let right = promoted_obj.get_prop_at(1).as_js_object_ptr();
|
|
|
|
assert!(std::ptr::eq(left, right));
|
|
assert!(!std::ptr::eq(left, child));
|
|
assert!(unsafe { (&*left).is_session_epoch() });
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_captured_and_home_object_links_promote() {
|
|
let mut vm = Vm::new();
|
|
let root = plain_object(&mut vm);
|
|
let proto = plain_object(&mut vm);
|
|
let captured = plain_object(&mut vm);
|
|
let home = plain_object(&mut vm);
|
|
let getter = plain_object(&mut vm);
|
|
let setter = plain_object(&mut vm);
|
|
unsafe {
|
|
(*root).set_proto(JsValue::from_js_object(proto)).expect("proto");
|
|
(*root).set_captured_this(JsValue::from_js_object(captured));
|
|
(*root).set_home_object(JsValue::from_js_object(home));
|
|
(*root).set_prop_at(0, JsValue::undefined());
|
|
(*root).set_accessor_meta(
|
|
0,
|
|
JsValue::from_js_object(getter),
|
|
JsValue::from_js_object(setter),
|
|
PropAttributes::DEFAULT_DATA,
|
|
);
|
|
}
|
|
|
|
let promoted = vm.promote_object(root);
|
|
let promoted_obj = unsafe { &*promoted };
|
|
let meta = promoted_obj.prop_meta_at(0).expect("meta");
|
|
|
|
assert!(!is_epoch_object(&vm, promoted_obj.proto()));
|
|
assert!(!is_epoch_object(&vm, promoted_obj.captured_this()));
|
|
assert!(!is_epoch_object(&vm, promoted_obj.home_object()));
|
|
assert!(!is_epoch_object(&vm, meta.get));
|
|
assert!(!is_epoch_object(&vm, meta.set));
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_barrier_promotes_global_root_write() {
|
|
let mut vm = Vm::new();
|
|
let value = JsValue::from_js_object(plain_object(&mut vm));
|
|
let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject;
|
|
|
|
let promoted = vm.promote_if_needed_for_write_ptr(global_ptr, value);
|
|
|
|
assert!(promoted.is_object());
|
|
assert!(!is_epoch_object(&vm, promoted));
|
|
assert!(unsafe { (&*promoted.as_js_object_ptr()).is_session_epoch() });
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_barrier_promotes_already_session_target_write() {
|
|
let mut vm = Vm::new();
|
|
let target_epoch = plain_object(&mut vm);
|
|
let target = vm.promote_object(target_epoch);
|
|
let value = JsValue::from_js_object(plain_object(&mut vm));
|
|
|
|
let promoted = vm.promote_if_needed_for_write_ptr(target, value);
|
|
|
|
assert!(!is_epoch_object(&vm, promoted));
|
|
assert!(unsafe { (&*promoted.as_js_object_ptr()).is_session_epoch() });
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_barrier_leaves_non_escape_target_write_unchanged() {
|
|
let mut vm = Vm::new();
|
|
let target = plain_object(&mut vm);
|
|
let value = JsValue::from_js_object(plain_object(&mut vm));
|
|
|
|
let unchanged = vm.promote_if_needed_for_write_ptr(target, value);
|
|
|
|
assert_eq!(unchanged, value);
|
|
}
|
|
|
|
#[test]
|
|
fn session_arena_promotion_rewrites_accessor_metadata_values() {
|
|
let mut vm = Vm::new();
|
|
let root = plain_object(&mut vm);
|
|
let getter = plain_object(&mut vm);
|
|
let setter = plain_object(&mut vm);
|
|
unsafe {
|
|
(*root).set_prop_at(0, JsValue::undefined());
|
|
(*root).set_accessor_meta(
|
|
0,
|
|
JsValue::from_js_object(getter),
|
|
JsValue::from_js_object(setter),
|
|
PropAttributes::DEFAULT_DATA,
|
|
);
|
|
}
|
|
|
|
let promoted = vm.promote_object(root);
|
|
let meta: PropMetaEntry = unsafe { &*promoted }.prop_meta_at(0).expect("meta");
|
|
|
|
assert!(!is_epoch_object(&vm, meta.get));
|
|
assert!(!is_epoch_object(&vm, meta.set));
|
|
}
|
|
|
|
#[test]
|
|
fn promote_clears_forwarding_map() {
|
|
let mut vm = Vm::new();
|
|
let first = plain_object(&mut vm);
|
|
let promoted = vm.promote_object(first);
|
|
assert!(!promoted.is_null());
|
|
// 共享 forwarding 表必须在每次 promote 后清空,使后续 promote(或 GC 清扫)
|
|
// 永不观察到过期的 old->new 映射。
|
|
assert!(vm.gc_state.forwarding.is_empty());
|
|
|
|
let second = plain_object(&mut vm);
|
|
let promoted2 = vm.promote_object(second);
|
|
assert!(!promoted2.is_null());
|
|
assert!(vm.gc_state.forwarding.is_empty());
|
|
}
|
|
}
|