mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 模块命名空间导出条目表与预注册
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
(
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 的未初始化读 / 键存在性 / 活值读不符"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user