schemurai 3.0.0 → 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 +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -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 +74 -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 +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- 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 +147 -109
- 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 +25 -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
|
}
|
|
@@ -109,10 +73,7 @@ impl PatternGroup {
|
|
|
109
73
|
return None;
|
|
110
74
|
}
|
|
111
75
|
let bytes = unsafe { byte_view(data, len) };
|
|
112
|
-
if encoding
|
|
113
|
-
&& ((encoding == ENCODING_ASCII && !bytes.is_ascii())
|
|
114
|
-
|| std::str::from_utf8(bytes).is_err())
|
|
115
|
-
{
|
|
76
|
+
if !valid_encoding(bytes, encoding) {
|
|
116
77
|
return None;
|
|
117
78
|
}
|
|
118
79
|
let input = Input::new(bytes).anchored(Anchored::Yes);
|
|
@@ -141,10 +102,6 @@ impl Pattern {
|
|
|
141
102
|
// Select the crate matcher once for a homogeneous array. The borrowed
|
|
142
103
|
// Ruby slices are used synchronously; classification and encoding are
|
|
143
104
|
// still checked on every element, including non-string instances.
|
|
144
|
-
pub(super) fn eval_items(&self, caches: &Caches, required: i32, values: &[Value]) -> Verdict {
|
|
145
|
-
self.visit_invalid_items(caches, required, values, |_, _| Invalid)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
105
|
pub(super) fn visit_invalid_items(
|
|
149
106
|
&self,
|
|
150
107
|
caches: &Caches,
|
|
@@ -152,30 +109,97 @@ impl Pattern {
|
|
|
152
109
|
values: &[Value],
|
|
153
110
|
invalid: impl FnMut(usize, Value) -> Verdict,
|
|
154
111
|
) -> Verdict {
|
|
155
|
-
match self {
|
|
156
|
-
|
|
157
|
-
|
|
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))
|
|
158
119
|
{
|
|
159
120
|
Ok(matched) => verdict(matched.is_some()),
|
|
160
121
|
Err(_) => Unsupported,
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
),
|
|
167
139
|
}
|
|
168
140
|
}
|
|
169
141
|
|
|
170
142
|
pub(super) fn memory_usage(&self) -> usize {
|
|
171
|
-
match self {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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,
|
|
175
147
|
}
|
|
176
148
|
}
|
|
177
149
|
|
|
178
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 {
|
|
179
203
|
if unsafe { sn_kind(value) } != TYPE_STRING {
|
|
180
204
|
return Self::Unsupported;
|
|
181
205
|
}
|
|
@@ -234,28 +258,11 @@ impl Pattern {
|
|
|
234
258
|
.unwrap_or(Self::Unsupported)
|
|
235
259
|
}
|
|
236
260
|
|
|
237
|
-
|
|
238
|
-
if matches!(self, Self::Unsupported) {
|
|
239
|
-
return Unsupported;
|
|
240
|
-
}
|
|
241
|
-
let mut data = std::ptr::null();
|
|
242
|
-
let mut len = 0;
|
|
243
|
-
let encoding = unsafe { sn_pattern_view(value, &mut data, &mut len) };
|
|
244
|
-
if encoding == ENCODING_OTHER {
|
|
245
|
-
return Unsupported;
|
|
246
|
-
}
|
|
247
|
-
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
|
|
248
|
-
self.eval_bytes(caches, bytes, encoding)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
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 {
|
|
252
262
|
if encoding == ENCODING_OTHER || matches!(self, Self::Unsupported) {
|
|
253
263
|
return Unsupported;
|
|
254
264
|
}
|
|
255
|
-
if encoding
|
|
256
|
-
&& ((encoding == ENCODING_ASCII && !bytes.is_ascii())
|
|
257
|
-
|| std::str::from_utf8(bytes).is_err())
|
|
258
|
-
{
|
|
265
|
+
if !valid_encoding(bytes, encoding) {
|
|
259
266
|
return Unsupported;
|
|
260
267
|
}
|
|
261
268
|
let matched = match self {
|
|
@@ -272,18 +279,37 @@ impl Pattern {
|
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
|
|
282
|
+
// Keep short checks in std and skip scanning when Ruby already proved ASCII.
|
|
283
|
+
// The compatibility SIMD API retains early rejection of malformed UTF-8.
|
|
284
|
+
fn valid_encoding(bytes: &[u8], encoding: i32) -> bool {
|
|
285
|
+
if encoding == ENCODING_SEVEN_BIT {
|
|
286
|
+
true
|
|
287
|
+
} else if encoding == ENCODING_ASCII {
|
|
288
|
+
bytes.is_ascii()
|
|
289
|
+
} else if bytes.len() < 64 {
|
|
290
|
+
std::str::from_utf8(bytes).is_ok()
|
|
291
|
+
} else {
|
|
292
|
+
simdutf8::compat::from_utf8(bytes).is_ok()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
275
296
|
fn scan_items(
|
|
276
297
|
required: i32,
|
|
277
298
|
values: &[Value],
|
|
278
299
|
mut invalid: impl FnMut(usize, Value) -> Verdict,
|
|
279
300
|
mut matches: impl FnMut(&[u8]) -> Verdict,
|
|
301
|
+
mut fallback: impl FnMut(Value) -> Verdict,
|
|
280
302
|
) -> Verdict {
|
|
281
303
|
for (index, &value) in values.iter().enumerate() {
|
|
282
304
|
let mut data = std::ptr::null();
|
|
283
305
|
let mut len = 0;
|
|
284
306
|
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
285
307
|
if encoding == -TYPE_UNSUPPORTED {
|
|
286
|
-
|
|
308
|
+
let result = invalid(index, value);
|
|
309
|
+
if result != Valid {
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
287
313
|
}
|
|
288
314
|
if encoding < 0 {
|
|
289
315
|
if required != 0 {
|
|
@@ -294,17 +320,21 @@ fn scan_items(
|
|
|
294
320
|
}
|
|
295
321
|
continue;
|
|
296
322
|
}
|
|
297
|
-
if encoding == ENCODING_OTHER {
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
let result =
|
|
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
|
+
};
|
|
308
338
|
match result {
|
|
309
339
|
Unsupported => return Unsupported,
|
|
310
340
|
Invalid => {
|
|
@@ -417,15 +447,23 @@ mod tests {
|
|
|
417
447
|
use super::*;
|
|
418
448
|
|
|
419
449
|
#[test]
|
|
420
|
-
fn
|
|
421
|
-
let
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
450
|
+
fn encoding_validation_matches_std_across_simd_blocks_and_tails() {
|
|
451
|
+
let original = "日𐀀é".repeat(32).into_bytes();
|
|
452
|
+
for length in [63, 64, 65, 127, 128, 129, original.len()] {
|
|
453
|
+
for at in 0..length {
|
|
454
|
+
for byte in 0..=255 {
|
|
455
|
+
let mut input = original[..length].to_vec();
|
|
456
|
+
input[at] = byte;
|
|
457
|
+
assert_eq!(
|
|
458
|
+
valid_encoding(&input, ENCODING_UTF8),
|
|
459
|
+
std::str::from_utf8(&input).is_ok(),
|
|
460
|
+
"length {length}, position {at}, byte {byte}"
|
|
461
|
+
);
|
|
462
|
+
assert_eq!(valid_encoding(&input, ENCODING_ASCII), input.is_ascii());
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
assert!(valid_encoding(&original, ENCODING_UTF8));
|
|
467
|
+
assert!(valid_encoding(b"already classified", ENCODING_SEVEN_BIT));
|
|
430
468
|
}
|
|
431
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
|
+
}
|