feat: Promise 基础子系统 + 微任务队列(resolve/reject/then/catch/finally + run() drain)

This commit is contained in:
2026-08-09 21:08:53 +08:00
parent 464712f7ba
commit 538a5670dc
11 changed files with 1265 additions and 40 deletions
+26 -10
View File
@@ -175,7 +175,7 @@ fn eval(code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>) -> ExitCode {
let mut guard = pool.spawn();
match guard.vm_mut().run(&module) {
Ok(result) => {
format_result(kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result);
format_result(guard.vm(), kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result);
ExitCode::SUCCESS
}
Err(err) => {
@@ -186,23 +186,35 @@ fn eval(code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>) -> ExitCode {
}
}
fn format_result(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) {
println!("{}", format_js_value(string_forge, shape_forge, val));
fn format_result(vm: &oxide_vm::vm::Vm, string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) {
println!("{}", format_js_value(vm, string_forge, shape_forge, val));
}
fn format_js_value(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) -> String {
fn format_js_value(
vm: &oxide_vm::vm::Vm, string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue,
) -> String {
if val.is_string() {
// SAFETY: val 已确认是字符串值。
let s = unsafe { (*val.as_string_ptr()).data.clone() };
format!("\"{s}\"")
} else if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_promise_obj() {
// 已 settle 的 Promise 打印其结算值(便于 eval 观察微任务结果)。
return match oxide_vm::promise::promise_settled_value(obj) {
Some((true, v)) => format_js_value(vm, string_forge, shape_forge, v),
Some((false, v)) => {
format!("Promise {{ <rejected> {} }}", format_js_value(vm, string_forge, shape_forge, v))
}
None => "Promise { <pending> }".to_string(),
};
}
if obj.is_function() {
"[Function]".to_string()
} else if obj.is_array() {
format_array(string_forge, shape_forge, obj)
format_array(vm, string_forge, shape_forge, obj)
} else {
format_object(string_forge, shape_forge, obj)
format_object(vm, string_forge, shape_forge, obj)
}
} else if val.is_undefined() {
"undefined".to_string()
@@ -211,7 +223,9 @@ fn format_js_value(string_forge: &PermInterner, shape_forge: &ShapeForge, val: J
}
}
fn format_object(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject) -> String {
fn format_object(
vm: &oxide_vm::vm::Vm, string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject,
) -> String {
let mut entries = Vec::new();
let shape_id = obj.shape_id();
let mut shape_ids = Vec::new();
@@ -239,7 +253,7 @@ fn format_object(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &Js
continue;
}
let name = string_forge.lookup(shape.property_name).unwrap_or_default();
let val_str = format_js_value(string_forge, shape_forge, prop_val);
let val_str = format_js_value(vm, string_forge, shape_forge, prop_val);
entries.push(format!("\"{name}\": {val_str}"));
}
}
@@ -248,12 +262,14 @@ fn format_object(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &Js
format!("{{{}}}", entries.join(", "))
}
fn format_array(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject) -> String {
fn format_array(
vm: &oxide_vm::vm::Vm, string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject,
) -> String {
let len = obj.prop_vec_len();
let mut items = Vec::new();
for i in 0..len {
let val = obj.get_prop_at(i);
items.push(format_js_value(string_forge, shape_forge, val));
items.push(format_js_value(vm, string_forge, shape_forge, val));
}
format!("[{}]", items.join(", "))
}
+7
View File
@@ -400,6 +400,8 @@ impl JsObject {
pub const OBJ_TYPE_CONSTRUCTOR: u8 = 9;
/// 生成器迭代器对象(`function*` 调用返回):状态快照存于 `native_data`。
pub const OBJ_TYPE_GENERATOR: u8 = 10;
/// Promise 对象:状态盒(Pending/Fulfilled/Rejected + reactions)存于 `native_data`。
pub const OBJ_TYPE_PROMISE: u8 = 11;
/// `is_session_epoch` 字段中的 session 标记位。
pub const SESSION_EPOCH_BIT: u8 = 0x01;
/// `is_session_epoch` 字段中的 GC 标记位。
@@ -450,6 +452,11 @@ impl JsObject {
pub fn is_generator_obj(&self) -> bool {
self.type_tag == Self::OBJ_TYPE_GENERATOR
}
/// 是否 Promise 对象。
#[inline]
pub fn is_promise_obj(&self) -> bool {
self.type_tag == Self::OBJ_TYPE_PROMISE
}
/// 构造无属性、可扩展的空对象(`new Object()` 的基础对象)。
pub fn new_empty(shape_id: ShapeId, proto: JsValue) -> Self {
+31 -28
View File
@@ -459,36 +459,36 @@ impl Vm {
} else {
// 注入模式:next(v) 写 reg 0;throw(e) 恢复异常上下文;return(v) 完成穿越。
match mode {
GeneratorResumeMode::Next(arg) => {
self.regs[0] = arg;
}
GeneratorResumeMode::Throw(exc) => {
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
if self.unwind().is_err() {
self.restore_inline_state(saved);
self.native_call_depth -= 1;
let thrown = self.last_uncaught_value.take().unwrap_or(exc);
unsafe { (*state_ptr).phase = GeneratorPhase::Completed };
return Ok(GeneratorStep::Thrown { value: thrown });
GeneratorResumeMode::Next(arg) => {
self.regs[0] = arg;
}
}
GeneratorResumeMode::Return(value) => {
// 等价于在挂起点执行 return:穿越 finally 或直接交付返回值。
// 直接交付路径走 do_return(帧弹出后需按内嵌 dispatch 语义返回)。
let prev_dispatch = self.generator_dispatch;
self.generator_dispatch = true;
let completed = self.complete_generator_return(value);
self.generator_dispatch = prev_dispatch;
if let Some(completed) = completed? {
self.restore_inline_state(saved);
self.native_call_depth -= 1;
let state = unsafe { &mut *state_ptr };
state.phase = GeneratorPhase::Completed;
state.result = completed;
return Ok(GeneratorStep::Completed { value: completed });
GeneratorResumeMode::Throw(exc) => {
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
if self.unwind().is_err() {
self.restore_inline_state(saved);
self.native_call_depth -= 1;
let thrown = self.last_uncaught_value.take().unwrap_or(exc);
unsafe { (*state_ptr).phase = GeneratorPhase::Completed };
return Ok(GeneratorStep::Thrown { value: thrown });
}
}
GeneratorResumeMode::Return(value) => {
// 等价于在挂起点执行 return:穿越 finally 或直接交付返回值。
// 直接交付路径走 do_return(帧弹出后需按内嵌 dispatch 语义返回)。
let prev_dispatch = self.generator_dispatch;
self.generator_dispatch = true;
let completed = self.complete_generator_return(value);
self.generator_dispatch = prev_dispatch;
if let Some(completed) = completed? {
self.restore_inline_state(saved);
self.native_call_depth -= 1;
let state = unsafe { &mut *state_ptr };
state.phase = GeneratorPhase::Completed;
state.result = completed;
return Ok(GeneratorStep::Completed { value: completed });
}
}
}
}
}
}
@@ -1039,6 +1039,9 @@ pub(crate) fn rewrite_generator_native(obj: &JsObject, mut rewrite: impl FnMut(J
/// 释放生成器状态盒(对象被 GC 回收时),返回释放字节数。
pub(crate) fn drop_generator_native(obj: &JsObject) -> u64 {
if !obj.is_generator_obj() {
return 0;
}
let ptr = obj.native_data() as *mut GeneratorState;
if ptr.is_null() {
return 0;
+2
View File
@@ -14,6 +14,8 @@ mod generator;
mod ic_helper;
/// native 函数签名类型([`native::NativeFn`]),builtin 绑定与 VM 调用约定依赖它。
pub mod native;
/// Promise 运行时(状态盒 + 微任务队列 + 构造器/方法实现)。
pub mod promise;
mod session_arena;
/// session 级 mark-sweep GC:标记-清扫 session arena 对象与 session 字符串。
pub mod session_gc;
File diff suppressed because it is too large Load Diff
+5
View File
@@ -70,6 +70,11 @@ impl Vm {
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)
});
}
dst
}
+19
View File
@@ -92,6 +92,9 @@ impl SessionGc {
if obj.is_generator_obj() {
edges.extend(crate::generator::generator_native_edges(obj));
}
if obj.is_promise_obj() {
edges.extend(crate::promise::promise_native_edges(obj));
}
// 遍历 upvalue cell 中的对象引用。
for cell_ptr in obj.upvalues_slice() {
if cell_ptr.is_null() {
@@ -154,6 +157,13 @@ impl SessionGc {
}
}
}
if obj.is_promise_obj() {
for value in crate::promise::promise_native_edges(obj) {
if value.is_string() {
live.insert(value.as_string_ptr_mut());
}
}
}
}
pub(crate) fn mark(&mut self, vm: &Vm) {
@@ -264,6 +274,7 @@ impl SessionGc {
freed_bytes += typed_array::drop_typed_array_native(obj);
freed_bytes += data_view::drop_data_view_native(obj);
freed_bytes += crate::generator::drop_generator_native(obj);
freed_bytes += crate::promise::drop_promise_native(obj);
freed_bytes
}
@@ -320,6 +331,8 @@ impl SessionGc {
typed_array::clone_typed_array_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_data_view_obj() {
data_view::clone_data_view_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_promise_obj() {
crate::promise::clone_promise_native_with_rewrite(old_ref, new_ref, |value| value);
}
forwarding.insert(old_ptr, new_ptr);
freed_bytes += Self::drop_session_object_heap_data(old_ptr);
@@ -351,6 +364,8 @@ impl SessionGc {
data_view::rewrite_data_view_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
} else if obj.is_generator_obj() {
crate::generator::rewrite_generator_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
} else if obj.is_promise_obj() {
crate::promise::rewrite_promise_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
}
}
@@ -533,6 +548,10 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
*value = rewrite_forwarded_value(*value, forwarding);
}
vm.iters.last_for_of_result = rewrite_forwarded_value(vm.iters.last_for_of_result, forwarding);
// 微任务队列中的值随 sweep 重写。
for job in &mut vm.job_queue {
crate::promise::rewrite_job_values(job, |value| rewrite_forwarded_value(value, forwarding));
}
// 已转换的不可变常量不是 session 值(标量 + perm 字符串)——不重写。
for iter in &mut vm.iters.for_in_iters {
if iter.is_null() {
+11
View File
@@ -1,5 +1,6 @@
#![allow(clippy::arc_with_non_send_sync)]
use std::collections::VecDeque;
use std::sync::{Arc, OnceLock};
use oxide_bytecode::module::{CompiledModule, Constant};
@@ -244,6 +245,12 @@ pub struct Vm {
/// `%GeneratorFunction.prototype%`:生成器函数对象的原型(`constructor` 指向
/// `%GeneratorFunction%`,使 `g.constructor.name` 解析为 "GeneratorFunction")。
pub generator_function_proto: P<JsObject>,
/// `%Promise%` 构造器(resolve/reject 静态方法挂此,global 的 Promise 槽指向它)。
pub promise_constructor: P<JsObject>,
/// `%Promise.prototype%`:Promise 实例的原型(then/catch/finally 方法挂此)。
pub promise_proto: P<JsObject>,
/// 微任务队列(Promise reactions / thenable 委托),`run()` 末尾 FIFO drain。
pub(crate) job_queue: VecDeque<crate::promise::Microtask>,
pub math_rng_state: u64,
/// 全局扁平模块表:下标 = 模块 `flat_id`(顶层 0,子模块 flatten 后全局唯一)。
/// 闭包 `sub_module_index` 即 flat_id,逃逸闭包也能自足解析。
@@ -526,6 +533,10 @@ impl Vm {
f(v);
}
f(self.iters.last_for_of_result);
// 微任务队列中的处理器/能力/值都是 GC 根。
for job in &self.job_queue {
crate::promise::for_each_job_value(job, &mut f);
}
// 已转换的不可变常量(标量 + perm 字符串)不作为 session GC 根,不参与扫描。
for iter in &self.iters.for_in_iters {
if iter.is_null() {
+4 -1
View File
@@ -236,7 +236,10 @@ impl Vm {
let global = self.session.global_object();
self.regs[254] = JsValue::from_js_object(global.as_ptr() as *mut JsObject);
self.dispatch()
let result = self.dispatch();
// 顶层执行结束后 drain 微任务队列:Promise reactions 与 thenable 委托在此执行。
self.drain_job_queue();
result
}
pub(crate) fn unwind(&mut self) -> Result<(), String> {
+20 -1
View File
@@ -1,5 +1,6 @@
#![allow(clippy::arc_with_non_send_sync)]
use std::collections::VecDeque;
use std::sync::Arc;
use oxide_bytecode::module::Constant;
@@ -34,6 +35,9 @@ impl Vm {
object_prototype: obj_proto,
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_constructor: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
job_queue: VecDeque::new(),
math_rng_state: 0,
sub_modules: Arc::new(Vec::new()),
saved_bytecode_stack: Vec::new(),
@@ -86,6 +90,9 @@ impl Vm {
cell_stack: Vec::new(),
};
vm.init_generator_intrinsics();
vm.init_promise_intrinsics();
// Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。
vm.session.record_snapshot();
vm_info!("Vm created");
vm
}
@@ -108,6 +115,9 @@ impl Vm {
object_prototype: obj_proto,
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_constructor: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
job_queue: VecDeque::new(),
math_rng_state: 0,
sub_modules: Arc::new(Vec::new()),
saved_bytecode_stack: Vec::new(),
@@ -160,6 +170,9 @@ impl Vm {
cell_stack: Vec::new(),
};
vm.init_generator_intrinsics();
vm.init_promise_intrinsics();
// Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。
vm.session.record_snapshot();
vm_info!("Vm created (pool)");
vm
}
@@ -185,9 +198,11 @@ impl Vm {
let global = unsafe { &mut *global_ptr };
bindings::bind_global_builtin_slots(&self.kernel_core, &self.session, global);
}
self.session.record_snapshot();
self.object_prototype = P::clone(&self.session.builtin_world().object_proto);
self.init_generator_intrinsics();
self.init_promise_intrinsics();
// 快照须在 Promise 全局绑定之后采集:绑定会修改 global 世代。
self.session.record_snapshot();
self.clear_full_reset_state();
vm_info!("full_reset completed");
}
@@ -199,6 +214,8 @@ impl Vm {
bindings::init_kernel_builtins(&self.kernel_core, &mut self.session);
self.object_prototype = P::clone(&self.session.builtin_world().object_proto);
self.init_generator_intrinsics();
self.init_promise_intrinsics();
self.session.record_snapshot();
self.clear_full_reset_state();
}
@@ -258,6 +275,8 @@ impl Vm {
self.generator_init_step = false;
self.generator_body_started = false;
self.native_call_depth = 0;
// 微任务队列是执行期状态:跨 run 不保留。
self.job_queue.clear();
}
/// 轻量重置:清空执行状态并回收 epoch 内存,但保留 session 字符串与 builtin。
+101
View File
@@ -0,0 +1,101 @@
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::promise::promise_settled_value;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&module)
}
/// 顶层表达式返回 Promise 时,run() 末尾 drain 已执行微任务,读其结算值。
fn settled(vm: &mut Vm, source: &str) -> (bool, JsValue) {
let result = eval(vm, source).expect("run should succeed");
assert!(result.is_object(), "expected a promise, got {result:?}");
let obj = unsafe { &*result.as_js_object_ptr() };
promise_settled_value(obj).expect("promise should be settled")
}
#[test]
fn resolve_then_drains_chain() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve(42).then(v => v * 2)");
assert!(ok);
assert_eq!(val.as_double(), 84.0);
}
#[test]
fn chain_orders_fifo() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve(1).then(v => v + 1).then(v => v * 10)");
assert!(ok);
assert_eq!(val.as_double(), 20.0);
}
#[test]
fn settle_only_once() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "new Promise((res, rej) => { res(1); res(2); rej(3); }).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 1);
}
#[test]
fn throw_in_handler_goes_to_catch() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve(1).then(v => { throw 'x' }).catch(e => e)");
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("x"));
}
#[test]
fn thenable_delegation() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve({ then: function(r) { r(7) } }).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 7);
}
#[test]
fn finally_passes_through() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "new Promise(r => { r(10) }).finally(() => {}).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 10);
}
#[test]
fn reject_propagates_through_empty_then() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.reject('e').then(undefined, e => 'caught:' + e)");
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("caught:e"));
}
#[test]
fn ctor_receiver_used_for_capability() {
let mut vm = Vm::new();
// Promise.resolve.call(NotPromise) 应构造 NotPromise 并捕获其 executor。
let result = eval(
&mut vm,
"var ef; function NotP(exec) { ef = exec; exec(function(){}, function(){}); } \
Promise.resolve.call(NotP); ef.length",
)
.unwrap();
assert_eq!(result.as_int(), 2);
}
#[test]
fn custom_ctor_failure_is_type_error() {
let mut vm = Vm::new();
// executor 未捕获 resolve/reject → TypeError。
let result = eval(
&mut vm,
"function NotP(exec) { exec(function(){}); } \
try { Promise.resolve.call(NotP); 'no-throw' } catch (e) { e.name }",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("TypeError"));
}