feat: 落地 deferred namespace 状态盒与 is_deferred 标志

This commit is contained in:
2026-10-09 12:46:50 +00:00
parent c0648b9014
commit 64c5fcbb1e
6 changed files with 227 additions and 23 deletions
Generated
+19 -19
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1226,7 +1226,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1235,7 +1235,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1255,7 +1255,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1269,7 +1269,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1284,7 +1284,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1296,7 +1296,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1305,7 +1305,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"bumpalo",
"dashmap",
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1331,7 +1331,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"tracing",
"tracing-appender",
@@ -1340,7 +1340,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1371,7 +1371,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"num-bigint",
"num-traits",
@@ -1384,7 +1384,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1402,7 +1402,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"bumpalo",
"dashmap",
@@ -1415,7 +1415,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.8.103"
version = "0.8.104"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
]
[workspace.package]
version = "0.8.103"
version = "0.8.104"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+164 -1
View File
@@ -60,10 +60,28 @@ pub struct ModuleNsEntry {
pub state: ModuleNsState,
}
/// deferred namespace 状态盒:`[[Module]]`/`[[Evaluated]]`/`[[Namespace]]` 三内部槽。
///
/// 以 `Option` 内嵌进 `ModuleNsTable`(`native_data` 的唯一分配),不新建 JsObject
/// 字段、不建第二个 Box——GC 边、字节核算、释放全部复用 `ModuleNamespace` 家族链,
/// 零新增注册点。
pub struct DeferredNsState {
/// `[[Module]]`:依赖模块函数(编译期 CREATE_CLOSURE 的闭包对象)。
pub module: JsValue,
/// `[[Evaluated]]`:依赖模块是否已求值(首次触发成功后置真,不回退)。
pub evaluated: bool,
/// `[[Namespace]]`:求值后返回的命名空间对象(求值前为 undefined)。
pub namespace: JsValue,
}
/// 模块命名空间条目表:挂在 ns 对象 `native_data` 上,是 exotic [[Get]] 的权威
/// 状态。键为 `PermInterner` interned 键,线性查找(导出名通常少于 50)。
///
/// `deferred` 仅对 deferred namespace 对象为 `Some`(`__moduleDeferObject` 安装),
/// 其余 ns 对象恒 `None`;求值触发点(后续轮次)经 `deferred_state` 只读消费。
pub struct ModuleNsTable {
entries: Vec<ModuleNsEntry>,
deferred: Option<DeferredNsState>,
}
/// 导出名状态查询结果。
@@ -86,6 +104,24 @@ fn ns_table_ptr(obj: &JsObject) -> *mut ModuleNsTable {
obj.native_data() as *mut ModuleNsTable
}
/// 只读访问 deferred namespace 状态盒(`[[Module]]`/`[[Evaluated]]`/`[[Namespace]]`)。
///
/// # 边界与前提
/// - 返回 `None`:非 module namespace / 无条目表 / 非 deferred 对象(`deferred` 为 `None`)。
/// - 返回 `Some`:deferred 对象且已安装状态盒,供求值触发点消费三内部槽。
///
/// # 注意事项
/// - 写入点唯一(`module_defer_object` 安装),本访问器只读,不改状态盒。
pub fn deferred_state(obj: &JsObject) -> Option<&DeferredNsState> {
let table = ns_table_ptr(obj);
if table.is_null() {
return None;
}
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
let table_ref = unsafe { &*table };
table_ref.deferred.as_ref()
}
/// 在条目表中按 interned 键定位条目下标。
fn entry_index(table: &ModuleNsTable, key_si: u32) -> Option<usize> {
table.entries.iter().position(|e| e.name_si == key_si)
@@ -93,7 +129,10 @@ fn entry_index(table: &ModuleNsTable, key_si: u32) -> Option<usize> {
/// 分配空条目表并挂到 ns 对象的 `native_data`。
fn install_ns_table(obj: &mut JsObject) -> *mut ModuleNsTable {
let table = Box::into_raw(Box::new(ModuleNsTable { entries: Vec::new() }));
let table = Box::into_raw(Box::new(ModuleNsTable {
entries: Vec::new(),
deferred: None,
}));
obj.set_native_data(table as *mut u8);
table
}
@@ -189,6 +228,10 @@ pub fn module_ns_export(obj: &JsObject, key_si: u32) -> Option<ModuleNsQuery> {
}
/// 收集条目表持有的 JsValue 边(已初始化 `Value` 与全部 `Cell` 内值),供 GC mark。
///
/// deferred namespace 对象额外持有 `[[Module]]` 与 `[[Namespace]]` 两条边:
/// 依赖模块函数与求值后命名空间对象须经本单一注册点入 mark,漏登则函数在
/// deferred ns 存活期间被回收,触发点解引用悬垂。
pub fn module_ns_native_edges(obj: &JsObject) -> Vec<JsValue> {
let table = ns_table_ptr(obj);
if table.is_null() {
@@ -212,6 +255,10 @@ pub fn module_ns_native_edges(obj: &JsObject) -> Vec<JsValue> {
}
}
}
if let Some(deferred) = &table_ref.deferred {
edges.push(deferred.module);
edges.push(deferred.namespace);
}
edges
}
@@ -812,6 +859,52 @@ pub fn module_eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
}
/// `__moduleDeferObject(fn)`:创建 deferred namespace 对象(`import defer * as` 产物)。
///
/// # 步骤
/// 1. 校验 `fn` 为函数对象(依赖模块函数,编译期 CREATE_CLOSURE 的闭包对象)。
/// 2. 建 null 原型对象,定义 `@@toStringTag` = "Deferred Module"(不可写不可枚举不可配置)。
/// 3. 置 module namespace + deferred 双标志,置 non-extensible。
/// 4. 安装条目表并写状态盒:`[[Module]]` = fn、`[[Evaluated]]` = false、
/// `[[Namespace]]` = undefined。
///
/// # 副作用
/// - 新对象创建起即标记 module namespace exotic 并置 non-extensible(同 `__moduleObject`);
/// 状态盒三内部槽由本入口唯一安装。
pub fn module_defer_object<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return type_error(vm, "__moduleDeferObject: 1 argument required");
}
let fn_val = vm.reg(args[1]);
if !fn_val.is_object() {
return type_error(vm, "__moduleDeferObject: target is not a function");
}
let ptr = fn_val.as_js_object_ptr();
if ptr.is_null() || !unsafe { (*ptr).is_function() } {
return type_error(vm, "__moduleDeferObject: target is not a function");
}
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let tag_si = encode_symbol_key(vm.realm_id(), TO_STRING_TAG_SYMBOL_ID);
let tag_val = vm.new_string("Deferred Module");
let obj_ref = unsafe { &mut *obj };
if let Err(e) = vm.define_data_property(obj_ref, tag_si, tag_val, PropAttributes::new(false, false, false)) {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
obj_ref.set_module_namespace(true);
obj_ref.set_deferred(true);
obj_ref.set_extensible(false);
let table = install_ns_table(obj_ref);
// SAFETY: table 由 install_ns_table 的 Box::into_raw 分配,本处恰好写入一次。
unsafe {
(*table).deferred = Some(DeferredNsState {
module: fn_val,
evaluated: false,
namespace: JsValue::undefined(),
});
}
NativeResult::Ok(JsValue::from_js_object(obj))
}
/// `__moduleData(kind, content)`:构造数据模块命名空间(json/text;bytes 未支持)。
///
/// # 副作用
@@ -853,3 +946,73 @@ pub fn module_data<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
obj_ref.set_extensible(false);
NativeResult::Ok(JsValue::from_js_object(obj))
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_compiler::DefaultCompilerService;
use oxide_vm::vm::Vm;
use std::sync::Arc;
/// 建一个带编译服务的 VM 与一个依赖模块函数(deferred ns 的 `[[Module]]` 载荷)。
fn vm_with_function() -> (Vm, JsValue) {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
let fn_val = vm
.create_dynamic_function(&[], "return 42;", false, false)
.expect("create function");
(vm, fn_val)
}
#[test]
fn module_defer_object_installs_state_box() {
let (mut vm, fn_val) = vm_with_function();
vm.set_reg(1, fn_val);
let ns_val = module_defer_object(&mut vm, &[0, 1]).unwrap();
assert!(ns_val.is_object());
let ns_ptr = ns_val.as_js_object_ptr();
assert!(!ns_ptr.is_null());
let ns_obj = unsafe { &*ns_ptr };
assert!(ns_obj.is_module_namespace(), "deferred ns 应标记 module namespace");
assert!(ns_obj.is_deferred(), "deferred ns 应标记 deferred");
assert!(!ns_obj.is_extensible(), "deferred ns 应 non-extensible");
// 状态盒三内部槽安装正确:[[Module]] = fn、[[Evaluated]] = false、[[Namespace]] = undefined。
let state = deferred_state(ns_obj).expect("deferred state 应已安装");
assert_eq!(state.module, fn_val, "[[Module]] 应存依赖模块函数");
assert!(!state.evaluated, "[[Evaluated]] 初始应为 false");
assert_eq!(state.namespace, JsValue::undefined(), "[[Namespace]] 求值前应为 undefined");
}
#[test]
fn module_defer_object_rejects_non_function() {
let (mut vm, _) = vm_with_function();
vm.set_reg(1, JsValue::int(42));
let result = module_defer_object(&mut vm, &[0, 1]);
assert!(matches!(result, NativeResult::Err(_)), "非函数应抛 TypeError");
}
#[test]
fn module_defer_ns_gc_edge_keeps_module_function_alive() {
let (mut vm, fn_val) = vm_with_function();
let fn_ptr = fn_val.as_js_object_ptr();
// 创建 deferred ns(持有 fn);fn + ns 两个 session 对象。
vm.set_reg(1, fn_val);
let ns_val = module_defer_object(&mut vm, &[0, 1]).unwrap();
let count_after_ns = vm.session_object_count();
// 仅 ns 为根:清掉持有 fn 的寄存器,fn 唯一可达路径是 ns 的 [[Module]] 边。
vm.set_reg(1, JsValue::undefined());
vm.set_reg(0, ns_val);
// 完整收集:若 [[Module]] 边漏登,fn 被回收、计数回落。
vm.collect_session_gc();
assert_eq!(vm.session_object_count(), count_after_ns, "依赖模块函数应跨收集存活(GC 边已登记)");
// 原地 sweep 不搬移:ns 与状态盒原地保留,[[Module]] 指针稳定。
let ns_obj = unsafe { &*ns_val.as_js_object_ptr() };
let state = deferred_state(ns_obj).expect("deferred state 应存活");
assert_eq!(state.module.as_js_object_ptr(), fn_ptr, "[[Module]] 指针应稳定");
}
}
+27 -1
View File
@@ -85,8 +85,9 @@ pub const MAX_DENSE_PROPS: usize = 1_000_000;
/// regexp_source: JsValue (8 字节,RegExp 实例的 source 字符串)
/// regexp_flags: JsValue (8 字节,RegExp 实例的 flags 字符串)
/// upvalues: *mut u8 (8 字节,指向闭包的 Box<Vec<*mut Cell>>)
/// deferred_flags: u8 (1 字节,bit 0 = is_deferred,deferred namespace 标志)
///
/// 字段自和:132 字节,另有 4 字节尾部对齐填充
/// 字段自和:133 字节,另有 3 字节尾部对齐填充
/// 总计:136 字节
/// 对齐:8 字节
pub struct JsObject {
@@ -138,6 +139,10 @@ pub struct JsObject {
/// RegExp 实例的 flags 字符串(RegExp.prototype.flags 访问器的数据源)。
regexp_flags: JsValue,
pub upvalues: *mut u8,
/// deferred namespace 标志位:bit 0 = is_deferred。仅对 module namespace
/// 对象有语义(deferred ns 同时置 module_namespace 与本位);其余对象恒 0。
/// 占尾部对齐填充 1 字节,136B 布局钉不变。
deferred_flags: u8,
}
impl JsObject {
@@ -435,6 +440,7 @@ impl JsObject {
regexp_source: JsValue::undefined(),
regexp_flags: JsValue::undefined(),
upvalues: std::ptr::null_mut(),
deferred_flags: 0,
}
}
@@ -464,6 +470,7 @@ impl JsObject {
regexp_source: JsValue::undefined(),
regexp_flags: JsValue::undefined(),
upvalues: std::ptr::null_mut(),
deferred_flags: 0,
};
let vec = Box::new(vec![JsValue::undefined(); n_elements.min(MAX_DENSE_PROPS)]);
obj.array_elements = Box::into_raw(vec) as *mut u8;
@@ -680,6 +687,25 @@ impl JsObject {
}
}
/// 是否 deferred namespace 对象(`import defer * as` 产物)。
///
/// # 边界与前提
/// - 仅当 `is_module_namespace()` 为真时有语义;deferred ns 同时置
/// module_namespace 位与本位,两维独立。
/// - 读 `deferred_flags` bit 0;其余对象恒 0。
pub fn is_deferred(&self) -> bool {
self.deferred_flags & 1 != 0
}
/// 设置 deferred namespace 标志(写 `deferred_flags` bit 0)。
pub fn set_deferred(&mut self, value: bool) {
if value {
self.deferred_flags |= 1;
} else {
self.deferred_flags &= !1;
}
}
/// 数组 length 虚拟属性是否可写。
///
/// # 边界与前提
+11 -1
View File
@@ -9,12 +9,22 @@ fn object_size_bounds() {
#[test]
fn js_object_exact_size() {
// 精确钉当前布局:字段自和 132 字节 + 4 字节尾部对齐填充。
// 精确钉当前布局:字段自和 133 字节 + 3 字节尾部对齐填充。
// 对象头尺寸是 GC 账目与内存基线的锚,静默膨胀须在此显式失败。
let sz = std::mem::size_of::<JsObject>();
assert_eq!(sz, 136, "JsObject size drifted: {sz}B");
}
#[test]
fn deferred_flag_roundtrip() {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
assert!(!obj.is_deferred());
obj.set_deferred(true);
assert!(obj.is_deferred());
obj.set_deferred(false);
assert!(!obj.is_deferred());
}
#[test]
fn new_empty_defaults() {
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
@@ -54,6 +54,11 @@ pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
("__moduleStar", oxide_builtins::module::module_star::<crate::vm::Vm> as *const (), 3),
("__moduleSeal", oxide_builtins::module::module_seal::<crate::vm::Vm> as *const (), 1),
("__moduleEval", oxide_builtins::module::module_eval::<crate::vm::Vm> as *const (), 1),
(
"__moduleDeferObject",
oxide_builtins::module::module_defer_object::<crate::vm::Vm> as *const (),
1,
),
("__moduleData", oxide_builtins::module::module_data::<crate::vm::Vm> as *const (), 2),
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
(