feat: 模块命名空间导出条目表与预注册

This commit is contained in:
2026-09-17 23:47:04 +00:00
parent 4d0f11e30a
commit cafac572fa
8 changed files with 519 additions and 4 deletions
+362 -2
View File
@@ -7,7 +7,7 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::object::{Cell, JsObject, PropAttributes};
use oxide_types::private_key::make_well_known_symbol_key;
use oxide_types::value::JsValue;
@@ -26,6 +26,326 @@ fn type_error<H: VmHost>(vm: &mut H, msg: &str) -> NativeResult {
NativeResult::Err(crate::error::create_type_error(vm, msg))
}
/// 命名空间条目表的来源身份(provenance)。
///
/// `Local` 为本模块直接声明或本地再导出的导出;`Reexport` 预留跨模块 star 再导出
/// 的来源身份,由 star 导出任务填入,避免另立 per-namespace 表形成两套判据。
#[derive(Clone, Copy)]
pub enum ModuleNsOrigin {
Local,
Reexport { path_id: u32, binding_id: u32, direct: bool },
}
/// 单个导出名的活值状态。
pub enum ModuleNsState {
/// 预注册后未初始化的一般导出;`initialized` 为 true 时 `value` 是当前活值。
Value { value: JsValue, initialized: bool },
/// 被捕获导出挂的共享 cell,活值由 cell 承载。
Cell(*mut Cell),
}
/// 命名空间条目:导出名 interned 键、活值状态与来源身份一一对应。
pub struct ModuleNsEntry {
pub name_si: u32,
pub origin: ModuleNsOrigin,
pub state: ModuleNsState,
}
/// 模块命名空间条目表:挂在 ns 对象 `native_data` 上,是 exotic [[Get]] 的权威
/// 状态。键为 `PermInterner` interned 键,线性查找(导出名通常少于 50)。
pub struct ModuleNsTable {
entries: Vec<ModuleNsEntry>,
}
/// 导出名状态查询结果。
pub enum ModuleNsQuery {
/// 未初始化(预注册未赋值,或未初始化的共享 cell)。
Uninitialized,
/// 已初始化活值(`Value` 条目或已初始化 cell)。
Initialized(JsValue),
}
/// 取 ns 对象的条目表指针;非 module namespace 或无表返回空指针。
///
/// # 注意事项
/// - 返回指针由 ns 对象 `native_data` 持有,在该对象存活期内有效;消费方须在
/// 同一 VM 世代内使用。
fn ns_table_ptr(obj: &JsObject) -> *mut ModuleNsTable {
if !obj.is_module_namespace() {
return std::ptr::null_mut();
}
obj.native_data() as *mut ModuleNsTable
}
/// 在条目表中按 interned 键定位条目下标。
fn entry_index(table: &ModuleNsTable, key_si: u32) -> Option<usize> {
table.entries.iter().position(|e| e.name_si == key_si)
}
/// 分配空条目表并挂到 ns 对象的 `native_data`。
fn install_ns_table(obj: &mut JsObject) -> *mut ModuleNsTable {
let table = Box::into_raw(Box::new(ModuleNsTable { entries: Vec::new() }));
obj.set_native_data(table as *mut u8);
table
}
/// 追加一条未初始化的 `Value` 条目。
///
/// # 注意事项
/// - 调用方保证 `name_si` 尚未入表(入口均为先查后插)。
fn push_uninitialized_entry(table: *mut ModuleNsTable, name_si: u32) {
push_entry(
table,
name_si,
ModuleNsState::Value {
value: JsValue::undefined(),
initialized: false,
},
);
}
/// 追加一条已初始化的 `Value` 条目。
///
/// # 注意事项
/// - 调用方保证 `name_si` 尚未入表。
fn push_initialized_entry(table: *mut ModuleNsTable, name_si: u32, value: JsValue) {
push_entry(table, name_si, ModuleNsState::Value { value, initialized: true });
}
/// 向条目表追加一条条目。
///
/// # 注意事项
/// - table 归调用方持有的 ns 对象所有,在同一 VM 世代内有效。
fn push_entry(table: *mut ModuleNsTable, name_si: u32, state: ModuleNsState) {
let entry = ModuleNsEntry {
name_si,
origin: ModuleNsOrigin::Local,
state,
};
// SAFETY: table 归调用方持有的 ns 对象所有,在同一 VM 世代内有效。
unsafe {
(*table).entries.push(entry);
}
}
/// 模块命名空间字符串导出名的状态查询(不抛错、不分配、不写状态)。
///
/// # 边界与前提
/// - 返回 `None`:非 module namespace / 无条目表 / 键非条目(含 symbol 键)。
/// - 返回 `Uninitialized`:预注册未赋值,或条目挂在未初始化的共享 cell 上。
/// - 返回 `Initialized(v)`:`Value` 已初始化,或 cell 已初始化。
///
/// # 注意事项
/// - `Cell` 读依赖「cell 由 `alloc_cell` 分配并登记,至 `full_reset` 才统一释放」
/// 的生命周期契约;同一 VM 世代内指针有效。
pub fn module_ns_export(obj: &JsObject, key_si: u32) -> Option<ModuleNsQuery> {
let table = ns_table_ptr(obj);
if table.is_null() {
return None;
}
// SAFETY: table 指向本 ns 对象持有的 Box<ModuleNsTable>,随对象存活。
let table_ref = unsafe { &*table };
let entry = table_ref.entries.get(entry_index(table_ref, key_si)?)?;
match &entry.state {
ModuleNsState::Value { value, initialized } => {
if *initialized {
Some(ModuleNsQuery::Initialized(*value))
} else {
Some(ModuleNsQuery::Uninitialized)
}
}
ModuleNsState::Cell(cell) => {
if cell.is_null() {
return Some(ModuleNsQuery::Uninitialized);
}
// SAFETY: cell 由 alloc_cell 分配并登记,至 full_reset 才释放。
let cell_ref = unsafe { &**cell };
if cell_ref.is_initialized() {
Some(ModuleNsQuery::Initialized(cell_ref.value))
} else {
Some(ModuleNsQuery::Uninitialized)
}
}
}
}
/// 收集条目表持有的 JsValue 边(已初始化 `Value` 与全部 `Cell` 内值),供 GC mark。
pub fn module_ns_native_edges(obj: &JsObject) -> Vec<JsValue> {
let table = ns_table_ptr(obj);
if table.is_null() {
return Vec::new();
}
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
let table_ref = unsafe { &*table };
let mut edges = Vec::new();
for entry in &table_ref.entries {
match &entry.state {
ModuleNsState::Value { value, initialized } => {
if *initialized {
edges.push(*value);
}
}
ModuleNsState::Cell(cell) => {
if !cell.is_null() {
// SAFETY: cell 生命周期见 `module_ns_export`。
edges.push(unsafe { (**cell).value });
}
}
}
}
edges
}
/// 原地重写条目表内的 JsValue 边(对象搬移/epoch 晋升后的引用重定位)。
///
/// # 副作用
/// - 改写 `Value` 条目的值与共享 cell 内的值;cell 指针本身稳定不变。
pub fn rewrite_module_ns_native<F>(obj: &mut JsObject, mut rewrite: F)
where
F: FnMut(JsValue) -> JsValue,
{
let table = ns_table_ptr(obj);
if table.is_null() {
return;
}
// SAFETY: table 归本 ns 对象持有,原地改写其 JsValue 边。
let table_ref = unsafe { &mut *table };
for entry in table_ref.entries.iter_mut() {
match &mut entry.state {
ModuleNsState::Value { value, .. } => *value = rewrite(*value),
ModuleNsState::Cell(cell) => {
if !cell.is_null() {
// SAFETY: cell 生命周期见 `module_ns_export`;共享 cell 内值一并改写。
let cell_ref = unsafe { &mut **cell };
cell_ref.value = rewrite(cell_ref.value);
}
}
}
}
}
/// 深拷贝条目表到 `dst`,并用 `rewrite` 改写其中的对象引用。
///
/// # 注意事项
/// - `clone_for_session_epoch` 只浅拷贝 `native_data` 指针;本函数必须在新对象上
/// 重建独立 Box,否则原件与克隆共享同一表会在释放时双放。
/// - `Cell` 指针本身跨搬移稳定,只改写其内部值。
pub fn clone_module_ns_native_with_rewrite<F>(src: &JsObject, dst: &mut JsObject, mut rewrite: F)
where
F: FnMut(JsValue) -> JsValue,
{
if !src.is_module_namespace() {
return;
}
let table = src.native_data() as *const ModuleNsTable;
if table.is_null() {
dst.set_native_data(std::ptr::null_mut());
return;
}
// SAFETY: src.native_data 指向 src 持有的 Box<ModuleNsTable>。
let table_ref = unsafe { &*table };
let mut entries = Vec::with_capacity(table_ref.entries.len());
for entry in &table_ref.entries {
let state = match &entry.state {
ModuleNsState::Value { value, initialized } => ModuleNsState::Value {
value: if value.is_object() { rewrite(*value) } else { *value },
initialized: *initialized,
},
ModuleNsState::Cell(cell) => {
if !cell.is_null() {
// SAFETY: cell 生命周期见 `module_ns_export`;晋升/搬移后改写其内值。
let cell_ref = unsafe { &mut **cell };
cell_ref.value = rewrite(cell_ref.value);
}
ModuleNsState::Cell(*cell)
}
};
entries.push(ModuleNsEntry {
name_si: entry.name_si,
origin: entry.origin,
state,
});
}
dst.set_native_data(Box::into_raw(Box::new(ModuleNsTable { entries })) as *mut u8);
}
/// 只读核算条目表的 native 数据字节(不释放),与 `drop_module_ns_native` 的
/// capacity 口径一致,供 GC 账目核算。
pub fn module_ns_native_size(obj: &JsObject) -> u64 {
let table = ns_table_ptr(obj);
if table.is_null() {
return 0;
}
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
let table_ref = unsafe { &*table };
(std::mem::size_of::<ModuleNsTable>() + table_ref.entries.capacity() * std::mem::size_of::<ModuleNsEntry>()) as u64
}
/// 释放条目表(`Box<ModuleNsTable>`),返回释放字节数供泄漏统计。
///
/// # 注意事项
/// - 每对象的表至多释放一次;释放后 `native_data` 置空保证幂等。
pub fn drop_module_ns_native(obj: &mut JsObject) -> u64 {
let table = ns_table_ptr(obj);
if table.is_null() {
return 0;
}
let bytes = module_ns_native_size(obj);
// SAFETY: table 由 install_ns_table 的 Box::into_raw 分配,本处恰好释放一次。
unsafe {
drop(Box::from_raw(table));
}
obj.set_native_data(std::ptr::null_mut());
bytes
}
/// `__modulePreRegister(ns, name)`:live 模块在 body 求值前预注册静态导出名。
///
/// # 步骤
/// 1. 校验 ns 为 module namespace 对象,取导出名的 interned 键。
/// 2. 无表时分配;键已有条目时 no-op(幂等)。
/// 3. 经瞬态可扩展窗口在 ns 上定义真实数据属性 `undefined`(可写可枚举不可配置)。
/// 4. 追加未初始化 `Value` 条目并按规范顺序重排导出键。
///
/// # 副作用
/// - 在 ns 上定义真实属性槽、挂载/扩展条目表,并重排 `[[OwnPropertyKeys]]` 顺序。
pub fn module_pre_register<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return type_error(vm, "__modulePreRegister: 2 arguments required");
}
let ns_val = vm.reg(args[1]);
let ns_ptr = match vm.checked_object_ptr(ns_val, "__modulePreRegister: target is not an object") {
Ok(Some(p)) => p,
Ok(None) => return type_error(vm, "__modulePreRegister: target is not an object"),
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
};
let name_val = vm.reg(args[2]);
let name_si = vm.property_key_si(name_val);
let obj = unsafe { &mut *ns_ptr };
if !obj.is_module_namespace() {
return type_error(vm, "__modulePreRegister: target is not a module namespace");
}
// 幂等:同名条目已存在时不再定义真实槽或重复入表。
let existing = ns_table_ptr(obj);
if !existing.is_null() && unsafe { entry_index(&*existing, name_si) }.is_some() {
return NativeResult::Ok(JsValue::undefined());
}
// 导出定义窗口:同 module_set,窗口内不运行 JS,错误路径先复位扩展性。
obj.set_extensible(true);
let result = vm.define_data_property(obj, name_si, JsValue::undefined(), ns_attrs());
obj.set_extensible(false);
if let Err(e) = result {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
let table = if existing.is_null() { install_ns_table(obj) } else { existing };
push_uninitialized_entry(table, name_si);
crate::object::sort_namespace_exports(vm, obj);
NativeResult::Ok(JsValue::undefined())
}
/// `__moduleObject()`:创建模块命名空间对象(null 原型,带 @@toStringTag Symbol 键)。
///
/// # 副作用
@@ -44,7 +364,16 @@ pub fn module_object<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::from_js_object(obj))
}
/// `__moduleSet(ns, name, value)`:在命名空间上定义导出属性。
/// `__moduleSet(ns, name, value)`:在命名空间上定义或更新导出属性。
///
/// # 步骤
/// 1. live ns 命中既有条目:`Cell` 条目 no-op(活值由 cell 承载);`Value` 条目
/// 更新活值并同步真实属性槽,键集不变故跳过重排。
/// 2. 其余(无表 / 非条目 / 新键):经瞬态可扩展窗口定义或改值真实属性槽;
/// live ns 追加已初始化条目,最后按规范顺序重排导出键。
///
/// # 副作用
/// - 修改条目表状态与 ns 属性槽;新键会重排 `[[OwnPropertyKeys]]` 顺序。
pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 4 {
return type_error(vm, "__moduleSet: 3 arguments required");
@@ -59,6 +388,28 @@ pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let value = vm.reg(args[3]);
let name_si = vm.property_key_si(name_val);
let obj = unsafe { &mut *ns_ptr };
// live 条目路径:命中条目则更新活值并同步真实槽,键集不变无需重排。
let table = ns_table_ptr(obj);
if !table.is_null() {
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
if let Some(idx) = unsafe { entry_index(&*table, name_si) } {
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
let entry_state = unsafe { &mut (&mut (*table).entries)[idx].state };
match entry_state {
ModuleNsState::Cell(_) => return NativeResult::Ok(JsValue::undefined()),
ModuleNsState::Value { value: slot, initialized } => {
*slot = value;
*initialized = true;
if let Some(pos) = vm.get_own_property_slot(obj, name_si) {
obj.set_prop_at(pos, value);
}
return NativeResult::Ok(JsValue::undefined());
}
}
}
}
// 导出定义窗口:命名空间创建起不可扩展,定义新导出须瞬态放开扩展性。窗口内
// 不运行 JS,无可观察副作用;错误路径先复位以维持 non-extensible 不变式。
obj.set_extensible(true);
@@ -67,6 +418,9 @@ pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if let Err(e) = result {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
if !table.is_null() {
push_initialized_entry(table, name_si, value);
}
// 每次导出写入后重排:自导入在 body 内即可见有序的 [[OwnPropertyKeys]]。
crate::object::sort_namespace_exports(vm, obj);
NativeResult::Ok(JsValue::undefined())
@@ -142,6 +496,8 @@ pub fn module_star<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let default_si = vm.kernel_core().perm_interner().intern("default").0;
let keys = crate::object::walk_own_keys(vm, src);
let dst = unsafe { &mut *dst_ptr };
// live dst:star 复制的新导出名须一并入表,否则 2b 的活值读/枚举看不到条目。
let dst_table = ns_table_ptr(dst);
for (name_si, pos) in keys {
if name_si == default_si {
continue;
@@ -159,6 +515,10 @@ pub fn module_star<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if let Err(e) = result {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
if !dst_table.is_null() {
// 2a 保持快照语义:star 名立即视为已初始化。
push_initialized_entry(dst_table, name_si, value);
}
}
// star 复制后重排:直接导出与 star 导出的合并键序保持规范顺序。
crate::object::sort_namespace_exports(vm, dst);