Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 0 additions & 57 deletions dag/gunbc/char_at_scaling_probe_support.dag

This file was deleted.

19 changes: 19 additions & 0 deletions dag/test/claim/char_at_unicode_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,22 @@ test fn char_at_byte_offset_would_mismatch_at_index_one() -> Bool {
test fn string_length_counts_code_points_not_bytes() -> Bool {
string_length(s: ae_b_sample()) == 3
}

data char_at_ascii_prefix_fast_path_note: String = "STRING-INDEX-0 discriminating control for the prefix-bounded ASCII fast path. char_at/substring no longer rescan the whole string for ASCII-ness; they test only the bytes up to the requested position, so a string whose ASCII PREFIX is followed by a multibyte char now takes the byte-indexed path for positions inside that prefix and the code-point path beyond it. On \"ab\" + U+00E9 + \"c\" (5 bytes, 4 code points) a byte-offset implementation returns U+00A9 at index 3 and \"c\" at index 4; code-point indexing returns \"c\" at index 3 and nothing at index 4. These two claims cover char_at only: substring takes the same prefix-bounded fast path, but removing its prefix check left every witness green, so no discriminating control for it is asserted here rather than one that has never been seen to fail."

fn ab_e_c_sample() -> String {
concat("ab", concat(from_code_point(cp: 233), "c"))
}

test fn char_at_agrees_across_the_ascii_prefix_boundary() -> Bool {
let s = ab_e_c_sample()
char_at(s: s, pos: 0) == "a"
&& char_at(s: s, pos: 1) == "b"
&& code_point(c: char_at(s: s, pos: 2)) == 233
&& char_at(s: s, pos: 3) == "c"
}

test fn char_at_past_the_multibyte_char_is_not_a_byte_offset() -> Bool {
let s = ab_e_c_sample()
char_at(s: s, pos: 3) != from_code_point(cp: 169) && char_at(s: s, pos: 4) == ""
}
46 changes: 20 additions & 26 deletions src/v1/runtime_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -95,37 +95,34 @@ fn rt_concat_trait() -> String {

fn rt_string_ops() -> String {
concat(
"/// Ascii-aware variant taking a precomputed `is_ascii` flag (the `RcStr` carrier fact)\n",
"/// instead of rescanning the whole string on every call -- the per-call `s.is_ascii()`\n",
"/// scan is what made repeated indexing over a large string O(n^2) (STRING-INDEX-0).\n",
"pub fn char_at_ascii_aware(s: &str, is_ascii: bool, pos: i64) -> String {\n",
"/// Code-point indexing over UTF-8, with the ASCII fast path bounded by `pos`\n",
"/// rather than by the whole string. A leading run of ASCII bytes makes the byte\n",
"/// offset equal the code-point offset, so nothing past `pos` is examined and the\n",
"/// per-call whole-string `s.is_ascii()` rescan is gone (STRING-INDEX-0). Cost is\n",
"/// O(min(pos, n)) -- the fallback's own cost -- never O(n) unconditionally.\n",
"/// RESIDUAL: a left-to-right walk of the whole string is still O(n^2). Making a\n",
"/// single call O(1) needs the ASCII fact carried on the string value itself, and\n",
"/// no such carrier exists in this seed -- that carrier, or a cursor surface that\n",
"/// does not re-index from zero, is this class's next rung.\n",
"pub fn char_at(s: &str, pos: i64) -> String {\n",
" let pos = pos.max(0) as usize;\n",
" if is_ascii {\n",
" let bytes = s.as_bytes();\n",
" if pos >= bytes.len() { return String::new(); }\n",
" return String::from(bytes[pos] as char);\n",
" }\n",
" let bytes = s.as_bytes();\n",
" if pos >= bytes.len() { return String::new(); }\n",
" if bytes[..=pos].is_ascii() { return String::from(bytes[pos] as char); }\n",
" s.chars().nth(pos).map(|ch| ch.to_string()).unwrap_or_default()\n",
"}\n\n",
"pub fn char_at(s: &str, pos: i64) -> String {\n",
" char_at_ascii_aware(s, s.is_ascii(), pos)\n",
"}\n\n",
"/// Ascii-aware variant taking a precomputed `is_ascii` flag; see `char_at_ascii_aware`.\n",
"pub fn string_length_ascii_aware(s: &str, is_ascii: bool) -> i64 {\n",
" if is_ascii { s.len() as i64 } else { s.chars().count() as i64 }\n",
"}\n\n",
"pub fn string_length(s: &str) -> i64 {\n",
" string_length_ascii_aware(s, s.is_ascii())\n",
" if s.is_ascii() { s.len() as i64 } else { s.chars().count() as i64 }\n",
"}\n\n",
"/// Ascii-aware variant taking a precomputed `is_ascii` flag; see `char_at_ascii_aware`.\n",
"pub fn substring_ascii_aware(s: &str, is_ascii: bool, start: i64, end: i64) -> String {\n",
"/// See `char_at`: the ASCII fast path is bounded by `end`, not by the whole string.\n",
"pub fn substring(s: &str, start: i64, end: i64) -> String {\n",
" let start = start.max(0) as usize;\n",
" let end = end.max(0) as usize;\n",
" if end <= start { return String::new(); }\n",
" if is_ascii {\n",
" let len = s.len();\n",
" if start >= len { return String::new(); }\n",
" let out_end = end.min(len);\n",
" let bytes = s.as_bytes();\n",
" if start >= bytes.len() { return String::new(); }\n",
" let out_end = end.min(bytes.len());\n",
" if bytes[..out_end].is_ascii() {\n",
" let take_len = out_end.saturating_sub(start);\n",
" record_substring_chars_walked(s, start, take_len);\n",
" return s[start..out_end].to_string();\n",
Expand All @@ -134,9 +131,6 @@ fn rt_string_ops() -> String {
" record_substring_chars_walked(s, start, take_len);\n",
" s.chars().skip(start).take(take_len).collect()\n",
"}\n\n",
"pub fn substring(s: &str, start: i64, end: i64) -> String {\n",
" substring_ascii_aware(s, s.is_ascii(), start, end)\n",
"}\n\n",
"pub fn string_contains(s: &str, sub: String) -> bool {\n",
" s.contains(&*sub)\n",
"}\n\n",
Expand Down
2 changes: 1 addition & 1 deletion src/v1/stage0/src/v1_compiler_runtime_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn rt_concat_trait() -> String {
}

pub fn rt_string_ops() -> String {
v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("/// Ascii-aware variant taking a precomputed `is_ascii` flag (the `RcStr` carrier fact)\n".to_string(), "/// instead of rescanning the whole string on every call -- the per-call `s.is_ascii()`\n".to_string()), "/// scan is what made repeated indexing over a large string O(n^2) (STRING-INDEX-0).\n".to_string()), "pub fn char_at_ascii_aware(s: &str, is_ascii: bool, pos: i64) -> String {\n".to_string()), " let pos = pos.max(0) as usize;\n".to_string()), " if is_ascii {\n".to_string()), " let bytes = s.as_bytes();\n".to_string()), " if pos >= bytes.len() { return String::new(); }\n".to_string()), " return String::from(bytes[pos] as char);\n".to_string()), " }\n".to_string()), " s.chars().nth(pos).map(|ch| ch.to_string()).unwrap_or_default()\n".to_string()), "}\n\n".to_string()), "pub fn char_at(s: &str, pos: i64) -> String {\n".to_string()), " char_at_ascii_aware(s, s.is_ascii(), pos)\n".to_string()), "}\n\n".to_string()), "/// Ascii-aware variant taking a precomputed `is_ascii` flag; see `char_at_ascii_aware`.\n".to_string()), "pub fn string_length_ascii_aware(s: &str, is_ascii: bool) -> i64 {\n".to_string()), " if is_ascii { s.len() as i64 } else { s.chars().count() as i64 }\n".to_string()), "}\n\n".to_string()), "pub fn string_length(s: &str) -> i64 {\n".to_string()), " string_length_ascii_aware(s, s.is_ascii())\n".to_string()), "}\n\n".to_string()), "/// Ascii-aware variant taking a precomputed `is_ascii` flag; see `char_at_ascii_aware`.\n".to_string()), "pub fn substring_ascii_aware(s: &str, is_ascii: bool, start: i64, end: i64) -> String {\n".to_string()), " let start = start.max(0) as usize;\n".to_string()), " let end = end.max(0) as usize;\n".to_string()), " if end <= start { return String::new(); }\n".to_string()), " if is_ascii {\n".to_string()), " let len = s.len();\n".to_string()), " if start >= len { return String::new(); }\n".to_string()), " let out_end = end.min(len);\n".to_string()), " let take_len = out_end.saturating_sub(start);\n".to_string()), " record_substring_chars_walked(s, start, take_len);\n".to_string()), " return s[start..out_end].to_string();\n".to_string()), " }\n".to_string()), " let take_len = end.saturating_sub(start);\n".to_string()), " record_substring_chars_walked(s, start, take_len);\n".to_string()), " s.chars().skip(start).take(take_len).collect()\n".to_string()), "}\n\n".to_string()), "pub fn substring(s: &str, start: i64, end: i64) -> String {\n".to_string()), " substring_ascii_aware(s, s.is_ascii(), start, end)\n".to_string()), "}\n\n".to_string()), "pub fn string_contains(s: &str, sub: String) -> bool {\n".to_string()), " s.contains(&*sub)\n".to_string()), "}\n\n".to_string()), "pub fn contains(s: String, sub: String) -> bool { string_contains(&s, sub) }\n\n".to_string()), "pub fn starts_with(s: String, prefix: String) -> bool { s.starts_with(&*prefix) }\n\n".to_string()), "pub fn ends_with(s: String, suffix: String) -> bool { s.ends_with(&*suffix) }\n\n".to_string()), "pub fn trim(s: String) -> String { s.trim().to_string() }\n\n".to_string()), "pub fn count<T: Clone>(items: Rc<Vec<T>>) -> i64 { items.len() as i64 }\n\n".to_string()), "pub fn str_eq(a: String, b: String) -> bool { a == b }\n\n".to_string()), "pub fn to_string(value: i64) -> String { value.to_string() }\n\n".to_string()), "/// RFC 3629 UTF-8 decode of a byte vector. Fail-closed on invalid UTF-8.\n".to_string()), "pub fn utf8_decode_bytes(bytes: &[u8]) -> Result<String, String> {\n".to_string()), " String::from_utf8(bytes.to_vec()).map_err(|e| format!(\"invalid UTF-8 in access payload: {}\", e))\n".to_string()), "}\n\n".to_string()), "pub fn clamp(val: i64, min_val: i64, max_val: i64) -> i64 {\n".to_string()), " val.clamp(min_val, max_val)\n".to_string()), "}\n\n".to_string())
v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("/// Code-point indexing over UTF-8, with the ASCII fast path bounded by `pos`\n".to_string(), "/// rather than by the whole string. A leading run of ASCII bytes makes the byte\n".to_string()), "/// offset equal the code-point offset, so nothing past `pos` is examined and the\n".to_string()), "/// per-call whole-string `s.is_ascii()` rescan is gone (STRING-INDEX-0). Cost is\n".to_string()), "/// O(min(pos, n)) -- the fallback's own cost -- never O(n) unconditionally.\n".to_string()), "/// RESIDUAL: a left-to-right walk of the whole string is still O(n^2). Making a\n".to_string()), "/// single call O(1) needs the ASCII fact carried on the string value itself, and\n".to_string()), "/// no such carrier exists in this seed -- that carrier, or a cursor surface that\n".to_string()), "/// does not re-index from zero, is this class's next rung.\n".to_string()), "pub fn char_at(s: &str, pos: i64) -> String {\n".to_string()), " let pos = pos.max(0) as usize;\n".to_string()), " let bytes = s.as_bytes();\n".to_string()), " if pos >= bytes.len() { return String::new(); }\n".to_string()), " if bytes[..=pos].is_ascii() { return String::from(bytes[pos] as char); }\n".to_string()), " s.chars().nth(pos).map(|ch| ch.to_string()).unwrap_or_default()\n".to_string()), "}\n\n".to_string()), "pub fn string_length(s: &str) -> i64 {\n".to_string()), " if s.is_ascii() { s.len() as i64 } else { s.chars().count() as i64 }\n".to_string()), "}\n\n".to_string()), "/// See `char_at`: the ASCII fast path is bounded by `end`, not by the whole string.\n".to_string()), "pub fn substring(s: &str, start: i64, end: i64) -> String {\n".to_string()), " let start = start.max(0) as usize;\n".to_string()), " let end = end.max(0) as usize;\n".to_string()), " if end <= start { return String::new(); }\n".to_string()), " let bytes = s.as_bytes();\n".to_string()), " if start >= bytes.len() { return String::new(); }\n".to_string()), " let out_end = end.min(bytes.len());\n".to_string()), " if bytes[..out_end].is_ascii() {\n".to_string()), " let take_len = out_end.saturating_sub(start);\n".to_string()), " record_substring_chars_walked(s, start, take_len);\n".to_string()), " return s[start..out_end].to_string();\n".to_string()), " }\n".to_string()), " let take_len = end.saturating_sub(start);\n".to_string()), " record_substring_chars_walked(s, start, take_len);\n".to_string()), " s.chars().skip(start).take(take_len).collect()\n".to_string()), "}\n\n".to_string()), "pub fn string_contains(s: &str, sub: String) -> bool {\n".to_string()), " s.contains(&*sub)\n".to_string()), "}\n\n".to_string()), "pub fn contains(s: String, sub: String) -> bool { string_contains(&s, sub) }\n\n".to_string()), "pub fn starts_with(s: String, prefix: String) -> bool { s.starts_with(&*prefix) }\n\n".to_string()), "pub fn ends_with(s: String, suffix: String) -> bool { s.ends_with(&*suffix) }\n\n".to_string()), "pub fn trim(s: String) -> String { s.trim().to_string() }\n\n".to_string()), "pub fn count<T: Clone>(items: Rc<Vec<T>>) -> i64 { items.len() as i64 }\n\n".to_string()), "pub fn str_eq(a: String, b: String) -> bool { a == b }\n\n".to_string()), "pub fn to_string(value: i64) -> String { value.to_string() }\n\n".to_string()), "/// RFC 3629 UTF-8 decode of a byte vector. Fail-closed on invalid UTF-8.\n".to_string()), "pub fn utf8_decode_bytes(bytes: &[u8]) -> Result<String, String> {\n".to_string()), " String::from_utf8(bytes.to_vec()).map_err(|e| format!(\"invalid UTF-8 in access payload: {}\", e))\n".to_string()), "}\n\n".to_string()), "pub fn clamp(val: i64, min_val: i64, max_val: i64) -> i64 {\n".to_string()), " val.clamp(min_val, max_val)\n".to_string()), "}\n\n".to_string())
}

