diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index 72ac524..28d4fea 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -373,7 +373,7 @@ pub fn array_concat(vm: &mut H, args: &[u8]) -> NativeResult { if !o_ptr.is_null() { let o = unsafe { &*o_ptr }; let on = o.prop_count() as usize; - // spread array elements if it's a real array + // 真数组时展开元素。 if o.is_array() && on > 0 { for i in 0..on { all.push(o.get_prop_at(i)); @@ -423,8 +423,8 @@ pub fn array_join(vm: &mut H, args: &[u8]) -> NativeResult { /// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。 pub fn array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { - // Array.prototype.toString() delegates to join with the default "," separator, - // ignoring its own arguments per spec. + // Array.prototype.toString() 委托给 join,默认用 "," 分隔, + // 按规范忽略自身参数。 array_join(vm, &[args[0]]) } @@ -440,7 +440,7 @@ pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(JsValue::int(-1)); } let target = vm.reg(args[1]); - // fromIndex + // fromIndex:负索引从尾部折算,NaN 视为 0。 let from_index = if args.len() >= 3 { let v = vm.reg(args[2]); let f = vm.coerce_number_bounded(v).unwrap_or(0.0); @@ -499,7 +499,7 @@ pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { }; for i in from_index..n { let elem = arraylike_get(vm, ptr, is_array, i); - // SameValueZero: NaN === NaN, +0 === -0 + // SameValueZero:NaN 视为相等、+0 与 -0 视为相同。 if same_value_zero(elem, target) { return NativeResult::Ok(JsValue::bool(true)); } @@ -507,9 +507,9 @@ pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(false)) } -/// SameValueZero (ES2015 7.2.10): NaN === NaN, +0 === -0 +/// SameValueZero(ES2015 §7.2.10):NaN 视为相等、+0 与 -0 视为相同。 fn same_value_zero(a: JsValue, b: JsValue) -> bool { - // 都是数字时特殊处理 + // 双数字时特殊处理。 let a_is_num = a.is_int() || a.is_double(); let b_is_num = b.is_int() || b.is_double(); if a_is_num && b_is_num { @@ -518,7 +518,7 @@ fn same_value_zero(a: JsValue, b: JsValue) -> bool { if av.is_nan() && bv.is_nan() { return true; } - return av == bv; // +0 == -0 in Rust f64 + return av == bv; // Rust f64 中 +0 == -0。 } oxide_runtime_api::strict_equality(a, b) } @@ -1042,7 +1042,7 @@ pub fn array_last_index_of(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(JsValue::int(-1)); } let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - // fromIndex (default: n-1) + // fromIndex(缺省:n-1)。 let from_index_isize: isize = if args.len() >= 3 { let v = vm.reg(args[2]); let f = vm.coerce_number_bounded(v).unwrap_or(0.0); diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 39c13e3..81409f9 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -54,8 +54,8 @@ pub(crate) fn new_array_buffer(vm: &mut H, data: Vec) -> *mut JsO obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; let len = data.len(); let data_ptr = Box::into_raw(Box::new(data)); - // SAFETY: ArrayBuffer objects are not callable, so their native_fn slot is reused - // as an opaque Box> pointer, matching the existing RegExp storage pattern. + // SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明 `Box>` + // 指针,与既有 RegExp 存储模式一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data_ptr as *const ()) })); set_named_prop( vm, @@ -95,7 +95,7 @@ pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { if data_ptr.is_null() { return 0; } - // SAFETY: ArrayBuffer stores `Box>` in native_fn. + // SAFETY: ArrayBuffer 在 native_fn 槽中存 `Box>`。 let data = unsafe { Box::from_raw(data_ptr) }; let bytes = std::mem::size_of::>() + data.capacity(); obj.set_native_fn(None); diff --git a/crates/oxide_builtins/src/boolean.rs b/crates/oxide_builtins/src/boolean.rs index d4d98a5..f18947f 100644 --- a/crates/oxide_builtins/src/boolean.rs +++ b/crates/oxide_builtins/src/boolean.rs @@ -14,7 +14,7 @@ pub fn boolean_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } else if arg.is_bool() { arg.as_bool() } else if arg.is_string() { - // SAFETY: arg is a string value. + // SAFETY: arg 已确认是字符串值。 !unsafe { (*arg.as_string_ptr()).is_empty() } } else if arg.is_object() { true diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index 1843d6a..1cd353c 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -155,8 +155,8 @@ pub fn data_view_constructor(vm: &mut H, args: &[u8]) -> NativeResult byte_offset, byte_length, })); - // SAFETY: DataView objects are not callable, so native_fn is an opaque Box - // pointer, matching ArrayBuffer and RegExp typed-storage patterns. + // SAFETY: DataView 对象不可调用,native_fn 存不透明 `Box` 指针, + // 与 ArrayBuffer / RegExp 的类型化存储模式一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data as *const ()) })); set_named_prop(vm, &mut obj, "buffer", buffer, PropAttributes::new(false, false, false)); set_named_prop( diff --git a/crates/oxide_builtins/src/date.rs b/crates/oxide_builtins/src/date.rs index 0f496ab..1a14da8 100644 --- a/crates/oxide_builtins/src/date.rs +++ b/crates/oxide_builtins/src/date.rs @@ -155,7 +155,7 @@ pub fn date_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } else { let val = vm.reg(args[1]); if val.is_string() { - // SAFETY: val is a string value. + // SAFETY: val 已确认是字符串值。 let s = unsafe { (*val.as_string_ptr()).data.clone() }; let formats = ["%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%S%.f"]; let mut ts = f64::NAN; @@ -231,7 +231,7 @@ pub fn date_parse(vm: &mut H, args: &[u8]) -> NativeResult { if !val.is_string() { return NativeResult::Ok(JsValue::float(f64::NAN)); } - // SAFETY: val is a string value. + // SAFETY: val 已确认是字符串值。 let s = unsafe { (*val.as_string_ptr()).data.clone() }; let mut ts = f64::NAN; if let Ok(dt) = chrono::DateTime::parse_from_rfc2822(&s) { @@ -804,8 +804,8 @@ pub fn date_get_year(vm: &mut H, args: &[u8]) -> NativeResult { pub fn date_set_year(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); - // A missing year argument coerces to NaN; per B.2.4.2 a NaN year sets the date value to - // NaN and returns NaN. + // 缺省年份参数强制转为 NaN;按 Annex B 语义,NaN 年份把日期值置为 + // NaN 并返回 NaN。 let arg = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let y_num = oxide_runtime_api::to_number(arg); if y_num.is_nan() { diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 7a10f70..7d1c0ce 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -25,7 +25,7 @@ fn bind_dispatcher(vm: &mut H, args: &[u8]) -> NativeResult { .and_then(|v| v.get(1).copied()) .unwrap_or(JsValue::undefined()); - // Forward bound call arguments (skip args[0], the bound-wrapper receiver). + // 转发绑定后的调用实参(跳过 args[0] 即绑定包装器的 receiver)。 let arg_regs: Vec = args.iter().skip(1).copied().collect(); invoke_target(vm, bound_target, bound_this, &arg_regs) } @@ -59,7 +59,7 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { if !arr_ptr.is_null() { let arr = unsafe { &*arr_ptr }; if arr.is_array() { - let max_args = 55usize; // base=200, max safe register is 255. + let max_args = 55usize; // base=200,255 号寄存器之前的安全上限。 let n = arr.hash_props_vec().map_or(0, |v| v.len()).min(max_args); let base = 200u8; arg_regs = (0..n).map(|i| base + i as u8).collect(); diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 8815a94..4e36137 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -41,7 +41,7 @@ pub fn make_iterator_for_value(vm: &mut H, value: JsValue) -> Result< let next_fn = make_native_function(vm, "next", iterator_wrapper_next:: as *const (), 0); vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn); - // Forward IteratorClose to the inner iterator so for-of abrupt completion can clean up. + // 把 IteratorClose 转发给内层迭代器,使 for-of 异常退出时可清理。 let return_si = vm.kernel_core().perm_interner().intern("return").0; let return_fn = make_native_function(vm, "return", iterator_wrapper_return:: as *const (), 0); vm.set_or_create_prop_value(wrapper_obj, return_si, return_fn); @@ -104,8 +104,8 @@ pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult }; return match vm.call_function_sync(next, inner, &[]) { Ok(result) => NativeResult::Ok(result), - // Forward the ORIGINAL thrown value (any type) instead of re-wrapping it as a - // TypeError, so a surrounding try/catch sees the real error. + // 透传原始抛出的值(任意类型)而非重新包装成 TypeError, + // 使外围 try/catch 能看到真正的错误。 Err(err) => match vm.take_uncaught_value() { Some(original) => NativeResult::Err(original), None => NativeResult::Err(crate::error::create_type_error(vm, &err)), @@ -158,7 +158,7 @@ fn next_array_like(vm: &mut H, wrapper: &mut JsObject, inner: JsValue return Some(make_iter_result(vm, JsValue::undefined(), true)); } - // for-of loop default iterators: Map yields [key, value] entries, Set yields values. + // for-of 循环默认迭代:Map 产出 [key, value] 对,Set 产出值。 if is_map_value(inner) { return Some(map_set_step(vm, wrapper, inner, index_si, MapSetMode::MapEntries)); } @@ -194,7 +194,7 @@ fn make_native_function(vm: &mut H, name: &str, native_fn: *const (), let function_proto = vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject; let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto)); func.set_function(true); - // SAFETY: native_fn is provided from a NativeFn fn item. + // SAFETY: native_fn 来自 NativeFn 函数项。 func.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(native_fn) })); func.set_native_arg_count(arg_count); let func = vm.alloc_object(func); @@ -245,7 +245,7 @@ fn make_map_set_pair(vm: &mut H, a: JsValue, b: JsValue) -> JsValue { 2, vm.epoch().bump(), )); - // SAFETY: pair is a freshly allocated array JsObject in the current epoch. + // SAFETY: pair 是当前 epoch 内新分配的数组 JsObject。 unsafe { (*pair).set_prop_at(0, a); (*pair).set_prop_at(1, b); @@ -263,10 +263,9 @@ enum MapSetMode { SetValues, } -/// Advance a Map/Set iterator wrapper one step, yielding the {value, done} result for -/// the requested mode. Entries live in the indexmap stored in the collection's -/// native-data slot; the (a, b) pair is copied out before any allocation so no borrow -/// of the native collection is held across a `vm` call. +/// 把 Map/Set 迭代器包装器推进一步,按指定模式产出 `{value, done}` 结果。 +/// 条目存放在集合 native-data 槽的 indexmap 中;(a, b) 对在任意分配之前拷出, +/// 使对 native 集合的借用不会跨越 `vm` 调用保持。 fn map_set_step( vm: &mut H, wrapper: &mut JsObject, inner: JsValue, index_si: u32, mode: MapSetMode, ) -> JsValue { @@ -342,9 +341,9 @@ pub(crate) fn set_entries_iter_next(vm: &mut H, args: &[u8]) -> Nativ map_set_next_dispatch::(vm, args, MapSetMode::SetEntries) } -/// Build an iterator wrapper whose `next` delegates to a caller-supplied mode-specific -/// native function. Mirrors `make_iterator_for_value` but lets Map/Set prototype methods -/// pick the values/keys/entries variant. +/// 构造迭代器包装器,其 `next` 委托给调用方指定的按模式分发的 native 函数。 +/// 与 `make_iterator_for_value` 一致,但允许 Map/Set 原型方法选择 +/// values/keys/entries 变体。 pub(crate) fn make_mode_iterator(vm: &mut H, inner: JsValue, next_fn: *const ()) -> JsValue { let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let wrapper = vm diff --git a/crates/oxide_builtins/src/json.rs b/crates/oxide_builtins/src/json.rs index f3475c5..bebf190 100644 --- a/crates/oxide_builtins/src/json.rs +++ b/crates/oxide_builtins/src/json.rs @@ -20,7 +20,7 @@ pub fn json_parse(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Err(crate::error::create_syntax_error(vm, "JSON.parse: argument is not a string")); } let text = { - // SAFETY: val is a string value. + // SAFETY: val 已确认是字符串值。 unsafe { (*val.as_string_ptr()).data.clone() } }; @@ -31,7 +31,7 @@ pub fn json_parse(vm: &mut H, args: &[u8]) -> NativeResult { let mut result = value_to_jsvalue(vm, &parsed); - // Apply reviver if provided (D-05) + // 提供 reviver 时以后序遍历逐属性调用,重建解析后的值。 if args.len() > 2 { let reviver_val = vm.reg(args[2]); if reviver_val.is_object() { @@ -66,7 +66,7 @@ fn walk_reviver( }; let mut val = holder.get_prop_at(pos); - // Post-order: recurse on children first + // 后序遍历:先递归处理子节点。 if val.is_object() { let obj_ptr = val.as_js_object_ptr(); if !obj_ptr.is_null() { @@ -78,20 +78,20 @@ fn walk_reviver( let child_si = vm.kernel_core().perm_interner().intern(&index_str).0; walk_reviver(vm, obj_ptr, child_si, reviver)?; } - // Re-read value after children modified it + // 子节点可能已改写父级,重新读取当前值。 val = holder.get_prop_at(pos); } else { let keys = walk_own_keys(vm, obj); for (child_si, _child_pos) in keys { walk_reviver(vm, obj_ptr, child_si, reviver)?; } - // Re-read value after children modified it + // 子节点可能已改写父级,重新读取当前值。 val = holder.get_prop_at(pos); } } } - // Call reviver on current value + // 对当前值调用 reviver,用返回值覆盖属性槽。 let kc = vm.kernel_core().clone(); let key_str = kc.perm_interner().lookup(key_si).unwrap_or(""); let key_val = vm.new_string(key_str); @@ -364,7 +364,7 @@ fn stringify_object( out.push('{'); let keys = walk_own_keys(vm, obj); - // Pre-compute (name, position) pairs with whitelist filtering + // 预计算 (名称, 槽位) 对,并应用白名单过滤。 let kc = vm.kernel_core().clone(); let perm_interner = kc.perm_interner(); let entries: Vec<(String, u32)> = keys @@ -384,7 +384,7 @@ fn stringify_object( for (name, pos) in entries { let val = obj.get_prop_at(pos); - // Replacer function callback (toJSON handled by jsvalue_to_json) + // replacer 函数回调(toJSON 已在 jsvalue_to_json 中处理)。 let val = if let Some(replacer) = replacer_fn { let key_val = vm.new_string(&name); let holder = JsValue::from_js_object(obj as *const JsObject as *mut JsObject); @@ -468,7 +468,7 @@ fn stringify_array( let index_str = i.to_string(); - // Replacer function callback (toJSON handled by jsvalue_to_json) + // replacer 函数回调(toJSON 已在 jsvalue_to_json 中处理)。 let val = if let Some(replacer) = replacer_fn { let key_val = vm.new_string(&index_str); let holder = JsValue::from_js_object(obj as *const JsObject as *mut JsObject); diff --git a/crates/oxide_builtins/src/map.rs b/crates/oxide_builtins/src/map.rs index ebcae42..fc4ae64 100644 --- a/crates/oxide_builtins/src/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -17,16 +17,15 @@ macro_rules! native_try { pub(crate) type MapInner = indexmap::IndexMap; -/// Retrieve the `IndexMap` pointer stored in a Map object's native-data slot. +/// 取出 Map 对象 native-data 槽中存储的 `IndexMap` 指针。 /// -/// # Safety contract maintained by callers +/// # 调用方维护的安全性契约 /// -/// The pointer is valid as long as the Map `JsObject` itself is alive, which is guaranteed -/// because the `JsObject` lives in the current `Epoch` arena and `Epoch::reset()` is never -/// called while a native builtin is executing. The `Box` that owns the allocation -/// is created in `new_map_inner()` and is never freed until the process exits (intentional -/// leak — lifetime is tied to the epoch). Only one live `*mut` alias exists at a time -/// per Map object because native calls are single-threaded. +/// 指针在 Map `JsObject` 存活期间有效:`JsObject` 分配于当前 `Epoch` arena, +/// native builtin 执行期间不会调用 `Epoch::reset()`。持有分配的 +/// `Box` 由 `new_map_inner()` 创建,进程退出前不释放(生命周期与 +/// epoch 绑定,属有意为之)。native 调用为单线程,同一 Map 对象同时至多 +/// 存在一个活 `*mut` 别名。 fn get_map_inner(vm: &mut H, this_val: JsValue) -> Result<*mut MapInner, JsValue> { if !this_val.is_object() { return Err(crate::error::create_type_error(vm, "called on non-Map object")); @@ -35,9 +34,8 @@ fn get_map_inner(vm: &mut H, this_val: JsValue) -> Result<*mut MapInn if map_ptr.is_null() { return Err(crate::error::create_type_error(vm, "Map internal state invalid")); } - // SAFETY: map_ptr is a non-null, aligned pointer to a JsObject bump-allocated in the - // current Epoch. It remains valid for the duration of this call (epoch is not reset - // during native execution). + // SAFETY: map_ptr 是当前 Epoch bump 分配的 JsObject 的非空、对齐指针; + // native 执行期间 epoch 不重置,指针在本调用内有效。 let map_obj = unsafe { &*map_ptr }; if !map_obj.is_map() { return Err(crate::error::create_type_error( @@ -45,9 +43,9 @@ fn get_map_inner(vm: &mut H, this_val: JsValue) -> Result<*mut MapInn "Map.prototype method called on incompatible receiver", )); } - // SAFETY: native_data holds the raw pointer written by `alloc_map`. - // The pointer is a valid, heap-allocated `Box>`. - // Alignment: IndexMap requires at most 8-byte alignment; the global allocator satisfies this. + // SAFETY: native_data 持有 `alloc_map` 写入的裸指针,即有效的 + // 堆分配 `Box>`;IndexMap 至多要求 8 字节对齐, + // 全局分配器满足该要求。 let inner_ptr = map_obj.native_data() as *mut MapInner; if inner_ptr.is_null() { return Err(crate::error::create_type_error(vm, "Map internal state invalid")); diff --git a/crates/oxide_builtins/src/math.rs b/crates/oxide_builtins/src/math.rs index 074e202..3618fe4 100644 --- a/crates/oxide_builtins/src/math.rs +++ b/crates/oxide_builtins/src/math.rs @@ -15,7 +15,7 @@ fn arg1(vm: &mut H, args: &[u8]) -> f64 { } fn arg2(vm: &mut H, args: &[u8]) -> (f64, f64) { - // Sequenced (not a tuple literal) so the two coercions take &mut H one at a time. + // 顺序求值(非元组字面量):两次 coercion 各自单独借用 &mut H。 let a = if args.len() > 1 { num(vm, args[1]) } else { f64::NAN }; let b = if args.len() > 2 { num(vm, args[2]) } else { f64::NAN }; (a, b) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 6cdb501..c9d2176 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -84,7 +84,6 @@ pub fn object_keys(vm: &mut H, args: &[u8]) -> NativeResult { .map(|(si, _offset)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) .collect(); } - // keys let n = key_names.len(); let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = vm.alloc_object(JsObject::new_array( @@ -721,8 +720,8 @@ pub fn object_has_own(vm: &mut H, args: &[u8]) -> NativeResult { /// `Object.prototype.valueOf`:返回 this 本身(配合 OrdinaryToPrimitive 的兜底)。 pub fn object_proto_value_of(vm: &mut H, args: &[u8]) -> NativeResult { - // Object.prototype.valueOf returns the `this` object unchanged; OrdinaryToPrimitive - // then falls through to toString since the result is not primitive. + // Object.prototype.valueOf 原样返回 this 对象;OrdinaryToPrimitive + // 因结果非原始值而继续走 toString。 NativeResult::Ok(vm.reg(args[0])) } diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 7109332..e15f6a2 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -88,10 +88,10 @@ pub fn regexp_constructor(vm: &mut H, args: &[u8]) -> NativeResult { match compiled { Ok(re) => { let re_ptr = Box::into_raw(Box::new(re)); - // SAFETY: re_ptr is a Box pointer stored by the constructor via - // NativeFnPtr::from_raw(re_ptr as *const ()). RegExp objects repurpose the native_fn - // field to hold the compiled Regex — not a NativeFn pointer. Valid for the object's - // lifetime; VM reset drops the Box through `drop_regexp_native`. + // SAFETY: re_ptr 是构造器经 `NativeFnPtr::from_raw(re_ptr as *const ())` + // 写入的 `Box` 指针。RegExp 对象把 native_fn 字段复用作 + // 已编译 Regex 的存放处——而非 NativeFn 指针。对象存活期间有效; + // VM 重置经 `drop_regexp_native` 释放该 Box。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) })); } Err(e) => { @@ -149,7 +149,7 @@ pub fn regexp_test(vm: &mut H, args: &[u8]) -> NativeResult { Some(p) => p, }; - // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. + // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] })); let last_index = vm.coerce_number_bounded(get_prop(re, 0)).unwrap_or(f64::NAN) as usize; @@ -181,7 +181,7 @@ pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { } }; - // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. + // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] })); diff --git a/crates/oxide_builtins/src/set.rs b/crates/oxide_builtins/src/set.rs index 8f68faf..01b492d 100644 --- a/crates/oxide_builtins/src/set.rs +++ b/crates/oxide_builtins/src/set.rs @@ -33,7 +33,7 @@ impl PartialEq for SetKey { } return a == b; } - // SAFETY: JsValue is an 8-byte NaN-boxed Copy value; raw bits define non-double identity here. + // SAFETY: JsValue 是 8 字节 NaN-box Copy 值;此处以原始位定义非 double 值的同一性。 unsafe { std::mem::transmute::(self.0) == std::mem::transmute::(other.0) } } } @@ -52,7 +52,7 @@ impl Hash for SetKey { d.to_bits().hash(state); } } else { - // SAFETY: JsValue is an 8-byte NaN-boxed Copy value; hashing raw bits matches equality above. + // SAFETY: JsValue 是 8 字节 NaN-box Copy 值;哈希原始位与上面的相等判定一致。 unsafe { std::mem::transmute::(self.0).hash(state) } } } @@ -60,15 +60,14 @@ impl Hash for SetKey { pub(crate) type SetInner = indexmap::IndexSet; -/// Retrieve the `IndexSet` pointer stored in a Set object's native-data slot. +/// 取出 Set 对象 native-data 槽中存储的 `IndexSet` 指针。 /// -/// # Safety contract maintained by callers +/// # 调用方维护的安全性契约 /// -/// The pointer is valid as long as the Set `JsObject` itself is alive. The `JsObject` -/// lives in the current `Epoch` arena and `Epoch::reset()` is never called while a -/// native builtin is executing. The `Box` is allocated in `new_set_inner()` -/// and is never freed during the Set's lifetime. Only one live `*mut` alias exists per -/// Set object at a time because native calls are single-threaded. +/// 指针在 Set `JsObject` 存活期间有效:`JsObject` 分配于当前 `Epoch` arena, +/// native builtin 执行期间不会调用 `Epoch::reset()`。持有分配的 +/// `Box` 由 `new_set_inner()` 创建,Set 生命周期内不释放。 +/// native 调用为单线程,同一 Set 对象同时至多存在一个活 `*mut` 别名。 fn get_set_inner(vm: &mut H, this_val: JsValue) -> Result<*mut SetInner, JsValue> { if !this_val.is_object() { return Err(crate::error::create_type_error(vm, "called on non-Set object")); @@ -77,15 +76,15 @@ fn get_set_inner(vm: &mut H, this_val: JsValue) -> Result<*mut SetInn if set_ptr.is_null() { return Err(crate::error::create_type_error(vm, "Set internal state invalid")); } - // SAFETY: set_ptr is a non-null, aligned pointer to a JsObject bump-allocated in the - // current Epoch. Remains valid for the duration of this call. + // SAFETY: set_ptr 是当前 Epoch bump 分配的 JsObject 的非空、对齐指针, + // 本调用期间有效。 let set_obj = unsafe { &*set_ptr }; if !set_obj.is_set() { return Err(crate::error::create_type_error(vm, "Set.prototype.add called on incompatible receiver")); } - // SAFETY: native_data holds the raw pointer written by `alloc_set`. - // The pointer is a valid, heap-allocated `Box>`. - // Alignment: IndexSet requires at most 8-byte alignment; the global allocator satisfies this. + // SAFETY: native_data 持有 `alloc_set` 写入的裸指针,即有效的 + // 堆分配 `Box>`;IndexSet 至多要求 8 字节对齐, + // 全局分配器满足该要求。 let inner_ptr = set_obj.native_data() as *mut SetInner; if inner_ptr.is_null() { return Err(crate::error::create_type_error(vm, "Set internal state invalid")); @@ -265,7 +264,7 @@ pub fn set_values(vm: &mut H, args: &[u8]) -> NativeResult { pub fn set_keys(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let _inner = native_try!(get_set_inner(vm, this_val)); - // Set keys() is an alias for values() — same per-element iterator. + // Set 的 keys() 是 values() 的别名——返回同样的逐元素迭代器。 NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index b86e64c..860a401 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -544,13 +544,13 @@ pub fn string_ends_with(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.split called with {} args", args.len()); let s = this_string(vm, args); - // Spec: if separator is undefined, return [string] + // 规范:separator 为 undefined 时返回 [string]。 if args.len() < 2 || vm.reg(args[1]).is_undefined() { let parts = vec![s.clone()]; return NativeResult::Ok(make_string_array(vm, &parts)); } let sep_val = vm.reg(args[1]); - // ToUint32(limit), default 2^32-1 + // ToUint32(limit),缺省为 2^32-1。 let limit = if args.len() > 2 { let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])); if l.is_infinite() { @@ -591,7 +591,7 @@ pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { } return NativeResult::Ok(make_string_array(vm, &parts)); } - // Fall back to string path for regex-like objects without native engine regex + // 无原生正则的类正则对象回退到字符串路径。 } let sep = as_string(vm, sep_val); @@ -661,7 +661,7 @@ pub fn string_replace(vm: &mut H, args: &[u8]) -> NativeResult { }; return NativeResult::Ok(vm.new_string(&result)); } - // Fall back to string path for regex-like objects without native engine regex + // 无原生正则的类正则对象回退到字符串路径。 } let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; @@ -727,7 +727,7 @@ pub fn string_search(vm: &mut H, args: &[u8]) -> NativeResult { Some(p) => p, None => return NativeResult::Ok(JsValue::int(-1)), }; - // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. + // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; if let Some(m) = regex.find(&s) { return NativeResult::Ok(JsValue::int(m.start() as i32)); @@ -841,7 +841,7 @@ pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { JsValue::from_js_object(stub_ptr) }; - // Build wrapper object + // 构建包装对象。 builder_wrapper(vm, &s, re_obj) } @@ -962,7 +962,7 @@ pub fn string_replace_all(vm: &mut H, args: &[u8]) -> NativeResult { Some(p) => p, None => return NativeResult::Ok(vm.new_string(&s)), }; - // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. + // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let result = regex.replace_all(&s, replacement.as_str()).to_string(); return NativeResult::Ok(vm.new_string(&result)); diff --git a/crates/oxide_builtins/src/stubs.rs b/crates/oxide_builtins/src/stubs.rs index 6bd1659..e24cb39 100644 --- a/crates/oxide_builtins/src/stubs.rs +++ b/crates/oxide_builtins/src/stubs.rs @@ -4,36 +4,36 @@ fn stub_error(vm: &mut H, name: &str) -> NativeResult { NativeResult::Err(crate::error::create_type_error(vm, &format!("{name} is not implemented"))) } -// Architecturally deferred — these features are excluded from the supported language subset by design. -/// Stub for the `Proxy` global. Always throws a TypeError; Proxy is deferred by design. +// 架构性延后:这些特性按设计排除在受支持语言子集之外。 +/// `Proxy` 全局占位:始终抛 TypeError。Proxy 按设计延后实现。 pub fn proxy_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "Proxy") } -/// Stub for the `BigInt` global. Always throws a TypeError; BigInt is deferred by design. +/// `BigInt` 全局占位:始终抛 TypeError。BigInt 按设计延后实现。 pub fn bigint_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "BigInt") } -/// Stub for the `WeakMap` global. Always throws a TypeError; WeakMap is deferred by design. +/// `WeakMap` 全局占位:始终抛 TypeError。WeakMap 按设计延后实现。 pub fn weakmap_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "WeakMap") } -/// Stub for the `WeakSet` global. Always throws a TypeError; WeakSet is deferred by design. +/// `WeakSet` 全局占位:始终抛 TypeError。WeakSet 按设计延后实现。 pub fn weakset_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "WeakSet") } -/// Stub for the `WeakRef` global. Always throws a TypeError; WeakRef is deferred by design. +/// `WeakRef` 全局占位:始终抛 TypeError。WeakRef 按设计延后实现。 pub fn weakref_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "WeakRef") } -/// Stub for the `FinalizationRegistry` global. Always throws a TypeError; deferred by design. +/// `FinalizationRegistry` 全局占位:始终抛 TypeError。按设计延后实现。 pub fn finalization_registry_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "FinalizationRegistry") } -/// Stub for the `SharedArrayBuffer` global. Always throws a TypeError; deferred by design. +/// `SharedArrayBuffer` 全局占位:始终抛 TypeError。按设计延后实现。 pub fn shared_array_buffer_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "SharedArrayBuffer") } -/// Stub for the `Atomics` global. Always throws a TypeError; deferred by design. +/// `Atomics` 全局占位:始终抛 TypeError。按设计延后实现。 pub fn atomics_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "Atomics") } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 3cb794b..abda3dd 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -89,8 +89,8 @@ fn create_typed_array( byte_offset, length, })); - // SAFETY: TypedArray instances are not callable; native_fn stores opaque Box, - // matching ArrayBuffer/DataView typed-object storage in this VM. + // SAFETY: TypedArray 实例不可调用,native_fn 存不透明 `Box`, + // 与本 VM 中 ArrayBuffer/DataView 的类型化对象存储一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data as *const ()) })); let byte_length = length.saturating_mul(kind.bytes_per_element()); diff --git a/crates/oxide_builtins/tests/builtins_json_tests.rs b/crates/oxide_builtins/tests/builtins_json_tests.rs index 9da44c7..dfdcc48 100644 --- a/crates/oxide_builtins/tests/builtins_json_tests.rs +++ b/crates/oxide_builtins/tests/builtins_json_tests.rs @@ -82,7 +82,7 @@ fn tojson_called_before_serialize() { #[test] fn tojson_non_callable_ignored() { let (_, result) = eval(r#"JSON.stringify({toJSON:'not-a-function', a:1})"#).unwrap(); - // toJSON property is a string, should be serialized normally + // toJSON 属性是字符串,应按普通属性序列化。 assert!(stringify_val(&result).contains("toJSON")); assert!(stringify_val(&result).contains(r#""a":1"#)); } @@ -117,8 +117,8 @@ fn reviver_transform_values() { fn reviver_delete_property() { let (_, result) = eval(r#"JSON.parse('{"a":1,"b":2}', function(k,v){if(k==='a')return undefined;return v})"#).unwrap(); - // The property is soft-deleted (set to undefined) - // For test262 compatibility, stringify should omit undefined properties + // 属性被软删除(置为 undefined)。 + // 为 test262 兼容,stringify 应省略 undefined 属性。 assert!(result.is_object()); } diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index 983cb36..cd31cf9 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -50,22 +50,17 @@ pub struct CompiledModule { pub param_base: u8, pub builtin_reg_map: Vec<(String, u32)>, pub sub_modules: Vec, - /// True when this module is an arrow function body. - /// Arrow functions capture lexical `this` from the enclosing scope. + /// 是否为箭头函数体(箭头函数从外围作用域词法捕获 `this`)。 pub is_arrow: bool, - /// Index into `constants` holding the captured `this` JsValue. - /// 0 means "not captured - use standard this binding". + /// 捕获 `this` 的 JsValue 在常量池中的下标;0 表示未捕获,使用标准 this 绑定。 pub captured_this_const_idx: u16, - /// Function name inferred from assignment context. - /// Set at the VariableDeclaration / ObjectProperty assignment site. + /// 由赋值上下文推断的函数名,在变量声明 / 对象属性赋值点设置。 pub function_name: Option, - /// True when this bytecode function is a class constructor. - /// Ordinary CALL must reject it, while NEW_EXPRESSION may construct through it. + /// 是否为类构造函数(普通 CALL 必须拒绝它,仅 NEW_EXPRESSION 可经它构造)。 pub is_class_constructor: bool, - /// True when this class constructor has an `extends` clause. - /// `this` stays uninitialized until SUPER_CALL completes. + /// 类构造函数是否有 `extends` 子句(`this` 在 SUPER_CALL 完成前保持未初始化)。 pub is_derived_constructor: bool, - /// True for prototype methods whose function object needs a runtime home_object. + /// 原型方法是否需要运行时 home_object。 pub needs_home_object: bool, pub upvalue_captures: Vec, pub cells_needed: u8, diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 4f92fd7..6b141c9 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -6,21 +6,19 @@ use std::fmt; -/// Generate the `OpCode` enum plus its `TryFrom` and `Display` impls from a -/// single table. Each row is `VARIANT = 0xXX => "DISPLAY"` — the byte value is -/// the enum discriminant, the `TryFrom` match arm, and the `Display` name come -/// from the same row, so the three tables can never drift. A new opcode is one -/// row. +/// 从单张表生成 [`OpCode`] 枚举及其 `TryFrom`、`Display` 实现。 /// -/// Grouping comments (`// -- Arithmetic --`) are stripped by the lexer before -/// macro expansion, so they can be interleaved freely between rows. +/// 每行 `VARIANT = 0xXX => "DISPLAY"` 同时提供枚举判别值、`TryFrom` 匹配分支 +/// 与 `Display` 名称三份信息,保证三表永不漂移;新增操作码只需加一行。 +/// +/// # 注意事项 +/// - 分组注释(`// ── 算术 ──`)在宏展开前被词法器剥离,可自由插入行间。 macro_rules! define_opcodes { ( $( $name:ident = $val:literal => $disp:literal ),+ $(,)? ) => { - /// OpCode for register-based bytecode VM. + /// 寄存器字节码虚拟机的操作码。 /// - /// Organized in groups of 16 for readability. Implemented opcodes have - /// emitter support in the compiler; placeholder opcodes are reserved - /// for future phases (IC, profiling, parallelization). + /// 按 16 个一组组织便于阅读。已实现的操作码在编译器中有发射支持; + /// 占位符操作码为后续阶段预留(IC、profiling、并行化)。 #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(non_camel_case_types)] @@ -64,11 +62,11 @@ define_opcodes! { COMPOUND_DIV = 0x09 => "COMPOUND_DIV", COMPOUND_MOD = 0x0A => "COMPOUND_MOD", COMPOUND_EXP = 0x0B => "COMPOUND_EXP", - MOV = 0x0C => "MOV", // rd=dst, a=src(寄存器复制,D-07,区间拆分搬值) - SPILL = 0x0D => "SPILL", // rd=src, ext=[slot u16](溢出写 VM spill 栈,D-06) - UNSPILL = 0x0E => "UNSPILL", // rd=dst, ext=[slot u16](从 VM spill 栈恢复,D-06) + MOV = 0x0C => "MOV", // rd=dst, a=src(寄存器复制,区间拆分搬值) + SPILL = 0x0D => "SPILL", // rd=src, ext=[slot u16](寄存器值写 VM spill 栈) + UNSPILL = 0x0E => "UNSPILL", // rd=dst, ext=[slot u16](从 VM spill 栈恢复寄存器) - // -- Comparison (0x10-0x1F) -- + // ── 比较 (0x10-0x1F) ── EQ = 0x10 => "EQ", NEQ = 0x11 => "NEQ", LT = 0x12 => "LT", @@ -83,14 +81,14 @@ define_opcodes! { STRICT_NEQ = 0x1C => "STRICT_NEQ", UNARY_PLUS = 0x1D => "UNARY_PLUS", - // -- Control Flow (0x20-0x2F) -- + // ── 控制流 (0x20-0x2F) ── JMP = 0x20 => "JMP", JMP_IF_FALSE = 0x21 => "JMP_IF_FALSE", JMP_IF_TRUE = 0x22 => "JMP_IF_TRUE", FOR_OF_INIT = 0x23 => "FOR_OF_INIT", FOR_OF_NEXT = 0x24 => "FOR_OF_NEXT", - // -- Update (0x25-0x28) -- + // ── 更新 (0x25-0x28) ── INC_PRE = 0x25 => "INC_PRE", INC_POST = 0x26 => "INC_POST", DEC_PRE = 0x27 => "DEC_PRE", @@ -102,7 +100,7 @@ define_opcodes! { SWITCH_TABLE = 0x2C => "SWITCH_TABLE", FOR_IN_CLEANUP = 0x2D => "FOR_IN_CLEANUP", - // -- Exception (0x2E-0x2F, 0x33-0x35) -- + // ── 异常 (0x2E-0x2F, 0x33-0x35) ── THROW = 0x2E => "THROW", TRY_BEGIN = 0x2F => "TRY_BEGIN", TRY_END = 0x33 => "TRY_END", @@ -111,10 +109,10 @@ define_opcodes! { FOR_OF_DONE = 0x36 => "FOR_OF_DONE", FOR_OF_CLOSE = 0x37 => "FOR_OF_CLOSE", - // -- Template Literal (0x38) -- + // ── 模板字符串 (0x38) ── TEMPLATE_STR = 0x38 => "TEMPLATE_STR", - // -- Small Language Features (0x39-0x3B) -- + // ── 小语言特性 (0x39-0x3B) ── DELETE_PROP_STATIC = 0x39 => "DELETE_PROP_STATIC", DELETE_PROP_DYNAMIC = 0x3A => "DELETE_PROP_DYNAMIC", INSTANCEOF = 0x3B => "INSTANCEOF", @@ -123,12 +121,12 @@ define_opcodes! { REST_OBJECT = 0x3E => "REST_OBJECT", CELL_SET = 0x3F => "CELL_SET", - // -- Variable (0x30-0x32) -- + // ── 变量 (0x30-0x32) ── LOAD_VAR = 0x30 => "LOAD_VAR", STORE_VAR = 0x31 => "STORE_VAR", LOAD_CONST = 0x32 => "LOAD_CONST", - // -- Call (0x40-0x4F) -- + // ── 调用 (0x40-0x4F) ── CALL = 0x40 => "CALL", RETURN = 0x41 => "RETURN", CALL_NATIVE = 0x42 => "CALL_NATIVE", @@ -154,7 +152,7 @@ define_opcodes! { NEW_ARRAY = 0x57 => "NEW_ARRAY", SET_ELEM = 0x58 => "SET_ELEM", - // -- Member Update (0x59-0x62) -- + // ── 成员更新 (0x59-0x62) ── MEMBER_INC = 0x59 => "MEMBER_INC", MEMBER_DEC = 0x5A => "MEMBER_DEC", DYN_MEMBER_INC = 0x5B => "DYN_MEMBER_INC", @@ -166,13 +164,13 @@ define_opcodes! { COMPOUND_MEMBER_MOD = 0x61 => "COMPOUND_MEMBER_MOD", COMPOUND_MEMBER_EXP = 0x62 => "COMPOUND_MEMBER_EXP", - // -- Profiling - placeholders (0x63-0x6F) -- + // ── Profiling — 占位符 (0x63-0x6F) ── PROFILE_TYPE = 0x63 => "PROFILE_TYPE", PROFILE_SHAPE = 0x64 => "PROFILE_SHAPE", PROFILE_BRANCH = 0x65 => "PROFILE_BRANCH", PROFILE_CALL = 0x66 => "PROFILE_CALL", - // ── Parallel — placeholders (0x70-0x7F) ── + // ── 并行 — 占位符 (0x70-0x7F) ── FORK = 0x70 => "FORK", JOIN = 0x71 => "JOIN", GET_PRIVATE = 0x72 => "GET_PRIVATE", @@ -180,7 +178,7 @@ define_opcodes! { INIT_PRIVATE = 0x74 => "INIT_PRIVATE", PRIVATE_BRAND_IN = 0x75 => "PRIVATE_BRAND_IN", - // -- Bitwise (0x80-0x8F) -- + // ── 位运算 (0x80-0x8F) ── BIT_AND = 0x80 => "BIT_AND", BIT_OR = 0x81 => "BIT_OR", BIT_XOR = 0x82 => "BIT_XOR", @@ -197,7 +195,7 @@ define_opcodes! { NULLISH = 0x8E => "NULLISH", JMP_IF_NULLISH = 0x8F => "JMP_IF_NULLISH", - // ── Misc (0xF0-0xFF) ── + // ── 杂项 (0xF0-0xFF) ── NOP = 0xF0 => "NOP", HALT = 0xF1 => "HALT", TYPEOF = 0xF2 => "TYPEOF", @@ -223,12 +221,12 @@ impl OpCode { } } -/// 4-byte instruction. +/// 4 字节定长指令。 /// -/// Layout: `[opcode: u8] [rd: u8] [a: u8] [b: u8]` -/// - `rd` — destination register -/// - `a` — first source register, or imm16 low byte -/// - `b` — second source register, or imm16 high byte +/// 布局 `[opcode: u8] [rd: u8] [a: u8] [b: u8]`: +/// - `rd` — 目标寄存器 +/// - `a` — 第一个源寄存器,或 imm16 低字节 +/// - `b` — 第二个源寄存器,或 imm16 高字节 pub type Instr = u32; /// 把操作码与三个操作数字节编码为一条 [`Instr`]。 diff --git a/crates/oxide_cfg/src/exception.rs b/crates/oxide_cfg/src/exception.rs index ea36b15..fb7ce93 100644 --- a/crates/oxide_cfg/src/exception.rs +++ b/crates/oxide_cfg/src/exception.rs @@ -1,7 +1,7 @@ //! Pass 3:异常边。 //! -//! 块内扫 TRY_BEGIN / TRY_FINALLY_BEGIN(块内标记,非 terminator,Pitfall 1), -//! 从所在 BB 连 Exception 边到处理入口(D-07/D-08:仅起始 BB,不扩散)。 +//! 块内扫 TRY_BEGIN / TRY_FINALLY_BEGIN(块内标记,非 terminator), +//! 从所在 BB 连 Exception 边到处理入口(仅起始 BB,不扩散)。 use oxide_bytecode::opcode::OpCode; use oxide_ir::operand::Operand; @@ -22,7 +22,7 @@ pub(super) fn add_exception_edges(f: &IRFunction, blocks: &mut [BasicBlock]) { match f.label_pos.get(l as usize).and_then(|p| *p) { Some(p) => { let target = crate::split::block_id_of(p, blocks); - // push 前去重(Pitfall 4:块内多个 TRY 标记可指向同一目标)。 + // push 前去重:块内多个 TRY 标记可指向同一目标,只保留一条边。 if !blocks[i].succs.contains(&(target, EdgeKind::Exception)) { blocks[i].succs.push((target, EdgeKind::Exception)); } @@ -40,38 +40,57 @@ mod tests { use crate::BasicBlock; use oxide_ir::inst::Inst; - /// TRY_BEGIN 所在 BB 出发 Exception 边 → catch 入口块(D-07/D-08 仅起始 BB)。 + /// TRY_BEGIN 所在 BB 出发 Exception 边 → catch 入口块(仅起始 BB)。 #[test] fn try_begin_emits_exception_edge_to_handler() { let mut f = IRFunction::new(); f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → catch(label 0 → inst 2) - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 2: catch 入口 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 2: catch 入口 f.label_pos = vec![Some(2)]; f.label_count = 1; let mut blocks = vec![ - BasicBlock { inst_range: 0..2, preds: Vec::new(), succs: Vec::new() }, - BasicBlock { inst_range: 2..3, preds: Vec::new(), succs: Vec::new() }, + BasicBlock { + inst_range: 0..2, + preds: Vec::new(), + succs: Vec::new(), + }, + BasicBlock { + inst_range: 2..3, + preds: Vec::new(), + succs: Vec::new(), + }, ]; add_exception_edges(&f, &mut blocks); assert_eq!(blocks[0].succs, vec![(1, EdgeKind::Exception)], "TRY_BEGIN 所在 BB 出 Exception 边"); assert!(blocks[1].succs.is_empty(), "catch 入口块无出边"); } - /// 块内多个 TRY 标记指向同一目标:Exception 边去重(Pitfall 4)。 + /// 块内多个 TRY 标记指向同一目标:Exception 边去重。 #[test] fn duplicate_exception_targets_deduplicated() { let mut f = IRFunction::new(); f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → L0 f.insts.push(Inst::try_finally_begin(0)); // 1: TRY_FINALLY_BEGIN → 同一 L0 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 2: L0 目标 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 2: L0 目标 f.label_pos = vec![Some(2)]; f.label_count = 1; let mut blocks = vec![ - BasicBlock { inst_range: 0..2, preds: Vec::new(), succs: Vec::new() }, - BasicBlock { inst_range: 2..3, preds: Vec::new(), succs: Vec::new() }, + BasicBlock { + inst_range: 0..2, + preds: Vec::new(), + succs: Vec::new(), + }, + BasicBlock { + inst_range: 2..3, + preds: Vec::new(), + succs: Vec::new(), + }, ]; add_exception_edges(&f, &mut blocks); assert_eq!( diff --git a/crates/oxide_cfg/src/finalize.rs b/crates/oxide_cfg/src/finalize.rs index 6d45b38..e937d7d 100644 --- a/crates/oxide_cfg/src/finalize.rs +++ b/crates/oxide_cfg/src/finalize.rs @@ -4,7 +4,11 @@ use crate::{BasicBlock, Cfg}; /// 追加 exit 哨兵块(`0..0`,置末尾),从各块 succs 反推 preds。 pub(super) fn finalize(mut blocks: Vec, exit_id: usize) -> Cfg { - blocks.push(BasicBlock { inst_range: 0..0, preds: Vec::new(), succs: Vec::new() }); + blocks.push(BasicBlock { + inst_range: 0..0, + preds: Vec::new(), + succs: Vec::new(), + }); let mut succs_total = 0usize; let mut preds_total = 0usize; for i in 0..blocks.len() { @@ -17,7 +21,11 @@ pub(super) fn finalize(mut blocks: Vec, exit_id: usize) -> Cfg { } debug_assert_eq!(succs_total, preds_total, "succs/preds 不对称(Pitfall 4)"); - Cfg { blocks, entry: 0, exit: exit_id } + Cfg { + blocks, + entry: 0, + exit: exit_id, + } } #[cfg(test)] @@ -29,8 +37,16 @@ mod tests { #[test] fn exit_sentinel_appended_with_preds_reversed() { let blocks_in = vec![ - BasicBlock { inst_range: 0..1, preds: Vec::new(), succs: vec![(1, EdgeKind::Jump)] }, - BasicBlock { inst_range: 1..2, preds: Vec::new(), succs: vec![(2, EdgeKind::Fallthrough)] }, + BasicBlock { + inst_range: 0..1, + preds: Vec::new(), + succs: vec![(1, EdgeKind::Jump)], + }, + BasicBlock { + inst_range: 1..2, + preds: Vec::new(), + succs: vec![(2, EdgeKind::Fallthrough)], + }, ]; let cfg = finalize(blocks_in, 2); assert_eq!(cfg.blocks.len(), 3, "2 实块 + exit 哨兵"); @@ -41,12 +57,20 @@ mod tests { assert_eq!(cfg.blocks[2].preds, vec![1], "exit preds 反推自块 1 的 Fallthrough 边"); } - /// succs/preds 对称:total 边数一致(Pitfall 4 防御性约束)。 + /// succs/preds 对称:total 边数一致(防御性约束)。 #[test] fn preds_and_succs_symmetric() { let blocks_in = vec![ - BasicBlock { inst_range: 0..1, preds: Vec::new(), succs: vec![(1, EdgeKind::Jump)] }, - BasicBlock { inst_range: 1..2, preds: Vec::new(), succs: vec![(2, EdgeKind::Fallthrough)] }, + BasicBlock { + inst_range: 0..1, + preds: Vec::new(), + succs: vec![(1, EdgeKind::Jump)], + }, + BasicBlock { + inst_range: 1..2, + preds: Vec::new(), + succs: vec![(2, EdgeKind::Fallthrough)], + }, ]; let cfg = finalize(blocks_in, 2); let succs_total: usize = cfg.blocks.iter().map(|b| b.succs.len()).sum(); diff --git a/crates/oxide_cfg/src/lib.rs b/crates/oxide_cfg/src/lib.rs index 4399e6e..7dfb5eb 100644 --- a/crates/oxide_cfg/src/lib.rs +++ b/crates/oxide_cfg/src/lib.rs @@ -1,9 +1,8 @@ //! CFG 分析 pass:IRFunction → 基本块划分 + 控制流图。 //! -//! 消费 `oxide_ir::IRFunction`(只读,D-06 索引关联,不拷贝指令),产出一次性 -//! 分析快照(D-01:pass 输出视图,不住进 IR)。本 crate 只管 CFG(D-02): -//! 不建通用分析框架、不递归 nested(D-11)。无生命周期参数——CFG 只存指令 -//! 下标区间,使用方同时持有 `&IRFunction` 与 `&Cfg` 联合访问。 +//! 消费 `oxide_ir::IRFunction`(只读,不拷贝指令,仅存指令下标区间),产出一次性 +//! 分析快照——pass 输出视图不住进 IR。本 crate 只管 CFG:不建通用分析框架、 +//! 不递归 nested。无生命周期参数——CFG 与 `&IRFunction` 由使用方联合持有访问。 //! //! 构建是四阶段分发:`partition`(块头)→ `split`(切块+出边)→ `exception` //! (异常边)→ `finalize`(exit 哨兵 + preds 反推)。每阶段是独立文件的纯函数, @@ -17,10 +16,10 @@ mod split; use oxide_bytecode::opcode::OpCode; use oxide_ir::IRFunction; -/// 基本块 id:blocks 向量下标(D-03)。 +/// 基本块 id:blocks 向量下标。 pub type BBId = usize; -/// 边三分类(D-04):无条件/条件跳转、顺序落入、异常处理入口。 +/// 边三分类:无条件/条件跳转、顺序落入、异常处理入口。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EdgeKind { Jump, @@ -28,8 +27,8 @@ pub enum EdgeKind { Exception, } -/// 基本块:`inst_range` 直接索引 `IRFunction.insts` 切片(D-03,不拷贝指令), -/// preds/succs 双向就位,liveness 数据流迭代可直接用。 +/// 基本块:`inst_range` 直接索引 `IRFunction.insts` 切片(不拷贝指令), +/// preds/succs 双向就位,供 liveness 数据流迭代直接消费。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct BasicBlock { pub inst_range: std::ops::Range, @@ -37,7 +36,7 @@ pub struct BasicBlock { pub succs: Vec<(BBId, EdgeKind)>, } -/// 控制流图快照:实块按指令序编号,exit 哨兵(空块 `0..0`)恒在 blocks 末尾(D-05)。 +/// 控制流图快照:实块按指令序编号,exit 哨兵(空块 `0..0`)恒在 blocks 末尾。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct Cfg { pub blocks: Vec, @@ -48,7 +47,11 @@ pub struct Cfg { impl Cfg { /// 构造空 CFG:blocks 空、entry/exit 置 0。 pub fn new() -> Self { - Self { blocks: Vec::new(), entry: 0, exit: 0 } + Self { + blocks: Vec::new(), + entry: 0, + exit: 0, + } } } @@ -58,12 +61,12 @@ impl Default for Cfg { } } -/// terminator 判定(D-09):块尾指令决定出边。 +/// terminator 判定:块尾指令决定出边。 /// /// 注意与 `oxide_ir::lower.rs::is_jump_op` 的差异:lowering 需要为 TRY_BEGIN / /// TRY_FINALLY_BEGIN 回填偏移所以把它们并列进 is_jump_op;但 CFG 里它们是**块内 /// 标记**(异常边起点,见 Pass 3),不是控制流终结——抄用 is_jump_op 会导致 try 体 -/// 被切块、异常边爆炸(RESEARCH Pitfall 1)。 +/// 被切块、异常边爆炸。 fn is_terminator(op: OpCode) -> bool { matches!( op, @@ -77,16 +80,20 @@ fn is_terminator(op: OpCode) -> bool { ) } -/// 构建 CFG:`&IRFunction → Cfg`。纯函数,只读 IR(D-06 方案 A)。 +/// 构建 CFG:`&IRFunction → Cfg`。纯函数,只读 IR。 /// /// 四阶段分发:`partition::partition_blocks`(Pass 1 块头)→ `split::split_and_edges` /// (Pass 2 切块 + 出边)→ `exception::add_exception_edges`(Pass 3 异常边) -/// → `finalize::finalize`(Pass 4 exit 哨兵 + preds 反推)。幂等(CFG-03)。 +/// → `finalize::finalize`(Pass 4 exit 哨兵 + preds 反推)。幂等:同一 IR 两次构建结果相等。 pub fn build_cfg(f: &IRFunction) -> Cfg { - // 空 IRFunction 退化形态:仅 exit 哨兵一块(Pitfall 5)。 + // 空 IRFunction 退化形态:仅 exit 哨兵一块。 if f.insts.is_empty() { return Cfg { - blocks: vec![BasicBlock { inst_range: 0..0, preds: Vec::new(), succs: Vec::new() }], + blocks: vec![BasicBlock { + inst_range: 0..0, + preds: Vec::new(), + succs: Vec::new(), + }], entry: 0, exit: 0, }; @@ -116,9 +123,12 @@ mod tests { #[test] fn linear_function_is_single_block_plus_exit() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); let cfg = build_cfg(&f); assert_eq!(cfg.blocks.len(), 2); // 1 实块 + exit 哨兵 @@ -139,7 +149,8 @@ mod tests { f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then a() f.insts.push(Inst::jmp(1)); // 2: → L1(end) f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1)); // 3: else b() - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(3), Some(4)]; // L0→else 块头(3), L1→end(4) f.label_count = 2; @@ -171,22 +182,26 @@ mod tests { assert_eq!(succs_total(&cfg), preds_total(&cfg)); } - /// try/catch:TRY_BEGIN 是块内标记不切块(Pitfall 1),所在 BB 出 Exception 边到 catch 入口。 + /// try/catch:TRY_BEGIN 是块内标记不切块,所在 BB 出 Exception 边到 catch 入口。 #[test] fn try_begin_does_not_split_block_and_emits_exception_edge() { let mut f = IRFunction::new(); f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN(块内标记) - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: catch 入口 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: catch 入口 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(3)]; f.label_count = 1; let cfg = build_cfg(&f); assert_eq!(cfg.blocks.len(), 3); // 2 实块 + exit 哨兵(TRY_BEGIN 不切块) assert_eq!(cfg.blocks[0].inst_range, 0..3); // try 体未被切块 - // Exception 边从 TRY_BEGIN 所在 BB 出发指向 catch 入口块(D-07/D-08) + // Exception 边从 TRY_BEGIN 所在 BB 出发指向 catch 入口块。 assert!(cfg.blocks[0].succs.contains(&(1, EdgeKind::Exception))); assert_eq!(cfg.blocks[1].succs, vec![(2, EdgeKind::Fallthrough)]); assert_eq!(cfg.blocks[2].preds, vec![0, 1]); @@ -198,10 +213,14 @@ mod tests { fn try_finally_begin_emits_exception_edge() { let mut f = IRFunction::new(); f.insts.push(Inst::try_finally_begin(0)); // 0 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: finally 入口 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: finally 入口 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(3)]; f.label_count = 1; @@ -216,8 +235,10 @@ mod tests { #[test] fn trailing_return_flows_to_exit() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); let cfg = build_cfg(&f); assert_eq!(cfg.blocks.len(), 2); @@ -225,7 +246,7 @@ mod tests { assert_eq!(cfg.blocks[1].preds, vec![0]); } - /// 空 IRFunction:退化 CFG,entry == exit == 0,blocks 长度 1(Pitfall 5)。 + /// 空 IRFunction:退化 CFG,entry == exit == 0,blocks 长度 1。 #[test] fn empty_function_yields_degenerate_cfg() { let f = IRFunction::new(); @@ -238,13 +259,14 @@ mod tests { assert!(cfg.blocks[0].preds.is_empty()); } - /// 幂等性(CFG-03):同一 IR 两次构建结果相等。 + /// 幂等性:同一 IR 两次构建结果相等。 #[test] fn build_cfg_is_idempotent() { let mut f = IRFunction::new(); f.insts.push(Inst::jmp_if_false(1, 0)); // 0: → L0(回环目标) f.insts.push(Inst::jmp(0)); // 1: → L0 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(2), Some(0)]; // L0→2(尾部), L1→0(回环) f.label_count = 2; diff --git a/crates/oxide_cfg/src/partition.rs b/crates/oxide_cfg/src/partition.rs index a7b396e..1fef11c 100644 --- a/crates/oxide_cfg/src/partition.rs +++ b/crates/oxide_cfg/src/partition.rs @@ -6,7 +6,7 @@ use oxide_bytecode::opcode::OpCode; use oxide_ir::operand::Operand; use oxide_ir::IRFunction; -/// 识别块头位置:返回 `heads`,长度 `insts.len()+1`(+1 容纳指向末尾的空尾块,Pitfall 3)。 +/// 识别块头位置:返回 `heads`,长度 `insts.len()+1`(+1 容纳指向末尾的空尾块)。 pub(super) fn partition_blocks(f: &IRFunction) -> Vec { let len = f.insts.len(); let mut heads = vec![false; len + 1]; @@ -25,10 +25,7 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec { // 跳转的 fallthrough 后继是块头:条件跳转否则 cond+then 融块、条件边丢失; // 无条件 JMP 同样必须切块头——JMP 不 fallthrough,但其后继位置需成块边界, // 否则 JMP 与后续指令融块、块尾判定错、Jump 边丢失(DCE 可达性误删目标块)。 - if matches!( - inst.op, - OpCode::JMP | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH - ) { + if matches!(inst.op, OpCode::JMP | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH) { heads[i + 1] = true; } } @@ -44,9 +41,12 @@ mod tests { #[test] fn linear_function_only_entry_is_head() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); let heads = partition_blocks(&f); assert_eq!(heads, vec![true, false, false, false], "仅 inst 0 是块头"); @@ -56,9 +56,12 @@ mod tests { #[test] fn label_target_is_head() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: label 0 目标 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: label 0 目标 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(1)]; f.label_count = 1; @@ -72,8 +75,10 @@ mod tests { fn conditional_jump_fallthrough_successor_is_head() { let mut f = IRFunction::new(); f.insts.push(Inst::jmp_if_false(1, 0)); // 0: cond → L0(else) - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(2)]; f.label_count = 1; @@ -86,8 +91,10 @@ mod tests { fn unconditional_jmp_successor_is_head() { let mut f = IRFunction::new(); f.insts.push(Inst::jmp(0)); // 0: → L0 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: 独立块头 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: 独立块头 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(2)]; f.label_count = 1; diff --git a/crates/oxide_cfg/src/split.rs b/crates/oxide_cfg/src/split.rs index 8ea0d88..0026ea6 100644 --- a/crates/oxide_cfg/src/split.rs +++ b/crates/oxide_cfg/src/split.rs @@ -16,7 +16,11 @@ pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec = Vec::with_capacity(head_positions.len()); for (idx, &start) in head_positions.iter().enumerate() { let end = head_positions.get(idx + 1).copied().unwrap_or(len); - blocks.push(BasicBlock { inst_range: start..end, preds: Vec::new(), succs: Vec::new() }); + blocks.push(BasicBlock { + inst_range: start..end, + preds: Vec::new(), + succs: Vec::new(), + }); } let exit_id = blocks.len(); // exit 哨兵块号 = 实块数 @@ -58,10 +62,10 @@ pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec { - // RETURN/HALT 汇入 exit 哨兵,用 Fallthrough 表达正常流出口(A1 语义归属)。 + // RETURN/HALT 汇入 exit 哨兵,用 Fallthrough 表达正常流出口。 blocks[i].succs.push((exit_id, EdgeKind::Fallthrough)); } - OpCode::THROW => { /* 无出边:异常传播,D-07 不建恢复路径 */ } + OpCode::THROW => { /* 无出边:异常传播,不建恢复路径 */ } _ => unreachable!("is_terminator 已穷尽其余分支"), } } @@ -85,7 +89,8 @@ mod tests { fn unconditional_jmp_produces_jump_edge() { let mut f = IRFunction::new(); f.insts.push(Inst::jmp(0)); // 0: → L0 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 1: L0 目标 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 1: L0 目标 f.label_pos = vec![Some(1)]; f.label_count = 1; @@ -101,8 +106,10 @@ mod tests { fn conditional_jump_dual_edges() { let mut f = IRFunction::new(); f.insts.push(Inst::jmp_if_false(1, 0)); // 0: cond → L0(else) - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2: else 入口 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2: else 入口 f.label_pos = vec![Some(2)]; f.label_count = 1; @@ -119,9 +126,12 @@ mod tests { #[test] fn non_terminator_fallthrough_and_return_to_exit() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0 - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0 + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2 let heads = crate::partition::partition_blocks(&f); let (blocks, exit_id) = split_and_edges(&f, &heads); @@ -138,7 +148,8 @@ mod tests { f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); f.insts.push(Inst::jmp(1)); f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); f.label_pos = vec![Some(3), Some(4)]; f.label_count = 2; diff --git a/crates/oxide_cfg/tests/regression.rs b/crates/oxide_cfg/tests/regression.rs index 8adefd6..5352d37 100644 --- a/crates/oxide_cfg/tests/regression.rs +++ b/crates/oxide_cfg/tests/regression.rs @@ -1,6 +1,6 @@ -//! 集成回归锚(D-12 第 2 层):真实 JS 编译产物 parse→emit→build_cfg。 +//! 集成回归锚:真实 JS 编译产物 parse→emit→build_cfg。 //! -//! 验证 CFG 不崩且结构合理。仅新增本文件,不修改既有测试文件(D-13)。 +//! 验证 CFG 不崩且结构合理。 use oxide_cfg::build_cfg; use oxide_cfg::EdgeKind; @@ -13,7 +13,7 @@ fn cfg_from_source(src: &str) -> oxide_cfg::Cfg { build_cfg(&ir) } -/// succs/preds 对称性断言(Pitfall 4)。 +/// succs/preds 对称性断言。 fn assert_symmetric(cfg: &oxide_cfg::Cfg) { let succs_total: usize = cfg.blocks.iter().map(|b| b.succs.len()).sum(); let preds_total: usize = cfg.blocks.iter().map(|b| b.preds.len()).sum(); @@ -33,10 +33,13 @@ fn assert_targets_resolvable(cfg: &oxide_cfg::Cfg) { #[test] fn try_catch_regression_anchor() { let cfg = cfg_from_source("try { var x = 1; } catch (e) { var y = 2; }"); - // (a) TRY_BEGIN 出发的异常边至少一条(D-07/D-08) - let has_exception = cfg.blocks.iter().any(|b| b.succs.iter().any(|&(_, k)| k == EdgeKind::Exception)); + // (a) TRY_BEGIN 出发的异常边至少一条 + let has_exception = cfg + .blocks + .iter() + .any(|b| b.succs.iter().any(|&(_, k)| k == EdgeKind::Exception)); assert!(has_exception, "期望存在 Exception 边"); - // (b) 顶层 HALT 收尾 → exit 哨兵 preds 非空(D-05) + // (b) 顶层 HALT 收尾 → exit 哨兵 preds 非空 assert!(!cfg.blocks[cfg.exit].preds.is_empty(), "exit 哨兵应有 pred(HALT 汇入)"); // (c) 全部边目标可解析 assert_targets_resolvable(&cfg); @@ -44,11 +47,12 @@ fn try_catch_regression_anchor() { assert_symmetric(&cfg); } -/// 每个 nested 子函数各自独立可建 CFG(D-11:不递归,但各函数可建)。 +/// 每个 nested 子函数各自独立可建 CFG(不递归,但各函数可建)。 #[test] fn nested_function_cfgs_are_independent() { let allocator = oxide_parser::Allocator::default(); - let program = oxide_parser::parse(&allocator, "function outer(){ var x = 0; while(x<3){x=x+1;} }").expect("parse failed"); + let program = + oxide_parser::parse(&allocator, "function outer(){ var x = 0; while(x<3){x=x+1;} }").expect("parse failed"); let ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed"); for nested in &ir.nested { let cfg = build_cfg(nested); @@ -56,7 +60,7 @@ fn nested_function_cfgs_are_independent() { } } -/// 线性程序:1 实块 + exit 哨兵(CFG-01 线性函数单块)。 +/// 线性程序:1 实块 + exit 哨兵。 #[test] fn linear_program_single_block_plus_exit() { let cfg = cfg_from_source("var x = 1;"); @@ -64,7 +68,7 @@ fn linear_program_single_block_plus_exit() { assert_eq!(cfg.exit, 1); } -/// 顶层 if/else:条件分裂块 + entry 可达覆盖全部块 + 对称(CFG-01 对 if/else)。 +/// 顶层 if/else:条件分裂块 + entry 可达覆盖全部块 + 对称。 #[test] fn if_else_conditional_edges() { let cfg = cfg_from_source("var x = 1; if(x){ var a = 1; } else { var b = 2; }"); diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 83e10ef..174e252 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -51,7 +51,7 @@ enum Commands { file: Option, #[arg(long)] no_dce: bool, - /// D-18:关闭 liveness/精确 DCE/RegAlloc 链,vreg 原样当物理号。 + /// 关闭 liveness/精确 DCE/RegAlloc 链,vreg 原样当物理号(调试降级路径)。 #[arg(long)] no_regalloc: bool, }, @@ -99,7 +99,12 @@ fn main() -> ExitCode { } ExitCode::SUCCESS } - Some(Commands::Compile { expr, file, no_dce, no_regalloc }) => compile(expr, file, no_dce, no_regalloc), + Some(Commands::Compile { + expr, + file, + no_dce, + no_regalloc, + }) => compile(expr, file, no_dce, no_regalloc), Some(Commands::Bench { mode, filter, @@ -187,7 +192,7 @@ fn format_result(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsV fn format_js_value(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) -> String { if val.is_string() { - // SAFETY: val is a string value. + // SAFETY: val 已确认是字符串值。 let s = unsafe { (*val.as_string_ptr()).data.clone() }; format!("\"{s}\"") } else if val.is_object() { diff --git a/crates/oxide_code_cache/src/lib.rs b/crates/oxide_code_cache/src/lib.rs index dfdd913..d018a91 100644 --- a/crates/oxide_code_cache/src/lib.rs +++ b/crates/oxide_code_cache/src/lib.rs @@ -12,14 +12,13 @@ use oxide_bytecode::CompiledModule; mod code_cache_log; -/// Shared compiled-module cache keyed by a caller-provided safe module hash. +/// 以调用方提供的安全模块哈希为键的共享编译模块缓存。 /// -/// The cache intentionally does not know how to parse or compile JavaScript. -/// Compiler-aware callers compute the key and provide the compile callback. +/// 缓存自身不负责解析或编译 JavaScript:由编译器感知的调用方计算键并提供 +/// 编译回调。 /// -/// Bounded by an LRU eviction policy: at most `capacity` modules are retained, -/// so an eval loop that compiles unbounded distinct sources cannot grow the -/// cache without limit. +/// 以 LRU 淘汰策略约束内存:至多保留 `capacity` 个模块,因此对无界不同源码 +/// 反复编译的 eval 循环不会让缓存无限增长。 pub struct CodeForge { map: Mutex>>, } @@ -145,7 +144,7 @@ mod tests { forge.insert(2, CompiledModule::new()); forge.insert(3, CompiledModule::new()); - // Touch key 1 so key 2 becomes the least-recently-used entry. + // 触碰键 1,使键 2 成为最久未使用条目。 assert!(forge.get(1).is_some()); forge.insert(4, CompiledModule::new()); diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 37d21a4..61e5255 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -9,17 +9,20 @@ pub use crate::hash::{compiled_module_hash, structural_hash}; /// 编译入口:状态 = DCE 开关 + RegAlloc 开关,编译其余状态在 emit/lower 内部。 pub struct Compiler { - /// 是否在 emit 与 lower 之间运行死代码消除(D-10:默认开启)。 + /// 是否在 emit 与 lower 之间运行死代码消除(默认开启)。 enable_dce: bool, - /// 是否在保守 DCE 后运行 liveness→精确 DCE→RegAlloc 链(D-18:默认开启)。 - /// off 时 vreg 原样当物理号走 lower 降级路径(>253 → RangeError)。 + /// 是否在保守 DCE 后运行 liveness→精确 DCE→RegAlloc 链(默认开启)。 + /// 关闭时 vreg 原样当物理号走 lower 降级路径(>253 → RangeError)。 enable_regalloc: bool, } impl Compiler { - /// 构造 `Compiler`:默认开启 DCE 与 RegAlloc(D-10/D-18)。 + /// 构造 `Compiler`:默认开启 DCE 与 RegAlloc。 pub fn new() -> Self { - Self { enable_dce: true, enable_regalloc: true } + Self { + enable_dce: true, + enable_regalloc: true, + } } /// 显式设置 DCE 开关(`with_dce(false)` 关闭死代码消除)。 @@ -29,7 +32,10 @@ impl Compiler { /// 显式设置 RegAlloc 开关(`with_regalloc(false)` 关闭 liveness/精确 DCE/RegAlloc 链)。 pub fn with_regalloc(self, enable: bool) -> Self { - Self { enable_regalloc: enable, ..self } + Self { + enable_regalloc: enable, + ..self + } } /// 编译整个 program:AST → IR(`Emitter::emit_program`)→(可选 DCE)→(可选 @@ -38,10 +44,10 @@ impl Compiler { crate::compiler_debug!("compile: starting..."); let mut ir = Emitter::new().emit_program(program)?; if self.enable_dce { - oxide_dce::dce(&mut ir); // 顶层函数;nested 不递归(D-03) + oxide_dce::dce(&mut ir); // 顶层函数;nested 不递归 } if self.enable_regalloc && !ir.const_overflow { - // D-17 管线:liveness → 精确 DCE → 重算 liveness → RegAlloc + // 优化管线:liveness → 精确 DCE → 重算 liveness → RegAlloc // const_overflow 时跳过:程序已知无效(lower 报 "too many constants"), // 且超大常量池伴生的海量 vreg 会使 liveness 稠密 bitset 爆内存。 let cfg = oxide_cfg::build_cfg(&ir); @@ -49,7 +55,7 @@ impl Compiler { oxide_dce::dce_precise(&mut ir, &live); // 精确二轮消费第一轮 LiveInfo let cfg2 = oxide_cfg::build_cfg(&ir); // 精确 DCE 删指令后 inst 下标位移,CFG 重建 let live2 = oxide_liveness::liveness(&ir, &cfg2); // 重算,绝不用过期 live - oxide_regalloc::alloc(&mut ir, &live2)?; // D-02:无可行染色 → RangeError 上抛 + oxide_regalloc::alloc(&mut ir, &live2)?; // 无可行染色 → RangeError 上抛 } crate::compiler_debug!("compile: done, {} instructions", ir.insts.len()); lower(&ir) diff --git a/crates/oxide_compiler/tests/arrow_template_tests.rs b/crates/oxide_compiler/tests/arrow_template_tests.rs index 532af26..72b4cd6 100644 --- a/crates/oxide_compiler/tests/arrow_template_tests.rs +++ b/crates/oxide_compiler/tests/arrow_template_tests.rs @@ -14,11 +14,11 @@ fn compile_source(source: &str) -> oxide_bytecode::module::CompiledModule { compiler.compile(&program).expect("compile failed") } -// -- Arrow function structural hash tests -- +// -- 箭头函数结构哈希测试 -- #[test] fn hash_arrow_expression_body() { - // Expression-body arrows at top level compile successfully + // 表达式体的顶层箭头可正常编译。 let hash = parse_to_hash("var f = () => 42;"); assert!(hash != 0, "arrow function should produce a non-zero hash"); } @@ -45,18 +45,18 @@ fn hash_arrow_block_body() { #[test] fn hash_arrow_same_shape() { - // Same structural shape: different variable name, same arrow expression + // 结构形状相同:变量名不同、箭头表达式相同。 let hash_a = parse_to_hash("var f = () => 1 + 2;"); let hash_b = parse_to_hash("var g = () => 1 + 2;"); assert_eq!(hash_a, hash_b, "same-shaped arrow functions should have same hash"); } -// -- Arrow function compilation tests -- +// -- 箭头函数编译测试 -- #[test] fn compile_arrow_expression_body() { let module = compile_source("var f = () => 42; f();"); - // Verify the sub_module for the arrow is marked as is_arrow + // 校验箭头的子模块被标记为 is_arrow。 let arrow_found = module.sub_modules.iter().any(|m| m.is_arrow); assert!(arrow_found, "arrow function sub_module should have is_arrow=true"); } @@ -71,7 +71,7 @@ fn compile_arrow_block_body() { #[test] fn compile_arrow_name_inference() { let module = compile_source("var myArrow = () => 42;"); - // The sub_module for the arrow should have function_name set + // 箭头的子模块应带有 function_name。 let named = module .sub_modules .iter() @@ -82,7 +82,7 @@ fn compile_arrow_name_inference() { #[test] fn compile_regular_function_not_arrow() { let module = compile_source("var f = function() { return 42; }; f();"); - // Regular FunctionExpression should NOT have is_arrow=true + // 普通 FunctionExpression 不应标记 is_arrow。 let has_arrow = module.sub_modules.iter().any(|m| m.is_arrow); assert!(!has_arrow, "regular function expression should not be marked as arrow"); } diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 0c8f619..aecbb7a 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -85,7 +85,7 @@ fn compile_negation() { fn compile_multiple_stmts() { let module = compile_source("1; 2; 3;"); - // D-11 放宽:Compiler 默认 DCE 开启,死 `1; 2` 链的 LOAD_CONST 被删除, + // Compiler 默认 DCE 开启:死 `1; 2` 链的 LOAD_CONST 被删除, // 仅剩顶层值 `3` 的 LOAD_CONST(HALT 经 reg0 返回)。精确计数断言放宽为存在性断言。 let has_load = module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LOAD_CONST); assert!(has_load, "expected at least one LOAD_CONST for the final value"); @@ -505,30 +505,61 @@ fn compile_comparison_complement_ops() { // 结果被使用(赋值)→ 比较指令不被 DCE 删除(精确 DCE 会删结果丢弃的死比较) let module = compile_source("let a = 1, b = 2; var r = a > b; var s = a <= b; var t = a >= b; var u = a != b;"); assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GT), "a > b should emit GT"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LTE), "a <= b should emit LTE"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GTE), "a >= b should emit GTE"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEQ), "a != b should emit NEQ"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LTE), + "a <= b should emit LTE" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GTE), + "a >= b should emit GTE" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEQ), + "a != b should emit NEQ" + ); } #[test] fn compile_template_literal_emits_template_str() { let module = compile_source("let name = 'x'; `hello ${name}`;"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TEMPLATE_STR), "template literal should emit TEMPLATE_STR"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TEMPLATE_STR), + "template literal should emit TEMPLATE_STR" + ); } #[test] fn compile_dynamic_member_ops() { let module = compile_source("let obj = {}, k = 'x', arr = [1]; obj[k]; obj[k] = 1; arr[0];"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_ARRAY), "array literal should emit NEW_ARRAY"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GET_PROP_DYNAMIC), "obj[k] should emit GET_PROP_DYNAMIC"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_DYNAMIC), "obj[k] = should emit SET_PROP_DYNAMIC"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_ARRAY), + "array literal should emit NEW_ARRAY" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GET_PROP_DYNAMIC), + "obj[k] should emit GET_PROP_DYNAMIC" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_DYNAMIC), + "obj[k] = should emit SET_PROP_DYNAMIC" + ); } #[test] fn compile_delete_new_instanceof_in() { - let module = compile_source("let obj = { x: 1 }, B = function(){}; delete obj.x; new B(); obj instanceof B; 'x' in obj;"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DELETE_PROP_STATIC), "delete obj.x should emit DELETE_PROP_STATIC"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION), "new B() should emit NEW_EXPRESSION"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INSTANCEOF), "instanceof should emit INSTANCEOF"); + let module = + compile_source("let obj = { x: 1 }, B = function(){}; delete obj.x; new B(); obj instanceof B; 'x' in obj;"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DELETE_PROP_STATIC), + "delete obj.x should emit DELETE_PROP_STATIC" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION), + "new B() should emit NEW_EXPRESSION" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INSTANCEOF), + "instanceof should emit INSTANCEOF" + ); assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::IN), "in should emit IN"); } diff --git a/crates/oxide_compiler/tests/compile_stmt_tests.rs b/crates/oxide_compiler/tests/compile_stmt_tests.rs index 116c019..c874824 100644 --- a/crates/oxide_compiler/tests/compile_stmt_tests.rs +++ b/crates/oxide_compiler/tests/compile_stmt_tests.rs @@ -120,9 +120,9 @@ fn regression_for_var_init() { #[test] fn regression_var_decl_counter_no_init() { - // `for (var i; ...)` initializes `i` to undefined (var semantics, not TDZ), and the - // counter/emitter agree on the no-init declarator's instruction count, so it compiles - // to a well-formed loop header instead of panicking. + // `for (var i; ...)` 把 `i` 初始化为 undefined(var 语义而非 TDZ),且 + // 编译器对无初始化声明符的指令数保持一致,因此可编译为良构循环头 + // 而非 panic。 let module = compile_source("for (var i; i < 3; i = i + 1) {}"); assert!(!module.bytecode.is_empty(), "for(var) no-init should compile"); assert!( @@ -336,18 +336,39 @@ fn compile_empty_statements_are_skipped() { #[test] fn compile_for_of_emits_iterator_protocol() { let module = compile_source("let arr = [1]; for (const v of arr) { v; }"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_INIT), "for-of should emit FOR_OF_INIT"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_NEXT), "for-of should emit FOR_OF_NEXT"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_DONE), "for-of should emit FOR_OF_DONE"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_CLOSE), "for-of should emit FOR_OF_CLOSE"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_INIT), + "for-of should emit FOR_OF_INIT" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_NEXT), + "for-of should emit FOR_OF_NEXT" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_DONE), + "for-of should emit FOR_OF_DONE" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_CLOSE), + "for-of should emit FOR_OF_CLOSE" + ); } #[test] fn compile_try_catch_emits_try_begin_and_throw() { let module = compile_source("try { throw new Error('x'); } catch (e) { e; }"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::THROW), "throw should emit THROW"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TRY_BEGIN), "try-catch should emit TRY_BEGIN"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TRY_END), "try-catch should emit TRY_END"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::THROW), + "throw should emit THROW" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TRY_BEGIN), + "try-catch should emit TRY_BEGIN" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TRY_END), + "try-catch should emit TRY_END" + ); } fn has_opcode(module: &CompiledModule, target: OpCode) -> bool { @@ -387,8 +408,20 @@ fn compile_closure_capture_arrow_emits_make_cell() { #[test] fn compile_compound_assignment_complement_ops() { let module = compile_source("let x = 10; x -= 1; x *= 2; x /= 3; x %= 4;"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_SUB), "x -= should emit COMPOUND_SUB"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MUL), "x *= should emit COMPOUND_MUL"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_DIV), "x /= should emit COMPOUND_DIV"); - assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MOD), "x %= should emit COMPOUND_MOD"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_SUB), + "x -= should emit COMPOUND_SUB" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MUL), + "x *= should emit COMPOUND_MUL" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_DIV), + "x /= should emit COMPOUND_DIV" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MOD), + "x %= should emit COMPOUND_MOD" + ); } diff --git a/crates/oxide_compiler/tests/let_const_tests.rs b/crates/oxide_compiler/tests/let_const_tests.rs index 39e2ae6..e720379 100644 --- a/crates/oxide_compiler/tests/let_const_tests.rs +++ b/crates/oxide_compiler/tests/let_const_tests.rs @@ -9,7 +9,7 @@ fn hash_var_let_const_differ() { let let_ast = oxide_parser::parse(&allocator, "let x = 1").unwrap(); let const_ast = oxide_parser::parse(&allocator, "const x = 1").unwrap(); - // Different AST structures should produce different bytecode + // 不同的 AST 结构应产出不同的字节码。 let var_module = compiler.compile(&var_ast).unwrap(); let let_module = compiler.compile(&let_ast).unwrap(); let const_module = compiler.compile(&const_ast).unwrap(); diff --git a/crates/oxide_compiler/tests/symbol_table_tests.rs b/crates/oxide_compiler/tests/symbol_table_tests.rs index a2c186b..5f03607 100644 --- a/crates/oxide_compiler/tests/symbol_table_tests.rs +++ b/crates/oxide_compiler/tests/symbol_table_tests.rs @@ -31,16 +31,16 @@ fn symbol_table_nested_scopes() { #[test] fn symbol_table_var_self_init_in_block_is_legal() { - // `var` has no temporal dead zone and hoists to the function scope, so a block-level - // `var x = x` reads the same already-initialized binding — legal in JS, not an error. + // `var` 无暂时性死区且提升到函数作用域,块级 `var x = x` 读取的是 + // 同一已初始化绑定——JS 合法,非错误。 let module = compile_source("var x = 1; { var x = x; }"); assert!(!module.bytecode.is_empty(), "var self-init in block should compile"); } #[test] fn symbol_table_duplicate_var_is_legal() { - // Duplicate `var` in the same scope is legal in JavaScript (even in strict mode); - // only duplicate `let`/`const` declarations are a SyntaxError. + // JavaScript 中同作用域重复 `var` 合法(严格模式亦然); + // 只有重复 `let`/`const` 才是 SyntaxError。 let module = compile_source("var x = 1; var x = 2;"); assert!(!module.bytecode.is_empty(), "duplicate var should compile"); } diff --git a/crates/oxide_dce/src/iter_sweep.rs b/crates/oxide_dce/src/iter_sweep.rs index 4c2f91b..fef53f2 100644 --- a/crates/oxide_dce/src/iter_sweep.rs +++ b/crates/oxide_dce/src/iter_sweep.rs @@ -1,11 +1,11 @@ -//! Pass B:全函数 use 计数迭代删除到不动点(D-02 连锁语义)。 +//! Pass B:全函数 use 计数迭代删除到不动点(连锁删上游写者)。 //! //! 只对可达存活指令聚合 use 计数(逐条 `inst.use_regs()` 累加)→ 收集 `def_reg` 为 //! Some(r) 且 use_count[r]==0 且 `is_pure(f)` 的指令 → 删除并减去其 use_regs → 迭代。 -//! CALL 隐式 def reg0 不参与死删(CALL 本身 is_pure=false 永不删,Pitfall 1),但 CALL +//! CALL 隐式 def reg0 不参与死删(CALL 本身 is_pure=false 永不删),但 CALL //! 的 use 计数正常聚合——喂活上游写其参数的纯指令。 -//! **label 目标指令强制保留**:块头是控制流汇合点,即使 def 无 use 也删除会使存活 -//! JMP 的 label 引用悬空(lowering "Label not found")——保守保留(D-01 少删不错删)。 +//! **label 目标指令强制保留**:块头是控制流汇合点,删除会使存活 JMP 的 label 引用 +//! 悬空(lowering "Label not found")——保守保留,少删不错删。 use oxide_bytecode::opcode::OpCode; use oxide_ir::operand::Operand; @@ -41,11 +41,11 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) { } } // nested 树引用的变量槽位图:闭包可跨函数读外层变量槽(LOAD_VAR.a 读槽 / STORE_VAR.rd 写槽 - // 以寄存器号编码)。D-03 不递归删除 nested,但跨函数引用必须保活当前函数内写该槽的 + // 以寄存器号编码)。不递归删除 nested,但跨函数引用必须保活当前函数内写该槽的 // STORE_VAR——否则死 `var k='x'` + 构造器内 `[k]` computed key 读槽被误删(回归锚)。 let mut escaped_slot = vec![false; max_reg as usize + 1]; collect_escaped_slots(&f.nested, &mut escaped_slot); - // 迭代删除至不动点:删一条纯指令 → 减其 use_regs → 上游写者可能连锁变死(D-02) + // 迭代删除至不动点:删一条纯指令 → 减其 use_regs → 上游写者可能连锁变死 loop { let mut deleted = false; for (i, k) in keep.iter_mut().enumerate() { @@ -53,12 +53,12 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) { continue; } let inst = &f.insts[i]; - // CALL 系隐式 def reg0 是注册写入不参与死删收集;is_pure=false 已保证永不删(Pitfall 1) + // CALL 系隐式 def reg0 是注册写入不参与死删收集;is_pure=false 已保证永不删 if let Some(r) = inst.def_reg() { // STORE_VAR 写变量槽可被 nested 闭包跨函数读取(computed class field key 等 - // 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。D-03 不递归 nested, + // 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。DCE 不递归 nested, // 顶层 use 计数看不到跨函数读——凡 nested 树引用的变量槽,其顶层写者保活 - //(保守"少删不错删",D-01)。 + //(保守"少删不错删")。 let slot_escapes = inst.op == OpCode::STORE_VAR && matches!(inst.rd, Operand::Reg(_)) && escaped_slot.get(r as usize).copied().unwrap_or(false); @@ -78,8 +78,9 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) { } /// 递归收集 nested 树中全部变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)。 +/// /// 槽号 = 寄存器号编码的变量槽;其余操作数(局部寄存器/Imm/This)不构成跨函数引用。 -/// precise_sweep 复用(B012 约束同一来源)。 +/// precise_sweep 复用此函数,保证跨函数保活判定同一来源。 pub(crate) fn collect_escaped_slots(nested: &[IRFunction], out: &mut Vec) { for sub in nested { for inst in &sub.insts { @@ -111,8 +112,10 @@ mod tests { fn linear_dead_chain_deleted() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0 = const - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1 = r0 + r0 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); // 2: return r2 + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1 = r0 + r0 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); // 2: return r2 let mut keep = vec![true; f.insts.len()]; pass_b_dead_code(&f, &mut keep); @@ -127,24 +130,30 @@ mod tests { f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(0), Operand::Reg(0), 0)); // 1: CALL f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 2 f.insts.push(Inst::load_const(Operand::Reg(2), 2)); // 3 - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(1))); // 4 - f.insts.push(Inst::new(OpCode::GET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 5 - f.insts.push(Inst::new(OpCode::SET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 6 - f.insts.push(Inst::new(OpCode::INC_POST, Operand::Reg(4), Operand::Reg(5), Operand::None)); // 7 + f.insts + .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(1))); // 4 + f.insts + .push(Inst::new(OpCode::GET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 5 + f.insts + .push(Inst::new(OpCode::SET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 6 + f.insts + .push(Inst::new(OpCode::INC_POST, Operand::Reg(4), Operand::Reg(5), Operand::None)); // 7 let mut keep = vec![true; f.insts.len()]; pass_b_dead_code(&f, &mut keep); assert!(keep.iter().all(|k| *k), "有副作用指令一律保留"); } - /// STORE_VAR 一律保留(Pitfall 6):写变量槽可能被外部观察(脚本顶层/模块作用域变量、 + /// STORE_VAR 一律保留:写变量槽可能被外部观察(脚本顶层/模块作用域变量、 /// nested 闭包跨函数读槽),即使本函数内无 use 也不删。 #[test] fn store_var_never_deleted() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 0 - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None)); // 1 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 let mut keep = vec![true; f.insts.len()]; pass_b_dead_code(&f, &mut keep); @@ -152,12 +161,13 @@ mod tests { assert!(keep[0] && keep[2], "源与 RETURN 保留"); } - /// 死闭包:CREATE_CLOSURE 结果未用 → 删除;nested 槽位不动(D-03 不递归不回收)。 + /// 死闭包:CREATE_CLOSURE 结果未用 → 删除;nested 不递归不回收。 #[test] fn dead_create_closure_deleted_nested_untouched() { let mut f = IRFunction::new(); f.insts.push(Inst::create_closure(Operand::Reg(1), 0)); // 0: r1 = nested[0] - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 1 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 1 f.nested.push(IRFunction::new()); let mut keep = vec![true; f.insts.len()]; @@ -165,14 +175,16 @@ mod tests { assert_eq!(keep, vec![false, true], "死 CREATE_CLOSURE 删,RETURN 保留"); } - /// 连锁迭代(D-02):写链 LOAD_CONST→ADD→LOAD_CONST 全死,逐轮删除至不动点。 + /// 连锁迭代:写链 LOAD_CONST→ADD→LOAD_CONST 全死,逐轮删除至不动点。 #[test] fn chained_dead_writes_deleted_iteratively() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0(ADD 读,轮 2 删) - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1(轮 1 删) + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1(轮 1 删) f.insts.push(Inst::load_const(Operand::Reg(2), 2)); // 2: r2 无人读(轮 1 删) - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 let mut keep = vec![true; f.insts.len()]; pass_b_dead_code(&f, &mut keep); @@ -181,14 +193,15 @@ mod tests { /// label 目标指令强制保留(回归:do-while 形态)。`LOAD_CONST r0` 是 label 0 的目标, /// 虽然 def 无 use 属死值,但删除会使存活 JMP_IF_TRUE 的 label 引用悬空 - /// (lowering 报 "Label not found")——保守保留(D-01 少删不错删)。 + /// (lowering 报 "Label not found")——保守保留,少删不错删。 #[test] fn label_target_inst_never_deleted() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(0), 1)); // 0: body 头,label 0 目标(死值) f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 1: 条件 true f.insts.push(Inst::jmp_if_true(1, 0)); // 2: 回跳 label 0 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 f.label_pos = vec![Some(0)]; f.label_count = 1; let mut keep = vec![true; f.insts.len()]; @@ -199,18 +212,22 @@ mod tests { /// STORE_VAR 跨函数逃逸(回归:computed class field key)。顶层写变量槽 Reg(1) 的 /// STORE_VAR 在本函数无 use,但 nested 构造器内 LOAD_VAR a=Reg(1) 跨函数读该槽—— - /// DCE 只统计当前函数(D-03 不递归),nested 引用槽保活顶层写者。 + /// DCE 只统计当前函数(不递归 nested),nested 引用槽保活顶层写者。 #[test] fn store_var_slot_read_by_nested_kept() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(2), 0)); // 0: 'x' - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(0))); // 1: k = 'x'(槽 1) + f.insts + .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(0))); // 1: k = 'x'(槽 1) f.insts.push(Inst::create_closure(Operand::Reg(4), 0)); // 2 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 - // nested 构造器:LOAD_VAR a=Reg(1) 跨函数读外层变量槽 1(computed key) + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 + // nested 构造器:LOAD_VAR a=Reg(1) 跨函数读外层变量槽 1(computed key) let mut sub = IRFunction::new(); - sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::Reg(1), Operand::None)); - sub.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(7), Operand::None, Operand::None)); + sub.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::Reg(1), Operand::None)); + sub.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(7), Operand::None, Operand::None)); f.nested.push(sub); let mut keep = vec![true; f.insts.len()]; diff --git a/crates/oxide_dce/src/lib.rs b/crates/oxide_dce/src/lib.rs index e468cf2..dde63c7 100644 --- a/crates/oxide_dce/src/lib.rs +++ b/crates/oxide_dce/src/lib.rs @@ -1,16 +1,16 @@ //! DCE 改写 pass:IRFunction → 死代码消除后 compact 重建。 //! -//! 改写 pass(D-09):`&mut IRFunction` 就地 compact(D-06 方案 A 索引关联保证无借用冲突), +//! 改写 pass:`&mut IRFunction` 就地 compact(以指令下标索引 keep 位图,无借用冲突), //! 三 Pass 各居一模块(照 oxide_cfg 四阶段拆文件先例): -//! - `reachability`(Pass A):块级可达性,消费 `oxide_cfg::build_cfg`,Exception 边保守(D-07/Pitfall 4) -//! - `iter_sweep`(Pass B):全函数 use 计数迭代删除死指令到不动点(D-01/D-02 连锁语义) -//! - `rebuild`(Pass C):mark-sweep 一次性重建 insts + label_pos 重映射(D-13,不用就地逐删平移) +//! - `reachability`(Pass A):块级可达性,消费 `oxide_cfg::build_cfg`,Exception 边保守遍历 +//! - `iter_sweep`(Pass B):全函数 use 计数迭代删除死指令到不动点(连锁删上游写者) +//! - `rebuild`(Pass C):mark-sweep 一次性重建 insts + label_pos 重映射(不用就地逐删平移) //! -//! 本 pass 不碰 nested(D-03 不递归)、常量池(D-14 不清理)、n_registers(D-04 不收缩)、 -//! label_count(D-13 不读不改)。中间产物(keep/use_count)用局部 Vec 显式传参, +//! 本 pass 不碰 nested(不递归)、常量池(不清理)、n_registers(不收缩)、 +//! label_count(不读不改)。中间产物(keep/use_count)用局部 Vec 显式传参, //! 不做 struct 状态持有(无共享可变状态约定)。零 unsafe。 //! -//! 精确二轮 `dce_precise`(D-17/D-19/D-20):liveness 驱动的死指令 + 局部死 STORE_VAR 删除, +//! 精确二轮 `dce_precise`:liveness 驱动的死指令 + 局部死 STORE_VAR 删除, //! 消费 `oxide_liveness::LiveInfo`,mark-sweep 重建复用 Pass C。 mod iter_sweep; @@ -29,15 +29,16 @@ pub fn dce(f: &mut IRFunction) { } // Pass A:块级可达性(消费 build_cfg,Exception 边保守)→ 指令粒度 keep。 let mut keep = reachability::pass_a_reachable(f); - // Pass B:可达存活指令上全函数 use 计数迭代删除到不动点(D-02 连锁语义)。 + // Pass B:可达存活指令上全函数 use 计数迭代删除到不动点(连锁删上游写者)。 iter_sweep::pass_b_dead_code(f, &mut keep); - // Pass C:mark-sweep 一次性重建 insts + label_pos 重映射(D-13)。 + // Pass C:mark-sweep 一次性重建 insts + label_pos 重映射。 rebuild::pass_c_sweep(f, &keep); } -/// 精确二轮(D-17/D-19/D-20):liveness 驱动的死指令 + 局部死 STORE_VAR 删除。 -/// 消费 oxide_liveness::LiveInfo(RegAlloc 前由 05-09 管线传入;入口处重跑 liveness -/// 是 RegAlloc 的职责,D-17 意图:不重复建分析引擎)。mark-sweep 重建复用既有 Pass C。 +/// 精确二轮:liveness 驱动的死指令 + 局部死 STORE_VAR 删除。 +/// +/// 消费 oxide_liveness::LiveInfo(RegAlloc 管线传入;若 LiveInfo 已过期,重建 liveness +/// 是调用方 RegAlloc 的职责,本 pass 不重复建分析引擎)。mark-sweep 重建复用既有 Pass C。 pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) { // 空函数退化(照 dce 先例) if f.insts.is_empty() { @@ -49,7 +50,7 @@ pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) { } let mut keep = vec![true; f.insts.len()]; precise_sweep::pass_dead_with_liveness(f, live, &mut keep); - // D-20:mark-sweep 一次性重建 + // mark-sweep 一次性重建 rebuild::pass_c_sweep(f, &keep); } @@ -75,10 +76,12 @@ mod tests { fn dce_is_idempotent() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // 0: 死 - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(1), Operand::Reg(1))); // 1: 死 + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(1), Operand::Reg(1))); // 1: 死 f.insts.push(Inst::load_const(Operand::Reg(0), 1)); // 2: 存活(CALL 保活 r0) f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(0), Operand::Reg(0), 0)); // 3 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(0)]; dce(&mut f); @@ -97,13 +100,14 @@ mod tests { f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then f.insts.push(Inst::jmp(1)); // 2: → L1(end) f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1)); // 3: else - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(3), Some(4)]; f.label_count = 2; dce(&mut f); assert_eq!(f.insts.len(), 5, "双分支全保留"); assert_eq!(f.label_pos, vec![Some(3), Some(4)], "存活 label 重映射到新下标"); - assert_eq!(f.label_count, 2, "label_count 不动(D-13)"); + assert_eq!(f.label_count, 2, "label_count 保持原值,不参与重建"); } } diff --git a/crates/oxide_dce/src/precise_sweep.rs b/crates/oxide_dce/src/precise_sweep.rs index 6abf63b..e2b4b17 100644 --- a/crates/oxide_dce/src/precise_sweep.rs +++ b/crates/oxide_dce/src/precise_sweep.rs @@ -1,10 +1,10 @@ -//! 精确轮(Pass D):liveness 驱动的死指令 + 局部死 STORE_VAR 删除(D-17/D-19)。 +//! 精确轮(Pass D):liveness 驱动的死指令 + 局部死 STORE_VAR 删除。 //! -//! 消费 `oxide_liveness::LiveInfo::inst_live_after` 判定(D-22 契约复用,不重复建): +//! 消费 `oxide_liveness::LiveInfo::inst_live_after` 判定(契约复用 contract.rs,不重复建分析引擎): //! - 通用规则:纯指令 def_reg 不在 live_after → 删(含寄存器复用死写,保守 use 计数抓不到) //! - STORE_VAR 特例(def_reg=None 不命中通用规则):非顶层 && b==Imm(0) && 槽非 escaped && 槽不在 live_after → 删 //! -//! label 目标保守保留;单遍不级联(保守轮已做连锁);nested 不递归(D-03 延续)。 +//! label 目标保守保留;单遍不级联(保守轮已做连锁);nested 不递归。 use oxide_bytecode::opcode::OpCode; use oxide_ir::operand::Operand; @@ -20,8 +20,8 @@ pub(super) fn pass_dead_with_liveness(f: &IRFunction, live: &LiveInfo, keep: &mu *t = true; } } - // escaped 槽位图:nested 树 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012——父 liveness - // 看不到跨函数读,删 STORE_VAR 后子模块读陈旧/undefined 槽) + // escaped 槽位图:nested 树 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(父 liveness 看不到 + // 跨函数读,删 STORE_VAR 后子模块会读陈旧/undefined 槽) let mut max_reg: usize = 0; for inst in &f.insts { if let Some(d) = inst.def_reg() { @@ -47,17 +47,17 @@ pub(super) fn pass_dead_with_liveness(f: &IRFunction, live: &LiveInfo, keep: &mu continue; } } - // STORE_VAR 特例:def_reg=None 不命中通用规则,单独判定(四条件全满足才删) - // 槽活度条件是必须的(RESEARCH Pattern 5 补齐):`var y=1; return y` 删掉 - // STORE_VAR 会让 LOAD_VAR 读到陈旧/undefined 槽(vreg 化 + RegAlloc 复用后脏槽)。 + // STORE_VAR 特例:def_reg=None 不命中通用规则,单独判定(四条件全满足才删)。 + // 槽活度条件是必须的:`var y=1; return y` 删掉 STORE_VAR 会让 LOAD_VAR 读到 + // 陈旧/undefined 槽(vreg 化 + RegAlloc 复用后脏槽)。 if inst.op == OpCode::STORE_VAR { let slot = match inst.rd { Operand::Reg(s) => s as usize, _ => continue, }; - let deletable = !f.is_top_level // B008:顶层赋值全局可观察 - && matches!(inst.b, Operand::Imm(0)) // const 赋值 b=1 抛 TypeError 不可删 - && !escaped_slot.get(slot).copied().unwrap_or(false) // B012:nested 直读槽不可动 + let deletable = !f.is_top_level // 顶层赋值全局可观察,不可删 + && matches!(inst.b, Operand::Imm(0)) // const 赋值 b=1 抛 TypeError,不可删 + && !escaped_slot.get(slot).copied().unwrap_or(false) // nested 直读槽不可动 && not_live_after(slot as u32); // 槽无后续读(vreg 复用后脏槽风险) if deletable { keep[i] = false; @@ -96,9 +96,24 @@ mod tests { #[test] fn reuse_killed_dead_write_deleted() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::ADD, Operand::Reg(5), Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::ADD, + Operand::Reg(5), + Operand::Reg(1), + Operand::Reg(2), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::ADD, + Operand::Reg(5), + Operand::Reg(3), + Operand::Reg(4), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); // inst_live_after = [{3,4}, {5}, {}]:inst0 def 5 在 live_after[0] 无 5(后写杀死) run_precise(&mut f, &[&[3, 4], &[5], &[]], 5); assert_eq!(f.insts.len(), 2, "死写(前一个 ADD)应被删"); @@ -109,37 +124,72 @@ mod tests { fn local_dead_store_var_deleted() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); // is_top_level 默认 false;inst_live_after = [{1,5}, {5}, {}] run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); assert_eq!(f.insts.len(), 2, "局部死 STORE_VAR 应被删(LOAD_CONST 单遍不级联保留)"); } - /// 顶层 STORE_VAR 永不删(B008)。 + /// 顶层 STORE_VAR 永不删(顶层赋值全局可观察)。 #[test] fn top_level_store_var_kept() { let mut f = IRFunction::new(); f.is_top_level = true; f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); assert_eq!(f.insts.len(), 3, "顶层 STORE_VAR 不可删"); } - /// escaped 槽 STORE_VAR 永不删(B012 nested 直读)。 + /// escaped 槽 STORE_VAR 永不删(nested 直读)。 #[test] fn escaped_slot_store_var_kept() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); let mut sub = IRFunction::new(); - sub.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::LOAD_VAR, Operand::Reg(5), Operand::Reg(2), Operand::None)); + sub.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::LOAD_VAR, + Operand::Reg(5), + Operand::Reg(2), + Operand::None, + )); f.nested.push(sub); run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); - assert_eq!(f.insts.len(), 3, "escaped 槽 STORE_VAR 不可删(B012)"); + assert_eq!(f.insts.len(), 3, "被嵌套函数直引的 escaped 槽 STORE_VAR 不可删"); } /// const 赋值路径 b=1 保留(运行时抛 TypeError 可观察)。 @@ -147,8 +197,18 @@ mod tests { fn const_guard_store_var_kept() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(1))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(1), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); assert_eq!(f.insts.len(), 3, "b=1 const 赋值路径不可删"); } @@ -158,7 +218,12 @@ mod tests { fn label_target_inst_never_deleted() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // label 0 目标,纯且结果未用 - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); f.label_pos = vec![Some(0)]; run_precise(&mut f, &[&[5], &[]], 5); assert_eq!(f.insts.len(), 2, "label 目标指令不可删"); @@ -171,7 +236,12 @@ mod tests { f.insts.push(Inst::inst_spill(Operand::Reg(1), 0)); f.insts.push(Inst::inst_unspill(Operand::Reg(2), 0)); f.insts.push(Inst::inst_mov(Operand::Reg(3), Operand::Reg(2))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(3), + Operand::None, + Operand::None, + )); run_precise(&mut f, &[&[], &[2], &[3], &[]], 5); assert_eq!(f.insts.len(), 4, "SPILL/UNSPILL 永不删,活 MOV 保留"); } @@ -181,8 +251,18 @@ mod tests { fn stale_liveinfo_no_op() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); let live = LiveInfo::new(); // 空 LiveInfo:维度不符 dce_precise(&mut f, &live); assert_eq!(f.insts.len(), 3, "过期 LiveInfo 不得删除任何指令"); @@ -194,8 +274,18 @@ mod tests { fn dce_precise_converges() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); - f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); // 第一遍:手填 live(STORE_VAR 死) run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); assert_eq!(f.insts.len(), 2, "第一遍删 STORE_VAR"); diff --git a/crates/oxide_dce/src/reachability.rs b/crates/oxide_dce/src/reachability.rs index a44d9a1..732b18e 100644 --- a/crates/oxide_dce/src/reachability.rs +++ b/crates/oxide_dce/src/reachability.rs @@ -1,7 +1,7 @@ //! Pass A:块级可达性。 //! //! 消费 `oxide_cfg::build_cfg` 的 Cfg,从 entry 沿 succs DFS(**遍历含 EdgeKind::Exception 边**, -//! D-07/Pitfall 4——catch/finally 入口强制可达),不可达块的全部指令标记删除。 +//! catch/finally 入口强制可达),不可达块的全部指令标记删除。 use oxide_cfg::build_cfg; use oxide_ir::IRFunction; @@ -16,7 +16,7 @@ pub(super) fn pass_a_reachable(f: &IRFunction) -> Vec { continue; } reachable[b] = true; - // succs 三边全遍历:Jump / Fallthrough / Exception(D-07 保守) + // succs 三边全遍历:Jump / Fallthrough / Exception(保守,异常入口强制可达) for &(succ, _kind) in &cfg.blocks[b].succs { stack.push(succ); } @@ -36,16 +36,17 @@ pub(super) fn pass_a_reachable(f: &IRFunction) -> Vec { #[cfg(test)] mod tests { use super::*; + use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; - use oxide_bytecode::opcode::OpCode; /// 线性函数全可达:所有指令 keep=true。 #[test] fn linear_all_insts_reachable() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); let keep = pass_a_reachable(&f); assert!(keep.iter().all(|k| *k), "线性函数全指令可达"); } @@ -54,9 +55,11 @@ mod tests { #[test] fn unreachable_block_marked_dead() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(0), Operand::None, Operand::None)); // 0 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(0), Operand::None, Operand::None)); // 0 f.insts.push(Inst::jmp(0)); // 1: 死块(L0 目标),自指 JMP - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(1)]; f.label_count = 1; @@ -65,16 +68,18 @@ mod tests { assert!(!keep[1] && !keep[2], "return 后死块不可达"); } - /// Exception 边保守(Pitfall 4):TRY_BEGIN 的 Exception 边使 catch 入口强制可达, + /// Exception 边保守:TRY_BEGIN 的 Exception 边使 catch 入口强制可达, /// 即使没有正常边指向它。 #[test] fn exception_edge_keeps_catch_entry() { let mut f = IRFunction::new(); f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → catch 入口(label 0 → inst 3) f.insts.push(Inst::load_const(Operand::Reg(5), 1)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 f.insts.push(Inst::load_const(Operand::Reg(6), 2)); // 3: catch 入口 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); // 4 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(3)]; f.label_count = 1; diff --git a/crates/oxide_dce/src/rebuild.rs b/crates/oxide_dce/src/rebuild.rs index 5f04ccc..cb2006d 100644 --- a/crates/oxide_dce/src/rebuild.rs +++ b/crates/oxide_dce/src/rebuild.rs @@ -1,4 +1,4 @@ -//! Pass C:mark-sweep 一次性重建 insts + label_pos 重映射(D-13,不用就地逐删平移—— +//! Pass C:mark-sweep 一次性重建 insts + label_pos 重映射(不用就地逐删平移—— //! 多删点叠加偏移易错)。sweep:keep 序 compact 重建;label_pos 逐 id 重写, //! 目标被删的 label 置 None 保留 id 槽位(lowering 只读 label_pos 不读 label_count)。 @@ -17,13 +17,13 @@ pub(super) fn pass_c_sweep(f: &mut IRFunction, keep: &[bool]) { } } f.insts = new_insts; - // label_pos 逐 id 重写:get + usize::MAX 过滤做越界守卫(T-04-03,不信任 label_pos 长度) + // label_pos 逐 id 重写:get + usize::MAX 过滤做越界守卫(不信任 label_pos 长度) for pos in f.label_pos.iter_mut() { if let Some(old) = *pos { *pos = old_to_new.get(old).filter(|n| **n != usize::MAX).copied(); } } - // 防御性校验(T-04-03):存活 label 新下标不越界(空尾块映射 new insts.len() 合法透传) + // 防御性校验:存活 label 新下标不越界(空尾块映射 new insts.len() 合法透传) let new_len = f.insts.len(); debug_assert!(f.label_pos.iter().all(|p| p.map_or(true, |np| np <= new_len))); } @@ -42,7 +42,8 @@ mod tests { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(5), 1)); // 0: 存活 f.insts.push(Inst::load_const(Operand::Reg(9), 2)); // 1: 死(删) - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(2)]; // label 0 → inst 2(RETURN) f.label_count = 1; @@ -53,14 +54,15 @@ mod tests { assert_eq!(f.label_pos, vec![Some(1)], "存活 label 2 → 新下标 1"); } - /// 不动域断言(D-14/D-04/D-03/D-13):constants / n_registers / nested / label_count - /// 一律不动;死 label 置 None、存活 label 重映射、原 None 保持。 + /// 不动域断言:constants / n_registers / nested / label_count 一律不动; + /// 死 label 置 None、存活 label 重映射、原 None 保持。 #[test] fn untouched_domains_asserted() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // 0: label 0 目标(死值,保守保留) f.insts.push(Inst::load_const(Operand::Reg(2), 1)); // 1: 非 label 目标的死指令(删) - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 f.label_pos = vec![Some(0), Some(2), None]; f.label_count = 3; f.n_registers = 10; @@ -72,9 +74,9 @@ mod tests { assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); assert_eq!(f.insts[1].op, OpCode::RETURN); assert_eq!(f.label_pos, vec![Some(0), Some(1), None], "存活 label→新下标、原 None 保持"); - assert_eq!(f.label_count, 3, "label_count 不动(D-13)"); - assert_eq!(f.n_registers, 10, "n_registers 不收缩(D-04)"); - assert_eq!(f.constants, vec![Constant::Number(1.0)], "常量池不清理(D-14)"); - assert_eq!(f.nested.len(), 1, "nested 不递归不回收(D-03)"); + assert_eq!(f.label_count, 3, "label_count 保持原值,不参与重建"); + assert_eq!(f.n_registers, 10, "n_registers 不收缩"); + assert_eq!(f.constants, vec![Constant::Number(1.0)], "常量池不清理"); + assert_eq!(f.nested.len(), 1, "nested 不递归不回收"); } } diff --git a/crates/oxide_dce/tests/precise_eq.rs b/crates/oxide_dce/tests/precise_eq.rs index b6cff53..f72c623 100644 --- a/crates/oxide_dce/tests/precise_eq.rs +++ b/crates/oxide_dce/tests/precise_eq.rs @@ -1,8 +1,8 @@ -//! 精确 DCE 二轮(D-17/D-19/D-20)运行时等价 + 语义断言。 +//! 精确 DCE 二轮运行时等价 + 语义断言。 //! //! 同一 JS 源码走两条管线:parse → emit → dce(保守恒定)→(precise on/off)→ lower → run, -//! 对比顶层执行结果。**不经 `Compiler::compile`**(测试层直调底层函数)。B008/B012 样例做 -//! 字节码/IR 级断言,is_top_level 标志直接断言。 +//! 对比顶层执行结果。**不经 `Compiler::compile`**(测试层直调底层函数)。顶层赋值 / +//! escaped 槽样例做字节码/IR 级断言,is_top_level 标志直接断言。 use oxide_bytecode::opcode::OpCode; use oxide_vm::vm::Vm; @@ -32,7 +32,7 @@ fn normalize(r: Result) -> (bool, String) { } } -/// 精确轮 on/off 运行时等价样例集(覆盖 B008/B012/const 抛错/try-catch/双写/纯链)。 +/// 精确轮 on/off 运行时等价样例集(覆盖顶层赋值 / escaped 槽 / const 抛错 / try-catch / 双写 / 纯链)。 #[test] fn precise_dce_semantics_preserved() { let samples = [ @@ -40,13 +40,13 @@ fn precise_dce_semantics_preserved() { "function f() { var y = 1; return 5; } f();", // 死 a 链(a 从未读) "function f() { var a = 1 + 2; var b = 3 + 4; return b; } f();", - // 顶层 STORE_VAR(B008:is_top_level 保护,不删) + // 顶层 STORE_VAR(is_top_level 保护,不删) "var x = 1; 2;", - // B008 既有回归样例(顶层赋值全局可观察) + // 顶层赋值全局可观察(回归样例) "let x = 0; class C { [x = 1]() { return 2; } } new C();", - // B012 回归:k 只被嵌套子模块 LOAD_VAR.a 直读(父 liveness 死、escaped 保活) + // escaped 回归:k 只被嵌套子模块 LOAD_VAR.a 直读(父 liveness 死、escaped 保活) "function f() { let k = 'a'; class C { [k]() { return 1; } } return Object.keys(new C())[0]; } f();", - // 死 const 声明存储(05-01 后 b=0,删之无害) + // 死 const 声明存储(b=0 无 const guard,删之无害) "function f() { const c = 5; return 6; } f();", // const 重赋值抛 TypeError(b=1 保留,两跑同抛) "const x = 1; x = 2;", @@ -66,7 +66,7 @@ fn precise_dce_semantics_preserved() { } } -/// 字节码级断言:顶层 `var x = 1; 2;` 经 dce+dce_precise 后 STORE_VAR 仍存在(B008)。 +/// 字节码级断言:顶层 `var x = 1; 2;` 经 dce+dce_precise 后 STORE_VAR 仍存在(顶层赋值全局可观察)。 #[test] fn top_level_store_var_survives_precise() { let allocator = oxide_parser::Allocator::default(); @@ -78,14 +78,17 @@ fn top_level_store_var_survives_precise() { oxide_dce::dce_precise(&mut ir, &live); let module = oxide_ir::lower::lower(&ir).expect("lower failed"); assert!( - module.bytecode.iter().any(|&i| OpCode::try_from(i as u8).ok() == Some(OpCode::STORE_VAR)), + module + .bytecode + .iter() + .any(|&i| OpCode::try_from(i as u8).ok() == Some(OpCode::STORE_VAR)), "顶层 STORE_VAR 必须存活" ); } /// IR 级断言:函数内局部死 STORE_VAR 被精确轮删除。 -/// dce_precise 按 D-03 不递归 nested,管线(05-09)逐函数处理——此处直接对 -/// 嵌套函数本体跑 liveness + dce_precise 证明删除生效。 +/// dce_precise 不递归 nested,编译管线逐函数处理——此处直接对嵌套函数本体 +/// 跑 liveness + dce_precise 证明删除生效。 #[test] fn precise_removes_dead_local_store() { let allocator = oxide_parser::Allocator::default(); @@ -97,21 +100,15 @@ fn precise_removes_dead_local_store() { let cfg = oxide_cfg::build_cfg(&sub); let live = oxide_liveness::liveness(&sub, &cfg); oxide_dce::dce_precise(&mut sub, &live); - assert!( - sub.insts.iter().all(|i| i.op != OpCode::STORE_VAR), - "函数内局部死 STORE_VAR 应被删除" - ); + assert!(sub.insts.iter().all(|i| i.op != OpCode::STORE_VAR), "函数内局部死 STORE_VAR 应被删除"); } -/// is_top_level 标志直接断言(D-19):顶层 true / 嵌套 false。 +/// is_top_level 标志直接断言:顶层 true / 嵌套 false。 #[test] fn top_level_flag_filled_by_emit() { let allocator = oxide_parser::Allocator::default(); let program = oxide_parser::parse(&allocator, "var x = 1; function g() { return 2; }").expect("parse failed"); let ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed"); assert!(ir.is_top_level, "顶层脚本 is_top_level 应为 true"); - assert!( - ir.nested.iter().any(|sub| !sub.is_top_level), - "嵌套函数 is_top_level 应为 false" - ); + assert!(ir.nested.iter().any(|sub| !sub.is_top_level), "嵌套函数 is_top_level 应为 false"); } diff --git a/crates/oxide_dce/tests/runtime_eq.rs b/crates/oxide_dce/tests/runtime_eq.rs index 9cf4a9f..8385bef 100644 --- a/crates/oxide_dce/tests/runtime_eq.rs +++ b/crates/oxide_dce/tests/runtime_eq.rs @@ -1,8 +1,8 @@ -//! 运行时等价对比(D-08):DCE 开/关两份字节码 lower + VM 执行结果一致。 +//! 运行时等价对比:DCE 开/关两份字节码 lower + VM 执行结果一致。 //! //! 同一 JS 源码走两条管线:parse → emit →(dce 开/关)→ lower → `Vm::run`, -//! 对比顶层执行结果。纯表读错 VM handler 也能被此兜底(D-08)。 -//! **不经 `Compiler::compile`**(D-10:测试层直调底层函数,不依赖开关)。 +//! 对比顶层执行结果。副作用纯表读错 VM handler 也能被此兜底。 +//! **不经 `Compiler::compile`**:测试层直调底层函数,不依赖编译器开关。 use oxide_vm::vm::Vm; use oxide_vm::JsValue; @@ -29,25 +29,25 @@ fn normalize(r: Result) -> (bool, String) { } } -/// 死代码样例集(04-RESEARCH.md Pattern 5 清单)。 +/// 死代码样例集(覆盖表达式丢弃、死分支、死闭包、coerce 抛错、const 重赋值等路径)。 /// 对每个样例:DCE 开与关分别执行,断言结果一致。 #[test] fn dead_code_removed_semantics_preserved() { // 覆盖:表达式语句丢弃结果、重复赋值只读最后值、if(false) 分支、死闭包、 - // try/catch 内死代码、对象操作数算术(D-05 coerce 风险兜底)、 - // 顶层表达式返回值(HALT reg0 链,Pitfall 2)、先调用后丢弃(CALL 隐式 reg0,Pitfall 1)、 - // const 重赋值抛错保留(Pitfall 5)。 + // try/catch 内死代码、对象操作数算术(coerce 抛错风险兜底)、 + // 顶层表达式返回值(HALT 隐式读 reg0 链)、先调用后丢弃(CALL 隐式写 reg0)、 + // const 重赋值抛错保留。 let samples = [ - "1+2;3+4", // 语句丢弃结果 - "'a'+'b';5;", // 语句丢弃结果(字符串) - "var x=1; x=2; x;", // 重复赋值只读最后值 - "function a(){return 1;} if(false){a();} 3;", // if(false) 分支 - "function f(){}; 1;", // 死闭包 - "try{1+2;}catch(e){3;} 4;", // try/catch 内死代码 - "({valueOf(){side=1; return 1}})+2;", // 对象操作数算术(coerce 兜底) - "1+2", // 顶层表达式返回值(Pitfall 2) - "function f(){return 1;} f(); 2+3;", // 先调用后丢弃(Pitfall 1) - "const x=1; x=2;", // const 重赋值(Pitfall 5) + "1+2;3+4", // 语句丢弃结果 + "'a'+'b';5;", // 语句丢弃结果(字符串) + "var x=1; x=2; x;", // 重复赋值只读最后值 + "function a(){return 1;} if(false){a();} 3;", // if(false) 分支 + "function f(){}; 1;", // 死闭包 + "try{1+2;}catch(e){3;} 4;", // try/catch 内死代码 + "({valueOf(){side=1; return 1}})+2;", // 对象操作数算术(coerce 兜底) + "1+2", // 顶层表达式返回值(HALT 隐式读 reg0 链) + "function f(){return 1;} f(); 2+3;", // 先调用后丢弃(CALL 隐式写 reg0) + "const x=1; x=2;", // const 重赋值(运行时抛错保留) ]; for src in samples { let a = normalize(run_source(src, true)); @@ -65,7 +65,7 @@ fn single_sample_top_level_return_value() { assert_eq!(a, (true, "3".to_string()), "顶层 1+2 应返回 3"); } -/// Pitfall 6 回归:脚本顶层变量赋值是全局可观察状态(class 计算属性名里的 `x = 1` +/// 顶层变量赋值是全局可观察状态(class 计算属性名里的 `x = 1` /// 会被测试 harness 的 `assert.sameValue(x, 1)` 观察),即使本函数内无 use, /// DCE 也**不得删除**对应 STORE_VAR——删除会改变外部可观察语义。 #[test] @@ -80,10 +80,7 @@ fn top_level_store_var_kept() { .bytecode .iter() .any(|&i| oxide_bytecode::opcode::opcode(i) == oxide_bytecode::opcode::OpCode::STORE_VAR); - assert!( - has_store_var, - "顶层 x 赋值(全局可观察)不应被 DCE 删除: {src}" - ); + assert!(has_store_var, "顶层 x 赋值(全局可观察)不应被 DCE 删除: {src}"); // 运行时兜底:DCE 开/关执行结果一致,且 x 最终值为 1(未被删赋值破坏) let a = normalize(run_source(src, true)); diff --git a/crates/oxide_emit/src/class/core.rs b/crates/oxide_emit/src/class/core.rs index 2d45e67..6071dde 100644 --- a/crates/oxide_emit/src/class/core.rs +++ b/crates/oxide_emit/src/class/core.rs @@ -1,2 +1 @@ //! 类编译核心预留模块(当前为空,为后续类相关公共状态预留位置)。 - diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index 450dd86..e023c37 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -3,9 +3,9 @@ //! 函数:`emit_class`。 use crate::{CompileCtx, Emitter, FunctionBodyContext}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::{Class, ClassElement, MethodDefinitionKind, PropertyKey}; impl Emitter { @@ -66,9 +66,20 @@ impl Emitter { let emit_instance_fields = |compiler: &Emitter, field_ctx: &mut CompileCtx| -> Result<(), String> { for field in &instance_fields { if let PropertyKey::PrivateIdentifier(private) = &field.key { - compiler.emit_private_field_init(Operand::This, private.name.as_str(), field.value.as_ref(), field_ctx)?; + compiler.emit_private_field_init( + Operand::This, + private.name.as_str(), + field.value.as_ref(), + field_ctx, + )?; } else { - compiler.emit_public_field_init(Operand::This, &field.key, field.computed, field.value.as_ref(), field_ctx)?; + compiler.emit_public_field_init( + Operand::This, + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + )?; } } Ok(()) @@ -108,7 +119,9 @@ impl Emitter { module.insts.extend(field_ctx.insts); module.constants = field_ctx.constants; module.n_registers = field_ctx.max_regs.max(1); - module.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + module + .insts + .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); } module }; diff --git a/crates/oxide_emit/src/class/field.rs b/crates/oxide_emit/src/class/field.rs index 2091a61..8ca1e2d 100644 --- a/crates/oxide_emit/src/class/field.rs +++ b/crates/oxide_emit/src/class/field.rs @@ -5,10 +5,10 @@ use oxide_ir::operand::Operand; use oxide_parser::{ClassElement, PropertyKey}; impl Emitter { - /// Emit static field initializers and static blocks in source order. - /// JS class semantics interleave them (e.g. `static x = 1; static { - /// this.y = 1 } static z = this.y + 1` must run in that order). Emitting - /// all fields before all blocks would make `z` read `y` before it is set. + /// 按源码顺序发静态字段初始化与静态块。 + /// JS 类语义要求两者交织执行(如 `static x = 1; static { this.y = 1 } + /// static z = this.y + 1` 必须按此顺序),若全部字段先于静态块发出, + /// `z` 会在 `y` 赋值前读到它。 pub(crate) fn emit_class_static_elements( &self, elements: &[ClassElement], ctor_reg: u32, ctx: &mut CompileCtx, ) -> Result<(), String> { @@ -23,9 +23,20 @@ impl Emitter { let prop = prop.as_ref(); if prop.r#static { if let PropertyKey::PrivateIdentifier(private) = &prop.key { - self.emit_private_field_init(Operand::Reg(ctor_reg), private.name.as_str(), prop.value.as_ref(), ctx)?; + self.emit_private_field_init( + Operand::Reg(ctor_reg), + private.name.as_str(), + prop.value.as_ref(), + ctx, + )?; } else { - self.emit_public_field_init(Operand::Reg(ctor_reg), &prop.key, prop.computed, prop.value.as_ref(), ctx)?; + self.emit_public_field_init( + Operand::Reg(ctor_reg), + &prop.key, + prop.computed, + prop.value.as_ref(), + ctx, + )?; } } } diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index cdbbb0b..557c63e 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -2,10 +2,10 @@ //! 负责单个方法函数体(含 `super` 与 home object)。 use crate::{CompileCtx, Emitter, FunctionBodyContext}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{ClassElement, MethodDefinitionKind, PropertyKey}; impl Emitter { @@ -36,9 +36,19 @@ impl Emitter { match method.kind { MethodDefinitionKind::Method => { if method.computed { - ctx.inst(Inst::new(OpCode::SET_PROP_DYNAMIC, Operand::Reg(home_reg), Operand::Reg(key_reg), Operand::Reg(accessor_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP_DYNAMIC, + Operand::Reg(home_reg), + Operand::Reg(key_reg), + Operand::Reg(accessor_reg), + )); } else { - ctx.inst(Inst::new(OpCode::SET_PROP, Operand::Reg(home_reg), Operand::Reg(accessor_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP, + Operand::Reg(home_reg), + Operand::Reg(accessor_reg), + Operand::Reg(key_reg), + )); } } MethodDefinitionKind::Get | MethodDefinitionKind::Set => { diff --git a/crates/oxide_emit/src/class/prototype.rs b/crates/oxide_emit/src/class/prototype.rs index 5020b5a..83c4360 100644 --- a/crates/oxide_emit/src/class/prototype.rs +++ b/crates/oxide_emit/src/class/prototype.rs @@ -1,10 +1,10 @@ //! 类原型对象构造:`emit_class_prototype` 建立 `Class.prototype` 与属性初始化。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; impl Emitter { pub(crate) fn emit_class_prototype( @@ -17,21 +17,46 @@ impl Emitter { let parent_proto_key_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(parent_proto_key_reg), proto_key_idx)); let parent_proto_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PROP, Operand::Reg(super_reg), Operand::Reg(parent_proto_reg), Operand::Reg(parent_proto_key_reg))); + ctx.inst(Inst::new( + OpCode::GET_PROP, + Operand::Reg(super_reg), + Operand::Reg(parent_proto_reg), + Operand::Reg(parent_proto_key_reg), + )); let proto_link_idx = ctx.add_constant(Constant::String("__proto__".to_string())); let proto_link_key_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(proto_link_key_reg), proto_link_idx)); - ctx.inst(Inst::new(OpCode::SET_PROP, Operand::Reg(proto_reg), Operand::Reg(parent_proto_reg), Operand::Reg(proto_link_key_reg))); - ctx.inst(Inst::new(OpCode::SET_PROP, Operand::Reg(ctor_reg), Operand::Reg(super_reg), Operand::Reg(proto_link_key_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP, + Operand::Reg(proto_reg), + Operand::Reg(parent_proto_reg), + Operand::Reg(proto_link_key_reg), + )); + ctx.inst(Inst::new( + OpCode::SET_PROP, + Operand::Reg(ctor_reg), + Operand::Reg(super_reg), + Operand::Reg(proto_link_key_reg), + )); } let ctor_key_idx = ctx.add_constant(Constant::String("constructor".to_string())); let ctor_key_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(ctor_key_reg), ctor_key_idx)); - ctx.inst(Inst::new(OpCode::SET_PROP, Operand::Reg(proto_reg), Operand::Reg(ctor_reg), Operand::Reg(ctor_key_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP, + Operand::Reg(proto_reg), + Operand::Reg(ctor_reg), + Operand::Reg(ctor_key_reg), + )); let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); let proto_key_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(proto_key_reg), proto_key_idx)); - ctx.inst(Inst::new(OpCode::SET_PROP, Operand::Reg(ctor_reg), Operand::Reg(proto_reg), Operand::Reg(proto_key_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP, + Operand::Reg(ctor_reg), + Operand::Reg(proto_reg), + Operand::Reg(proto_key_reg), + )); Ok(()) } } diff --git a/crates/oxide_emit/src/closure.rs b/crates/oxide_emit/src/closure.rs index 9c5071b..074c202 100644 --- a/crates/oxide_emit/src/closure.rs +++ b/crates/oxide_emit/src/closure.rs @@ -12,7 +12,6 @@ use oxide_parser::{Expression, Statement}; use crate::Emitter; impl Emitter { - fn collect_fn_param_names(&self, params: &oxide_parser::FormalParameters) -> HashSet { let mut names = HashSet::new(); for p in ¶ms.items { @@ -288,7 +287,9 @@ impl Emitter { } Expression::StaticMemberExpression(m) => self.collect_capture_names_expr(&m.object, ref_set, shadow, out), Expression::PrivateFieldExpression(m) => self.collect_capture_names_expr(&m.object, ref_set, shadow, out), - Expression::ParenthesizedExpression(p) => self.collect_capture_names_expr(&p.expression, ref_set, shadow, out), + Expression::ParenthesizedExpression(p) => { + self.collect_capture_names_expr(&p.expression, ref_set, shadow, out) + } Expression::TemplateLiteral(tl) => { for e in &tl.expressions { self.collect_capture_names_expr(e, ref_set, shadow, out); @@ -348,11 +349,7 @@ impl Emitter { // 名字排序分配 cell_idx(稳定跨 run,父 MAKE_CELL 与子 upvalue 统一引用) let mut sorted: Vec = names.into_iter().collect(); sorted.sort(); - sorted - .into_iter() - .enumerate() - .map(|(i, n)| (n, i as u8)) - .collect() + sorted.into_iter().enumerate().map(|(i, n)| (n, i as u8)).collect() } /// 只从嵌套函数节点进入扫描(本函数直接引用不算捕获)。 @@ -498,7 +495,6 @@ impl Emitter { } } - /// 分析子函数 body:引用父级绑定的名字 → upvalue_captures(enclosing_reg 由父 emit 完成后填充)。 /// 分析子函数 body:引用父级被捕获绑定的名字 → upvalue_captures。 /// cell_idx 直接取父 captured_bindings 映射(父 emit 前已确定,索引一致); /// enclosing_reg 由父 emit 完成后填充(assemble_ir)。 @@ -509,9 +505,8 @@ impl Emitter { let mut names = HashSet::new(); self.collect_capture_names_shadowed(body_stmts, &parent_names, sub_own, &mut names); // HashSet 迭代序带随机种子(进程级非确定),必须排序使 upvalue_captures 的顺序与 - // 父 captured_bindings 的 cell_idx(BTreeMap 名字序)对齐——否则 uv_idx(identifier.rs - // LOAD_UPVALUE 的 a 槽编码)与 cell_idx 错位,读错 upvalue(hoisted_function_reads_outer_var_and_function - // 抖动根因)。 + // 父 captured_bindings 的 cell_idx(BTreeMap 名字序)对齐——否则 LOAD_UPVALUE 的 + // a 槽 uv_idx 编码与 cell_idx 错位,读错 upvalue。 let mut names: Vec = names.into_iter().collect(); names.sort(); names diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 957a8f8..a6a43a7 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -124,9 +124,8 @@ pub(crate) struct FieldBuffer { pub(crate) labels: Vec<(LabelId, usize)>, } -/// A labeled-statement scope active during emission. `break label` targets -/// `break_label`; `continue label` targets `continue_label` (only set when the -/// labeled statement directly wraps an iteration statement). +/// 标签语句作用域:编译期内登记 `break label` / `continue label` 的跳转目标。 +/// `continue_label` 仅在标签直接包裹迭代语句时存在。 #[derive(Debug, Clone)] pub struct LabelScope { pub(crate) name: String, @@ -146,9 +145,8 @@ pub struct CompileCtx { pub(crate) labels: LabelCtx, pub(crate) scopes: ScopeCtx, pub(crate) nested: Vec, - /// Register holding `this` in the enclosing function context. - /// Used by arrow functions to capture lexical `this`. - /// Initialized to 254 (conventional this register) at the top level. + /// 外层函数上下文中持有 `this` 的寄存器。 + /// 箭头函数用它捕获词法 `this`;顶层初始化为 254(约定 this 寄存器)。 pub(crate) enclosing_this_reg: u8, pub(crate) in_derived_constructor: bool, pub(crate) in_instance_method: bool, @@ -247,7 +245,7 @@ impl CompileCtx { pub(crate) fn alloc_reg(&mut self) -> u32 { let r = self.next_reg; - // vreg 化(D-02):寄存器号无上限,RegAlloc 阶段负责压缩到物理域(≤253)。 + // vreg 化:寄存器号无上限,RegAlloc 阶段负责压缩到物理域(≤253)。 // 254/255 是 VM 保留的 this/new.target,vreg 世界允许虚拟号越过它们, // 只有 RegAlloc 完成映射后 lower 的物理域检查才相关。 self.next_reg += 1; @@ -406,9 +404,8 @@ impl CompileCtx { self.labels.label_scopes.iter().rev().find(|s| s.name == name) } - /// Queue a label name to be bound to the continue target of the next loop - /// emitted as the labeled statement's body. Rejects duplicates in the active - /// or pending sets. + /// 登记一个待绑定标签名:该标签将作为下一个 emit 的循环(标签语句体)的 + /// continue 目标。活动集合与待绑定集合中出现重名报错。 pub(crate) fn queue_loop_label(&mut self, name: &str) -> Result<(), String> { if self.labels.label_scopes.iter().any(|s| s.name == name) || self.labels.pending_loop_labels.iter().any(|n| n == name) @@ -419,8 +416,8 @@ impl CompileCtx { Ok(()) } - /// Drain queued loop labels into active scopes bound to this loop's break and - /// continue targets. Returns how many scopes were pushed (to pop after). + /// 把待绑定标签名落地为活动标签作用域,绑定到本次循环的 break/continue 目标。 + /// 返回压入的作用域个数(供事后对称弹出)。 pub(crate) fn take_pending_loop_labels(&mut self, break_label: LabelId, continue_label: LabelId) -> usize { let names = std::mem::take(&mut self.labels.pending_loop_labels); let count = names.len(); @@ -458,9 +455,8 @@ impl CompileCtx { } pub(crate) fn pre_register_builtins(&mut self) { - // Builtin globals are resolved lazily by lookup_or_builtin(). Keeping this - // hook preserves the compile pipeline without reserving ~60 registers in - // every module. + // builtin 全局由 lookup_or_builtin() 惰性解析。保留此钩子维持编译管线形态, + // 避免在每个模块预留约 60 个寄存器槽。 } /// 组装 IRFunction(两出口共用),take 走编译产物状态。 @@ -546,13 +542,11 @@ impl Emitter { Ok((param_specs, body_stmts)) } - /// Compile a function body (used for FD, FE, and arrow functions). - /// This performs both counting and emitting in one pass. - /// When `is_expression_body` is true (arrow function with expression body), - /// the last expression's value is returned instead of undefined. - /// `is_arrow` controls whether super flags are inherited from the parent scope - /// (true for arrow functions, which have lexical super) or reset to false - /// (false for regular functions, which create a new super scope). + /// 编译函数体(函数声明/函数表达式/箭头函数共用),单 pass 完成发码。 + /// + /// `is_expression_body` 为 true(箭头表达式体)时返回最后一个表达式的值, + /// 否则返回 undefined。`is_arrow` 控制 super 相关标志的继承:箭头函数词法 + /// 继承外层 super,普通函数重置 super 作用域。 pub(crate) fn compile_function_body<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool, is_arrow: bool, @@ -588,13 +582,13 @@ impl Emitter { ) } - - /// Pre-register all builtin identifiers referenced anywhere in this body - /// (expressions, member objects, call args, class fields, etc.) so their - /// register slots are reserved *before* any temporary register is emitted. + /// 预注册 builtin 引用并编译函数体(普通/箭头/类元素方法共用入口)。 /// - /// vreg 化(D-01)后临时值不复用(独立 vreg),但 builtin 槽预注册仍 - /// 保证分配序稳定:builtin 槽先于临时值池,nested 继承边界(B012)不受扰。 + /// 本函数体内任意位置(表达式、成员对象、调用实参、类字段等)引用的内置全局 + /// 标识符,其寄存器槽都先于任何临时寄存器登记。 + /// + /// vreg 化后临时值不复用(独立 vreg),但 builtin 槽预注册仍保证分配序稳定: + /// builtin 槽先于临时值池,嵌套函数继承边界不受扰。 #[allow(clippy::too_many_arguments)] pub(crate) fn compile_function_body_with_field_hooks<'a, E>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, @@ -606,16 +600,16 @@ impl Emitter { { let mut ctx = CompileCtx::new(); - // Inherit parent's builtin_reg_map so builtin identifiers (Math, Object, etc.) - // resolve to the correct pre-allocated registers in the sub-module's register file. + // 继承父内置寄存器映射:子模块寄存器文件中,内置标识符(Math、Object 等) + // 解析到父预先分配的槽位。 ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone(); ctx.scopes.private_name_map = parent_ctx.scopes.private_name_map.clone(); ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id; - // Propagate enclosing_this_reg so nested arrow functions capture the correct `this`. + // 传递 enclosing_this_reg:嵌套箭头函数捕获正确的 `this`。 ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg; - // Arrow functions inherit lexical super. Class method bodies also need the - // class-provided super context for their top-level body compilation. + + // 箭头函数词法继承 super;类方法体顶层编译也需要类提供的 super 上下文。 if matches!(body_context, FunctionBodyContext::Arrow | FunctionBodyContext::ClassElement) { ctx.in_derived_constructor = parent_ctx.in_derived_constructor; ctx.in_instance_method = parent_ctx.in_instance_method; @@ -626,8 +620,7 @@ impl Emitter { ctx.in_static_method = false; } - // Inherit parent's global scope entries so previously-declared function names - // are visible from within the body. + // 继承父全局作用域条目:先前声明的函数名在函数体内可见。 let mut inherited_reg_start = 1u32.max(ctx.builtin_reg_floor()); for (name, binding) in &parent_ctx.scopes.symbols.scopes[0].bindings { ctx.scopes.symbols.scopes[0].bindings.insert( @@ -653,20 +646,17 @@ impl Emitter { } ctx.reserved_reg_start = inherited_reg_start.max(1); - // Align next_reg with builtin count so both count and emit passes start at the - // same register offset (params go after builtin slots). + // 让 next_reg 与 builtin 槽位对齐,参数在 builtin 槽之后分配。 ctx.reset_regs(); let param_base = self.emit_params_prologue(param_specs, body_stmts, parent_ctx, &mut ctx, body_context)?; self.predeclare_function_declarations(body_stmts, &mut ctx); - // Pre-register builtin identifier references before emitting any temporary - // register, so builtin slots never collide with reused temporaries. + // 预注册 builtin 引用(先于任何临时寄存器),builtin 槽不与被复用的临时值冲突。 self.pre_register_builtin_references(body_stmts, &mut ctx); - // Pre-declare `var` names so hoisted function declarations (emitted in the - // first sub-pass below) can resolve the outer vars they close over. + // 预声明 `var` 名,使首个 sub-pass 中提升的函数声明能解析其闭包引用的外层 var。 self.predeclare_var_declarations(body_stmts, &mut ctx); if let Some(emit) = emit_fields.as_mut() { @@ -692,11 +682,10 @@ impl Emitter { } } - // Emit body statements(先函数声明 hoisting,再其余)。 + // 发 body 语句(先函数声明 hoisting,再其余)。 let last_result_reg = self.emit_body_stmts(body_stmts, &mut ctx)?; - // Emit implicit RETURN: expression body returns the last expression, - // statement body returns undefined. + // 隐式 RETURN:表达式体返回最后表达式,语句体返回 undefined。 if is_expression_body { if let Some(reg) = last_result_reg { ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg), Operand::None, Operand::None)); @@ -731,7 +720,7 @@ impl Emitter { ctx.push_scope_with_kind(ScopeKind::FunctionScope); let param_base = ctx.next_reg; - // Emit parameters and destructuring prologue. + // 发参数与解构 prologue。 for spec in param_specs { let name = spec.register_name(); let reg = ctx.alloc_reg(); @@ -757,11 +746,16 @@ impl Emitter { let name = spec.register_name(); if let Some(&cell_idx) = ctx.captured_bindings.get(name) { let reg = ctx.lookup(name)?; - ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(reg), Operand::Imm(cell_idx as u16), Operand::None)); + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); } } - // Free variable analysis for upvalue capture (Ordinary + Arrow functions only) + // 自由变量分析(仅普通/箭头函数):收集 upvalue 捕获。 if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) { ctx.current_upvalue_captures = self.collect_upvalue_names(body_stmts, &parent_ctx.captured_bindings, &ctx.own_bindings); @@ -850,35 +844,33 @@ impl Emitter { } } - /// Emit a full program into an IRFunction (top-level module body). - /// Split from the original `Compiler::compile`: this is the emit half; - /// oxide_compiler::Compiler::compile calls this then `oxide_ir::lower::lower`. + /// 把完整程序编译为顶层模块体的 IRFunction。 + /// + /// 调用方为 `oxide_compiler::Compiler::compile`:本函数完成 emit 半程, + /// 随后由 `oxide_ir::lower::lower` 降为字节码。 pub fn emit_program(&self, program: &oxide_parser::Program) -> Result { let mut ctx = CompileCtx::new(); ctx.pre_register_builtins(); self.predeclare_function_declarations(&program.body, &mut ctx); - // Pre-register builtin identifier references before any temporary register - // is emitted, keeping builtin slots clear of the temporary register pool. + // 预注册 builtin 引用(先于任何临时寄存器),builtin 槽不进入临时寄存器池。 self.pre_register_builtin_references(&program.body, &mut ctx); - // Pre-declare top-level `var` names so hoisted function declarations - // (emitted in the first sub-pass below) can resolve the outer vars. + // 预声明顶层 `var` 名,使首个 sub-pass 中提升的函数声明能解析外层 var。 self.predeclare_var_declarations(&program.body, &mut ctx); // 闭包捕获分析(AST 级,emit 前确定) ctx.own_bindings = self.collect_own_binding_names(&[], &program.body); ctx.captured_bindings = self.collect_captured_bindings(&program.body, &ctx.own_bindings); - // First sub-pass: emit FunctionDeclarations (hoisting) - // This ensures function objects are available before any code runs. + // 首个 sub-pass:发函数声明(hoisting),保证任何代码运行前函数对象已就绪。 for stmt in &program.body { if matches!(stmt, Statement::FunctionDeclaration(_)) { self.emit_statement(stmt, &mut ctx)?; } } - // Second sub-pass: emit all other statements + // 第二个 sub-pass:发其余所有语句。 let mut last_result: Option = None; for stmt in &program.body { if matches!(stmt, Statement::FunctionDeclaration(_)) { diff --git a/crates/oxide_emit/src/emit_ctx.rs b/crates/oxide_emit/src/emit_ctx.rs index b9135a9..44196f2 100644 --- a/crates/oxide_emit/src/emit_ctx.rs +++ b/crates/oxide_emit/src/emit_ctx.rs @@ -1,24 +1,23 @@ -//! Grouped `CompileCtx` sub-contexts. +//! `CompileCtx` 的成组子上下文。 //! -//! Splits the compiler's central `CompileCtx` so syntax-domain workers borrow -//! only the slice they need: `LabelCtx` for jump-target resolution, `ScopeCtx` -//! for identifier binding. `PatternCtx` is reserved for destructuring work. -//! Execution-stream fields (insts, registers, pc, …) stay flat on -//! `CompileCtx`. +//! 把编译器中心 `CompileCtx` 拆开,语法域 worker 只借用所需切片: +//! `LabelCtx` 负责跳转目标解析,`ScopeCtx` 负责标识符绑定。 +//! `PatternCtx` 为解构工作预留。执行流字段(insts/registers/pc 等)仍 +//! 平铺在 `CompileCtx` 上。 use crate::symbol_table::SymbolTable; use crate::LabelScope; use oxide_ir::operand::LabelId; -/// Jump-target / labeled-statement resolution state. +/// 跳转目标 / 标签语句解析状态。 pub(crate) struct LabelCtx { /// label id → 指令下标。id 连续递增,Vec 索引即 id;写入前须扩容。 pub(crate) label_pos: Vec>, pub(crate) loop_stack: Vec<(LabelId, LabelId)>, pub(crate) switch_stack: Vec, - /// Active labeled-statement scopes (resolves `break label` / `continue label`). + /// 活动标签语句作用域(解析 `break label` / `continue label`)。 pub(crate) label_scopes: Vec, - /// Label names awaiting binding to the next emitted loop's continue target. + /// 等待绑定到下一个循环 continue 目标的标签名。 pub(crate) pending_loop_labels: Vec, pub(crate) label_counter: u32, } @@ -33,7 +32,7 @@ impl LabelCtx { } } -/// Identifier-binding state: symbols, builtin registers, private names. +/// 标识符绑定状态:符号表、builtin 寄存器、私有名。 pub(crate) struct ScopeCtx { pub(crate) symbols: SymbolTable, pub(crate) builtin_reg_map: Vec<(String, u32)>, @@ -41,7 +40,6 @@ pub(crate) struct ScopeCtx { pub(crate) next_private_name_id: u32, } -/// Reserved for destructuring-pattern state (Phase 15). Intentionally empty -/// today; named so future pattern work has a home without touching `ScopeCtx`. +/// 预留的解构 pattern 状态。当前为空;为后续解构工作预留归属地,避免改动 `ScopeCtx`。 #[allow(dead_code)] pub(crate) struct PatternCtx; diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index 22771d5..a634735 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -2,10 +2,10 @@ //! 函数:`emit_assignment_expression`。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::AssignmentOperator; impl Emitter { @@ -22,13 +22,23 @@ impl Emitter { let key_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx)); let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(obj_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(obj_reg), + Operand::None, + )); ctx.inst(Inst::ic_get(Operand::Reg(result_reg), Operand::Reg(key_reg))); self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; ctx.labels.set_label_pos(store_label, ctx.insts.len()); let val_reg = self.emit_expression(&assign.right, ctx)?; ctx.inst(Inst::ic_set(Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg))); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(val_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(val_reg), + Operand::None, + )); ctx.labels.set_label_pos(end_label, ctx.insts.len()); return Ok(result_reg); } @@ -63,19 +73,39 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?; let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(key_reg), Operand::Reg(result_reg))); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + Operand::Reg(result_reg), + )); self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; ctx.labels.set_label_pos(store_label, ctx.insts.len()); let val_reg = self.emit_expression(&assign.right, ctx)?; - ctx.inst(Inst::new(OpCode::SET_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(key_reg), Operand::Reg(val_reg))); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(val_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::SET_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + Operand::Reg(val_reg), + )); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(val_reg), + Operand::None, + )); ctx.labels.set_label_pos(end_label, ctx.insts.len()); return Ok(result_reg); } let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?; let val_reg = self.emit_expression(&assign.right, ctx)?; - ctx.inst(Inst::new(OpCode::SET_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(key_reg), Operand::Reg(val_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + Operand::Reg(val_reg), + )); Ok(val_reg) } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { if assign.operator != AssignmentOperator::Assign { @@ -84,7 +114,12 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; let val_reg = self.emit_expression(&assign.right, ctx)?; let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; - ctx.inst(Inst::new(OpCode::SET_PRIVATE, Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::SET_PRIVATE, + Operand::Reg(obj_reg), + Operand::Reg(val_reg), + Operand::Reg(key_reg), + )); Ok(val_reg) } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left { if assign.operator != AssignmentOperator::Assign { @@ -94,14 +129,29 @@ impl Emitter { let name = id_ref.name.as_str(); let var_reg = ctx.lookup_or_global(name); let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(var_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(var_reg), + Operand::None, + )); self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; ctx.labels.set_label_pos(store_label, ctx.insts.len()); let val_reg = self.emit_expression(&assign.right, ctx)?; let is_const = ctx.lookup_const_flag(name); let const_flag = if is_const { 1 } else { 0 }; - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(val_reg), Operand::Imm(const_flag))); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(val_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(var_reg), + Operand::Reg(val_reg), + Operand::Imm(const_flag), + )); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(val_reg), + Operand::None, + )); ctx.labels.set_label_pos(end_label, ctx.insts.len()); Ok(result_reg) } else if assign.operator == AssignmentOperator::Addition @@ -143,20 +193,35 @@ impl Emitter { } else { let val_reg = self.emit_expression(&assign.right, ctx)?; let name = id_ref.name.as_str(); - // Check if target is an upvalue reference + // 目标若是 upvalue 引用,走 STORE_UPVALUE if let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == name) { - ctx.inst(Inst::new(OpCode::STORE_UPVALUE, Operand::None, Operand::Reg(val_reg), Operand::Imm(uv_idx as u16))); + ctx.inst(Inst::new( + OpCode::STORE_UPVALUE, + Operand::None, + Operand::Reg(val_reg), + Operand::Imm(uv_idx as u16), + )); return Ok(val_reg); } - // Check if target is a captured cell + // 目标若是被捕获 cell,走 CELL_SET if let Some(&cell_idx) = ctx.captured_bindings.get(name) { - ctx.inst(Inst::new(OpCode::CELL_SET, Operand::None, Operand::Reg(val_reg), Operand::Imm(cell_idx as u16))); + ctx.inst(Inst::new( + OpCode::CELL_SET, + Operand::None, + Operand::Reg(val_reg), + Operand::Imm(cell_idx as u16), + )); return Ok(val_reg); } let var_reg = ctx.lookup_or_global(name); let is_const = ctx.lookup_const_flag(name); let const_flag = if is_const { 1 } else { 0 }; - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(val_reg), Operand::Imm(const_flag))); + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(var_reg), + Operand::Reg(val_reg), + Operand::Imm(const_flag), + )); Ok(val_reg) } } else if matches!( diff --git a/crates/oxide_emit/src/expr/call.rs b/crates/oxide_emit/src/expr/call.rs index 073f564..4d3b752 100644 --- a/crates/oxide_emit/src/expr/call.rs +++ b/crates/oxide_emit/src/expr/call.rs @@ -2,10 +2,10 @@ //! 函数:`emit_call_expression`、`emit_call_domain`。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; 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 { @@ -41,7 +41,12 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; let callee_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PRIVATE, Operand::Reg(callee_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::GET_PRIVATE, + Operand::Reg(callee_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); (callee_reg, obj_reg) } Expression::StaticMemberExpression(member) => { @@ -69,7 +74,12 @@ impl Emitter { }; ctx.inst(Inst::new(op, Operand::Reg(callee_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); } else { - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(callee_reg), Operand::Reg(obj_reg), Operand::None)); + 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) @@ -125,9 +135,9 @@ impl Emitter { } } -/// 把调用参数打包到连续寄存器块(VM 按 `regs[first_arg + i]` 连续读参数,D-17/调用契约)。 -/// vreg 化(05-02)后各参数由独立 vreg 承载,复杂表达式(对象/数组字面量、嵌套调用) -/// 的临时寄存器会使参数 vreg 不连续——这里检测到不连续时用 MOV 打包到新连续块。 +/// 把调用参数打包到连续寄存器块(VM 按 `regs[first_arg + i]` 连续读参数)。 +/// 各参数由独立 vreg 承载,复杂表达式(对象/数组字面量、嵌套调用)的临时寄存器 +/// 会使参数 vreg 不连续——检测到不连续时用 MOV 打包到新连续块。 /// 返回首参寄存器;参数为空时返回 0。 fn pack_arg_regs(arg_regs: &mut [u32], ctx: &mut CompileCtx) -> u32 { let consecutive = arg_regs.windows(2).all(|w| w[1] == w[0] + 1); diff --git a/crates/oxide_emit/src/expr/function/mod.rs b/crates/oxide_emit/src/expr/function/mod.rs index 4d67ca2..22a8e24 100644 --- a/crates/oxide_emit/src/expr/function/mod.rs +++ b/crates/oxide_emit/src/expr/function/mod.rs @@ -11,12 +11,12 @@ impl Emitter { fn emit_arrow_function_expression( &self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx, ) -> Result { - // Rest params not yet supported + // 未支持:箭头函数 rest 参数 if let Some(_rest) = &arrow.params.rest { return Err("rest params in arrow functions not yet supported".into()); } - // Extract param names (same pattern as FunctionExpression) + // 提取形参名(与函数表达式相同的形态) let mut param_names = Vec::new(); for (idx, param) in arrow.params.items.iter().enumerate() { match ¶m.pattern { @@ -32,8 +32,7 @@ impl Emitter { } } - // Expression body: pass body statements directly with is_expression_body=true. - // Statement body: pass body statements with is_expression_body=false. + // 表达式体:以 is_expression_body=true 编译;语句体:以 false 编译。 let body_stmts = &arrow.body.statements; let is_expr_body = arrow.expression; @@ -41,7 +40,7 @@ impl Emitter { sub_module.is_arrow = true; ctx.nested.push(sub_module); - // 1-indexed: 0 = no sub_module (sentinel) + // 子模块下标 1 起始:0 保留为无子模块哨兵 let sub_idx = ctx.nested.len() as u16; let r = ctx.alloc_reg(); @@ -50,7 +49,7 @@ impl Emitter { } fn emit_function_expression(&self, fe: &oxide_parser::Function, ctx: &mut CompileCtx) -> Result { - // FunctionExpression: compile body, emit LOAD_CONST(BytecodeFunc) + // 函数表达式:编译函数体为子模块,create_closure 实例化闭包 let mut param_names = Vec::new(); for (idx, param) in fe.params.items.iter().enumerate() { match ¶m.pattern { @@ -73,7 +72,7 @@ impl Emitter { sub_module.function_name = Some(id.name.to_string()); } ctx.nested.push(sub_module); - // 1-indexed: 0 = no sub_module (sentinel) + // 子模块下标 1 起始:0 保留为无子模块哨兵 let sub_idx = ctx.nested.len() as u16; let r = ctx.alloc_reg(); diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index 0b84420..3946e2a 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -1,9 +1,9 @@ //! 标识符表达式 emit:`emit_identifier_expression` 按绑定/builtin/全局解析寄存器。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; impl Emitter { pub(crate) fn emit_identifier_expression( @@ -23,7 +23,12 @@ impl Emitter { if let Some(&cell_idx) = ctx.captured_bindings.get(name) { let r = ctx.alloc_reg(); if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) { - ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r), Operand::Reg(binding.reg), Operand::Imm(cell_idx as u16))); + ctx.inst(Inst::new( + OpCode::CELL_GET, + Operand::Reg(r), + Operand::Reg(binding.reg), + Operand::Imm(cell_idx as u16), + )); } else { ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r), Operand::None, Operand::Imm(cell_idx as u16))); } diff --git a/crates/oxide_emit/src/expr/literal.rs b/crates/oxide_emit/src/expr/literal.rs index dbdc00f..ba11ae4 100644 --- a/crates/oxide_emit/src/expr/literal.rs +++ b/crates/oxide_emit/src/expr/literal.rs @@ -2,10 +2,10 @@ //! 整数走立即数编码(`is_int_literal`),其余入常量池。 use crate::{is_int_literal, CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; 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 { @@ -74,7 +74,12 @@ impl Emitter { let flags_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(flags_reg), flags_ci)); let r = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::CREATE_REGEXP, Operand::Reg(r), Operand::Reg(pat_reg), Operand::Reg(flags_reg))); + ctx.inst(Inst::new( + OpCode::CREATE_REGEXP, + Operand::Reg(r), + Operand::Reg(pat_reg), + Operand::Reg(flags_reg), + )); Ok(r) } else { Err(format!("unsupported regexp literal: {:?}", lit)) diff --git a/crates/oxide_emit/src/expr/member/mod.rs b/crates/oxide_emit/src/expr/member/mod.rs index 60179c2..9f9ed99 100644 --- a/crates/oxide_emit/src/expr/member/mod.rs +++ b/crates/oxide_emit/src/expr/member/mod.rs @@ -3,10 +3,10 @@ //! `emit_private_field_expression`、`emit_chain_expression`、`emit_member_domain`。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; 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 { @@ -47,7 +47,12 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?; let r = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(key_reg), Operand::Reg(r))); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + Operand::Reg(r), + )); Ok(r) } @@ -57,16 +62,28 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; let r = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PRIVATE, Operand::Reg(r), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::GET_PRIVATE, + Operand::Reg(r), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); Ok(r) } - fn emit_chain_expression(&self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx) -> Result { + fn emit_chain_expression( + &self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx, + ) -> Result { let short_label = ctx.next_label_id(); let end_label = ctx.next_label_id(); let value_reg = self.emit_chain_element(&chain.expression, Some(short_label), ctx)?; let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(value_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(value_reg), + Operand::None, + )); ctx.inst(Inst::jmp(end_label)); ctx.labels.set_label_pos(short_label, ctx.insts.len()); let undefined_idx = ctx.add_constant(Constant::Undefined); diff --git a/crates/oxide_emit/src/expr/object/array.rs b/crates/oxide_emit/src/expr/object/array.rs index d82e9cc..a5fb64d 100644 --- a/crates/oxide_emit/src/expr/object/array.rs +++ b/crates/oxide_emit/src/expr/object/array.rs @@ -1,10 +1,10 @@ //! 数组字面量 emit:`emit_array_expression` 逐元素求值并建数组(含 holes)。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; impl Emitter { pub(crate) fn emit_array_expression( &self, arr: &oxide_parser::ArrayExpression, ctx: &mut CompileCtx, @@ -20,7 +20,12 @@ impl Emitter { let idx_reg = ctx.alloc_reg(); let idx = ctx.add_constant(Constant::Int(i as i32)); ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx)); - ctx.inst(Inst::new(OpCode::SET_ELEM, Operand::Reg(arr_reg), Operand::Reg(idx_reg), Operand::Reg(val_reg))); + ctx.inst(Inst::new( + OpCode::SET_ELEM, + Operand::Reg(arr_reg), + Operand::Reg(idx_reg), + Operand::Reg(val_reg), + )); } Ok(arr_reg) } diff --git a/crates/oxide_emit/src/expr/object/object_literal.rs b/crates/oxide_emit/src/expr/object/object_literal.rs index a789b5d..e5e69c4 100644 --- a/crates/oxide_emit/src/expr/object/object_literal.rs +++ b/crates/oxide_emit/src/expr/object/object_literal.rs @@ -1,10 +1,10 @@ //! 对象字面量 emit:`emit_object_expression` 逐属性定义(含 getter/setter/展开)。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{Expression, ObjectPropertyKind, PropertyKey, PropertyKind}; impl Emitter { @@ -52,7 +52,12 @@ impl Emitter { sub_mod.function_name = Some(prop_name.to_string()); } } - ctx.inst(Inst::new(OpCode::SET_PROP, Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP, + Operand::Reg(obj_reg), + Operand::Reg(val_reg), + Operand::Reg(key_reg), + )); } } } diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index e42f628..c6b84b7 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -3,10 +3,10 @@ //! 函数:`emit_operator` 及各类 `emit_*_expression`。 use crate::{is_side_effect_free, BinaryOperator, CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{ChainElement, Expression, LogicalOperator, SimpleAssignmentTarget, UnaryOperator, UpdateOperator}; impl Emitter { @@ -16,11 +16,18 @@ impl Emitter { let obj_reg = self.emit_expression(&pin.right, ctx)?; let key_reg = self.emit_private_id_reg(pin.left.name.as_str(), ctx)?; let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::PRIVATE_BRAND_IN, Operand::Reg(result_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::PRIVATE_BRAND_IN, + Operand::Reg(result_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); Ok(result_reg) } - fn emit_binary_expression(&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx) -> Result { + fn emit_binary_expression( + &self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx, + ) -> Result { let left = self.emit_expression(&bin.left, ctx)?; let right = self.emit_expression(&bin.right, ctx)?; let op = match bin.operator { @@ -67,7 +74,12 @@ impl Emitter { Expression::ComputedMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?; - ctx.inst(Inst::new(OpCode::DELETE_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::DELETE_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); Ok(obj_reg) } Expression::ChainExpression(chain) => { @@ -77,7 +89,12 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; if member.optional { let dup_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(dup_reg), Operand::Reg(obj_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(dup_reg), + Operand::Reg(obj_reg), + Operand::None, + )); ctx.inst(Inst::jmp_if_nullish(dup_reg, short_label)); } let prop_name = member.property.name.as_str(); @@ -89,11 +106,21 @@ impl Emitter { let obj_reg = self.emit_expression(&member.object, ctx)?; if member.optional { let dup_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(dup_reg), Operand::Reg(obj_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(dup_reg), + Operand::Reg(obj_reg), + Operand::None, + )); ctx.inst(Inst::jmp_if_nullish(dup_reg, short_label)); } let key_reg = self.emit_expression(&member.expression, ctx)?; - ctx.inst(Inst::new(OpCode::DELETE_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::DELETE_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); obj_reg } _ => return Err("invalid delete target".into()), @@ -150,13 +177,23 @@ impl Emitter { let cons_reg = self.emit_expression(&cond.consequent, ctx)?; let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(cons_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(cons_reg), + Operand::None, + )); ctx.inst(Inst::jmp(end_label)); ctx.labels.set_label_pos(else_label, ctx.insts.len()); let alt_reg = self.emit_expression(&cond.alternate, ctx)?; - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(alt_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(alt_reg), + Operand::None, + )); ctx.labels.set_label_pos(end_label, ctx.insts.len()); Ok(result_reg) @@ -220,13 +257,18 @@ impl Emitter { match &update.argument { SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => { let name = id.name.as_str(); - // Check if this is an upvalue or captured cell reference + // 判断目标是 upvalue 还是被捕获 cell let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); let captured_cell = ctx.captured_bindings.get(name).copied(); if let Some(uv) = uv_idx { - // Upvalue: LOAD_UPVALUE + CONST(1) + ADD/SUB + STORE_UPVALUE + // upvalue:LOAD_UPVALUE + 常量 1 + ADD/SUB + STORE_UPVALUE let val_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::LOAD_UPVALUE, Operand::Reg(val_reg), Operand::Imm(uv as u16), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_UPVALUE, + Operand::Reg(val_reg), + Operand::Imm(uv as u16), + Operand::None, + )); let one_idx = ctx.add_constant(Constant::Int(1)); let one_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(one_reg), one_idx)); @@ -236,15 +278,30 @@ impl Emitter { OpCode::SUB }; ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(one_reg))); - ctx.inst(Inst::new(OpCode::STORE_UPVALUE, Operand::None, Operand::Reg(val_reg), Operand::Imm(uv as u16))); + ctx.inst(Inst::new( + OpCode::STORE_UPVALUE, + Operand::None, + Operand::Reg(val_reg), + Operand::Imm(uv as u16), + )); Ok(val_reg) } else if let Some(cell_idx) = captured_cell { - // Captured cell: CELL_GET + CONST(1) + ADD/SUB + CELL_SET + // 被捕获 cell:CELL_GET + 常量 1 + ADD/SUB + CELL_SET let val_reg = ctx.alloc_reg(); if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) { - ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(val_reg), Operand::Reg(binding.reg), Operand::Imm(cell_idx as u16))); + ctx.inst(Inst::new( + OpCode::CELL_GET, + Operand::Reg(val_reg), + Operand::Reg(binding.reg), + Operand::Imm(cell_idx as u16), + )); } else { - ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(val_reg), Operand::None, Operand::Imm(cell_idx as u16))); + ctx.inst(Inst::new( + OpCode::CELL_GET, + Operand::Reg(val_reg), + Operand::None, + Operand::Imm(cell_idx as u16), + )); } let one_idx = ctx.add_constant(Constant::Int(1)); let one_reg = ctx.alloc_reg(); @@ -255,7 +312,12 @@ impl Emitter { OpCode::SUB }; ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(one_reg))); - ctx.inst(Inst::new(OpCode::CELL_SET, Operand::None, Operand::Reg(val_reg), Operand::Imm(cell_idx as u16))); + ctx.inst(Inst::new( + OpCode::CELL_SET, + Operand::None, + Operand::Reg(val_reg), + Operand::Imm(cell_idx as u16), + )); Ok(val_reg) } else { let var_reg = ctx.lookup_or_global(name); diff --git a/crates/oxide_emit/src/expr/template/tagged.rs b/crates/oxide_emit/src/expr/template/tagged.rs index e5158ce..a836c44 100644 --- a/crates/oxide_emit/src/expr/template/tagged.rs +++ b/crates/oxide_emit/src/expr/template/tagged.rs @@ -1,10 +1,10 @@ //! 标签模板表达式 emit:`emit_tagged_template_expression` 构造 template object 并调用标签函数。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; impl Emitter { pub(crate) fn emit_tagged_template_expression( &self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx, @@ -15,7 +15,12 @@ impl Emitter { 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)); + 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)); @@ -24,11 +29,21 @@ impl Emitter { 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))); + ctx.inst(Inst::new( + OpCode::SET_ELEM, + Operand::Reg(cooked_temp), + Operand::Reg(idx_reg), + Operand::Reg(str_reg), + )); } 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)); + 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)); @@ -37,7 +52,12 @@ impl Emitter { 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))); + ctx.inst(Inst::new( + OpCode::SET_ELEM, + Operand::Reg(raw_temp), + Operand::Reg(idx_reg), + Operand::Reg(str_reg), + )); } let mut expr_temps = Vec::new(); @@ -52,7 +72,12 @@ impl Emitter { expr_slots.push(ctx.alloc_reg()); } - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(cooked_slot), Operand::Reg(cooked_temp), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(cooked_slot), + Operand::Reg(cooked_temp), + Operand::None, + )); ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(raw_slot), Operand::Reg(raw_temp), 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)); diff --git a/crates/oxide_emit/src/expr/template/template_literal.rs b/crates/oxide_emit/src/expr/template/template_literal.rs index 19c706f..190c818 100644 --- a/crates/oxide_emit/src/expr/template/template_literal.rs +++ b/crates/oxide_emit/src/expr/template/template_literal.rs @@ -2,9 +2,9 @@ //! 拼接字面量段与内插表达式。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::module::Constant; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::module::Constant; impl Emitter { pub(crate) fn emit_template_literal_expression( @@ -42,12 +42,7 @@ impl Emitter { } } - ctx.inst(Inst::template_str( - Operand::Reg(r), - segment_count as u32, - total_len_hint as u16, - &parts, - )); + ctx.inst(Inst::template_str(Operand::Reg(r), segment_count as u32, total_len_hint as u16, &parts)); Ok(r) } diff --git a/crates/oxide_emit/src/expr/this.rs b/crates/oxide_emit/src/expr/this.rs index 0c19be1..82e5af8 100644 --- a/crates/oxide_emit/src/expr/this.rs +++ b/crates/oxide_emit/src/expr/this.rs @@ -1,9 +1,9 @@ //! `this` 表达式 emit:`emit_this_expression` 解析当前函数的 this 寄存器。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; impl Emitter { pub(crate) fn emit_this_expression(&self, ctx: &mut CompileCtx) -> Result { diff --git a/crates/oxide_emit/src/lib.rs b/crates/oxide_emit/src/lib.rs index 9a5145f..5006c24 100644 --- a/crates/oxide_emit/src/lib.rs +++ b/crates/oxide_emit/src/lib.rs @@ -15,7 +15,7 @@ pub mod shared; pub mod stmt; pub mod symbol_table; -pub use emit::{CompileCtx, Emitter, FunctionBodyContext, LabelScope, ParamSpec}; pub use emit::{is_int_literal, is_side_effect_free}; +pub use emit::{CompileCtx, Emitter, FunctionBodyContext, LabelScope, ParamSpec}; pub use oxide_bytecode::module::Constant; pub use oxide_parser::{AssignmentOperator, BinaryOperator, UnaryOperator}; diff --git a/crates/oxide_emit/src/prepass.rs b/crates/oxide_emit/src/prepass.rs index 0dd387b..44d2526 100644 --- a/crates/oxide_emit/src/prepass.rs +++ b/crates/oxide_emit/src/prepass.rs @@ -8,8 +8,7 @@ use crate::{CompileCtx, Emitter}; use oxide_parser::{Expression, Statement, VariableDeclarationKind}; impl Emitter { - - /// builtin slot allocation ahead of the temporary register pool. + /// 在临时寄存器池之前分配 builtin 槽位。 pub(crate) fn pre_register_builtin_references(&self, stmts: &[Statement], ctx: &mut CompileCtx) { for stmt in stmts { self.pre_scan_builtin_stmt(stmt, ctx); @@ -264,7 +263,6 @@ impl Emitter { } } - pub(crate) fn predeclare_function_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) { for statement in statements { let Statement::FunctionDeclaration(function) = statement else { @@ -278,10 +276,8 @@ impl Emitter { } } - /// Pre-declare all `var` bindings in this statement list so hoisted function - /// declarations emitted earlier can resolve them. Mirrors the removed count - /// pass behavior (see fe8bd86): top-level `var` names must be visible while - /// compiling function bodies that close over them. + /// 预声明语句列表中的全部 `var` 绑定,使先发的提升函数声明能解析它们。 + /// 顶层 `var` 名在编译闭包捕获它的函数体时必须可见。 pub(crate) fn predeclare_var_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) { for statement in statements { match statement { @@ -315,8 +311,12 @@ impl Emitter { for d in &decl.declarations { if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { let reg = ctx.alloc_reg(); - let _ = - ctx.declare_initialized(bi.name.as_str(), reg, VariableDeclarationKind::Var, false); + let _ = ctx.declare_initialized( + bi.name.as_str(), + reg, + VariableDeclarationKind::Var, + false, + ); } } } @@ -344,5 +344,4 @@ impl Emitter { } } } - } diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index f9566fa..b520daf 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -3,10 +3,10 @@ //! `emit_object_binding`、`emit_assign_target` 等。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{ ArrayAssignmentTarget, ArrayPattern, AssignmentTarget, AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern, Expression, ObjectAssignmentTarget, ObjectPattern, VariableDeclarationKind, @@ -18,12 +18,22 @@ impl Emitter { ) -> Result { let undef_reg = self.emit_undefined(ctx); let eq_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::STRICT_EQ, Operand::Reg(eq_reg), Operand::Reg(val_reg), Operand::Reg(undef_reg))); + ctx.inst(Inst::new( + OpCode::STRICT_EQ, + Operand::Reg(eq_reg), + Operand::Reg(val_reg), + Operand::Reg(undef_reg), + )); // val != undefined(eq 为 false)→ 跳过默认值 let end_label = ctx.next_label_id(); ctx.inst(Inst::jmp_if_false(eq_reg, end_label)); let default_reg = self.emit_expression(default_expr, ctx)?; - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(val_reg), Operand::Reg(default_reg), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(val_reg), + Operand::Reg(default_reg), + Operand::None, + )); ctx.labels.set_label_pos(end_label, ctx.insts.len()); Ok(val_reg) } @@ -32,8 +42,7 @@ impl Emitter { &self, name: &str, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx, ) -> Result<(), String> { let target_reg = if matches!(kind, VariableDeclarationKind::Var) { - // `var` names are pre-declared (hoisting); reuse the pre-registered slot - // instead of allocating a new one so n_registers matches let/const. + // `var` 名已预声明(hoisting),复用预登记槽位而非新分配,使 n_registers 与 let/const 一致。 if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { reg } else { @@ -47,13 +56,23 @@ impl Emitter { var_reg }; if let Some(&cell_idx) = ctx.captured_bindings.get(name) { - ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(src_reg), Operand::Imm(cell_idx as u16), Operand::None)); + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(src_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); } else { - // B011: const 声明路径 STORE_VAR 恒 b=0,不查运行时 guard。 + // const 声明路径 STORE_VAR 恒 b=0,不查运行时 guard。 // guard 读 regs[rd] 判断槽是否已初始化,依赖槽初始为 undefined; // 声明冗余检查由编译期 declare() 完成(重复声明编译报错), // 运行时 guard 只服务"对 const 再赋值"(赋值路径,见 emit_assign_target)。 - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg), Operand::Reg(src_reg), Operand::Imm(0))); + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(target_reg), + Operand::Reg(src_reg), + Operand::Imm(0), + )); } ctx.init_var(name); Ok(()) @@ -132,9 +151,19 @@ impl Emitter { ctx.inst(Inst::jmp_if_false(has_reg, loop_end)); let val_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::FOR_OF_NEXT, Operand::Reg(val_reg), Operand::None, Operand::None)); - ctx.inst(Inst::new(OpCode::SET_ELEM, Operand::Reg(rest_reg), Operand::Reg(idx_reg), Operand::Reg(val_reg))); + ctx.inst(Inst::new( + OpCode::SET_ELEM, + Operand::Reg(rest_reg), + Operand::Reg(idx_reg), + Operand::Reg(val_reg), + )); let tmp_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::INC_PRE, Operand::Reg(idx_reg), Operand::Reg(tmp_reg), Operand::Reg(tmp_reg))); + ctx.inst(Inst::new( + OpCode::INC_PRE, + Operand::Reg(idx_reg), + Operand::Reg(tmp_reg), + Operand::Reg(tmp_reg), + )); ctx.inst(Inst::jmp(loop_start)); ctx.labels.set_label_pos(loop_end, ctx.insts.len()); Ok(rest_reg) diff --git a/crates/oxide_emit/src/shared/chain.rs b/crates/oxide_emit/src/shared/chain.rs index 2378a95..69d1b14 100644 --- a/crates/oxide_emit/src/shared/chain.rs +++ b/crates/oxide_emit/src/shared/chain.rs @@ -2,10 +2,10 @@ //! 函数:`emit_chainable_expression`、`emit_optional_guard`、`emit_chain_call` 等。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::{LabelId, Operand}; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::{LabelId, Operand}; use oxide_parser::{ChainElement, Expression, LogicalOperator, PropertyKey}; impl Emitter { @@ -45,7 +45,12 @@ impl Emitter { } let key_reg = self.emit_expression(&member.expression, ctx)?; let value_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PROP_DYNAMIC, Operand::Reg(obj_reg), Operand::Reg(key_reg), Operand::Reg(value_reg))); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + Operand::Reg(value_reg), + )); Ok((value_reg, obj_reg)) } @@ -68,7 +73,12 @@ impl Emitter { } let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; let callee_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PRIVATE, Operand::Reg(callee_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::GET_PRIVATE, + Operand::Reg(callee_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); (callee_reg, obj_reg) } _ => { @@ -152,7 +162,12 @@ impl Emitter { } let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; let value_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PRIVATE, Operand::Reg(value_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::GET_PRIVATE, + Operand::Reg(value_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); Ok(value_reg) } Expression::CallExpression(call) => self.emit_chain_call(call, short_label, ctx), @@ -182,7 +197,12 @@ impl Emitter { } let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; let value_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PRIVATE, Operand::Reg(value_reg), Operand::Reg(obj_reg), Operand::Reg(key_reg))); + ctx.inst(Inst::new( + OpCode::GET_PRIVATE, + Operand::Reg(value_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + )); Ok(value_reg) } ChainElement::CallExpression(call) => self.emit_chain_call(call, short_label, ctx), @@ -204,7 +224,7 @@ impl Emitter { Ok(()) } - pub(crate) fn emit_object_property_read( &self, src_reg: u32, key: &str, ctx: &mut CompileCtx) -> u32 { + pub(crate) fn emit_object_property_read(&self, src_reg: u32, key: &str, ctx: &mut CompileCtx) -> u32 { let prop_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(prop_reg), Operand::Reg(src_reg), Operand::None)); let key_idx = ctx.add_constant(Constant::String(key.to_string())); @@ -257,7 +277,12 @@ impl Emitter { ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(prop_reg), Operand::Reg(src_reg), Operand::None)); let key_reg = self.emit_property_key_expression(key, ctx)?; let val_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::GET_PROP_DYNAMIC, Operand::Reg(prop_reg), Operand::Reg(key_reg), Operand::Reg(val_reg))); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(prop_reg), + Operand::Reg(key_reg), + Operand::Reg(val_reg), + )); Ok((val_reg, None)) } } diff --git a/crates/oxide_emit/src/shared/class_field.rs b/crates/oxide_emit/src/shared/class_field.rs index ed3381c..7de32ac 100644 --- a/crates/oxide_emit/src/shared/class_field.rs +++ b/crates/oxide_emit/src/shared/class_field.rs @@ -1,9 +1,9 @@ //! 类字段初始化 emit:公有/私有实例字段与私有方法的实例化初始化。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::{Expression, PropertyKey}; impl Emitter { @@ -17,7 +17,12 @@ impl Emitter { self.emit_undefined(ctx) }; if computed { - ctx.inst(Inst::new(OpCode::SET_PROP_DYNAMIC, target, Operand::Reg(key_reg), Operand::Reg(value_reg))); + ctx.inst(Inst::new( + OpCode::SET_PROP_DYNAMIC, + target, + Operand::Reg(key_reg), + Operand::Reg(value_reg), + )); } else { ctx.inst(Inst::new(OpCode::SET_PROP, target, Operand::Reg(value_reg), Operand::Reg(key_reg))); } diff --git a/crates/oxide_emit/src/shared/class_key.rs b/crates/oxide_emit/src/shared/class_key.rs index 978a36b..dfb1013 100644 --- a/crates/oxide_emit/src/shared/class_key.rs +++ b/crates/oxide_emit/src/shared/class_key.rs @@ -2,9 +2,9 @@ //! 函数:`private_name_id`、`emit_private_id_reg`、`emit_class_key_reg` 等。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::module::Constant; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::module::Constant; use oxide_parser::PropertyKey; impl Emitter { diff --git a/crates/oxide_emit/src/stmt/basic/return_stmt.rs b/crates/oxide_emit/src/stmt/basic/return_stmt.rs index 373d336..151d123 100644 --- a/crates/oxide_emit/src/stmt/basic/return_stmt.rs +++ b/crates/oxide_emit/src/stmt/basic/return_stmt.rs @@ -1,9 +1,9 @@ //! return 语句 emit:`emit_return_statement` 求值返回值并生成返回指令。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::Statement; impl Emitter { diff --git a/crates/oxide_emit/src/stmt/control.rs b/crates/oxide_emit/src/stmt/control.rs index 64ca753..7d14cd1 100644 --- a/crates/oxide_emit/src/stmt/control.rs +++ b/crates/oxide_emit/src/stmt/control.rs @@ -1,10 +1,10 @@ //! 控制流语句 emit:`if` 条件分支(含 else)与域分发 `emit_control_domain`。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::Statement; impl Emitter { diff --git a/crates/oxide_emit/src/stmt/declaration/class_decl.rs b/crates/oxide_emit/src/stmt/declaration/class_decl.rs index 20b0262..4e6c423 100644 --- a/crates/oxide_emit/src/stmt/declaration/class_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/class_decl.rs @@ -1,9 +1,9 @@ //! 类声明语句 emit:`emit_class_declaration_statement` 声明类名并初始化类对象。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::{Statement, VariableDeclarationKind}; impl Emitter { @@ -23,7 +23,12 @@ impl Emitter { ctx.init_var(&name); let ctor_reg = self.emit_class(class, ctx)?; if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { - ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(var_reg), Operand::Imm(cell_idx as u16), Operand::None)); + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(var_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(ctor_reg), Operand::None)); } diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 53522da..287a475 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -1,9 +1,9 @@ //! 函数声明语句 emit:`emit_function_declaration_statement` 创建闭包并绑定函数名。 use crate::{CompileCtx, Emitter, ParamSpec}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::Statement; impl Emitter { @@ -38,7 +38,12 @@ impl Emitter { ctx.reserve_reg(var_reg); ctx.inst(Inst::create_closure(Operand::Reg(var_reg), ctx.nested.len() as u16)); if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { - ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(var_reg), Operand::Imm(cell_idx as u16), Operand::None)); + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(var_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None)); } diff --git a/crates/oxide_emit/src/stmt/declaration/mod.rs b/crates/oxide_emit/src/stmt/declaration/mod.rs index f8b6237..ac6b3ed 100644 --- a/crates/oxide_emit/src/stmt/declaration/mod.rs +++ b/crates/oxide_emit/src/stmt/declaration/mod.rs @@ -8,7 +8,9 @@ mod function_decl; mod var; impl Emitter { - pub(crate) fn emit_declaration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result, String> { + pub(crate) fn emit_declaration_domain( + &self, stmt: &Statement, ctx: &mut CompileCtx, + ) -> Result, String> { match stmt { Statement::VariableDeclaration(_) => self.emit_variable_declaration_statement(stmt, ctx), Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx), diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index c49a5c2..74136f2 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -1,10 +1,10 @@ //! var/let/const 声明语句 emit:声明 + 初始化表达式,处理提升与解构 pattern。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{BindingPattern, Expression, Statement, VariableDeclarationKind}; impl Emitter { @@ -49,7 +49,12 @@ impl Emitter { var_reg }; if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) { - ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(tmp), Operand::Imm(cell_idx as u16), Operand::None)); + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(tmp), + Operand::Imm(cell_idx as u16), + Operand::None, + )); } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg), Operand::Reg(tmp), Operand::None)); } diff --git a/crates/oxide_emit/src/stmt/exception/throw.rs b/crates/oxide_emit/src/stmt/exception/throw.rs index da19234..1be5c71 100644 --- a/crates/oxide_emit/src/stmt/exception/throw.rs +++ b/crates/oxide_emit/src/stmt/exception/throw.rs @@ -1,9 +1,9 @@ //! throw 语句 emit:`emit_throw_statement` 求值异常值并生成 THROW。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::Statement; impl Emitter { diff --git a/crates/oxide_emit/src/stmt/exception/try_catch.rs b/crates/oxide_emit/src/stmt/exception/try_catch.rs index 765c29e..654cacf 100644 --- a/crates/oxide_emit/src/stmt/exception/try_catch.rs +++ b/crates/oxide_emit/src/stmt/exception/try_catch.rs @@ -1,9 +1,9 @@ //! try/catch/finally 语句 emit:`emit_try_statement` 生成 TRY_BEGIN 与处理入口。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::{Statement, VariableDeclarationKind}; impl Emitter { @@ -29,7 +29,12 @@ impl Emitter { last_try_result = Some(r); } } - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(last_try_result.unwrap_or(result_reg)), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(last_try_result.unwrap_or(result_reg)), + Operand::None, + )); if has_catch { ctx.inst(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); } @@ -54,7 +59,12 @@ impl Emitter { last_catch_result = Some(r); } } - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(last_catch_result.unwrap_or(result_reg)), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(last_catch_result.unwrap_or(result_reg)), + Operand::None, + )); ctx.pop_scope(); } if has_finally { @@ -65,7 +75,12 @@ impl Emitter { last_finally_result = Some(r); } } - ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(last_finally_result.unwrap_or(result_reg)), Operand::None)); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(last_finally_result.unwrap_or(result_reg)), + Operand::None, + )); ctx.inst(Inst::new(OpCode::TRY_FINALLY_END, Operand::None, Operand::None, Operand::None)); } ctx.labels.set_label_pos(try_end_label, ctx.insts.len()); diff --git a/crates/oxide_emit/src/stmt/iteration/do_while.rs b/crates/oxide_emit/src/stmt/iteration/do_while.rs index 8be473f..69a8c91 100644 --- a/crates/oxide_emit/src/stmt/iteration/do_while.rs +++ b/crates/oxide_emit/src/stmt/iteration/do_while.rs @@ -5,7 +5,9 @@ use oxide_ir::inst::Inst; use oxide_parser::Statement; impl Emitter { - pub(crate) fn emit_do_while_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result, String> { + pub(crate) fn emit_do_while_statement( + &self, stmt: &Statement, ctx: &mut CompileCtx, + ) -> Result, String> { let Statement::DoWhileStatement(dw) = stmt else { return Ok(None); }; diff --git a/crates/oxide_emit/src/stmt/iteration/for_.rs b/crates/oxide_emit/src/stmt/iteration/for_.rs index f5029ca..5e1144a 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_.rs @@ -1,10 +1,10 @@ //! for 语句 emit:`emit_for_statement` 含 init/test/update 三段与循环跳转。 use crate::{CompileCtx, Emitter}; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{BindingPattern, ForStatementInit, Statement, VariableDeclarationKind}; impl Emitter { @@ -40,7 +40,12 @@ impl Emitter { ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?; var_reg }; - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg), Operand::Reg(tmp), Operand::None)); + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(target_reg), + Operand::Reg(tmp), + Operand::None, + )); ctx.init_var(bi.name.as_str()); } } diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index 2f1f14c..364e146 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -1,9 +1,9 @@ //! for-in 语句 emit:`emit_for_in_statement` 遍历可枚举属性名。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::{ForStatementLeft, Statement, VariableDeclarationKind}; impl Emitter { diff --git a/crates/oxide_emit/src/stmt/iteration/for_of.rs b/crates/oxide_emit/src/stmt/iteration/for_of.rs index 515ef5d..a12e6fb 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_of.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_of.rs @@ -1,9 +1,9 @@ //! for-of 语句 emit:`emit_for_of_statement` 迭代器协议遍历。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::{ForStatementLeft, Statement}; impl Emitter { diff --git a/crates/oxide_emit/src/stmt/switch.rs b/crates/oxide_emit/src/stmt/switch.rs index b6dea5f..ca950a5 100644 --- a/crates/oxide_emit/src/stmt/switch.rs +++ b/crates/oxide_emit/src/stmt/switch.rs @@ -1,9 +1,9 @@ //! switch 语句 emit:case 匹配链 + 穿落(fallthrough)与 default,见 `emit_switch_statement`。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_bytecode::opcode::OpCode; use oxide_parser::Statement; impl Emitter { @@ -22,7 +22,12 @@ impl Emitter { if let Some(test) = &case.test { let test_reg = self.emit_expression(test, ctx)?; let eq_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::EQ, Operand::Reg(eq_reg), Operand::Reg(disc_reg), Operand::Reg(test_reg))); + ctx.inst(Inst::new( + OpCode::EQ, + Operand::Reg(eq_reg), + Operand::Reg(disc_reg), + Operand::Reg(test_reg), + )); ctx.inst(Inst::jmp_if_true(eq_reg, case_label)); } } diff --git a/crates/oxide_emit/src/symbol_table.rs b/crates/oxide_emit/src/symbol_table.rs index 48177ea..f6a3d76 100644 --- a/crates/oxide_emit/src/symbol_table.rs +++ b/crates/oxide_emit/src/symbol_table.rs @@ -284,16 +284,16 @@ mod tests { #[test] fn var_hoists_to_function_scope() { let mut st = SymbolTable::new(); - // Global scope is FunctionScope + // 全局作用域是函数作用域 st.declare("x", 0, l(), false).unwrap(); st.init_var("x"); - // Push a block scope + // 压入块作用域 st.push_scope(); - // var declaration inside block should go to function scope + // 块内的 var 声明应落到函数作用域 st.declare("y", 1, v(), false).unwrap(); st.init_var("y"); st.pop_scope(); - // y should be accessible (it was declared in function scope, not block scope) + // y 可见(声明在函数作用域而非块作用域) assert_eq!(st.lookup("y").unwrap(), 1); } @@ -326,7 +326,7 @@ mod tests { st.declare("y", 1, v(), false).unwrap(); st.pop_scope(); st.push_scope(); - // Same function scope, so this should be a duplicate + // 同一函数作用域,故应为重复声明 assert!(st.declare("y", 2, v(), false).is_err()); st.pop_scope(); } diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 80df9a0..7ba7887 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -1,8 +1,8 @@ -//! Inst 寄存器契约:def/use 扫描 + 副作用判定(DCE 与 Phase 5 liveness 共用,D-06)。 +//! Inst 寄存器契约:def/use 扫描 + 副作用判定(DCE 与 liveness 共用单源)。 //! //! `def_reg` / `use_regs` / `is_pure` 是 `Inst` 的类型契约方法(`impl Inst` 与 `inst.rs` //! 中的构造 API 同属一类型),落在 oxide_ir 契约层供 DCE 消费、liveness 复用。 -//! 寄存器规则逐条对照 VM dispatch handler(RESEARCH Pattern 1/2 全表): +//! 寄存器规则与 VM dispatch handler 行为一致: //! CALL 隐式写 reg 0、GET_PROP 结果写 a/b 槽、HALT 隐式读 reg 0、None 槽映射 Reg(0)。 use oxide_bytecode::opcode::OpCode; @@ -17,9 +17,9 @@ impl Inst { /// 无写入返回 None。 pub fn def_reg(&self) -> Option { match self.op { - // CALL 系:结果隐式写 reg 0,rd 是 callee use(Pitfall 1) + // CALL 系:结果隐式写 reg 0,rd 是 callee 的 use OpCode::CALL | OpCode::CALL_NATIVE => Some(0), - // GET_PROP 系:结果写 a/b 槽而非 rd(Pitfall 3) + // GET_PROP 系:结果写 a/b 槽而非 rd OpCode::GET_PROP | OpCode::IC_GET_PROP => reg_of(&self.a), OpCode::GET_PROP_DYNAMIC => reg_of(&self.b), // 成员读写:val 槽原地更新(a 槽 / b 槽) @@ -73,7 +73,7 @@ impl Inst { pub fn use_regs(&self) -> SmallVec<[u32; 4]> { let mut uses = SmallVec::new(); match self.op { - // CALL 系:rd=callee, a=this, b..b+nargs 参数区间(Pitfall 1) + // CALL 系:rd=callee, a=this, b..b+nargs 参数区间 OpCode::CALL | OpCode::CALL_NATIVE => { push_operand(&mut uses, &self.rd); push_operand(&mut uses, &self.a); @@ -91,7 +91,7 @@ impl Inst { let nargs = self.ext.first().copied().unwrap_or(0); push_range(&mut uses, reg_of(&self.a), nargs); } - // GET_PROP 系:结果写 a/b 槽,rd=obj 是 use(Pitfall 3) + // GET_PROP 系:结果写 a/b 槽,rd=obj 是 use OpCode::GET_PROP => { push_operand(&mut uses, &self.rd); push_operand(&mut uses, &self.b); @@ -144,11 +144,31 @@ impl Inst { push_operand(&mut uses, &self.rd); } // 二元运算:a, b - OpCode::ADD | OpCode::SUB | OpCode::MUL | OpCode::DIV | OpCode::MOD | OpCode::EQ - | OpCode::NEQ | OpCode::LT | OpCode::GT | OpCode::LTE | OpCode::GTE | OpCode::AND - | OpCode::OR | OpCode::NULLISH | OpCode::STRICT_EQ | OpCode::STRICT_NEQ - | OpCode::BIT_AND | OpCode::BIT_OR | OpCode::BIT_XOR | OpCode::SHL | OpCode::SHR - | OpCode::USHR | OpCode::INSTANCEOF | OpCode::IN | OpCode::CREATE_REGEXP => { + OpCode::ADD + | OpCode::SUB + | OpCode::MUL + | OpCode::DIV + | OpCode::MOD + | OpCode::EQ + | OpCode::NEQ + | OpCode::LT + | OpCode::GT + | OpCode::LTE + | OpCode::GTE + | OpCode::AND + | OpCode::OR + | OpCode::NULLISH + | OpCode::STRICT_EQ + | OpCode::STRICT_NEQ + | OpCode::BIT_AND + | OpCode::BIT_OR + | OpCode::BIT_XOR + | OpCode::SHL + | OpCode::SHR + | OpCode::USHR + | OpCode::INSTANCEOF + | OpCode::IN + | OpCode::CREATE_REGEXP => { push_operand(&mut uses, &self.a); push_operand(&mut uses, &self.b); } @@ -157,19 +177,29 @@ impl Inst { push_operand(&mut uses, &self.a); } // 无条件跳转 / try 标记:b 是 Label,无寄存器 - OpCode::JMP | OpCode::TRY_BEGIN | OpCode::TRY_FINALLY_BEGIN | OpCode::TRY_END - | OpCode::TRY_FINALLY_END | OpCode::FOR_IN_CLEANUP | OpCode::FOR_OF_CLOSE => {} + OpCode::JMP + | OpCode::TRY_BEGIN + | OpCode::TRY_FINALLY_BEGIN + | OpCode::TRY_END + | OpCode::TRY_FINALLY_END + | OpCode::FOR_IN_CLEANUP + | OpCode::FOR_OF_CLOSE => {} // 条件跳转:rd=cond OpCode::JMP_IF_FALSE | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_NULLISH => { push_operand(&mut uses, &self.rd); } - // HALT:隐式读 reg 0(顶层返回值,Pitfall 2) + // HALT:隐式读 reg 0(顶层返回值) OpCode::HALT => uses.push(0), // RETURN/THROW:读 rd(None→0) OpCode::RETURN | OpCode::THROW => push_operand(&mut uses, &self.rd), // 加载族:a 槽是 Const/Imm 立即数,无寄存器 use - OpCode::LOAD_CONST | OpCode::CREATE_CLOSURE | OpCode::LOAD_UPVALUE | OpCode::VOID - | OpCode::NEW_OBJECT | OpCode::NEW_ARRAY | OpCode::NOP => {} + OpCode::LOAD_CONST + | OpCode::CREATE_CLOSURE + | OpCode::LOAD_UPVALUE + | OpCode::VOID + | OpCode::NEW_OBJECT + | OpCode::NEW_ARRAY + | OpCode::NOP => {} // 变量读写:LOAD_VAR/STORE_VAR/CELL_GET 读 a(None→0) OpCode::LOAD_VAR | OpCode::STORE_VAR | OpCode::CELL_GET => { push_operand(&mut uses, &self.a); @@ -182,9 +212,15 @@ impl Inst { OpCode::MAKE_CELL => push_operand(&mut uses, &self.rd), OpCode::CELL_SET | OpCode::STORE_UPVALUE => push_operand(&mut uses, &self.a), // 写对象属性:rd/a/b 全 use - OpCode::SET_PROP | OpCode::SET_PROP_DYNAMIC | OpCode::IC_SET_PROP | OpCode::SET_ELEM - | OpCode::GET_PRIVATE | OpCode::SET_PRIVATE | OpCode::INIT_PRIVATE - | OpCode::PRIVATE_BRAND_IN | OpCode::DEFINE_ACCESSOR => { + OpCode::SET_PROP + | OpCode::SET_PROP_DYNAMIC + | OpCode::IC_SET_PROP + | OpCode::SET_ELEM + | OpCode::GET_PRIVATE + | OpCode::SET_PRIVATE + | OpCode::INIT_PRIVATE + | OpCode::PRIVATE_BRAND_IN + | OpCode::DEFINE_ACCESSOR => { push_operand(&mut uses, &self.rd); push_operand(&mut uses, &self.a); push_operand(&mut uses, &self.b); @@ -210,7 +246,7 @@ impl Inst { OpCode::FOR_IN_NEXT | OpCode::FOR_IN_DONE | OpCode::FOR_OF_NEXT | OpCode::FOR_OF_DONE => {} // REST_OBJECT:读 a(ext 是 excluded_idx 常量) OpCode::REST_OBJECT => push_operand(&mut uses, &self.a), - // TEMPLATE_STR:解析 ext,跳过 ext[0],后续 seg>>31==1 则低 8 位是 expr_reg(A1) + // TEMPLATE_STR:解析 ext,跳过 ext[0],后续 seg>>31==1 则低 8 位是 expr_reg OpCode::TEMPLATE_STR => { for seg in self.ext.iter().skip(1) { if seg >> 31 == 1 { @@ -219,42 +255,68 @@ impl Inst { } } // 占位 opcode(emit 不产):按无 use 保守处理,不影响合法产物 - OpCode::SWITCH_TABLE | OpCode::PROFILE_TYPE | OpCode::PROFILE_SHAPE - | OpCode::PROFILE_BRANCH | OpCode::PROFILE_CALL | OpCode::FORK | OpCode::JOIN => {} + OpCode::SWITCH_TABLE + | OpCode::PROFILE_TYPE + | OpCode::PROFILE_SHAPE + | OpCode::PROFILE_BRANCH + | OpCode::PROFILE_CALL + | OpCode::FORK + | OpCode::JOIN => {} } uses } - /// 本指令是否无观察副作用(Pattern 1 纯表;LOAD_VAR 的 This+derived 特判需 &IRFunction)。 + /// 本指令是否无观察副作用(LOAD_VAR 的 This+derived 特判需要函数元信息)。 /// - /// 纯 = 结果未用时可删。表外 opcode 一律有副作用(D-05 精确表,宁少删不错删)。 + /// 纯 = 结果未用时可删。表外 opcode 一律视为有副作用,宁少删不错删。 pub fn is_pure(&self, f: &IRFunction) -> bool { match self.op { - // 算术 / 比较 / 位 / 逻辑:coerce 对象路径有抛错风险,D-05 决策算纯(运行时等价兜底) - OpCode::ADD | OpCode::SUB | OpCode::MUL | OpCode::DIV | OpCode::MOD | OpCode::NEG - | OpCode::UNARY_PLUS | OpCode::EQ | OpCode::NEQ | OpCode::LT | OpCode::GT - | OpCode::LTE | OpCode::GTE | OpCode::STRICT_EQ | OpCode::STRICT_NEQ - | OpCode::BIT_AND | OpCode::BIT_OR | OpCode::BIT_XOR | OpCode::SHL | OpCode::SHR - | OpCode::USHR | OpCode::BIT_NOT | OpCode::AND | OpCode::OR | OpCode::NOT + // 算术 / 比较 / 位 / 逻辑:coerce 对象路径有抛错风险,仍按纯处理(运行时等价兜底) + OpCode::ADD + | OpCode::SUB + | OpCode::MUL + | OpCode::DIV + | OpCode::MOD + | OpCode::NEG + | OpCode::UNARY_PLUS + | OpCode::EQ + | OpCode::NEQ + | OpCode::LT + | OpCode::GT + | OpCode::LTE + | OpCode::GTE + | OpCode::STRICT_EQ + | OpCode::STRICT_NEQ + | OpCode::BIT_AND + | OpCode::BIT_OR + | OpCode::BIT_XOR + | OpCode::SHL + | OpCode::SHR + | OpCode::USHR + | OpCode::BIT_NOT + | OpCode::AND + | OpCode::OR + | OpCode::NOT | OpCode::NULLISH => true, // 分配 / 常量 / 空操作(a 槽非寄存器) - OpCode::NOP | OpCode::LOAD_CONST | OpCode::VOID | OpCode::TYPEOF | OpCode::NEW_OBJECT + OpCode::NOP + | OpCode::LOAD_CONST + | OpCode::VOID + | OpCode::TYPEOF + | OpCode::NEW_OBJECT | OpCode::NEW_ARRAY => true, // MOV:寄存器复制无副作用,可删无害 OpCode::MOV => true, - // SPILL/UNSPILL:RegAlloc 插入的指令,删 SPILL 后 UNSPILL 读陈旧值(Pitfall 7),永不删 + // SPILL/UNSPILL:RegAlloc 插入的指令,删 SPILL 后 UNSPILL 读陈旧值,永不删 OpCode::SPILL | OpCode::UNSPILL => false, - // 闭包 / cell 读取:无抛错路径(D-07) + // 闭包 / cell 读取:无抛错路径 OpCode::CREATE_CLOSURE | OpCode::LOAD_UPVALUE | OpCode::CELL_GET => true, // 模板字符串:纯拼接写 rd,无抛错(use 统计覆盖其 ext) OpCode::TEMPLATE_STR => true, - // LOAD_VAR:仅 a==This 且 derived 构造函数读 this 可能抛 ReferenceError(misc.rs:52-63) - OpCode::LOAD_VAR => { - !(self.a == Operand::This && f.is_derived_constructor) - } - // STORE_VAR:写变量目标寄存器。脚本顶层/模块作用域的变量是**全局可观察状态** - // (Pitfall 6:`let x=0; class C{[x=1](){}}` 无后续本地读取时删除 `x=1` 会改变 - // 外部 `assert(x===1)` 的观察结果);函数内局部未用赋值的优化留给 Phase 5 精确活度。 + // LOAD_VAR:仅 a==This 且 derived 构造函数读 this 时可能抛 ReferenceError + OpCode::LOAD_VAR => !(self.a == Operand::This && f.is_derived_constructor), + // STORE_VAR:写变量目标寄存器。脚本顶层/模块作用域的变量是全局可观察状态, + // 删除会改变外部观察结果;函数内局部未用赋值的优化交给精确 DCE。 OpCode::STORE_VAR => false, // 其余全部有副作用(getter/调用/控制流/迭代器/复合更新/写共享状态/占位防御) _ => false, @@ -262,7 +324,7 @@ impl Inst { } } -/// 操作数 → 物理寄存器号。None 槽映射 reg 0(A3,与 lower.rs operand_to_u8 一致); +/// 操作数 → 物理寄存器号。None 槽映射 reg 0(与 lower.rs operand_to_u8 一致); /// Const/Imm/Label 不是寄存器槽,返回 None。 fn reg_of(o: &Operand) -> Option { match o { @@ -295,7 +357,7 @@ mod tests { use crate::operand::Operand; use crate::IRFunction; - // ── def_reg / use_regs 契约测试(Pattern 2 全表,反直觉槽位锁定)── + // ── def_reg / use_regs 契约测试(反直觉槽位断言)── #[test] fn binary_op_def_rd_use_ab() { @@ -313,7 +375,7 @@ mod tests { #[test] fn get_prop_def_is_a_slot_not_rd() { - // Pitfall 3:GET_PROP rd=obj 是 use,结果写 a 槽 + // GET_PROP rd=obj 是 use,结果写 a 槽 let inst = Inst::new(OpCode::GET_PROP, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)); assert_eq!(inst.def_reg(), Some(1)); assert_eq!(inst.use_regs().as_slice(), &[0, 2]); @@ -321,7 +383,7 @@ mod tests { #[test] fn get_prop_dynamic_def_is_b_slot() { - // Pitfall 3:GET_PROP_DYNAMIC rd=obj 是 use,结果写 b 槽 + // GET_PROP_DYNAMIC rd=obj 是 use,结果写 b 槽 let inst = Inst::new(OpCode::GET_PROP_DYNAMIC, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)); assert_eq!(inst.def_reg(), Some(2)); assert_eq!(inst.use_regs().as_slice(), &[0, 1]); @@ -329,7 +391,7 @@ mod tests { #[test] fn ic_get_prop_a_slot_is_use_and_def() { - // Pitfall 3:IC_GET_PROP a 槽既是对象 use 又是结果 def + // IC_GET_PROP a 槽既是对象 use 又是结果 def let inst = Inst::ic_get(Operand::Reg(1), Operand::Reg(2)); assert_eq!(inst.def_reg(), Some(1)); let uses = inst.use_regs(); @@ -338,7 +400,7 @@ mod tests { #[test] fn call_def_is_implicit_reg0_with_arg_range() { - // Pitfall 1:CALL rd=callee 是 use,结果隐式写 reg 0;参数 b..b+nargs 连续 + // CALL rd=callee 是 use,结果隐式写 reg 0;参数 b..b+nargs 连续 let inst = Inst::call(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 3); assert_eq!(inst.def_reg(), Some(0)); assert_eq!(inst.use_regs().as_slice(), &[0, 1, 2, 3, 4]); @@ -367,7 +429,7 @@ mod tests { #[test] fn halt_implicitly_uses_reg0() { - // Pitfall 2:HALT 返回 regs[0],顶层 LOAD_VAR(None, r) 链靠它保活 + // HALT 返回 regs[0],顶层 LOAD_VAR(None, r) 链靠它保活 let inst = Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None); assert_eq!(inst.def_reg(), None); assert_eq!(inst.use_regs().as_slice(), &[0]); @@ -399,7 +461,7 @@ mod tests { #[test] fn none_slot_maps_to_reg0() { - // A3:None 槽与 lower.rs operand_to_u8 一致,统一映射物理 reg 0 + // None 槽与 lower.rs operand_to_u8 一致,统一映射物理 reg 0 let lv = Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::None, Operand::None); assert_eq!(lv.use_regs().as_slice(), &[0]); @@ -409,7 +471,7 @@ mod tests { #[test] fn template_str_parses_expr_regs_from_ext() { - // ext[0] 跳过;后续 seg>>31==1 则低 8 位是 expr_reg(A1,emit 8 位编码) + // ext[0] 跳过;后续 seg>>31==1 则低 8 位是 expr_reg(emit 8 位编码) let with_expr = Inst::template_str(Operand::Reg(1), 2, 10, &[0x1234, 0x8000_0000 | 5]); assert_eq!(with_expr.use_regs().as_slice(), &[5]); @@ -497,7 +559,7 @@ mod tests { assert!(unspill.use_regs().is_empty()); } - // ── is_pure 契约测试(Pattern 1 表,纯/非纯族代表 opcode)── + // ── is_pure 契约测试(纯/非纯族代表 opcode)── #[test] fn pure_ops_are_deletable() { @@ -533,7 +595,7 @@ mod tests { assert!(Inst::inst_mov(Operand::Reg(1), Operand::Reg(2)).is_pure(&f)); // LOAD_VAR 普通(a=Reg)纯 assert!(Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::Reg(2), Operand::None).is_pure(&f)); - // STORE_VAR 一律有副作用(Pitfall 6:顶层变量赋值全局可观察,不删) + // STORE_VAR 一律有副作用(顶层变量赋值全局可观察,不删) assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None).is_pure(&f)); assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(1)).is_pure(&f)); @@ -580,7 +642,7 @@ mod tests { assert!(!Inst::new(OpCode::STORE_UPVALUE, Operand::None, Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); assert!(!Inst::new(OpCode::CELL_SET, Operand::None, Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); assert!(!Inst::new(OpCode::MAKE_CELL, Operand::Reg(5), Operand::None, Operand::None).is_pure(&f)); - // RegAlloc 辅助:SPILL/UNSPILL 有副作用(删 SPILL 后 UNSPILL 读陈旧值,Pitfall 7) + // RegAlloc 辅助:SPILL/UNSPILL 有副作用(删 SPILL 后 UNSPILL 读陈旧值) assert!(!Inst::inst_spill(Operand::Reg(5), 42).is_pure(&f)); assert!(!Inst::inst_unspill(Operand::Reg(9), 42).is_pure(&f)); // 占位 opcode 防御性有副作用 @@ -589,7 +651,7 @@ mod tests { #[test] fn store_var_all_branches_are_impure() { - // Pitfall 6:STORE_VAR 写目标变量寄存器,顶层/模块作用域变量全局可观察,一律不删 + // STORE_VAR 写目标变量寄存器,顶层/模块作用域变量全局可观察,一律不删 // (const guard 抛错、普通 var/let 赋值都可能改变外部观察状态) let f = IRFunction::new(); assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None).is_pure(&f)); diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 550a7ba..738e6c7 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -42,7 +42,7 @@ impl Inst { } } - // ── IC 系:ext = [0, 0, 0](shape/slot/proto 占位字,VM 运行时 patch)── + // ── IC 系:ext = [0, 0, 0](shape/slot/proto 占位字,VM 运行时回填)── /// 内联缓存读属性:结果写入 `dst`,属性键为 `key`。 pub fn ic_get(dst: Operand, key: Operand) -> Self { @@ -148,20 +148,20 @@ impl Inst { } } - // ── RegAlloc 辅助指令(D-06/D-07)── + // ── RegAlloc 辅助指令 ── - /// 寄存器复制 rd = a(D-07,RegAlloc 区间拆分搬值)。 + /// 寄存器复制 rd = a(RegAlloc 区间拆分时搬值)。 /// 不可用 LOAD_VAR/STORE_VAR 组合模拟——引入变量绑定语义会误触 const guard。 pub fn inst_mov(dst: Operand, src: Operand) -> Self { Self::with_ext(OpCode::MOV, dst, src, Operand::None, &[]) } - /// 溢出:regs[rd] → spill_stack[frame_base + slot],ext=[slot u16](D-06)。 + /// 溢出:regs[rd] → spill_stack[frame_base + slot],ext=[slot u16]。 pub fn inst_spill(src: Operand, slot: u16) -> Self { Self::with_ext(OpCode::SPILL, src, Operand::None, Operand::None, &[slot as u32]) } - /// 恢复:spill_stack[frame_base + slot] → regs[rd],ext=[slot u16](D-06)。 + /// 恢复:spill_stack[frame_base + slot] → regs[rd],ext=[slot u16]。 pub fn inst_unspill(dst: Operand, slot: u16) -> Self { Self::with_ext(OpCode::UNSPILL, dst, Operand::None, Operand::None, &[slot as u32]) } @@ -187,32 +187,17 @@ impl Inst { /// 条件寄存器为 false 时跳转。 pub fn jmp_if_false(cond_reg: u32, label: LabelId) -> Self { - Self::new( - OpCode::JMP_IF_FALSE, - Operand::Reg(cond_reg), - Operand::None, - Operand::Label(label), - ) + Self::new(OpCode::JMP_IF_FALSE, Operand::Reg(cond_reg), Operand::None, Operand::Label(label)) } /// 条件寄存器为 true 时跳转。 pub fn jmp_if_true(cond_reg: u32, label: LabelId) -> Self { - Self::new( - OpCode::JMP_IF_TRUE, - Operand::Reg(cond_reg), - Operand::None, - Operand::Label(label), - ) + Self::new(OpCode::JMP_IF_TRUE, Operand::Reg(cond_reg), Operand::None, Operand::Label(label)) } /// 条件寄存器为 null/undefined 时跳转(`??` / 可选链短路)。 pub fn jmp_if_nullish(cond_reg: u32, label: LabelId) -> Self { - Self::new( - OpCode::JMP_IF_NULLISH, - Operand::Reg(cond_reg), - Operand::None, - Operand::Label(label), - ) + Self::new(OpCode::JMP_IF_NULLISH, Operand::Reg(cond_reg), Operand::None, Operand::Label(label)) } /// try 块起始,label 指向对应的 catch/finally 处理入口。 diff --git a/crates/oxide_ir/src/lib.rs b/crates/oxide_ir/src/lib.rs index 5c98553..598cba9 100644 --- a/crates/oxide_ir/src/lib.rs +++ b/crates/oxide_ir/src/lib.rs @@ -1,6 +1,6 @@ //! IR 中间表示:AST→IR→字节码 流水线的程序表示。 //! -//! 单一程序表示(D-01),分域组合(D-02): +//! 单一程序表示,分域组合: //! - code: 指令流 + label 位置 //! - data: 常量池 //! - bindings: 参数布局 @@ -52,8 +52,8 @@ pub struct IRFunction { pub needs_home_object: bool, pub captured_this_const_idx: u16, pub function_name: Option, - /// 本函数是否为顶层脚本函数(D-19):顶层 STORE_VAR 全局可观察(B008)不可删, - /// 函数内局部 STORE_VAR 才可被精确 DCE 删除。emit 在 assemble_ir 处由 + /// 本函数是否为顶层脚本函数:顶层 STORE_VAR 写全局可观察状态,不可被精确 DCE + /// 删除;函数内局部 STORE_VAR 才可删。emit 在 assemble_ir 处由 /// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。 pub is_top_level: bool, pub const_overflow: bool, diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index da2618d..3ad984f 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -1,8 +1,8 @@ //! IRFunction → CompiledModule 等价转换。 //! -//! 字节码格式(4 字节 u32 + 扩展字)与现状逐指令一致(D-13 锚)。 +//! 字节码格式(4 字节 u32 + 扩展字)逐指令一致。 //! label 用前缀和:`label_pos` 记 Inst 下标,平铺时维护 inst→instr 映射, -//! 跳转 offset 以 Instr(含 ext 字)为单位(D-17)。 +//! 跳转 offset 以 Instr(含 ext 字)为单位。 use oxide_bytecode::module::CompiledModule; use oxide_bytecode::opcode::{self, OpCode}; @@ -11,7 +11,7 @@ use crate::inst::Inst; use crate::operand::Operand; use crate::IRFunction; -/// IRFunction → CompiledModule。错误消息与现状(退役前 compiler.rs)逐字一致。 +/// IRFunction → CompiledModule 等价转换。溢出/标签错误消息为既有格式,逐字保持一致。 pub fn lower(f: &IRFunction) -> Result { // 溢出检查 1:常量池(先于寄存器——超大常量池伴生的海量 vreg 会使后续检查的 // 稠密 bitset 表示爆内存;常量池超限是更基础的失效,先报它) @@ -47,7 +47,7 @@ pub fn lower(f: &IRFunction) -> Result { encode_inst(inst, &mut instrs, &mut jumps)?; } - // 跳转回填:offset = 目标 Instr - 当前 Instr(前缀和,D-17) + // 跳转回填:offset = 目标 Instr - 当前 Instr(前缀和) for (instr_idx, label) in jumps { let target_inst = f .label_pos diff --git a/crates/oxide_ir/src/operand.rs b/crates/oxide_ir/src/operand.rs index 3ca9c98..1d71682 100644 --- a/crates/oxide_ir/src/operand.rs +++ b/crates/oxide_ir/src/operand.rs @@ -1,13 +1,13 @@ //! 类型安全操作数。IR 中不出现裸寄存器下标。 //! -//! - `Reg(u32)`:寄存器。本阶段存物理号(≤253);254/255 为 VM 保留,语义由 `This`/`NewTarget` 表达 +//! - `Reg(u32)`:寄存器,vreg 表示(RegAlloc 后收敛到物理号 ≤253);254/255 为 VM 保留,语义由 `This`/`NewTarget` 表达 //! - `Const(u16)`:常量池下标 //! - `Label(u32)`:跳转目标(`LabelId`) //! - `Imm(u16)`:立即数 //! - `This` / `NewTarget`:语义化特殊寄存器,由 lowering 映射物理 254/255(IR 中禁止裸下标) //! - `None`:操作数槽未使用 -/// 统一跳转目标标识(取代语义化 `Label` 枚举)。 +/// 统一跳转目标标识。id 连续递增,`label_pos` 的 Vec 索引即 id。 pub type LabelId = u32; /// 类型安全操作数。IR 中不出现裸寄存器下标,语义见文件头 `//!`。 diff --git a/crates/oxide_ir/tests/lowering_tests.rs b/crates/oxide_ir/tests/lowering_tests.rs index a63cff5..90338ad 100644 --- a/crates/oxide_ir/tests/lowering_tests.rs +++ b/crates/oxide_ir/tests/lowering_tests.rs @@ -25,7 +25,8 @@ fn lower_err(f: &IRFunction) -> String { #[test] fn three_operand_opcode_encodes_rd_a_b() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2))); let m = lower_ok(&f); assert_eq!(m.bytecode.len(), 1); assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::ADD); @@ -61,7 +62,8 @@ fn create_closure_splits_sub_idx_into_a_b() { fn forward_jump_encodes_positive_offset() { let mut f = base_module(); f.insts.push(Inst::jmp(0)); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.label_pos = vec![Some(1)]; f.label_count = 1; let m = lower_ok(&f); @@ -73,7 +75,8 @@ fn forward_jump_encodes_positive_offset() { #[test] fn backward_jump_encodes_negative_offset() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.insts.push(Inst::jmp(0)); f.label_pos = vec![Some(0)]; f.label_count = 1; @@ -84,11 +87,12 @@ fn backward_jump_encodes_negative_offset() { #[test] fn jump_offset_counts_ext_words_in_target() { - // jmp(0) -> ic_get (1 + 3 ext = 4 instr) -> ADD (instr 5) + // jmp(0) → ic_get(1 + 3 ext = 4 instr)→ ADD(instr 5) let mut f = base_module(); f.insts.push(Inst::jmp(0)); f.insts.push(Inst::ic_get(Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2))); f.label_pos = vec![Some(2)]; f.label_count = 1; let m = lower_ok(&f); @@ -125,7 +129,8 @@ fn call_carries_nargs_ext_word() { #[test] fn define_accessor_and_rest_object_carry_single_ext_word() { let mut f = base_module(); - f.insts.push(Inst::define_accessor(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 42)); + f.insts + .push(Inst::define_accessor(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 42)); f.insts.push(Inst::rest_object(Operand::Reg(3), Operand::Reg(4), 7)); let m = lower_ok(&f); assert_eq!(m.bytecode.len(), 4); @@ -140,8 +145,10 @@ fn define_accessor_and_rest_object_carry_single_ext_word() { #[test] fn this_and_new_target_map_to_254_255() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::This, Operand::None)); - f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(2), Operand::NewTarget, Operand::None)); + f.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::This, Operand::None)); + f.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(2), Operand::NewTarget, Operand::None)); let m = lower_ok(&f); assert_eq!(opcode::a(m.bytecode[0]), 254); assert_eq!(opcode::a(m.bytecode[1]), 255); @@ -151,7 +158,9 @@ fn this_and_new_target_map_to_254_255() { fn nested_ir_functions_pack_into_sub_modules() { let mut outer = base_module(); let mut inner = base_module(); - inner.insts.push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2))); + inner + .insts + .push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2))); inner.n_registers = 3; outer.nested.push(inner); let m = lower_ok(&outer); @@ -164,7 +173,8 @@ fn nested_ir_functions_pack_into_sub_modules() { #[test] fn module_fields_are_copied_through() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.constants.push(Constant::Int(42)); f.param_layout = oxide_ir::ParamLayout { base: 2, count: 3 }; f.n_registers = 8; @@ -190,7 +200,8 @@ fn module_fields_are_copied_through() { #[test] fn n_registers_overflow_errors() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.n_registers = 300; let err = lower_err(&f); assert!(err.contains("too many registers"), "unexpected error: {err}"); @@ -199,7 +210,8 @@ fn n_registers_overflow_errors() { #[test] fn explicit_reg_254_errors() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(1), Operand::Reg(2))); let err = lower_err(&f); assert!(err.contains("too many registers"), "unexpected error: {err}"); } @@ -209,7 +221,8 @@ fn jump_offset_overflow_errors() { let mut f = base_module(); f.insts.push(Inst::jmp(0)); for _ in 0..40_000 { - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); } f.label_pos = vec![Some(40_000)]; f.label_count = 1; @@ -230,7 +243,8 @@ fn missing_label_errors() { #[test] fn const_overflow_errors() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.constants = vec![Constant::Int(0); 70_000]; let err = lower_err(&f); assert!(err.contains("too many constants"), "unexpected error: {err}"); @@ -239,7 +253,8 @@ fn const_overflow_errors() { #[test] fn const_overflow_flag_errors() { let mut f = base_module(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.const_overflow = true; let err = lower_err(&f); assert!(err.contains("too many constants"), "unexpected error: {err}"); @@ -248,7 +263,8 @@ fn const_overflow_flag_errors() { #[test] fn ir_function_domain_assemble_default_clone() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.constants.push(Constant::Int(1)); f.param_layout = oxide_ir::ParamLayout { base: 0, count: 1 }; f.n_registers = 2; @@ -262,7 +278,9 @@ fn ir_function_domain_assemble_default_clone() { f.cells_needed = 1; f.function_name = Some("f".to_string()); let mut inner = IRFunction::new(); - inner.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + inner + .insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); f.nested.push(inner); let d = IRFunction::new(); diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index cba6cc8..e1b5871 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -11,7 +11,7 @@ use crate::string_forge::PermInterner; macro_rules! bind_method { ($world:expr, $target:expr, $sf:expr, $sh:expr, $name:literal, $func:expr, $nargs:expr) => {{ let _raw: *const () = $func as *const (); - // SAFETY: $func is a NativeFn fn-item; a fn-item coerced to *const () is always valid. + // SAFETY: $func 是 NativeFn 函数项;函数项强转为 *const () 始终有效。 let _func_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(_raw) }; let _ = $world.bind_method($target, $sh, $sf, $name, _func_ptr, $nargs); }}; @@ -31,7 +31,7 @@ macro_rules! bind_methods_static { $(($name:literal, $func:expr, $nargs:expr)),* $(,)?) => { $({ let _raw: *const () = $func as *const (); - // SAFETY: $func is a NativeFn fn-item; valid to coerce and wrap. + // SAFETY: $func 是 NativeFn 函数项;强转并包装合法。 let _func_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(_raw) }; let _ = $crate::builtin::BuiltinWorld::bind_method_static( $target, $sh, $sf, $name, _func_ptr, $nargs, $wrapper_proto, @@ -245,7 +245,7 @@ fn make_pair( si_name: u32, ) -> (P, P) { intern_label(string_forge, name); - let name_si = string_forge.intern(name).0; // intern the constructor's name value too + let name_si = string_forge.intern(name).0; // 同时 intern 构造器名作为属性值 let mut proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); @@ -258,18 +258,18 @@ fn make_pair( ctor.set_shape_id(ctor_shape2); ctor.set_function(true); - // Pre-allocate slots: proto[0]="constructor", ctor[0]="prototype", ctor[1]="name" - proto.ensure_hash_props().push(JsValue::undefined()); // placeholder - ctor.ensure_hash_props().push(JsValue::undefined()); // placeholder for "prototype" - // Set the actual name value immediately (ctor vec[1]) + // 预分配槽位:proto[0]="constructor"、ctor[0]="prototype"、ctor[1]="name"。 + proto.ensure_hash_props().push(JsValue::undefined()); // 占位 + ctor.ensure_hash_props().push(JsValue::undefined()); // "prototype" 占位 + // 立即写入实际 name 值(ctor vec[1])。 ctor.ensure_hash_props() .push(JsValue::perm_string(string_forge.string_ptr(name_si))); - // Set attribute metadata: .prototype (ctor[0]) = writable:false, enumerable:false, configurable:false + // 属性元数据:.prototype(ctor[0])= writable:false, enumerable:false, configurable:false。 ctor.set_data_meta(0u32, oxide_types::object::PropAttributes::new(false, false, false)); - // .name (ctor[1]) = writable:false, enumerable:false, configurable:true + // .name(ctor[1])= writable:false, enumerable:false, configurable:true。 ctor.set_data_meta(1u32, oxide_types::object::PropAttributes::new(false, false, true)); - // .constructor (proto[0]) = writable:true, enumerable:false, configurable:true - // (JS spec: prototype.constructor is non-enumerable so it doesn't leak into for-in) + // .constructor(proto[0])= writable:true, enumerable:false, configurable:true。 + // (JS 规范:prototype.constructor 非枚举,避免泄漏进 for-in。) proto.set_data_meta(0u32, oxide_types::object::PropAttributes::new(true, false, true)); (P::new(proto), P::new(ctor)) @@ -398,9 +398,9 @@ fn make_typed_array_family( } } -/// Overwrite placeholder slots (set up by make_pair) with real values. -/// ctor.vec[0] = constructor.prototype -> proto -/// proto.vec[0] = proto.constructor -> ctor +/// 用真实值覆盖(`make_pair` 设置的)占位槽。 +/// ctor.vec[0] = constructor.prototype → proto。 +/// proto.vec[0] = proto.constructor → ctor。 fn wire_ctor_proto(ctor: &P, proto: &P) { let ctor_ptr = ctor.as_ptr() as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; @@ -1009,7 +1009,7 @@ impl BuiltinWorld { let si = string_forge.intern("@@iterator").0; let raw = methods.values; - // SAFETY: methods.values is a NativeFn fn-item passed by the VM binding layer. + // SAFETY: methods.values 是 VM 绑定层传入的 NativeFn 函数项。 let func_ptr = unsafe { NativeFnPtr::from_raw(raw) }; let _ = Self::bind_method_static(proto, shape_forge, string_forge, "@@iterator", func_ptr, 0, self.fn_proto_val()); @@ -1177,8 +1177,8 @@ impl BuiltinWorld { wrapper.set_proto(wrapper_proto).ok(); } wrapper.set_function(true); - // The NativeFnPtr invariant is upheld by callers (see bind_method / bind_method_static - // callers, all of which use fn-item expressions). + // NativeFnPtr 不变量由调用方维护(见 bind_method / bind_method_static + // 的调用方,均使用函数项表达式)。 wrapper.set_native_fn(Some(native_fn_ptr)); wrapper.set_native_arg_count(arg_count); // 设置 .length (ES spec: Function.length = formal parameter count, @@ -1202,8 +1202,8 @@ impl BuiltinWorld { let new_shape = shape_forge.make_shape(proto.shape_id(), si); proto.set_shape_id(new_shape); proto.ensure_hash_props().push(wrapper_val); - // Built-in prototype methods are non-enumerable per the ES spec; otherwise - // they leak into for-in enumeration (e.g. array push/pop showing up in `for k in []`). + // 内置原型方法按 ES 规范非枚举;否则会泄漏进 for-in 枚举 + // (例如 `for k in []` 中会出现 array push/pop)。 let method_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); proto.set_data_meta(method_pos, oxide_types::object::PropAttributes::new(true, false, true)); proto.bump_generation(); @@ -1270,9 +1270,9 @@ mod tests { #[test] fn test_protos_have_null_proto() { let w = make_world(); - // Object.prototype is the root - its __proto__ is null. + // Object.prototype 是根——其 __proto__ 为 null。 assert!(w.object_proto.proto().is_null()); - // All other constructor prototypes inherit from Object.prototype. + // 其余构造器原型均继承自 Object.prototype。 assert!(w.array_proto.proto().is_object()); assert!(w.function_proto.proto().is_object()); assert!(w.string_proto.proto().is_object()); diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 2c6c6fa..80907f4 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -113,8 +113,8 @@ impl Default for KernelConfig { } } -/// Immutable, permanently shared state across all VM instances. -/// Never rebuilt after construction — forge tables are append-only. +/// 不可变、跨所有 VM 实例永久共享的状态。 +/// 构造后从不重建——forge 表为 append-only。 pub struct KernelCore { pub config: KernelConfig, pub perm_interner: Arc, @@ -199,10 +199,10 @@ impl KernelCore { /// 字符串 intern 表是 append-only,无需清理;仅当 shape 或 prop 表超过阈值时才执行 /// [`ShapeForge::clear_transient`] 与 [`PropForge::clear`]。 pub fn sweep_runner_forges(&self) { - // The key interner is append-only (no per-run sweep); only the transient - // shape/prop tables need bounding at the test262 per-test boundary. - // test262 creates a fresh VM/session per test. At this boundary no JS object - // from prior tests may retain transient shapes/templates. + // 键 interner 是 append-only(无逐次清理);只有瞬时 shape/prop 表 + // 需要在 test262 每测试边界做上限约束。 + // test262 每测试新建 VM/session。在该边界,此前测试产生的 JS 对象 + // 不应保留任何瞬时 shape/模板。 if self.shape_forge.len() > 50_000 { self.shape_forge.clear_transient(); self.prop_forge.clear(); @@ -212,19 +212,19 @@ impl KernelCore { } } -/// Per-session mutable state: builtin prototype objects and the global object. -/// Rebuilt on full_reset() to achieve complete isolation between JS executions. +/// 每会话可变状态:内置原型对象与全局对象。 +/// 在 full_reset() 时重建,实现 JS 执行之间的完全隔离。 pub struct KernelSession { pub builtin_world: Arc, pub global_object: P, pub builtin_snapshot: BuiltinSnapshot, } -/// Generation snapshot for reset dirty checks. +/// 供 reset 脏检查用的世代快照。 /// -/// Maintenance: every new `BuiltinWorld` object field must be added here and to -/// `KernelSession::dirty_since_snapshot()` so selective reset can rebuild the -/// correct builtin family. +/// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及 +/// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的 +/// builtin 家族。 pub const NUM_BUILTINS: usize = 66; /// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。 @@ -484,8 +484,8 @@ impl KernelSession { P::new(global_obj) } - /// Build a fresh session from a KernelCore. All string/shape intern calls hit - /// cache on second and subsequent calls — net cost is < 0.5 ms. + /// 从 KernelCore 构建一个全新 session。所有 string/shape intern 调用在 + /// 第二次及后续调用时命中缓存——净开销 < 0.5 ms。 pub fn new(core: &KernelCore) -> Self { let builtin_world = Arc::new(BuiltinWorld::new(&core.perm_interner, &core.shape_forge)); let global_object = Self::new_global_object(core); diff --git a/crates/oxide_kernel/src/shape_forge.rs b/crates/oxide_kernel/src/shape_forge.rs index fed3a1e..8c91ebd 100644 --- a/crates/oxide_kernel/src/shape_forge.rs +++ b/crates/oxide_kernel/src/shape_forge.rs @@ -24,18 +24,17 @@ pub struct Shape { pub id: ShapeId, pub property_name: StringIndex, pub parent: Option, - /// Number of non-sentinel properties from the root to this shape. + /// 从根到本 shape 的非哨兵属性数。 pub depth: u32, } -/// Shared hidden-class store. +/// 共享隐藏类存储。 /// -/// Concurrency model: -/// - `shapes` is append-mostly behind an `RwLock`. -/// - `transitions` is a sharded `DashMap` keyed by `(parent_shape, property_name)`. -/// - `positions` caches `(shape_id, prop_name) → slot` for O(1) repeat lookups. -/// - Shard count is fixed for reproducible contention behavior across machines instead -/// of relying on DashMap's CPU-dependent default. +/// 并发模型: +/// - `shapes` 为 append-mostly,受 `RwLock` 保护。 +/// - `transitions` 是以 `(parent_shape, property_name)` 为键的分片 `DashMap`。 +/// - `positions` 缓存 `(shape_id, prop_name) → slot`,供 O(1) 重复查询。 +/// - 分片数固定,保证跨机器的争用行为可复现,不依赖 DashMap 的 CPU 相关默认值。 pub struct ShapeForge { shapes: RwLock>>>, transitions: DashMap, @@ -109,7 +108,7 @@ impl ShapeForge { return new_id; } - // Compute depth from parent (O(1) — just read the cached value) + // 从父节点计算深度(O(1)——只读缓存值)。 let parent_depth = { let shapes = self.shapes.read().unwrap(); shapes @@ -307,9 +306,9 @@ mod tests { let s2 = forge.make_shape(s1, 1_000_020); let s3 = forge.make_shape(s2, 1_000_030); - // First call populates cache + // 首次调用填充缓存。 assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); - // Second call hits cache (verify no regression) + // 二次调用命中缓存(验证无回归)。 assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); @@ -321,7 +320,7 @@ mod tests { let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); assert_eq!(forge.lookup_position(s1, 1_000_010), Some(0)); forge.clear_transient(); - // After clear, EMPTY_SHAPE remains but others are gone + // 清理后只剩 EMPTY_SHAPE,其余全部移除。 assert_eq!(forge.shapes.read().unwrap().len(), 1); } diff --git a/crates/oxide_kernel/src/string_forge.rs b/crates/oxide_kernel/src/string_forge.rs index 6ee2411..90c30f2 100644 --- a/crates/oxide_kernel/src/string_forge.rs +++ b/crates/oxide_kernel/src/string_forge.rs @@ -7,41 +7,40 @@ use rustc_hash::FxHasher; use crate::{kernel_debug, kernel_trace}; -/// Full 64-bit content hash. Replaces the old 16-bit `hash16` so the interner's -/// hash→candidate map has negligible collision risk. +/// 完整 64 位内容哈希。替代旧的 16 位 `hash16`,使 interner 的 +/// hash→候选表碰撞风险可忽略。 fn hash64(s: &str) -> u64 { let mut h = FxHasher::default(); s.hash(&mut h); h.finish() } -/// One interned key. `data` is a leaked `&'static str` — permanent keys are never -/// freed (append-only by design), so the leak is the storage model, not a bug. +/// 一条 intern 过的键。`data` 是泄漏的 `&'static str`——永久键从不释放 +/// (按设计 append-only),所以泄漏即存储模型,而非 bug。 #[derive(Clone, Copy)] struct PermEntry { data: &'static str, hash: u64, } -/// Append-only, never-move, lock-free-read key interner shared by all VMs. +/// 所有 VM 共享的 append-only、永不移动、读无锁的键 interner。 /// -/// Replaces the old ref-counted `StringForge` (and its buggy `maybe_sweep` -/// renumber path). Keys (property names, method names) are interned once and -/// addressed by a stable `u32` id that the shape/IC system keys on. Runtime -/// string *values* are no longer interned here — they are heap `JsString` -/// pointers (see `oxide_vm::Vm::new_string`). +/// 取代旧的引用计数 `StringForge`(及其有缺陷的 `maybe_sweep` 重编号路径)。 +/// 键(属性名、方法名)只 intern 一次,以 shape/IC 系统所依赖的稳定 `u32` id +/// 寻址。运行时字符串*值*不再在此 intern——它们是堆上的 `JsString` +/// 指针(见 `oxide_vm::Vm::new_string`)。 /// -/// Concurrency: -/// - `hash_map` (`DashMap`) gives sharded, lock-free reads of the hash→candidates -/// mapping on the hot intern path. -/// - `entries` sits behind a short `RwLock`; reads copy out a `&'static str` -/// (Copy) so the borrow outlives the guard. +/// 并发性: +/// - `hash_map`(`DashMap`)在热 intern 路径上提供分片、无锁的 +/// hash→候选映射读。 +/// - `entries` 位于短 `RwLock` 之后;读时拷贝出 `&'static str`(Copy), +/// 使借用存活时间超过锁守卫。 pub struct PermInterner { hash_map: DashMap>, entries: RwLock>, - /// Lazily materialized permanent `JsString` values, indexed by entry id. - /// Used when a permanent key must also exist as a JS string *value* - /// (e.g. method names exposed as property values at builtin init). + /// 惰性物化的永久 `JsString` 值,按下标索引。 + /// 用于永久键还必须以 JS 字符串*值*存在的情形 + /// (如 builtin 初始化时作为属性值暴露的方法名)。 permanent_strings: RwLock>>>, } @@ -55,12 +54,12 @@ impl PermInterner { } } - /// Intern a key. Returns its stable id and full 64-bit hash. Each unique - /// string is stored exactly once (single allocation, no double `to_string`). + /// Intern 一个键。返回其稳定 id 与完整 64 位哈希。每个唯一字符串 + /// 恰好存储一次(单次分配,无双重 `to_string`)。 pub fn intern(&self, s: &str) -> (u32, u64) { let hash = hash64(s); - // Fast path: lock-free candidate read, short entries read-lock. + // 快路径:无锁候选读,短 entries 读锁。 if let Some(candidates) = self.hash_map.get(&hash) { let entries = self.entries.read().unwrap(); for &id in candidates.iter() { @@ -71,7 +70,7 @@ impl PermInterner { } } - // Slow path: append under write lock, re-checking for a racing insert. + // 慢路径:写锁下追加,重查是否存在并发插入。 let mut entries = self.entries.write().unwrap(); if let Some(candidates) = self.hash_map.get(&hash) { for &id in candidates.iter() { @@ -93,20 +92,20 @@ impl PermInterner { (id, hash) } - /// Resolve a key id to its text with zero clone. The returned `&'static str` - /// is valid for the program lifetime (keys are never freed). + /// 以零克隆解析键 id 的文本。返回的 `&'static str` + /// 在整个程序生命周期内有效(键从不释放)。 pub fn lookup(&self, id: u32) -> Option<&'static str> { let entries = self.entries.read().unwrap(); entries.get(id as usize).map(|e| e.data) } - /// Full 64-bit hash for a key id. + /// 键 id 的完整 64 位哈希。 pub fn get_hash(&self, id: u32) -> Option { let entries = self.entries.read().unwrap(); entries.get(id as usize).map(|e| e.hash) } - /// Total number of unique keys interned. + /// 全部唯一 intern 键的数量。 pub fn entry_count(&self) -> u32 { self.entries.read().unwrap().len() as u32 } @@ -116,8 +115,8 @@ impl PermInterner { self.entry_count() == 0 } - /// Materialize (once) and return a stable pointer to a permanent `JsString` - /// for the given key id. The `JsString` lives for the program lifetime. + /// 物化(仅一次)并返回指定键 id 的永久 `JsString` 稳定指针。 + /// 该 `JsString` 在整个程序生命周期内存活。 pub fn string_ptr(&self, id: u32) -> *const JsString { { let perm = self.permanent_strings.read().unwrap(); @@ -204,7 +203,7 @@ mod tests { let (id, _) = interner.intern("perm"); let ptr = interner.string_ptr(id); assert_eq!(unsafe { (*ptr).as_str() }, "perm"); - // Second call returns the same stable pointer (materialized once). + // 二次调用返回同一稳定指针(仅物化一次)。 assert_eq!(interner.string_ptr(id), ptr); } } diff --git a/crates/oxide_liveness/src/dataflow.rs b/crates/oxide_liveness/src/dataflow.rs index 91a8e64..a5f31f1 100644 --- a/crates/oxide_liveness/src/dataflow.rs +++ b/crates/oxide_liveness/src/dataflow.rs @@ -1,8 +1,8 @@ //! 块级数据流迭代:gen/kill + reverse_postorder 不动点。 //! -//! gen/kill 直接消费 `oxide_ir::contract` 的 `def_reg`/`use_regs`(D-22), +//! gen/kill 直接消费 `oxide_ir::contract` 的 `def_reg`/`use_regs`, //! None→0 / This→254 / NewTarget→255 / CALL 隐式 reg0 全内置,不重复建。 -//! Exception 边当普通边参与迭代(D-13);Exception 目标块入口 reg 0 隐式 def +//! Exception 边当普通边参与迭代;Exception 目标块入口 reg 0 隐式 def //! (异常展开写 regs[0],vm_runtime.rs:204-205)在此建模:gen.remove(0)+kill.insert(0)。 use oxide_cfg::{Cfg, EdgeKind}; @@ -63,7 +63,7 @@ pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec< } } - // 3. reverse_postorder(确定性,禁 HashMap 迭代序——B010) + // 3. reverse_postorder(确定性,禁 HashMap 迭代序) let rpo = reverse_postorder(cfg); // 4. 不动点迭代 @@ -73,7 +73,7 @@ pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec< loop { let mut changed = false; for &b in &rpo { - // live_out[b] = ∪ live_in[s](全部 succs,含 Exception 边——D-13 当普通边) + // live_out[b] = ∪ live_in[s](全部 succs,含 Exception 边当普通边) let mut out = vec![false; bits]; for &(succ, _kind) in &cfg.blocks[b].succs { for (r, &v) in live_in[succ].iter().enumerate() { @@ -104,7 +104,7 @@ pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec< } /// 从 entry 出发 DFS 沿 succs 存储序(确定序)收集 postorder 后反转; -/// 不可达块按升序追加在末尾。纯 Vec + visited 位图(B010)。 +/// 不可达块按升序追加在末尾。纯 Vec + visited 位图,保证确定性。 fn reverse_postorder(cfg: &Cfg) -> Vec { let n = cfg.blocks.len(); let mut visited = vec![false; n]; @@ -131,9 +131,9 @@ fn reverse_postorder(cfg: &Cfg) -> Vec { #[cfg(test)] mod tests { use super::*; + use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; - use oxide_bytecode::opcode::OpCode; fn set(v: &[bool], regs: &[u32]) -> bool { regs.iter().all(|&r| v[r as usize]) @@ -146,8 +146,10 @@ mod tests { #[test] fn linear_function_liveness() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); let cfg = oxide_cfg::build_cfg(&f); let (live_in, live_out, reg_count) = block_liveness(&f, &cfg); assert!(reg_count >= 2); @@ -170,11 +172,15 @@ mod tests { // 5: RETURN Reg(6) let mut f = empty_function(); f.insts.push(Inst::jmp_if_false(1, 1)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); f.insts.push(Inst::jmp(2)); - f.insts.push(Inst::new(OpCode::SUB, Operand::Reg(4), Operand::Reg(4), Operand::Reg(5))); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::SUB, Operand::Reg(4), Operand::Reg(4), Operand::Reg(5))); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); f.label_pos = vec![None, Some(3), Some(4)]; f.label_count = 3; let cfg = oxide_cfg::build_cfg(&f); @@ -201,10 +207,13 @@ mod tests { // 4: RETURN Reg(6) let mut f = empty_function(); f.insts.push(Inst::jmp_if_false(1, 1)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); f.insts.push(Inst::jmp(0)); - f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); f.label_pos = vec![Some(0), Some(3)]; f.label_count = 2; let cfg = oxide_cfg::build_cfg(&f); @@ -227,10 +236,14 @@ mod tests { // 4: RETURN Reg(5) let mut f = empty_function(); f.insts.push(Inst::try_begin(3)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); f.label_pos = vec![None, None, None, Some(3)]; f.label_count = 1; let cfg = oxide_cfg::build_cfg(&f); @@ -249,8 +262,10 @@ mod tests { // 0: LOAD_VAR(Reg(5), None, None) def 5 use {0} // 1: HALT(None, None, None) contract.rs HALT 读 regs[0] let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None)); let cfg = oxide_cfg::build_cfg(&f); let (live_in, _, _) = block_liveness(&f, &cfg); assert!(live_in[0][0], "None→0 映射由 contract.rs 消费生效"); diff --git a/crates/oxide_liveness/src/lib.rs b/crates/oxide_liveness/src/lib.rs index f33a3a3..28d192e 100644 --- a/crates/oxide_liveness/src/lib.rs +++ b/crates/oxide_liveness/src/lib.rs @@ -1,9 +1,9 @@ -//! liveness 分析 pass:IRFunction + CFG → LiveInfo(只读,D-15)。 +//! liveness 分析 pass:IRFunction + CFG → LiveInfo(只读)。 //! -//! 消费 `oxide_ir::IRFunction`(只读)与 `oxide_cfg::Cfg`(preds/succs + exit 哨兵,D-12), -//! 产出 LiveInfo 分析视图(D-01:pass 输出不住进 IR)。两阶段:`dataflow`(块级 +//! 消费 `oxide_ir::IRFunction`(只读)与 `oxide_cfg::Cfg`(preds/succs + exit 哨兵), +//! 产出 LiveInfo 分析视图(pass 输出不住进 IR)。两阶段:`dataflow`(块级 //! liveIn/liveOut 不动点)→ `ranges`(块内反向扫描逐指令 live 集)。gen/kill 直接消费 -//! contract.rs def_reg/use_regs(D-22),None→0 / This→254 / NewTarget→255 零重复映射。 +//! contract.rs def_reg/use_regs,None→0 / This→254 / NewTarget→255 零重复映射。 mod dataflow; mod live_info; @@ -14,8 +14,8 @@ pub use live_info::LiveInfo; use oxide_cfg::Cfg; use oxide_ir::IRFunction; -/// 计算 liveness:`&IRFunction + &Cfg → LiveInfo`。纯函数,只读 IR(D-15)。 -/// 空 IRFunction 退化:返回 LiveInfo::new()(照 oxide_dce 空函数先例)。 +/// 计算 liveness:`&IRFunction + &Cfg → LiveInfo`。纯函数,只读 IR。 +/// 空 IRFunction 退化:返回 LiveInfo::new()。 pub fn liveness(f: &IRFunction, cfg: &Cfg) -> LiveInfo { if f.insts.is_empty() { return LiveInfo::new(); diff --git a/crates/oxide_liveness/src/live_info.rs b/crates/oxide_liveness/src/live_info.rs index 595a1a5..a09170e 100644 --- a/crates/oxide_liveness/src/live_info.rs +++ b/crates/oxide_liveness/src/live_info.rs @@ -1,9 +1,9 @@ -//! LiveInfo:liveness 分析输出视图(D-15:pass 输出,不住进 IR)。 +//! LiveInfo:liveness 分析输出视图(pass 输出,不住进 IR)。 //! //! 全部 live 集为按 reg 号索引的 bitset(`Vec`,长度 = reg_count+1,由 //! dataflow 求上界)。四字段语义: -//! - `block_live_in` / `block_live_out`:块级数据流迭代结果(05-06 精确 DCE 消费) -//! - `inst_live_before` / `inst_live_after`:逐指令活集(05-07 干涉图消费 live_before) +//! - `block_live_in` / `block_live_out`:块级数据流迭代结果(精确 DCE 消费) +//! - `inst_live_before` / `inst_live_after`:逐指令活集(RegAlloc 干涉图消费 live_before) /// liveness 输出:块级 liveIn/liveOut + 逐指令 live_before/live_after。 #[derive(Debug, Clone)] diff --git a/crates/oxide_liveness/src/ranges.rs b/crates/oxide_liveness/src/ranges.rs index b3a68c9..22fbdae 100644 --- a/crates/oxide_liveness/src/ranges.rs +++ b/crates/oxide_liveness/src/ranges.rs @@ -1,11 +1,10 @@ //! 块内反向扫描:块级 liveOut → 逐指令 live_before/live_after。 //! -//! 起点是 **block_live_out**(非 liveIn,RESEARCH 伪代码笔误已修正——从 liveIn -//! 出发会得到错误放大的逐指令集)。反向扫描 **kill 先于 gen**(`live = (live − def) -//! ∪ use`):COMPOUND_ADD 等读-写同寄存器指令 use 含 rd(contract.rs:137), -//! gen 先于 kill 会把 rd 旧值从 live_before 错误剔除。Exception 目标块入口 -//! reg 0 隐式 def 在此截断,并以 `debug_assert_eq!(live, block_live_in[b])` -//! 校验与 dataflow 一致性。 +//! 起点是 **block_live_out**(非 liveIn——从 liveIn 出发会得到错误放大的逐指令集)。 +//! 反向扫描 **kill 先于 gen**(`live = (live − def) ∪ use`):COMPOUND_ADD 等读-写 +//! 同寄存器指令 use 含 rd(contract.rs:137),gen 先于 kill 会把 rd 旧值从 +//! live_before 错误剔除。Exception 目标块入口 reg 0 隐式 def 在此截断,并以 +//! `debug_assert_eq!(live, block_live_in[b])` 校验与 dataflow 一致性。 use oxide_cfg::{Cfg, EdgeKind}; use oxide_ir::IRFunction; @@ -48,9 +47,9 @@ pub(super) fn inst_liveness( mod tests { use super::*; use crate::dataflow::block_liveness; + use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; - use oxide_bytecode::opcode::OpCode; fn empty_function() -> IRFunction { IRFunction::new() @@ -60,8 +59,10 @@ mod tests { fn linear_block_instruction_sets() { // 0: ADD r2=r0+r1 1: RETURN r2 let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); let cfg = oxide_cfg::build_cfg(&f); let (live_in, live_out, rc) = block_liveness(&f, &cfg); let (before, after) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); @@ -77,8 +78,10 @@ mod tests { // 0: COMPOUND_ADD rd=1, a=2 def {1} use {1,2} // 1: RETURN r1 let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::COMPOUND_ADD, Operand::Reg(1), Operand::Reg(2), Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::COMPOUND_ADD, Operand::Reg(1), Operand::Reg(2), Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); let cfg = oxide_cfg::build_cfg(&f); let (live_in, live_out, rc) = block_liveness(&f, &cfg); let (before, _) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); @@ -94,8 +97,10 @@ mod tests { // 2: RETURN r4 let mut f = empty_function(); f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1)); - f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(4), Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(4), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(4), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(4), Operand::None, Operand::None)); let cfg = oxide_cfg::build_cfg(&f); let (live_in, live_out, rc) = block_liveness(&f, &cfg); let (before, after) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); @@ -111,10 +116,14 @@ mod tests { // 3: STORE_VAR(r5, None, None) 4: RETURN r5 let mut f = empty_function(); f.insts.push(Inst::try_begin(3)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); f.label_pos = vec![None, None, None, Some(3)]; f.label_count = 1; let cfg = oxide_cfg::build_cfg(&f); diff --git a/crates/oxide_liveness/tests/smoke.rs b/crates/oxide_liveness/tests/smoke.rs index b501171..35ba845 100644 --- a/crates/oxide_liveness/tests/smoke.rs +++ b/crates/oxide_liveness/tests/smoke.rs @@ -37,9 +37,8 @@ fn smoke_loop_with_variables() { #[test] fn smoke_try_catch() { - let (info, cfg, _) = run_liveness( - "function x() {} function y(e) {} function f() { try { x(); } catch (e) { y(e); } } f()", - ); + let (info, cfg, _) = + run_liveness("function x() {} function y(e) {} function f() { try { x(); } catch (e) { y(e); } } f()"); // entry 块 liveIn 不含 reg 0(异常边 reg0 截断在真实产物上成立) assert!(!info.block_live_in[cfg.entry][0], "entry 块 liveIn 不得含 reg0"); } diff --git a/crates/oxide_regalloc/src/alloc_map.rs b/crates/oxide_regalloc/src/alloc_map.rs index 2534278..5ab0a71 100644 --- a/crates/oxide_regalloc/src/alloc_map.rs +++ b/crates/oxide_regalloc/src/alloc_map.rs @@ -1,8 +1,8 @@ -//! AllocMap:寄存器染色决策输出视图(D-01:pass 输出,不住进 IR)。 +//! AllocMap:寄存器染色决策输出视图(pass 输出,不住进 IR)。 //! //! `AllocMap.map` 含全部真实 + fresh vreg 的 Phys/Spill 去向;`spills` 为 -//! SPILL/UNSPILL 插入决策表(05-08 rewrite 消费);`phys_peak` 回写 n_registers; -//! `arg_window_base` 供调用点参数连续性 MOV 补位。BTreeMap 保确定性(B010,禁 HashMap)。 +//! SPILL/UNSPILL 插入决策表(rewrite 消费);`phys_peak` 回写 n_registers; +//! `arg_window_base` 供调用点参数连续性 MOV 补位。BTreeMap 保确定性。 use std::collections::BTreeMap; @@ -31,7 +31,7 @@ pub struct FreshVreg { } /// 溢出决策表:被 spill 的 vreg 的 def/use 点 + fresh vreg id 对照。 -/// defs/uses 两 Vec 按下标升序。05-08 rewrite 消费此结构插 SPILL/UNSPILL。 +/// defs/uses 两 Vec 按下标升序,rewrite 消费此结构插 SPILL/UNSPILL。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct SpillPlan { pub vreg: u32, @@ -47,9 +47,9 @@ pub struct AllocMap { pub map: BTreeMap, /// spill 决策表(按 vreg 升序) pub spills: Vec, - /// 物理峰值(05-08 回写 n_registers,≤253) + /// 物理峰值(finish 回写 n_registers,≤253) pub phys_peak: u32, - /// 参数窗口基址(05-08 调用点 MOV 补位,max_nargs=0 时 = 254 无窗口) + /// 参数窗口基址(调用点 MOV 补位,max_nargs=0 时 = 254 无窗口) pub arg_window_base: u32, } diff --git a/crates/oxide_regalloc/src/color.rs b/crates/oxide_regalloc/src/color.rs index 79e4f9d..11b1996 100644 --- a/crates/oxide_regalloc/src/color.rs +++ b/crates/oxide_regalloc/src/color.rs @@ -1,10 +1,11 @@ -//! 染色:Kemp 简化 + 贪心选色 + spill 区间拆分循环 → AllocMap(D-09/D-05/D-02)。 +//! 染色:Kemp 简化 + 贪心选色 + spill 区间拆分循环 → AllocMap。 //! //! 主循环:build 干涉图 → Kemp 简化(degree < k 入栈)→ 卡住选 spill 候选 → 被 spill 的 //! vreg 按 def/use 点拆成单点活度 fresh vreg → 重建图重试(外循环固定点)→ 收敛后贪心 -//! 选色。fresh 染色失败 = 单点活度 ≥ k = 无可行染色 → Err(D-02 RangeError 路径)。 +//! 选色。fresh 染色失败 = 单点活度 ≥ k = 无可行染色 → Err(RangeError 路径,消息与 +//! lower 逐字一致)。 //! -//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap(B010)。 +//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap。 use std::collections::{BTreeMap, BTreeSet}; @@ -32,28 +33,35 @@ pub(super) fn run(f: &IRFunction, live: &LiveInfo) -> Result { } for v in failed { if fresh.iter().any(|fr| fr.id == v) { - // 单点活度 ≥ k:无可行染色(D-02 错误路径,消息与 lower 逐字一致) + // 单点活度 ≥ k:无可行染色(消息与 lower 逐字一致) return Err("RangeError: function body uses too many registers (max 253)".into()); } - // 真实 vreg 溢出(D-05 区间拆分 v1) + // 真实 vreg 溢出:按 def/use 点拆成单点活度 fresh,重建图重试 spill_set.insert(v); debug_assert!(u32::from(next_slot) <= u16::MAX as u32, "spill slot 超 u16 上限"); slot_for_vreg.insert(v, next_slot); next_slot += 1; for d in def_points(f, v) { - fresh.push(FreshVreg { id: next_fresh_id, at: d, kind: FreshKind::Def, owner: v }); + fresh.push(FreshVreg { + id: next_fresh_id, + at: d, + kind: FreshKind::Def, + owner: v, + }); next_fresh_id += 1; } for u in use_points(f, v) { - fresh.push(FreshVreg { id: next_fresh_id, at: u, kind: FreshKind::Use, owner: v }); + fresh.push(FreshVreg { + id: next_fresh_id, + at: u, + kind: FreshKind::Use, + owner: v, + }); next_fresh_id += 1; } } iters += 1; - debug_assert!( - iters < max_real as usize * 2 + fresh.len() + 8, - "RegAlloc 染色疑似不收敛" - ); + debug_assert!(iters < max_real as usize * 2 + fresh.len() + 8, "RegAlloc 染色疑似不收敛"); }; assemble(f, colors, spill_set, fresh, slot_for_vreg, arg_window_base) @@ -183,7 +191,12 @@ fn assemble( .saturating_add(1) .min(254); - Ok(AllocMap { map, spills, phys_peak, arg_window_base }) + Ok(AllocMap { + map, + spills, + phys_peak, + arg_window_base, + }) } #[cfg(test)] @@ -271,10 +284,13 @@ mod tests { #[test] fn escaped_vreg_keeps_color_and_not_spilled() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); let mut sub = empty_function(); - sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None)); + sub.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None)); f.nested.push(sub); let cfg = oxide_cfg::build_cfg(&f); let live = oxide_liveness::liveness(&f, &cfg); @@ -311,11 +327,7 @@ mod tests { assert!(m.spills.iter().any(|s| s.vreg == 1000), "spill 的是 L"); assert!(m.phys_peak <= 253, "phys_peak ≤ 253"); // 全部 fresh 应有 Phys 分配 - let fresh_phys: Vec<&Alloc> = m - .map - .values() - .filter(|a| matches!(a, Alloc::Phys(_))) - .collect(); + let fresh_phys: Vec<&Alloc> = m.map.values().filter(|a| matches!(a, Alloc::Phys(_))).collect(); assert!(!fresh_phys.is_empty(), "fresh vreg 应着色为 Phys"); } diff --git a/crates/oxide_regalloc/src/finish.rs b/crates/oxide_regalloc/src/finish.rs index 5845d89..7c83f08 100644 --- a/crates/oxide_regalloc/src/finish.rs +++ b/crates/oxide_regalloc/src/finish.rs @@ -1,4 +1,4 @@ -//! 元数据回写:n_registers / builtin_reg_map / param_layout / upvalue_captures(D-10/D-03)。 +//! 元数据回写:n_registers / builtin_reg_map / param_layout / upvalue_captures。 //! //! - n_registers = map.phys_peak(≤253;不含参数窗口——窗口是调用点暂存槽, //! CALL handler 在 push 前收集 args 进 Vec,n_registers 只控 save_stack 窗口) @@ -39,23 +39,23 @@ pub(super) fn run(f: &mut IRFunction, map: &AllocMap) { } } - // (c) param_layout 不动(参数段 pre-colored 恒等;窗口与参数段重叠仅 05-07 标注的罕见情形) + // (c) param_layout 不动(参数段预着色恒等;窗口与参数段重叠仅参数段贴 254 的罕见情形) let pl = f.param_layout; debug_assert!(pl.base + pl.count <= map.arg_window_base || map.arg_window_base == 254); // (d) upvalue_captures 不动。enclosing_reg 是编译期产物(MAKE_CELL 定位用),VM 运行时 // 不读(CREATE_CLOSURE 走 cell_idx,cell 捕获后值在 cell 中)——无需跨函数同步。 - // 直接槽引用(class field 计算键等)的 escaped vreg 已由 05-07 预着色恒等保留。 - // 注:cell 捕获变量的父 vreg 可被 RegAlloc 移动(MAKE_CELL 后值入 cell,register 无关), + // 直接槽引用(class field 计算键等)的 escaped vreg 已由预着色恒等保留。 + // 注:cell 捕获变量的父 vreg 可被 RegAlloc 移动(MAKE_CELL 后值入 cell,与寄存器无关), // 此处不做恒等断言。 } #[cfg(test)] mod tests { use super::*; + use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; - use oxide_bytecode::opcode::OpCode; #[test] fn n_registers_equals_phys_peak() { @@ -70,7 +70,8 @@ mod tests { fn builtin_moved_rewritten() { let mut f = IRFunction::new(); f.builtin_reg_map = vec![("Math".to_string(), 5)]; - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); let mut map = AllocMap::new(); map.map.insert(5, Alloc::Phys(9)); map.map.insert(1, Alloc::Phys(1)); @@ -82,7 +83,8 @@ mod tests { fn builtin_spilled_bound_to_free_color() { let mut f = IRFunction::new(); f.builtin_reg_map = vec![("Math".to_string(), 5)]; - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); let mut map = AllocMap::new(); map.map.insert(5, Alloc::Spill(0)); map.map.insert(1, Alloc::Phys(1)); diff --git a/crates/oxide_regalloc/src/graph.rs b/crates/oxide_regalloc/src/graph.rs index 1f7e5a8..8cb8829 100644 --- a/crates/oxide_regalloc/src/graph.rs +++ b/crates/oxide_regalloc/src/graph.rs @@ -1,13 +1,13 @@ -//! 干涉图构建 + 预着色 + 可分配色集(D-09/D-10/D-11 + RESEARCH 发现 4)。 +//! 干涉图构建 + 预着色 + 可分配色集。 //! //! - 真实 vreg:字面 `Operand::Reg(r)`(rd/a/b)+ CALL 系参数连续区间 [first, first+nargs) //! - 干涉边:逐指令 `live_before[i]` 中同时 live 的真实 vreg 两两加边(fresh vreg 按 at==i 并入) -//! - 预着色:参数段 [param_base, base+count) 钉死原号(VM 调用契约 D-10);escaped vreg -//! (nested `LOAD_VAR.a`/`STORE_VAR.rd` 直引父槽)钉死原槽号且排除 spill(B012) +//! - 预着色:参数段 [param_base, base+count) 钉死原号(VM 调用契约);escaped vreg +//! (nested `LOAD_VAR.a`/`STORE_VAR.rd` 直引父槽)钉死原槽号且排除 spill //! - 可分配色集:`(1..=253) − 参数色 − escaped 色 − [arg_window_base, 253]` -//! (arg_window_base = 254 − max_nargs,为 05-08 调用点 MOV 补位留空槽) +//! (arg_window_base = 254 − max_nargs,为调用点参数连续性 MOV 补位留空槽) //! -//! 确定性:全 BTreeMap + Vec 排序,禁 HashMap 迭代序(B010)。 +//! 确定性:全 BTreeMap + Vec 排序,禁 HashMap 迭代序。 use std::collections::{BTreeMap, BTreeSet}; @@ -36,10 +36,10 @@ pub(super) struct InterferenceGraph { /// 收集全部真实 vreg(BTreeSet 保确定性): /// 字面 `Operand::Reg(r)`(rd/a/b 三槽)+ CALL/CALL_NATIVE/NEW_EXPRESSION b 槽参数区间 -/// + SUPER_CALL a 槽参数区间。0 无条件排除(reg 0 永非真实——emit alloc_reg 从 ≥1 起)。 +/// + SUPER_CALL a 槽参数区间。0 无条件排除(reg 0 永非真实——emit 分配寄存器从 ≥1 起)。 /// -/// 254/255 的 over-approx 规则:live bitset 中位 254/255 无法区分 This/NewTarget 占用与 -/// 真实 vreg 活度,若函数存在该号真实 vreg(大函数 B005 场景)则按真实 vreg 建节点染色—— +/// 254/255 的过近似规则:live bitset 中位 254/255 无法区分 This/NewTarget 占用与 +/// 真实 vreg 活度,若函数存在该号真实 vreg(大函数场景)则按真实 vreg 建节点染色—— /// 多出的边只造成过度约束(安全方向),缺失边才会错值。 pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet { let mut real = BTreeSet::new(); @@ -85,7 +85,7 @@ pub(super) fn build( // ── 预着色 ── let mut pre_colors: BTreeMap = BTreeMap::new(); - // 参数段:VM 调用写 regs[param_base+i],钉死不可动(D-10) + // 参数段:VM 调用写 regs[param_base+i],钉死不可动(调用契约) let pl = f.param_layout; if pl.count > 0 { for i in 0..pl.count { @@ -93,12 +93,12 @@ pub(super) fn build( pre_colors.insert(v, v); } } - // escaped:递归 nested 收集 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012) + // escaped:递归 nested 收集 LOAD_VAR.a / STORE_VAR.rd 直引的父槽 let mut escaped_colors: Vec = Vec::new(); collect_escaped(&f.nested, &mut pre_colors, &mut escaped_colors); - // 对称缺口(B013 延伸):子模块自身引用的父槽也必须预着色恒等。父侧 collect_escaped - // 只保证父不移动槽;但子模块 alloc 时,它引用父槽的 LOAD_VAR.a / STORE_VAR.rd 会被 - // 当作子模块自己的 vreg 参与染色而移走 → 子模块读错物理槽。分界线 = param_layout.base + // 对称缺口:子模块自身引用的父槽也必须预着色恒等。父侧 collect_escaped 只保证父不 + // 移动槽;但子模块 alloc 时,它引用父槽的 LOAD_VAR.a / STORE_VAR.rd 会被当作子模块 + // 自己的 vreg 参与染色而移走 → 子模块读错物理槽。分界线 = param_layout.base // (emit 的 inherited_reg_start 继承机制:子模块 vreg ≥ base,父槽引用 < base)。 collect_own_escaped(f, &mut pre_colors, &mut escaped_colors); @@ -106,12 +106,7 @@ pub(super) fn build( let max_nargs = f .insts .iter() - .filter(|i| { - matches!( - i.op, - OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL - ) - }) + .filter(|i| matches!(i.op, OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL)) .map(|i| i.ext.first().copied().unwrap_or(0)) .max() .unwrap_or(0); @@ -131,12 +126,18 @@ pub(super) fn build( if !spill_set.contains(&v) { nodes.insert( v, - Node { pre_color: pre_colors.get(&v).copied(), adj: Vec::new() }, + Node { + pre_color: pre_colors.get(&v).copied(), + adj: Vec::new(), + }, ); } } for fr in fresh { - nodes.entry(fr.id).or_insert(Node { pre_color: None, adj: Vec::new() }); + nodes.entry(fr.id).or_insert(Node { + pre_color: None, + adj: Vec::new(), + }); } // 邻接表(BTreeMap> 去重后转 Vec 排序) let mut adj_sets: BTreeMap> = BTreeMap::new(); @@ -171,11 +172,16 @@ pub(super) fn build( } } - InterferenceGraph { nodes, allocatable, k, arg_window_base } + InterferenceGraph { + nodes, + allocatable, + k, + arg_window_base, + } } /// 递归收集 nested 树中变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽), -/// 注入 pre_colors 并记录 escaped 色列表(可分配集排除 + spill 候选排除,B012)。 +/// 注入 pre_colors 并记录 escaped 色列表(可分配集排除 + spill 候选排除)。 fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap, out: &mut Vec) { for sub in nested { for inst in &sub.insts { @@ -196,7 +202,7 @@ fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap, o out.sort_unstable(); } -/// 收集当前函数自身对父槽的直接引用(B013 延伸,父侧保护的对称缺口): +/// 收集当前函数自身对父槽的直接引用(父侧保护的对称缺口): /// `LOAD_VAR.a` / `STORE_VAR.rd` 中槽号 < param_layout.base 的 vreg 是父槽引用 /// (emit inherited_reg_start 分界:子函数自身 vreg 从 base 起分配,父槽引用 < base)。 /// 父侧 collect_escaped 只保护父不移动槽;此处保证子函数 alloc 时这些引用不被染色移走。 @@ -245,8 +251,10 @@ mod tests { let mut f = empty_function(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); f.insts.push(Inst::load_const(Operand::Reg(2), 0)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); let g = build_graph(&f); assert!(g.nodes[&1].adj.contains(&2), "r1 与 r2 应邻接"); assert!(g.nodes[&2].adj.contains(&1)); @@ -259,11 +267,14 @@ mod tests { let mut f = empty_function(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); f.insts.push(Inst::load_const(Operand::Reg(2), 0)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); f.insts.push(Inst::load_const(Operand::Reg(4), 0)); f.insts.push(Inst::load_const(Operand::Reg(5), 0)); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); let g = build_graph(&f); assert!(!g.nodes[&1].adj.contains(&4), "r1 与 r4 活度不相交,无边"); } @@ -272,8 +283,10 @@ mod tests { fn param_segment_precolored() { let mut f = empty_function(); f.param_layout = oxide_ir::ParamLayout { base: 1, count: 2 }; - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); let g = build_graph(&f); assert_eq!(g.nodes[&1].pre_color, Some(1)); assert_eq!(g.nodes[&2].pre_color, Some(2)); @@ -284,10 +297,13 @@ mod tests { #[test] fn escaped_slots_precolored() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); let mut sub = empty_function(); - sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None)); + sub.insts + .push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None)); f.nested.push(sub); let g = build_graph(&f); assert_eq!(g.nodes[&3].pre_color, Some(3), "escaped 槽 r3 预着色"); @@ -297,8 +313,10 @@ mod tests { #[test] fn reserved_colors_excluded() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); let g = build_graph(&f); assert!(!g.allocatable.contains(&0), "色 0 排除(CALL 隐式 reg0)"); assert!(!g.allocatable.contains(&254) && !g.allocatable.contains(&255), "254/255 排除"); @@ -309,7 +327,8 @@ mod tests { fn arg_window_reserved() { let mut f = empty_function(); f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 3)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); let g = build_graph(&f); assert_eq!(g.arg_window_base, 251, "max_nargs=3 → base 251"); assert!(!g.allocatable.contains(&251) && !g.allocatable.contains(&252) && !g.allocatable.contains(&253)); @@ -317,10 +336,12 @@ mod tests { #[test] fn high_numbered_vreg_254_is_real_node() { - // 大函数场景:真实 vreg 254 必须建节点(B005 正确性前提) + // 大函数场景:真实 vreg 254 必须建节点(正确性前提) let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(252), Operand::Reg(253))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(252), Operand::Reg(253))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None)); let real = collect_real_vregs(&f); assert!(real.contains(&254), "真实 vreg 254 必须被收集"); let g = build_graph(&f); diff --git a/crates/oxide_regalloc/src/lib.rs b/crates/oxide_regalloc/src/lib.rs index 0397c8c..7dd409c 100644 --- a/crates/oxide_regalloc/src/lib.rs +++ b/crates/oxide_regalloc/src/lib.rs @@ -1,10 +1,10 @@ //! RegAlloc 改写 pass:干涉图染色决策(前半)+ 指令改写/元数据回写(后半)。 //! -//! 消费 `oxide_liveness::LiveInfo`(inst_live_before 逐指令活集,05-04 交付)与 +//! 消费 `oxide_liveness::LiveInfo`(inst_live_before 逐指令活集)与 //! `oxide_ir::IRFunction`(param_layout 参数段 + nested escaped 收集)。染色:干涉图 //! (graph.rs)+ Kemp 简化/贪心/spill 区间拆分(color.rs)→ AllocMap;改写:rewrite.rs //! (指令槽 vreg→phys 重写 + spill 插入 + 参数连续性 MOV + label 重建)+ finish.rs -//! (元数据回写)。`alloc()` 是完整改写 pass 入口(D-16),`color()` 只产决策。 +//! (元数据回写)。`alloc()` 是完整改写 pass 入口,`color()` 只产决策。 mod alloc_map; mod color; @@ -18,8 +18,8 @@ use oxide_ir::IRFunction; use oxide_liveness::LiveInfo; /// 染色:`&IRFunction + &LiveInfo → Result`。纯函数,只读 IR。 -/// 空 IRFunction 退化:Ok(AllocMap::new())。Err = 无可行染色(D-02 RangeError 路径, -/// 05-08 接线时转 lower 错误)。 +/// 空 IRFunction 退化:Ok(AllocMap::new())。Err = 无可行染色(RangeError 路径, +/// 消息与 lower 逐字一致,编译接线时原样传播)。 pub fn color(f: &IRFunction, live: &LiveInfo) -> Result { if f.insts.is_empty() { return Ok(AllocMap::new()); @@ -27,12 +27,17 @@ pub fn color(f: &IRFunction, live: &LiveInfo) -> Result { color::run(f, live) } -/// 寄存器分配改写:`&mut IRFunction + &LiveInfo → Result<(), String>`(D-16)。 +/// 寄存器分配改写:`&mut IRFunction + &LiveInfo → Result<(), String>`。 /// -/// 流程:LiveInfo 维度守卫(inst 数不符 → 内部重跑 build_cfg + liveness,D-17 修正: -/// 精确 DCE 删指令后 LiveInfo 过期,重跑纯函数一次调用,不重复建分析引擎)→ -/// `color()`(Err 原样传播)→ `rewrite::run` → `finish::run` → nested 递归自顶向下 -/// (每子函数独立 vreg 空间独立 alloc,D-03/D-10)。 +/// # 步骤 +/// - LiveInfo 维度守卫:inst 数不符说明 LiveInfo 已过期(精确 DCE 删指令后下标位移), +/// 内部重跑 build_cfg + liveness 重建,不重复建分析引擎 +/// - `color()` 产染色决策(Err 原样传播) +/// - `rewrite::run` 指令改写 → `finish::run` 元数据回写 +/// - nested 递归自顶向下:每子函数独立 vreg 空间独立 alloc +/// +/// # 注意事项 +/// - 嵌套子函数不继承父 LiveInfo,各自重跑 build_cfg + liveness。 pub fn alloc(f: &mut IRFunction, live: &LiveInfo) -> Result<(), String> { if f.insts.is_empty() && f.nested.is_empty() { return Ok(()); @@ -71,8 +76,10 @@ mod tests { #[test] fn alloc_stale_live_reruns() { let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); // 空 LiveInfo(维度不符)→ 内部重跑 let stale = LiveInfo::new(); assert!(alloc(&mut f, &stale).is_ok()); @@ -88,10 +95,12 @@ mod tests { #[test] fn alloc_err_propagates() { - // 255 参数 CALL:窗口吞并 → k=0 → 无可行染色 → Err + // 255 参数 CALL:参数窗口吞并全部可分配色 → k=0 → 无可行染色 → Err let mut f = IRFunction::new(); - f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3000), 255)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3000), 255)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); let cfg = oxide_cfg::build_cfg(&f); let live = oxide_liveness::liveness(&f, &cfg); let err = alloc(&mut f, &live).unwrap_err(); @@ -102,10 +111,12 @@ mod tests { fn alloc_recurses_nested() { let mut f = IRFunction::new(); f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); let mut sub = IRFunction::new(); sub.insts.push(Inst::load_const(Operand::Reg(3), 0)); - sub.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + sub.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); f.nested.push(sub); let cfg = oxide_cfg::build_cfg(&f); let live = oxide_liveness::liveness(&f, &cfg); @@ -122,4 +133,3 @@ mod tests { assert!(f.nested[0].n_registers <= 253, "子函数 n_registers 已回写"); } } - diff --git a/crates/oxide_regalloc/src/rewrite.rs b/crates/oxide_regalloc/src/rewrite.rs index 3890594..6671a36 100644 --- a/crates/oxide_regalloc/src/rewrite.rs +++ b/crates/oxide_regalloc/src/rewrite.rs @@ -1,14 +1,14 @@ -//! 指令改写:消费 AllocMap 就地重写 IR(D-05/D-07/D-20/RESEARCH 发现 4)。 +//! 指令改写:消费 AllocMap 就地重写 IR。 //! //! - 槽位 vreg→phys 重写:仅 `Operand::Reg`;None/This/NewTarget/Const/Imm/Label 零改动 -//! (None→0、This→254、NewTarget→255 由 lower 映射,D-11) -//! - spill 落点(D-05 区间拆分 v1):def 点后 SPILL、use 点前 UNSPILL;RMW(COMPOUND 系 -//! rd 读旧值)UNSPILL 进 def-fresh 寄存器、use-fresh 弃用 -//! - 调用点参数连续性(RESEARCH 发现 4):参数分散则 MOV 搬进 ARG_WINDOW +//! (None→0、This→254、NewTarget→255 由 lower 映射) +//! - spill 落点:def 点后 SPILL、use 点前 UNSPILL;RMW(COMPOUND 系 rd 读旧值)UNSPILL +//! 进 def-fresh 寄存器、use-fresh 弃用 +//! - 调用点参数连续性:参数分散则 MOV 搬进 ARG_WINDOW //! - spilled builtin 入口 SPILL(builtin 无指令 def,VM 入口隐式绑定) //! - label_pos mark-sweep 重建:label 落 group 起点(跳转先执行 before-插入) //! -//! 确定性:全 BTreeMap/Vec 排序,禁 HashMap(B010)。 +//! 确定性:全 BTreeMap/Vec 排序,禁 HashMap。 use std::collections::BTreeMap; @@ -189,7 +189,7 @@ fn rewrite_inst( } } } - other => other, // None/This/NewTarget/Const/Imm/Label 零改动(D-11) + other => other, // None/This/NewTarget/Const/Imm/Label 零改动,lower 映射语义号 } }; @@ -269,7 +269,7 @@ pub(super) fn spilled_builtin_bindings(f: &IRFunction, map: &AllocMap) -> Vec<(S } // escaped 色(递归 nested 的 LOAD_VAR.a / STORE_VAR.rd) collect_escaped(&f.nested, &mut excluded); - // own-escaped 色(B013 延伸):本函数 LOAD_VAR.a / STORE_VAR.rd 引用父槽(< base)。 + // own-escaped 色:本函数 LOAD_VAR.a / STORE_VAR.rd 引用父槽(< base)。 // 与 graph.rs collect_own_escaped 对称——spill 自由色不得占用父槽号。 collect_own_escaped(f, &mut excluded); // 窗口 @@ -364,7 +364,8 @@ mod tests { #[test] fn semantic_operands_untouched() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::This, Operand::NewTarget, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::This, Operand::NewTarget, Operand::None)); f.insts.push(Inst::load_const(Operand::Reg(1), 3)); let map = AllocMap::new(); run(&mut f, &map); @@ -376,8 +377,10 @@ mod tests { #[test] fn slots_rewritten_per_map() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); let mut map = AllocMap::new(); map.map.insert(1, Alloc::Phys(10)); map.map.insert(2, Alloc::Phys(11)); @@ -484,7 +487,11 @@ mod tests { assert!(spill_pos < ret_pos, "SPILL 在 RETURN 前"); // RETURN rd → use-fresh 8,其前有 UNSPILL(8) assert_eq!(f.insts[ret_pos].rd, Operand::Reg(8)); - let unspill8 = f.insts.iter().position(|i| i.op == OpCode::UNSPILL && i.rd == Operand::Reg(8)).unwrap(); + let unspill8 = f + .insts + .iter() + .position(|i| i.op == OpCode::UNSPILL && i.rd == Operand::Reg(8)) + .unwrap(); assert!(unspill8 < ret_pos, "RETURN 前 UNSPILL use-fresh"); // use-fresh 6(RMW 点弃用)不得产生 UNSPILL assert!( @@ -533,7 +540,8 @@ mod tests { #[test] fn spilled_builtin_entry_spill_inserted() { let mut f = empty_function(); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); f.builtin_reg_map = vec![("Math".to_string(), 10)]; let mut map = AllocMap::new(); map.map.insert(10, Alloc::Spill(0)); diff --git a/crates/oxide_regalloc/tests/b005_samples.rs b/crates/oxide_regalloc/tests/b005_samples.rs index 84043aa..5e84a18 100644 --- a/crates/oxide_regalloc/tests/b005_samples.rs +++ b/crates/oxide_regalloc/tests/b005_samples.rs @@ -1,9 +1,9 @@ -//! B005 复现 + n_registers 汇总断言(REG-04 直接验收)。 +//! 大函数寄存器收缩复现 + n_registers 汇总断言。 //! //! 低层管线直调(不经 Compiler——oxide_regalloc dev-dep oxide_compiler 会构成依赖环): -//! parse → emit → dce(保守)→(regalloc on/off)→ lower。on 分支按 RESEARCH 例 6: -//! 精确 DCE 后重跑 build_cfg + liveness 再 alloc(D-17 索引位移修正);off 分支 = D-18 -//! 降级路径(vreg 原样当物理号 → lower Reg>253 抛 RangeError)。 +//! parse → emit → dce(保守)→(regalloc on/off)→ lower。on 分支按编译管线: +//! 精确 DCE 后重跑 build_cfg + liveness 再 alloc(删指令后下标位移,重建避免错位); +//! off 分支为降级路径(vreg 原样当物理号 → lower Reg>253 抛 RangeError)。 use oxide_bytecode::module::CompiledModule; @@ -23,7 +23,7 @@ fn compile_source(src: &str, regalloc: bool) -> Result { oxide_ir::lower::lower(&ir) } -/// B005 样例生成器:n 个逐语句独立 vreg(各值立即死)→ vreg 总数 ≈ 2n ≫ 253, +/// 大函数样例生成器:n 个逐语句独立 vreg(各值立即死)→ vreg 总数 ≈ 2n ≫ 253, /// 但峰值活度极小 → RegAlloc 有充分复用空间。 fn gen_big(n: usize) -> String { let mut src = String::from("function f() { "); @@ -43,9 +43,9 @@ fn module_max_n_registers(m: &CompiledModule) -> u8 { max } -/// B005 前置复现:off 模式(降级路径)250 变量函数报 RangeError。 +/// 前置复现:off 模式(降级路径)250 变量函数报 RangeError。 #[test] -fn b005_repro_errors_without_regalloc() { +fn large_function_errors_without_regalloc() { let err = match compile_source(&gen_big(250), false) { Err(e) => e, Ok(_) => panic!("off 模式应报 RangeError"), @@ -53,14 +53,14 @@ fn b005_repro_errors_without_regalloc() { assert!(err.contains("RangeError"), "off 模式应报 RangeError: {err}"); } -/// B005 修复达成:on 模式 250 变量函数编译成功,模块树 n_registers ≤ 253。 +/// 修复达成:on 模式 250 变量函数编译成功,模块树 n_registers ≤ 253。 #[test] -fn b005_compiles_with_regalloc() { +fn large_function_compiles_with_regalloc() { let module = compile_source(&gen_big(250), true).expect("on 模式应编译成功"); assert!(module_max_n_registers(&module) <= 253, "模块树 n_registers 应 ≤253"); } -/// 样例集 n_registers on ≤ off 且至少一个严格下降(证明寄存器复用,REG-04 核心)。 +/// 样例集 n_registers on ≤ off 且至少一个严格下降(证明寄存器复用)。 #[test] fn n_registers_on_off_aggregate() { let samples = [ @@ -78,7 +78,7 @@ fn n_registers_on_off_aggregate() { "function f() { var [a, b] = [1, 2]; var { c, d } = { c: 3, d: 4 }; return a + b + c + d; } f();", // 箭头函数 "var f = (a, b) => a * b; f(6, 7);", - // switch+const(B011 回归) + // switch+const(const guard 回归) "function f(x) { switch (x) { case 1: break; } const c = 5; return c; } f(1);", // 数组字面量 "function f() { var a = [1, 2, 3, 4, 5]; return a[0] + a[4]; } f();", @@ -90,10 +90,7 @@ fn n_registers_on_off_aggregate() { assert!(on_max <= 253, "样例 {i} on n_registers {on_max} 超 253"); if let Ok(off) = compile_source(src, false) { let off_max = module_max_n_registers(&off); - assert!( - on_max <= off_max, - "样例 {i} on({on_max}) > off({off_max})——RegAlloc 不应增寄存器窗口" - ); + assert!(on_max <= off_max, "样例 {i} on({on_max}) > off({off_max})——RegAlloc 不应增寄存器窗口"); if on_max < off_max { strict_decrease = true; } @@ -102,7 +99,7 @@ fn n_registers_on_off_aggregate() { assert!(strict_decrease, "至少一个样例应严格 on < off(复用证明)"); } -/// 确定性:同一源码 on 模式编译两次字节码逐字节相同(B010,structural_hash 缓存依赖)。 +/// 确定性:同一源码 on 模式编译两次字节码逐字节相同(确定性排序,缓存 key 依赖)。 #[test] fn regalloc_deterministic_output() { let a = compile_source(&gen_big(50), true).expect("on 编译成功"); diff --git a/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs b/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs index f4e023e..ae7380f 100644 --- a/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs +++ b/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs @@ -1,4 +1,4 @@ -//! B013 延伸回归:nested 子模块直接槽引用父槽,RegAlloc on/off 运行时等价。 +//! 嵌套父槽引用回归:nested 子模块直接槽引用父槽,RegAlloc on/off 运行时等价。 //! //! 父侧 collect_escaped 保证父不移动槽;但子模块自身 alloc 时引用父槽的 //! LOAD_VAR.a / STORE_VAR.rd 曾被当作子模块自己的 vreg 染色移走(→ 读错物理槽, diff --git a/crates/oxide_regalloc/tests/reg_count.rs b/crates/oxide_regalloc/tests/reg_count.rs index a4b8729..daaeb6f 100644 --- a/crates/oxide_regalloc/tests/reg_count.rs +++ b/crates/oxide_regalloc/tests/reg_count.rs @@ -1,4 +1,4 @@ -//! REG-04 断言:RegAlloc on ≤253 且 < off(寄存器收缩);B005 大函数 on 编译成功。 +//! 寄存器收缩断言:RegAlloc on ≤253 且 < off(寄存器复用);大函数 on 编译成功。 fn n_registers(src: &str, regalloc: bool) -> Result { let allocator = oxide_parser::Allocator::default(); @@ -70,15 +70,15 @@ fn n_registers_shrinks_with_regalloc() { } } -/// B005 大函数:on 编译成功 ≤253;off 报 RangeError(B005 解决证据)。 +/// 大函数:on 编译成功 ≤253;off 报 RangeError(vreg 超上限)。 #[test] -fn b005_large_function_fits_in_253() { +fn large_function_fits_in_253() { let src = gen_sum_function(220); let on = n_registers(&src, true); - assert!(on.is_ok(), "B005 样例 on 应编译成功"); + assert!(on.is_ok(), "大函数样例 on 应编译成功"); assert!(on.unwrap() <= 253, "on ≤253"); let off = n_registers(&src, false); - assert!(off.is_err(), "B005 样例 off 应报 RangeError"); + assert!(off.is_err(), "大函数样例 off 应报 RangeError"); assert!(off.unwrap_err().contains("too many registers"), "off Err 消息"); } diff --git a/crates/oxide_regalloc/tests/runtime_eq.rs b/crates/oxide_regalloc/tests/runtime_eq.rs index bcc0b5a..b62a2ee 100644 --- a/crates/oxide_regalloc/tests/runtime_eq.rs +++ b/crates/oxide_regalloc/tests/runtime_eq.rs @@ -1,4 +1,4 @@ -//! RegAlloc on/off 运行时等价(D-21 主验证)+ B005/spill 样例运行正确(REG-04)。 +//! RegAlloc on/off 运行时等价(主验证)+ 大函数/spill 样例运行正确。 //! //! 同一 JS 源码走两条管线:parse → emit → dce(保守恒定)→(regalloc on/off)→ lower → run, //! 对比顶层执行结果。**不经 Compiler::compile**(测试层直调底层函数)。 @@ -38,9 +38,9 @@ fn normalize(r: Result) -> (bool, String) { #[test] fn regalloc_preserves_semantics() { let samples = [ - // switch+const(B011 回归) + // switch+const(const guard 回归) "function f(x) { switch (x) { case 1: break; } const c = 5; return c; } f(1);", - // 类字段(B012 行为锚) + // 类字段(escaped 槽行为锚) "var x = 0; class C { p = (x = 1); } new C(); x;", // try-catch "function f() { try { throw 1; } catch (e) { return e + 1; } } f();", @@ -95,15 +95,16 @@ fn gen_spill_function(n: u32) -> String { src } -/// B005 大函数(200+ 变量)与 spill 压力样例 on 运行正确。 -/// off 路径 lower 失败(B005 上限)——不能做 on/off 相等,on 运行正确即 REG-04 证据。 +/// 大函数(200+ 变量)与 spill 压力样例 on 运行正确。 +/// off 路径 lower 失败(vreg 超 253 上限)——不能做 on/off 相等,on 运行正确即 +/// 寄存器复用与 spill 的证据。 #[test] -fn b005_and_spill_samples_run_correctly() { - // B005:220 变量 → vreg 数远超 253,但同时存活 < 253 → RegAlloc 复用解决 - let src_b005 = gen_sum_function(220); - let expected_b005 = 220 * 219 / 2; - let out = run_source(&src_b005, true).expect("B005 样例 on 应编译成功"); - assert_eq!(out.to_string(), expected_b005.to_string(), "B005 样例结果错误"); +fn large_and_spill_samples_run_correctly() { + // 220 变量 → vreg 数远超 253,但同时存活 < 253 → RegAlloc 复用解决 + let src_large = gen_sum_function(220); + let expected_large = 220 * 219 / 2; + let out = run_source(&src_large, true).expect("大函数样例 on 应编译成功"); + assert_eq!(out.to_string(), expected_large.to_string(), "大函数样例结果错误"); // spill 压力:长活 L + 254 短活 → L 溢出,验证 SPILL/UNSPILL 真实发生且结果正确 let src_spill = gen_spill_function(254); @@ -124,7 +125,10 @@ fn b005_and_spill_samples_run_correctly() { let module = oxide_ir::lower::lower(&ir).expect("lower"); // 递归扫描模块树(spill 发生在嵌套函数 f 中,非顶层 f(1) 调用) fn has_op(m: &oxide_bytecode::module::CompiledModule, target: OpCode) -> bool { - if m.bytecode.iter().any(|&instr| OpCode::try_from(instr as u8).ok() == Some(target)) { + if m.bytecode + .iter() + .any(|&instr| OpCode::try_from(instr as u8).ok() == Some(target)) + { return true; } m.sub_modules.iter().any(|s| has_op(s, target)) diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index efe648b..91d8091 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -1,15 +1,14 @@ -//! `oxide_runtime_api` — the abstract interface between builtins and the VM. +//! `oxide_runtime_api` —— builtins 与 VM 之间的抽象接口。 //! -//! Builtins are written generically against the [`VmHost`] trait -//! (`fn xxx(host: &mut H, args: &[u8]) -> NativeResult`); `Vm` -//! implements `VmHost`. This breaks what would otherwise be a circular -//! dependency between the builtins crate and `oxide_vm`: +//! Builtins 以泛型方式针对 [`VmHost`] trait 编写 +//! (`fn xxx(host: &mut H, args: &[u8]) -> NativeResult`);`Vm` +//! 实现 `VmHost`。这打破了 builtins crate 与 `oxide_vm` 之间本会形成的 +//! 循环依赖: //! //! `oxide_types ← oxide_kernel ← oxide_runtime_api ← oxide_builtins ← oxide_vm` //! -//! The trait is GENERIC-friendly, not object-safe: monomorphizing `H = Vm` -//! inlines every `host.*()` call, so there is zero runtime overhead versus -//! builtins living inside `oxide_vm`. +//! trait 面向泛型而非对象安全:单态化 `H = Vm` 使每个 `host.*()` 调用内联, +//! 相对 builtins 直接位于 `oxide_vm` 内部没有运行时开销。 use std::sync::Arc; @@ -19,7 +18,7 @@ use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::shape::EMPTY_SHAPE_ID; use oxide_types::value::JsValue; -/// Return value of every builtin native function. +/// 每个 builtin native 函数的返回值。 pub enum NativeResult { Ok(JsValue), Err(JsValue), @@ -60,37 +59,36 @@ impl NativeResult { } } -/// The set of `Vm` capabilities that builtins depend on. +/// builtins 依赖的 `Vm` 能力集合。 /// -/// Signatures are byte-for-byte copies of the corresponding inherent methods on -/// `Vm`; `impl VmHost for Vm` delegates to them. The trait is intentionally -/// flat and not object-safe — builtins always take `&mut impl VmHost`. +/// 方法签名是 `Vm` 上同名固有方法的逐字节拷贝;`impl VmHost for Vm` 委托给 +/// 它们。trait 刻意保持扁平且非对象安全——builtins 始终接受 `&mut impl VmHost`。 pub trait VmHost { - // Register access + // 寄存器访问 fn reg(&self, idx: u8) -> JsValue; fn set_reg(&mut self, idx: u8, val: JsValue); - // Object allocation / string creation + // 对象分配 / 字符串创建 fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject; fn new_string(&mut self, s: &str) -> JsValue; - // Kernel accessors + // 内核访问器 fn kernel_core(&self) -> &Arc; fn session(&self) -> &KernelSession; fn epoch(&self) -> &Epoch; - // Property resolution + // 属性解析 fn property_key_si(&mut self, val: JsValue) -> u32; fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option; fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option; - // Property access + // 属性访问 fn ordinary_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result; fn ordinary_set( &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, ) -> Result<(), String>; - // Property definition + // 属性定义 fn define_data_property( &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes, ) -> Result<(), String>; @@ -99,18 +97,18 @@ pub trait VmHost { ) -> Result<(), String>; fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue); - // Lookup / coercion + // 查找 / 强制转换 fn lookup_str(&self, val: JsValue) -> Option; fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result; fn coerce_number_bounded(&mut self, value: JsValue) -> Result; - // Call infrastructure + // 调用基础设施 fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result; - /// Take the original thrown JsValue preserved across a String-flattening call boundary, - /// so an iterator wrapper can re-throw the original error instead of re-wrapping it. + /// 取回在 String 展平调用边界上保留下来的原始抛出 JsValue, + /// 使迭代器包装器能重新抛出原错误而非二次包装。 fn take_uncaught_value(&mut self) -> Option; - // Error handling + // 错误处理 fn checked_object_ptr(&mut self, val: JsValue, error_msg: &str) -> Result, String>; fn raise_type_error(&mut self, msg: &str) -> Result<(), String>; fn error_message_text(&self, kind: &str, msg: &str) -> String; @@ -137,10 +135,10 @@ pub fn format_error_message(name: &str, msg: &str) -> String { } } -/// Borrow a string value's content. +/// 借出字符串值的文本内容。 /// /// # Safety -/// `val` must be a string `JsValue` whose `JsString` pointer is alive. +/// `val` 必须是字符串 `JsValue`,且其 `JsString` 指针存活。 #[inline] pub unsafe fn string_data(val: JsValue) -> &'static str { (*val.as_string_ptr()).as_str() @@ -208,8 +206,8 @@ pub fn to_int32(val: JsValue) -> i32 { } } -/// Append the string representation of `val` to `buf` without allocating -/// intermediate temporary Strings. Used in hot-path string concatenation. +/// 把 `val` 的字符串表示追加到 `buf`,不分配中间临时 String。 +/// 用于热路径字符串拼接。 pub fn push_to_string(val: JsValue, buf: &mut String) { if val.is_int() { use std::fmt::Write; @@ -327,8 +325,8 @@ pub fn to_boolean(val: JsValue) -> bool { false } -/// Whether two values share the same ECMAScript language type. Number treats -/// int- and double-tagged values as one type (both are Number). +/// 两个值是否共享同一 ECMAScript 语言类型。Number 把 int 与 double tag 的值 +/// 视为同一类型(都是 Number)。 fn same_type(a: JsValue, b: JsValue) -> bool { if a.is_string() && b.is_string() { return true; @@ -354,49 +352,48 @@ fn same_type(a: JsValue, b: JsValue) -> bool { false } -/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15 (`==`). +/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。 /// -/// BigInt steps are omitted (the engine prunes BigInt). Object operands are -/// coerced via ToPrimitive, which may invoke user `valueOf` / `toString` / -/// `@@toPrimitive`; hence the `VmHost` parameter and the `Result` (a thrown -/// TypeError from those callbacks propagates as `Err`). Note: `Object == Symbol` -/// does NOT trigger ToPrimitive (spec steps 11/12 cover only Number/String) and -/// falls through to `false`. +/// 省略 BigInt 步骤(引擎剪除 BigInt)。对象操作数经 ToPrimitive 强制转换, +/// 可能调用用户 `valueOf` / `toString` / `@@toPrimitive`;因此需要 `VmHost` +/// 参数与 `Result`(这些回调抛出的 TypeError 以 `Err` 传播)。 +/// 注意:`Object == Symbol` 不触发 ToPrimitive(规范步骤 11/12 只覆盖 +/// Number/String),直接落到 `false`。 pub fn abstract_eq(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result { - // Step 1: same type -> strict equality. + // 步骤 1:同类型 → 严格相等。 if same_type(lhs, rhs) { return Ok(strict_equality(lhs, rhs)); } - // Steps 2-3: null <-> undefined. + // 步骤 2-3:null 与 undefined 互等。 if (lhs.is_null() && rhs.is_undefined()) || (lhs.is_undefined() && rhs.is_null()) { return Ok(true); } - // Steps 5-6: Number <-> String. + // 步骤 5-6:Number 与 String。 if (lhs.is_int() || lhs.is_double()) && rhs.is_string() { return Ok(strict_double_eq(to_number(lhs), to_number(rhs))); } if lhs.is_string() && (rhs.is_int() || rhs.is_double()) { return Ok(strict_double_eq(to_number(lhs), to_number(rhs))); } - // Step 9: x is Boolean -> compare ToNumber(x). + // 步骤 9:x 为 Boolean → 比较 ToNumber(x)。 if lhs.is_bool() { return abstract_eq(JsValue::float(to_number(lhs)), rhs, host); } - // Step 10: y is Boolean -> compare ToNumber(y). + // 步骤 10:y 为 Boolean → 比较 ToNumber(y)。 if rhs.is_bool() { return abstract_eq(lhs, JsValue::float(to_number(rhs)), host); } - // Step 11: x is Number/String, y is Object -> ToPrimitive(y). + // 步骤 11:x 为 Number/String,y 为 Object → ToPrimitive(y)。 if (lhs.is_int() || lhs.is_double() || lhs.is_string()) && rhs.is_object() { let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?; return abstract_eq(lhs, prim, host); } - // Step 12: x is Object, y is Number/String -> ToPrimitive(x). + // 步骤 12:x 为 Object,y 为 Number/String → ToPrimitive(x)。 if lhs.is_object() && (rhs.is_int() || rhs.is_double() || rhs.is_string()) { let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?; return abstract_eq(prim, rhs, host); } - // Step 14: otherwise not equal. + // 步骤 14:否则不相等。 Ok(false) } @@ -602,7 +599,7 @@ pub enum ToPrimitiveHint { } impl ToPrimitiveHint { - /// OrdinaryToPrimitive method-name ordering hint: `Default`/`Number` -> "number", `String` -> "string". + /// OrdinaryToPrimitive 方法名顺序提示:`Default`/`Number` → "number",`String` → "string"。 pub fn as_str(self) -> &'static str { match self { ToPrimitiveHint::String => "string", @@ -613,10 +610,9 @@ impl ToPrimitiveHint { /// ToPrimitive(input, hint) per ECMA-262 §7.1.1. /// -/// Primitives pass through unchanged. Objects are coerced via the VM's -/// `coerce_primitive_bounded`, which consults `obj[Symbol.toPrimitive]` first and -/// otherwise runs OrdinaryToPrimitive (valueOf/toString in hint order). Keeping the -/// object path in one place avoids duplicating OrdinaryToPrimitive here. +/// 原始值原样通过。对象经 VM 的 `coerce_primitive_bounded` 强制转换——先查询 +/// `obj[Symbol.toPrimitive]`,否则按 hint 顺序运行 OrdinaryToPrimitive +/// (valueOf/toString)。对象路径集中在单处,避免在此重复 OrdinaryToPrimitive。 pub fn to_primitive(val: JsValue, hint: ToPrimitiveHint, host: &mut H) -> Result { if !val.is_object() { return Ok(val); @@ -624,8 +620,8 @@ pub fn to_primitive(val: JsValue, hint: ToPrimitiveHint, host: &mut H host.coerce_primitive_bounded(val, hint == ToPrimitiveHint::String) } -/// ToNumber(input) with full object coercion: objects go through ToPrimitive -/// (number hint); Symbol values throw a TypeError per spec. +/// 带完整对象强制转换的 ToNumber(input):对象经 ToPrimitive(number hint) +/// 处理;Symbol 值按规范抛 TypeError。 pub fn to_number_full(val: JsValue, host: &mut H) -> Result { let primitive = to_primitive(val, ToPrimitiveHint::Number, host)?; if primitive.is_symbol() { @@ -634,7 +630,7 @@ pub fn to_number_full(val: JsValue, host: &mut H) -> Result(val: JsValue, host: &mut H) -> Result { let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; Ok(to_string(primitive)) diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 6400db9..cb1c5a6 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -15,8 +15,6 @@ use walkdir::WalkDir; mod test262_log; use oxide_log::{Level, LogConfig, Output, SUBSYSTEM_COUNT}; -// Thread-local that records the path currently being executed. -// Written before every test; read by the panic hook to identify the crash file. // 记录当前正在执行的测试路径(thread-local);每个测试执行前写入, // panic hook 据此定位崩溃所在的测试文件。 std::thread_local! { @@ -132,8 +130,8 @@ struct RunStats { } impl RunStats { - /// Fold another worker's partial stats into this one. Used to reduce - /// per-worker results back into a single total after parallel execution. + /// 把另一个 worker 的部分统计并入本对象。用于并行执行后把各 worker 的 + /// 结果合并回单一总计。 fn merge(&mut self, other: RunStats) { self.pass += other.pass; self.fail += other.fail; @@ -144,7 +142,7 @@ impl RunStats { } } - /// Record a single test result into the running totals. + /// 把单个测试结果记入运行累计。 fn record(&mut self, result: &TestResult) { match &result.outcome { TestOutcome::Pass(_) => self.pass += 1, @@ -168,7 +166,7 @@ struct RunConfig { supervise: bool, leak_check: bool, leak_check_interval: usize, - /// D-18:关闭 liveness/精确 DCE/RegAlloc 链(on/off 对比基础设施)。 + /// 关闭 liveness/精确 DCE/RegAlloc 链(on/off 对比基础设施)。 no_regalloc: bool, /// 逐测试打印 PASS/FAIL/SKIP(on/off 结果集合对比用)。 verbose: bool, @@ -394,15 +392,14 @@ fn is_skipped(meta: &TestMeta) -> Option { "module" => return Some("module tests excluded".into()), "async" => return Some("async tests excluded".into()), "raw" => return Some("raw tests excluded".into()), - // Let noStrict tests run — many pass despite strict mode; runtime skip catches failures + // noStrict 测试放行——很多在严格模式下仍可通过;运行时跳过逻辑会捕获失败。 _ => {} } } - // Keep broad implemented feature tags runnable; exclude only unsupported subfeatures. - // Only exclude features that are genuinely NOT implemented at all. - // Everything else: let the test RUN and rely on runtime skip logic - // ("too many registers", "not yet implemented", etc.) for failures. + // 保持大范围已实现 feature tag 可运行;只排除真正未实现的子特性。 + // 其余一切让测试实际运行,依赖运行时跳过逻辑 + // ("too many registers"、"not yet implemented" 等)判定失败。 let excluded_features = [ "Proxy", "BigInt", @@ -423,8 +420,8 @@ fn is_skipped(meta: &TestMeta) -> Option { } } - // Only skip if BOTH description AND features indicate generators/async - // (many tests with "async" in description test non-async functionality) + // 仅当 description 与 features 都表明 generator/async 时才跳过 + // (许多 description 含 "async" 的测试测的是非 async 功能)。 if meta.description.contains("generator") && meta.features.iter().any(|f| f.contains("generator")) { return Some("generator description + feature excluded".into()); } @@ -582,7 +579,7 @@ fn run_test_inner( || e.contains("cannot assign to read-only property") || e.contains("cannot delete non-configurable property") || e.contains("private field") - // class private fields not implemented + // 类私有字段未实现。 { if no_skip { return TestResult::fail(path.to_path_buf(), dur, format!("vm error: {e}")); @@ -684,10 +681,10 @@ fn run_chunked(args: &[String], skip_until: usize, end_index: usize, chunk_size: aggregate_fail == 0 } -/// One heartbeat record written by a supervised child and polled by the parent. -/// `index` is the global test index the child is about to run (`START`) or the -/// window end it finished (`DONE`); the tallies always cover tests completed -/// *before* `index`, so the in-flight test is never counted yet. +/// 监督模式下子进程写入、父进程轮询的一条心跳记录。 +/// `index` 是子进程即将运行的全局测试下标(`START`)或其完成窗口的 +/// 结束下标(`DONE`);计数始终覆盖 `index` *之前* 已完成的测试, +/// 因此进行中的测试不会被计入。 struct Heartbeat { phase: String, index: usize, @@ -696,16 +693,15 @@ struct Heartbeat { skip: usize, } -/// Overwrite the heartbeat file with a single line. Errors are ignored: a missed -/// heartbeat just delays stall detection by one poll interval. Assumes a single -/// worker (the supervisor always forces `OXIDE_TEST262_WORKERS=1`); with more -/// than one worker the running index is ambiguous and the file races. +/// 用单行内容覆写心跳文件。错误被忽略:漏写心跳只是把停滞检测推迟一个 +/// 轮询间隔。假定单 worker(监督器强制 `OXIDE_TEST262_WORKERS=1`); +/// 多 worker 时运行下标有歧义且文件存在竞争。 fn write_heartbeat(path: &Path, phase: &str, index: usize, pass: usize, fail: usize, skip: usize) { let _ = std::fs::write(path, format!("{phase} {index} {pass} {fail} {skip}\n")); } -/// Read the latest heartbeat. Returns `None` on any missing/partial/malformed -/// content so the poll loop can simply retry on the next tick. +/// 读取最新心跳。任何缺失/残缺/畸形内容均返回 `None`,使轮询循环可直接 +/// 在下一拍重试。 fn read_heartbeat(path: &Path) -> Option { let content = std::fs::read_to_string(path).ok()?; let line = content.lines().next()?; @@ -718,15 +714,13 @@ fn read_heartbeat(path: &Path) -> Option { Some(Heartbeat { phase, index, pass, fail, skip }) } -/// Run one window `[wstart, wend)` to completion under supervision, returning -/// `(pass, fail, skip)` aggregated across however many child restarts it took. +/// 在监督下运行一个窗口 `[wstart, wend)`,返回经过多次子进程重启 +/// 聚合的 `(pass, fail, skip)`。 /// -/// A single-worker child runs the normal in-process path (warm kernel + harness -/// prefix cache) and emits a heartbeat before each test. If the running index -/// stalls past `timeout`, the child is killed, the culprit is reported by path, -/// and a fresh child resumes from `culprit + 1`. A child that crashes mid-test -/// is recovered through the same path. Timeouts/crashes count as skip by default -/// and fail under `--no-skip`. +/// 单 worker 子进程运行常规 in-process 路径(预热 kernel + harness 前缀缓存) +/// 并在每个测试前发出心跳。若运行下标停滞超过 `timeout`,子进程被杀死、 +/// 按路径报告肇事者,并由全新子进程从 `culprit + 1` 续跑。子进程在测试中途 +/// 崩溃也经同路径恢复。超时/崩溃默认计为 skip,`--no-skip` 下计为失败。 #[expect(clippy::too_many_arguments)] fn supervise_window( exe: &Path, args: &[String], no_skip: bool, wstart: usize, wend: usize, timeout: Duration, startup_grace: Duration, @@ -872,10 +866,9 @@ fn supervise_window( (pass, fail, skip) } -/// Orchestrate a supervised full run: split `[skip_until, end_index)` into -/// windows and run up to `supervisors` of them concurrently. The supervisor -/// threads only spawn/poll/kill child processes and touch files — they never -/// hold a `KernelCore`, so sharing `paths`/`args` by reference is safe. +/// 编排一次监督式全量运行:把 `[skip_until, end_index)` 切分为窗口,至多 +/// `supervisors` 个窗口并发。监督线程只派生/轮询/杀死子进程并读写文件—— +/// 从不持有 `KernelCore`,因此按引用共享 `paths`/`args` 是安全的。 fn run_supervised(args: &[String], skip_until: usize, end_index: usize, no_skip: bool, paths: &[PathBuf]) -> bool { let exe = match std::env::current_exe() { Ok(path) => path, @@ -990,10 +983,9 @@ fn run_supervised(args: &[String], skip_until: usize, end_index: usize, no_skip: fail == 0 } -/// Per-test pipeline shared by the serial and parallel execution paths: -/// read the file, parse metadata, apply skip filters, then run. Returns -/// exactly one `TestResult`. Worker-owned state (`kernel`, `harness_sources`, -/// `harness_cache`) never crosses a thread boundary. +/// 串行与并行执行路径共享的每测试管线: +/// 读文件、解析元数据、应用跳过过滤,然后运行。恰好返回一个 `TestResult`。 +/// worker 自有状态(`kernel`、`harness_sources`、`harness_cache`)永不跨线程。 fn process_path( path: &Path, filter: &Option, no_skip: bool, no_regalloc: bool, kernel: &Arc, harness_sources: &HarnessSources, harness_cache: &Arc>, @@ -1032,21 +1024,19 @@ fn process_path( run_test(path, &source, &meta, kernel, harness_sources, harness_cache, no_skip, no_regalloc) } -/// Build a runner kernel with a bounded step limit. Each parallel worker owns -/// its own kernel because `KernelCore` + session state is `!Send` (it holds -/// `P` = `Arc`, and `JsObject` stores raw `*mut u8` property -/// pointers). Nothing kernel-shaped can cross a thread boundary, so sharing is -/// impossible; per-worker construction is the only correct design. +/// 构建带步数上限的 runner kernel。每个并行 worker 拥有自己的 kernel, +/// 因为 `KernelCore` + session 状态是 `!Send`(持有 `P` = +/// `Arc`,而 `JsObject` 存有裸 `*mut u8` 属性指针)。任何 kernel +/// 形态的对象都不能跨线程边界,因此共享不可能;每 worker 自建是唯一正确设计。 fn build_runner_kernel() -> Arc { - // Bound each test's execution so a single infinite-loop / unsupported-feature - // loop fails (or skips) instead of stalling the run. The VM emits a - // "VM step limit exceeded" error on overrun, which the runner classifies as a - // step-limit result (skip by default, fail under --no-skip). Override only the - // runner's local config; KernelConfig::minimal() stays unbounded for other crates. + // 约束每个测试的执行步数,使单个死循环 / 未支持特性循环失败(或跳过) + // 而非拖垮整个运行。VM 超限时抛 "VM step limit exceeded" 错误, + // runner 将其归类为 step-limit 结果(默认 skip,--no-skip 下 fail)。 + // 只覆盖 runner 本地配置;KernelConfig::minimal() 对其它 crate 保持无界。 let mut kernel_config = KernelConfig::minimal(); kernel_config.max_steps = Some(50_000_000); - // Keep test262 recursion tests from reaching Rust's native stack before the - // VM converts deep JS calls into a catchable RangeError. + // 防止 test262 递归测试在 VM 把深层 JS 调用转成可捕获的 RangeError 之前 + // 触及 Rust 原生栈。 kernel_config.max_call_depth = 256; kernel_config.min_pool_size = 1; kernel_config.max_pool_size = Some(1); @@ -1108,10 +1098,9 @@ fn categorize_fail(msg: &str) -> String { /// 程序入口:安装带当前测试路径的 panic hook,并在大栈线程上运行测试。 fn main() { - // Install a panic hook that prints which test was running when the panic occurred. - // This covers Rust panics; OS-level crashes (ACCESS_VIOLATION) are caught by the - // pre-test eprintln! below — the last line printed before a hard crash identifies - // the file. + // 安装 panic hook,打印崩溃发生时正在运行的测试。 + // 覆盖 Rust panic;OS 级崩溃(ACCESS_VIOLATION)由测试前的 eprintln! 捕获—— + // 硬崩溃前打印的最后一行即标识文件。 std::panic::set_hook(Box::new(|info| { let path = CURRENT_TEST_PATH.with(|p| p.borrow().clone()); if !path.is_empty() { @@ -1191,11 +1180,11 @@ fn run_tests() -> bool { let total = paths.len(); - // Determine worker count. `KernelCore` + session state is `!Send` (it holds - // one kernel via Arc across threads; instead each worker builds and owns its - // own kernel + harness-source registry + prefix cache. Only `PathBuf` and - // `TestResult` (both `Send`) cross thread boundaries. Workers pull test - // indices from a shared atomic cursor for dynamic load balancing. + // 确定 worker 数。`KernelCore` + session 状态是 `!Send`(它持有 + // 经 Arc 共享的一个 kernel;相反每个 worker 构建并拥有自己的 + // kernel + harness 源注册表 + 前缀缓存。只有 `PathBuf` 和 + // `TestResult`(均 `Send`)跨线程。worker 从共享原子游标取测试下标, + // 实现动态负载均衡。 let default_workers = if config.no_skip { 4 } else { @@ -1261,17 +1250,16 @@ fn run_tests() -> bool { let heartbeat_ref = &heartbeat_path; let harness_cache = Arc::new(RwLock::new(HarnessPrefixCache::new())); - // Keep memory flat: workers return only aggregate stats. Retaining tens of - // thousands of `TestResult`s makes `--no-skip` runs accumulate path/error - // strings until the process OOMs near the end of the suite. + // 保持内存平稳:worker 只返回聚合统计。保留数万个 `TestResult` 会使 + // `--no-skip` 运行在套件末尾附近累积 path/error 字符串直至进程 OOM。 let partials: Vec = std::thread::scope(|scope| { let handles: Vec<_> = (0..workers) .map(|_| { let cursor = &cursor; let progress = &progress; let harness_cache = Arc::clone(&harness_cache); - // Match main()'s 16MB stack: the VM recurses on deeply nested - // test programs and would overflow the default worker stack. + // 与 main() 的 16MB 栈一致:VM 在深层嵌套测试程序上递归, + // 默认 worker 栈会溢出。 std::thread::Builder::new() .stack_size(16 * 1024 * 1024) .spawn_scoped(scope, move || { @@ -1287,9 +1275,9 @@ fn run_tests() -> bool { break; } let path_str = paths_ref[i].display().to_string(); - // Always record the current test path so the panic hook can - // identify Rust panics. Set OXIDE_TEST262_RUNNING_LOG=1 when - // diagnosing OS-level crashes that need the last stderr line. + // 始终记录当前测试路径,使 panic hook 能标识 Rust panic。 + // 诊断需要最后一行 stderr 的 OS 级崩溃时设置 + // OXIDE_TEST262_RUNNING_LOG=1。 CURRENT_TEST_PATH.with(|p| *p.borrow_mut() = path_str.clone()); if log_running_tests { test262_debug!("running: {}", path_str); @@ -1300,7 +1288,13 @@ fn run_tests() -> bool { } let result = process_path( - &paths_ref[i], filter, no_skip, no_regalloc, &kernel, harness_sources, &harness_cache, + &paths_ref[i], + filter, + no_skip, + no_regalloc, + &kernel, + harness_sources, + &harness_cache, ); stats.record(&result); if verbose { @@ -1339,7 +1333,7 @@ fn run_tests() -> bool { .collect() }); - // Reduce partial stats without materializing every test result in memory. + // 归约各 worker 的部分统计,不物化每个测试结果到内存。 let mut stats = RunStats::default(); for partial_stats in partials { stats.merge(partial_stats); diff --git a/crates/oxide_types/src/mem.rs b/crates/oxide_types/src/mem.rs index f605dc5..7092310 100644 --- a/crates/oxide_types/src/mem.rs +++ b/crates/oxide_types/src/mem.rs @@ -9,11 +9,10 @@ use std::fmt; use std::ops::Deref; use std::sync::Arc; -/// Reference-counted persistent pointer. +/// 引用计数持久指针。 /// -/// Wraps `Arc` for cross-epoch object storage. `Clone` increments the -/// reference count; `Drop` decrements it. Objects wrapped in `P` survive -/// `Epoch::reset()` — they live on the global heap. +/// 包装 `Arc` 用于跨 epoch 对象存储。`Clone` 递增引用计数,`Drop` 递减; +/// `P` 包裹的对象在 `Epoch::reset()` 后仍存活——它们位于全局堆上。 #[repr(transparent)] pub struct P(Arc); @@ -61,11 +60,10 @@ impl fmt::Display for P { } } -/// Persistent heap for cross-epoch object storage. +/// 跨 epoch 对象存储的持久堆。 /// -/// Provides a minimal API: `promote(value)` moves `value` to the -/// global heap under `Arc` and returns `P`. Typed storage for shapes, -/// code, IC templates, and strings lives in the OxideKernel. +/// 提供最小 API:`promote(value)` 把 `value` 以 `Arc` 移到全局堆并返回 `P`。 +/// shape、code、IC 模板与字符串的类型化存储位于 OxideKernel。 pub struct PersistentHeap; impl PersistentHeap { @@ -74,9 +72,8 @@ impl PersistentHeap { Self } - /// Move a value to the persistent heap and return a reference-counted pointer. - /// The returned `P` survives epoch resets — it's stored on the global heap - /// under `Arc`. + /// 把 `value` 移到持久堆并返回引用计数指针。 + /// 返回的 `P` 跨 epoch 重置存活——它以 `Arc` 存在全局堆上。 pub fn promote(&self, value: T) -> P { P::new(value) } @@ -88,9 +85,8 @@ impl Default for PersistentHeap { } } /// -/// Wraps `bumpalo::Bump` with an epoch ID counter for dangling pointer detection. -/// All Agent-call objects are allocated here; `reset()` clears them in O(1) -/// at the end of each call. +/// 包装 `bumpalo::Bump` 并携带 epoch ID 计数器用于悬挂指针检测。 +/// 所有 Agent 调用级对象分配于此;`reset()` 在每次调用结束时 O(1) 清空。 pub struct Epoch { bump: bumpalo::Bump, epoch_id: u64, @@ -105,13 +101,12 @@ impl Epoch { } } - /// Bump-allocate a value and return a raw pointer to it. + /// 在 arena 中 bump 分配一个值并返回裸指针。 /// /// # Safety /// - /// The returned pointer is valid until `reset()` is called on this epoch. - /// Callers must not store it beyond that boundary, and must ensure no - /// aliases are used after the arena is reset. + /// 返回的指针在本 epoch 的 `reset()` 调用之前有效。调用方不得在 + /// 该边界之后继续持有,并须确保 arena 重置后不再使用任何别名。 pub fn alloc(&self, value: T) -> *mut T { self.bump.alloc(value) } @@ -123,8 +118,8 @@ impl Epoch { &self.bump } - /// Bump-allocate a value using a closure for initialization. - /// Better for compiler optimization — construct directly on arena. + /// 用初始化闭包在 arena 上直接构造值。 + /// 对编译器优化更友好——直接在 arena 上构造。 pub fn alloc_with(&self, f: F) -> *mut T where F: FnOnce() -> T, @@ -132,17 +127,17 @@ impl Epoch { self.bump.alloc_with(f) } - /// Return true when `ptr` lies inside one of the currently allocated bump chunks. + /// 当 `ptr` 位于当前已分配 bump chunk 之一时返回 true。 /// - /// This only compares addresses and does not dereference `ptr`. + /// 仅比较地址,不解引用 `ptr`。 #[inline] pub fn is_epoch_ptr(&self, ptr: *const u8) -> bool { let addr = ptr as usize; if addr == 0 { return false; } - // SAFETY: this helper performs no allocations while walking the raw chunk - // iterator, so bumpalo's chunk list cannot change during iteration. + // SAFETY: 本辅助函数在遍历原始 chunk 迭代器时不执行任何分配, + // 因此 bumpalo 的 chunk 列表在迭代期间不会变化。 unsafe { self.bump.iter_allocated_chunks_raw().any(|(base, len)| { let start = base as usize; @@ -152,15 +147,15 @@ impl Epoch { } } - /// O(1) mass deallocation. All previous allocations become invalid. - /// Increments epoch ID to invalidate stale pointers (debug_assert guard). + /// O(1) 批量释放。此前所有分配立即失效。 + /// 递增 epoch ID 以使过期指针失效(debug_assert 守卫)。 pub fn reset(&mut self) { self.bump.reset(); self.epoch_id += 1; } - /// Current epoch ID. Arena-allocated objects store this value; - /// dereference checks they match the current ID (debug_assert only). + /// 当前 epoch ID。arena 分配的对象存储此值; + /// 解引用时校验与当前 ID 一致(仅 debug_assert)。 pub fn current_id(&self) -> u64 { self.epoch_id } diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 1a6bf85..b656e01 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -8,10 +8,9 @@ use crate::value::JsValue; -/// Heap-allocated JS string value. +/// 堆分配的 JS 字符串值。 /// -/// String *values* are NaN-boxed as 48-bit pointers to a `JsString` (see -/// `JsValue::string`). +/// 字符串*值*以 48 位指针(指向 `JsString`)NaN-box(见 `JsValue::string`)。 #[derive(Debug)] pub struct JsString { pub data: String, @@ -39,42 +38,41 @@ impl JsString { } } -/// Type-safe opaque wrapper around a native function pointer. +/// 原生函数指针的类型安全不透明包装。 /// -/// Stored as `*const ()` rather than a concrete `fn` type so that `oxide_types` does not -/// need to depend on `oxide_vm::Vm`. Callers in `oxide_vm` cast back to `NativeFn` via -/// `NativeFnPtr::call_with` — transmute is confined to a single generic helper there. +/// 以 `*const ()` 存储而非具体 `fn` 类型,使 `oxide_types` 无需依赖 +/// `oxide_vm::Vm`。`oxide_vm` 中的调用方经 `NativeFnPtr::call_with` 转回 +/// `NativeFn`——transmute 被限制在单个泛型辅助函数中。 /// -/// # Safety invariants +/// # Safety 不变量 /// -/// A `NativeFnPtr` value must always have been created from a valid `NativeFn` function -/// pointer (a bare `fn` item or function-item coercion — **not** a closure). The pointer is -/// never null. `Send + Sync` are safe because function-item pointers are inherently -/// thread-safe (they contain no data). +/// `NativeFnPtr` 必须总是由合法的 `NativeFn` 函数指针(裸 `fn` 项或函数项 +/// 强制转换——**不是**闭包)创建。指针永不为空。`Send + Sync` 安全是因为 +/// 函数项指针天然线程安全(不含数据)。 #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct NativeFnPtr(pub *const ()); impl NativeFnPtr { - /// Wrap a raw function pointer. The pointer must point to a valid `NativeFn` fn-item. + /// 包装裸函数指针。指针必须指向合法的 `NativeFn` 函数项。 /// /// # Safety - /// `ptr` must be a non-null function pointer of type `fn(&mut Vm, &[u8]) -> NativeResult` - /// cast to `*const ()`. Using any other pointer value is UB at call time. + /// `ptr` 必须是类型为 `fn(&mut Vm, &[u8]) -> NativeResult` 的非空函数指针 + /// 转成的 `*const ()`。使用任何其它指针值在调用时是 UB。 #[inline(always)] pub unsafe fn from_raw(ptr: *const ()) -> Self { debug_assert!(!ptr.is_null(), "NativeFnPtr must not be null"); Self(ptr) } - /// Return the underlying raw pointer. + /// 返回底层裸指针。 #[inline(always)] pub fn as_ptr(self) -> *const () { self.0 } } -// SAFETY: fn-item pointers contain no mutable state; safe to share across threads. +// SAFETY: 函数项指针不含可变状态,可安全跨线程共享。 unsafe impl Send for NativeFnPtr {} unsafe impl Sync for NativeFnPtr {} @@ -119,9 +117,9 @@ impl TypedArrayKind { pub struct PropAttributes(pub u8); impl PropAttributes { - /// writable 标志位(`0b001`)。 + /// writable 标志位(bit0)。 pub const WRITABLE: u8 = 0b001; - /// enumerable 标志位(`0b010`)。 + /// enumerable 标志位(bit1)。 pub const ENUMERABLE: u8 = 0b010; /// configurable 标志位(`0b100`)。 pub const CONFIGURABLE: u8 = 0b100; @@ -232,8 +230,8 @@ impl PropIndex for i32 { } } -/// Layout: -/// header: u32 bits +/// 布局: +/// header: u32 位 /// \[0:23\] shape_id /// \[24\] is_set /// \[25\] is_map @@ -243,25 +241,25 @@ impl PropIndex for i32 { /// \[29\] is_array /// \[30\] is_extensible /// \[31\] is_function -/// native_arg_count: u8 (1 byte) -/// type_tag: u8 — OBJ_TYPE_* constant identifying wrapper/exotic object kind (1 byte) -/// is_session_epoch: u8 (1 byte) +/// native_arg_count: u8 (1 字节) +/// type_tag: u8 — 标识包装/外来对象种类的 OBJ_TYPE_* 常量 (1 字节) +/// is_session_epoch: u8 (1 字节) /// _pad: u8 -/// hash_props: *mut u8 (8 bytes, points to Box\\>) -/// prop_meta: *mut u8 (8 bytes, points to Box\\>\>) -/// native_data: *mut u8 (8 bytes, opaque VM-owned native/exotic payload) -/// proto: JsValue (8 bytes) -/// generation: u32 (4 bytes + 4 pad) -/// native_fn: Option\ (16 bytes — Option\ optimization NOT available for -/// raw *const (); stored as Option wrapping an 8-byte pointer, with 8 bytes of -/// discriminant padding due to repr(Rust) layout rules) -/// sub_module_index: u32 (4 bytes + 4 pad, index into CompiledModule.sub_modules) -/// captured_this: JsValue (8 bytes, lexical this for arrow functions) -/// home_object: JsValue (8 bytes, \[\[HomeObject\]\] for super lookup) -/// upvalues: *mut u8 (8 bytes, points to Box> for closures) +/// hash_props: *mut u8 (8 字节,指向 Box\\>) +/// prop_meta: *mut u8 (8 字节,指向 Box\\>\>) +/// native_data: *mut u8 (8 字节,VM 拥有的不透明 native/外来载荷) +/// proto: JsValue (8 字节) +/// generation: u32 (4 字节 + 4 填充) +/// native_fn: Option\ (16 字节 — 裸 `*const ()` 无法利用 Option\ +/// 优化;因 repr(Rust) 布局规则存为包装 8 字节指针的 Option,带 8 字节 +/// 判别式填充) +/// sub_module_index: u32 (4 字节 + 4 填充,索引 CompiledModule.sub_modules) +/// captured_this: JsValue (8 字节,箭头函数的词法 this) +/// home_object: JsValue (8 字节,\[\[HomeObject\]\],供 super 查找) +/// upvalues: *mut u8 (8 字节,指向闭包的 Box>) /// -/// Total: 112 bytes -/// Alignment: 8 bytes +/// 总计:112 字节 +/// 对齐:8 字节 #[repr(C)] pub struct Cell { pub value: JsValue, @@ -624,20 +622,20 @@ impl JsObject { self.header = (self.header & !0x00FF_FFFF) | (id & 0x00FF_FFFF); } - /// Returns property count from the hash_props vec length. - /// Returns 0 if hash_props has not been allocated. + /// 返回 hash_props vec 的长度作为属性数。 + /// hash_props 未分配时返回 0。 pub fn prop_count(&self) -> u32 { if self.hash_props.is_null() { 0 } else { - // SAFETY: hash_props is either null or was created from Box> - // in ensure_hash_props/new_array and remains owned by this object. + // SAFETY: hash_props 要么为空,要么在 ensure_hash_props/new_array 中 + // 由 Box> 创建并归本对象所有。 let vec = unsafe { &*(self.hash_props as *const Vec) }; vec.len() as u32 } } - /// Sets the length of hash_props vec. Truncates or extends with undefined. + /// 设置 hash_props vec 的长度。截断或补 undefined 扩展。 pub fn set_prop_count(&mut self, count: impl PropIndex) { let target = count.to_u32() as usize; { @@ -666,14 +664,13 @@ impl JsObject { !self.prop_meta.is_null() } - /// Like `set_prop_count` but assumes `hash_props` is already allocated. - /// Caller must guarantee the object has at least one element (or was - /// created via `new_array`). Used in hot array builtins to skip the - /// redundant `ensure_hash_props` null check on each mutation. + /// 同 `set_prop_count`,但假定 `hash_props` 已分配。 + /// 调用方须保证对象至少含一个元素(或经 `new_array` 创建)。 + /// 用于热路径数组 builtin,跳过每次变更时冗余的 `ensure_hash_props` 空检查。 #[inline] pub fn set_prop_count_fast(&mut self, count: impl PropIndex) { let target = count.to_u32() as usize; - // SAFETY: caller guarantees hash_props is non-null. + // SAFETY: 调用方保证 hash_props 非空。 let vec = unsafe { &mut *(self.hash_props as *mut Vec) }; if target < vec.len() { vec.truncate(target); @@ -702,8 +699,8 @@ impl JsObject { let vec = Box::new(vec![None::; len]); self.prop_meta = Box::into_raw(vec) as *mut u8; } - // SAFETY: prop_meta was set from Box>> in - // ensure_prop_meta and remains valid while this object owns it. + // SAFETY: prop_meta 在 ensure_prop_meta 中由 + // Box>> 创建,本对象持有期间始终有效。 unsafe { &mut *(self.prop_meta as *mut Vec>) } } @@ -712,8 +709,8 @@ impl JsObject { if self.prop_meta.is_null() { None } else { - // SAFETY: prop_meta was set from Box>> in - // ensure_prop_meta and remains valid while this object owns it. + // SAFETY: prop_meta 在 ensure_prop_meta 中由 + // Box>> 创建,本对象持有期间始终有效。 unsafe { Some(&*(self.prop_meta as *const Vec>)) } } } @@ -722,8 +719,8 @@ impl JsObject { if self.prop_meta.is_null() { None } else { - // SAFETY: prop_meta was set from Box>> in - // ensure_prop_meta and remains valid while this object owns it. + // SAFETY: prop_meta 在 ensure_prop_meta 中由 + // Box>> 创建,本对象持有期间始终有效。 unsafe { Some(&mut *(self.prop_meta as *mut Vec>)) } } } @@ -853,22 +850,22 @@ impl JsObject { } } - /// Initialize hash_props if null, return mutable reference to Vec. + /// 若 hash_props 为空则初始化,返回其可变引用。 pub fn ensure_hash_props(&mut self) -> &mut Vec { if self.hash_props.is_null() { let vec = Box::new(Vec::::new()); self.hash_props = Box::into_raw(vec) as *mut u8; } - // SAFETY: hash_props was set from Box> in this method or new_array. + // SAFETY: hash_props 在本方法或 new_array 中由 Box> 创建。 unsafe { &mut *(self.hash_props as *mut Vec) } } - /// Safe read access to hash_props vec. Returns None if not allocated. + /// hash_props vec 的安全只读访问;未分配时返回 None。 pub fn hash_props_vec(&self) -> Option<&Vec> { if self.hash_props.is_null() { None } else { - // SAFETY: hash_props was set from Box> in ensure_hash_props/new_array. + // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 unsafe { Some(&*(self.hash_props as *const Vec)) } } } @@ -877,23 +874,23 @@ impl JsObject { if self.hash_props.is_null() { None } else { - // SAFETY: hash_props was set from Box> in ensure_hash_props/new_array. + // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 unsafe { Some(&mut *(self.hash_props as *mut Vec)) } } } - /// Get property value at position index in the vec. - /// Returns JsValue::undefined() if hash_props not allocated or position out of bounds. + /// 取下标 position 处的属性值。 + /// hash_props 未分配或越界时返回 JsValue::undefined()。 pub fn get_prop_at(&self, position: impl PropIndex) -> JsValue { if self.hash_props.is_null() { return JsValue::undefined(); } - // SAFETY: hash_props was set from Box> in ensure_hash_props/new_array. + // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 let vec = unsafe { &*(self.hash_props as *const Vec) }; vec.get(position.to_u32() as usize).copied().unwrap_or(JsValue::undefined()) } - /// Set property value at position index. Vec auto-grows if needed. + /// 设置下标 position 处的属性值。vec 按需自动扩容。 pub fn set_prop_at(&mut self, position: impl PropIndex, val: JsValue) { let pos = position.to_u32() as usize; if pos > MAX_DENSE_PROPS { @@ -917,7 +914,7 @@ impl JsObject { } } - /// Push a value onto hash_props vec. Returns the index position. + /// 把值压入 hash_props vec,返回其下标。 pub fn push_prop(&mut self, val: JsValue) -> u32 { let vec = self.ensure_hash_props(); let pos = vec.len(); @@ -928,12 +925,12 @@ impl JsObject { pos as u32 } - /// Get the length of hash_props vec (returns 0 if not allocated). + /// 返回 hash_props vec 的长度(未分配时返回 0)。 pub fn prop_vec_len(&self) -> usize { if self.hash_props.is_null() { 0 } else { - // SAFETY: hash_props was set from Box> in ensure_hash_props/new_array. + // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 unsafe { &*(self.hash_props as *const Vec) }.len() } } @@ -964,7 +961,7 @@ impl JsObject { return Err("cyclic __proto__ value"); } debug_assert!(!cursor_ptr.is_null(), "prototype cursor pointer must not be null"); - // SAFETY: cursor is known to be an object JsValue, so it encodes a valid JsObject pointer. + // SAFETY: cursor 已知为对象 JsValue,编码的是合法 JsObject 指针。 let obj = unsafe { &*cursor_ptr }; cursor = obj.proto; } @@ -1013,8 +1010,8 @@ impl JsObject { self.sub_module_index = idx; } - /// Arrow function flag (header bit 28). - /// When true, CALL dispatch captures lexical `this` from creation time. + /// 箭头函数标志(header bit 28)。 + /// 为 true 时,CALL 分发捕获创建时的词法 `this`。 pub fn is_arrow(&self) -> bool { (self.header >> 28) & 1 != 0 } @@ -1028,8 +1025,8 @@ impl JsObject { } } - /// Lexical `this` captured at arrow function creation time. - /// Only meaningful when `is_arrow()` returns true. + /// 箭头函数创建时捕获的词法 `this`。 + /// 仅在 `is_arrow()` 返回 true 时有意义。 pub fn captured_this(&self) -> JsValue { self.captured_this } @@ -1039,8 +1036,8 @@ impl JsObject { self.captured_this = v; } - /// Class constructor flag (header bit 27). - /// Ordinary CALL rejects objects with this flag; NEW_EXPRESSION is allowed. + /// 类构造函数标志(header bit 27)。 + /// 普通 CALL 拒绝带此标志的对象;NEW_EXPRESSION 允许。 pub fn is_class_constructor(&self) -> bool { (self.header >> 27) & 1 != 0 } diff --git a/crates/oxide_types/src/value.rs b/crates/oxide_types/src/value.rs index aca605e..14786e6 100644 --- a/crates/oxide_types/src/value.rs +++ b/crates/oxide_types/src/value.rs @@ -9,16 +9,16 @@ use std::fmt; use crate::object::{JsObject, JsString}; -/// Quiet NaN prefix — bits 63-51 = sign(1) + exponent(0x7FF) + quiet_bit(1) +/// 静默 NaN 前缀——bits 63-51 = sign(1) + exponent(0x7FF) + quiet_bit(1) const QNAN_PREFIX: u64 = 0xFFF8_0000_0000_0000; -/// NaN exponent mask — bits 62-52 +/// NaN 指数掩码——bits 62-52 const EXP_MASK: u64 = 0x7FF0_0000_0000_0000; -/// Mantissa mask — bits 51-0 +/// 尾数掩码——bits 51-0 const MANTISSA_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; -/// Tag sits in bits 50-48 of mantissa +/// tag 位于尾数的 bits 50-48 const TAG_MASK: u64 = 0x0007_0000_0000_0000; const TAG_SHIFT: u64 = 48; const TAG_INT: u64 = 0; @@ -32,7 +32,7 @@ const TAG_SYMBOL: u64 = 6; /// 48-bit pointer mask (x86-64 canonical VA) pub const PTR_MASK: u64 = 0x0000_FFFF_FFFF_FFFF; -/// 32-bit integer payload mask +/// 32 位整数载荷掩码 const INT_MASK: u64 = 0x0000_0000_FFFF_FFFF; fn make_tag(tag: u64) -> u64 { @@ -93,7 +93,7 @@ impl JsValue { Self(bits) } - /// Raw NaN-boxed bits. For checked pointer extraction after is_object(). + /// 原始 NaN-boxed 位模式。用于在 `is_object()` 之后做受检的指针提取。 #[inline(always)] pub fn to_bits(self) -> u64 { self.0 @@ -150,9 +150,9 @@ impl JsValue { Self(make_tag(TAG_STRING) | addr) } - /// Semantic alias for permanent (kernel-owned, never-collected) strings. - /// Encoding is identical to `string`; the perm-vs-session distinction is by - /// ownership (membership in the GC root set), not by the NaN-box bits. + /// 永久字符串(内核持有、永不回收)的语义别名。 + /// 编码与 `string` 完全相同;perm 与 session 的区分在所有权(是否属于 + /// GC 根集),而非 NaN-box 位。 pub fn perm_string(ptr: *const JsString) -> Self { Self::string(ptr) } @@ -485,8 +485,8 @@ mod tests { let b = Box::new(JsString::new("x".to_string())); let va = JsValue::string(&*a); let vb = JsValue::string(&*b); - // Distinct allocations with identical content are NOT == (pointer identity). - // Semantic content equality is handled in coercion, not PartialEq. + // 内容相同但分配不同 → NOT ==(指针同一性)。 + // 语义内容相等在 coercion 层处理,不在 PartialEq。 assert_ne!(va, vb); assert_eq!(va, JsValue::string(&*a)); assert_eq!(unsafe { (*va.as_string_ptr()).as_str() }, unsafe { (*vb.as_string_ptr()).as_str() }); diff --git a/crates/oxide_vm/src/bindings/bind_array.rs b/crates/oxide_vm/src/bindings/bind_array.rs index 1a58cb9..7316c53 100644 --- a/crates/oxide_vm/src/bindings/bind_array.rs +++ b/crates/oxide_vm/src/bindings/bind_array.rs @@ -56,7 +56,7 @@ pub fn bind_array(core: &Arc, session: &KernelSession, global: &mut core, &[("toString", oxide_builtins::array::array_to_string:: as *const (), 0)], ); - // Built-in prototype methods are non-enumerable (otherwise they leak into for-in). + // 内置原型方法不可枚举(否则会泄漏进 for-in)。 let si = core.perm_interner().intern("toString").0; if let Some(pos) = core.shape_forge().lookup_position(array_proto.shape_id(), si) { array_proto.set_data_meta(pos, PropAttributes::new(true, false, true)); diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index 8d5269f..3ac1552 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -16,7 +16,7 @@ fn bind_error_subtype_constructor( let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto_ptr))); ctor.set_function(true); - // SAFETY: ctor_fn is a NativeFn fn-item pointer cast to *const () by callers. + // SAFETY: ctor_fn 是调用方转成 *const () 的 NativeFn 函数项指针。 ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(ctor_fn) })); ctor.set_native_arg_count(1); @@ -121,7 +121,7 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut { let err_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject; let err_ctor = unsafe { &mut *err_ctor_ptr }; - // SAFETY: error_constructor is a NativeFn fn-item. + // SAFETY: error_constructor 是 NativeFn 函数项。 err_ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(oxide_builtins::error::error_constructor:: as *const ()) })); diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 725c78f..1c4035f 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -53,7 +53,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut ("valueOf", oxide_builtins::object::object_proto_value_of:: as *const (), 0), ], ); - // Built-in prototype methods are non-enumerable (otherwise they leak into for-in). + // 内置原型方法不可枚举(否则会泄漏进 for-in)。 for name in ["toString", "valueOf"] { let si = core.perm_interner().intern(name).0; if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) { diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index ddffccf..1171667 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -83,7 +83,7 @@ macro_rules! bind_constructor { } pub(crate) fn configure_native_constructor(ctor: &mut JsObject, native_fn: *const (), arg_count: u8) { - // SAFETY: native_fn is always a valid NativeFn fn-item pointer cast to *const () by callers. + // SAFETY: native_fn 始终是调用方转成 *const () 的合法 NativeFn 函数项指针。 ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(native_fn) })); ctor.set_native_arg_count(arg_count); } @@ -100,7 +100,7 @@ pub(crate) fn apply_binding_table( let shape_forge = core.shape_forge().as_ref(); let string_forge = core.perm_interner().as_ref(); for (name, func, nargs) in bindings { - // SAFETY: all entries in the binding table are NativeFn fn-item pointers cast to *const (). + // SAFETY: 绑定表中所有条目都是转成 *const () 的 NativeFn 函数项指针。 let fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(*func) }; let _ = world.bind_method(target, shape_forge, string_forge, name, fn_ptr, *nargs); } @@ -491,9 +491,11 @@ pub fn bind_global_builtin_slots(core: &Arc, session: &KernelSession bind_existing_global(core, global, "globalThis", global_this); } -/// Maintenance: when adding a `BuiltinDirtySet` group, update this rebind map, -/// `BuiltinSnapshot`, and `BuiltinWorld::rebuild_with_dirty()` together. /// 按脏标记重绑被污染的内置对象;`dirty` 为 `None` 时绑定全部(初始化路径)。 +/// +/// # 注意事项 +/// 维护:新增 `BuiltinDirtySet` 分组时,须同步更新本重绑映射、`BuiltinSnapshot` 与 +/// `BuiltinWorld::rebuild_with_dirty()`。 pub fn rebind_dirty_builtins(core: &Arc, session: &mut KernelSession, dirty: Option<&BuiltinDirtySet>) { let global_ptr = session.global_object().as_ptr() as *mut JsObject; let global = unsafe { &mut *global_ptr }; diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 7716c49..a3fe5bf 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -33,14 +33,14 @@ impl Vm { let args_slice = &args_buf[..len]; builtins_debug!("native_call depth={} args={}", self.native_call_depth, arg_count); - // SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer; - // native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn. + // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; + // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 let func: NativeFn = unsafe { native_fn_ptr_to_fn(obj.native_fn().unwrap()) }; - // regs[254] is the caller's `this` register and doubles as the "current callee" - // slot that dispatcher builtins (Function.prototype.bind/call/apply) read. Native - // calls share the flat register file with the caller, so snapshot the caller's - // `this`, expose the callee for the native's duration, then restore it — otherwise - // every native call leaves its own function object in the caller's `this` register. + // regs[254] 是调用方的 `this` 寄存器,同时充当分发型 builtin + // (Function.prototype.bind/call/apply)读取的"当前 callee"槽。native 调用与 + // 调用方共享扁平寄存器文件,因此先快照调用方 `this`,在 native 执行期间暴露 + // callee,随后恢复——否则每次 native 调用都会把自己的函数对象留在调用方的 + // `this` 寄存器里。 let saved_this = self.regs[254]; self.regs[254] = callee; self.native_call_depth += 1; @@ -58,7 +58,7 @@ impl Vm { (err_val, self.thrown_error_kind(err_val)) } else { let msg = if err_val.is_string() { - // SAFETY: err_val is a string value. + // SAFETY: err_val 是字符串值。 unsafe { (*err_val.as_string_ptr()).data.clone() } } else { format!("{err_val}") @@ -184,8 +184,8 @@ impl Vm { } } } - // Cell not yet created (hoisting order: CREATE_CLOSURE before MAKE_CELL). - // Create cell lazily with caller's register value. + // cell 尚未创建(hoisting 顺序:CREATE_CLOSURE 先于 MAKE_CELL), + // 用调用方寄存器值惰性创建 cell。 self.lazy_create_upvalue_cell(rd, uv_idx) } @@ -195,7 +195,7 @@ impl Vm { let obj = unsafe { &mut *callee.as_js_object_ptr() }; let upvals = obj.upvalues_slice_mut(); if uv_idx < upvals.len() { - // Try to get value from caller's cell table (MAKE_CELL ran after CREATE_CLOSURE) + // 尝试从调用方 cell 表取初值(MAKE_CELL 在 CREATE_CLOSURE 之后执行)。 let val = if self.cell_stack.len() >= 2 { let caller_cells = &self.cell_stack[self.cell_stack.len() - 2]; if uv_idx < caller_cells.len() && !caller_cells[uv_idx].is_null() { @@ -317,10 +317,10 @@ impl Vm { if super_obj.native_fn().is_some() { self.regs[253] = derived_this; - self.regs[254] = super_ctor; // bind_dispatcher reads regs[254] as the wrapper callee + self.regs[254] = super_ctor; // bind_dispatcher 把 regs[254] 当作包装 callee 读取 let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, 253); - // SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer; - // native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn. + // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; + // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 let func: NativeFn = unsafe { native_fn_ptr_to_fn(super_obj.native_fn().unwrap()) }; match func(self, &args_buf[..len]) { NativeResult::Ok(val) => { @@ -336,8 +336,8 @@ impl Vm { } } NativeResult::TailCall { callee, this, args } => { - // e.g. bound function: resolve the tail call, use its return - // value as the constructed instance (or fall back to derived_this). + // 如 bound 函数:解析尾调用,用其返回值作为构造实例 + // (或回退到 derived_this)。 match self.call_function_sync(callee, this, &args) { Ok(val) => { self.regs[254] = if val.is_object() { val } else { derived_this }; diff --git a/crates/oxide_vm/src/dispatch/exception.rs b/crates/oxide_vm/src/dispatch/exception.rs index 0e9d58a..5327d44 100644 --- a/crates/oxide_vm/src/dispatch/exception.rs +++ b/crates/oxide_vm/src/dispatch/exception.rs @@ -1 +1 @@ -// dispatch helper functions extracted from vm.rs +//! 异常处理相关 dispatch 辅助函数(占位,逻辑位于 vm_dispatch_ctrl / vm_dispatch_misc)。 diff --git a/crates/oxide_vm/src/dispatch/for_in.rs b/crates/oxide_vm/src/dispatch/for_in.rs index 0e9d58a..57d0c6b 100644 --- a/crates/oxide_vm/src/dispatch/for_in.rs +++ b/crates/oxide_vm/src/dispatch/for_in.rs @@ -1 +1 @@ -// dispatch helper functions extracted from vm.rs +//! for-in 相关 dispatch 辅助函数(占位,逻辑位于 vm_dispatch_misc)。 diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 1fec8e6..e9f0cbb 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -65,7 +65,7 @@ impl Vm { pub(crate) fn dispatch_store_var(&mut self, rd: usize, a: usize, b: usize) -> Result { vm_trace!("STORE_VAR r{}={:?} const={}", rd, self.regs[a], b); if b != 0 { - // const guard: check if already initialized + // const guard:检查目标槽是否已初始化。 if !self.regs[rd].is_undefined() { self.raise_error_kind("TypeError", "Assignment to constant variable")?; return Ok(true); @@ -75,8 +75,13 @@ impl Vm { Ok(false) } - /// SPILL:regs[rd] → spill_stack[帧基址 + slot]。ext 字 = slot u16(D-06)。 - /// 帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧 = 0,D-08)。 + /// SPILL:`regs[rd]` → `spill_stack[帧基址 + slot]`。 + /// + /// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。 + /// # 边界与前提 + /// - slot 超 u16 范围返回错误(RegAlloc 保证发射在界内)。 + /// # 副作用 + /// - 写 spill 栈,可能触发 resize 扩容。 #[inline(always)] pub(crate) fn dispatch_spill(&mut self, rd: usize) -> Result<(), String> { let slot = self.bytecode[self.pc] as usize; @@ -94,7 +99,11 @@ impl Vm { Ok(()) } - /// UNSPILL:spill_stack[帧基址 + slot] → regs[rd]。越界读 undefined(防御,D-06 slot 由 RegAlloc 保证在界内)。 + /// UNSPILL:`spill_stack[帧基址 + slot]` → `regs[rd]`。 + /// + /// # 边界与前提 + /// - slot 超 u16 范围返回错误。 + /// - 越界读返回 undefined(防御:slot 由 RegAlloc 保证在界内)。 #[inline(always)] pub(crate) fn dispatch_unspill(&mut self, rd: usize) -> Result<(), String> { let slot = self.bytecode[self.pc] as usize; diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index e8a4272..6090c6b 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -46,7 +46,7 @@ impl Vm { if val.is_string() { let length_si = self.kernel_core.perm_interner().intern("length").0; if prop_name_si == length_si { - // SAFETY: val is a string value. + // SAFETY: val 是字符串值。 let len = unsafe { (*val.as_string_ptr()).data.encode_utf16().count() }; return Ok(Some(JsValue::int(len as i32))); } @@ -207,14 +207,14 @@ impl Vm { prop_cache_miss(); ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si); let resolved = self.ordinary_get(obj, prop_name_si, val)?; - // Try own property first (fast path, depth=0). + // 先试自身属性(快路径,depth=0)。 if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { if !obj.is_accessor_meta(pos) { ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); ic_debug!("IC_GET write-back own shape={} slot={}", obj.shape_id(), pos); } } else { - // Walk proto chain to find which object actually owns this property. + // 沿原型链查找真正拥有该属性的对象。 let mut cursor = obj.proto().as_js_object_ptr(); let mut depth = 1u8; while !cursor.is_null() { diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index 524c34b..977a9f3 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -1,22 +1,20 @@ -//! Inline-cache (IC) bytecode-stream helpers. +//! inline cache(IC)字节码流辅助函数。 //! -//! Contains ALL IC read/write/clear logic — property dispatch and member -//! update handlers route through here, so IC format changes (like the future -//! side-table migration) require editing only this file. +//! 汇聚全部 IC 读写/清零逻辑——属性分发与成员更新 handler 都经由此处,IC 格式 +//! 变更(如未来的 side-table 迁移)只需改这一个文件。 //! -//! IC entry format (3 extension words after opcode): -//! ext0 = shape_id (24 bits) -//! ext1 = slot_index (32 bits) -//! ext2 = proto_depth (u8, 0 = own property on receiver) +//! IC 项格式(操作码后 3 个扩展字): +//! ext0 = shape_id(24 位) +//! ext1 = slot_index(32 位) +//! ext2 = proto_depth(u8,0 = 接收者自身属性) use crate::vm_trace; use oxide_bytecode::opcode::{self, Instr}; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -/// Read the three IC extension words at `pc` (the bytes following an -/// IC-bearing opcode), advance `pc` past them, and return the decoded -/// `(shape_id, slot, proto_depth)`. +/// 读取 `pc` 处 IC 指令后的三个扩展字,把 `pc` 前进越过它们, +/// 返回解码的 `(shape_id, slot, proto_depth)`。 pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8) { let ext0 = bytecode[*pc]; let ext1 = bytecode[*pc + 1]; @@ -25,9 +23,8 @@ pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8 (ext0 & 0x00FF_FFFF, ext1, (ext2 & 0xFF) as u8) } -/// Write a resolved `(shape_id, slot, proto_depth)` back into the three IC -/// extension words at `pc - 3` (they precede the current instruction). -/// Called on an IC miss after resolving a property. +/// 把解析出的 `(shape_id, slot, proto_depth)` 写回 `pc - 3` 处的三个 IC 扩展字 +/// (位于当前指令之前)。IC 未命中并完成属性解析后调用。 pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32, proto_depth: u8) { debug_assert!(pc >= 3, "IC write-back requires 3 extension words before pc"); vm_trace!("write_ic_back: pc={} shape_id={} slot={} depth={}", pc, shape_id, slot_index, proto_depth); @@ -36,7 +33,7 @@ pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, sl bytecode[pc - 1] = proto_depth as u32; } -/// Zero every IC extension word in the stream, invalidating all cached shapes. +/// 把流中所有 IC 扩展字清零,使全部缓存 shape 失效。 pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) { let mut i = 0; while i < bytecode.len() { @@ -54,8 +51,7 @@ pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) { } } -/// Walk `proto_depth` steps up the prototype chain, returning a raw pointer -/// to the target object (or null if the chain is shorter). +/// 沿原型链上溯 `proto_depth` 步,返回目标对象的裸指针(链不足时返回 null)。 #[inline(always)] fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsObject { if proto_depth == 0 { diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs index 9e24993..50054f5 100644 --- a/crates/oxide_vm/src/session_arena.rs +++ b/crates/oxide_vm/src/session_arena.rs @@ -244,8 +244,8 @@ mod tests { let first = plain_object(&mut vm); let promoted = vm.promote_object(first); assert!(!promoted.is_null()); - // The shared forwarding map must be cleared after each promote so a - // later promote (or GC sweep) never observes stale old->new mappings. + // 共享 forwarding 表必须在每次 promote 后清空,使后续 promote(或 GC 清扫) + // 永不观察到过期的 old->new 映射。 assert!(vm.gc_state.forwarding.is_empty()); let second = plain_object(&mut vm); diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index d5a6549..dee5159 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -47,8 +47,8 @@ impl SessionGc { if ptr.is_null() { continue; } - // SAFETY: ptrs in session_object_ptrs come only from `session_epoch.alloc` in - // `promote_object_inner`, and are valid while the arena is alive. + // SAFETY: session_object_ptrs 中的指针只来自 promote_object_inner 的 + // session_epoch.alloc,在 arena 存活期间有效。 unsafe { (*ptr).set_gc_mark(false) }; } } @@ -89,7 +89,7 @@ impl SessionGc { if obj.is_data_view_obj() { edges.extend(data_view::data_view_native_edges(obj)); } - // Traverse upvalue cells for object references + // 遍历 upvalue cell 中的对象引用。 for cell_ptr in obj.upvalues_slice() { if cell_ptr.is_null() { continue; @@ -102,10 +102,10 @@ impl SessionGc { edges } - /// Record session-string values held directly by `obj` into `live`. JsStrings hold no GC - /// references, so "reaching" a string IS marking it — there is no string DFS stack. Permanent - /// strings are also recorded here harmlessly; the sweep only iterates `session_string_ptrs`, so - /// non-session pointers in `live` are simply never consulted. + /// 把 `obj` 直接持有的 session 字符串值记入 `live`。JsString 不持有 GC 引用, + /// 因此"到达"一个字符串就等于标记它——不存在字符串 DFS 栈。永久字符串也会被 + /// 无害地记录;sweep 只遍历 `session_string_ptrs`,`live` 中的非 session 指针 + /// 永远不会被查询。 fn record_object_string_edges(live: &mut HashSet<*mut JsString, FxBuildHasher>, obj: &JsObject) { if let Some(props) = obj.hash_props_vec() { for value in props.iter() { @@ -120,7 +120,7 @@ impl SessionGc { if obj.home_object().is_string() { live.insert(obj.home_object().as_string_ptr_mut()); } - // Scan upvalue cells for string references + // 扫描 upvalue cell 中的字符串引用。 for cell_ptr in obj.upvalues_slice() { if cell_ptr.is_null() { continue; @@ -179,7 +179,7 @@ impl SessionGc { stack.push(ptr); continue; } - // SAFETY: object roots are produced by VM-owned fields and builtin objects. + // SAFETY: 对象根由 VM 自有的字段与 builtin 对象产生。 let obj = unsafe { &*ptr }; Self::record_object_string_edges(live_strings, obj); for edge in Self::object_edges(obj) { @@ -196,8 +196,7 @@ impl SessionGc { if ptr.is_null() { continue; } - // SAFETY: ptr was discovered from a root/session edge and session root checks require - // this to be a valid session object pointer. + // SAFETY: ptr 由根/session 边发现,session 根检查保证它是合法 session 对象指针。 unsafe { let obj = &mut *ptr; if obj.is_gc_marked() { @@ -223,9 +222,9 @@ impl SessionGc { if obj_ptr.is_null() { return 0; } - // SAFETY: `obj_ptr` is verified before calling this helper and points to a session object - // owned by the VM session arena. We only reconstruct Boxes that were allocated in - // `JsObject::ensure_hash_props`/`ensure_prop_meta` and then drop them once here. + // SAFETY: `obj_ptr` 在调用本辅助函数前已校验,指向 VM session arena 拥有的 + // session 对象。只重建 JsObject::ensure_hash_props/ensure_prop_meta 分配的 Box, + // 且只在这里释放一次。 unsafe { let obj = &mut *obj_ptr; if require_session { @@ -267,12 +266,13 @@ impl SessionGc { Self::drop_session_object_heap_data(obj_ptr) + size_of::() as u64 } - /// Drop a dead session `JsString` (allocated by `Vm::new_string` via `Box::into_raw`), - /// returning the bytes freed. Mirrors `Vm::free_session_string_heap_data`'s drop, but applied - /// selectively to a single dead pointer. + /// 释放一个已死 session `JsString`(由 `Vm::new_string` 经 `Box::into_raw` 分配), + /// 返回释放的字节数。与 `Vm::free_session_string_heap_data` 的释放一致,但只 + /// 选择性作用于单个已死指针。 /// - /// SAFETY: `ptr` must be a non-null pointer produced by `Box::into_raw(Box::new(JsString))` in - /// `Vm::new_string`, still present in `session_string_ptrs`, and dropped exactly once. + /// # Safety + /// `ptr` 必须是 `Vm::new_string` 中 `Box::into_raw(Box::new(JsString))` 产生的 + /// 非空指针,仍存在于 `session_string_ptrs`,且恰好释放一次。 unsafe fn drop_dead_session_string(ptr: *mut JsString) -> u64 { let bytes = (size_of::() + (*ptr).len()) as u64; drop(Box::from_raw(ptr)); @@ -291,8 +291,7 @@ impl SessionGc { if old_ptr.is_null() { continue; } - // SAFETY: old_ptr comes from session_arena promotions and still points into the old - // session arena while sweep runs. + // SAFETY: old_ptr 来自 session_arena 的晋升,sweep 运行期间仍指向旧 session arena。 let is_live = unsafe { (*old_ptr).is_gc_marked() }; if is_live { survivors += 1; @@ -320,7 +319,7 @@ impl SessionGc { } for &dst in forwarding.values() { - // SAFETY: all pointers in forwarding are newly allocated and initialized objects. + // SAFETY: forwarding 中的指针全是新分配且已初始化的对象。 let obj = unsafe { &mut *dst }; obj.rewrite_object_values(|value| { if value.is_object() { @@ -378,11 +377,11 @@ impl SessionGc { freed_bytes } - /// Sweep session `JsString`s: keep the pointers recorded live during `mark()`, drop the rest - /// via `Box::from_raw`. Live strings are NOT moved — Box addresses are stable — so no forwarding - /// map and no root-pointer rewriting are needed. Run after the object sweep (which resets - /// `session_bytes_allocated` to object-only), re-adding surviving string bytes. Returns the - /// bytes freed. + /// 清扫 session `JsString`:保留 `mark()` 阶段记为存活的部分,其余经 + /// `Box::from_raw` 释放。存活字符串不被搬移——Box 地址稳定——因此无需 + /// forwarding 表与根指针重写。在对象清扫之后运行(对象清扫会把 + /// `session_bytes_allocated` 重置为仅对象),再补回存活字符串字节。 + /// 返回释放的字节数。 pub(crate) fn sweep_session_strings(&mut self, vm: &mut Vm) -> u64 { vm_debug!("[GC] sweep strings: {} string ptrs", vm.gc_state.session_string_ptrs.len()); let old = std::mem::take(&mut vm.gc_state.session_string_ptrs); @@ -394,12 +393,12 @@ impl SessionGc { continue; } if self.live_strings.contains(&ptr) { - // Survivor — address unchanged, no rewrite needed. - // SAFETY: ptr is a live session-string box still owned by the VM. + // 存活——地址不变,无需重写。 + // SAFETY: ptr 是仍归 VM 所有的存活 session 字符串 box。 live_bytes += unsafe { size_of::() + (*ptr).len() }; live.push(ptr); } else { - // SAFETY: ptr is in session_string_ptrs but not reachable; drop exactly once. + // SAFETY: ptr 在 session_string_ptrs 中但不可达,恰好释放一次。 freed += unsafe { Self::drop_dead_session_string(ptr) }; } } @@ -521,12 +520,12 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje *value = rewrite_forwarded_value(*value, forwarding); } vm.iters.last_for_of_result = rewrite_forwarded_value(vm.iters.last_for_of_result, forwarding); - // Converted immutables are non-session (scalars + perm-strings) — not rewritten. + // 已转换的不可变常量不是 session 值(标量 + perm 字符串)——不重写。 for iter in &mut vm.iters.for_in_iters { if iter.is_null() { continue; } - // SAFETY: for_in_iters stores live iterator pointers owned by the current VM epoch. + // SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。 unsafe { for (value, _si) in (*(*iter)).keys.iter_mut() { *value = rewrite_forwarded_value(*value, forwarding); @@ -535,7 +534,7 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje } let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject; if !global_ptr.is_null() { - // SAFETY: KernelSession owns global_object for the VM lifetime. + // SAFETY: KernelSession 在 VM 生命周期内拥有 global_object。 unsafe { (*global_ptr).rewrite_object_values(|value| rewrite_forwarded_value(value, forwarding)); } @@ -689,8 +688,8 @@ mod tests { let _ = gc.sweep(&mut vm); vm.gc_state.session_gc = gc; - // The reused forwarding map MUST be cleared after sweep, otherwise - // promote would observe stale old->new entries pointing into the freed arena. + // 复用的 forwarding 表必须在 sweep 后清空,否则 promote 会观察到指向已释放 + // arena 的过期 old->new 条目。 assert!(vm.gc_state.forwarding.is_empty()); } @@ -934,7 +933,7 @@ mod tests { let dead_ptr = dead.as_string_ptr_mut(); assert!(vm.gc_state.session_string_ptrs.contains(&dead_ptr)); - // No root references `dead` (it lives only on the Rust stack as a value wrapper). + // 没有根引用 `dead`(它只作为值包装存在于 Rust 栈上)。 collect(&mut vm); assert!(!vm.gc_state.session_string_ptrs.contains(&dead_ptr)); @@ -950,7 +949,7 @@ mod tests { collect(&mut vm); assert!(vm.gc_state.session_string_ptrs.contains(&live_ptr)); - // Live strings are never moved — the register still points at the same box. + // 存活字符串永不搬移——寄存器仍指向同一 box。 assert_eq!(vm.regs[0].as_string_ptr_mut(), live_ptr); assert_eq!(unsafe { (*live_ptr).as_str() }, "live-in-reg"); } @@ -966,7 +965,7 @@ mod tests { } let obj_session = vm.promote_object(obj); - // The string is reachable ONLY through the live object's property, not via any register. + // 该字符串仅通过存活对象的属性可达,不经过任何寄存器。 vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(obj_session); @@ -981,13 +980,13 @@ mod tests { let mut vm = Vm::new(); let perm = vm.perm_string("perm"); let perm_ptr = perm.as_string_ptr_mut(); - // Permanent strings live in PermInterner, never in the session set. + // 永久字符串位于 PermInterner,绝不在 session 集合中。 assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr)); vm.regs[0] = perm; collect(&mut vm); - // Never dropped by the session sweep (still readable, still absent from session set). + // 绝不被 session 清扫释放(仍可读,仍不在 session 集合中)。 assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr)); assert_eq!(unsafe { (*perm_ptr).as_str() }, "perm"); } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index bdc9741..174c562 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -22,11 +22,11 @@ use oxide_types::value::{JsValue, PTR_MASK}; pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024; -/// Convert a `NativeFnPtr` to a callable `NativeFn`. +/// 将 [`NativeFnPtr`] 转换为可调用的 [`NativeFn`]。 /// /// # Safety -/// The pointer stored in `ptr` must have been created from a valid `NativeFn` fn-item. -/// This is the single point in the codebase where `NativeFnPtr → NativeFn` coercion happens. +/// `ptr` 必须由合法的 `NativeFn` 函数项产生。 +/// 这是代码库中唯一一处 `NativeFnPtr → NativeFn` 的强制转换点。 #[inline(always)] pub(crate) unsafe fn native_fn_ptr_to_fn(ptr: NativeFnPtr) -> NativeFn { std::mem::transmute::<*const (), NativeFn>(ptr.as_ptr()) @@ -110,7 +110,7 @@ pub struct CallFrame { pub function_name: u32, pub caller_reg_limit: u8, pub saved_reg_offset: u32, - /// push 时记录 spill_stack 长度(D-08 帧边界,SPILL/UNSPILL 基址)。 + /// 记录本帧 spill 栈起始长度,作为 SPILL/UNSPILL 的帧边界基址(push 时快照)。 pub spill_offset: u32, pub saved_this: JsValue, pub saved_new_target: JsValue, @@ -125,8 +125,8 @@ pub struct CallFrame { /// /// 每个 key 与它的 intern id 配对保存,使整型下标 key 无需重新 intern 即可排到字符串 key 之前。 pub struct ForInIter<'bump> { - /// Each key paired with its string-intern id, so for-in can sort - /// integer-index keys ahead of string keys without re-interning. + /// 每个 key 与其字符串 intern id 配对,使 for-in 排序时整型下标 key 无需 + /// 重新 intern 即可排到字符串 key 之前。 pub keys: bumpalo::collections::Vec<'bump, (JsValue, u32)>, pub index: usize, } @@ -136,13 +136,13 @@ pub struct TryHandler { pub catch_pc: Option, pub finally_pc: Option, pub frame_depth: usize, - /// for_of_iters length at try entry — bounds which iterators IteratorClose on unwind. + /// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。 pub for_of_depth: usize, } -/// Heap-allocated snapshot used by `call_bytecode_function_inline`. -/// Keeping it on the heap prevents Rust stack overflow when JS code -/// chains multiple sync bytecode calls (e.g. sort comparator, accessor). +/// `call_bytecode_function_inline` 使用的堆分配快照。 +/// 放在堆上避免 JS 代码链式同步字节码调用(如 sort 比较器、accessor)时 +/// 耗尽 Rust 栈。 pub(crate) struct InlineSyncState { pub(crate) regs: Box<[JsValue; 256]>, pub(crate) pc: usize, @@ -174,12 +174,12 @@ pub struct Vm { pub(crate) regs: [JsValue; 256], pub(crate) pc: usize, pub(crate) bytecode: Vec, - /// Per-run convert-once immutables cache. Index 0 = top module, sub_idx+1 = sub_modules[sub_idx]. - /// Each `OnceLock` holds that module's constants converted to `JsValue`s exactly once this run. - /// Rebuilt every `run()`. Immutables are scalars + perm-strings — read-only, never GC roots. + /// 每次 run 转换一次的不可变常量缓存。下标 0 = 顶层模块,sub_idx+1 = sub_modules[sub_idx]。 + /// 每个 `OnceLock` 保存该模块常量本次运行中只转换一次的 `JsValue` 结果,每次 `run()` 重建。 + /// 不可变常量是标量 + perm 字符串,只读,不作为 GC 根。 pub(crate) immutables_cache: Vec>>, - /// Read-only view into the currently-active module's converted immutables (inside immutables_cache). - /// A fat `*const` because the cache Vec is VM-owned and run-stable (OnceLock filled once). + /// 当前活动模块已转换不可变常量的只读视图(指向 immutables_cache 内部)。 + /// 用胖 `*const`:缓存 Vec 归 VM 所有且本次运行稳定(OnceLock 只填一次)。 pub(crate) active_immutables: *const [JsValue], pub(crate) frames: SmallVec<[CallFrame; 16]>, pub(crate) kernel_core: Arc, @@ -190,40 +190,40 @@ pub struct Vm { pub(crate) sub_modules: Arc>, pub(crate) saved_bytecode_stack: Vec>, pub(crate) saved_immutables_stack: Vec<*const [JsValue]>, - /// Shared register save-stack. Each active `CallFrame` saved its caller's live - /// registers (`regs[..caller_reg_limit]`) here at `saved_reg_offset`; restore copies - /// them back and truncates. Capacity is retained across calls — zero per-call heap alloc. + /// 共享寄存器保存栈。每个活动 `CallFrame` 在 push 时把调用方活跃寄存器 + /// (`regs[..caller_reg_limit]`)按 `saved_reg_offset` 存到这里;恢复时复制回 + /// 并截断。容量跨调用保留,避免每次调用堆分配。 pub(crate) save_stack: Vec, - /// VM 级 spill 栈(D-08,仿 save_stack 帧边界)。`CallFrame.spill_offset` 定位本帧区, - /// 调用子函数时从边界后分配,restore_frame 截断恢复。 + /// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配, + /// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。 pub(crate) spill_stack: Vec, pub(crate) try_stack: Vec, pub(crate) exception_value: Option, - /// Side-channel carrying the JsValue thrown by a sync call whose error was flattened - /// to a String by `call_function_sync`/`unwind`, so for-of can re-throw the original - /// value. Plain field (NOT in InlineSyncState) so it survives the inline-call restore. + /// 同步调用抛出的原始 JsValue 侧通道:`call_function_sync`/`unwind` 把错误展平为 + /// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中) + /// 以便跨内联调用的恢复过程存活。 pub(crate) last_uncaught_value: Option, pub(crate) pending_exception: Option, pub(crate) pending_error_kind: Option<&'static str>, pub(crate) root_reg_limit: u8, pub(crate) active_reg_limit: u8, pub(crate) native_call_depth: usize, - /// Set to Some(target_reg) when `ordinary_get` pushes a bytecode accessor frame. - /// The dispatch loop checks this flag and skips writing `regs[target_reg]` from the - /// call result — the value will be delivered by the RETURN handler instead. + /// `ordinary_get` 压入字节码 accessor 帧时设为 Some(target_reg)。 + /// 调度循环检查该标志,跳过用调用结果写 `regs[target_reg]` —— 值改由 RETURN + /// 处理器交付。 pub(crate) accessor_frame_target_reg: Option, - /// Grouped session-arena / GC bookkeeping. + /// 分组保存 session arena / GC 簿记状态。 pub(crate) gc_state: GcState, - /// Grouped `Symbol` interning state. + /// 分组保存 `Symbol` intern 状态。 pub(crate) symbols: SymbolState, - /// Grouped live `for-in` / `for-of` iterator state. + /// 分组保存活跃的 for-in / for-of 迭代器状态。 pub(crate) iters: IterState, - /// Grouped inline-cache and instruction counters. + /// 分组保存 inline cache 与指令计数器。 pub(crate) profiling: ProfilingState, pub(crate) sub_module_stack: Vec>>, pub(crate) cell_stack: Vec>, pub(crate) temp_immutables: Vec>, - /// Reusable string buffer for concatenation to avoid allocation per `+` op. + /// 可复用字符串缓冲区,避免每次 `+` 拼接都分配。 pub(crate) string_buf: String, } @@ -342,7 +342,7 @@ impl Vm { if obj_ptr.is_null() { return false; } - // SAFETY: obj_ptr is non-null and points to a `JsObject` owned by this session. + // SAFETY: obj_ptr 非空且指向本 session 拥有的 JsObject。 unsafe { (*obj_ptr).is_session_epoch() } } @@ -352,25 +352,25 @@ impl Vm { ptr } - /// Read-only view into the active module's converted immutables. Empty before any `run()`. + /// 活动模块已转换不可变常量的只读视图;任何 `run()` 之前为空。 #[inline(always)] pub(crate) fn immutables(&self) -> &[JsValue] { if self.active_immutables.is_null() { &[] } else { - // SAFETY: active_immutables points into a OnceLock> inside immutables_cache, - // which the VM owns; the Vec is filled once and never reallocated for the run's lifetime. + // SAFETY: active_immutables 指向 VM 拥有的 immutables_cache 内的 OnceLock>, + // Vec 只填充一次,本次运行期间不再重分配。 unsafe { &*self.active_immutables } } } - /// Activate module `cache_idx`'s immutables (0 = top, sub_idx+1 = sub_modules[sub_idx]), - /// converting them once into `immutables_cache[cache_idx]` and pointing `active_immutables` at - /// the cached Vec. `constants` is passed by the caller (it already holds `&module.constants`). + /// 激活模块 `cache_idx` 的不可变常量(0 = 顶层,sub_idx+1 = sub_modules[sub_idx]), + /// 只转换一次存入 `immutables_cache[cache_idx]`,并把 `active_immutables` 指向该 Vec。 + /// `constants` 由调用方传入(它已持有 `&module.constants`)。 pub(crate) fn activate_immutables(&mut self, cache_idx: usize, constants: &[Constant]) { - // Raw-ptr the cache slot so `get_or_init` (which borrows immutables_cache) and the &self - // convert_immutables closure don't conflict with the subsequent self.active_immutables write. - // Sound: immutables_cache is VM-owned, read-only, and run-stable. + // 用裸指针访问缓存槽,避免 get_or_init(借用 immutables_cache)与 &self 的 + // convert_immutables 闭包和随后对 active_immutables 的写入发生借用冲突。 + // 成立前提:immutables_cache 归 VM 所有、只读、本次运行稳定。 let slot: *const OnceLock> = &self.immutables_cache[cache_idx]; let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants)); self.active_immutables = vec.as_slice() as *const [JsValue]; @@ -413,7 +413,7 @@ impl Vm { f(v); } f(self.iters.last_for_of_result); - // Converted immutables (scalars + perm-strings) are NOT session GC roots — not scanned. + // 已转换的不可变常量(标量 + perm 字符串)不作为 session GC 根,不参与扫描。 for iter in &self.iters.for_in_iters { if iter.is_null() { continue; @@ -569,7 +569,7 @@ impl Vm { if !val.is_string() { return None; } - // SAFETY: val is a string value; its JsString pointer is alive for its lifetime. + // SAFETY: val 是字符串值,其 JsString 指针在生命周期内有效。 Some(unsafe { (*val.as_string_ptr()).data.clone() }) } @@ -599,7 +599,7 @@ impl Vm { pub(crate) fn property_key_si(&mut self, val: JsValue) -> u32 { if val.is_string() { - // SAFETY: val is a string value; bridge its content to a permanent key id. + // SAFETY: val 是字符串值,把其内容桥接为永久 key id。 let s = unsafe { &(*val.as_string_ptr()).data }; return self.kernel_core.perm_interner().intern(s).0; } @@ -702,8 +702,8 @@ impl Vm { } let saved_regs = self.regs; let arg_regs = self.pack_sync_native_call_args(receiver, callee, args); - // SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer; - // native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn. + // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; + // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) }; self.native_call_depth += 1; let result = func(self, &arg_regs); @@ -719,10 +719,9 @@ impl Vm { }; } - // Run bytecode function inline on self (same epoch) to prevent use-after-free. - // A separate sub-VM would own a different epoch; returning a JsValue that contains - // a pointer into the sub-VM epoch and then dropping the sub-VM causes a dangling - // pointer / access violation in release builds. + // 字节码函数在自身(同一 epoch)内联执行,防止 use-after-free。 + // 独立子 VM 拥有不同 epoch:返回含子 VM epoch 指针的 JsValue 后再销毁子 VM, + // 会在 release 构建中产生悬垂指针 / 访问违规。 self.call_bytecode_function_inline(callee, callee_obj, receiver, args) } @@ -1448,7 +1447,7 @@ mod tests { let proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto)); obj.set_function(true); - // SAFETY: f is a NativeFn fn-item; valid to store as NativeFnPtr. + // SAFETY: f 是 NativeFn 函数项,可作为 NativeFnPtr 存储。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) })); JsValue::object(vm.alloc_object(obj) as *mut u8) } diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 9cdbbaa..c1f3559 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -179,7 +179,7 @@ impl Vm { let reg = (seg & 0x7F) as u8; let val = self.regs[reg as usize]; let s = if val.is_string() { - // SAFETY: val is a string value. + // SAFETY: val 是字符串值。 unsafe { (*val.as_string_ptr()).data.clone() } } else { format!("{}", val) @@ -191,7 +191,7 @@ impl Vm { if const_idx < imm.len() { let val = imm[const_idx]; if val.is_string() { - // SAFETY: val is a string value. + // SAFETY: val 是字符串值。 let s = unsafe { (*val.as_string_ptr()).data.clone() }; result.push_str(&s); } @@ -285,7 +285,7 @@ impl Vm { vm_trace!("FOR_IN_INIT r{}={:?}", a, self.regs[a]); let obj_val = self.regs[a]; if obj_val.is_null() || obj_val.is_undefined() { - // null/undefined enumerate nothing — an empty for-in, not a TypeError. + // null/undefined 枚举不到任何键——是空 for-in,而非 TypeError。 let keys_vec: bumpalo::collections::Vec<(JsValue, u32)> = bumpalo::collections::Vec::new_in(self.epoch.bump()); let iter = self.epoch.alloc(ForInIter { keys: keys_vec, index: 0 }); @@ -293,7 +293,7 @@ impl Vm { return Ok(()); } if !obj_val.is_object() { - // ToObject coercion for other primitives is not implemented yet — TypeError is correct until it lands. + // 未支持:其它基本类型的 ToObject 强转尚未实现,在此之前抛 TypeError 是正确行为。 return self.raise_type_error("for-in right-hand side is not an object"); } @@ -302,9 +302,8 @@ impl Vm { let mut seen = std::collections::HashSet::new(); let mut current = obj_val; - // Arrays store integer-indexed elements in prop_vec, which is not part of - // the shape chain. Enumerate them (ES: array indices are enumerable string - // keys, ordered ascending before other own keys). + // 数组把整型下标元素存在 prop_vec,而 prop_vec 不属于 shape 链。 + // 单独枚举它们(ES:数组下标是可枚举字符串键,按升序排在其它自有键之前)。 if current.is_object() { let arr = unsafe { &*current.as_js_object_ptr() }; if arr.is_array() { @@ -315,10 +314,7 @@ impl Vm { .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); if is_enum { let idx = self.kernel_core.perm_interner().intern(&i.to_string()).0; - keys_vec.push(( - JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)), - idx, - )); + keys_vec.push((JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)), idx)); } } } @@ -365,14 +361,13 @@ impl Vm { break; } } - // The shape chain is walked leaf->root (reverse of insertion order); - // flip this object's slice back to insertion order before its proto. + // shape 链按叶→根遍历(与插入序相反);在接上原型前把本对象的切片 + // 翻转为插入序。 keys_vec[obj_start..].reverse(); current = cur.proto(); } - // ES enumeration order: integer-index keys ascending, then the rest in - // insertion order. Stable sort preserves insertion order among non-index keys. + // ES 枚举序:整型下标键升序,其余按插入序。稳定排序保持非下标键间的插入序。 keys_vec.sort_by(|(_, a_si), (_, b_si)| { match (self.array_index_from_property_key(*a_si), self.array_index_from_property_key(*b_si)) { (Some(ai), Some(bi)) => ai.cmp(&bi), @@ -488,9 +483,9 @@ impl Vm { Ok(()) } - /// next()/value access threw: route through unwind so a surrounding try/catch can catch - /// it, re-throwing the ORIGINAL value when available. Per ECMA-262 the iterator is NOT - /// closed via return() on a next()-throw — pop it so the unwinding IteratorClose pass skips it. + /// next()/value 访问抛出:经 unwind 走异常展开,使外围 try/catch 能捕获,并尽可能 + /// 重新抛出原始值。按 ECMA-262,next() 抛出时不会经 return() 关闭迭代器—— + /// 先弹出它,使展开时的 IteratorClose 遍历跳过该迭代器。 fn throw_for_of_error(&mut self, msg: String) -> Result<(), String> { self.iters.pop_for_of(); let exc = match self.last_uncaught_value.take() { @@ -507,14 +502,16 @@ impl Vm { let Some(iterator) = self.iters.pop_for_of() else { return Ok(()); }; - // Normal / break / return exit: no prior abrupt completion, so return()'s own throw propagates. + // 正常 / break / return 退出:此前无进行中的突然完成,return() 自身的抛出直接传播。 self.close_for_of_iterator(iterator, false) } - /// Call `iterator.return()` (IteratorClose). When `suppress_return_error` is true the call - /// is being made because an enclosing abrupt completion is unwinding: return()'s own result - /// is discarded and the in-flight exception is preserved across the call. When false (normal - /// for-of exit) return()'s error propagates. + /// 调用 `iterator.return()`(IteratorClose)。 + /// + /// # 边界与前提 + /// - `suppress_return_error` 为 true 时表示正在展开某个外围突然完成:return() 的 + /// 自身结果被丢弃,并保留在途异常跨调用存活。 + /// - 为 false 时(正常 for-of 退出)return() 的错误向外传播。 pub(crate) fn close_for_of_iterator( &mut self, iterator: JsValue, suppress_return_error: bool, ) -> Result<(), String> { @@ -549,9 +546,9 @@ impl Vm { Ok(()) } - /// IteratorClose every active for-of iterator above `depth`, used by `unwind()` to close - /// loops abandoned by a throw. Pops before closing so a re-entrant call can't re-close, - /// and so the next()-throw path (which already popped its own iterator) is skipped. + /// 对 `depth` 以上的所有活跃 for-of 迭代器执行 IteratorClose,供 `unwind()` 关闭 + /// 被异常中断的循环。先弹出再关闭,防止重入调用重复关闭,同时跳过 next()-throw + /// 路径(它已弹出自己的迭代器)。 pub(crate) fn close_for_of_above(&mut self, depth: usize) { while self.iters.for_of_iters.len() > depth { let Some(iterator) = self.iters.pop_for_of() else { @@ -574,7 +571,7 @@ impl Vm { .get(excluded_idx) .and_then(|v| { if v.is_string() { - // SAFETY: v is a string constant value. + // SAFETY: v 是字符串常量值。 Some(unsafe { (*v.as_string_ptr()).data.clone() }) } else { None diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 0115ec4..83750f6 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -124,7 +124,7 @@ impl Vm { self.ordinary_set_inner(obj, prop_name_si, val, receiver, false) } - /// Dispatch-time entry: caller (dispatch_set_prop etc.) already promoted the value. + /// 分发期入口:调用方(dispatch_set_prop 等)已对值做过 promote。 pub(crate) fn ordinary_set_dispatch( &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, ) -> Result<(), String> { @@ -235,17 +235,17 @@ impl Vm { Ok(val) } - /// Perform ordinary_get and write back IC with proto chain depth. + /// 执行 ordinary_get 并把 IC 按原型链深度写回。 fn proto_chain_ic_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result { let resolved = self.ordinary_get(obj, prop_name_si, receiver)?; - // Fast path: own property (depth=0). + // 快路径:自身属性(depth=0)。 if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { if !obj.is_accessor_meta(pos) { crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); } return Ok(resolved); } - // Walk proto chain for inherited properties. + // 沿原型链查找继承属性。 let mut cursor = obj.proto().as_js_object_ptr(); let mut depth = 1u8; while !cursor.is_null() { diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 8fdba64..0d4c226 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -138,7 +138,7 @@ impl Vm { let len = frame.caller_reg_limit as usize; self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]); self.save_stack.truncate(offset); - // spill 帧边界截断(D-08,仿 save_stack 窗口):子函数 spill 数据在帧恢复后丢弃 + // spill 帧边界截断:子函数写入的 spill 数据在帧恢复后丢弃。 self.spill_stack.truncate(frame.spill_offset as usize); self.regs[254] = frame.saved_this; self.regs[255] = frame.saved_new_target; @@ -192,7 +192,7 @@ impl Vm { self.restore_frame(frame); } } - // IteratorClose any for-of loops abandoned inside this handler's scope. + // 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。 self.close_for_of_above(handler.for_of_depth); if let Some(finally_pc) = handler.finally_pc { vm_trace!("unwind: entering finally at pc={}", finally_pc); @@ -216,8 +216,8 @@ impl Vm { self.restore_frame(frame); } let exc = self.exception_value.take().unwrap_or(JsValue::undefined()); - // Preserve the escaping JsValue so a for-of next()-throw can re-throw the original - // value instead of the flattened string (consumed by dispatch_for_of_done/next). + // 保留逃逸的 JsValue,使 for-of 的 next() 抛出时可重新抛出原值而非展平后的 + // 字符串(由 dispatch_for_of_done/next 消费)。 self.last_uncaught_value = Some(exc); let kind_str = self.pending_error_kind.take().unwrap_or("Error"); let exc_text = self.error_text(exc); diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index ed5bdb6..ab00611 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -1,13 +1,12 @@ -//! Grouped `Vm` state sub-structs. +//! `Vm` 的状态子结构分组。 //! -//! `Vm` owns these as fields (`gc_state`, `symbols`, `iters`, `profiling`) so -//! that workers refactoring one subsystem touch one struct instead of the -//! central `Vm` god-struct. Per the project's parallel-dev decisions: -//! Symbol/Iter/Profiling are full sinks (their logic is self-contained), while -//! `GcState` is field-classification only — GC `mark`/`sweep`/`alloc_object` -//! scan roots across *all* sub-structs and stay as `&mut Vm` methods. +//! `Vm` 以字段持有这些子结构(`gc_state`、`symbols`、`iters`、`profiling`), +//! 使各子系统维护者只需改动一个结构而非庞大的 `Vm` 主结构。约定: +//! Symbol/Iter/Profiling 为完整子模块(逻辑自包含);`GcState` 仅做字段分类—— +//! GC 的 mark/sweep/alloc_object 需要扫描跨全部子结构的根(regs、frames、 +//! 迭代器等),仍以 `&mut Vm` 方法驻留。 //! -//! This file defines fields only; methods live with `Vm`. +//! 本文件只定义字段;方法随 `Vm` 存放。 use std::cell::Cell; use std::collections::HashMap; @@ -20,11 +19,11 @@ use oxide_types::value::JsValue; use crate::session_gc::SessionGc; use crate::vm::ForInIter; -/// Session-arena and garbage-collection bookkeeping. +/// session arena 与 GC 簿记。 /// -/// Field-classify only. `mark`/`sweep`/`rewrite_vm_roots` live on `Vm` because -/// GC scans all sub-struct roots (regs, frames, for_in_iters, for_of_iters, -/// exception_value, etc.) — they cannot be confined to GcState. +/// 仅做字段分类。mark/sweep/rewrite_vm_roots 驻留在 `Vm` 上:GC 需扫描所有 +/// 子结构的根(regs、frames、for_in_iters、for_of_iters、exception_value 等), +/// 无法限制在 GcState 内。 pub(crate) struct GcState { pub(crate) session_epoch: bumpalo::Bump, pub(crate) session_gc: SessionGc, @@ -41,7 +40,7 @@ impl GcState { } } -/// `Symbol` interning state. +/// Symbol 的 intern 状态。 pub(crate) struct SymbolState { pub(crate) symbol_counter: u32, pub(crate) symbol_descriptions: Vec, @@ -83,7 +82,7 @@ impl SymbolState { } } -/// Live iterator state for `for-in` / `for-of`. +/// for-in / for-of 的活跃迭代器状态。 pub(crate) struct IterState { pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>, pub(crate) for_of_iters: Vec, @@ -134,7 +133,7 @@ impl IterState { } } -/// Inline-cache hit/miss and instruction counters. +/// inline cache 命中/未命中计数与指令计数。 pub(crate) struct ProfilingState { pub(crate) ic_hits: Cell, pub(crate) ic_misses: Cell, diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index b017fcb..03ab948 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -203,11 +203,11 @@ impl Vm { } pub(crate) fn clear_execution_state(&mut self) { - // Reset contract: - // - Clears register file, pc, frame/iterator stacks, saved execution stacks, - // try handlers, pending exceptions, and native call depth. - // - Leaves kernel-owned shared state intact. - // - `reset()` additionally clears bytecode/constants and resets epoch ownership. + // 重置契约: + // - 清空寄存器文件、pc、帧/迭代器栈、保存的执行栈、try 处理器、 + // 待处理异常与 native 调用深度。 + // - 保留 kernel 共享状态不变。 + // - `reset()` 额外清空 bytecode/constants 并重置 epoch 归属。 self.regs = [JsValue::undefined(); 256]; self.pc = 0; self.frames.clear(); @@ -244,7 +244,7 @@ impl Vm { self.new_string_owned(s.to_string()) } - /// Same as `new_string` but takes `String` by move, avoiding a clone. + /// 同 `new_string`,但以 move 接收 `String`,避免一次克隆。 pub fn new_string_owned(&mut self, s: String) -> JsValue { let len = s.len(); let ptr = Box::into_raw(Box::new(JsString::new(s))); @@ -258,34 +258,30 @@ impl Vm { self.kernel_core.perm_interner().intern(s).0 } - /// Intern a compile-time string literal as a permanent, process-lifetime - /// `JsString` value shared across sessions. Source literals and RegExp - /// source/flags recur (templated/repetitive code) and are immutable, so - /// sharing them via `PermInterner` restores cross-session string reuse — - /// without interning transient computed values, which stay session-heap - /// (`new_string`) so they remain collectable. + /// 把编译期字符串字面量 intern 为永久、进程生命周期内、跨 session 共享的 + /// `JsString` 值。源码字面量与 RegExp 的 source/flags 会反复出现(模板化/ + /// 重复代码)且不可变,经 `PermInterner` 共享可恢复跨 session 字符串复用—— + /// 不 intern 瞬态计算值(它们走 session 堆 `new_string`,保持可回收)。 pub fn perm_string(&self, s: &str) -> JsValue { let id = self.kernel_core.perm_interner().intern(s).0; JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(id)) } - /// Drop all session-heap `JsString` values. Called only on full isolation reset - /// (`full_reset` / `clear_full_reset_state`), where no surviving session object - /// can reference them. The lighter `reset()` deliberately keeps them alive, - /// mirroring session-object survival across evals. + /// 释放全部 session 堆 `JsString`。仅在完全隔离重置 + /// (`full_reset` / `clear_full_reset_state`)时调用,此时没有存活的 session 对象 + /// 会引用它们。较轻量的 `reset()` 刻意保留它们,与 session 对象跨 eval 存活一致。 fn free_session_string_heap_data(&mut self) { for ptr in self.gc_state.session_string_ptrs.drain(..) { - // SAFETY: each ptr came from Box::into_raw(Box::new(JsString)) in new_string - // and is dropped exactly once here. + // SAFETY: 每个指针来自 new_string 中的 Box::into_raw(Box::new(JsString)), + // 且只在这里恰好释放一次。 unsafe { drop(Box::from_raw(ptr)); } } } - /// Create a function JsObject for a BytecodeFunc constant. - /// When `is_arrow` is true, captures the current `this` (regs[254]) - /// for lexical this binding at call time. + /// 为 BytecodeFunc 常量创建函数 JsObject。 + /// 当 `is_arrow` 为 true 时,捕获当前 `this`(regs[254]),供调用时词法 this 绑定。 pub(crate) fn create_function_object( &mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool, needs_home_object: bool, @@ -364,9 +360,9 @@ impl Vm { } } - /// Convert a module's immutable constant pool to `JsValue`s. Infallible — the pool holds only - /// scalars + permanent-interner string values after CreateClosure/CreateRegExp pulled functions - /// and regexes out of the pool. `&self` so it can run inside `OnceLock::get_or_init`. + /// 把模块的不可变常量池转换为 `JsValue`。不可能失败——CreateClosure/CreateRegExp + /// 已把函数与正则移出常量池,池中只剩标量 + 永久 intern 字符串值。`&self` + /// 使其可运行于 `OnceLock::get_or_init` 内部。 pub(crate) fn convert_immutables(&self, constants: &[Constant]) -> Vec { constants.iter().map(|c| self.convert_constant(c)).collect() } @@ -491,13 +487,13 @@ mod tests { #[test] fn immutables_cache_filled_once_per_module() { let mut vm = Vm::new(); - // `f` recurses (same sub-module entered 4x). Its immutables convert once via OnceLock. + // `f` 递归(同一子模块进入 4 次),其不可变常量经 OnceLock 只转换一次。 let result = run_source(&mut vm, "function f(n){ if(n<=0){ return 'done'; } return f(n-1); } f(3)"); assert!(result.is_string()); assert_eq!(vm.lookup_str(result).as_deref(), Some("done")); - // Cache = top module + 1 sub-module (f); the sub-module slot was initialized by the calls. + // 缓存 = 顶层模块 + 1 个子模块(f);子模块槽由这些调用初始化。 assert_eq!(vm.immutables_cache.len(), 2); - // Sub-module constants now use temp_immutables (avoiding cache index collision) + // 子模块常量改走 temp_immutables(避免缓存下标冲突) } #[test] diff --git a/crates/oxide_vm/tests/arrow_tests.rs b/crates/oxide_vm/tests/arrow_tests.rs index 7838bee..94703c7 100644 --- a/crates/oxide_vm/tests/arrow_tests.rs +++ b/crates/oxide_vm/tests/arrow_tests.rs @@ -112,9 +112,8 @@ fn multiple_arrows_in_same_scope() { assert_eq!(eval("var a = () => 1, b = () => 2; a() + b()"), "3"); } -// NOTE: Arrow functions capturing lexical `this` from enclosing functions -// require nested function call support (sub_module flattening), which is -// a pre-existing limitation. These tests will be enabled in a future phase: +// 未支持:箭头函数从外围函数捕获词法 `this` 依赖嵌套函数调用(子模块展平), +// 是既有能力限制。以下测试待能力补齐后启用: // // fn arrow_captures_enclosing_this() { // // const o = {x:10, f:function(){ return (() => this.x)(); }}; o.f() @@ -127,4 +126,3 @@ fn multiple_arrows_in_same_scope() { fn closure_captures_outer_let() { assert_eq!(eval("var f; { let x = 7; f = () => x; } f()"), "7"); } - diff --git a/crates/oxide_vm/tests/builtins_date_tests.rs b/crates/oxide_vm/tests/builtins_date_tests.rs index 7f5c5fc..e261559 100644 --- a/crates/oxide_vm/tests/builtins_date_tests.rs +++ b/crates/oxide_vm/tests/builtins_date_tests.rs @@ -35,7 +35,7 @@ fn date_parse_invalid() { assert!(r.as_double().is_nan()); } -// -- new Date() constructor via JS -- +// ── 经 JS 调用 new Date() 构造器 ── #[test] fn date_new_multi_arg() { @@ -100,7 +100,7 @@ fn date_value_of() { #[test] fn date_to_iso_string() { let mut vm = Vm::new(); - // Parse an explicit ISO UTC string to avoid timezone dependency + // 解析显式 ISO UTC 字符串,避免时区依赖。 let r = eval(&mut vm, "new Date('2020-01-01T00:00:00Z').toISOString()").unwrap(); let s = str_val(&vm, r); assert!(s.starts_with("2020-01-01"), "got: {s}"); @@ -109,7 +109,7 @@ fn date_to_iso_string() { #[test] fn date_to_json() { let mut vm = Vm::new(); - // Parse an explicit ISO UTC string to avoid timezone dependency + // 解析显式 ISO UTC 字符串,避免时区依赖。 let r = eval(&mut vm, "new Date('2020-01-01T00:00:00Z').toJSON()").unwrap(); let s = str_val(&vm, r); assert!(s.starts_with("2020-01-01"), "got: {s}"); @@ -136,7 +136,7 @@ fn date_objects_use_bounded_numeric_coercion() { assert_eq!(r.as_double(), 0.0); } -// -- Plan 01 timezone-correct tests -- +// ── 时区正确性测试 ── #[test] fn date_local_getters_differ_from_utc() { @@ -145,7 +145,7 @@ fn date_local_getters_differ_from_utc() { let local_hour = r.as_double(); let r = eval(&mut vm, "var d = new Date(2020, 6, 15, 12, 0, 0); d.getUTCHours()").unwrap(); let utc_hour = r.as_double(); - // In UTC+0 they are equal (correct), otherwise they differ + // 在 UTC+0 下二者相等(正确),否则不相等。 if utc_hour != local_hour { assert_ne!(local_hour, utc_hour, "in non-UTC+0, local vs UTC hours should differ"); } @@ -155,7 +155,7 @@ fn date_local_getters_differ_from_utc() { #[test] fn date_constructor_uses_local_timezone() { let mut vm = Vm::new(); - // getTimezoneOffset = UTC - local; UTC hour = local + offset/60 + // getTimezoneOffset = UTC - local;UTC 小时 = local + offset/60 let r = eval(&mut vm, "var d = new Date(2020, 0, 1, 12, 0, 0); d.getUTCHours()").unwrap(); let utc_hour = r.as_double(); let r = eval(&mut vm, "var d = new Date(2020, 0, 1, 12, 0, 0); d.getTimezoneOffset()").unwrap(); @@ -167,7 +167,7 @@ fn date_constructor_uses_local_timezone() { #[test] fn date_constructor_defaults_from_current_time() { let mut vm = Vm::new(); - // D-02: missing args default to current time components + // 缺省参数回退到当前时间分量。 let r = eval(&mut vm, "var d = new Date(2020, 5); d.getMonth()").unwrap(); assert_eq!(r.as_double(), 5.0); let r = eval(&mut vm, "new Date(2020, 5).getDate()").unwrap(); @@ -177,7 +177,7 @@ fn date_constructor_defaults_from_current_time() { #[test] fn date_constructor_truncation() { let mut vm = Vm::new(); - // D-03: ToInteger truncation + // 年月参数按 ToInteger 截断。 let r = eval(&mut vm, "new Date(2024.7, 5.9).getFullYear()").unwrap(); assert_eq!(r.as_double(), 2024.0); let r = eval(&mut vm, "new Date(2024.7, 5.9).getMonth()").unwrap(); @@ -187,7 +187,7 @@ fn date_constructor_truncation() { #[test] fn date_setters_return_timestamp() { let mut vm = Vm::new(); - // D-05: setters return modified timestamp + // setter 返回修改后的时间戳。 let r = eval(&mut vm, "var d = new Date(0); var ts = d.setHours(12); ts == d.getTime() ? 1 : 0").unwrap(); assert_eq!(r.as_int(), 1); } @@ -195,12 +195,12 @@ fn date_setters_return_timestamp() { #[test] fn date_invalid_returns_nan() { let mut vm = Vm::new(); - // D-06: Invalid Date getters/setters return NaN + // Invalid Date 的 getter/setter 返回 NaN。 let r = eval(&mut vm, "new Date(NaN).getFullYear()").unwrap(); assert!(r.as_double().is_nan()); let r = eval(&mut vm, "new Date(NaN).setHours(0)").unwrap(); assert!(r.as_double().is_nan()); - // D-04: Invalid Date constructor + // Invalid Date 构造(NaN 参数)。 let r = eval(&mut vm, "new Date(NaN, 0).getTime()").unwrap(); assert!(r.as_double().is_nan()); } @@ -208,7 +208,7 @@ fn date_invalid_returns_nan() { #[test] fn date_get_timezone_offset_uses_date_timestamp() { let mut vm = Vm::new(); - // D-09/D-10: offset uses date's timestamp, not now() + // 偏移基于日期的自身时间戳,而非当前时间。 let r = eval(&mut vm, "new Date(0).getTimezoneOffset()").unwrap(); assert!(r.as_double().is_finite()); } @@ -216,7 +216,7 @@ fn date_get_timezone_offset_uses_date_timestamp() { #[test] fn date_setters_optional_chain() { let mut vm = Vm::new(); - // D-08: setFullYear supports optional month/day params + // setFullYear 支持可选的月/日参数。 let r = eval(&mut vm, "var d = new Date(2020, 0, 1); d.setFullYear(2025, 11, 25); d.getFullYear()").unwrap(); assert_eq!(r.as_double(), 2025.0); let r = eval(&mut vm, "var d = new Date(2020, 0, 1); d.setFullYear(2025, 11, 25); d.getMonth()").unwrap(); @@ -228,12 +228,12 @@ fn date_setters_optional_chain() { #[test] fn date_local_setters_preserve_utc_offset() { let mut vm = Vm::new(); - // setHours changes local hour; verify local hour value + // setHours 改变本地小时,验证本地小时值。 let r = eval(&mut vm, "var d = new Date(2020, 0, 1, 0, 0, 0); d.setHours(12); d.getHours()").unwrap(); assert_eq!(r.as_double(), 12.0, "setHours(12) should make getHours() return 12"); } -// -- Plan 02 new method tests -- +// ── 后续新增方法测试 ── #[test] fn date_set_utc_full_year_works() { @@ -313,7 +313,7 @@ fn date_get_year_deprecated() { #[test] fn date_set_year_deprecated() { let mut vm = Vm::new(); - // setYear(0-99) adds 1900; setYear(20) → fullYear 1920 + // setYear(0-99) 加 1900;setYear(20) → fullYear 1920。 let r = eval(&mut vm, "var d = new Date(2020, 0, 1); d.setYear(20); d.getFullYear()").unwrap(); assert_eq!(r.as_double(), 1920.0); } @@ -347,8 +347,8 @@ fn date_set_utc_invalid_returns_nan() { assert!(r.as_double().is_nan()); } -// setYear with no argument must not index a missing argument register (previously panicked); -// a missing / NaN year sets the date value to NaN and returns NaN (B.2.4.2). +// setYear 无参数时不得索引缺失的参数寄存器(此前会 panic); +// 缺失 / NaN 的年份把日期值置为 NaN 并返回 NaN(B.2.4.2)。 #[test] fn date_set_year_no_arg_returns_nan() { let mut vm = Vm::new(); @@ -384,8 +384,8 @@ fn date_set_year_four_digit_kept() { assert_eq!(r.as_double() as i64, 2020); } -// UTC setters called with no argument previously indexed a missing argument register -// (panic / process abort). A missing primary argument now yields NaN and sets the date NaN. +// UTC setter 无参数调用此前会索引缺失的参数寄存器(panic / 进程中止)。 +// 缺失的主参数现在产出 NaN 并把日期置为 NaN。 #[test] fn date_set_utc_setters_no_arg_return_nan() { let mut vm = Vm::new(); diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index 8be47cb..8c61f87 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -144,7 +144,7 @@ fn error_subtype_constructors_produce_named_objects() { assert_eq!(format!("{}", eval("new SyntaxError('boom').name == 'SyntaxError'").unwrap()), "true"); } -// New tests for prototype properties + constructor fixes +// ── 原型属性与构造器修复测试 ── #[test] fn error_prototype_has_name() { @@ -276,7 +276,7 @@ fn create_type_error_no_own_name() { assert_eq!(obj.prop_count(), 1); } -// Plan 02: toString tests +// ── toString 测试 ── #[test] fn error_to_string_basic() { @@ -319,7 +319,7 @@ fn error_to_string_non_object_throws() { } } -// format_error_message tests +// ── format_error_message 测试 ── #[test] fn format_error_message_both() { @@ -339,7 +339,7 @@ fn format_error_message_empty_name() { assert_eq!(result, "msg"); } -// stack tests +// ── stack 测试 ── #[test] fn error_stack_is_string() { diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index ea1bb17..3b18e2f 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -95,9 +95,9 @@ fn function_to_string_non_function_throws() { #[test] fn function_call_returns_object_stays_valid() { - // Regression: call_function_sync used to run bytecode in a sub-VM with a separate - // epoch; objects allocated in the sub-VM epoch became dangling after sub-VM drop. - // This test forces the returned object to be dereferenced after the call returns. + // 回归:call_function_sync 曾在一个带独立 epoch 的子 VM 中运行字节码; + // 子 VM epoch 中分配的对象在子 VM 销毁后成为悬垂指针。 + // 本测试强制在调用返回后解引用返回对象。 let mut vm = Vm::new(); let result = eval(&mut vm, "function f() { return {x: 42}; } f.call(null).x").unwrap(); assert_eq!(result, JsValue::int(42)); @@ -112,8 +112,8 @@ fn function_apply_returns_object_stays_valid() { #[test] fn getter_returns_object_stays_valid() { - // Same class of bug: ordinary_get sync-path (target_reg=None) for bytecode - // accessor ran in sub-VM; returned object was freed on sub-VM drop. + // 同类 bug:字节码 accessor 经 ordinary_get 同步路径(target_reg=None)在子 VM + // 中运行,返回对象在子 VM 销毁后被释放。 let mut vm = Vm::new(); let result = eval(&mut vm, "var o = { get p() { return {z: 7}; } }; o.p.z").unwrap(); assert_eq!(result, JsValue::int(7)); diff --git a/crates/oxide_vm/tests/builtins_map_tests.rs b/crates/oxide_vm/tests/builtins_map_tests.rs index d4642e4..36db6a4 100644 --- a/crates/oxide_vm/tests/builtins_map_tests.rs +++ b/crates/oxide_vm/tests/builtins_map_tests.rs @@ -65,7 +65,7 @@ fn tmap_size_and_clear() { assert_eq!(map_size(&mut vm, &[0]).unwrap().as_double(), 0.0); } -// -- JS eval tests using new keyword (all in one eval per test) -- +// ── 经 JS 使用 new 关键字的 eval 测试(每个测试一次 eval)── #[test] fn map_new_set_get() { diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index 437c64a..a4edeab 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -166,7 +166,7 @@ fn object_proto_has_own_property_call_works() { #[test] fn object_proto_property_is_enumerable_call_works() { let (_vm, result) = eval("Object.prototype.propertyIsEnumerable.call(Object, 'assign')").unwrap(); - // Built-in methods are non-enumerable per the ES spec. + // 内置方法按 ES 规范不可枚举。 assert_eq!(result, JsValue::bool(false)); } diff --git a/crates/oxide_vm/tests/builtins_string_tests.rs b/crates/oxide_vm/tests/builtins_string_tests.rs index 4108930..0cca2a5 100644 --- a/crates/oxide_vm/tests/builtins_string_tests.rs +++ b/crates/oxide_vm/tests/builtins_string_tests.rs @@ -268,7 +268,7 @@ fn string_call_conversion_stays_primitive() { assert_eq!(to_str(&vm, v), "123"); } -// ── Plan 01: replace tests ── +// ── replace 测试 ── #[test] fn string_replace_non_global_first_only() { @@ -305,7 +305,7 @@ fn string_replace_string_pattern() { assert_eq!(to_str(&vm, s), "heLlo"); } -// ── Plan 02: split tests ── +// ── split 测试 ── #[test] fn string_split_regex_capture_groups() { @@ -348,7 +348,7 @@ fn string_split_string_separator() { assert_eq!(obj.prop_count(), 3); } -// ── Plan 03: match tests ── +// ── match 测试 ── #[test] fn string_match_non_global_captures() { @@ -390,7 +390,7 @@ fn string_match_string_pattern() { assert_eq!(to_str(&vm, obj.get_prop_at(0)), "ll"); } -// ── Plan 04: matchAll tests ── +// ── matchAll 测试 ── #[test] fn string_match_all_returns_iterator() { @@ -420,7 +420,7 @@ fn string_match_all_next_exhausted() { assert!(done); } -// ── Plan 05: substring tests ── +// ── substring 测试 ── #[test] fn string_substring_nan_index() { @@ -443,7 +443,7 @@ fn string_substring_swap() { assert_eq!(to_str(&vm, s), "el"); } -// ── Plan 06: substr + at tests ── +// ── substr + at 测试 ── #[test] fn string_substr_positive() { @@ -487,7 +487,7 @@ fn string_at_out_of_range() { assert!(s.is_undefined()); } -// ── Plan 07: lastIndexOf tests ── +// ── lastIndexOf 测试 ── #[test] fn string_last_index_of_basic() { diff --git a/crates/oxide_vm/tests/coercion_tests.rs b/crates/oxide_vm/tests/coercion_tests.rs index e870e96..e1430d6 100644 --- a/crates/oxide_vm/tests/coercion_tests.rs +++ b/crates/oxide_vm/tests/coercion_tests.rs @@ -113,32 +113,31 @@ fn primitive_property_write_throws_clean_type_error() { } } -// --- Phase 14.0.1: type-conversion infrastructure (ENG-13) --- -// SC-1..SC-8 from ROADMAP. eval() returns the Display of the final value; -// every assertion below targets a bool/number result to avoid string-quoting -// ambiguity. +// --- 类型转换基础设施测试 --- +// eval() 返回最终值的 Display;以下断言都针对 bool/number 结果, +// 避免字符串引用的歧义。 -// SC-1 ToPrimitive: boxed Number unwraps via valueOf (was "NaN" before). +// ToPrimitive:装箱 Number 经 valueOf 解包。 #[test] fn to_primitive_number_boxed() { assert_eq!(eval("+new Number(42)"), "42"); } -// SC-1 ToPrimitive: boxed String coerces to its primitive in == (object vs string). +// ToPrimitive:装箱 String 在 == 中强转为原始值(对象 vs 字符串)。 #[test] fn to_primitive_string_boxed() { assert_eq!(eval("new String('x') == 'x'"), "true"); } -// SC-1 ToPrimitive: obj[Symbol.toPrimitive] is consulted (callback runs, returns 7). +// ToPrimitive:优先调用 obj[Symbol.toPrimitive](回调执行并返回 7)。 #[test] fn to_primitive_symbol_hint_invoked() { assert_eq!(eval("var o = {}; o[Symbol.toPrimitive] = function (h) { return 7; }; +o"), "7"); } -// SC-2 ToNumber: to_number_full throws TypeError on a Symbol (per D-02). -// Note: the unary-plus operator path uses coerce_number_bounded (no symbol -// throw) so `+Symbol()` yields NaN at the operator level; see test below. +// ToNumber:to_number_full 对 Symbol 抛 TypeError。 +// 注意:一元加号路径用 coerce_number_bounded(对 Symbol 不抛),因此运算符层的 +// +Symbol() 得到 NaN;见下个测试。 #[test] fn to_number_full_symbol_throws() { let mut vm = Vm::new(); @@ -148,73 +147,73 @@ fn to_number_full_symbol_throws() { assert!(result.unwrap_err().contains("Symbol"), "expected a Symbol TypeError message"); } -// SC-2/SC-7 operator-level reality: +Symbol is NaN (symbol-throw not wired into arithmetic). +// 运算符层实况:+Symbol 为 NaN(Symbol 抛出未接入算术路径)。 #[test] fn unary_plus_symbol_is_nan() { assert_eq!(eval("+Symbol('x')"), "NaN"); } -// SC-3 Abstract Equality: string vs number coerces. +// 抽象相等:字符串与数字互相强转。 #[test] fn abstract_eq_string_number() { assert_eq!(eval("'1' == 1"), "true"); } -// SC-3 Abstract Equality: bool->number recursion + string->number. +// 抽象相等:布尔转数字的递归 + 字符串转数字。 #[test] fn abstract_eq_bool_number() { assert_eq!(eval("'1' == true"), "true"); } -// SC-3 Abstract Equality: object vs number via ToPrimitive ([42] -> "42" -> 42). +// 抽象相等:对象 vs 数字经 ToPrimitive([42] -> "42" -> 42)。 #[test] fn abstract_eq_object_primitive() { assert_eq!(eval("[42] == 42"), "true"); } -// SC-3 Abstract Equality: null == undefined (regression guard, spec step 2-3). +// 抽象相等:null == undefined(回归锚,规范第 2-3 步)。 #[test] fn abstract_eq_null_undefined() { assert_eq!(eval("null == undefined"), "true"); } -// SC-3 Abstract Equality: null == 0 is false (spec falls through to step 14). +// 抽象相等:null == 0 为 false(规范落到第 14 步)。 #[test] fn abstract_eq_null_zero_mismatch() { assert_eq!(eval("null == 0"), "false"); } -// SC-3 Abstract Equality: same-type delegates to strict equality. +// 抽象相等:同类型委托给严格相等。 #[test] fn abstract_eq_same_type_string() { assert_eq!(eval("'abc' == 'abc'"), "true"); } -// SC-4 Strict Equality: +0 === -0 is true (verify-only; already correct). +// 严格相等:+0 === -0 为 true(仅验证)。 #[test] fn strict_eq_signed_zero_eval() { assert_eq!(eval("+0 === -0"), "true"); } -// SC-4 Strict Equality: NaN === NaN is false (verify-only; already correct). +// 严格相等:NaN === NaN 为 false(仅验证)。 #[test] fn strict_eq_nan_eval() { assert_eq!(eval("NaN === NaN"), "false"); } -// SC-5 Arithmetic +: object operand coerces via valueOf. +// 算术 +:对象操作数经 valueOf 强转。 #[test] fn add_object_valueof_coercion() { assert_eq!(eval("var o = {valueOf: function () { return 42; }}; o + 1"), "43"); } -// SC-6 Relational: object operands coerce via ToPrimitive then compare as strings. +// 关系比较:对象操作数经 ToPrimitive 后按字符串比较。 #[test] fn relational_object_coercion() { assert_eq!(eval("[3] > [2]"), "true"); } -// SC-1/D-02 Date: Date object coerces to its timestamp in == (default hint -> valueOf). +// Date 对象在 == 中强转为自身时间戳(默认 hint -> valueOf)。 #[test] fn date_object_equals_timestamp() { assert_eq!(eval("new Date(0) == 0"), "true"); diff --git a/crates/oxide_vm/tests/compiler_alignment_binding_tests.rs b/crates/oxide_vm/tests/compiler_alignment_binding_tests.rs index 1e4ba3c..7d33fe0 100644 --- a/crates/oxide_vm/tests/compiler_alignment_binding_tests.rs +++ b/crates/oxide_vm/tests/compiler_alignment_binding_tests.rs @@ -19,8 +19,7 @@ fn eval(source: &str) -> String { } } -// Destructured parameter followed by a loop body: the counter must count the parameter -// destructuring prologue or the loop's back-jump lands at the wrong PC. +// 解构参数后跟循环体:计数器必须计入参数解构 prologue,否则循环回跳落到错误 PC。 #[test] fn param_array_pattern_with_loop_body() { assert_eq!(eval("function f([a]){var s=0; while(a>0){s=s+1;a=a-1;} return s;} f([3])"), "3"); @@ -31,32 +30,31 @@ fn param_array_pattern_default_element() { assert_eq!(eval("function f([a=5]){return a;} f([])"), "5"); } -// Array assignment target with a default element followed by a loop. +// 含默认元素的数组赋值目标后跟循环。 #[test] fn array_assignment_default_element_then_loop() { assert_eq!(eval("var a,c=0; [a=5]=[]; while(c String { } } -// A class body must not drift the instruction count of code that follows it. Each repro -// places a bounded loop after the class so a wrong count corrupts the loop's jump target. +// 类体不得漂移其后代码的指令计数。每个复现都在类后放置有界循环, +// 计数错误会破坏循环跳转目标。 #[test] fn non_static_private_method_then_loop() { @@ -32,8 +32,7 @@ fn static_private_method_then_loop() { assert_eq!(eval("class C{static #m(){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3"); } -// Multi-word computed key — a single-identifier key happens to be one word and would not -// expose the drift. +// 多字计算键——单标识符键恰好是单字,暴露不出漂移。 #[test] fn computed_method_key_then_loop() { assert_eq!(eval("class C{[\"x\"+\"y\"](){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3"); @@ -44,8 +43,8 @@ fn computed_static_field_key_then_loop() { assert_eq!(eval("class C{static [\"a\"+\"b\"]=1;} var c=0; while(c<3){c=c+1;} c"), "3"); } -// Derived constructor with an instance field and a loop after super(): the field code is -// injected after SUPER_CALL, so the loop's jump target must account for it. +// 含实例字段的派生构造函数在 super() 后跟循环:字段代码注入在 SUPER_CALL 之后, +// 循环跳转目标必须计入它。 #[test] fn derived_ctor_field_then_post_super_loop() { assert_eq!( @@ -67,7 +66,7 @@ fn derived_ctor_statement_before_super() { ); } -// Non-derived class with an instance field and a ctor loop stays correct (unchanged path). +// 非派生类含实例字段与构造循环保持正确(路径未变)。 #[test] fn non_derived_ctor_field_then_loop() { assert_eq!( @@ -76,7 +75,7 @@ fn non_derived_ctor_field_then_loop() { ); } -// Behavioral regressions: methods/getters/private calls still work. +// 行为回归:方法/getter/私有调用仍正常。 #[test] fn plain_method_call_regression() { assert_eq!(eval("class C{m(){return 9;}} new C().m()"), "9"); diff --git a/crates/oxide_vm/tests/compiler_alignment_expr_tests.rs b/crates/oxide_vm/tests/compiler_alignment_expr_tests.rs index b871d13..6279cce 100644 --- a/crates/oxide_vm/tests/compiler_alignment_expr_tests.rs +++ b/crates/oxide_vm/tests/compiler_alignment_expr_tests.rs @@ -19,8 +19,8 @@ fn eval(source: &str) -> String { } } -// Non-simple `??` (side-effecting operands) followed by a label-bearing construct must not -// shift the downstream label id (which produced `Label ... not found` at emit time). +// 非简单 `??`(带副作用操作数)后跟带标签的构造不得把下游 label id 移位 +// (否则 emit 期报 `Label ... not found`)。 #[test] fn coalesce_nonsimple_then_if() { @@ -38,10 +38,9 @@ fn coalesce_nonsimple_then_while() { ); } -// A jump-bearing construct (if/loop) followed by a hoisted function declaration: -// the count pass must walk in the same function-hoisting order as the emit pass, -// otherwise the construct's jump targets drift past the hoisted body and the -// program loops forever at run time. +// 含跳转的构造(if/loop)后跟被提升的函数声明: +// 计数 pass 必须与 emit pass 采用相同的函数提升顺序, +// 否则构造的跳转目标越过提升的函数体,程序运行时无限循环。 #[test] fn if_then_hoisted_function() { @@ -58,9 +57,9 @@ fn while_then_hoisted_function() { assert_eq!(eval("var n=0; while(n<3){n=n+1;} function f(){return 100} n + f()"), "103"); } -// A hoisted function declaration is emitted before the `var` statements it closes over, -// so the count pass must register top-level `var` names too (not just function names); -// otherwise the hoisted body cannot resolve the outer var ("Identifier not defined"). +// 提升的函数声明先于它闭包的 `var` 语句发射, +// 因此计数 pass 必须同时登记顶层 `var` 名(不只是函数名); +// 否则提升的函数体无法解析外层 var("Identifier not defined")。 #[test] fn hoisted_function_reads_outer_var() { @@ -82,15 +81,14 @@ fn coalesce_nonsimple_then_for() { #[test] fn coalesce_nonsimple_short_circuits_on_value() { - // 0 is not nullish, so `a() ?? b()` is 0 and the loop never runs. + // 0 不是 nullish,因此 `a() ?? b()` 为 0,循环不会运行。 assert_eq!( eval("function a(){return 0;} function b(){return 9;} var v=a()??b(); var c=0; while(c String { } } -// B011 复现样例:switch 语句(checkpoint 复用槽)后跟 const 声明。 +// 复现样例:switch 语句(checkpoint 复用槽)后跟 const 声明。 // 修复前误抛 "Assignment to constant variable",修复后输出 5。 #[test] fn switch_then_const_declaration_no_false_positive() { diff --git a/crates/oxide_vm/tests/define_property_descriptor_tests.rs b/crates/oxide_vm/tests/define_property_descriptor_tests.rs index fa1d5b7..e669d4b 100644 --- a/crates/oxide_vm/tests/define_property_descriptor_tests.rs +++ b/crates/oxide_vm/tests/define_property_descriptor_tests.rs @@ -22,35 +22,35 @@ fn assert_err_contains(result: Result, expected: &str) { } } -// -- Partial writable update preserves existing value -- +// ── 部分 writable 更新保留既有值 ── #[test] fn define_property_partial_writable_preserves_value() { let r = eval_str("var obj={}; obj.x=42; Object.defineProperty(obj,'x',{writable:false}); obj.x").unwrap(); assert!(r.is_int() && r.as_int() == 42, "value should be preserved at 42, got {:?}", r); } -// -- Mixed descriptor (value+get) throws TypeError -- +// ── 混合描述符(value+get)抛 TypeError ── #[test] fn define_property_mixed_value_get_throws() { let result = eval_str("var obj={}; Object.defineProperty(obj,'x',{value:1,get:function(){}});"); assert_err_contains(result, "mix"); } -// -- Mixed descriptor (writable+set) throws TypeError -- +// ── 混合描述符(writable+set)抛 TypeError ── #[test] fn define_property_mixed_writable_set_throws() { let result = eval_str("var obj={}; Object.defineProperty(obj,'x',{writable:true,set:function(){}});"); assert_err_contains(result, "mix"); } -// -- Partial enumerable-only update preserves value -- +// ── 仅 enumerable 的部分更新保留既有值 ── #[test] fn define_property_partial_enumerable_only_preserves_value() { let r = eval_str("var obj={}; obj.x=42; Object.defineProperty(obj,'x',{enumerable:true}); obj.x").unwrap(); assert!(r.is_int() && r.as_int() == 42, "value should be preserved at 42, got {:?}", r); } -// -- getOwnPropertyDescriptor returns data fields (no get/set) -- +// ── getOwnPropertyDescriptor 返回数据字段(无 get/set)── #[test] fn get_own_property_descriptor_data_fields() { let r = eval_str( @@ -70,7 +70,7 @@ fn get_own_property_descriptor_data_fields() { assert!(obj.get_prop_at(5).is_undefined(), "set should be undefined"); } -// -- getOwnPropertyDescriptor returns accessor fields (no value/writable) -- +// ── getOwnPropertyDescriptor 返回访问器字段(无 value/writable)── #[test] fn get_own_property_descriptor_accessor_fields() { let r = eval_str( @@ -92,7 +92,7 @@ fn get_own_property_descriptor_accessor_fields() { ); } -// -- New property without value or accessor defaults to data descriptor (ES spec behavior) -- +// ── 无 value/accessor 的新属性默认为数据描述符(ES 规范行为)── #[test] fn define_property_new_attributes_only_defaults_value_undefined() { let r = eval_str("var obj={}; Object.defineProperty(obj,'x',{enumerable:true}); obj.x").unwrap(); @@ -103,7 +103,7 @@ fn define_property_new_attributes_only_defaults_value_undefined() { ); } -// -- Partial configurable on existing property preserves value -- +// ── 既有属性的部分 configurable 更新保留既有值 ── #[test] fn define_property_partial_configurable_preserves_value() { let r = eval_str("var obj={}; obj.x=1; Object.defineProperty(obj,'x',{configurable:false}); [obj.x]").unwrap(); diff --git a/crates/oxide_vm/tests/destructuring_tests.rs b/crates/oxide_vm/tests/destructuring_tests.rs index 5656ddc..b516cdd 100644 --- a/crates/oxide_vm/tests/destructuring_tests.rs +++ b/crates/oxide_vm/tests/destructuring_tests.rs @@ -88,10 +88,9 @@ fn function_and_method_parameter_destructuring() { assert_num(result, 10.0); } -// Regression: a for-of whose assignment target has a NESTED rest pattern (the rest -// element is itself a pattern, e.g. `[...[x]]`) used to loop forever / error, because the -// counter undercounted the rest target's instructions vs the emitter, corrupting the loop's -// jump offsets. (Object rest with a nested target is a syntax error per spec, so untested.) +// 回归:赋值目标含嵌套 rest 模式(rest 元素本身是模式,如 `[...[x]]`)的 for-of +// 曾无限循环 / 报错,因为计数器相对 emitter 少计了 rest 目标的指令,破坏了循环跳转 +// 偏移。(对象 rest 嵌套目标按规范是语法错误,故不测。) #[test] fn for_of_assignment_nested_rest_target_runs_once() { let mut vm = Vm::new(); @@ -109,7 +108,7 @@ fn for_of_assignment_nested_rest_empty_body_binds_inner() { #[test] fn for_of_assignment_nested_rest_with_leading_element() { let mut vm = Vm::new(); - // outer iterates once; a=1, rest=[2,3,4], then [x,y] from rest -> x=2, y=3 + // 外层迭代一次;a=1,rest=[2,3,4],再从 rest 解构 [x,y] -> x=2, y=3 let result = eval(&mut vm, "var a,x,y; for([a,...[x,y]] of [[1,2,3,4]]){} a*100+x*10+y").unwrap(); assert_num(result, 123.0); } diff --git a/crates/oxide_vm/tests/for_in_null_undefined_tests.rs b/crates/oxide_vm/tests/for_in_null_undefined_tests.rs index a205bde..932dcbc 100644 --- a/crates/oxide_vm/tests/for_in_null_undefined_tests.rs +++ b/crates/oxide_vm/tests/for_in_null_undefined_tests.rs @@ -44,15 +44,14 @@ fn for_in_null_does_not_execute_body() { #[test] fn for_in_null_does_not_throw() { - // Must NOT produce a "vm error: ..." — a quiet empty loop, then a sentinel value. + // 不得产生 "vm error: ..."——静默空循环,然后哨兵值。 assert_eq!(eval("for(var k in null){} 7"), "7", "for-in null must not throw"); } #[test] fn for_in_number_primitive_currently_throws() { - // Interim behavior: ToObject coercion for non-null/undefined primitives is not - // implemented yet, so a number right-hand side still throws TypeError. When - // ToObject lands this becomes an empty loop and this assertion should change. + // 过渡行为:非 null/undefined 基本类型的 ToObject 强转尚未实现, + // 因此数字右侧仍抛 TypeError。ToObject 落地后它应变成空循环,届时本断言需变更。 let out = eval("for(var k in 42){}"); assert!(out.contains("TypeError"), "expected TypeError for number primitive, got: {out}"); } diff --git a/crates/oxide_vm/tests/for_in_ordering_tests.rs b/crates/oxide_vm/tests/for_in_ordering_tests.rs index 4e8c1f0..d7ff938 100644 --- a/crates/oxide_vm/tests/for_in_ordering_tests.rs +++ b/crates/oxide_vm/tests/for_in_ordering_tests.rs @@ -19,10 +19,9 @@ fn eval(source: &str) -> String { } } -// Order is asserted via a position map built inside JS, so the comparison -// returns a boolean (JsValue Display only reveals number/bool, not string -// contents). This is also tolerant of any inherited enumerable keys the -// engine appends after the object's own keys. +// 顺序经 JS 内部构建的位置映射断言,比较返回布尔(JsValue 的 Display 只暴露 +// number/bool,不暴露字符串内容)。该写法也容忍引擎在对象自有键之后追加的 +// 任意继承可枚举键。 #[test] fn for_in_ordering_integer_indices_first() { diff --git a/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs b/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs index 97702aa..bd25bf9 100644 --- a/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs +++ b/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs @@ -19,10 +19,10 @@ fn eval(source: &str) -> String { } } -// Spec (ECMA-262 §13.7.5): next()-throw propagates the ORIGINAL value WITHOUT calling -// return(); IteratorClose (return()) runs only on BODY abrupt completion (throw/break/return). -// return() is observed via globalThis (a function CAN read/write globalThis properties; -// it cannot write a plain outer `var` in this engine). +// 规范(ECMA-262 §13.7.5):next()-throw 传播原始值且不调用 return(); +// IteratorClose(return())只在循环体突然完成(throw/break/return)时执行。 +// return() 经 globalThis 观测(函数可读写 globalThis 属性; +// 本引擎中它不能写入外层普通 `var`)。 #[test] fn for_of_next_throw_is_catchable_with_original_type() { diff --git a/crates/oxide_vm/tests/for_of_builtin_iter_tests.rs b/crates/oxide_vm/tests/for_of_builtin_iter_tests.rs index a59fc33..b9a35ea 100644 --- a/crates/oxide_vm/tests/for_of_builtin_iter_tests.rs +++ b/crates/oxide_vm/tests/for_of_builtin_iter_tests.rs @@ -19,10 +19,10 @@ fn eval(source: &str) -> String { } } -// Assertions stay inside JS and resolve to a boolean/number, because JsValue Display -// reveals numbers/bools but not string/object contents. +// 断言放在 JS 内部并解析为布尔/数字,因为 JsValue 的 Display 只暴露 +// 数字/布尔,不暴露字符串/对象内容。 -// ---- for-of loop over Map/Set (default iterators) ---- +// ── 对 Map/Set(默认迭代器)的 for-of ── #[test] fn for_of_map_yields_key_value_entries() { @@ -52,7 +52,7 @@ fn for_of_empty_set_runs_zero_times() { assert_eq!(eval("var s=new Set();var r=0;for(var v of s){r=r+1;}r===0"), "true"); } -// ---- Map.prototype.{entries,values,keys} ---- +// ── Map.prototype.{entries,values,keys} ── #[test] fn map_values_iterator_yields_values() { @@ -80,7 +80,7 @@ fn map_iterator_reports_done_at_end() { assert_eq!(eval("var m=new Map();var it=m.values();it.next().done===true"), "true"); } -// ---- Set.prototype.{entries,values,keys} ---- +// ── Set.prototype.{entries,values,keys} ── #[test] fn set_values_iterator_yields_values() { diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index 5731120..bf94f4f 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -127,7 +127,7 @@ fn fd_calls_builtin_two_args() { assert!((result.as_double() - 9.0).abs() < 0.0001); } -// --- Multiple FDs, first FD calls second --- +// ── 多个函数声明,首个调用第二个 ── #[test] fn fd_chain_call() { @@ -136,7 +136,7 @@ fn fd_chain_call() { assert_eq!(result.as_int(), 10); } -// --- new Xxx() in function --- +// ── 函数内的 new Xxx() ── #[test] fn fd_returns_new_object() { @@ -152,7 +152,7 @@ fn fd_returns_new_array() { assert!(result.is_object()); } -// --- this expression --- +// ── this 表达式 ── #[test] fn this_in_function_reads_value() { diff --git a/crates/oxide_vm/tests/property_delete_tests.rs b/crates/oxide_vm/tests/property_delete_tests.rs index dc02c92..60faae2 100644 --- a/crates/oxide_vm/tests/property_delete_tests.rs +++ b/crates/oxide_vm/tests/property_delete_tests.rs @@ -22,7 +22,7 @@ fn assert_err_contains(result: Result, expected: &str) { } } -// -- Delete configurable property succeeds -- +// ── 删除可配置属性成功 ── #[test] fn delete_configurable_property_succeeds_and_returns_true() { let r = eval_many(&[ @@ -34,7 +34,7 @@ fn delete_configurable_property_succeeds_and_returns_true() { assert!(r.is_bool() && r.as_bool(), "delete configurable should return true, got {:?}", r); } -// -- Delete non-configurable property throws -- +// ── 删除不可配置属性抛错 ── #[test] fn delete_non_configurable_throws_type_error() { let result = eval_many(&[ @@ -45,21 +45,21 @@ fn delete_non_configurable_throws_type_error() { assert_err_contains(result, "cannot delete non-configurable property"); } -// -- Delete non-existent property returns true -- +// ── 删除不存在的属性返回 true ── #[test] fn delete_non_existent_returns_true() { let r = eval_many(&["var obj = {}", "delete obj.x"]).unwrap(); assert!(r.is_bool() && r.as_bool(), "delete non-existent should return true, got {:?}", r); } -// -- Delete default (no explicit defineProperty) configurable succeeds -- +// ── 删除默认(无显式 defineProperty)可配置属性成功 ── #[test] fn delete_default_configurable_succeeds() { let r = eval_many(&["var obj = {}", "obj.x = 1", "delete obj.x"]).unwrap(); assert!(r.is_bool() && r.as_bool(), "delete default configurable should return true, got {:?}", r); } -// -- Delete non-configurable proto property on child: child doesn't own it, so returns true -- +// ── 子对象删除原型上不可配置的属性:子对象不拥有它,因此返回 true ── #[test] fn delete_non_configurable_proto_property_on_child_returns_true() { let r = eval_many(&[ diff --git a/crates/oxide_vm/tests/property_keys_order_tests.rs b/crates/oxide_vm/tests/property_keys_order_tests.rs index 1e6febf..b74f213 100644 --- a/crates/oxide_vm/tests/property_keys_order_tests.rs +++ b/crates/oxide_vm/tests/property_keys_order_tests.rs @@ -22,7 +22,7 @@ fn keys_to_strings(result: JsValue) -> Vec { for i in 0..len { let v = obj.get_prop_at(i); if v.is_string() { - // SAFETY: string value pointing into VM arena (caller keeps vm alive) + // SAFETY: 字符串值指向 VM arena(调用方保持 vm 存活)。 let s = unsafe { &*v.as_string_ptr() }; keys.push(s.data.clone()); } @@ -30,7 +30,7 @@ fn keys_to_strings(result: JsValue) -> Vec { keys } -// -- Integer keys in ascending order -- +// ── 整型键按升序 ── #[test] fn keys_integer_order_ascending() { let r = eval_str("var obj={}; obj[3]='c'; obj[1]='a'; obj[2]='b'; Object.keys(obj).length").unwrap(); @@ -40,7 +40,7 @@ fn keys_integer_order_ascending() { assert_eq!(keys, vec!["1", "2", "3"], "integer keys should be ascending, got {:?}", keys); } -// -- Mixed keys: integers first, then strings in creation order -- +// ── 混合键:整型优先,字符串按创建序 ── #[test] fn keys_mixed_order_integers_first() { let (_vm, r) = eval("var obj={}; obj[2]='b'; obj['1']='a'; obj['a']=1; Object.keys(obj)").unwrap(); @@ -58,7 +58,7 @@ fn keys_mixed_order_integers_first() { } } -// -- Object.keys excludes non-enumerable properties -- +// ── Object.keys 排除不可枚举属性 ── #[test] fn keys_excludes_non_enumerable() { let r = eval_str( @@ -68,7 +68,7 @@ fn keys_excludes_non_enumerable() { assert!(r.is_int() && r.as_int() == 1, "keys should exclude non-enumerable, got {:?}", r); } -// -- Object.getOwnPropertyNames includes non-enumerable properties -- +// ── Object.getOwnPropertyNames 包含不可枚举属性 ── #[test] fn get_own_property_names_includes_non_enumerable() { let r = eval_str("var obj={a:1}; Object.defineProperty(obj,'hidden',{value:2,enumerable:false}); Object.getOwnPropertyNames(obj).length").unwrap(); @@ -79,7 +79,7 @@ fn get_own_property_names_includes_non_enumerable() { ); } -// -- Object.entries excludes non-enumerable properties -- +// ── Object.entries 排除不可枚举属性 ── #[test] fn entries_excludes_non_enumerable() { let r = eval_str( @@ -89,7 +89,7 @@ fn entries_excludes_non_enumerable() { assert!(r.is_int() && r.as_int() == 1, "entries should exclude non-enumerable, got {:?}", r); } -// -- Object.values excludes non-enumerable properties -- +// ── Object.values 排除不可枚举属性 ── #[test] fn values_excludes_non_enumerable() { let r = eval_str( @@ -99,7 +99,7 @@ fn values_excludes_non_enumerable() { assert!(r.is_int() && r.as_int() == 1, "values should exclude non-enumerable, got {:?}", r); } -// -- Leading zero strings are NOT integer indices -- +// ── 前导零字符串不是整型下标 ── #[test] fn keys_with_leading_zero_not_integer_index() { let (_vm, r) = eval("Object.keys({'01':'a','1':'b'})").unwrap(); diff --git a/crates/oxide_vm/tests/property_receiver_tests.rs b/crates/oxide_vm/tests/property_receiver_tests.rs index 8efeb3d..e3c3994 100644 --- a/crates/oxide_vm/tests/property_receiver_tests.rs +++ b/crates/oxide_vm/tests/property_receiver_tests.rs @@ -15,7 +15,7 @@ fn eval_str(source: &str) -> Result { eval(source).map(|(_vm, v)| v) } -// Getter on proto sets marker on receiver, then check receiver has marker (not proto) +// 原型上的 getter 在 receiver 上设置标记,随后检查 receiver 持有标记(而非原型)。 #[test] fn getter_on_proto_this_is_receiver() { let r = eval_str( @@ -24,7 +24,7 @@ fn getter_on_proto_this_is_receiver() { assert!(r.is_int() && r.as_int() == 1, "getter should set marker on child (receiver), got {:?}", r); } -// Getter on deep proto chain: grandchild → child → proto. Getter defined on proto. +// 深层原型链上的 getter:grandchild → child → proto,getter 定义在 proto 上。 #[test] fn getter_on_proto_deep_chain_this_is_receiver() { let r = eval_str( @@ -33,7 +33,7 @@ fn getter_on_proto_deep_chain_this_is_receiver() { assert!(r.is_bool() && r.as_bool(), "grandchild should have _deep marker, got {:?}", r); } -// Setter on proto receives receiver as this, sets marker on receiver not proto +// 原型上的 setter 以 receiver 作为 this,在 receiver 而非原型上设置标记。 #[test] fn setter_on_proto_this_is_receiver() { let (_vm, r) = eval( @@ -42,7 +42,7 @@ fn setter_on_proto_this_is_receiver() { assert!(r.is_string(), "child should have _set_by_setter, got {:?}", r); } -// Dynamic getter (obj[expr]) preserves receiver +// 动态 getter(obj[expr])保持 receiver。 #[test] fn dynamic_getter_on_proto_this_is_receiver() { let r = eval_str( @@ -51,7 +51,7 @@ fn dynamic_getter_on_proto_this_is_receiver() { assert!(r.is_int() && r.as_int() == 1, "dynamic getter should set marker on child, got {:?}", r); } -// Dynamic setter (obj[expr]=val) preserves receiver +// 动态 setter(obj[expr]=val)保持 receiver。 #[test] fn dynamic_setter_on_proto_this_is_receiver() { let (_vm, r) = eval( @@ -60,7 +60,7 @@ fn dynamic_setter_on_proto_this_is_receiver() { assert!(r.is_string(), "child should have _dyn_set after dynamic setter, got {:?}", r); } -// Own getter on object — this is the object itself +// 对象自身的 getter——this 即对象本身。 #[test] fn own_getter_this_is_self() { let r = eval_str( @@ -69,7 +69,7 @@ fn own_getter_this_is_self() { assert!(r.is_int() && r.as_int() == 42, "own getter should set marker on self, got {:?}", r); } -// Own setter on object — this is the object itself +// 对象自身的 setter——this 即对象本身。 #[test] fn own_setter_this_is_self() { let (_vm, r) = eval( @@ -78,7 +78,7 @@ fn own_setter_this_is_self() { assert!(r.is_string(), "own setter should set marker on self, got {:?}", r); } -// Getter returning undefined does not crash +// 返回 undefined 的 getter 不崩溃。 #[test] fn getter_returns_undefined_does_not_crash() { let r = eval_str( @@ -87,7 +87,7 @@ fn getter_returns_undefined_does_not_crash() { assert!(r.is_undefined(), "getter returning nothing should yield undefined, got {:?}", r); } -// String .length still works after proto getter is defined (primitive auto-boxing not broken) +// 定义原型 getter 后字符串 .length 仍正常(基本类型自动装箱未被破坏)。 #[test] fn string_length_still_works_after_proto_getter() { let r = eval_str("'hello'.length").unwrap(); diff --git a/crates/oxide_vm/tests/property_write_protection_tests.rs b/crates/oxide_vm/tests/property_write_protection_tests.rs index de0a178..29a167f 100644 --- a/crates/oxide_vm/tests/property_write_protection_tests.rs +++ b/crates/oxide_vm/tests/property_write_protection_tests.rs @@ -22,7 +22,7 @@ fn assert_err_contains(result: Result, expected: &str) { } } -// -- Assign to non-writable own property throws TypeError -- +// ── 向不可写自身属性赋值抛 TypeError ── #[test] fn assign_to_non_writable_throws() { let result = eval_many(&[ @@ -33,7 +33,7 @@ fn assign_to_non_writable_throws() { assert_err_contains(result, "read-only"); } -// -- Assign to writable own property succeeds -- +// ── 向可写自身属性赋值成功 ── #[test] fn assign_to_writable_succeeds() { let r = eval_many(&[ @@ -46,7 +46,7 @@ fn assign_to_writable_succeeds() { assert!(r.is_int() && r.as_int() == 2, "assign to writable should set value to 2, got {:?}", r); } -// -- Assign to inherited non-writable property on proto throws -- +// ── 向原型上不可写的继承属性赋值抛错 ── #[test] fn assign_to_inherited_non_writable_throws() { let result = eval_many(&[ @@ -58,9 +58,9 @@ fn assign_to_inherited_non_writable_throws() { assert_err_contains(result, "read-only"); } -// -- Assign creates own property on child when proto's is non-writable but child has no own prop +// ── 原型属性不可写但子对象无自有属性时,赋值会在子对象上创建自有属性 // ponytail: spec allows shadowing when proto prop is configurable; engine doesn't yet. -// The engine correctly throws for non-writable proto props. Full shadowing is a follow-up. +// 引擎对不可写原型属性正确抛错;完整遮蔽作为后续工作。 #[test] fn assign_non_writable_proto_configurable_prop_throws() { let result = eval_many(&[ diff --git a/crates/oxide_vm/tests/proto_chain_tests.rs b/crates/oxide_vm/tests/proto_chain_tests.rs index 56aedd4..cf0a7e4 100644 --- a/crates/oxide_vm/tests/proto_chain_tests.rs +++ b/crates/oxide_vm/tests/proto_chain_tests.rs @@ -24,7 +24,7 @@ macro_rules! assert_undefined { }; } -// -- Proto chains: every (non-Object) constructor.prototype should inherit from Object.prototype -- +// ── 原型链:每个(非 Object)构造器的 prototype 都应继承自 Object.prototype ── #[test] fn array_proto_chain() { assert_bool!(eval("Object.getPrototypeOf(Array.prototype) === Object.prototype"), true, "Array proto"); @@ -32,10 +32,10 @@ fn array_proto_chain() { #[test] fn function_proto_chain() { - // NOTE: Function global is not registered (pre-existing issue). - // Function.prototype.__proto__ IS wired to Object.prototype via kernel patch. - // Verified by checking that Function.prototype (accessed via other means) has correct proto. - // Skip this test until Function global is registered. + // 未支持:Function 全局尚未注册(既有能力限制)。 + // Function.prototype.__proto__ 已经由 kernel 补丁接到 Object.prototype。 + // 通过其它途径访问 Function.prototype 可验证其原型正确。 + // 待 Function 全局注册后再启用本测试。 } #[test] @@ -87,14 +87,14 @@ fn regexp_proto_chain() { assert_bool!(eval("Object.getPrototypeOf(RegExp.prototype) === Object.prototype"), true, "RegExp proto"); } -// -- Object.prototype is the root, its __proto__ is null -- +// ── Object.prototype 是根,其 __proto__ 为 null ── #[test] fn object_proto_is_root() { let r = eval("Object.getPrototypeOf(Object.prototype)").unwrap(); assert!(r.is_null(), "Object.prototype.__proto__ should be null"); } -// -- instanceof now works for all builtins against Object -- +// ── 所有 builtin 对 Object 的 instanceof 现在都成立 ── #[test] fn array_instanceof_object() { assert_bool!(eval("[] instanceof Object"), true, "[] instanceof Object"); @@ -108,16 +108,16 @@ fn function_instanceof_object() { #[test] fn string_instanceof_object() { - // NOTE: new String requires NEW_EXPRESSION support for user constructors. - // Pre-existing limitation. Verify String.prototype is accessible. + // 未支持:new String 依赖用户构造器的 NEW_EXPRESSION 支持(既有能力限制)。 + // 验证 String.prototype 可访问。 let r = eval("typeof String.prototype").unwrap(); assert!(r.is_string(), "String.prototype should be object"); } #[test] fn boolean_not_instanceof_function() { - // NOTE: Function global is not registered (pre-existing issue). - // Skip until Function global registration is fixed. + // 未支持:Function 全局尚未注册(既有能力限制)。 + // 待 Function 全局注册修复后再启用。 } #[test] @@ -179,7 +179,7 @@ fn derived_constructor_inherits_parent_constructor_chain() { ); } -// -- instanceof for native types should NOT respond to their own false positives -- +// ── native 类型的 instanceof 不应响应自身的误判 ── #[test] fn number_not_instanceof_array() { assert_bool!(eval("new Number(1) instanceof Array"), false, "Number instanceof Array"); @@ -190,7 +190,7 @@ fn object_not_instanceof_regexp() { assert_bool!(eval("({}) instanceof RegExp"), false, "({}) instanceof RegExp"); } -// -- Primitives should NOT be instanceof their wrapper constructors (no autoboxing) -- +// ── 基本类型不应 instanceof 其包装构造器(不自动装箱)── #[test] fn primitives_not_instanceof() { assert_bool!(eval("1 instanceof Number"), false, "1 instanceof Number"); @@ -198,7 +198,7 @@ fn primitives_not_instanceof() { assert_bool!(eval("true instanceof Boolean"), false, "true instanceof Boolean"); } -// -- VOID regression -- +// ── VOID 回归 ── #[test] fn void_returns_undefined() { assert_undefined!(eval("void 0"), "void 0"); diff --git a/crates/oxide_vm/tests/scoping_tests.rs b/crates/oxide_vm/tests/scoping_tests.rs index ae300cb..7fc963c 100644 --- a/crates/oxide_vm/tests/scoping_tests.rs +++ b/crates/oxide_vm/tests/scoping_tests.rs @@ -95,11 +95,11 @@ fn closure_nested_expr_capture() { #[test] fn for_let_per_iteration_independent() { let _ = eval("var fns=[]; for(let i=0;i<3;i++){ fns.push(function(){return i}); } fns[0]()+fns[1]()+fns[2]()"); - // TODO: Plan 04 — implement for-let per-iteration binding + // 未支持:for-let 逐次迭代绑定尚未实现。 } #[test] fn tdz_access_before_init_throws() { let _ = eval("x; let x=1"); - // TODO: Plan 04 — implement precise TDZ + // 未支持:精确 TDZ 尚未实现。 } diff --git a/crates/oxide_vm/tests/spill_stack_tests.rs b/crates/oxide_vm/tests/spill_stack_tests.rs index 26a8b5f..e30f26e 100644 --- a/crates/oxide_vm/tests/spill_stack_tests.rs +++ b/crates/oxide_vm/tests/spill_stack_tests.rs @@ -1,4 +1,4 @@ -//! VM spill 栈执行语义测试(D-06/D-07/D-08):MOV 复制、SPILL/UNSPILL 往返、 +//! VM spill 栈执行语义测试:MOV 复制、SPILL/UNSPILL 往返、 //! 帧边界(父 spill 跨子调用完好)、嵌套双 spill 交互、UNSPILL 越界防御。 //! //! 手工 IR 是唯一可达路径——RegAlloc 未接入,真实 JS 编译产物不会含三指令。 @@ -28,7 +28,7 @@ fn int_module(insts: Vec, value: i32, n_registers: u8, nested: Vec