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);
+17
View File
@@ -371,6 +371,9 @@ impl Emitter {
// 导出名 → 本模块源绑定名:self-import 的局部名别名到该源绑定。star 转发的
// 自导入名不在表内(退化为下方非别名占位路径)。
let export_name_map = module_export_name_map(body, &ctx.module_self_import_specs);
// live 命名空间门控:仅自导入 `import * as ns from './self'` 的模块需要预注册
// 真实导出槽,其余模块(含普通外部命名空间导入)编译产物逐字节不变。
let mut has_self_ns_import = false;
for stmt in body {
if let Statement::ImportDeclaration(imp) = stmt {
let dep_spec = imp.source.value.to_string();
@@ -456,6 +459,9 @@ impl Emitter {
ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
// 命名空间绑定引用 ns 对象本身:自导入时同一对象,
// body 执行后导出自然可见。
if is_self {
has_self_ns_import = true;
}
self.emit_bind_target(
s.local.name.as_str(),
dep_ns_reg,
@@ -471,6 +477,17 @@ impl Emitter {
}
}
// —— live 命名空间预注册:早于 hoisted 函数声明的 __moduleSet 与 body 求值,
// 按名序预注册全部本地导出名,使自导入 ns 在读点先观察到未初始化状态。 ——
if has_self_ns_import {
let mut export_names: Vec<String> = own_export_names.into_iter().collect();
export_names.sort();
for name in export_names {
let name_reg = self.load_string_const(&name, ctx);
self.emit_module_call(ctx, "__modulePreRegister", &[ns_reg, name_reg])?;
}
}
// —— 别名捕获后处理:源绑定被嵌套函数捕获时,别名必须落到同一 cell,
// 否则两处各持一份值。无捕获时别名读经源寄存器天然活。此后源被捕获的
// var 名在模块入口统一 MAKE_CELL(undefined),与脚本顶层实例化同语义。——
@@ -264,3 +264,46 @@ fn self_import_default_alias_has_no_placeholder_writeback() {
// 不出现旧占位路径的额外回写。
assert_eq!(store_var_count(&ir), 1, "不应出现占位回写带来的第二条 STORE_VAR");
}
/// 面 10:零 diff 守卫 G1——非 live 模块不发射 `__modulePreRegister`。
/// live 门控为自导入 `import * as ns from './self'`;纯导出、default 表达式与
/// 普通外部命名空间导入均不得出现预注册调用。
#[test]
fn no_pre_register_for_non_live_modules() {
let (ir, _) = emit_module("export let x = 1;", "./entry.js", &[]).expect("导出声明应编译成功");
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "纯导出模块不得预注册");
let (ir, _) = emit_module("export default 42;", "./entry.js", &[]).expect("default 应编译成功");
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "default 表达式模块不得预注册");
let (ir, _) = emit_module(
"import * as ns from './dep.js'; ns;",
"./entry.js",
&[("./dep.js", ModuleKind::Js, "export var z = 1;")],
)
.expect("外部命名空间导入应编译成功");
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "外部 ns 导入不得预注册");
}
/// 面 10b:live 模块按静态导出名逐个预注册,`__moduleObject` 仍为零参调用。
#[test]
fn pre_register_for_self_namespace_import() {
let (ir, _) = emit_module(
"import * as ns from './self.js'; export const a = 1; export default 2;",
"./self.js",
&[("./self.js", ModuleKind::Js, "export const a = 1;")],
)
.expect("self-ns 模块应编译成功");
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 2, "a 与 default 各预注册一次");
assert!(pool_strings(&ir).contains("a") && pool_strings(&ir).contains("default"), "导出名应入池");
// __moduleObject 保持零实参签名:仅一次调用且压缩实参计数为 0。
let reg = builtin_reg(&ir, "__moduleObject").expect("应链接 __moduleObject");
let object_calls: Vec<_> = ir
.insts
.iter()
.filter(|i| i.op == OpCode::CALL_NATIVE && matches!(i.rd, oxide_ir::operand::Operand::Reg(r) if r == reg))
.collect();
assert_eq!(object_calls.len(), 1, "__moduleObject 只应调用一次");
assert_eq!(object_calls[0].ext.first().copied(), Some(0), "__moduleObject 应保持零实参");
}
@@ -28,6 +28,11 @@ pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
),
// 模块求值 / 命名空间内部辅助(import 实现;非 JS 标准全局,以下划线开头避免撞名)。
("__moduleObject", oxide_builtins::module::module_object::<crate::vm::Vm> as *const (), 0),
(
"__modulePreRegister",
oxide_builtins::module::module_pre_register::<crate::vm::Vm> as *const (),
2,
),
("__moduleSet", oxide_builtins::module::module_set::<crate::vm::Vm> as *const (), 3),
("__moduleGet", oxide_builtins::module::module_get::<crate::vm::Vm> as *const (), 2),
(
+11 -1
View File
@@ -6,7 +6,7 @@ use oxide_types::value::JsValue;
use rustc_hash::FxBuildHasher;
use crate::vm::Vm;
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, regexp, set, typed_array};
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array};
impl Vm {
pub(crate) fn is_session_escape_root_ptr(&self, target_ptr: *mut JsObject) -> bool {
@@ -99,6 +99,12 @@ impl Vm {
} else if src_ref.holds_compiled_regex() {
// 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。
regexp::clone_regexp_native(src_ref, dst_ref);
} else if src_ref.is_module_namespace() {
// 条目表深拷贝到新对象并改写值边:`clone_for_session_epoch` 只浅拷贝
// native_data 指针,共享同一表会在释放时双放。
module::clone_module_ns_native_with_rewrite(src_ref, dst_ref, |value| {
self.promote_value_if_epoch_object(value, forwarding)
});
}
// 账目计入:对象头 + 堆数据(属性/元素/meta Vec + native 状态盒),用 clone 后的 dst 核算。
self.gc_state.session_bytes_allocated +=
@@ -205,6 +211,10 @@ impl Vm {
crate::async_generator::rewrite_async_generator_native(obj, |value| {
self.promote_value_if_epoch_object(value, forwarding)
});
} else if obj.is_module_namespace() {
module::rewrite_module_ns_native(obj, |value| {
self.promote_value_if_epoch_object(value, forwarding)
});
}
}
}
+20 -1
View File
@@ -8,7 +8,7 @@ use oxide_types::value::JsValue;
use rustc_hash::FxBuildHasher;
use crate::vm::Vm;
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, regexp, set, typed_array};
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array};
/// session 级 mark-sweep GC 的状态与统计。
///
@@ -113,6 +113,7 @@ impl SessionGc {
bytes += map::map_native_size(obj);
bytes += set::set_native_size(obj);
bytes += module::module_ns_native_size(obj);
bytes += disposable_stack::disposable_stack_native_size(obj);
bytes += array_buffer::array_buffer_native_size(obj);
bytes += regexp::regexp_native_size(obj);
@@ -186,6 +187,11 @@ impl SessionGc {
Self::process_edge(value, vm, stack, live_strings, live_bigints);
}
}
if obj.is_module_namespace() {
for value in module::module_ns_native_edges(obj) {
Self::process_edge(value, vm, stack, live_strings, live_bigints);
}
}
if obj.is_generator_obj() {
for value in crate::generator::generator_native_edges(obj) {
Self::process_edge(value, vm, stack, live_strings, live_bigints);
@@ -347,6 +353,13 @@ impl SessionGc {
}
}
}
if obj.is_module_namespace() {
for value in module::module_ns_native_edges(obj) {
if value.is_string() {
Self::mark_string_live(live, value.as_string_ptr_mut());
}
}
}
if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
for value in disposable_stack::dispose_edges(obj) {
if value.is_string() {
@@ -503,6 +516,7 @@ impl SessionGc {
freed_bytes += map::drop_map_native(obj);
freed_bytes += set::drop_set_native(obj);
freed_bytes += module::drop_module_ns_native(obj);
freed_bytes += disposable_stack::drop_dispose_native(obj);
freed_bytes += array_buffer::drop_array_buffer_native(obj);
freed_bytes += regexp::drop_regexp_native(obj);
@@ -608,6 +622,9 @@ impl SessionGc {
} else if old_ref.holds_compiled_regex() {
// 已编译正则是 Box 深拷贝到新对象:源 Box 由 drop 释放,互不共享。
regexp::clone_regexp_native(old_ref, new_ref);
} else if old_ref.is_module_namespace() {
// 条目表深拷贝:`clone_for_session_epoch` 只浅拷贝 native_data 指针。
module::clone_module_ns_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);
@@ -654,6 +671,8 @@ impl SessionGc {
crate::async_generator::rewrite_async_generator_native(obj, |value| {
rewrite_forwarded_value(value, &forwarding)
});
} else if obj.is_module_namespace() {
module::rewrite_module_ns_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
}
}
+18
View File
@@ -49,6 +49,24 @@ impl Vm {
let mut current = Some(obj);
let mut depth = 0usize;
while let Some(obj) = current {
// 模块命名空间 exotic [[Get]]:条目表是权威状态,预注册但未初始化的
// 导出读抛 ReferenceError;非 live ns(无表)落普通属性路径零行为变化。
if obj.is_module_namespace() {
if let Some(state) = oxide_builtins::module::module_ns_export(obj, prop_name_si) {
return match state {
oxide_builtins::module::ModuleNsQuery::Initialized(v) => Ok(v),
oxide_builtins::module::ModuleNsQuery::Uninitialized => {
let msg = "Cannot access module export before initialization";
if self.native_call_depth == 0 {
self.raise_error_kind("ReferenceError", msg)?;
} else {
return Err(self.error_message_text("ReferenceError", msg));
}
Ok(JsValue::undefined())
}
};
}
}
if obj.is_array() && prop_name_si == length_si {
return Ok(obj.logical_len_value());
}
@@ -143,3 +143,46 @@ fn namespace_exotic_semantics() {
"命名空间 exotic 语义不符"
);
}
/// live 自导入 ns 的条目语义:预注册后初始化前读抛 ReferenceError,`in` /
/// `Reflect.has` 已为 true;初始化后读到活值;枚举未初始化名不抛错且键数正确。
/// 循环读 `ns.y` 同时钉住 IC 慢路径与普通路径读结果一致。
#[test]
fn namespace_self_import_live_entries() {
let cwd = std::env::current_dir().expect("cwd");
let dir = cwd.join("__module_namespace_live__");
std::fs::create_dir_all(&dir).expect("mkdir");
std::fs::write(
dir.join("main.mjs"),
"import * as ns from './main.mjs';\n \
const init = [];\n \
for (const k of ['x', 'y', 'default']) {\n \
try { ns[k]; init.push('value'); } catch (e) { init.push(e instanceof ReferenceError); }\n \
}\n \
const hasBefore = ['x', 'y', 'default'].map(k => k in ns).join(',');\n \
const namesBefore = Object.getOwnPropertyNames(ns).join(',');\n \
const ownKeysCount = Reflect.ownKeys(ns).length;\n \
export let x = 'sx';\n \
export var y = 2;\n \
export default 3;\n \
let sum = 0;\n \
for (let i = 0; i < 128; i++) { sum += ns.y; }\n \
globalThis.__ns = [\n \
init.join('|'),\n \
hasBefore,\n \
namesBefore,\n \
ownKeysCount,\n \
ns.x,\n \
sum,\n \
ns.default,\n \
].join(';');",
)
.expect("write main");
let _cleanup = Cleanup(dir.clone());
let result = run_namespace_module(&dir);
assert_eq!(
result, "true|true|true;true,true,true;default,x,y;4;sx;256;3",
"live 自导入 ns 的未初始化读 / 键存在性 / 活值读不符"
);
}