From 0ffb5d2cf3b4c7c8c0100d4c6432d48e036b0fe6 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 22 Aug 2026 11:15:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20tagged=20template=20=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E2=80=94=E2=80=94cooked=20undefined/this?= =?UTF-8?q?=E7=BB=91=E5=AE=9A/site=E7=BC=93=E5=AD=98/=E5=86=BB=E7=BB=93=20?= =?UTF-8?q?+=20groupBy=20getter=20=E5=BC=82=E5=B8=B8=E4=BC=A0=E6=92=AD=20+?= =?UTF-8?q?=20defineGetter=20setter=20=E4=BF=9D=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/map.rs | 12 +- crates/oxide_builtins/src/object.rs | 38 +++- crates/oxide_bytecode/src/opcode.rs | 8 + crates/oxide_emit/src/emit.rs | 8 + crates/oxide_emit/src/expr/template/tagged.rs | 205 ++++++++++++------ crates/oxide_ir/src/inst.rs | 26 +++ crates/oxide_vm/src/vm.rs | 26 ++- crates/oxide_vm/src/vm_dispatch_misc.rs | 99 +++++++++ crates/oxide_vm/src/vm_runtime.rs | 16 ++ crates/oxide_vm/src/vm_support.rs | 11 + 10 files changed, 366 insertions(+), 83 deletions(-) diff --git a/crates/oxide_builtins/src/map.rs b/crates/oxide_builtins/src/map.rs index 4034c640..78fcbc24 100644 --- a/crates/oxide_builtins/src/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -394,7 +394,7 @@ pub fn map_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let sym_iter_si = oxide_types::private_key::make_well_known_symbol_key(0); let iter_method = match unsafe { vm.ordinary_get(&*items_ptr, sym_iter_si, items_val) } { Ok(m) => m, - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; if !crate::iterator::is_callable(iter_method) { return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: items[Symbol.iterator] is not callable")); @@ -413,19 +413,19 @@ pub fn map_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let next_si = vm.kernel_core().perm_interner().intern("next").0; let next_fn = match vm.ordinary_get(iter_obj, next_si, iter_val) { Ok(f) => f, - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; // 读取 Map.set / Map.get 方法。 let map_ref = unsafe { &*map_obj }; let set_si = vm.kernel_core().perm_interner().intern("set").0; let adder = match vm.ordinary_get(map_ref, set_si, map_val) { Ok(v) => v, - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; let get_si = vm.kernel_core().perm_interner().intern("get").0; let getter = match vm.ordinary_get(map_ref, get_si, map_val) { Ok(v) => v, - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; if !crate::iterator::is_callable(adder) || !crate::iterator::is_callable(getter) { return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: Map.set/get is not callable")); @@ -446,7 +446,7 @@ pub fn map_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let done_si = vm.kernel_core().perm_interner().intern("done").0; let done = match vm.ordinary_get(nr, done_si, next_result) { Ok(v) => oxide_runtime_api::to_boolean(v), - Err(_) => false, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; if done { break; @@ -454,7 +454,7 @@ pub fn map_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let value_si = vm.kernel_core().perm_interner().intern("value").0; let element = match vm.ordinary_get(nr, value_si, next_result) { Ok(v) => v, - Err(_) => JsValue::undefined(), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; // 调用 callbackFn(element, counter):counter 对数组 items 即元素下标 k。 let group_key = match vm.call_function_sync(callback_val, JsValue::undefined(), &[element, JsValue::int(counter)]) { diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 07f20b2c..3f7b68d4 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -712,6 +712,26 @@ pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> let obj_ptr = obj_val.as_js_object_ptr(); let key = vm.property_key_si(vm.reg(args[2])); let obj = unsafe { &*obj_ptr }; + + // 数组 length 是虚拟属性(无 shape 槽,ordinary_get 直接返回逻辑长度): + // 描述符 {value: len, writable: !frozen, enumerable: false, configurable: false}。 + // 冻结数组(含模板对象)writable=false;普通数组 writable=true。 + let length_si = vm.kernel_core().perm_interner().intern("length").0; + if obj.is_array() && key == length_si { + let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + let desc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto))); + let sh = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge; + let sf = vm.kernel_core().perm_interner().as_ref() as *const PermInterner; + let d: &mut JsObject = unsafe { &mut *desc }; + let sh = unsafe { &*sh }; + let sf = unsafe { &*sf }; + push_desc_prop(d, sh, sf.intern("value").0, obj.logical_len_value()); + push_desc_prop(d, sh, sf.intern("writable").0, JsValue::bool(!obj.is_frozen())); + push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(false)); + push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(false)); + return NativeResult::Ok(JsValue::from_js_object(desc)); + } + let Some(offset) = vm.get_own_property_slot(obj, key) else { return NativeResult::Ok(JsValue::undefined()); }; @@ -1275,7 +1295,15 @@ pub fn object_proto_define_getter(vm: &mut H, args: &[u8]) -> NativeR }; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; let attrs = PropAttributes::new(false, true, true); - if let Err(e) = vm.define_accessor_property(obj, key_si, getter, JsValue::undefined(), attrs) { + // DefinePropertyOrThrow 的 desc 无 [[Set]] 键:覆盖既有访问器时保留其 setter + // (define_accessor_property 是全量替换,set 缺省须显式传现有值)。 + let existing_set = vm + .get_own_property_slot(obj, key_si) + .and_then(|pos| obj.prop_meta_at(pos)) + .filter(|m| m.is_accessor) + .map(|m| m.set) + .unwrap_or(JsValue::undefined()); + if let Err(e) = vm.define_accessor_property(obj, key_si, getter, existing_set, attrs) { return NativeResult::Err(crate::error::create_type_error(vm, &e)); } NativeResult::Ok(JsValue::undefined()) @@ -1347,7 +1375,7 @@ pub fn object_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let sym_iter_si = make_well_known_symbol_key(0); let iter_method = match unsafe { vm.ordinary_get(&*items_ptr, sym_iter_si, items_val) } { Ok(m) => m, - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; if !is_callable(iter_method) { return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: items[Symbol.iterator] is not callable")); @@ -1366,7 +1394,7 @@ pub fn object_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let next_si = vm.kernel_core().perm_interner().intern("next").0; let next_fn = match vm.ordinary_get(iter_obj, next_si, iter_val) { Ok(f) => f, - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; // 创建结果对象(OrdinaryObjectCreate(null),与 Object.create(null) 同款)。 let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); @@ -1387,7 +1415,7 @@ pub fn object_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let done_si = vm.kernel_core().perm_interner().intern("done").0; let done = match vm.ordinary_get(nr, done_si, next_result) { Ok(v) => oxide_runtime_api::to_boolean(v), - Err(_) => false, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; if done { break; @@ -1395,7 +1423,7 @@ pub fn object_group_by(vm: &mut H, args: &[u8]) -> NativeResult { let value_si = vm.kernel_core().perm_interner().intern("value").0; let element = match vm.ordinary_get(nr, value_si, next_result) { Ok(v) => v, - Err(_) => JsValue::undefined(), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), }; // 调用 callbackFn(element, counter):counter 对数组 items 即元素下标 k。 let key = match vm.call_function_sync(callback_val, JsValue::undefined(), &[element, JsValue::int(counter)]) { diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index d4dce56d..e3c6348d 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -335,6 +335,14 @@ define_opcodes! { def = Some(SlotSpec::Slot(Slot::Rd)), uses = [SlotSpec::TemplateExprs], pure = true, jump = false, term = false, ic = false, + // ── 标签模板对象 (0x97) ── + // GetTemplateObject:按 site 缓存模板对象(同一编译树同 site 恒返回同一对象), + // 首次构建时冻结 cooked/raw 数组并设置规范描述符。ext 布局见 + // `Inst::get_template_object`;无寄存器 use(quasis 全为常量池下标)。 + GET_TEMPLATE_OBJECT = 0x97 => "GET_TEMPLATE_OBJECT", + def = Some(SlotSpec::Slot(Slot::Rd)), uses = [], + pure = false, jump = false, term = false, ic = false, + // ── 小语言特性 (0x39-0x3B) ── DELETE_PROP_STATIC = 0x39 => "DELETE_PROP_STATIC", def = None, uses = [SlotSpec::Slot(Slot::Rd)], diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index f90635fd..04a76544 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -230,6 +230,10 @@ pub struct CompileCtx { pub(crate) module_self_import_specs: HashSet, /// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。 pub(crate) module_self_aliases: HashMap, + /// 标签模板 site 计数器:本编译树内全局唯一(子 ctx 继承父值继续递增)。 + /// 运行时与模块 flat_id 组成模板对象缓存键,保证同一编译树同 site 恒返回 + /// 同一对象、不同编译树(eval 每次编译)互不共享。 + pub(crate) next_template_site: u32, } /// 函数体编译上下文:决定 `this`/`super` 绑定与参数前导(prologue)形态。 @@ -337,6 +341,7 @@ impl CompileCtx { module_dep_ns_regs: HashMap::new(), module_self_import_specs: HashSet::new(), module_self_aliases: HashMap::new(), + next_template_site: 0, } } @@ -1059,6 +1064,9 @@ impl Emitter { ctx.scopes.private_element_kinds = parent_ctx.scopes.private_element_kinds.clone(); ctx.scopes.private_brand_id = parent_ctx.scopes.private_brand_id; ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id; + // 标签模板 site 序号继承:整棵编译树全局唯一(跨嵌套函数递增), + // 运行时以 (flat_id, site_no) 缓存模板对象,跨函数不冲突。 + ctx.next_template_site = parent_ctx.next_template_site; // 传递 enclosing_this_reg:嵌套箭头函数捕获正确的 `this`。 ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg; diff --git a/crates/oxide_emit/src/expr/template/tagged.rs b/crates/oxide_emit/src/expr/template/tagged.rs index 1305acfa..0cfa5f36 100644 --- a/crates/oxide_emit/src/expr/template/tagged.rs +++ b/crates/oxide_emit/src/expr/template/tagged.rs @@ -1,107 +1,73 @@ -//! 标签模板表达式 emit:`emit_tagged_template_expression` 构造 template object 并调用标签函数。 +//! 标签模板表达式 emit:`emit_tagged_template_expression` 按 GetTemplateObject 语义 +//! 经 GET_TEMPLATE_OBJECT 指令取(缓存)模板对象并调用标签函数。 use crate::{CompileCtx, Emitter}; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; +use oxide_parser::Expression; impl Emitter { pub(crate) fn emit_tagged_template_expression( &self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx, ) -> Result { - let quasis = &tt.quasi.quasis; - let expressions = &tt.quasi.expressions; + // 标签求值 + receiver 绑定:成员表达式标签保留对象作 this(与 call.rs 同款 + // 约定),其余标签 this = undefined。 + let (tag_reg, this_reg) = self.emit_tagged_tag(&tt.tag, ctx)?; - let tag_reg = self.emit_expression(&tt.tag, ctx)?; - - let cooked_temp = ctx.alloc_reg(); - ctx.inst(Inst::new( - OpCode::NEW_ARRAY, - Operand::Reg(cooked_temp), - Operand::Imm(quasis.len() as u16), - Operand::None, - )); - for (i, quasi) in quasis.iter().enumerate() { - let s = quasi.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); - let const_idx = ctx.add_constant(Constant::String(s)); - let str_reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(str_reg), const_idx)); - let idx_const = ctx.add_constant(Constant::Int(i as i32)); - let idx_reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx_const)); - ctx.inst(Inst::new( - OpCode::SET_ELEM, - Operand::Reg(cooked_temp), - Operand::Reg(idx_reg), - Operand::Reg(str_reg), - )); + // 模板对象:GetTemplateObject 语义(cooked/raw 数组 + raw 属性,冻结 + + // 按 site 缓存)由 GET_TEMPLATE_OBJECT 指令在运行时统一构建/复用。 + // cooked 为 None(非法转义)→ 元素 undefined;raw 恒为字符串。 + let mut cooked_words: Vec = Vec::with_capacity(tt.quasi.quasis.len()); + let mut raw_idxs: Vec = Vec::with_capacity(tt.quasi.quasis.len()); + for quasi in &tt.quasi.quasis { + match quasi.value.cooked.as_ref() { + Some(c) => { + let idx = ctx.add_constant(Constant::String(c.to_string())); + cooked_words.push(idx as u32); + } + None => cooked_words.push(0x8000_0000), + } + let raw_idx = ctx.add_constant(Constant::String(quasi.value.raw.to_string())); + raw_idxs.push(raw_idx); } - - let raw_temp = ctx.alloc_reg(); - ctx.inst(Inst::new( - OpCode::NEW_ARRAY, - Operand::Reg(raw_temp), - Operand::Imm(quasis.len() as u16), - Operand::None, - )); - for (i, quasi) in quasis.iter().enumerate() { - let raw = quasi.value.raw.to_string(); - let const_idx = ctx.add_constant(Constant::String(raw)); - let str_reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(str_reg), const_idx)); - let idx_const = ctx.add_constant(Constant::Int(i as i32)); - let idx_reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx_const)); - ctx.inst(Inst::new( - OpCode::SET_ELEM, - Operand::Reg(raw_temp), - Operand::Reg(idx_reg), - Operand::Reg(str_reg), - )); - } - - // 模板对象:按 GetTemplateObject 语义把 raw 数组挂到 cooked 数组的 "raw" - // 属性,使标签函数首参为标准模板对象(cooked 数组 + raw 属性)。 - // String.raw / 模板标签测试依赖 template.raw 存在。 - let raw_key_idx = ctx.add_constant(Constant::String("raw".to_string())); - let raw_key_reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(raw_key_reg), raw_key_idx)); - ctx.inst(Inst::new( - OpCode::SET_PROP, - Operand::Reg(cooked_temp), - Operand::Reg(raw_temp), - Operand::Reg(raw_key_reg), + let site_no = ctx.next_template_site; + ctx.next_template_site += 1; + let template_reg = ctx.alloc_reg(); + ctx.inst(Inst::get_template_object( + Operand::Reg(template_reg), + site_no, + &cooked_words, + &raw_idxs, )); + // 表达式实参:求值顺序在模板对象之后(规范 TaggedTemplate 求值序: + // 标签引用 → GetTemplateObject → 实参表达式)。 let mut expr_temps = Vec::new(); - for expr in expressions { + for expr in &tt.quasi.expressions { expr_temps.push(self.emit_expression(expr, ctx)?); } let cooked_slot = ctx.alloc_reg(); let mut expr_slots = Vec::new(); - for _ in expressions { + for _ in &tt.quasi.expressions { expr_slots.push(ctx.alloc_reg()); } ctx.inst(Inst::new( OpCode::LOAD_VAR, Operand::Reg(cooked_slot), - Operand::Reg(cooked_temp), + Operand::Reg(template_reg), Operand::None, )); for (slot, temp) in expr_slots.iter().zip(expr_temps.iter()) { ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(*slot), Operand::Reg(*temp), Operand::None)); } - let undef_idx = ctx.add_constant(Constant::Undefined); - let undef_reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); - - let arg_count = 1 + expressions.len(); + let arg_count = 1 + tt.quasi.expressions.len(); ctx.inst(Inst::call( Operand::Reg(tag_reg), - Operand::Reg(undef_reg), + Operand::Reg(this_reg), Operand::Reg(cooked_slot), arg_count as u8, )); @@ -110,4 +76,101 @@ impl Emitter { ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::None, Operand::None)); Ok(result_reg) } -} + + /// 标签表达式的 `(callee, this)` 对:成员表达式(静态/计算/私有字段)保留 + /// 对象作 receiver(GetThisValue(tagRef)),其余标签 this = undefined。 + /// 与 call.rs 的 `emit_call_expression` 同款求值模式。 + fn emit_tagged_tag(&self, tag: &Expression, ctx: &mut CompileCtx) -> Result<(u32, u32), String> { + let undefined_reg = { + let idx = ctx.add_constant(Constant::Undefined); + let r = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(r), idx)); + r + }; + let pair = match tag { + Expression::StaticMemberExpression(member) => { + if matches!(&member.object, Expression::Super(_)) { + if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { + return Err("super property only supported in class methods".into()); + } + let prop_name = member.property.name.as_str(); + let key_reg = ctx.alloc_reg(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx)); + let this_reg = ctx.alloc_reg(); + ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(this_reg), Operand::This, Operand::None)); + let callee_reg = ctx.alloc_reg(); + let op = if ctx.in_static_method { + OpCode::SUPER_STATIC_GET_PROP + } else { + OpCode::SUPER_GET_PROP + }; + ctx.inst(Inst::new(op, Operand::Reg(callee_reg), Operand::Reg(this_reg), Operand::Reg(key_reg))); + (callee_reg, this_reg) + } else { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let callee_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(callee_reg), + Operand::Reg(obj_reg), + Operand::None, + )); + let prop_name = member.property.name.as_str(); + let key_reg = ctx.alloc_reg(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx)); + ctx.inst(Inst::ic_get(Operand::Reg(callee_reg), Operand::Reg(key_reg))); + (callee_reg, obj_reg) + } + } + Expression::ComputedMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + if let Some(key) = crate::expr::member::computed_const_key(&member.expression) { + let key_reg = ctx.alloc_reg(); + let idx = ctx.add_constant(Constant::String(key)); + ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx)); + let callee_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(callee_reg), + Operand::Reg(obj_reg), + Operand::None, + )); + ctx.inst(Inst::ic_get(Operand::Reg(callee_reg), Operand::Reg(key_reg))); + (callee_reg, obj_reg) + } else { + let key_reg = self.emit_expression(&member.expression, ctx)?; + let callee_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + Operand::Reg(callee_reg), + )); + (callee_reg, obj_reg) + } + } + Expression::PrivateFieldExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let name = member.field.name.as_str(); + let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?; + let key_reg = self.emit_private_id_reg(name, ctx)?; + let callee_reg = ctx.alloc_reg(); + ctx.inst(Inst::get_private( + Operand::Reg(callee_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + brand_reg, + brand_id, + )); + (callee_reg, obj_reg) + } + _ => { + let tag_reg = self.emit_expression(tag, ctx)?; + (tag_reg, undefined_reg) + } + }; + Ok(pair) + } +} \ No newline at end of file diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 35cf836a..a4abaf27 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -293,6 +293,32 @@ impl Inst { } } + /// GET_TEMPLATE_OBJECT:构建/取缓存模板对象(GetTemplateObject 语义)。 + /// + /// # ext 布局 + /// - `ext[0]`:quasis 段数 n; + /// - `ext[1..=2n]`:每段交错两个字——cooked 字(`0x8000_0000` 高位标记 + /// cooked 为 None(非法转义 → 元素为 undefined),低 31 位是常量池下标) + /// 与 raw 字(常量池下标); + /// - `ext[2n+1]`:site 序号(编译树内全局唯一,与模块 flat_id 组成缓存键)。 + pub fn get_template_object(dst: Operand, site_no: u32, cooked_idx: &[u32], raw_idx: &[u16]) -> Self { + let n = cooked_idx.len(); + let mut ext = SmallVec::with_capacity(2 + 2 * n); + ext.push(n as u32); + for i in 0..n { + ext.push(cooked_idx[i]); + ext.push(raw_idx[i] as u32); + } + ext.push(site_no); + Self { + op: OpCode::GET_TEMPLATE_OBJECT, + rd: dst, + a: Operand::None, + b: Operand::None, + ext, + } + } + // ── RegAlloc 辅助指令 ── /// 寄存器复制 rd = a(RegAlloc 区间拆分时搬值)。 diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index b8488249..084e4276 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1,6 +1,6 @@ #![allow(clippy::arc_with_non_send_sync)] -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use std::sync::{Arc, OnceLock}; use num_traits::Zero; @@ -375,6 +375,7 @@ pub(crate) struct InlineSyncState { pub(crate) inline_args_base: u32, pub(crate) inline_args_count: u16, pub(crate) accessor_frame_target_reg: Option, + pub(crate) active_flat_id: u32, } /// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。 @@ -522,6 +523,15 @@ pub struct Vm { /// 分组保存 inline cache 与指令计数器。 pub(crate) profiling: ProfilingState, pub(crate) cell_stack: Vec>, + /// 标签模板对象缓存(GetTemplateObject):键 = (模块 flat_id, site 序号)。 + /// 同一编译树同 site 恒返回同一对象;每次 `run()` 清空(flat_id 复用防误命中)。 + /// 值为 GC 根(for_each_value/rewrite_values 遍历)。 + pub(crate) template_objects: HashMap<(u32, u32), JsValue>, + /// 当前活动字节码所属模块的 flat_id(顶层 0;帧切换时随 bytecode 换)。 + /// GET_TEMPLATE_OBJECT 据其区分不同编译树(eval 每次编译独立 site)。 + pub(crate) active_flat_id: u32, + /// 帧切换时暂存调用方 flat_id 的栈(与 saved_bytecode_stack 同步 push/pop)。 + pub(crate) saved_flat_id_stack: Vec, } impl Drop for Vm { @@ -791,6 +801,10 @@ impl Vm { f(self.async_context.unwrap_or(JsValue::undefined())); f(self.async_gen_context.unwrap_or(JsValue::undefined())); f(self.inline_callee.unwrap_or(JsValue::undefined())); + // 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。 + for &cached in self.template_objects.values() { + f(cached); + } for &entry in &self.iters.for_of_iters { f(entry.iterator); f(entry.last_result); @@ -843,6 +857,9 @@ impl Vm { self.exception_value = self.exception_value.map(&mut rewrite); self.pending_exception = self.pending_exception.map(&mut rewrite); self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite); + for cached in self.template_objects.values_mut() { + *cached = rewrite(*cached); + } self.pending_completion = self.pending_completion.map(|completion| match completion { Completion::Return { value, @@ -1393,6 +1410,9 @@ impl Vm { self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode)); self.saved_immutables_stack.push(self.active_immutables); + // 记录调用方 flat_id,进入被调模块(标签模板 site 缓存按模块隔离)。 + self.saved_flat_id_stack.push(self.active_flat_id); + self.active_flat_id = sub_idx as u32; let function_name = self.sub_modules[sub_idx] .function_name @@ -1889,6 +1909,10 @@ impl Vm { self.dispatch_template_str(rd)?; } + OpCode::GET_TEMPLATE_OBJECT => { + self.dispatch_get_template_object(rd)?; + } + OpCode::DELETE_PROP_STATIC => match self.dispatch_delete_prop_static(rd) { Ok(true) => continue, Ok(false) => {} diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 050d5d20..3582ea60 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -1013,4 +1013,103 @@ impl Vm { } Ok(()) } + + /// GET_TEMPLATE_OBJECT:GetTemplateObject 语义——按 (模块 flat_id, site 序号) + /// 查缓存,未命中则构建模板对象(cooked/raw 数组 + raw 属性)并冻结。 + /// + /// # ext 布局(紧跟指令后的扩展字) + /// - `n`:quasis 段数; + /// - 每段两个字:cooked 字(高位 `0x8000_0000` 标记非法转义 → undefined, + /// 低 31 位为常量池下标)、raw 字(常量池下标); + /// - 末尾:site 序号。 + /// + /// # 副作用 + /// - 首次构建时分配 cooked/raw 数组并写入 `template_objects` 缓存(GC 根); + /// 缓存命中时零分配。 + pub(crate) fn dispatch_get_template_object(&mut self, rd: usize) -> Result<(), String> { + vm_trace!("GET_TEMPLATE_OBJECT rd={}", rd); + let n = self.bytecode[self.pc] as usize; + self.pc += 1; + let mut cooked_words = Vec::with_capacity(n); + let mut raw_idxs = Vec::with_capacity(n); + for _ in 0..n { + cooked_words.push(self.bytecode[self.pc]); + self.pc += 1; + raw_idxs.push(self.bytecode[self.pc]); + self.pc += 1; + } + let site_no = self.bytecode[self.pc]; + self.pc += 1; + + let key = (self.active_flat_id, site_no); + if let Some(&cached) = self.template_objects.get(&key) { + self.regs[rd] = cached; + return Ok(()); + } + + let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject; + let proto_val = JsValue::from_js_object(proto_ptr); + // 模板对象直接分配为 session 对象:跨 epoch 写入(存进 global/数组)时 + // 免 promote 搬移——若按 epoch 分配,首次写入 session 根会把对象搬到新 + // 地址,缓存中的旧指针与新实例分叉,同一 site 两次取值将返回不同对象。 + let mut alloc_session_array = |n: usize| { + let mut clone = JsObject::new_array(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, proto_val, n, self.epoch.bump()); + // 标记 session 归属:session_epoch 分配的对象须显式置位,GC/释放路径 + // 据 SESSION_EPOCH_BIT 判定归属(与 promote_object 的 clone 路径一致)。 + clone.set_session_epoch(true); + let ptr = self.gc_state.session_epoch.alloc(clone) as *mut JsObject; + self.gc_state.session_object_ptrs.push(ptr); + ptr + }; + let cooked = alloc_session_array(n); + let raw = alloc_session_array(n); + let cooked_val = JsValue::from_js_object(cooked); + let raw_val = JsValue::from_js_object(raw); + + // 元素:可枚举、不可写、不可配置(模板对象属性恒只读,禁止改写元素)。 + let elem_attrs = PropAttributes::new(false, true, false); + for i in 0..n { + let (c_val, r_val) = { + // immutables 借用限于本块:promote 需 &mut self,先取值再放行借用。 + let imm = self.immutables(); + let c_val = if cooked_words[i] & 0x8000_0000 != 0 { + JsValue::undefined() + } else { + imm.get(cooked_words[i] as usize).copied().unwrap_or(JsValue::undefined()) + }; + let r_val = imm.get(raw_idxs[i] as usize).copied().unwrap_or(JsValue::undefined()); + (c_val, r_val) + }; + let c_promoted = self.promote_if_needed_for_write_ptr(cooked, c_val); + let r_promoted = self.promote_if_needed_for_write_ptr(raw, r_val); + // SAFETY: cooked/raw 为本函数刚分配的 epoch 对象,借用仅在本循环内消费。 + unsafe { + (*cooked).set_prop_at(i, c_promoted); + (*cooked).set_data_meta(i, elem_attrs); + (*raw).set_prop_at(i, r_promoted); + (*raw).set_data_meta(i, elem_attrs); + } + } + + // raw 属性:不可枚举、不可写、不可配置(规范 desc 无 writable/enumerable + // 键,DefinePropertyOrThrow 默认 false)。先于冻结定义(冻结后不可扩展, + // 新增属性会被拒)。 + let raw_si = self.kernel_core.perm_interner().intern("raw").0; +// SAFETY: cooked 为本函数分配的 epoch 对象,借用仅在本次 define 内消费。 + self.define_data_property(unsafe { &mut *cooked }, raw_si, raw_val, PropAttributes::new(false, false, false))?; + + // 冻结两个数组:不可扩展 + length writable=false(is_frozen 使 length + // 赋值与元素写入经 ordinary_set 拒绝);length {e:false,w:false,c:false} + // 由 getOwnPropertyDescriptor 的数组 length 特判表达。 + // SAFETY: cooked/raw 为本函数分配的 session 对象。 + unsafe { + (*cooked).set_frozen(true); + (*cooked).set_extensible(false); + (*raw).set_frozen(true); + (*raw).set_extensible(false); + } + self.template_objects.insert(key, cooked_val); + self.regs[rd] = cooked_val; + Ok(()) + } } diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 70348f88..446f9177 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -109,6 +109,9 @@ macro_rules! inline_save_field { ($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => { $recv.accessor_frame_target_reg }; + ($recv:ident, $window_regs:ident, active_flat_id, copy) => { + $recv.active_flat_id + }; } /// restore 方向的字段写回语句。`regs` 只回拷窗口并把缓冲归还池。 @@ -189,6 +192,9 @@ macro_rules! inline_restore_field { ($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => { $recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg }; + ($recv:ident, $saved:ident, active_flat_id, copy) => { + $recv.active_flat_id = $saved.active_flat_id + }; } /// 内联同步调用可搬移执行核心字段的单一登记表。save/restore 双向由本宏展开; @@ -225,6 +231,7 @@ macro_rules! inline_core_fields { (inline_args_base, copy), // M (inline_args_count, copy), // M (accessor_frame_target_reg, copy), // M + (active_flat_id, copy), // M ) }; } @@ -370,6 +377,7 @@ impl Vm { self.pc = 0; self.bytecode = Arc::clone(&sub.bytecode); self.activate_immutables(sub_idx, &sub.constants); + self.active_flat_id = sub_idx as u32; self.active_reg_limit = sub.n_registers.max(1); self.root_reg_limit = self.active_reg_limit; self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); @@ -424,6 +432,9 @@ impl Vm { if let Some(saved_bc) = self.saved_bytecode_stack.pop() { self.bytecode = saved_bc; } + if let Some(saved_flat) = self.saved_flat_id_stack.pop() { + self.active_flat_id = saved_flat; + } vm_debug!( "restore_frame: return_addr={} bc_len={} saved_stack={} fn={:?}", frame.return_addr, @@ -466,6 +477,11 @@ impl Vm { pub fn run(&mut self, module: &CompiledModule) -> Result { vm_debug!("run: starting bytecode execution, {} instructions", module.bytecode.len()); self.clear_execution_state(); + // 模板对象缓存按 run 清空:flat_id 每次 run 从 0 重新分配,跨 run 复用会 + // 让不同编译树的 site 误命中(缓存键 = (flat_id, site_no))。 + self.template_objects.clear(); + self.saved_flat_id_stack.clear(); + self.active_flat_id = 0; self.cell_stack.clear(); self.cell_stack.push(Vec::new()); self.sub_modules = Arc::new(collect_flat_modules(module)); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index ce9c93a0..dcc29b73 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -117,6 +117,9 @@ impl Vm { instruction_count: 0, }, cell_stack: Vec::new(), + template_objects: std::collections::HashMap::new(), + active_flat_id: 0, + saved_flat_id_stack: Vec::new(), }; vm.init_generator_intrinsics(); vm.init_promise_intrinsics(); @@ -223,6 +226,9 @@ impl Vm { instruction_count: 0, }, cell_stack: Vec::new(), + template_objects: std::collections::HashMap::new(), + active_flat_id: 0, + saved_flat_id_stack: Vec::new(), }; vm.init_generator_intrinsics(); vm.init_promise_intrinsics(); @@ -304,6 +310,11 @@ impl Vm { fn clear_full_reset_state(&mut self) { self.clear_execution_state(); + // 模板对象缓存中的 JsValue 指向将被重建的 session 对象:全量重置后悬垂, + // 必须随 session 一并清空。 + self.template_objects.clear(); + self.saved_flat_id_stack.clear(); + self.active_flat_id = 0; self.bytecode = Arc::default(); self.immutables_cache.clear(); self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);