schemurai 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -4
- data/ext/schemurai_native/Cargo.lock +48 -1
- data/ext/schemurai_native/Cargo.toml +3 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +56 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +8 -6
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/lib.rs +910 -237
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +113 -101
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +24 -2
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
//! Runner-owned search caches and observations requested outside Rust frames.
|
|
2
|
+
use super::*;
|
|
3
|
+
mod properties;
|
|
4
|
+
mod reports;
|
|
5
|
+
mod scopes;
|
|
6
|
+
use regex_automata::{
|
|
7
|
+
Input,
|
|
8
|
+
meta::{Cache, Regex},
|
|
9
|
+
};
|
|
10
|
+
use std::collections::HashSet;
|
|
11
|
+
|
|
12
|
+
// Each validator owns its search caches. Compiled regex/DFA data stays shared.
|
|
13
|
+
#[derive(Default)]
|
|
14
|
+
pub struct Caches {
|
|
15
|
+
searches: std::cell::RefCell<Vec<Option<Box<Cache>>>>,
|
|
16
|
+
matches: std::cell::Cell<*const c_void>,
|
|
17
|
+
pending: std::cell::Cell<Option<(Value, Value, i32)>>,
|
|
18
|
+
deadline: std::cell::Cell<Option<std::time::Instant>>,
|
|
19
|
+
runtime_order: std::cell::Cell<bool>,
|
|
20
|
+
dynamic_calls: std::cell::Cell<bool>,
|
|
21
|
+
reorder: std::cell::Cell<bool>,
|
|
22
|
+
pending_rescue: std::cell::Cell<Option<StringError>>,
|
|
23
|
+
rescued_strings: std::cell::RefCell<HashSet<StringError>>,
|
|
24
|
+
call_cursor: std::cell::Cell<usize>,
|
|
25
|
+
pointers: std::cell::RefCell<Pointers>,
|
|
26
|
+
pending_pointer: std::cell::Cell<Option<usize>>,
|
|
27
|
+
messages: std::cell::RefCell<HashMap<Message, Option<Value>>>,
|
|
28
|
+
pending_message: std::cell::Cell<Option<Message>>,
|
|
29
|
+
pending_scalar: std::cell::Cell<Option<ScalarArguments>>,
|
|
30
|
+
frame_error: std::cell::Cell<bool>,
|
|
31
|
+
scalar_callback: std::cell::Cell<bool>,
|
|
32
|
+
callback_checkpoints: std::cell::Cell<bool>,
|
|
33
|
+
callbacks_allowed: std::cell::Cell<bool>,
|
|
34
|
+
continuations_allowed: std::cell::Cell<bool>,
|
|
35
|
+
pending_contains: std::cell::Cell<Option<ContainsArguments>>,
|
|
36
|
+
scopes: scopes::Scopes,
|
|
37
|
+
reports: reports::Reports,
|
|
38
|
+
properties: properties::Properties,
|
|
39
|
+
report_continuations_allowed: std::cell::Cell<bool>,
|
|
40
|
+
}
|
|
41
|
+
#[repr(C)]
|
|
42
|
+
#[derive(Clone, Copy)]
|
|
43
|
+
pub struct ContainsArguments {
|
|
44
|
+
rules: Value,
|
|
45
|
+
instance: Value,
|
|
46
|
+
child: usize,
|
|
47
|
+
required: i32,
|
|
48
|
+
scalar_kind: i32,
|
|
49
|
+
scalar_flags: i32,
|
|
50
|
+
}
|
|
51
|
+
type StringError = (Value, usize, &'static str);
|
|
52
|
+
#[repr(C)]
|
|
53
|
+
#[derive(Clone, Copy)]
|
|
54
|
+
pub struct ScalarArguments {
|
|
55
|
+
ordinal: Value,
|
|
56
|
+
rules: Value,
|
|
57
|
+
instance: Value,
|
|
58
|
+
}
|
|
59
|
+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
|
60
|
+
struct Message {
|
|
61
|
+
ordinal: usize,
|
|
62
|
+
keyword: &'static str,
|
|
63
|
+
opcode: i32,
|
|
64
|
+
operand: Value,
|
|
65
|
+
instance: Value,
|
|
66
|
+
detail: usize,
|
|
67
|
+
frame: Value,
|
|
68
|
+
}
|
|
69
|
+
#[repr(C)]
|
|
70
|
+
pub struct MessageArguments {
|
|
71
|
+
keyword: *const u8,
|
|
72
|
+
keyword_len: usize,
|
|
73
|
+
opcode: i32,
|
|
74
|
+
operand: Value,
|
|
75
|
+
instance: Value,
|
|
76
|
+
detail: usize,
|
|
77
|
+
frame: Value,
|
|
78
|
+
}
|
|
79
|
+
#[derive(Default)]
|
|
80
|
+
struct Pointers {
|
|
81
|
+
indices: HashMap<std::sync::Arc<pointer::Path>, usize>,
|
|
82
|
+
requests: Vec<PointerRequest>,
|
|
83
|
+
}
|
|
84
|
+
struct PointerRequest {
|
|
85
|
+
path: std::sync::Arc<pointer::Path>,
|
|
86
|
+
result: Option<Value>,
|
|
87
|
+
}
|
|
88
|
+
unsafe extern "C" {
|
|
89
|
+
fn sn_runtime_ordinal(ordinal: usize) -> Value;
|
|
90
|
+
fn sn_cached_runtime(
|
|
91
|
+
matches: *const c_void,
|
|
92
|
+
operation: i32,
|
|
93
|
+
source: Value,
|
|
94
|
+
value: Value,
|
|
95
|
+
) -> i32;
|
|
96
|
+
}
|
|
97
|
+
impl Caches {
|
|
98
|
+
fn reset_pass(&self) {
|
|
99
|
+
self.scopes.reset();
|
|
100
|
+
self.reports.reset();
|
|
101
|
+
self.properties.reset();
|
|
102
|
+
self.pending.set(None);
|
|
103
|
+
self.call_cursor.set(0);
|
|
104
|
+
self.pending_pointer.set(None);
|
|
105
|
+
self.pending_message.set(None);
|
|
106
|
+
self.pending_scalar.set(None);
|
|
107
|
+
self.pending_contains.set(None);
|
|
108
|
+
self.frame_error.set(false);
|
|
109
|
+
self.pending_rescue.set(None);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub(super) fn scope_guard(&self, resource: Value, enters: bool) -> scopes::Guard<'_> {
|
|
113
|
+
self.scopes.enter(resource, enters)
|
|
114
|
+
}
|
|
115
|
+
pub(super) fn reference_frame(&self, program: Value, instance: Value) -> scopes::Reference {
|
|
116
|
+
self.scopes.reference_frame(program, instance)
|
|
117
|
+
}
|
|
118
|
+
pub(super) fn reference_guard<'a>(
|
|
119
|
+
&'a self,
|
|
120
|
+
frame: &'a scopes::Reference,
|
|
121
|
+
) -> scopes::ReferenceGuard<'a> {
|
|
122
|
+
self.scopes.reference_guard(frame)
|
|
123
|
+
}
|
|
124
|
+
pub(super) fn reference_active(&self, program: Value, instance: Value) -> bool {
|
|
125
|
+
self.scopes.reference_active(program, instance)
|
|
126
|
+
}
|
|
127
|
+
pub(super) fn scopes_enabled(&self) -> bool {
|
|
128
|
+
self.scopes.enabled()
|
|
129
|
+
}
|
|
130
|
+
pub(super) fn scope_at(&self, index: usize) -> Option<Value> {
|
|
131
|
+
self.scopes.at(index)
|
|
132
|
+
}
|
|
133
|
+
pub(super) fn frame_error(&self, active: bool) {
|
|
134
|
+
self.frame_error.set(active);
|
|
135
|
+
}
|
|
136
|
+
pub(super) fn scalar_callback(&self) -> bool {
|
|
137
|
+
self.scalar_callback.get()
|
|
138
|
+
}
|
|
139
|
+
pub(super) fn callbacks_allowed(&self) -> bool {
|
|
140
|
+
self.callbacks_allowed.get()
|
|
141
|
+
}
|
|
142
|
+
pub(super) fn allow_callbacks(&self, allowed: bool) {
|
|
143
|
+
self.callbacks_allowed.set(allowed);
|
|
144
|
+
}
|
|
145
|
+
pub(super) fn allow_continuations(&self, allowed: bool) {
|
|
146
|
+
self.continuations_allowed.set(allowed);
|
|
147
|
+
}
|
|
148
|
+
pub(super) fn continuations_allowed(&self) -> bool {
|
|
149
|
+
self.continuations_allowed.get()
|
|
150
|
+
}
|
|
151
|
+
pub(super) fn allow_report_continuations(&self, allowed: bool) {
|
|
152
|
+
self.report_continuations_allowed.set(allowed);
|
|
153
|
+
}
|
|
154
|
+
pub(super) fn report_continuations_allowed(&self) -> bool {
|
|
155
|
+
self.report_continuations_allowed.get()
|
|
156
|
+
}
|
|
157
|
+
pub(super) fn admit(&self, engine: &Engine, root: usize) {
|
|
158
|
+
self.allow_callbacks(engine.scalar_callbacks_safe(root));
|
|
159
|
+
self.allow_continuations(engine.native_continuations_safe(root));
|
|
160
|
+
self.allow_report_continuations(engine.report_continuations_safe(root));
|
|
161
|
+
}
|
|
162
|
+
pub(super) fn initial_report(&self) -> Option<diagnostics::Report> {
|
|
163
|
+
self.reports.initial()
|
|
164
|
+
}
|
|
165
|
+
pub(super) fn report_child(
|
|
166
|
+
&self,
|
|
167
|
+
node: usize,
|
|
168
|
+
value: Value,
|
|
169
|
+
report: &mut diagnostics::Report,
|
|
170
|
+
annotations: bool,
|
|
171
|
+
) -> Result<tracking::Tracked, Verdict> {
|
|
172
|
+
self.report_request(
|
|
173
|
+
ContainsArguments {
|
|
174
|
+
rules: unsafe { sn_nil() },
|
|
175
|
+
instance: value,
|
|
176
|
+
child: node,
|
|
177
|
+
required: 0,
|
|
178
|
+
scalar_kind: CALLBACK_RUNTIME,
|
|
179
|
+
scalar_flags: 0,
|
|
180
|
+
},
|
|
181
|
+
RUNTIME_NATIVE_REPORT,
|
|
182
|
+
report,
|
|
183
|
+
annotations,
|
|
184
|
+
None,
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
pub(super) fn container_report(
|
|
188
|
+
&self,
|
|
189
|
+
node: usize,
|
|
190
|
+
value: Value,
|
|
191
|
+
header: (Value, i32, bool),
|
|
192
|
+
report: &mut diagnostics::Report,
|
|
193
|
+
prior: &tracking::Tracked,
|
|
194
|
+
annotations: bool,
|
|
195
|
+
) -> Result<tracking::Tracked, Verdict> {
|
|
196
|
+
self.report_request(
|
|
197
|
+
ContainsArguments {
|
|
198
|
+
rules: header.0,
|
|
199
|
+
instance: value,
|
|
200
|
+
child: node,
|
|
201
|
+
required: header.1,
|
|
202
|
+
scalar_kind: CALLBACK_RUNTIME,
|
|
203
|
+
scalar_flags: 0,
|
|
204
|
+
},
|
|
205
|
+
if header.2 {
|
|
206
|
+
RUNTIME_OBJECT_REPORT
|
|
207
|
+
} else {
|
|
208
|
+
RUNTIME_ARRAY_REPORT
|
|
209
|
+
},
|
|
210
|
+
report,
|
|
211
|
+
annotations,
|
|
212
|
+
Some(prior),
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
fn report_request(
|
|
216
|
+
&self,
|
|
217
|
+
arguments: ContainsArguments,
|
|
218
|
+
operation: i32,
|
|
219
|
+
report: &mut diagnostics::Report,
|
|
220
|
+
annotations: bool,
|
|
221
|
+
prior: Option<&tracking::Tracked>,
|
|
222
|
+
) -> Result<tracking::Tracked, Verdict> {
|
|
223
|
+
let ordinal = self.next_call()?;
|
|
224
|
+
if self.observe(ordinal, arguments.instance, operation) == Unsupported {
|
|
225
|
+
let seed = match prior {
|
|
226
|
+
Some(prior) => report.container_continuation(annotations, prior),
|
|
227
|
+
None => report.continuation(annotations),
|
|
228
|
+
};
|
|
229
|
+
self.reports.request(ordinal, seed);
|
|
230
|
+
self.pending_contains.set(Some(arguments));
|
|
231
|
+
return Err(Unsupported);
|
|
232
|
+
}
|
|
233
|
+
Ok(report.adopt(self.reports.result(ordinal).ok_or(Unsupported)?))
|
|
234
|
+
}
|
|
235
|
+
pub(super) fn native_child(&self, child: usize, instance: Value) -> Verdict {
|
|
236
|
+
self.children_request(
|
|
237
|
+
ContainsArguments {
|
|
238
|
+
rules: unsafe { sn_nil() },
|
|
239
|
+
instance,
|
|
240
|
+
child,
|
|
241
|
+
required: 0,
|
|
242
|
+
scalar_kind: CALLBACK_RUNTIME,
|
|
243
|
+
scalar_flags: 0,
|
|
244
|
+
},
|
|
245
|
+
RUNTIME_NATIVE_CHILD,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
pub(super) fn contains(
|
|
249
|
+
&self,
|
|
250
|
+
rules: Value,
|
|
251
|
+
instance: Value,
|
|
252
|
+
child: usize,
|
|
253
|
+
required: i32,
|
|
254
|
+
scalar: (i32, i32),
|
|
255
|
+
) -> Verdict {
|
|
256
|
+
self.children_request(
|
|
257
|
+
ContainsArguments {
|
|
258
|
+
rules,
|
|
259
|
+
instance,
|
|
260
|
+
child,
|
|
261
|
+
required,
|
|
262
|
+
scalar_kind: scalar.0,
|
|
263
|
+
scalar_flags: scalar.1,
|
|
264
|
+
},
|
|
265
|
+
RUNTIME_SCALAR_CONTAINS,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
pub(super) fn container_children(
|
|
269
|
+
&self,
|
|
270
|
+
rules: Value,
|
|
271
|
+
instance: Value,
|
|
272
|
+
node: usize,
|
|
273
|
+
required: i32,
|
|
274
|
+
object: bool,
|
|
275
|
+
) -> Verdict {
|
|
276
|
+
self.children_request(
|
|
277
|
+
ContainsArguments {
|
|
278
|
+
rules,
|
|
279
|
+
instance,
|
|
280
|
+
child: node,
|
|
281
|
+
required,
|
|
282
|
+
scalar_kind: CALLBACK_RUNTIME,
|
|
283
|
+
scalar_flags: 0,
|
|
284
|
+
},
|
|
285
|
+
if object {
|
|
286
|
+
RUNTIME_OBJECT_CHILDREN
|
|
287
|
+
} else {
|
|
288
|
+
RUNTIME_ARRAY_CHILDREN
|
|
289
|
+
},
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
fn children_request(&self, arguments: ContainsArguments, operation: i32) -> Verdict {
|
|
293
|
+
let Ok(ordinal) = self.next_call() else {
|
|
294
|
+
return Unsupported;
|
|
295
|
+
};
|
|
296
|
+
let result = self.observe(ordinal, arguments.instance, operation);
|
|
297
|
+
if result == Unsupported {
|
|
298
|
+
self.pending_contains.set(Some(arguments));
|
|
299
|
+
}
|
|
300
|
+
result
|
|
301
|
+
}
|
|
302
|
+
pub(super) fn runtime_order(&self) -> bool {
|
|
303
|
+
self.runtime_order.get()
|
|
304
|
+
}
|
|
305
|
+
pub(super) fn budget(&self, limits: budget::Limits) -> budget::Budget {
|
|
306
|
+
if self.scalar_callback() {
|
|
307
|
+
// These children execute shared Ruby scalar primitives. Their VM
|
|
308
|
+
// checkpoints own the method boundaries; an entry/exit native
|
|
309
|
+
// deadline check could preempt an exception caught by the caller's
|
|
310
|
+
// Ruby block. The parent retains its native invocation deadline.
|
|
311
|
+
return budget::Budget::new(budget::Limits {
|
|
312
|
+
enabled: limits.enabled & !2,
|
|
313
|
+
..limits
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
budget::Budget::resume(limits, &self.deadline)
|
|
317
|
+
}
|
|
318
|
+
pub(super) fn is_match(&self, index: usize, regex: &Regex, bytes: &[u8]) -> bool {
|
|
319
|
+
// Each runner is tied to one engine, whose regex indices are fixed at
|
|
320
|
+
// compilation. Allocate only the caches this validator actually uses.
|
|
321
|
+
let mut caches = self.searches.borrow_mut();
|
|
322
|
+
if caches.len() <= index {
|
|
323
|
+
caches.resize_with(index + 1, || None);
|
|
324
|
+
}
|
|
325
|
+
let cache = caches[index].get_or_insert_with(|| Box::new(regex.create_cache()));
|
|
326
|
+
regex
|
|
327
|
+
.search_half_with(cache, &Input::new(bytes).earliest(true))
|
|
328
|
+
.is_some()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
pub(super) fn ruby_match(&self, source: Value, value: Value, assertion: bool) -> Verdict {
|
|
332
|
+
self.observe(
|
|
333
|
+
source,
|
|
334
|
+
value,
|
|
335
|
+
if assertion {
|
|
336
|
+
RUNTIME_REGEXP_ASSERTION
|
|
337
|
+
} else {
|
|
338
|
+
RUNTIME_REGEXP_STRICT
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
pub(super) fn json(&self, value: Value) -> Verdict {
|
|
343
|
+
// Qundef cannot be a Ruby pattern operand, including malformed schemas.
|
|
344
|
+
self.observe(unsafe { sn_undefined() }, value, RUNTIME_JSON)
|
|
345
|
+
}
|
|
346
|
+
pub(super) fn dynamic_calls(&self) -> bool {
|
|
347
|
+
self.dynamic_calls.get()
|
|
348
|
+
}
|
|
349
|
+
fn next_call(&self) -> Result<Value, Verdict> {
|
|
350
|
+
if !self.dynamic_calls.replace(true) {
|
|
351
|
+
self.reorder.set(true);
|
|
352
|
+
}
|
|
353
|
+
let ordinal = self.call_cursor.get();
|
|
354
|
+
if ordinal > (isize::MAX >> 1) as usize {
|
|
355
|
+
return Err(Unsupported);
|
|
356
|
+
}
|
|
357
|
+
self.call_cursor.set(ordinal + 1);
|
|
358
|
+
Ok(unsafe { sn_runtime_ordinal(ordinal) })
|
|
359
|
+
}
|
|
360
|
+
pub(super) fn path_pop(&self) -> Result<(), Verdict> {
|
|
361
|
+
let ordinal = self.next_call()?;
|
|
362
|
+
if self.observe(ordinal, unsafe { sn_nil() }, RUNTIME_PATH_POP) == Unsupported {
|
|
363
|
+
Err(Unsupported)
|
|
364
|
+
} else {
|
|
365
|
+
Ok(())
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
pub(super) fn callback_checkpoint(&self, value: Value) -> Verdict {
|
|
369
|
+
if !self.callback_checkpoints.get() {
|
|
370
|
+
return Valid;
|
|
371
|
+
}
|
|
372
|
+
let Ok(ordinal) = self.next_call() else {
|
|
373
|
+
return Unsupported;
|
|
374
|
+
};
|
|
375
|
+
self.observe(ordinal, value, RUNTIME_CALLBACK_CHECKPOINT)
|
|
376
|
+
}
|
|
377
|
+
pub(super) fn type_predicate(&self, value: Value, mask: i32, union: bool) -> Verdict {
|
|
378
|
+
let Ok(ordinal) = self.next_call() else {
|
|
379
|
+
return Unsupported;
|
|
380
|
+
};
|
|
381
|
+
self.observe(ordinal, value, RUNTIME_TYPE + mask * 2 + i32::from(union))
|
|
382
|
+
}
|
|
383
|
+
pub(super) fn number(
|
|
384
|
+
&self,
|
|
385
|
+
rules: Value,
|
|
386
|
+
instance: Value,
|
|
387
|
+
required: i32,
|
|
388
|
+
) -> Result<ScalarFrame<'_>, Verdict> {
|
|
389
|
+
self.scalar(rules, instance, required, RUNTIME_NUMBER, 64)
|
|
390
|
+
}
|
|
391
|
+
pub(super) fn string(
|
|
392
|
+
&self,
|
|
393
|
+
rules: Value,
|
|
394
|
+
instance: Value,
|
|
395
|
+
flags: i32,
|
|
396
|
+
) -> Result<ScalarFrame<'_>, Verdict> {
|
|
397
|
+
self.scalar(rules, instance, flags, RUNTIME_STRING, 256)
|
|
398
|
+
}
|
|
399
|
+
fn scalar(
|
|
400
|
+
&self,
|
|
401
|
+
rules: Value,
|
|
402
|
+
instance: Value,
|
|
403
|
+
required: i32,
|
|
404
|
+
base: i32,
|
|
405
|
+
stride: i32,
|
|
406
|
+
) -> Result<ScalarFrame<'_>, Verdict> {
|
|
407
|
+
Ok(ScalarFrame {
|
|
408
|
+
caches: self,
|
|
409
|
+
arguments: ScalarArguments {
|
|
410
|
+
ordinal: self.next_call()?,
|
|
411
|
+
rules,
|
|
412
|
+
instance,
|
|
413
|
+
},
|
|
414
|
+
required,
|
|
415
|
+
base,
|
|
416
|
+
stride,
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
pub(super) fn container(
|
|
420
|
+
&self,
|
|
421
|
+
rules: Value,
|
|
422
|
+
instance: Value,
|
|
423
|
+
required: i32,
|
|
424
|
+
object: bool,
|
|
425
|
+
) -> Result<ScalarFrame<'_>, Verdict> {
|
|
426
|
+
self.scalar(
|
|
427
|
+
rules,
|
|
428
|
+
instance,
|
|
429
|
+
required,
|
|
430
|
+
if object {
|
|
431
|
+
RUNTIME_OBJECT_HEADER
|
|
432
|
+
} else {
|
|
433
|
+
RUNTIME_ARRAY_HEADER
|
|
434
|
+
},
|
|
435
|
+
64,
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
pub(super) fn equality(&self, operand: Value, instance: Value, operation: i32) -> Verdict {
|
|
439
|
+
let Ok(ordinal) = self.next_call() else {
|
|
440
|
+
return Unsupported;
|
|
441
|
+
};
|
|
442
|
+
let result = self.observe(ordinal, instance, operation);
|
|
443
|
+
if result == Unsupported {
|
|
444
|
+
self.pending_scalar.set(Some(ScalarArguments {
|
|
445
|
+
ordinal,
|
|
446
|
+
rules: operand,
|
|
447
|
+
instance,
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
result
|
|
451
|
+
}
|
|
452
|
+
pub(super) fn required_missing(&self, name: Value, value: Value) -> Verdict {
|
|
453
|
+
self.observe(name, value, RUNTIME_REQUIRED_MISSING)
|
|
454
|
+
}
|
|
455
|
+
pub(super) fn error_message(
|
|
456
|
+
&self,
|
|
457
|
+
keyword: &'static str,
|
|
458
|
+
source: (i32, Value),
|
|
459
|
+
instance: Value,
|
|
460
|
+
detail: usize,
|
|
461
|
+
ordinal: usize,
|
|
462
|
+
) -> Result<Value, Verdict> {
|
|
463
|
+
let message = Message {
|
|
464
|
+
ordinal,
|
|
465
|
+
keyword,
|
|
466
|
+
opcode: source.0,
|
|
467
|
+
operand: source.1,
|
|
468
|
+
instance,
|
|
469
|
+
detail,
|
|
470
|
+
frame: unsafe { sn_undefined() },
|
|
471
|
+
};
|
|
472
|
+
self.message(message)
|
|
473
|
+
}
|
|
474
|
+
pub(super) fn frame_message(
|
|
475
|
+
&self,
|
|
476
|
+
keyword: &'static str,
|
|
477
|
+
source: (i32, Value),
|
|
478
|
+
instance: Value,
|
|
479
|
+
frame: Value,
|
|
480
|
+
ordinal: usize,
|
|
481
|
+
rescued: bool,
|
|
482
|
+
) -> Result<Value, Verdict> {
|
|
483
|
+
self.message(Message {
|
|
484
|
+
ordinal,
|
|
485
|
+
keyword,
|
|
486
|
+
opcode: source.0,
|
|
487
|
+
operand: source.1,
|
|
488
|
+
instance,
|
|
489
|
+
detail: usize::from(rescued),
|
|
490
|
+
frame,
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
pub(super) fn begin_string_error(
|
|
494
|
+
&self,
|
|
495
|
+
frame: Value,
|
|
496
|
+
ordinal: usize,
|
|
497
|
+
keyword: &'static str,
|
|
498
|
+
) -> bool {
|
|
499
|
+
let key = (frame, ordinal, keyword);
|
|
500
|
+
if self.rescued_strings.borrow().contains(&key) {
|
|
501
|
+
self.apply_runtime(scopes::Key::Rescue(key));
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
self.pending_rescue.set(Some(key));
|
|
505
|
+
true
|
|
506
|
+
}
|
|
507
|
+
pub(super) fn end_string_error(&self) {
|
|
508
|
+
self.pending_rescue.set(None);
|
|
509
|
+
}
|
|
510
|
+
fn message(&self, message: Message) -> Result<Value, Verdict> {
|
|
511
|
+
if let Some(value) = self.messages.borrow_mut().entry(message).or_insert(None) {
|
|
512
|
+
self.apply_runtime(scopes::Key::Message(message));
|
|
513
|
+
return Ok(*value);
|
|
514
|
+
}
|
|
515
|
+
self.request_runtime(scopes::Key::Message(message));
|
|
516
|
+
self.pending_message.set(Some(message));
|
|
517
|
+
self.pending.set(Some((
|
|
518
|
+
unsafe { sn_undefined() },
|
|
519
|
+
unsafe { sn_nil() },
|
|
520
|
+
RUNTIME_DIAGNOSTIC_MESSAGE,
|
|
521
|
+
)));
|
|
522
|
+
Err(Unsupported)
|
|
523
|
+
}
|
|
524
|
+
pub(super) fn pointer(&self, path: &pointer::Path) -> Result<Value, Verdict> {
|
|
525
|
+
let mut pointers = self.pointers.borrow_mut();
|
|
526
|
+
let index = if let Some(&index) = pointers.indices.get(path) {
|
|
527
|
+
index
|
|
528
|
+
} else {
|
|
529
|
+
let path = std::sync::Arc::new(path.clone());
|
|
530
|
+
let index = pointers.requests.len();
|
|
531
|
+
pointers.indices.insert(path.clone(), index);
|
|
532
|
+
pointers
|
|
533
|
+
.requests
|
|
534
|
+
.push(PointerRequest { path, result: None });
|
|
535
|
+
index
|
|
536
|
+
};
|
|
537
|
+
if let Some(value) = pointers.requests[index].result {
|
|
538
|
+
self.apply_runtime(scopes::Key::Pointer(index));
|
|
539
|
+
return Ok(value);
|
|
540
|
+
}
|
|
541
|
+
self.request_runtime(scopes::Key::Pointer(index));
|
|
542
|
+
self.pending_pointer.set(Some(index));
|
|
543
|
+
self.pending.set(Some((
|
|
544
|
+
unsafe { sn_undefined() },
|
|
545
|
+
unsafe { sn_nil() },
|
|
546
|
+
RUNTIME_POINTER,
|
|
547
|
+
)));
|
|
548
|
+
Err(Unsupported)
|
|
549
|
+
}
|
|
550
|
+
pub(super) fn null_number(&self, value: Value, bit: i32, decimal: bool) -> Verdict {
|
|
551
|
+
self.observe(
|
|
552
|
+
unsafe { sn_nil() },
|
|
553
|
+
value,
|
|
554
|
+
RUNTIME_NULL_NUMBER + bit * 2 + i32::from(decimal),
|
|
555
|
+
)
|
|
556
|
+
}
|
|
557
|
+
pub(super) fn invalid_regexp(&self, source: Value, value: Value) -> bool {
|
|
558
|
+
let matches = self.matches.get();
|
|
559
|
+
let invalid = !matches.is_null()
|
|
560
|
+
&& unsafe { sn_cached_runtime(matches, RUNTIME_REGEXP_ASSERTION, source, value) }
|
|
561
|
+
== REGEXP_INVALID;
|
|
562
|
+
if invalid {
|
|
563
|
+
self.apply_runtime(scopes::Key::Value(source, value, RUNTIME_REGEXP_ASSERTION));
|
|
564
|
+
}
|
|
565
|
+
invalid
|
|
566
|
+
}
|
|
567
|
+
pub(super) fn capture_report(&self, report: &crate::diagnostics::Report) {
|
|
568
|
+
self.reports.capture(report);
|
|
569
|
+
}
|
|
570
|
+
pub(super) fn apply_report_effects(&self, report: &mut crate::diagnostics::Report) {
|
|
571
|
+
self.reports.apply(report);
|
|
572
|
+
}
|
|
573
|
+
fn request_runtime(&self, key: scopes::Key) {
|
|
574
|
+
self.scopes.request(key);
|
|
575
|
+
self.reports.request_runtime(key);
|
|
576
|
+
}
|
|
577
|
+
fn apply_runtime(&self, key: scopes::Key) {
|
|
578
|
+
self.scopes.apply(key);
|
|
579
|
+
self.reports.apply_runtime(key);
|
|
580
|
+
}
|
|
581
|
+
fn observe(&self, source: Value, value: Value, operation: i32) -> Verdict {
|
|
582
|
+
let matches = self.matches.get();
|
|
583
|
+
if !matches.is_null() {
|
|
584
|
+
let cached = unsafe { sn_cached_runtime(matches, operation, source, value) };
|
|
585
|
+
if operation == RUNTIME_REGEXP_ASSERTION && cached == REGEXP_INVALID {
|
|
586
|
+
self.apply_runtime(scopes::Key::Value(source, value, operation));
|
|
587
|
+
return Invalid;
|
|
588
|
+
}
|
|
589
|
+
let result = decode(cached);
|
|
590
|
+
if result != Unsupported {
|
|
591
|
+
self.apply_runtime(scopes::Key::Value(source, value, operation));
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
self.request_runtime(scopes::Key::Value(source, value, operation));
|
|
596
|
+
self.pending.set(Some((source, value, operation)));
|
|
597
|
+
Unsupported
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
pub(super) struct ScalarFrame<'a> {
|
|
601
|
+
caches: &'a Caches,
|
|
602
|
+
arguments: ScalarArguments,
|
|
603
|
+
required: i32,
|
|
604
|
+
base: i32,
|
|
605
|
+
stride: i32,
|
|
606
|
+
}
|
|
607
|
+
impl ScalarFrame<'_> {
|
|
608
|
+
pub(super) fn ordinal(&self) -> Value {
|
|
609
|
+
self.arguments.ordinal
|
|
610
|
+
}
|
|
611
|
+
pub(super) fn step(&self, step: i32) -> Verdict {
|
|
612
|
+
let Some(operation) = step
|
|
613
|
+
.checked_mul(self.stride)
|
|
614
|
+
.and_then(|step| self.base.checked_add(step))
|
|
615
|
+
.and_then(|operation| operation.checked_add(self.required))
|
|
616
|
+
else {
|
|
617
|
+
return Unsupported;
|
|
618
|
+
};
|
|
619
|
+
let result =
|
|
620
|
+
self.caches
|
|
621
|
+
.observe(self.arguments.ordinal, self.arguments.instance, operation);
|
|
622
|
+
if result == Unsupported {
|
|
623
|
+
self.caches.pending_scalar.set(Some(self.arguments));
|
|
624
|
+
}
|
|
625
|
+
result
|
|
626
|
+
}
|
|
627
|
+
pub(super) fn predicate(&self, step: i32) -> Result<bool, Verdict> {
|
|
628
|
+
match self.step(step) {
|
|
629
|
+
Valid => Ok(true),
|
|
630
|
+
Invalid => Ok(false),
|
|
631
|
+
Unsupported => Err(Unsupported),
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/// # Safety
|
|
636
|
+
/// Caches must be null or the currently active runner's caches, and klass one
|
|
637
|
+
/// of the exact identity scalar classes. No Ruby allocation is performed here.
|
|
638
|
+
#[unsafe(no_mangle)]
|
|
639
|
+
pub unsafe extern "C" fn sn_identity_kind(caches: *const Caches, klass: Value) -> i32 {
|
|
640
|
+
let Some(caches) = (unsafe { caches.as_ref() }) else {
|
|
641
|
+
return TYPE_UNSUPPORTED;
|
|
642
|
+
};
|
|
643
|
+
match caches.observe(klass, unsafe { sn_nil() }, RUNTIME_IDENTITY_METHODS) {
|
|
644
|
+
Valid => TYPE_IDENTITY_SCALAR,
|
|
645
|
+
Invalid | Unsupported => TYPE_UNSUPPORTED,
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
#[unsafe(no_mangle)]
|
|
649
|
+
pub extern "C" fn sn_caches_new() -> *mut Caches {
|
|
650
|
+
Box::into_raw(Box::new(Caches::default()))
|
|
651
|
+
}
|
|
652
|
+
/// # Safety
|
|
653
|
+
/// Caches must be owned, returned by sn_caches_new, and freed exactly once.
|
|
654
|
+
#[unsafe(no_mangle)]
|
|
655
|
+
pub unsafe extern "C" fn sn_caches_free(caches: *mut Caches) {
|
|
656
|
+
drop(unsafe { Box::from_raw(caches) });
|
|
657
|
+
}
|
|
658
|
+
/// # Safety
|
|
659
|
+
/// Caches must be live; call under the GVL or while all Ractors are stopped.
|
|
660
|
+
#[unsafe(no_mangle)]
|
|
661
|
+
pub unsafe extern "C" fn sn_caches_size(caches_ptr: *const Caches) -> usize {
|
|
662
|
+
// GC may inspect a stopped runner while its search has borrowed a cache.
|
|
663
|
+
let Ok(caches) = (unsafe { &*caches_ptr }).searches.try_borrow() else {
|
|
664
|
+
return std::mem::size_of::<Caches>();
|
|
665
|
+
};
|
|
666
|
+
let pointers = unsafe { &*caches_ptr }
|
|
667
|
+
.pointers
|
|
668
|
+
.try_borrow()
|
|
669
|
+
.map_or(0, |pointers| {
|
|
670
|
+
pointers.indices.capacity()
|
|
671
|
+
* std::mem::size_of::<(std::sync::Arc<pointer::Path>, usize)>()
|
|
672
|
+
+ pointers.requests.capacity() * std::mem::size_of::<PointerRequest>()
|
|
673
|
+
+ pointers
|
|
674
|
+
.requests
|
|
675
|
+
.iter()
|
|
676
|
+
.map(|r| std::mem::size_of::<pointer::Path>() + r.path.memory_usage())
|
|
677
|
+
.sum::<usize>()
|
|
678
|
+
});
|
|
679
|
+
let messages = unsafe { &*caches_ptr }
|
|
680
|
+
.messages
|
|
681
|
+
.try_borrow()
|
|
682
|
+
.map_or(0, |messages| {
|
|
683
|
+
messages.capacity() * std::mem::size_of::<(Message, Option<Value>)>()
|
|
684
|
+
});
|
|
685
|
+
let rescued = unsafe { &*caches_ptr }
|
|
686
|
+
.rescued_strings
|
|
687
|
+
.try_borrow()
|
|
688
|
+
.map_or(0, |set| set.capacity() * std::mem::size_of::<StringError>());
|
|
689
|
+
std::mem::size_of::<Caches>()
|
|
690
|
+
+ unsafe { &*caches_ptr }.scopes.size()
|
|
691
|
+
+ rescued
|
|
692
|
+
+ messages
|
|
693
|
+
+ unsafe { &*caches_ptr }.reports.memory_usage()
|
|
694
|
+
+ unsafe { &*caches_ptr }.properties.memory_usage()
|
|
695
|
+
+ pointers
|
|
696
|
+
+ caches.capacity() * std::mem::size_of::<Option<Box<Cache>>>()
|
|
697
|
+
+ caches
|
|
698
|
+
.iter()
|
|
699
|
+
.flatten()
|
|
700
|
+
.map(|cache| std::mem::size_of::<Cache>() + cache.memory_usage())
|
|
701
|
+
.sum::<usize>()
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/// # Safety
|
|
705
|
+
/// All pointers must be live. The C table belongs to the owning runner.
|
|
706
|
+
#[unsafe(no_mangle)]
|
|
707
|
+
pub unsafe extern "C" fn sn_caches_runtime(caches: *const Caches, matches: *const c_void) {
|
|
708
|
+
let caches = unsafe { &*caches };
|
|
709
|
+
caches.matches.set(matches);
|
|
710
|
+
caches.reset_pass();
|
|
711
|
+
}
|
|
712
|
+
/// # Safety
|
|
713
|
+
/// Caches must be live and idle under the GVL.
|
|
714
|
+
#[unsafe(no_mangle)]
|
|
715
|
+
pub unsafe extern "C" fn sn_caches_frame_error(caches: *const Caches) -> i32 {
|
|
716
|
+
i32::from(unsafe { &*caches }.frame_error.get())
|
|
717
|
+
}
|
|
718
|
+
/// # Safety
|
|
719
|
+
/// Caches must be idle under the GVL. Capture only a RegexpError raised while
|
|
720
|
+
/// constructing a reached string error; its rescue handler must not catch again.
|
|
721
|
+
#[unsafe(no_mangle)]
|
|
722
|
+
pub unsafe extern "C" fn sn_caches_rescue_regexp(caches: *const Caches) -> i32 {
|
|
723
|
+
let caches = unsafe { &*caches };
|
|
724
|
+
if let Some(key) = caches.pending_rescue.take() {
|
|
725
|
+
caches.scopes.rescue(key);
|
|
726
|
+
caches.rescued_strings.borrow_mut().insert(key);
|
|
727
|
+
1
|
|
728
|
+
} else {
|
|
729
|
+
0
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/// # Safety
|
|
733
|
+
/// Caches must be live and idle under the GVL. Consume a transition to ordered
|
|
734
|
+
/// scalar calls before C services a request with newly assigned ordinals.
|
|
735
|
+
#[unsafe(no_mangle)]
|
|
736
|
+
pub unsafe extern "C" fn sn_caches_take_reorder(caches: *const Caches) -> i32 {
|
|
737
|
+
i32::from(unsafe { &*caches }.reorder.replace(false))
|
|
738
|
+
}
|
|
739
|
+
/// # Safety
|
|
740
|
+
/// Caches must be live and called only between complete validation calls.
|
|
741
|
+
#[unsafe(no_mangle)]
|
|
742
|
+
pub unsafe extern "C" fn sn_caches_begin(caches: *const Caches) {
|
|
743
|
+
let caches = unsafe { &*caches };
|
|
744
|
+
caches.scopes.clear();
|
|
745
|
+
caches.reports.clear();
|
|
746
|
+
caches.properties.clear();
|
|
747
|
+
caches.report_continuations_allowed.set(false);
|
|
748
|
+
caches.reset_pass();
|
|
749
|
+
caches.deadline.set(None);
|
|
750
|
+
caches.runtime_order.set(false);
|
|
751
|
+
caches.dynamic_calls.set(false);
|
|
752
|
+
caches.scalar_callback.set(false);
|
|
753
|
+
caches.callback_checkpoints.set(false);
|
|
754
|
+
caches.callbacks_allowed.set(false);
|
|
755
|
+
caches.continuations_allowed.set(false);
|
|
756
|
+
caches.reorder.set(false);
|
|
757
|
+
caches.rescued_strings.borrow_mut().clear();
|
|
758
|
+
*caches.pointers.borrow_mut() = Pointers::default();
|
|
759
|
+
*caches.messages.borrow_mut() = HashMap::new();
|
|
760
|
+
}
|
|
761
|
+
/// # Safety
|
|
762
|
+
/// Caches must be live and no Rust evaluation may be active.
|
|
763
|
+
#[unsafe(no_mangle)]
|
|
764
|
+
pub unsafe extern "C" fn sn_caches_observe(caches: *const Caches) {
|
|
765
|
+
let caches = unsafe { &*caches };
|
|
766
|
+
caches.reset_pass();
|
|
767
|
+
caches.runtime_order.set(true);
|
|
768
|
+
}
|
|
769
|
+
/// # Safety
|
|
770
|
+
/// Caches and output pointers must be live under the GVL.
|
|
771
|
+
#[unsafe(no_mangle)]
|
|
772
|
+
pub unsafe extern "C" fn sn_caches_pending(
|
|
773
|
+
caches: *const Caches,
|
|
774
|
+
source: *mut Value,
|
|
775
|
+
value: *mut Value,
|
|
776
|
+
operation: *mut i32,
|
|
777
|
+
) -> i32 {
|
|
778
|
+
if let Some((operand, instance, kind)) = unsafe { &*caches }.pending.get() {
|
|
779
|
+
unsafe {
|
|
780
|
+
*source = operand;
|
|
781
|
+
*value = instance;
|
|
782
|
+
*operation = kind;
|
|
783
|
+
}
|
|
784
|
+
1
|
|
785
|
+
} else {
|
|
786
|
+
0
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/// # Safety
|
|
790
|
+
/// Both caches must be live and idle under the GVL. The parent has unwound.
|
|
791
|
+
#[unsafe(no_mangle)]
|
|
792
|
+
pub unsafe extern "C" fn sn_caches_child(
|
|
793
|
+
caches: *const Caches,
|
|
794
|
+
parent: *const Caches,
|
|
795
|
+
limited: i32,
|
|
796
|
+
scalar: i32,
|
|
797
|
+
) {
|
|
798
|
+
let caches = unsafe { &*caches };
|
|
799
|
+
caches.deadline.set(unsafe { &*parent }.deadline.get());
|
|
800
|
+
caches.scalar_callback.set(scalar != 0);
|
|
801
|
+
caches.callback_checkpoints.set(scalar != 0 && limited != 0);
|
|
802
|
+
caches.dynamic_calls.set(true);
|
|
803
|
+
caches.runtime_order.set(true);
|
|
804
|
+
}
|
|
805
|
+
/// # Safety
|
|
806
|
+
/// Caches must be live and idle under the GVL.
|
|
807
|
+
#[unsafe(no_mangle)]
|
|
808
|
+
pub unsafe extern "C" fn sn_caches_is_scalar(caches: *const Caches) -> i32 {
|
|
809
|
+
i32::from(unsafe { &*caches }.scalar_callback())
|
|
810
|
+
}
|
|
811
|
+
/// # Safety
|
|
812
|
+
/// Caches must be live and idle under the GVL, after a Ruby scalar has returned.
|
|
813
|
+
#[unsafe(no_mangle)]
|
|
814
|
+
pub unsafe extern "C" fn sn_caches_deadline_expired(caches: *const Caches) -> i32 {
|
|
815
|
+
i32::from(
|
|
816
|
+
unsafe { &*caches }
|
|
817
|
+
.deadline
|
|
818
|
+
.get()
|
|
819
|
+
.is_some_and(|deadline| std::time::Instant::now() >= deadline),
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
/// # Safety
|
|
823
|
+
/// Caches must hold a pending child request and output must be writable.
|
|
824
|
+
#[unsafe(no_mangle)]
|
|
825
|
+
pub unsafe extern "C" fn sn_contains_arguments(
|
|
826
|
+
caches: *const Caches,
|
|
827
|
+
output: *mut ContainsArguments,
|
|
828
|
+
) {
|
|
829
|
+
unsafe {
|
|
830
|
+
*output = (&*caches)
|
|
831
|
+
.pending_contains
|
|
832
|
+
.get()
|
|
833
|
+
.expect("pending contains callback")
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/// # Safety
|
|
837
|
+
/// Call from the owning runner's GC mark callback while caches are live.
|
|
838
|
+
#[unsafe(no_mangle)]
|
|
839
|
+
pub unsafe extern "C" fn sn_caches_mark(caches: *const Caches) {
|
|
840
|
+
unsafe { &*caches }.scopes.mark();
|
|
841
|
+
unsafe { &*caches }.reports.mark();
|
|
842
|
+
unsafe { &*caches }.properties.mark();
|
|
843
|
+
if let Some(arguments) = unsafe { &*caches }.pending_contains.get() {
|
|
844
|
+
unsafe {
|
|
845
|
+
sn_pin(arguments.rules);
|
|
846
|
+
sn_pin(arguments.instance);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
// All keys also belong to the live original instance/schema. C roots every
|
|
850
|
+
// completed result before recording it, including during stopped-Ractor GC.
|
|
851
|
+
if let Ok(pointers) = unsafe { &*caches }.pointers.try_borrow() {
|
|
852
|
+
for request in &pointers.requests {
|
|
853
|
+
for &(_, key) in &request.path.names {
|
|
854
|
+
unsafe { sn_pin(key) };
|
|
855
|
+
}
|
|
856
|
+
if let Some(value) = request.result {
|
|
857
|
+
unsafe { sn_pin(value) };
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if let Ok(messages) = unsafe { &*caches }.messages.try_borrow() {
|
|
863
|
+
for (message, result) in messages.iter() {
|
|
864
|
+
unsafe {
|
|
865
|
+
sn_pin(message.operand);
|
|
866
|
+
sn_pin(message.instance);
|
|
867
|
+
if let Some(value) = result {
|
|
868
|
+
sn_pin(*value);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if let Some(scalar) = unsafe { &*caches }.pending_scalar.get() {
|
|
874
|
+
unsafe {
|
|
875
|
+
sn_pin(scalar.rules);
|
|
876
|
+
sn_pin(scalar.instance);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if let Some((source, value, _)) = unsafe { &*caches }.pending.get() {
|
|
880
|
+
unsafe {
|
|
881
|
+
sn_pin(source);
|
|
882
|
+
sn_pin(value);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/// # Safety
|
|
888
|
+
/// Caches must have a pending pointer request and be idle under the GVL.
|
|
889
|
+
#[unsafe(no_mangle)]
|
|
890
|
+
pub unsafe extern "C" fn sn_pointer_parts(caches: *const Caches) -> usize {
|
|
891
|
+
let caches = unsafe { &*caches };
|
|
892
|
+
caches.pointers.borrow().requests[caches.pending_pointer.get().unwrap()]
|
|
893
|
+
.path
|
|
894
|
+
.names
|
|
895
|
+
.len()
|
|
896
|
+
+ 1
|
|
897
|
+
}
|
|
898
|
+
/// # Safety
|
|
899
|
+
/// Caches must have a pending pointer request; index must be in bounds and
|
|
900
|
+
/// output pointers writable. Text remains owned by the request until cleanup.
|
|
901
|
+
#[unsafe(no_mangle)]
|
|
902
|
+
pub unsafe extern "C" fn sn_pointer_part(
|
|
903
|
+
caches: *const Caches,
|
|
904
|
+
index: usize,
|
|
905
|
+
data: *mut *const u8,
|
|
906
|
+
len: *mut usize,
|
|
907
|
+
key: *mut Value,
|
|
908
|
+
) {
|
|
909
|
+
let caches = unsafe { &*caches };
|
|
910
|
+
let pointers = caches.pointers.borrow();
|
|
911
|
+
let path = &pointers.requests[caches.pending_pointer.get().unwrap()].path;
|
|
912
|
+
let start = if index == 0 {
|
|
913
|
+
0
|
|
914
|
+
} else {
|
|
915
|
+
path.names[index - 1].0
|
|
916
|
+
};
|
|
917
|
+
let (end, name) = path
|
|
918
|
+
.names
|
|
919
|
+
.get(index)
|
|
920
|
+
.copied()
|
|
921
|
+
.unwrap_or((path.text.len(), unsafe { sn_undefined() }));
|
|
922
|
+
unsafe {
|
|
923
|
+
*data = path.text.as_ptr().add(start);
|
|
924
|
+
*len = end - start;
|
|
925
|
+
*key = name;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/// # Safety
|
|
929
|
+
/// Caches must have a pending pointer request. Value must be rooted by the
|
|
930
|
+
/// owning C runner until cleanup, with no active Rust evaluation.
|
|
931
|
+
#[unsafe(no_mangle)]
|
|
932
|
+
pub unsafe extern "C" fn sn_pointer_result(caches: *const Caches, value: Value) {
|
|
933
|
+
let caches = unsafe { &*caches };
|
|
934
|
+
caches.pointers.borrow_mut().requests[caches.pending_pointer.get().unwrap()].result =
|
|
935
|
+
Some(value);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/// # Safety
|
|
939
|
+
/// Caches must have a pending message and the output pointer must be writable.
|
|
940
|
+
#[unsafe(no_mangle)]
|
|
941
|
+
pub unsafe extern "C" fn sn_message_arguments(
|
|
942
|
+
caches: *const Caches,
|
|
943
|
+
output: *mut MessageArguments,
|
|
944
|
+
) {
|
|
945
|
+
let message = unsafe { &*caches }.pending_message.get().unwrap();
|
|
946
|
+
unsafe {
|
|
947
|
+
*output = MessageArguments {
|
|
948
|
+
keyword: message.keyword.as_ptr(),
|
|
949
|
+
keyword_len: message.keyword.len(),
|
|
950
|
+
opcode: message.opcode,
|
|
951
|
+
operand: message.operand,
|
|
952
|
+
instance: message.instance,
|
|
953
|
+
detail: message.detail,
|
|
954
|
+
frame: message.frame,
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/// # Safety
|
|
959
|
+
/// Caches must have a pending message with no active Rust evaluation.
|
|
960
|
+
#[unsafe(no_mangle)]
|
|
961
|
+
pub unsafe extern "C" fn sn_message_result(caches: *const Caches, result: Value) {
|
|
962
|
+
let caches = unsafe { &*caches };
|
|
963
|
+
caches
|
|
964
|
+
.messages
|
|
965
|
+
.borrow_mut()
|
|
966
|
+
.insert(caches.pending_message.get().unwrap(), Some(result));
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/// # Safety
|
|
970
|
+
/// Caches must have a pending scalar request and output must be writable.
|
|
971
|
+
#[unsafe(no_mangle)]
|
|
972
|
+
pub unsafe extern "C" fn sn_scalar_arguments(caches: *const Caches, output: *mut ScalarArguments) {
|
|
973
|
+
unsafe {
|
|
974
|
+
*output = (&*caches).pending_scalar.get().unwrap();
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
#[cfg(test)]
|
|
979
|
+
mod tests {
|
|
980
|
+
use super::*;
|
|
981
|
+
#[test]
|
|
982
|
+
fn child_modes_inherit_deadlines_without_adding_normal_scalar_checkpoints() {
|
|
983
|
+
let parent = Caches::default();
|
|
984
|
+
let child = Caches::default();
|
|
985
|
+
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
|
986
|
+
parent.deadline.set(Some(deadline));
|
|
987
|
+
unsafe { sn_caches_child(&child, &parent, 1, 0) };
|
|
988
|
+
assert_eq!(child.deadline.get(), Some(deadline));
|
|
989
|
+
assert!(!child.scalar_callback());
|
|
990
|
+
assert!(!child.callback_checkpoints.get());
|
|
991
|
+
assert!(child.dynamic_calls());
|
|
992
|
+
assert!(child.runtime_order());
|
|
993
|
+
unsafe { sn_caches_child(&child, &parent, 1, 1) };
|
|
994
|
+
assert_eq!(child.deadline.get(), Some(deadline));
|
|
995
|
+
assert!(child.scalar_callback());
|
|
996
|
+
assert!(child.callback_checkpoints.get());
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
#[test]
|
|
1000
|
+
fn reports_memory_when_a_search_cache_is_borrowed() {
|
|
1001
|
+
let caches = Caches::default();
|
|
1002
|
+
let regex = Regex::new("value[0-9]+").unwrap();
|
|
1003
|
+
assert!(caches.is_match(0, ®ex, b"value1"));
|
|
1004
|
+
assert!(unsafe { sn_caches_size(&caches) } > std::mem::size_of::<Caches>());
|
|
1005
|
+
let _search = caches.searches.borrow_mut();
|
|
1006
|
+
assert_eq!(
|
|
1007
|
+
unsafe { sn_caches_size(&caches) },
|
|
1008
|
+
std::mem::size_of::<Caches>()
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/// # Safety
|
|
1014
|
+
/// Caches are idle at the start of a public invocation with a reused Ruby pool.
|
|
1015
|
+
#[unsafe(no_mangle)]
|
|
1016
|
+
pub unsafe extern "C" fn sn_caches_reuse_buffers(caches: *const Caches) {
|
|
1017
|
+
let caches = unsafe { &*caches };
|
|
1018
|
+
caches.dynamic_calls.set(true);
|
|
1019
|
+
caches.runtime_order.set(true);
|
|
1020
|
+
}
|