diff --git a/Cargo.lock b/Cargo.lock index 50771e5f..8daa6a9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,6 +1202,7 @@ name = "oxide_emit" version = "0.1.0" dependencies = [ "num-bigint", + "oxc_allocator", "oxide_bytecode", "oxide_ir", "oxide_log", @@ -1303,6 +1304,7 @@ version = "0.1.0" dependencies = [ "oxide_bytecode", "oxide_compiler", + "oxide_emit", "oxide_kernel", "oxide_log", "oxide_parser", diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index 057984f8..ffac0733 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -79,6 +79,9 @@ pub struct CompiledModule { /// 全局扁平模块 id:编译末端 flatten 阶段分配(顶层 0,子模块 DFS 递增)。 /// `CREATE_CLOSURE` 的 imm16 在 flatten 后即此 id,运行时以它为平表下标。 pub flat_id: u32, + /// 是否为 ES module 顶层:VM 据此把顶层 `this` 绑定为 undefined + /// (模块环境记录 GetThisBinding 返回 undefined,区别于脚本全局 this)。 + pub is_es_module: bool, } impl CompiledModule { @@ -104,6 +107,7 @@ impl CompiledModule { upvalue_captures: Vec::new(), cells_needed: 0, flat_id: 0, + is_es_module: false, } } } @@ -136,6 +140,7 @@ impl Clone for CompiledModule { upvalue_captures: self.upvalue_captures.clone(), cells_needed: self.cells_needed, flat_id: self.flat_id, + is_es_module: self.is_es_module, } } } diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 9d084d27..1c74d914 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -38,12 +38,31 @@ impl Compiler { } } - /// 编译整个 program:AST → IR(`Emitter::emit_program`)→(可选 DCE)→(可选 - /// liveness/精确 DCE/RegAlloc)→ bytecode(`lower`)。 + /// 编译整个 script program:AST → IR(`Emitter::emit_program`)→ 统一 IR 管线。 pub fn compile(&self, program: &oxide_parser::Program) -> Result { crate::compiler_debug!("compile: starting..."); - let mut ir = Emitter::new().emit_program(program)?; - crate::compiler_debug!("compile: after emit {} insts", ir.insts.len()); + let ir = Emitter::new().emit_program(program)?; + self.compile_ir(ir) + } + + /// 编译 ES module:AST → IR(`Emitter::emit_program_module`,含依赖模块链接)→ 统一 IR 管线。 + /// `module_path` 为模块文件的规范路径(依赖解析基准 = 其父目录),依赖加载经 + /// `loader` 解析。 + pub fn compile_module( + &self, program: &oxide_parser::Program, module_path: &str, + loader: &mut dyn oxide_emit::module::ModuleSourceLoader, + ) -> Result { + crate::compiler_debug!("compile_module: starting..."); + let ir = Emitter::new().emit_program_module(program, module_path, loader)?; + let mut module = self.compile_ir(ir)?; + module.is_es_module = true; + Ok(module) + } + + /// 统一 IR 后处理管线:(可选 DCE)→(可选 liveness/精确 DCE/RegAlloc)→ + /// bytecode(`lower`)→ 子模块拍平。 + fn compile_ir(&self, mut ir: oxide_ir::IRFunction) -> Result { + crate::compiler_debug!("compile_ir: after emit {} insts", ir.insts.len()); if self.enable_dce { oxide_dce::dce(&mut ir); // 顶层函数;nested 不递归 crate::compiler_debug!("compile: after DCE {} insts", ir.insts.len()); diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index b58319cf..16619e6e 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -97,6 +97,7 @@ pub fn lower(f: &IRFunction) -> Result { upvalue_captures: f.upvalue_captures.clone(), cells_needed: f.cells_needed, flat_id: 0, + is_es_module: false, }) } diff --git a/crates/oxide_test262/Cargo.toml b/crates/oxide_test262/Cargo.toml index b4d90ce3..ab2f46b7 100644 --- a/crates/oxide_test262/Cargo.toml +++ b/crates/oxide_test262/Cargo.toml @@ -13,6 +13,7 @@ path = "src/main.rs" [dependencies] oxide_bytecode = { path = "../oxide_bytecode" } oxide_compiler = { path = "../oxide_compiler" } +oxide_emit = { path = "../oxide_emit" } oxide_vm = { path = "../oxide_vm" } oxide_parser = { path = "../oxide_parser" } oxide_kernel = { path = "../oxide_kernel" } diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index e1d1be55..3f751d93 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -436,6 +436,44 @@ fn run_test( } } +/// test262 模块用例的依赖加载器:以测试文件父目录为基准解析相对导入, +/// 读取磁盘上的 fixture 源码;json/text/bytes 经 import attributes 判定。 +struct Test262ModuleLoader; + +impl oxide_emit::module::ModuleSourceLoader for Test262ModuleLoader { + fn resolve( + &mut self, base_dir: &str, specifier: &str, attributes: &[(&str, &str)], + ) -> Result { + use oxide_emit::module::{ModuleKind, ResolvedModule}; + let kind = attributes + .iter() + .find(|(k, _)| *k == "type") + .map(|(_, v)| match *v { + "json" => ModuleKind::Json, + "text" => ModuleKind::Text, + "bytes" => ModuleKind::Bytes, + _ => ModuleKind::Js, + }) + .unwrap_or(ModuleKind::Js); + let base = Path::new(base_dir); + let full = if specifier.starts_with('/') { + PathBuf::from(specifier.trim_start_matches('/')) + } else { + base.join(specifier) + }; + let canonical = full + .canonicalize() + .map_err(|e| format!("cannot resolve module {specifier}: {e}"))?; + let content = std::fs::read_to_string(&canonical) + .map_err(|e| format!("cannot read module {specifier}: {e}"))?; + Ok(ResolvedModule { + source: content, + path: canonical.to_string_lossy().to_string(), + kind, + }) + } +} + /// 单测执行主流程:拼 harness 前缀 → parse → compile → run; /// 依据 `negative` 元数据校验期望错误,未实现特性按 no_skip 选择跳过或失败。 #[expect(clippy::too_many_arguments)] @@ -495,7 +533,12 @@ fn run_test_inner( }; let compiler = if no_regalloc { Compiler::new().with_regalloc(false) } else { Compiler::new() }; - let module = match compiler.compile(&program) { + let module = match if is_module { + let mut loader = Test262ModuleLoader; + compiler.compile_module(&program, &path.to_string_lossy().to_string(), &mut loader) + } else { + compiler.compile(&program) + } { Ok(m) => m, Err(e) => { let dur = start.elapsed().as_millis() as u64; diff --git a/crates/oxide_vm/src/bindings/bind_global.rs b/crates/oxide_vm/src/bindings/bind_global.rs index 72fe5ec9..a926669a 100644 --- a/crates/oxide_vm/src/bindings/bind_global.rs +++ b/crates/oxide_vm/src/bindings/bind_global.rs @@ -26,6 +26,15 @@ pub fn bind_global(core: &Arc, session: &KernelSession, global: &mut oxide_builtins::global::decode_uri_component:: as *const (), 1, ), + // 模块求值 / 命名空间内部辅助(import 实现;非 JS 标准全局,以下划线开头避免撞名)。 + ("__moduleObject", oxide_builtins::module::module_object:: as *const (), 0), + ("__moduleSet", oxide_builtins::module::module_set:: as *const (), 3), + ("__moduleGet", oxide_builtins::module::module_get:: as *const (), 2), + ("__moduleLinkGet", oxide_builtins::module::module_link_get:: as *const (), 2), + ("__moduleStar", oxide_builtins::module::module_star:: as *const (), 2), + ("__moduleSeal", oxide_builtins::module::module_seal:: as *const (), 1), + ("__moduleEval", oxide_builtins::module::module_eval:: as *const (), 1), + ("__moduleData", oxide_builtins::module::module_data:: as *const (), 2), ], ); } diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 1e033da6..9a0b6f03 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -333,9 +333,14 @@ impl Vm { } } - // 全局代码的 this 绑定为全局对象(ECMA-262 全局执行上下文)。 + // 顶层 this:脚本为全局对象(ECMA-262 全局执行上下文); + // ES module 顶层环境 GetThisBinding 返回 undefined。 let global = self.session.global_object(); - self.regs[254] = JsValue::from_js_object(global.as_ptr() as *mut JsObject); + self.regs[254] = if module.is_es_module { + JsValue::undefined() + } else { + JsValue::from_js_object(global.as_ptr() as *mut JsObject) + }; let result = self.dispatch(); // 顶层执行结束后 drain 微任务队列:Promise reactions 与 thenable 委托在此执行。