From 35efd73ea0edd02b5d024969668e73bffbcd4a21 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 12 Oct 2026 13:15:05 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=9F=9F?= =?UTF-8?q?=E5=8A=A0=E6=A8=A1=E6=9D=BF=E6=8A=98=E5=8F=A0=E4=B8=BA=E5=8D=95?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 40 +++--- Cargo.toml | 2 +- crates/oxide_emit/src/expr/operator/fold.rs | 103 ++++++++++++++- crates/oxide_emit/src/expr/operator/mod.rs | 10 +- .../src/expr/template/template_literal.rs | 8 ++ crates/oxide_emit/tests/emit_ir_tests.rs | 118 +++++++++++++++++- crates/oxide_emit/tests/fold_tests.rs | 7 +- 7 files changed, 257 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7e51a92..361a47c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.16.15" +version = "0.16.16" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.16.15" +version = "0.16.16" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.16.15" +version = "0.16.16" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.16.15" +version = "0.16.16" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1257,7 +1257,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.16.15" +version = "0.16.16" dependencies = [ "num-bigint", "oxc_allocator", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1299,7 +1299,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1308,7 +1308,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.16.15" +version = "0.16.16" dependencies = [ "bumpalo", "dashmap", @@ -1322,7 +1322,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1334,7 +1334,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.16.15" +version = "0.16.16" dependencies = [ "tracing", "tracing-appender", @@ -1343,11 +1343,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.16.15" +version = "0.16.16" [[package]] name = "oxide_parser" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1362,7 +1362,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1378,7 +1378,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.16.15" +version = "0.16.16" dependencies = [ "num-bigint", "num-traits", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.16.15" +version = "0.16.16" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1411,7 +1411,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.16.15" +version = "0.16.16" dependencies = [ "bumpalo", "dashmap", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.16.15" +version = "0.16.16" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 821360dd..69b62627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.16.15" +version = "0.16.16" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_emit/src/expr/operator/fold.rs b/crates/oxide_emit/src/expr/operator/fold.rs index d3a7b27c..cd0aead9 100644 --- a/crates/oxide_emit/src/expr/operator/fold.rs +++ b/crates/oxide_emit/src/expr/operator/fold.rs @@ -1,18 +1,29 @@ -//! 双常量折叠:两操作数均为字面量且通过语义守卫时,编译期折成单常量, +//! 常量折叠:操作数均为字面量且通过语义守卫时,编译期折成单常量, //! 发射单条 `LOAD_CONST`。折值编码逐运算符镜像运行时指令的产物编码, //! 保证物化后与运行时执行结果逐字节一致。 //! -//! 三条语义守卫:任一操作数为 BigInt 字面量不折(BigInt 混合运行期抛 +//! 两个域并列:数值域(`literal_constant`/`fold_binary`,两操作数形态, +//! 守卫排除字符串与 BigInt)与字符串域(`literal_units`/`fold_string_chain`/ +//! `fold_template`,Addition 链与全字面量模板,单元口径与运行期 +//! `push_units_to` 逐段一致)。 +//! +//! 数值域语义守卫:任一操作数为 BigInt 字面量不折(BigInt 混合运行期抛 //! TypeError,BigInt 与 BigInt 有独立指令族);`DIV`/`MOD` 除数为零不折、 //! `EXP` 指数为负不折(保持运行时指令为除零/负指数编码的唯一来源)。 -//! 字符串字面量不折(字符串域是独立任务)。 +//! 字符串域语义守卫:Addition 链须至少一个字符串字面量(全数字链落回 +//! 数值域与 `CONCAT_N`,两域零重叠);模板须全部 cooked 为 Some。 use oxide_bytecode::module::Constant; use oxide_parser::{BinaryOperator, Expression, UnaryOperator}; -use oxide_runtime_api::{abstract_eq_primitives, relational_compare, strict_equality, to_int32, to_number, to_uint32}; +use oxide_runtime_api::{ + abstract_eq_primitives, bigint_to_string, js_number_to_string, relational_compare, strict_equality, to_int32, + to_number, to_uint32, +}; +use oxide_types::string_forge::{decode_key, encode_key}; use oxide_types::value::JsValue; use crate::is_int_literal; +use crate::shared::string_pool::pool_key_marker; /// 字面量表达式提取常量:剥括号与一元负号后,数字(`is_int_literal` 分派 /// `Int`/`Number`)、布尔、null 三类;BigInt 字面量、字符串字面量、RegExp 与 @@ -186,3 +197,87 @@ fn relational(l: &Constant, r: &Constant, swap: bool, negate: bool) -> Option Option> { + let (inner, negated) = peel_negation(expr); + match inner { + Expression::StringLiteral(s) if !negated => Some(decode_key(&pool_key_marker(&s.value, s.lone_surrogates))), + Expression::NumericLiteral(n) => { + let value = if negated { -n.value } else { n.value }; + let text = if is_int_literal(value) { + (value as i32).to_string() + } else { + js_number_to_string(value) + }; + Some(text.encode_utf16().collect()) + } + Expression::BigIntLiteral(b) => { + let value = if let Ok(v) = b.value.parse::() { + num_bigint::BigInt::from(v) + } else { + num_bigint::BigInt::parse_bytes(b.value.as_bytes(), 10)? + }; + let value = if negated { -value } else { value }; + Some(bigint_to_string(&value).encode_utf16().collect()) + } + Expression::BooleanLiteral(b) if !negated => { + let text = if b.value { "true" } else { "false" }; + Some(text.encode_utf16().collect()) + } + Expression::NullLiteral(_) if !negated => Some("null".encode_utf16().collect()), + _ => None, + } +} + +/// 判定操作数是否为字符串字面量(剥括号):规范判定整条 `+` 链为字符串 +/// 拼接的条件。 +fn is_string_literal(expr: &Expression) -> bool { + let mut e = expr; + while let Expression::ParenthesizedExpression(p) = e { + e = &p.expression; + } + matches!(e, Expression::StringLiteral(_)) +} + +/// Addition 链字符串域折叠:全操作数经 `literal_units` 可定值且至少一个是 +/// 字符串字面量时,拼接各段单元经 `encode_key` 回池键形态,返回 +/// `Constant::String`;全数字链(无字符串字面量)返回 `None` 落回 `CONCAT_N`。 +pub(crate) fn fold_string_chain(operands: &[&Expression]) -> Option { + // 字符串域守卫置于首行:至少一个字符串字面量,全数字链留给数值域。 + if !operands.iter().any(|op| is_string_literal(op)) { + return None; + } + let mut units: Vec = Vec::new(); + for op in operands { + units.extend(literal_units(op)?); + } + Some(Constant::String(encode_key(&units))) +} + +/// 模板字面量折叠:全部 quasi 的 cooked 为 Some 且全部内插表达式经 +/// `literal_units` 可定值时,按 quasi 与表达式交替序拼接单元经 `encode_key` +/// 回池键形态,返回 `Constant::String`;任一 cooked 为 None(非法转义, +/// 解析期 SyntaxError 的防御路径)返回 `None` 落回 `TEMPLATE_STR`。 +pub(crate) fn fold_template(tl: &oxide_parser::TemplateLiteral) -> Option { + // cooked 守卫置于首行:任一段为 None 时整模板不折。 + if !tl.quasis.iter().all(|q| q.value.cooked.is_some()) { + return None; + } + let mut units: Vec = Vec::new(); + for (i, q) in tl.quasis.iter().enumerate() { + let cooked = q.value.cooked.as_ref().expect("守卫已确认全部 cooked 为 Some"); + units.extend(decode_key(&pool_key_marker(cooked, q.lone_surrogates))); + if let Some(expr) = tl.expressions.get(i) { + units.extend(literal_units(expr)?); + } + } + Some(Constant::String(encode_key(&units))) +} diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 46db97bf..7a304f30 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -2,7 +2,7 @@ //! 逻辑与 `??` 用短路跳转,复合求值利用 `is_side_effect_free` 优化。 //! 函数:`emit_operator` 及各类 `emit_*_expression`。 -mod fold; +pub(crate) mod fold; use crate::{is_side_effect_free, BinaryOperator, CompileCtx, Emitter}; use oxide_bytecode::module::Constant; @@ -50,6 +50,14 @@ impl Emitter { let mut operands = Vec::new(); collect_add_operands(&bin.left, &mut operands); operands.push(&bin.right); + // 字符串域链折叠:全操作数为可折叠字面量且至少一个字符串字面量时, + // 编译期折单字符串常量(单元口径见 fold.rs),发射单条 LOAD_CONST。 + if let Some(folded) = fold::fold_string_chain(&operands) { + let idx = ctx.add_constant(folded); + let r = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(r), idx)); + return Ok(r); + } // 上限防寄存器压力:CONCAT_N 单点读全部操作数(须同时存活),超长链回退 // 左结合 ADD(寄存器随链复用,纯表达式寄存器占用有界)。 if operands.len() >= 3 && operands.len() <= MAX_CONCAT_N_OPERANDS { diff --git a/crates/oxide_emit/src/expr/template/template_literal.rs b/crates/oxide_emit/src/expr/template/template_literal.rs index d1e76f80..c4585328 100644 --- a/crates/oxide_emit/src/expr/template/template_literal.rs +++ b/crates/oxide_emit/src/expr/template/template_literal.rs @@ -10,6 +10,14 @@ impl Emitter { pub(crate) fn emit_template_literal_expression( &self, tl: &oxide_parser::TemplateLiteral, ctx: &mut CompileCtx, ) -> Result { + // 全字面量模板折叠:全部内插表达式为可折叠字面量且全部 cooked 为 Some 时, + // 编译期折单字符串常量,发射单条 LOAD_CONST(单元口径见 fold.rs)。 + if let Some(folded) = crate::expr::operator::fold::fold_template(tl) { + let idx = ctx.add_constant(folded); + let r = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(r), idx)); + return Ok(r); + } let r = ctx.alloc_reg(); let quasis = &tl.quasis; let expressions = &tl.expressions; diff --git a/crates/oxide_emit/tests/emit_ir_tests.rs b/crates/oxide_emit/tests/emit_ir_tests.rs index 6c459905..b235452d 100644 --- a/crates/oxide_emit/tests/emit_ir_tests.rs +++ b/crates/oxide_emit/tests/emit_ir_tests.rs @@ -265,10 +265,11 @@ fn destructured_rest_and_holes() { } /// 面 6:模板字面量的 TEMPLATE_STR 面与 quasis 池键(键经 string_forge 编码, -/// 纯 ASCII 键按值还原即原形态)。 +/// 纯 ASCII 键按值还原即原形态)。全字面量模板已折单常量(见字符串域折叠 +/// 测试),此处用非字面量内插表达式保持 TEMPLATE_STR 路径。 #[test] fn template_literal_pool_keys() { - let src = "\"use strict\";\nconst t = `a${1}b`;\nconst tag = (s) => s[0];\ntag`x${2}y`"; + let src = "\"use strict\";\nvar x = 1;\nconst t = `a${x}b`;\nconst tag = (s) => s[0];\ntag`x${2}y`"; let ir = emit_ir(src); assert!(count_op(&ir.insts, OpCode::TEMPLATE_STR) >= 1); let pool = pool_strings(&ir); @@ -422,3 +423,116 @@ fn binary_literal_fold_entry() { assert!(ir.constants.contains(&Constant::Number(6.0)), "常量池应含乘法折值 Number(6.0)"); assert!(ir.constants.contains(&Constant::Int(3)), "常量池应含加法折值 Int(3)"); } + +/// 面 15:字符串域链折叠命中矩阵——全操作数为可折叠字面量且至少一个字符串 +/// 字面量时,Addition 链折单字符串常量:ADD/CONCAT_N 计数为 0、池含折值键。 +#[test] +fn string_chain_fold_hit() { + // 纯字符串链:池含 "ab"。 + let ir = emit_ir("var s = \"a\" + \"b\";"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0, "全字面量字符串链应折叠,ADD 计数为 0"); + assert_eq!(count_op(&ir.insts, OpCode::CONCAT_N), 0, "全字面量字符串链应折叠,CONCAT_N 计数为 0"); + assert!(pool_strings(&ir).contains("ab"), "池应含折值 ab"); + + // 字符串加数字加布尔加 null:池含 "a1truenull"。 + let ir = emit_ir("var s = \"a\" + 1 + true + null;"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0); + assert_eq!(count_op(&ir.insts, OpCode::CONCAT_N), 0); + assert!(pool_strings(&ir).contains("a1truenull"), "池应含折值 a1truenull"); + + // 字符串加 BigInt:池含 "x10"。 + let ir = emit_ir("var s = \"x\" + 10n;"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0); + assert!(pool_strings(&ir).contains("x10"), "池应含折值 x10"); + + // 字符串加非整数字面量(Number 臂走 js_number_to_string):池含 "a1.5"。 + let ir = emit_ir("var s = \"a\" + 1.5;"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0); + assert!(pool_strings(&ir).contains("a1.5"), "池应含折值 a1.5"); + + // 字符串加 -0(is_int_literal 为真走 Int 臂):池含 "a0"。 + let ir = emit_ir("var s = \"a\" + -0;"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0); + assert!(pool_strings(&ir).contains("a0"), "池应含折值 a0"); + + // 孤立 surrogate 字符串段:池键为 FFFD+hex4 marker 形态加 "a"。 + let ir = emit_ir("var s = \"\\ud800\" + \"a\";"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0); + let pool = pool_strings(&ir); + assert!(pool.contains("\u{FFFD}d800a"), "池键应为 FFFD+hex4 marker 形态加 a: {pool:?}"); + + // 超平面字符段:经 encode_key 良配对形态入池。 + let ir = emit_ir("var s = \"\\u{1F600}\" + \"a\";"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 0); + assert!(pool_strings(&ir).contains("\u{1F600}a"), "池应含超平面字符良配对形态加 a"); +} + +/// 面 16:字符串域链不折矩阵——含标识符操作数或全数字链不落字符串域, +/// 既有 CONCAT_N/ADD 路径计数不变、池不含折值键。 +#[test] +fn string_chain_fold_miss() { + // 标识符操作数(两操作数形态):不折,落回左结合 ADD。 + let ir = emit_ir("var x = 1; var s = x + \"a\";"); + assert_eq!(count_op(&ir.insts, OpCode::ADD), 1, "含标识符的链应落回 ADD"); + assert_eq!(count_op(&ir.insts, OpCode::CONCAT_N), 0); + assert!(!pool_strings(&ir).contains("xa"), "含标识符的链不应折出字符串键"); + + // 全数字链:数值域守卫排除,不折出字符串键,落回 CONCAT_N。 + let ir = emit_ir("var s = 1 + 2 + 3;"); + assert_eq!(count_op(&ir.insts, OpCode::CONCAT_N), 1, "全数字链应落回 CONCAT_N"); + assert!(!pool_strings(&ir).contains("123"), "全数字链不应折出字符串键 123"); + + // 字符串夹标识符(三操作数形态):不折,落回 CONCAT_N。 + let ir = emit_ir("var x = 1; var s = \"a\" + x + \"b\";"); + assert_eq!(count_op(&ir.insts, OpCode::CONCAT_N), 1, "含标识符的链应落回 CONCAT_N"); + assert!(!pool_strings(&ir).contains("axb"), "含标识符的链不应折出字符串键"); +} + +/// 面 17:模板字面量折叠命中矩阵——全内插表达式为可折叠字面量且全部 cooked +/// 为 Some 时折单字符串常量:TEMPLATE_STR 计数为 0、池含折值键。 +#[test] +fn template_fold_hit() { + let ir = emit_ir("var t = `a${1}b`;"); + assert_eq!(count_op(&ir.insts, OpCode::TEMPLATE_STR), 0, "全字面量模板应折叠,TEMPLATE_STR 计数为 0"); + assert!(pool_strings(&ir).contains("a1b"), "池应含折值 a1b"); + + // 内插 BigInt:池含 "x10y"。 + let ir = emit_ir("var t = `x${10n}y`;"); + assert_eq!(count_op(&ir.insts, OpCode::TEMPLATE_STR), 0); + assert!(pool_strings(&ir).contains("x10y"), "池应含折值 x10y"); + + // 无内插表达式的单 quasi 模板:折单串。 + let ir = emit_ir("var t = `hello`;"); + assert_eq!(count_op(&ir.insts, OpCode::TEMPLATE_STR), 0); + assert!(pool_strings(&ir).contains("hello"), "池应含折值 hello"); + + // 孤立 surrogate quasi:池键为 marker 形态。 + let ir = emit_ir("var t = `\\ud800${1}`;"); + assert_eq!(count_op(&ir.insts, OpCode::TEMPLATE_STR), 0); + let pool = pool_strings(&ir); + assert!(pool.contains("\u{FFFD}d8001"), "池键应为 FFFD+hex4 marker 形态加 1: {pool:?}"); +} + +/// 面 18:模板字面量不折矩阵——内插表达式非字面量时落回 TEMPLATE_STR; +/// 带标签模板走 GET_TEMPLATE_OBJECT 加 CALL 的独立路径,不经折叠。 +#[test] +fn template_fold_miss() { + // 表达式非字面量:TEMPLATE_STR 计数 1,池不含折值键。 + let ir = emit_ir("var x = 1; var t = `a${x}b`;"); + assert_eq!(count_op(&ir.insts, OpCode::TEMPLATE_STR), 1, "非字面量内插应落回 TEMPLATE_STR"); + assert!(!pool_strings(&ir).contains("axb"), "不折时池不应含折值键"); + + // 带标签模板:走 GET_TEMPLATE_OBJECT 加 CALL,不折。 + let ir = emit_ir("var tag = (s) => s[0]; var t = tag`a${1}`;"); + assert_eq!(count_op(&ir.insts, OpCode::TEMPLATE_STR), 0, "带标签模板不发 TEMPLATE_STR"); + assert_eq!(count_op(&ir.insts, OpCode::GET_TEMPLATE_OBJECT), 1, "带标签模板应发 GET_TEMPLATE_OBJECT"); + assert_eq!(count_op(&ir.insts, OpCode::CALL), 1, "带标签模板应发 CALL"); +} + +/// 面 19:字符串域折叠发射确定性——折叠路径纯函数无迭代序依赖,链折叠与 +/// 模板折叠源串同进程连续编译指纹全等。 +#[test] +fn string_fold_emission_stable() { + assert_emission_stable("var s = \"a\" + 1 + true + null;", false); + assert_emission_stable("var t = `a${1}b`;", false); +} diff --git a/crates/oxide_emit/tests/fold_tests.rs b/crates/oxide_emit/tests/fold_tests.rs index deea3030..62dabf43 100644 --- a/crates/oxide_emit/tests/fold_tests.rs +++ b/crates/oxide_emit/tests/fold_tests.rs @@ -75,7 +75,8 @@ fn fold_hit_matrix() { /// 守卫不折矩阵:断言目标操作码计数为 1(落回运行时路径)。 /// 除零守卫须同时覆盖 Int(0) 与 Number(±0.0);负指数守卫须同时覆盖 Int/Number -/// 负值;BigInt 字面量、字符串字面量、标识符均不折。 +/// 负值;BigInt 字面量、标识符均不折;字符串域守卫(全数字链无字符串字面量) +/// 落回 CONCAT_N。 #[test] fn fold_guard_matrix() { // (源串, 目标操作码) @@ -90,8 +91,8 @@ fn fold_guard_matrix() { ("1n + 2n", OpCode::ADD), ("1 + 1n", OpCode::ADD), ("1n < 2n", OpCode::LT), - // 字符串域不折。 - ("\"a\" + \"b\"", OpCode::ADD), + // 字符串域守卫:全数字链无字符串字面量,不落字符串域,落回 CONCAT_N。 + ("1 + 2 + 3", OpCode::CONCAT_N), // 标识符不折。 ("1 + x", OpCode::ADD), ];