pub fn rt_collection_ops() -> String {
Expand Down
4 changes: 2 additions & 2 deletions src/v1/stage0/src/v1_interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14762,7 +14762,7 @@ pub fn eval_profile_reset() {
/// (v2 02_parse.dag) calls `length` on the full token stream every parse
/// attempt; without this fast path that is an O(n) clone per attempt, an
/// O(n^2) tax the compiled (Rust-emitted) realization never pays. Method-call
/// `.length()` on native `Value::Str` routes through `string_length_ascii_aware`
/// `.length()` on native `Value::Str` routes through `v1_rt::string_length`
/// so it does not flatten strings into per-codepoint `Value`s (LIST-CARRIER-0 /
/// materialize OOM). Free-call `length`/`string_length` already avoided
/// `free_monoid_to_vec` on `Str` via `chars().count()`; this arm closes the
Expand All @@ -14787,7 +14787,7 @@ pub(crate) fn native_len(val: &Value) -> Option<i64> {
// NEXT-RUNG TRIGGER: a workload that repeatedly length-queries the same non-ASCII
// string. If that appears, the amortization argument inverts and a carried count
// becomes correct.
Value::Str(s) => Some(v1_rt::string_length_ascii_aware(&s, s.is_ascii())),
Value::Str(s) => Some(v1_rt::string_length(s)),
_ => None,
}
}
Expand Down
Loading
Loading