perf: 字符串构造类方法改借用免整串拷贝

This commit is contained in:
2026-08-13 23:13:34 +08:00
parent 33a8539680
commit 781d0c832f
2 changed files with 165 additions and 118 deletions
+2 -2
View File
@@ -355,7 +355,7 @@ pub fn regexp_symbol_match<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if matches.is_empty() {
return NativeResult::Ok(JsValue::null());
}
NativeResult::Ok(crate::string::make_string_array(vm, &matches))
NativeResult::Ok(crate::string::make_string_array(vm, matches))
}
/// `RegExp.prototype[Symbol.replace](string, replacement)`:按匹配替换。
@@ -473,7 +473,7 @@ pub fn regexp_symbol_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if last_end <= haystack.len() && parts.len() < limit {
parts.push(haystack[last_end..].to_string());
}
NativeResult::Ok(crate::string::make_string_array(vm, &parts))
NativeResult::Ok(crate::string::make_string_array(vm, parts))
}
/// `RegExp.prototype[Symbol.matchAll](string)`:返回按 global 语义逐个产出
+163 -116
View File
@@ -185,15 +185,15 @@ pub fn string_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
))
}
pub(crate) fn make_string_array<H: VmHost>(vm: &mut H, parts: &[String]) -> JsValue {
pub(crate) fn make_string_array<H: VmHost>(vm: &mut H, parts: Vec<String>) -> JsValue {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = parts.len();
let arr =
vm.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()));
unsafe {
for (i, s) in parts.iter().enumerate() {
let sv = vm.new_string(s);
for (i, s) in parts.into_iter().enumerate() {
let sv = vm.new_string_owned(s);
(*arr).set_prop_at(i, sv);
}
(*arr).set_prop_count(n);
@@ -554,85 +554,104 @@ pub fn string_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `String.prototype.slice(start, end)`:按字符区间(支持负索引)取子串。
pub fn string_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.slice called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// 位置参数先行(&mut 转换),后借 this 取子串。
let start_raw = if args.len() > 1 {
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32)
} else {
None
};
let end_raw = if args.len() > 2 {
Some(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32)
} else {
None
};
let s = try_string!(this_string(vm, args));
let n = char_len(&s) as i32;
let start = if args.len() > 1 {
let v = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32;
if v < 0 {
(n + v).max(0)
} else {
v.min(n)
let start = match start_raw {
Some(v) => {
if v < 0 {
(n + v).max(0)
} else {
v.min(n)
}
}
} else {
0
None => 0,
};
let end = if args.len() > 2 {
let v = vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32;
if v < 0 {
(n + v).max(0)
} else {
v.min(n)
let end = match end_raw {
Some(v) => {
if v < 0 {
(n + v).max(0)
} else {
v.min(n)
}
}
} else {
n
None => n,
};
let start = start as usize;
let end = end as usize;
let result = if start < end { char_slice(&s, start, end) } else { "" };
NativeResult::Ok(vm.new_string(result))
let result = if start < end { char_slice(&s, start as usize, end as usize) } else { "" };
let owned = result.to_string();
NativeResult::Ok(vm.new_string_owned(owned))
}
/// `String.prototype.substring(start, end)`:取子串,start/end 自动对调且取非负。
pub fn string_substring<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.substring called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// 寄存器取值先行(纯函数),后借 this 取子串。
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
let end_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
let s = try_string!(this_string(vm, args));
let n = char_len(&s) as i32;
let mut start = if args.len() > 1 {
let v = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
if v.is_nan() || v < 0.0 {
0
} else {
(v as i32).min(n)
let mut start = match start_arg {
Some(v) => {
let v = oxide_runtime_api::to_integer_or_infinity(v);
if v.is_nan() || v < 0.0 {
0
} else {
(v as i32).min(n)
}
}
} else {
0
None => 0,
};
let mut end = if args.len() > 2 {
let v = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
if v.is_nan() || v < 0.0 {
0
} else {
(v as i32).min(n)
let mut end = match end_arg {
Some(v) => {
let v = oxide_runtime_api::to_integer_or_infinity(v);
if v.is_nan() || v < 0.0 {
0
} else {
(v as i32).min(n)
}
}
} else {
n
None => n,
};
if start > end {
std::mem::swap(&mut start, &mut end);
}
let result = char_slice(&s, start as usize, end as usize);
NativeResult::Ok(vm.new_string(result))
let owned = result.to_string();
NativeResult::Ok(vm.new_string_owned(owned))
}
/// `String.prototype.substr(start, length)`:从 start 取 length 个字符(Annex B,支持负 start)。
pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.substr called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// 寄存器取值先行(纯函数),后借 this 截取。
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
let length_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
let s = try_string!(this_string(vm, args));
let len = char_len(&s) as isize;
let start = if args.len() > 1 {
let n = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as isize;
if n < 0 {
(len + n).max(0)
} else {
n.min(len)
let start = match start_arg {
Some(v) => {
let n = oxide_runtime_api::to_integer_or_infinity(v) as isize;
if n < 0 {
(len + n).max(0)
} else {
n.min(len)
}
}
} else {
0
None => 0,
} as usize;
let length = if args.len() > 2 {
(oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize).max(0) as usize
} else {
len as usize - start
let length = match length_arg {
Some(v) => (oxide_runtime_api::to_integer_or_infinity(v) as isize).max(0) as usize,
None => len as usize - start,
};
let result = take_chars(&s[byte_index_at_char(&s, start)..], length.min(len as usize - start));
NativeResult::Ok(vm.new_string_owned(result))
@@ -707,57 +726,65 @@ pub fn string_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
/// `String.prototype.toUpperCase`:全大写转换。
pub fn string_to_upper_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toUpperCase called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
NativeResult::Ok(vm.new_string_owned(s.to_uppercase()))
let s = try_string!(this_string(vm, args));
let upper = s.to_uppercase();
NativeResult::Ok(vm.new_string_owned(upper))
}
/// `String.prototype.toLowerCase`:全小写转换。
pub fn string_to_lower_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toLowerCase called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
NativeResult::Ok(vm.new_string_owned(s.to_lowercase()))
let s = try_string!(this_string(vm, args));
let lower = s.to_lowercase();
NativeResult::Ok(vm.new_string_owned(lower))
}
/// `String.prototype.trim`:去除两端空白。
pub fn string_trim<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trim called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
NativeResult::Ok(vm.new_string(s.trim()))
let s = try_string!(this_string(vm, args));
let trimmed = s.trim().to_string();
NativeResult::Ok(vm.new_string_owned(trimmed))
}
/// `String.prototype.repeat(count)`:重复字符串 count 次(当前上限 10000 防滥用)。
pub fn string_repeat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.repeat called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// count 转换先行(&mut 路径),后借 this 重复。
let n = if args.len() > 1 {
(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize).min(10000)
} else {
1
};
NativeResult::Ok(vm.new_string_owned(s.repeat(n)))
let s = try_string!(this_string(vm, args));
let repeated = s.repeat(n);
NativeResult::Ok(vm.new_string_owned(repeated))
}
/// `String.prototype.padStart(targetLength, padString)`:在头部补足 padString 到目标长度。
pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.padStart called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
let s_len = char_len(&s);
let target = if args.len() > 1 {
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
// 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。
let target_arg = if args.len() > 1 {
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize)
} else {
s_len
None
};
if target > 10000 {
builtins_error!("String.prototype.padStart: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
let pad = if args.len() > 2 {
as_string(vm, vm.reg(args[2])).into_owned()
} else {
" ".to_string()
};
let s = try_string!(this_string(vm, args));
let s_len = char_len(&s);
let target = target_arg.unwrap_or(s_len);
if target > 10000 {
builtins_error!("String.prototype.padStart: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.new_string_owned(s));
let owned = s.into_owned();
return NativeResult::Ok(vm.new_string_owned(owned));
}
let needed = target - s_len;
let pad_len = char_len(&pad).max(1);
@@ -770,29 +797,32 @@ pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `String.prototype.padEnd(targetLength, padString)`:在尾部补足 padString 到目标长度。
pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.padEnd called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
let s_len = char_len(&s);
let target = if args.len() > 1 {
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
// 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。
let target_arg = if args.len() > 1 {
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize)
} else {
s_len
None
};
if target > 10000 {
builtins_error!("String.prototype.padEnd: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
let pad = if args.len() > 2 {
as_string(vm, vm.reg(args[2])).into_owned()
} else {
" ".to_string()
};
let s = try_string!(this_string(vm, args));
let s_len = char_len(&s);
let target = target_arg.unwrap_or(s_len);
if target > 10000 {
builtins_error!("String.prototype.padEnd: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.new_string_owned(s));
let owned = s.into_owned();
return NativeResult::Ok(vm.new_string_owned(owned));
}
let needed = target - s_len;
let pad_len = char_len(&pad).max(1);
let reps = needed.div_ceil(pad_len);
let mut out = s;
let mut out = s.into_owned();
out.push_str(&take_chars(&pad.repeat(reps), needed));
NativeResult::Ok(vm.new_string_owned(out))
}
@@ -847,13 +877,19 @@ pub fn string_ends_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 分隔符可为 RegExp(含捕获组)或字符串。
pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.split called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// 规范: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]);
// 参数转换先行:separator 为对象时 ToString 可能触发用户代码(&mut 路径),
// limit 仅纯函数计算;之后借 this 源串扫描切分。
let sep_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
let is_undefined_sep = sep_val.map(|v| v.is_undefined()).unwrap_or(true);
let is_re = match sep_val {
Some(v) if !v.is_undefined() => is_regexp_obj(v, vm),
_ => false,
};
let sep = if is_undefined_sep || is_re {
String::new()
} else {
as_string(vm, sep_val.unwrap()).into_owned()
};
// ToUint32(limit),缺省为 2^32-1。
let limit = if args.len() > 2 {
let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
@@ -865,9 +901,14 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
u32::MAX as usize
};
if is_regexp_obj(sep_val, vm) {
let re_ptr = sep_val.as_js_object_ptr();
let s = try_string!(this_string(vm, args));
// 规范:separator 为 undefined 时返回 [this]。
if is_undefined_sep {
let owned = s.into_owned();
return NativeResult::Ok(make_string_array(vm, vec![owned]));
}
if is_re {
let re_ptr = sep_val.unwrap().as_js_object_ptr();
let re = unsafe { &*re_ptr };
if let Some(fn_ptr) = re.native_fn() {
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
@@ -896,18 +937,16 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if last_end <= s.len() && parts.len() < limit {
parts.push(s[last_end..].to_string());
}
return NativeResult::Ok(make_string_array(vm, &parts));
return NativeResult::Ok(make_string_array(vm, parts));
}
// 无原生正则的类正则对象回退到字符串路径。
}
let sep = as_string(vm, sep_val).into_owned();
let parts: Vec<String> = if sep.is_empty() {
s.chars().map(|c| c.to_string()).take(limit).collect()
} else {
s.split(&sep).map(|p| p.to_string()).take(limit).collect()
};
NativeResult::Ok(make_string_array(vm, &parts))
NativeResult::Ok(make_string_array(vm, parts))
}
/// `String.prototype.replace(pattern, replacement)`:替换首个匹配;
@@ -972,24 +1011,31 @@ pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 否则返回首个匹配及捕获组,无匹配返回 null。
pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.match called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// 正则判定与参数字符串转换先行(&mut 路径),后借 this 匹配扫描。
let pattern_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
let is_re = pattern_val.map(|v| is_regexp_obj(v, vm)).unwrap_or(false);
let pattern = match pattern_val {
Some(v) if !is_re => as_string(vm, v).into_owned(),
_ => String::new(),
};
let s = try_string!(this_string(vm, args));
if args.len() < 2 {
return NativeResult::Ok(JsValue::null());
}
let pattern_val = vm.reg(args[1]);
if is_regexp_obj(pattern_val, vm) {
let pattern_val = pattern_val.unwrap();
if is_re {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() {
Some(p) => p,
None => return NativeResult::Ok(JsValue::null()),
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
if is_global {
let matches: Vec<String> = regex.find_iter(&s).map(|m| s[m.range()].to_string()).collect();
return NativeResult::Ok(make_string_array(vm, &matches));
return NativeResult::Ok(make_string_array(vm, matches));
}
if let Some(m) = regex.find(&s) {
let range = m.range();
@@ -1001,15 +1047,13 @@ pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
None => parts.push(String::new()),
}
}
return NativeResult::Ok(make_string_array(vm, &parts));
return NativeResult::Ok(make_string_array(vm, parts));
}
return NativeResult::Ok(JsValue::null());
}
let pattern = as_string(vm, pattern_val);
if let Some(pos) = s.find(pattern.as_ref()) {
if let Some(pos) = s.find(&pattern) {
let matched = s[pos..pos + pattern.len()].to_string();
return NativeResult::Ok(make_string_array(vm, &[matched]));
return NativeResult::Ok(make_string_array(vm, vec![matched]));
}
NativeResult::Ok(JsValue::null())
}
@@ -1052,22 +1096,24 @@ pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `String.prototype.trimStart`:去除头部空白。
pub fn string_trim_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trimStart called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
NativeResult::Ok(vm.new_string(s.trim_start()))
let s = try_string!(this_string(vm, args));
let trimmed = s.trim_start().to_string();
NativeResult::Ok(vm.new_string_owned(trimmed))
}
/// `String.prototype.trimEnd`:去除尾部空白。
pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trimEnd called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
NativeResult::Ok(vm.new_string(s.trim_end()))
let s = try_string!(this_string(vm, args));
let trimmed = s.trim_end().to_string();
NativeResult::Ok(vm.new_string_owned(trimmed))
}
/// `String.prototype.codePointAt(pos)`:按 UTF-16 code unit 位置取 code point
/// (surrogate pair 合并);越界或孤立代理返回 undefined。
pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.codePointAt called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
// pos 转换先行(&mut 路径),后借 this 按 code unit 定位。
let pos = if args.len() > 1 {
let pos_val = vm.reg(args[1]);
if pos_val.is_symbol() {
@@ -1091,6 +1137,7 @@ pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
return NativeResult::Ok(JsValue::undefined());
}
let pos = pos as usize;
let s = try_string!(this_string(vm, args));
// JS 规范索引是 UTF-16 code unit 位置,须按代理对展开定位(astral 字符占两单元)。
let units: Vec<u16> = s.encode_utf16().collect();
if pos >= units.len() {
@@ -1131,7 +1178,7 @@ pub fn string_is_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
/// `String.prototype.toWellFormed()`:把孤立 surrogate 替换为 U+FFFD 返回新字符串。
pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toWellFormed called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned();
let s = try_string!(this_string(vm, args));
// 引擎字符串为合法 UTF-8,Rust char 不可能落在 surrogate 区间;遍历保留
// 通用来正确编码未来可能出现的替换路径。
let mut out = String::with_capacity(s.len());
@@ -1149,12 +1196,12 @@ pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.normalize called with {} args", args.len());
use unicode_normalization::UnicodeNormalization;
let s = try_string!(this_string(vm, args)).into_owned();
let form = if args.len() > 1 {
as_string(vm, vm.reg(args[1])).into_owned()
} else {
"NFC".to_string()
};
let s = try_string!(this_string(vm, args));
let result: String = match form.as_str() {
"NFD" => s.nfd().collect(),
"NFKC" => s.nfkc().collect(),
@@ -1294,7 +1341,7 @@ pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
idx = range.end;
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(idx as i32));
let arr_val = make_string_array(vm, &parts);
let arr_val = make_string_array(vm, parts);
make_match_done_result(vm, arr_val)
} else {
make_match_done_result(vm, JsValue::undefined())