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
|
@@ -1,61 +1,25 @@
|
|
|
1
1
|
//! Crate-backed matching over borrowed Ruby string bytes.
|
|
2
2
|
//! The compatibility adapter translates VM shorthands and absolute anchors;
|
|
3
|
-
//! regex-syntax parses syntax
|
|
3
|
+
//! regex-syntax parses syntax and regex-automata handles the fast searches.
|
|
4
|
+
//! Other cases request a libruby match after the Rust evaluation returns.
|
|
4
5
|
use super::*;
|
|
5
6
|
use regex_automata::{
|
|
6
7
|
Anchored, Input, MatchKind,
|
|
7
8
|
dfa::{Automaton, OverlappingState, StartKind, dense},
|
|
8
|
-
meta::
|
|
9
|
+
meta::Regex,
|
|
9
10
|
nfa::thompson,
|
|
10
11
|
};
|
|
11
12
|
use regex_syntax::ast::{Ast, RepetitionKind, RepetitionRange};
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
#[derive(Default)]
|
|
15
|
-
pub struct Caches(std::cell::RefCell<Vec<Option<Box<Cache>>>>);
|
|
16
|
-
impl Caches {
|
|
17
|
-
fn is_match(&self, index: usize, regex: &Regex, bytes: &[u8]) -> bool {
|
|
18
|
-
// Each runner is tied to one engine, whose regex indices are fixed at
|
|
19
|
-
// compilation. Allocate only the caches this validator actually uses.
|
|
20
|
-
let mut caches = self.0.borrow_mut();
|
|
21
|
-
if caches.len() <= index {
|
|
22
|
-
caches.resize_with(index + 1, || None);
|
|
23
|
-
}
|
|
24
|
-
let cache = caches[index].get_or_insert_with(|| Box::new(regex.create_cache()));
|
|
25
|
-
regex
|
|
26
|
-
.search_half_with(cache, &Input::new(bytes).earliest(true))
|
|
27
|
-
.is_some()
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
#[unsafe(no_mangle)]
|
|
31
|
-
pub extern "C" fn sn_caches_new() -> *mut Caches {
|
|
32
|
-
Box::into_raw(Box::new(Caches::default()))
|
|
33
|
-
}
|
|
34
|
-
/// # Safety
|
|
35
|
-
/// Caches must be owned, returned by sn_caches_new, and freed exactly once.
|
|
36
|
-
#[unsafe(no_mangle)]
|
|
37
|
-
pub unsafe extern "C" fn sn_caches_free(caches: *mut Caches) {
|
|
38
|
-
drop(unsafe { Box::from_raw(caches) });
|
|
39
|
-
}
|
|
40
|
-
/// # Safety
|
|
41
|
-
/// Caches must be live; call under the GVL or while all Ractors are stopped.
|
|
42
|
-
#[unsafe(no_mangle)]
|
|
43
|
-
pub unsafe extern "C" fn sn_caches_size(caches: *const Caches) -> usize {
|
|
44
|
-
// GC may inspect a stopped runner while its search has borrowed a cache.
|
|
45
|
-
let Ok(caches) = (unsafe { &*caches }).0.try_borrow() else {
|
|
46
|
-
return std::mem::size_of::<Caches>();
|
|
47
|
-
};
|
|
48
|
-
std::mem::size_of::<Caches>()
|
|
49
|
-
+ caches.capacity() * std::mem::size_of::<Option<Box<Cache>>>()
|
|
50
|
-
+ caches
|
|
51
|
-
.iter()
|
|
52
|
-
.flatten()
|
|
53
|
-
.map(|cache| std::mem::size_of::<Cache>() + cache.memory_usage())
|
|
54
|
-
.sum::<usize>()
|
|
55
|
-
}
|
|
14
|
+
use super::runtime::Caches;
|
|
56
15
|
|
|
57
16
|
#[derive(Debug)]
|
|
58
|
-
pub(super)
|
|
17
|
+
pub(super) struct Pattern {
|
|
18
|
+
source: Value,
|
|
19
|
+
matcher: Matcher,
|
|
20
|
+
}
|
|
21
|
+
#[derive(Debug)]
|
|
22
|
+
enum Matcher {
|
|
59
23
|
Unsupported,
|
|
60
24
|
Dfa(Box<dense::DFA<Vec<u32>>>),
|
|
61
25
|
Compiled { regex: Regex, index: usize },
|
|
@@ -73,7 +37,7 @@ impl PatternGroup {
|
|
|
73
37
|
if !(2..=64).contains(&patterns.len())
|
|
74
38
|
|| !patterns
|
|
75
39
|
.iter()
|
|
76
|
-
.all(|(pattern, _)| matches!(pattern,
|
|
40
|
+
.all(|(pattern, _)| matches!(pattern.matcher, Matcher::Dfa(_)))
|
|
77
41
|
{
|
|
78
42
|
return None;
|
|
79
43
|
}
|
|
@@ -138,10 +102,6 @@ impl Pattern {
|
|
|
138
102
|
// Select the crate matcher once for a homogeneous array. The borrowed
|
|
139
103
|
// Ruby slices are used synchronously; classification and encoding are
|
|
140
104
|
// still checked on every element, including non-string instances.
|
|
141
|
-
pub(super) fn eval_items(&self, caches: &Caches, required: i32, values: &[Value]) -> Verdict {
|
|
142
|
-
self.visit_invalid_items(caches, required, values, |_, _| Invalid)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
105
|
pub(super) fn visit_invalid_items(
|
|
146
106
|
&self,
|
|
147
107
|
caches: &Caches,
|
|
@@ -149,30 +109,97 @@ impl Pattern {
|
|
|
149
109
|
values: &[Value],
|
|
150
110
|
invalid: impl FnMut(usize, Value) -> Verdict,
|
|
151
111
|
) -> Verdict {
|
|
152
|
-
match self {
|
|
153
|
-
|
|
154
|
-
|
|
112
|
+
match &self.matcher {
|
|
113
|
+
Matcher::Dfa(dfa) => scan_items(
|
|
114
|
+
required,
|
|
115
|
+
values,
|
|
116
|
+
invalid,
|
|
117
|
+
|bytes| match dfa
|
|
118
|
+
.try_search_fwd(&Input::new(bytes).anchored(Anchored::Yes).earliest(true))
|
|
155
119
|
{
|
|
156
120
|
Ok(matched) => verdict(matched.is_some()),
|
|
157
121
|
Err(_) => Unsupported,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
122
|
+
},
|
|
123
|
+
|value| caches.ruby_match(self.source, value, true),
|
|
124
|
+
),
|
|
125
|
+
Matcher::Compiled { regex, index } => scan_items(
|
|
126
|
+
required,
|
|
127
|
+
values,
|
|
128
|
+
invalid,
|
|
129
|
+
|bytes| verdict(caches.is_match(*index, regex, bytes)),
|
|
130
|
+
|value| caches.ruby_match(self.source, value, true),
|
|
131
|
+
),
|
|
132
|
+
Matcher::Unsupported => scan_items(
|
|
133
|
+
required,
|
|
134
|
+
values,
|
|
135
|
+
invalid,
|
|
136
|
+
|_| Unsupported,
|
|
137
|
+
|value| caches.ruby_match(self.source, value, true),
|
|
138
|
+
),
|
|
164
139
|
}
|
|
165
140
|
}
|
|
166
141
|
|
|
167
142
|
pub(super) fn memory_usage(&self) -> usize {
|
|
168
|
-
match self {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
143
|
+
match &self.matcher {
|
|
144
|
+
Matcher::Dfa(dfa) => std::mem::size_of_val(&**dfa) + dfa.memory_usage(),
|
|
145
|
+
Matcher::Compiled { regex, .. } => regex.memory_usage(),
|
|
146
|
+
Matcher::Unsupported => 0,
|
|
172
147
|
}
|
|
173
148
|
}
|
|
174
149
|
|
|
175
150
|
pub(super) fn compile(value: Value, cache_count: &mut usize) -> Self {
|
|
151
|
+
Self {
|
|
152
|
+
source: value,
|
|
153
|
+
matcher: Matcher::compile(value, cache_count),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub(super) fn eval(&self, caches: &Caches, value: Value) -> Verdict {
|
|
158
|
+
self.eval_value(caches, value, false)
|
|
159
|
+
}
|
|
160
|
+
pub(super) fn assertion(&self, caches: &Caches, value: Value) -> Verdict {
|
|
161
|
+
self.eval_value(caches, value, true)
|
|
162
|
+
}
|
|
163
|
+
fn eval_value(&self, caches: &Caches, value: Value, assertion: bool) -> Verdict {
|
|
164
|
+
if matches!(self.matcher, Matcher::Unsupported) {
|
|
165
|
+
return caches.ruby_match(self.source, value, assertion);
|
|
166
|
+
}
|
|
167
|
+
let mut data = std::ptr::null();
|
|
168
|
+
let mut len = 0;
|
|
169
|
+
let encoding = unsafe { sn_pattern_view(value, &mut data, &mut len) };
|
|
170
|
+
if encoding == ENCODING_OTHER {
|
|
171
|
+
return caches.ruby_match(self.source, value, assertion);
|
|
172
|
+
}
|
|
173
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
174
|
+
let result = self.matcher.eval_bytes(caches, bytes, encoding);
|
|
175
|
+
if result == Unsupported {
|
|
176
|
+
caches.ruby_match(self.source, value, assertion)
|
|
177
|
+
} else {
|
|
178
|
+
result
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
pub(super) fn eval_bytes(
|
|
183
|
+
&self,
|
|
184
|
+
caches: &Caches,
|
|
185
|
+
value: Value,
|
|
186
|
+
bytes: &[u8],
|
|
187
|
+
encoding: i32,
|
|
188
|
+
) -> Verdict {
|
|
189
|
+
let result = self.matcher.eval_bytes(caches, bytes, encoding);
|
|
190
|
+
if result == Unsupported {
|
|
191
|
+
caches.ruby_match(self.source, value, true)
|
|
192
|
+
} else {
|
|
193
|
+
result
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
pub(super) fn invalid(&self, caches: &Caches, value: Value) -> bool {
|
|
197
|
+
caches.invalid_regexp(self.source, value)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl Matcher {
|
|
202
|
+
fn compile(value: Value, cache_count: &mut usize) -> Self {
|
|
176
203
|
if unsafe { sn_kind(value) } != TYPE_STRING {
|
|
177
204
|
return Self::Unsupported;
|
|
178
205
|
}
|
|
@@ -231,21 +258,7 @@ impl Pattern {
|
|
|
231
258
|
.unwrap_or(Self::Unsupported)
|
|
232
259
|
}
|
|
233
260
|
|
|
234
|
-
|
|
235
|
-
if matches!(self, Self::Unsupported) {
|
|
236
|
-
return Unsupported;
|
|
237
|
-
}
|
|
238
|
-
let mut data = std::ptr::null();
|
|
239
|
-
let mut len = 0;
|
|
240
|
-
let encoding = unsafe { sn_pattern_view(value, &mut data, &mut len) };
|
|
241
|
-
if encoding == ENCODING_OTHER {
|
|
242
|
-
return Unsupported;
|
|
243
|
-
}
|
|
244
|
-
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
|
|
245
|
-
self.eval_bytes(caches, bytes, encoding)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
pub(super) fn eval_bytes(&self, caches: &Caches, bytes: &[u8], encoding: i32) -> Verdict {
|
|
261
|
+
fn eval_bytes(&self, caches: &Caches, bytes: &[u8], encoding: i32) -> Verdict {
|
|
249
262
|
if encoding == ENCODING_OTHER || matches!(self, Self::Unsupported) {
|
|
250
263
|
return Unsupported;
|
|
251
264
|
}
|
|
@@ -285,13 +298,18 @@ fn scan_items(
|
|
|
285
298
|
values: &[Value],
|
|
286
299
|
mut invalid: impl FnMut(usize, Value) -> Verdict,
|
|
287
300
|
mut matches: impl FnMut(&[u8]) -> Verdict,
|
|
301
|
+
mut fallback: impl FnMut(Value) -> Verdict,
|
|
288
302
|
) -> Verdict {
|
|
289
303
|
for (index, &value) in values.iter().enumerate() {
|
|
290
304
|
let mut data = std::ptr::null();
|
|
291
305
|
let mut len = 0;
|
|
292
306
|
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
293
307
|
if encoding == -TYPE_UNSUPPORTED {
|
|
294
|
-
|
|
308
|
+
let result = invalid(index, value);
|
|
309
|
+
if result != Valid {
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
295
313
|
}
|
|
296
314
|
if encoding < 0 {
|
|
297
315
|
if required != 0 {
|
|
@@ -302,14 +320,21 @@ fn scan_items(
|
|
|
302
320
|
}
|
|
303
321
|
continue;
|
|
304
322
|
}
|
|
305
|
-
if encoding == ENCODING_OTHER {
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
323
|
+
let result = if encoding == ENCODING_OTHER {
|
|
324
|
+
Unsupported
|
|
325
|
+
} else {
|
|
326
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
327
|
+
if valid_encoding(bytes, encoding) {
|
|
328
|
+
matches(bytes)
|
|
329
|
+
} else {
|
|
330
|
+
Unsupported
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
let result = if result == Unsupported {
|
|
334
|
+
fallback(value)
|
|
335
|
+
} else {
|
|
336
|
+
result
|
|
337
|
+
};
|
|
313
338
|
match result {
|
|
314
339
|
Unsupported => return Unsupported,
|
|
315
340
|
Invalid => {
|
|
@@ -441,17 +466,4 @@ mod tests {
|
|
|
441
466
|
assert!(valid_encoding(&original, ENCODING_UTF8));
|
|
442
467
|
assert!(valid_encoding(b"already classified", ENCODING_SEVEN_BIT));
|
|
443
468
|
}
|
|
444
|
-
|
|
445
|
-
#[test]
|
|
446
|
-
fn reports_memory_when_a_search_cache_is_borrowed() {
|
|
447
|
-
let caches = Caches::default();
|
|
448
|
-
let regex = Regex::new("value[0-9]+").unwrap();
|
|
449
|
-
assert!(caches.is_match(0, ®ex, b"value1"));
|
|
450
|
-
assert!(unsafe { sn_caches_size(&caches) } > std::mem::size_of::<Caches>());
|
|
451
|
-
let _search = caches.0.borrow_mut();
|
|
452
|
-
assert_eq!(
|
|
453
|
-
unsafe { sn_caches_size(&caches) },
|
|
454
|
-
std::mem::size_of::<Caches>()
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
469
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//! Native UTF-8 pointer text with deferred names that need Ruby string semantics.
|
|
2
|
+
use super::*;
|
|
3
|
+
|
|
4
|
+
#[derive(Clone, Default, Eq, Hash, PartialEq)]
|
|
5
|
+
pub(super) struct Path {
|
|
6
|
+
pub(super) text: String,
|
|
7
|
+
pub(super) names: Vec<(usize, Value)>,
|
|
8
|
+
}
|
|
9
|
+
impl Path {
|
|
10
|
+
pub(super) fn checkpoint(&self) -> (usize, usize) {
|
|
11
|
+
(self.text.len(), self.names.len())
|
|
12
|
+
}
|
|
13
|
+
pub(super) fn truncate(&mut self, checkpoint: (usize, usize)) {
|
|
14
|
+
self.text.truncate(checkpoint.0);
|
|
15
|
+
self.names.truncate(checkpoint.1);
|
|
16
|
+
}
|
|
17
|
+
pub(super) fn push_str(&mut self, text: &str) {
|
|
18
|
+
self.text.push_str(text);
|
|
19
|
+
}
|
|
20
|
+
pub(super) fn index(&mut self, index: usize) {
|
|
21
|
+
use std::fmt::Write;
|
|
22
|
+
write!(self.text, "/{index}").unwrap();
|
|
23
|
+
}
|
|
24
|
+
pub(super) fn name(&mut self, value: Value) -> Result<(), Verdict> {
|
|
25
|
+
if unsafe { sn_kind(value) } != TYPE_STRING {
|
|
26
|
+
return Err(Unsupported);
|
|
27
|
+
}
|
|
28
|
+
self.text.push('/');
|
|
29
|
+
let mut data = std::ptr::null();
|
|
30
|
+
let mut len = 0;
|
|
31
|
+
let encoding = unsafe { sn_string_view(value, &mut data, &mut len) };
|
|
32
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
33
|
+
if matches!(encoding, ENCODING_UTF8 | ENCODING_ASCII)
|
|
34
|
+
&& (encoding != ENCODING_ASCII || bytes.is_ascii())
|
|
35
|
+
&& let Ok(name) = std::str::from_utf8(bytes)
|
|
36
|
+
{
|
|
37
|
+
for character in name.chars() {
|
|
38
|
+
match character {
|
|
39
|
+
'~' => self.text.push_str("~0"),
|
|
40
|
+
'/' => self.text.push_str("~1"),
|
|
41
|
+
_ => self.text.push(character),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
self.names.push((self.text.len(), value));
|
|
46
|
+
}
|
|
47
|
+
Ok(())
|
|
48
|
+
}
|
|
49
|
+
pub(super) fn render(
|
|
50
|
+
&self,
|
|
51
|
+
caches: &runtime::Caches,
|
|
52
|
+
keyword: Option<&str>,
|
|
53
|
+
) -> Result<Rendered, Verdict> {
|
|
54
|
+
if self.names.is_empty() {
|
|
55
|
+
let text = keyword.map_or_else(
|
|
56
|
+
|| self.text.clone(),
|
|
57
|
+
|keyword| format!("{}/{keyword}", self.text),
|
|
58
|
+
);
|
|
59
|
+
Ok(Rendered::Native(text))
|
|
60
|
+
} else if let Some(keyword) = keyword {
|
|
61
|
+
let mut path = self.clone();
|
|
62
|
+
path.push_str("/");
|
|
63
|
+
path.push_str(keyword);
|
|
64
|
+
caches.pointer(&path).map(Rendered::Ruby)
|
|
65
|
+
} else {
|
|
66
|
+
caches.pointer(self).map(Rendered::Ruby)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
70
|
+
self.text.capacity() + self.names.capacity() * std::mem::size_of::<(usize, Value)>()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
#[derive(Clone)]
|
|
74
|
+
pub(super) enum Rendered {
|
|
75
|
+
Native(String),
|
|
76
|
+
Ruby(Value),
|
|
77
|
+
}
|
|
78
|
+
impl Rendered {
|
|
79
|
+
pub(super) fn bytes(&self) -> &[u8] {
|
|
80
|
+
match self {
|
|
81
|
+
Self::Native(text) => text.as_bytes(),
|
|
82
|
+
Self::Ruby(_) => &[],
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
pub(super) fn value(&self) -> Value {
|
|
86
|
+
match self {
|
|
87
|
+
Self::Native(_) => unsafe { sn_nil() },
|
|
88
|
+
Self::Ruby(value) => *value,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
92
|
+
match self {
|
|
93
|
+
Self::Native(text) => text.capacity(),
|
|
94
|
+
Self::Ruby(_) => 0,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//! Opaque evaluation buffers for annotation merges that require Ruby semantics.
|
|
2
|
+
use super::*;
|
|
3
|
+
use crate::tracking::{RuntimeLocations, Tracked};
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
|
|
6
|
+
struct Part {
|
|
7
|
+
runtime: Option<RuntimeLocations>,
|
|
8
|
+
keys: Vec<Value>,
|
|
9
|
+
items: Vec<usize>,
|
|
10
|
+
}
|
|
11
|
+
impl Part {
|
|
12
|
+
fn new(engine: &Engine, tracked: &Tracked) -> Self {
|
|
13
|
+
Self {
|
|
14
|
+
runtime: tracked.runtime_locations(),
|
|
15
|
+
keys: tracked.native_properties(engine),
|
|
16
|
+
items: tracked.native_items(),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
fn mark(&self) {
|
|
20
|
+
if let Some(runtime) = self.runtime {
|
|
21
|
+
runtime.mark();
|
|
22
|
+
}
|
|
23
|
+
for &key in &self.keys {
|
|
24
|
+
unsafe {
|
|
25
|
+
sn_pin(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
fn memory_usage(&self) -> usize {
|
|
30
|
+
self.keys.capacity() * std::mem::size_of::<Value>()
|
|
31
|
+
+ self.items.capacity() * std::mem::size_of::<usize>()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
#[derive(Default)]
|
|
35
|
+
pub(super) struct Properties {
|
|
36
|
+
pending: RefCell<Option<(Value, [Part; 2])>>,
|
|
37
|
+
completed: RefCell<HashMap<Value, RuntimeLocations>>,
|
|
38
|
+
}
|
|
39
|
+
impl Properties {
|
|
40
|
+
pub(super) fn reset(&self) {
|
|
41
|
+
self.pending.borrow_mut().take();
|
|
42
|
+
}
|
|
43
|
+
pub(super) fn clear(&self) {
|
|
44
|
+
self.reset();
|
|
45
|
+
*self.completed.borrow_mut() = HashMap::new();
|
|
46
|
+
}
|
|
47
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
48
|
+
self.pending.try_borrow().map_or(0, |pending| {
|
|
49
|
+
pending.as_ref().map_or(0, |(_, parts)| {
|
|
50
|
+
parts.iter().map(Part::memory_usage).sum::<usize>()
|
|
51
|
+
})
|
|
52
|
+
}) + self.completed.try_borrow().map_or(0, |results| {
|
|
53
|
+
results.capacity() * std::mem::size_of::<(Value, RuntimeLocations)>()
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
pub(super) fn mark(&self) {
|
|
57
|
+
if let Ok(pending) = self.pending.try_borrow()
|
|
58
|
+
&& let Some((_, parts)) = &*pending
|
|
59
|
+
{
|
|
60
|
+
for part in parts {
|
|
61
|
+
part.mark();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if let Ok(completed) = self.completed.try_borrow() {
|
|
65
|
+
for runtime in completed.values() {
|
|
66
|
+
runtime.mark();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
impl Caches {
|
|
72
|
+
pub(crate) fn merge_properties(
|
|
73
|
+
&self,
|
|
74
|
+
engine: &Engine,
|
|
75
|
+
left: &Tracked,
|
|
76
|
+
right: &Tracked,
|
|
77
|
+
) -> Result<RuntimeLocations, Verdict> {
|
|
78
|
+
let ordinal = self.next_call()?;
|
|
79
|
+
if self.observe(ordinal, unsafe { sn_nil() }, RUNTIME_PROPERTY_MERGE) == Unsupported {
|
|
80
|
+
*self.properties.pending.borrow_mut() =
|
|
81
|
+
Some((ordinal, [Part::new(engine, left), Part::new(engine, right)]));
|
|
82
|
+
return Err(Unsupported);
|
|
83
|
+
}
|
|
84
|
+
self.properties
|
|
85
|
+
.completed
|
|
86
|
+
.borrow()
|
|
87
|
+
.get(&ordinal)
|
|
88
|
+
.copied()
|
|
89
|
+
.ok_or(Unsupported)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/// # Safety
|
|
93
|
+
/// Caches are idle with a pending merge. Side is 0/1 and outputs are writable.
|
|
94
|
+
#[unsafe(no_mangle)]
|
|
95
|
+
pub unsafe extern "C" fn sn_properties_part(
|
|
96
|
+
caches: *const Caches,
|
|
97
|
+
side: usize,
|
|
98
|
+
properties: *mut usize,
|
|
99
|
+
items: *mut usize,
|
|
100
|
+
) -> Value {
|
|
101
|
+
let pending = unsafe { &*caches }.properties.pending.borrow();
|
|
102
|
+
let part = &pending.as_ref().expect("property merge").1[side];
|
|
103
|
+
unsafe {
|
|
104
|
+
*properties = part.keys.len();
|
|
105
|
+
*items = part.items.len();
|
|
106
|
+
}
|
|
107
|
+
part.runtime
|
|
108
|
+
.map_or_else(|| unsafe { sn_nil() }, |runtime| runtime.buffer)
|
|
109
|
+
}
|
|
110
|
+
/// # Safety
|
|
111
|
+
/// Same pending merge requirements as sn_properties_part; index is in bounds.
|
|
112
|
+
#[unsafe(no_mangle)]
|
|
113
|
+
pub unsafe extern "C" fn sn_properties_key(
|
|
114
|
+
caches: *const Caches,
|
|
115
|
+
side: usize,
|
|
116
|
+
index: usize,
|
|
117
|
+
) -> Value {
|
|
118
|
+
unsafe { &*caches }
|
|
119
|
+
.properties
|
|
120
|
+
.pending
|
|
121
|
+
.borrow()
|
|
122
|
+
.as_ref()
|
|
123
|
+
.expect("property merge")
|
|
124
|
+
.1[side]
|
|
125
|
+
.keys[index]
|
|
126
|
+
}
|
|
127
|
+
/// # Safety
|
|
128
|
+
/// Same pending merge requirements as sn_properties_part; index is in bounds.
|
|
129
|
+
#[unsafe(no_mangle)]
|
|
130
|
+
pub unsafe extern "C" fn sn_properties_item(
|
|
131
|
+
caches: *const Caches,
|
|
132
|
+
side: usize,
|
|
133
|
+
index: usize,
|
|
134
|
+
) -> usize {
|
|
135
|
+
unsafe { &*caches }
|
|
136
|
+
.properties
|
|
137
|
+
.pending
|
|
138
|
+
.borrow()
|
|
139
|
+
.as_ref()
|
|
140
|
+
.expect("property merge")
|
|
141
|
+
.1[side]
|
|
142
|
+
.items[index]
|
|
143
|
+
}
|
|
144
|
+
/// # Safety
|
|
145
|
+
/// Caches are idle; locations is readable and all handles are rooted by C first.
|
|
146
|
+
#[unsafe(no_mangle)]
|
|
147
|
+
pub unsafe extern "C" fn sn_properties_result(
|
|
148
|
+
caches: *const Caches,
|
|
149
|
+
locations: *const RuntimeLocations,
|
|
150
|
+
) {
|
|
151
|
+
let properties = &unsafe { &*caches }.properties;
|
|
152
|
+
let ordinal = properties
|
|
153
|
+
.pending
|
|
154
|
+
.borrow()
|
|
155
|
+
.as_ref()
|
|
156
|
+
.expect("property merge")
|
|
157
|
+
.0;
|
|
158
|
+
properties
|
|
159
|
+
.completed
|
|
160
|
+
.borrow_mut()
|
|
161
|
+
.insert(ordinal, unsafe { *locations });
|
|
162
|
+
}
|