Skip to main content

shape_jit/ffi/call_method/
string.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 18 sites
3//     jit_box(HK_STRING, ...) — toUpperCase, toLowerCase, trim, replace,
4//     charAt, substring, concat, padStart, padEnd, repeat, trimStart, trimEnd
5//     jit_box(HK_ARRAY, ...) — split, chars
6//   Category B (intermediate/consumed): 0 sites
7//   Category C (heap islands): 2 sites (split, chars)
8//!
9//! String method implementations for JIT
10
11use crate::jit_array::JitArray;
12use crate::nan_boxing::*;
13
14/// Call a method on a string value
15#[inline(always)]
16pub fn call_string_method(receiver_bits: u64, method_name: &str, args: &[u64]) -> u64 {
17    unsafe {
18        let s = jit_unbox::<String>(receiver_bits);
19
20        match method_name {
21            "length" | "len" => box_number(s.len() as f64),
22            "toUpperCase" | "to_upper_case" => jit_box(HK_STRING, s.to_uppercase()),
23            "toLowerCase" | "to_lower_case" => jit_box(HK_STRING, s.to_lowercase()),
24            "trim" => jit_box(HK_STRING, s.trim().to_string()),
25            "split" => {
26                let separator = if !args.is_empty() {
27                    if is_heap_kind(args[0], HK_STRING) {
28                        jit_unbox::<String>(args[0]).clone()
29                    } else {
30                        " ".to_string()
31                    }
32                } else {
33                    " ".to_string()
34                };
35
36                // AUDIT(C2): heap island — each split part is jit_box'd into a
37                // JitAlloc<String> and stored as raw u64 in the Vec. These inner
38                // string allocations escape into the JitArray element buffer without
39                // GC tracking. When GC feature enabled, route through gc_allocator.
40                let parts: Vec<u64> = s
41                    .split(&separator)
42                    .map(|part| jit_box(HK_STRING, part.to_string()))
43                    .collect();
44
45                jit_box(HK_ARRAY, JitArray::from_vec(parts))
46            }
47            "includes" | "contains" => {
48                if args.is_empty() {
49                    return TAG_BOOL_FALSE;
50                }
51                if is_heap_kind(args[0], HK_STRING) {
52                    let needle = jit_unbox::<String>(args[0]);
53                    if s.contains(needle.as_str()) {
54                        return TAG_BOOL_TRUE;
55                    }
56                }
57                TAG_BOOL_FALSE
58            }
59            "startsWith" | "starts_with" => {
60                if args.is_empty() {
61                    return TAG_BOOL_FALSE;
62                }
63                if is_heap_kind(args[0], HK_STRING) {
64                    let prefix = jit_unbox::<String>(args[0]);
65                    if s.starts_with(prefix.as_str()) {
66                        return TAG_BOOL_TRUE;
67                    }
68                }
69                TAG_BOOL_FALSE
70            }
71            "endsWith" | "ends_with" => {
72                if args.is_empty() {
73                    return TAG_BOOL_FALSE;
74                }
75                if is_heap_kind(args[0], HK_STRING) {
76                    let suffix = jit_unbox::<String>(args[0]);
77                    if s.ends_with(suffix.as_str()) {
78                        return TAG_BOOL_TRUE;
79                    }
80                }
81                TAG_BOOL_FALSE
82            }
83            "replace" | "replaceAll" | "replace_all" => {
84                if args.len() < 2 {
85                    return receiver_bits;
86                }
87                if is_heap_kind(args[0], HK_STRING) && is_heap_kind(args[1], HK_STRING) {
88                    let from = jit_unbox::<String>(args[0]);
89                    let to = jit_unbox::<String>(args[1]);
90                    let replaced = s.replace(from.as_str(), to.as_str());
91                    return jit_box(HK_STRING, replaced);
92                }
93                receiver_bits
94            }
95            "charAt" | "char_at" => {
96                if args.is_empty() {
97                    return jit_box(HK_STRING, String::new());
98                }
99                if is_number(args[0]) {
100                    let idx = unbox_number(args[0]) as usize;
101                    if let Some(ch) = s.chars().nth(idx) {
102                        return jit_box(HK_STRING, ch.to_string());
103                    }
104                }
105                jit_box(HK_STRING, String::new())
106            }
107            "substring" | "slice" => {
108                if args.is_empty() {
109                    return receiver_bits;
110                }
111                let start = if is_number(args[0]) {
112                    unbox_number(args[0]) as usize
113                } else {
114                    0
115                };
116                let end = if args.len() > 1 && is_number(args[1]) {
117                    unbox_number(args[1]) as usize
118                } else {
119                    s.len()
120                };
121                let len = if end >= start { end - start + 1 } else { 0 };
122                let sub: String = s.chars().skip(start).take(len).collect();
123                jit_box(HK_STRING, sub)
124            }
125            "concat" => {
126                let mut result = s.clone();
127                for arg in args.iter() {
128                    if is_heap_kind(*arg, HK_STRING) {
129                        let arg_s = jit_unbox::<String>(*arg);
130                        result.push_str(arg_s);
131                    } else if is_number(*arg) {
132                        result.push_str(&format!("{}", unbox_number(*arg)));
133                    }
134                }
135                jit_box(HK_STRING, result)
136            }
137            "indexOf" | "index_of" => {
138                if args.is_empty() {
139                    return box_number(-1.0);
140                }
141                if is_heap_kind(args[0], HK_STRING) {
142                    let needle = jit_unbox::<String>(args[0]);
143                    if let Some(idx) = s.find(needle.as_str()) {
144                        return box_number(idx as f64);
145                    }
146                }
147                box_number(-1.0)
148            }
149            "lastIndexOf" | "last_index_of" => {
150                if args.is_empty() {
151                    return box_number(-1.0);
152                }
153                if is_heap_kind(args[0], HK_STRING) {
154                    let needle = jit_unbox::<String>(args[0]);
155                    if let Some(idx) = s.rfind(needle.as_str()) {
156                        return box_number(idx as f64);
157                    }
158                }
159                box_number(-1.0)
160            }
161            "trimStart" | "trim_start" => jit_box(HK_STRING, s.trim_start().to_string()),
162            "trimEnd" | "trim_end" => jit_box(HK_STRING, s.trim_end().to_string()),
163            "toNumber" | "to_number" => match s.trim().parse::<f64>() {
164                Ok(n) => box_number(n),
165                Err(_) => TAG_NULL,
166            },
167            "toBool" | "to_bool" => match s.trim() {
168                "true" => TAG_BOOL_TRUE,
169                "false" => TAG_BOOL_FALSE,
170                _ => TAG_NULL,
171            },
172            "chars" => {
173                // AUDIT(C3): heap island — each char is jit_box'd into a
174                // JitAlloc<String> and stored as raw u64 in the Vec. These inner
175                // string allocations escape into the JitArray element buffer without
176                // GC tracking. When GC feature enabled, route through gc_allocator.
177                let chars: Vec<u64> = s
178                    .chars()
179                    .map(|ch| jit_box(HK_STRING, ch.to_string()))
180                    .collect();
181                jit_box(HK_ARRAY, JitArray::from_vec(chars))
182            }
183            "isEmpty" | "is_empty" => {
184                if s.is_empty() {
185                    TAG_BOOL_TRUE
186                } else {
187                    TAG_BOOL_FALSE
188                }
189            }
190            "repeat" => {
191                if args.is_empty() {
192                    return receiver_bits;
193                }
194                if is_number(args[0]) {
195                    let count = unbox_number(args[0]) as usize;
196                    let repeated = s.repeat(count);
197                    return jit_box(HK_STRING, repeated);
198                }
199                receiver_bits
200            }
201            "padStart" | "pad_start" => {
202                if args.is_empty() {
203                    return receiver_bits;
204                }
205                let target_len = if is_number(args[0]) {
206                    unbox_number(args[0]) as usize
207                } else {
208                    return receiver_bits;
209                };
210                let pad_char = if args.len() > 1 && is_heap_kind(args[1], HK_STRING) {
211                    let pad_s = jit_unbox::<String>(args[1]);
212                    pad_s.chars().next().unwrap_or(' ')
213                } else {
214                    ' '
215                };
216                if s.len() >= target_len {
217                    return receiver_bits;
218                }
219                let padding: String = std::iter::repeat_n(pad_char, target_len - s.len()).collect();
220                let padded = format!("{}{}", padding, s);
221                jit_box(HK_STRING, padded)
222            }
223            "padEnd" | "pad_end" => {
224                if args.is_empty() {
225                    return receiver_bits;
226                }
227                let target_len = if is_number(args[0]) {
228                    unbox_number(args[0]) as usize
229                } else {
230                    return receiver_bits;
231                };
232                let pad_char = if args.len() > 1 && is_heap_kind(args[1], HK_STRING) {
233                    let pad_s = jit_unbox::<String>(args[1]);
234                    pad_s.chars().next().unwrap_or(' ')
235                } else {
236                    ' '
237                };
238                if s.len() >= target_len {
239                    return receiver_bits;
240                }
241                let padding: String = std::iter::repeat_n(pad_char, target_len - s.len()).collect();
242                let padded = format!("{}{}", s, padding);
243                jit_box(HK_STRING, padded)
244            }
245            _ => TAG_NULL,
246        }
247    }
248}