1use oxide_regexp::CompiledRegExp;
10
11use super::error::{self, ErrorKind};
12use super::{Cx, Throw};
13use crate::heap::hash::FxHashMap;
14use crate::heap::object::ObjectKind;
15use crate::runtime::{Intrinsic, Names};
16use crate::shape::PropertyFlags;
17use crate::value::{BytecodeId, StringId, Value};
18
19#[derive(Clone, Copy, Debug)]
22pub struct RegExpRecord {
23 pub source: StringId,
24 pub flags: StringId,
25}
26
27#[derive(Default)]
28pub struct RegExpStore {
29 programs: Vec<Option<(RegExpRecord, CompiledRegExp)>>,
31 index: FxHashMap<(StringId, StringId), u32>,
32 free: Vec<u32>,
34 #[cfg(feature = "gc-stress")]
37 quarantine: Vec<u32>,
38 marks: Vec<u64>,
39 live: usize,
40 bytes: usize,
41}
42
43fn program_bytes(program: &CompiledRegExp) -> usize {
45 size_of::<Option<(RegExpRecord, CompiledRegExp)>>()
46 + std::mem::size_of_val(program.instructions())
47 + program.group_names().map_or(0, |names| {
48 std::mem::size_of_val(names)
49 + names
50 .iter()
51 .flatten()
52 .map(|name| std::mem::size_of_val(&**name))
53 .sum::<usize>()
54 })
55}
56
57impl RegExpStore {
58 pub(crate) fn intern(
63 &mut self,
64 strings: &crate::heap::arena::Arena<crate::heap::string::StringRecord>,
65 source: StringId,
66 flags: StringId,
67 ) -> Result<u32, String> {
68 if let Some(&index) = self.index.get(&(source, flags)) {
69 return Ok(index);
70 }
71 let units = |id: StringId| -> Vec<u16> {
72 strings
73 .get(id.index())
74 .flat()
75 .expect("an atom is flat")
76 .units()
77 .collect()
78 };
79 let program = oxide_regexp::compile(&units(source), &units(flags))
80 .map_err(|error| oxide_regexp::javascript_compile_error_message(&error).to_owned())?;
81 self.bytes += program_bytes(&program);
82 self.live += 1;
83 let entry = Some((RegExpRecord { source, flags }, program));
84 let index = match self.free.pop() {
85 Some(index) => {
86 self.programs[index as usize] = entry;
87 index
88 }
89 None => {
90 self.programs.push(entry);
91 let index = (self.programs.len() - 1) as u32;
92 if index as usize / 64 == self.marks.len() {
93 self.marks.push(0);
94 }
95 index
96 }
97 };
98 self.index.insert((source, flags), index);
99 Ok(index)
100 }
101
102 #[track_caller]
103 fn entry(&self, index: u32) -> &(RegExpRecord, CompiledRegExp) {
104 self.programs[index as usize]
105 .as_ref()
106 .expect("a live regular-expression program")
107 }
108
109 pub fn get(&self, index: u32) -> RegExpRecord {
110 self.entry(index).0
111 }
112
113 pub fn program(&self, index: u32) -> &CompiledRegExp {
114 &self.entry(index).1
115 }
116
117 pub fn len(&self) -> usize {
119 self.live
120 }
121
122 pub fn is_empty(&self) -> bool {
123 self.live == 0
124 }
125
126 pub fn bytes(&self) -> usize {
128 self.bytes
129 }
130
131 pub(crate) fn clear_marks(&mut self) {
132 self.marks.fill(0);
133 }
134
135 #[inline]
137 pub(crate) fn mark(&mut self, index: u32) -> Option<RegExpRecord> {
138 let (word, mask) = ((index / 64) as usize, 1u64 << (index % 64));
139 if self.marks[word] & mask != 0 {
140 return None;
141 }
142 self.marks[word] |= mask;
143 Some(self.get(index))
144 }
145
146 fn is_marked(&self, index: u32) -> bool {
147 self.marks[(index / 64) as usize] & (1u64 << (index % 64)) != 0
148 }
149
150 pub(crate) fn sweep(&mut self) {
152 #[cfg(feature = "gc-stress")]
153 let held = {
154 let released = std::mem::take(&mut self.quarantine);
155 self.free.extend_from_slice(&released);
156 self.free.len()
157 };
158 for index in (0..self.programs.len() as u32).rev() {
159 if self.is_marked(index) {
160 continue;
161 }
162 let Some((record, program)) = self.programs[index as usize].take() else {
163 continue;
164 };
165 self.index.remove(&(record.source, record.flags));
166 self.bytes -= program_bytes(&program);
167 self.live -= 1;
168 self.free.push(index);
169 }
170 #[cfg(feature = "gc-stress")]
171 self.quarantine.extend(self.free.drain(held..));
172 }
173
174 pub(crate) fn verify(&self) {
176 for (index, program) in self.programs.iter().enumerate() {
177 assert_eq!(
178 program.is_some(),
179 self.is_marked(index as u32),
180 "gc verify: regular-expression program {index} is {}",
181 if program.is_some() {
182 "live but unreachable"
183 } else {
184 "reachable but freed"
185 }
186 );
187 }
188 }
189}
190
191#[inline(never)]
195pub(crate) fn new_regexp(
196 cx: &mut Cx<'_>,
197 bytecode: BytecodeId,
198 constant: u32,
199) -> Result<Value, Throw> {
200 let source = cx.heap.bytecode_constant(bytecode, constant);
201 let flags = cx.heap.bytecode_constant(bytecode, constant + 1);
202 let source = source.as_string().expect("a source atom");
203 let flags = flags.as_string().expect("a flags atom");
204 regexp_object(cx, source, flags)
205}
206
207pub(crate) fn regexp_object(
210 cx: &mut Cx<'_>,
211 source: StringId,
212 flags: StringId,
213) -> Result<Value, Throw> {
214 let prototype = cx.realm.intrinsic(Intrinsic::RegExpPrototype);
215 regexp_object_with(cx, source, flags, prototype)
216}
217
218pub(crate) fn set_program(
222 cx: &mut Cx<'_>,
223 object: crate::value::ObjectId,
224 source: StringId,
225 flags: StringId,
226) -> Result<(), Throw> {
227 let heap = &mut *cx.heap;
228 match heap.regexps.intern(&heap.strings, source, flags) {
229 Ok(program) => {
230 heap.set_payload(object, program);
231 Ok(())
232 }
233 Err(message) => Err(error::throw(cx, ErrorKind::Syntax, &message)),
234 }
235}
236
237pub(crate) fn regexp_object_with(
239 cx: &mut Cx<'_>,
240 source: StringId,
241 flags: StringId,
242 prototype: crate::value::ObjectId,
243) -> Result<Value, Throw> {
244 let names = cx.realm.names;
245 let heap = &mut *cx.heap;
246 let mark = heap.roots().mark();
247 heap.roots().push(Value::string(source));
248 heap.roots().push(Value::string(flags));
249 let object = heap.alloc_object(ObjectKind::RegExp, Some(prototype), 1);
250 heap.roots().truncate(mark);
251 let object = object.map_err(|error| error::heap_error(cx, error))?;
252 set_program(cx, object, source, flags)?;
253 let heap = &mut *cx.heap;
254 heap.add_property(
255 object,
256 Names::key(names.last_index),
257 PropertyFlags::data(true, false, false),
258 Value::int32(0),
259 )
260 .map_err(|error| error::heap_error(cx, error))?;
261 if prototype == cx.realm.intrinsic(Intrinsic::RegExpPrototype)
262 && cx.realm.regexp_instance_shape.get().is_none()
263 {
264 let (slot, _) = cx
265 .heap
266 .lookup_own(object, Names::key(names.last_index))
267 .expect("lastIndex");
268 cx.realm
269 .regexp_instance_shape
270 .set(Some((cx.heap.shape_of(object), slot)));
271 }
272 Ok(Value::object(object))
273}
274
275#[cfg(test)]
276mod tests {
277 #[test]
278 fn unmarked_programs_are_freed_and_their_keys_forgotten() {
279 let mut heap = crate::Heap::default();
280 let source = heap.intern("a+b").unwrap();
281 let other = heap.intern("c").unwrap();
282 let flags = heap.intern("g").unwrap();
283 let store = &mut heap.regexps;
284 let kept = store.intern(&heap.strings, source, flags).unwrap();
285 store.intern(&heap.strings, other, flags).unwrap();
286 assert_eq!(store.intern(&heap.strings, source, flags), Ok(kept));
287 assert_eq!(store.len(), 2);
288 let bytes = store.bytes();
289 store.clear_marks();
290 assert!(store.mark(kept).is_some());
291 assert!(store.mark(kept).is_none(), "marked once");
292 store.sweep();
293 assert_eq!(store.len(), 1);
294 assert!(store.bytes() < bytes);
295 assert_eq!(store.get(kept).source, source);
296 let again = store.intern(&heap.strings, other, flags).unwrap();
298 assert_eq!(store.get(again).source, other);
299 assert_ne!(again, kept);
300 assert!(
301 store.intern(&heap.strings, flags, source).is_err(),
302 "invalid flags"
303 );
304 }
305}