diff --git a/crates/oxide_vm/src/promise/aggregate.rs b/crates/oxide_vm/src/promise/aggregate.rs index 30dd28bb..cc800e83 100644 --- a/crates/oxide_vm/src/promise/aggregate.rs +++ b/crates/oxide_vm/src/promise/aggregate.rs @@ -1,21 +1,27 @@ -//! Promise 静态聚合方法(all/race/allSettled/any/withResolvers)与 +//! Promise 静态聚合方法(all/race/allSettled/any/allKeyed 族/withResolvers)与 //! AggregateError 内建初始化。 //! //! 元素处理器共享结算计数记录(剩余计数 / 结果数组 / 能力闭包); //! 计数从 1 起步,尾部哨兵计入整趟迭代。 +//! keyed 族(allKeyed / allSettledKeyed)与 all 族共用能力 / 元素函数 / +//! 结算路径,差异仅在元素来源(own keys 枚举替代迭代器)与结果承载 +//! (null 原型键控对象替代数组)。 use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_runtime_api::NativeResult; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; -use oxide_types::private_key::make_int_key; +use oxide_types::private_key::{ + is_symbol_key, make_int_key, make_symbol_key, make_well_known_symbol_key, symbol_index_from_key, + well_known_symbol_id_from_key, WELL_KNOWN_SYMBOL_COUNT, +}; use oxide_types::value::JsValue; use crate::native::NativeFn; use crate::vm::Vm; use super::{ - AggregateKind, AGG_ALREADY_PROP, AGG_INDEX_PROP, AGG_RECORD_PROP, AGG_REJECT_PROP, AGG_REMAINING_PROP, - AGG_RESOLVE_PROP, AGG_VALUES_PROP, TRY_ARGS_PROP, TRY_EXECUTOR_PROP, + AggregateKind, AGG_ALREADY_PROP, AGG_INDEX_PROP, AGG_KEYS_PROP, AGG_RECORD_PROP, AGG_REJECT_PROP, + AGG_REMAINING_PROP, AGG_RESOLVE_PROP, AGG_VALUES_PROP, TRY_ARGS_PROP, TRY_EXECUTOR_PROP, }; impl Vm { @@ -37,6 +43,26 @@ impl Vm { JsValue::from_js_object(ptr) } + /// 创建 keyed 聚合静态方法的共享记录对象:剩余计数(初始 1)、结果平行数组、 + /// 键平行数组与能力 resolve/reject 闭包存为自身属性。 + fn make_agg_record_keyed(&mut self, values: JsValue, keys: JsValue, resolve: JsValue, reject: JsValue) -> JsValue { + let proto_val = JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject); + let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); + // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;本次 native 调用内不搬移,借出期间无别名。 + let obj = unsafe { &mut *ptr }; + let rem_si = self.kernel_core.perm_interner().intern(AGG_REMAINING_PROP).0; + self.set_or_create_prop_value(obj, rem_si, JsValue::int(1)); + let val_si = self.kernel_core.perm_interner().intern(AGG_VALUES_PROP).0; + self.set_or_create_prop_value(obj, val_si, values); + let key_si = self.kernel_core.perm_interner().intern(AGG_KEYS_PROP).0; + self.set_or_create_prop_value(obj, key_si, keys); + let res_si = self.kernel_core.perm_interner().intern(AGG_RESOLVE_PROP).0; + self.set_or_create_prop_value(obj, res_si, resolve); + let rej_si = self.kernel_core.perm_interner().intern(AGG_REJECT_PROP).0; + self.set_or_create_prop_value(obj, rej_si, reject); + JsValue::from_js_object(ptr) + } + /// 创建聚合静态方法的元素处理器闭包:携带共享记录与元素下标,`already` 标志 /// 初始 false。内部状态属性非枚举且追加在 length/name 之后,保持内建函数 /// "length 先于 name" 的属性序。 @@ -370,6 +396,17 @@ pub(super) fn promise_static_any(vm: &mut Vm, args: &[u8]) -> NativeResult { } } +/// `Promise.allKeyed(object)`:输入自身可枚举键的元素全部结算后以 null 原型 +/// 键控结果对象完成,任一元素拒绝则拒绝。 +pub(super) fn promise_static_all_keyed(vm: &mut Vm, args: &[u8]) -> NativeResult { + let ctor = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let promises = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + match perform_promise_combine_keyed(vm, ctor, promises, KeyedVariant::All) { + Ok(promise) => NativeResult::Ok(promise), + Err(err) => NativeResult::Err(err), + } +} + /// 聚合静态方法的共享核心:迭代可迭代输入,逐元素调 `C.resolve` 建 promise 并注册反应, /// 按模式在全部/任一结算后交付能力 promise。 /// @@ -536,6 +573,200 @@ fn perform_promise_combine( Ok(promise) } +/// keyed 聚合族的语义模式:决定元素处理器形态(all 拒绝侧直拒 / +/// allSettled 双侧记录)。 +#[derive(Clone, Copy, PartialEq, Eq)] +enum KeyedVariant { + All, + // allSettledKeyed 入口尚未挂入构造器静态表,核心实现先行备齐。 + #[expect(dead_code)] + AllSettled, +} + +/// keyed 聚合静态方法(allKeyed / allSettledKeyed)共享核心:按 own keys 序 +/// 枚举输入对象的可枚举自身键,逐元素包装 promise 并注册反应,全部结算后 +/// 以 null 原型键控结果对象交付能力 promise。 +/// +/// # 步骤 +/// 1. 建能力(NewPromiseCapability(C) 抛错同步上抛);取 C.resolve +/// (读错 / 不可调用 → 拒绝)。 +/// 2. 非对象输入 → 异步以新 TypeError 拒绝(键枚举之前)。 +/// 3. 按 OwnKeys 序枚举(整数键升序、字符串键与符号键插入序):可枚举键 +/// 经完整 Get 取值,`Call(C.resolve, C, «value»)` 包装,注册元素处理器 +/// (剩余计数先 +1 再注册)。 +/// 4. 循环尾部哨兵计数递减,归 0 时建结果对象并结算。 +/// +/// # 边界与前提 +/// - Get / resolve 调用 / then 注册抛错均异步拒绝能力 promise +/// (IfAbruptRejectPromise),不同步抛出;仅 NewPromiseCapability 同步传播。 +/// - ownKeys 枚举在非 Proxy 对象上无抛点(本引擎无 Proxy);描述符读取与 +/// Get 同走现件路径。 +fn perform_promise_combine_keyed( + vm: &mut Vm, ctor: JsValue, promises: JsValue, variant: KeyedVariant, +) -> Result { + // NewPromiseCapability(C):本函数唯一同步传播臂。 + let (promise, resolve, reject) = vm.new_promise_capability_with_ctor(ctor)?; + + // GetPromiseResolve(C):读取抛错或不可调用 → 以原抛出值拒绝。 + let resolve_si = vm.kernel_core.perm_interner().intern("resolve").0; + let promise_resolve = if ctor.is_object() { + // SAFETY: ctor 是存活对象。 + let ctor_obj = unsafe { &*ctor.as_js_object_ptr() }; + match vm.ordinary_get(ctor_obj, resolve_si, ctor) { + Ok(v) => v, + Err(e) => { + let exc = agg_engine_error(vm, &e); + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + return Ok(promise); + } + } + } else { + JsValue::undefined() + }; + if !oxide_builtins::iterator::is_callable(promise_resolve) { + let exc = oxide_builtins::error::create_type_error(vm, "resolve is not a function"); + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + return Ok(promise); + } + + // 非对象输入:能力已建、键枚举之前,异步以新 TypeError 拒绝。 + if !promises.is_object() { + let exc = oxide_builtins::error::create_type_error(vm, "promises argument is not an object"); + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + return Ok(promise); + } + + // 平行承载:values 结果数组与 keys 键值数组同下标对齐,记录对象持有两者。 + // SAFETY: promises 是存活对象。 + let promises_obj = unsafe { &*promises.as_js_object_ptr() }; + let values = vm.make_plain_array(Vec::new()); + let keys = vm.make_plain_array(Vec::new()); + let record = vm.make_agg_record_keyed(values, keys, resolve, reject); + + // OwnKeys 序:整数键升序、字符串键插入序;符号键按插入序追加(shape 位置 + // 换算绝对存储下标,与 walk_own_keys 口径一致)。 + let mut all_keys = oxide_builtins::object::walk_own_keys(vm, promises_obj); + for sym in oxide_builtins::object::own_symbol_key_values(vm, promises_obj) { + let idx = sym.as_symbol_index(); + let si = if idx < WELL_KNOWN_SYMBOL_COUNT { + make_well_known_symbol_key(idx) + } else { + make_symbol_key(idx) + }; + if let Some(pos) = vm.kernel_core.shape_forge().lookup_position(promises_obj.shape_id(), si) { + let store = if promises_obj.is_array() { promises_obj.array_prop_count + pos } else { pos }; + all_keys.push((si, store)); + } + } + + let mut index: i32 = 0; + for (si, store) in all_keys { + // 可枚举性:TA 元素键(哨兵存储下标)恒在场且可枚举;其余查元数据区 + // (无元数据 = 默认数据描述符,可枚举)。 + let enumerable = if store == u32::MAX { + true + } else { + promises_obj + .prop_meta_at(store) + .map_or(true, |m| !m.is_hole() && m.attributes.enumerable()) + }; + if !enumerable { + continue; + } + + // Get:完整读(走原型链,访问器触发);抛错异步拒绝。 + let value = match vm.ordinary_get(promises_obj, si, promises) { + Ok(v) => v, + Err(e) => { + let exc = agg_engine_error(vm, &e); + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + return Ok(promise); + } + }; + + // 键值物化进 keys 数组(与 values 下标平行)。 + let key_value = key_si_to_key_value(vm, si); + // SAFETY: keys 是本函数新建的存活数组对象;元素区直写不触发数组 setter。 + vm.set_or_create_prop_value(unsafe { &mut *keys.as_js_object_ptr() }, make_int_key(index as u32), key_value); + + // nextPromise = Call(C.resolve, C, «value»);抛错异步拒绝。 + let next_promise = match vm.call_function_sync(promise_resolve, ctor, &[value]) { + Ok(p) => p, + Err(e) => { + let exc = agg_engine_error(vm, &e); + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + return Ok(promise); + } + }; + + // 剩余计数先 +1 再注册:thenable 同步结算时 handler 依赖计数已含自身。 + let cur = agg_read_remaining(vm, record); + agg_write_remaining(vm, record, cur + 1); + + // 元素处理器按模式注册:all 变体拒绝侧直挂能力 reject, + // allSettled 变体双侧用记录元素函数。 + let outcome = match variant { + KeyedVariant::All => { + let re = vm.make_agg_element_fn(promise_keyed_resolve_element, record, index); + vm.invoke_then(next_promise, &[re, reject]) + } + KeyedVariant::AllSettled => { + let re = vm.make_agg_element_fn(promise_keyed_settled_resolve_element, record, index); + let rj = vm.make_agg_element_fn(promise_keyed_settled_reject_element, record, index); + vm.invoke_then(next_promise, &[re, rj]) + } + }; + if let Err(exc) = outcome { + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + return Ok(promise); + } + index += 1; + } + + // 尾部哨兵递减;归 0 时结算(键非空时 thenable 同步结算亦可发生)。 + let remaining = agg_read_remaining(vm, record) - 1; + agg_write_remaining(vm, record, remaining); + if remaining == 0 { + settle_keyed_aggregate(vm, record); + } + Ok(promise) +} + +/// 键 si 物化为 JS 可见键值:字符串 / 数字串经文本还原,符号键还原为符号值。 +fn key_si_to_key_value(vm: &mut Vm, si: u32) -> JsValue { + if is_symbol_key(si) { + if let Some(id) = well_known_symbol_id_from_key(si) { + return JsValue::symbol(id); + } + return JsValue::symbol(symbol_index_from_key(si)); + } + oxide_builtins::object::key_si_to_js_value(vm, si) +} + +/// 结算 keyed 聚合:按 keys/values 平行数组建 null 原型结果对象(键序 = +/// 输入 OwnKeys 序),经能力 resolve 交付;结算抛错降级为拒绝。 +fn settle_keyed_aggregate(vm: &mut Vm, record: JsValue) { + let keys = agg_record_val(vm, record, AGG_KEYS_PROP); + let values = agg_record_val(vm, record, AGG_VALUES_PROP); + let ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;结算循环内属性写入不搬移对象,借出期间无别名。 + let obj = unsafe { &mut *ptr }; + if keys.is_object() && values.is_object() { + // SAFETY: keys/values 是本次调用新建的存活平行数组;元素区读写不搬移对象。 + let keys_obj = unsafe { &*keys.as_js_object_ptr() }; + let values_obj = unsafe { &*values.as_js_object_ptr() }; + for i in 0..keys_obj.prop_count() { + let key_val = keys_obj.get_prop_at(i); + let si = match vm.property_key_si(key_val) { + Ok(si) => si, + Err(_) => continue, + }; + vm.set_or_create_prop_value(obj, si, values_obj.get_prop_at(i)); + } + } + agg_call_resolve(vm, record, JsValue::from_js_object(ptr)); +} + /// 聚合元素处理器的公共 prologue:取共享记录与下标;`already` 已置位返回 None /// (元素函数只生效一次),否则置位后返回记录。 fn agg_element_state(vm: &mut Vm) -> Option<(JsValue, i32)> { @@ -719,6 +950,71 @@ fn promise_any_reject_element(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } +/// `Promise.allKeyed` resolve 元素:写 `values[index]`,剩余计数归零时建 +/// null 原型结果对象并完成。 +fn promise_keyed_resolve_element(vm: &mut Vm, args: &[u8]) -> NativeResult { + let Some((record, index)) = agg_element_state(vm) else { + return NativeResult::Ok(JsValue::undefined()); + }; + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let values = agg_record_val(vm, record, AGG_VALUES_PROP); + if values.is_object() { + let key_si = make_int_key(index as u32); + // SAFETY: values 是存活数组对象;直写元素区不触发数组 setter。 + vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, value); + } + let remaining = agg_read_remaining(vm, record) - 1; + agg_write_remaining(vm, record, remaining); + if remaining == 0 { + settle_keyed_aggregate(vm, record); + } + NativeResult::Ok(JsValue::undefined()) +} + +/// `Promise.allSettledKeyed` resolve 元素:写 `{status:'fulfilled', value}` +/// 记录,剩余计数归零时建结果对象并完成。 +fn promise_keyed_settled_resolve_element(vm: &mut Vm, args: &[u8]) -> NativeResult { + let Some((record, index)) = agg_element_state(vm) else { + return NativeResult::Ok(JsValue::undefined()); + }; + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let values = agg_record_val(vm, record, AGG_VALUES_PROP); + if values.is_object() { + let settled = vm.make_settled_record("fulfilled", "value", value); + let key_si = make_int_key(index as u32); + // SAFETY: values 是存活数组对象。 + vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, settled); + } + let remaining = agg_read_remaining(vm, record) - 1; + agg_write_remaining(vm, record, remaining); + if remaining == 0 { + settle_keyed_aggregate(vm, record); + } + NativeResult::Ok(JsValue::undefined()) +} + +/// `Promise.allSettledKeyed` reject 元素:写 `{status:'rejected', reason}` +/// 记录,剩余计数归零时建结果对象并完成。 +fn promise_keyed_settled_reject_element(vm: &mut Vm, args: &[u8]) -> NativeResult { + let Some((record, index)) = agg_element_state(vm) else { + return NativeResult::Ok(JsValue::undefined()); + }; + let reason = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let values = agg_record_val(vm, record, AGG_VALUES_PROP); + if values.is_object() { + let settled = vm.make_settled_record("rejected", "reason", reason); + let key_si = make_int_key(index as u32); + // SAFETY: values 是存活数组对象。 + vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, settled); + } + let remaining = agg_read_remaining(vm, record) - 1; + agg_write_remaining(vm, record, remaining); + if remaining == 0 { + settle_keyed_aggregate(vm, record); + } + NativeResult::Ok(JsValue::undefined()) +} + /// `AggregateError(errors, message)` 构造器:message 非 undefined 时先 ToString 建 /// 自身属性,再把 errors 可迭代收集为 `errors` 数据属性。 fn aggregate_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index 60c0457b..d9b24e77 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -12,8 +12,8 @@ use oxide_types::value::JsValue; use crate::vm::{FrameArgs, FrameContinuation, Vm}; use super::aggregate::{ - promise_static_all, promise_static_all_settled, promise_static_any, promise_static_race, promise_static_reject, - promise_static_resolve, promise_static_try, promise_static_with_resolvers, + promise_static_all, promise_static_all_keyed, promise_static_all_settled, promise_static_any, promise_static_race, + promise_static_reject, promise_static_resolve, promise_static_try, promise_static_with_resolvers, }; use super::reactions::{promise_catch, promise_finally, promise_then}; use super::{is_constructor_value, PromiseState, PromiseStateKind}; @@ -285,6 +285,7 @@ impl Vm { ("all", promise_static_all as *const (), 1), ("race", promise_static_race as *const (), 1), ("allSettled", promise_static_all_settled as *const (), 1), + ("allKeyed", promise_static_all_keyed as *const (), 1), ("any", promise_static_any as *const (), 1), ("withResolvers", promise_static_with_resolvers as *const (), 0), ("try", promise_static_try as *const (), 1), diff --git a/crates/oxide_vm/src/promise/mod.rs b/crates/oxide_vm/src/promise/mod.rs index 4d4bc86f..bed3fe5b 100644 --- a/crates/oxide_vm/src/promise/mod.rs +++ b/crates/oxide_vm/src/promise/mod.rs @@ -106,6 +106,8 @@ const AGG_ALREADY_PROP: &str = "__oxide_agg_already__"; const AGG_REMAINING_PROP: &str = "__oxide_agg_remaining__"; /// 记录对象上存结果数组(all 的 values,any 的 errors)。 const AGG_VALUES_PROP: &str = "__oxide_agg_values__"; +/// 记录对象上存结果键平行数组(keyed 族:JS 键值序列,与 values 按下标对齐)。 +const AGG_KEYS_PROP: &str = "__oxide_agg_keys__"; /// 记录对象上存能力 resolve 闭包。 const AGG_RESOLVE_PROP: &str = "__oxide_agg_resolve__"; /// 记录对象上存能力 reject 闭包。 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 2883f07d..242cc585 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -813,7 +813,10 @@ impl Vm { prototype.bump_generation(); } - // 函数自身 `prototype` 属性:生成器 writable:true / enumerable:false / configurable:false。 + // 函数自身 `prototype` 属性:生成器 writable:true / enumerable:false / + // configurable:false;普通函数与构造器 writable:true / enumerable:false / + // configurable:true。显式落元数据——缺元数据按默认描述符(可枚举) + // 判定,`prototype` 会泄漏进 keys / for-in / 聚合枚举。 let prototype_si = self.kernel_core.perm_interner().intern("prototype").0; let func = unsafe { &mut *obj_ptr }; let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si); @@ -821,6 +824,8 @@ impl Vm { let prototype_pos = func.push_prop(prototype_val); if is_generator || is_async_generator { func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false)); + } else { + func.set_data_meta(prototype_pos, PropAttributes::new(true, false, true)); } func.bump_generation(); }