docs: 补充注释与格式化
This commit is contained in:
@@ -373,7 +373,7 @@ pub fn array_concat<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
/// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。
|
||||
pub fn array_to_string<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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);
|
||||
|
||||
@@ -54,8 +54,8 @@ pub(crate) fn new_array_buffer<H: VmHost>(vm: &mut H, data: Vec<u8>) -> *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<Vec<u8>> pointer, matching the existing RegExp storage pattern.
|
||||
// SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明 `Box<Vec<u8>>`
|
||||
// 指针,与既有 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<Vec<u8>>` in native_fn.
|
||||
// SAFETY: ArrayBuffer 在 native_fn 槽中存 `Box<Vec<u8>>`。
|
||||
let data = unsafe { Box::from_raw(data_ptr) };
|
||||
let bytes = std::mem::size_of::<Vec<u8>>() + data.capacity();
|
||||
obj.set_native_fn(None);
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn boolean_constructor<H: VmHost>(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
|
||||
|
||||
@@ -155,8 +155,8 @@ pub fn data_view_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
byte_offset,
|
||||
byte_length,
|
||||
}));
|
||||
// SAFETY: DataView objects are not callable, so native_fn is an opaque Box<DataViewData>
|
||||
// pointer, matching ArrayBuffer and RegExp typed-storage patterns.
|
||||
// SAFETY: DataView 对象不可调用,native_fn 存不透明 `Box<DataViewData>` 指针,
|
||||
// 与 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(
|
||||
|
||||
@@ -155,7 +155,7 @@ pub fn date_constructor<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pub fn date_set_year<H: VmHost>(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() {
|
||||
|
||||
@@ -25,7 +25,7 @@ fn bind_dispatcher<H: VmHost>(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<u8> = args.iter().skip(1).copied().collect();
|
||||
invoke_target(vm, bound_target, bound_this, &arg_regs)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ pub fn function_apply<H: VmHost>(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();
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn make_iterator_for_value<H: VmHost>(vm: &mut H, value: JsValue) -> Result<
|
||||
let next_fn = make_native_function(vm, "next", iterator_wrapper_next::<H> 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::<H> as *const (), 0);
|
||||
vm.set_or_create_prop_value(wrapper_obj, return_si, return_fn);
|
||||
@@ -104,8 +104,8 @@ pub fn iterator_wrapper_next<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(
|
||||
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<H: VmHost>(vm: &mut H, args: &[u8]) -> Nativ
|
||||
map_set_next_dispatch::<H>(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<H: VmHost>(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
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn json_parse<H: VmHost>(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<H: VmHost>(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<H: VmHost>(
|
||||
};
|
||||
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<H: VmHost>(
|
||||
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<H: VmHost>(
|
||||
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<H: VmHost>(
|
||||
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<H: VmHost>(
|
||||
|
||||
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);
|
||||
|
||||
@@ -17,16 +17,15 @@ macro_rules! native_try {
|
||||
|
||||
pub(crate) type MapInner = indexmap::IndexMap<SetKey, JsValue>;
|
||||
|
||||
/// 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<IndexMap>` 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<IndexMap>` 由 `new_map_inner()` 创建,进程退出前不释放(生命周期与
|
||||
/// epoch 绑定,属有意为之)。native 调用为单线程,同一 Map 对象同时至多
|
||||
/// 存在一个活 `*mut` 别名。
|
||||
fn get_map_inner<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<IndexMap<SetKey, JsValue>>`.
|
||||
// Alignment: IndexMap requires at most 8-byte alignment; the global allocator satisfies this.
|
||||
// SAFETY: native_data 持有 `alloc_map` 写入的裸指针,即有效的
|
||||
// 堆分配 `Box<IndexMap<SetKey, JsValue>>`;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"));
|
||||
|
||||
@@ -15,7 +15,7 @@ fn arg1<H: VmHost>(vm: &mut H, args: &[u8]) -> f64 {
|
||||
}
|
||||
|
||||
fn arg2<H: VmHost>(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)
|
||||
|
||||
@@ -84,7 +84,6 @@ pub fn object_keys<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
/// `Object.prototype.valueOf`:返回 this 本身(配合 OrdinaryToPrimitive 的兜底)。
|
||||
pub fn object_proto_value_of<H: VmHost>(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]))
|
||||
}
|
||||
|
||||
|
||||
@@ -88,10 +88,10 @@ pub fn regexp_constructor<H: VmHost>(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<regex::Regex> 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<regex::Regex>` 指针。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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
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] }));
|
||||
|
||||
|
||||
@@ -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::<JsValue, u64>(self.0) == std::mem::transmute::<JsValue, u64>(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::<JsValue, u64>(self.0).hash(state) }
|
||||
}
|
||||
}
|
||||
@@ -60,15 +60,14 @@ impl Hash for SetKey {
|
||||
|
||||
pub(crate) type SetInner = indexmap::IndexSet<SetKey>;
|
||||
|
||||
/// 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<IndexSet>` 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<IndexSet>` 由 `new_set_inner()` 创建,Set 生命周期内不释放。
|
||||
/// native 调用为单线程,同一 Set 对象同时至多存在一个活 `*mut` 别名。
|
||||
fn get_set_inner<H: VmHost>(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<H: VmHost>(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<IndexSet<SetKey>>`.
|
||||
// Alignment: IndexSet requires at most 8-byte alignment; the global allocator satisfies this.
|
||||
// SAFETY: native_data 持有 `alloc_set` 写入的裸指针,即有效的
|
||||
// 堆分配 `Box<IndexSet<SetKey>>`;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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pub fn set_keys<H: VmHost>(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,
|
||||
|
||||
@@ -544,13 +544,13 @@ pub fn string_ends_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pub fn string_split<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
None => return NativeResult::Ok(JsValue::int(-1)),
|
||||
};
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
None => return NativeResult::Ok(vm.new_string(&s)),
|
||||
};
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
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));
|
||||
|
||||
@@ -4,36 +4,36 @@ fn stub_error<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
stub_error(vm, "Atomics")
|
||||
}
|
||||
|
||||
@@ -89,8 +89,8 @@ fn create_typed_array<H: VmHost>(
|
||||
byte_offset,
|
||||
length,
|
||||
}));
|
||||
// SAFETY: TypedArray instances are not callable; native_fn stores opaque Box<TypedArrayData>,
|
||||
// matching ArrayBuffer/DataView typed-object storage in this VM.
|
||||
// SAFETY: TypedArray 实例不可调用,native_fn 存不透明 `Box<TypedArrayData>`,
|
||||
// 与本 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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -50,22 +50,17 @@ pub struct CompiledModule {
|
||||
pub param_base: u8,
|
||||
pub builtin_reg_map: Vec<(String, u32)>,
|
||||
pub sub_modules: Vec<CompiledModule>,
|
||||
/// 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<String>,
|
||||
/// 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<UpvalueCapture>,
|
||||
pub cells_needed: u8,
|
||||
|
||||
@@ -6,21 +6,19 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Generate the `OpCode` enum plus its `TryFrom<u8>` 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<u8>`、`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`]。
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -4,7 +4,11 @@ use crate::{BasicBlock, Cfg};
|
||||
|
||||
/// 追加 exit 哨兵块(`0..0`,置末尾),从各块 succs 反推 preds。
|
||||
pub(super) fn finalize(mut blocks: Vec<BasicBlock>, 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<BasicBlock>, 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();
|
||||
|
||||
+57
-35
@@ -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<usize>,
|
||||
@@ -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<BasicBlock>,
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<bool> {
|
||||
let len = f.insts.len();
|
||||
let mut heads = vec![false; len + 1];
|
||||
@@ -25,10 +25,7 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec<bool> {
|
||||
// 跳转的 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;
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec<BasicBlock
|
||||
let mut blocks: Vec<BasicBlock> = 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<BasicBlock
|
||||
blocks[i].succs.push((i + 1, EdgeKind::Fallthrough));
|
||||
}
|
||||
OpCode::RETURN | OpCode::HALT => {
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -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; }");
|
||||
|
||||
@@ -51,7 +51,7 @@ enum Commands {
|
||||
file: Option<String>,
|
||||
#[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() {
|
||||
|
||||
@@ -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<LruCache<u64, Arc<CompiledModule>>>,
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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<bool>) {
|
||||
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()];
|
||||
|
||||
|
||||
+21
-17
@@ -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 保持原值,不参与重建");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<bool> {
|
||||
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<bool> {
|
||||
#[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;
|
||||
|
||||
|
||||
@@ -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 不递归不回收");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JsValue, String>) -> (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");
|
||||
}
|
||||
|
||||
@@ -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<JsValue, String>) -> (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));
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
//! 类编译核心预留模块(当前为空,为后续类相关公共状态预留位置)。
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
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<String> = 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<String> = names.into_iter().collect();
|
||||
names.sort();
|
||||
names
|
||||
|
||||
@@ -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<IRFunction>,
|
||||
/// 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<IRFunction, String> {
|
||||
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<u32> = None;
|
||||
for stmt in &program.body {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
|
||||
@@ -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<Option<usize>>,
|
||||
pub(crate) loop_stack: Vec<(LabelId, LabelId)>,
|
||||
pub(crate) switch_stack: Vec<LabelId>,
|
||||
/// Active labeled-statement scopes (resolves `break label` / `continue label`).
|
||||
/// 活动标签语句作用域(解析 `break label` / `continue label`)。
|
||||
pub(crate) label_scopes: Vec<LabelScope>,
|
||||
/// Label names awaiting binding to the next emitted loop's continue target.
|
||||
/// 等待绑定到下一个循环 continue 目标的标签名。
|
||||
pub(crate) pending_loop_labels: Vec<String>,
|
||||
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;
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,12 +11,12 @@ impl Emitter {
|
||||
fn emit_arrow_function_expression(
|
||||
&self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
// 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<u32, String> {
|
||||
// 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();
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<u32, String> {
|
||||
fn emit_chain_expression(
|
||||
&self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<u32, String> {
|
||||
fn emit_binary_expression(
|
||||
&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<u32, String> {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<u32, String> {
|
||||
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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ mod function_decl;
|
||||
mod var;
|
||||
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_declaration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
|
||||
pub(crate) fn emit_declaration_domain(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u32>, String> {
|
||||
match stmt {
|
||||
Statement::VariableDeclaration(_) => self.emit_variable_declaration_statement(stmt, ctx),
|
||||
Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<Option<u32>, String> {
|
||||
pub(crate) fn emit_do_while_statement(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u32>, String> {
|
||||
let Statement::DoWhileStatement(dw) = stmt else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
+115
-53
@@ -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<u32> {
|
||||
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<u32> {
|
||||
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));
|
||||
|
||||
@@ -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 处理入口。
|
||||
|
||||
@@ -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<String>,
|
||||
/// 本函数是否为顶层脚本函数(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,
|
||||
|
||||
@@ -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<CompiledModule, String> {
|
||||
// 溢出检查 1:常量池(先于寄存器——超大常量池伴生的海量 vreg 会使后续检查的
|
||||
// 稠密 bitset 表示爆内存;常量池超限是更基础的失效,先报它)
|
||||
@@ -47,7 +47,7 @@ pub fn lower(f: &IRFunction) -> Result<CompiledModule, String> {
|
||||
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
|
||||
|
||||
@@ -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 中不出现裸寄存器下标,语义见文件头 `//!`。
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<JsObject>, P<JsObject>) {
|
||||
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<JsObject>, proto: &P<JsObject>) {
|
||||
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());
|
||||
|
||||
@@ -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<PermInterner>,
|
||||
@@ -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<BuiltinWorld>,
|
||||
pub global_object: P<JsObject>,
|
||||
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);
|
||||
|
||||
@@ -24,18 +24,17 @@ pub struct Shape {
|
||||
pub id: ShapeId,
|
||||
pub property_name: StringIndex,
|
||||
pub parent: Option<ShapeId>,
|
||||
/// 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<Vec<Option<Arc<Shape>>>>,
|
||||
transitions: DashMap<u64, ShapeId>,
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<u64, Vec<u32>>,
|
||||
entries: RwLock<Vec<PermEntry>>,
|
||||
/// 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<Vec<Option<Box<JsString>>>>,
|
||||
}
|
||||
|
||||
@@ -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<u64> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<bool>>, 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<bool>>, 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<bool>>, Vec<
|
||||
}
|
||||
|
||||
/// 从 entry 出发 DFS 沿 succs 存储序(确定序)收集 postorder 后反转;
|
||||
/// 不可达块按升序追加在末尾。纯 Vec + visited 位图(B010)。
|
||||
/// 不可达块按升序追加在末尾。纯 Vec + visited 位图,保证确定性。
|
||||
fn reverse_postorder(cfg: &Cfg) -> Vec<usize> {
|
||||
let n = cfg.blocks.len();
|
||||
let mut visited = vec![false; n];
|
||||
@@ -131,9 +131,9 @@ fn reverse_postorder(cfg: &Cfg) -> Vec<usize> {
|
||||
#[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 消费生效");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! LiveInfo:liveness 分析输出视图(D-15:pass 输出,不住进 IR)。
|
||||
//! LiveInfo:liveness 分析输出视图(pass 输出,不住进 IR)。
|
||||
//!
|
||||
//! 全部 live 集为按 reg 号索引的 bitset(`Vec<bool>`,长度 = 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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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<u32, Alloc>,
|
||||
/// spill 决策表(按 vreg 升序)
|
||||
pub spills: Vec<SpillPlan>,
|
||||
/// 物理峰值(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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AllocMap, String> {
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<u32> {
|
||||
let mut real = BTreeSet::new();
|
||||
@@ -85,7 +85,7 @@ pub(super) fn build(
|
||||
|
||||
// ── 预着色 ──
|
||||
let mut pre_colors: BTreeMap<u32, u32> = 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<u32> = 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<u32, BTreeSet<u32>> 去重后转 Vec 排序)
|
||||
let mut adj_sets: BTreeMap<u32, BTreeSet<u32>> = 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<u32, u32>, out: &mut Vec<u32>) {
|
||||
for sub in nested {
|
||||
for inst in &sub.insts {
|
||||
@@ -196,7 +202,7 @@ fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap<u32, u32>, 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);
|
||||
|
||||
@@ -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<AllocMap, String>`。纯函数,只读 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<AllocMap, String> {
|
||||
if f.insts.is_empty() {
|
||||
return Ok(AllocMap::new());
|
||||
@@ -27,12 +27,17 @@ pub fn color(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
|
||||
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 已回写");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user