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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
//! Owned diagnostic seeds and results. No native frame is retained across Ruby.
|
|
2
|
+
use super::*;
|
|
3
|
+
use crate::diagnostics::Report;
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
|
|
6
|
+
#[derive(Default)]
|
|
7
|
+
pub(super) struct Reports {
|
|
8
|
+
initial: RefCell<Option<Report>>,
|
|
9
|
+
callback: RefCell<Option<Report>>,
|
|
10
|
+
pending: RefCell<Option<(Value, Report)>>,
|
|
11
|
+
completed: RefCell<HashMap<Value, Report>>,
|
|
12
|
+
live: RefCell<Option<Report>>,
|
|
13
|
+
boundary: RefCell<Option<Report>>,
|
|
14
|
+
runtime_key: std::cell::Cell<Option<scopes::Key>>,
|
|
15
|
+
effects: RefCell<HashMap<scopes::Key, Report>>,
|
|
16
|
+
replay: RefCell<Vec<Report>>,
|
|
17
|
+
}
|
|
18
|
+
impl Reports {
|
|
19
|
+
pub(super) fn clear(&self) {
|
|
20
|
+
self.boundary.borrow_mut().take();
|
|
21
|
+
self.live.borrow_mut().take();
|
|
22
|
+
self.runtime_key.set(None);
|
|
23
|
+
self.effects.borrow_mut().clear();
|
|
24
|
+
self.replay.borrow_mut().clear();
|
|
25
|
+
self.initial.borrow_mut().take();
|
|
26
|
+
self.callback.borrow_mut().take();
|
|
27
|
+
self.pending.borrow_mut().take();
|
|
28
|
+
*self.completed.borrow_mut() = HashMap::new();
|
|
29
|
+
}
|
|
30
|
+
pub(super) fn capture(&self, report: &Report) {
|
|
31
|
+
*self.boundary.borrow_mut() = Some(report.clone());
|
|
32
|
+
if self.live.borrow().is_none() {
|
|
33
|
+
*self.live.borrow_mut() = Some(report.clone());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
pub(super) fn request_runtime(&self, key: scopes::Key) {
|
|
37
|
+
self.runtime_key.set(Some(key));
|
|
38
|
+
}
|
|
39
|
+
pub(super) fn apply_runtime(&self, key: scopes::Key) {
|
|
40
|
+
if let Some(effect) = self.effects.borrow().get(&key) {
|
|
41
|
+
self.replay.borrow_mut().push(effect.clone());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
pub(super) fn apply(&self, report: &mut Report) {
|
|
45
|
+
for effect in self.replay.borrow_mut().drain(..) {
|
|
46
|
+
report.apply_effect(effect);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
pub(super) fn reset(&self) {
|
|
50
|
+
*self.callback.borrow_mut() = self.initial.borrow().clone();
|
|
51
|
+
self.boundary.borrow_mut().take();
|
|
52
|
+
self.live.borrow_mut().take();
|
|
53
|
+
self.runtime_key.set(None);
|
|
54
|
+
self.replay.borrow_mut().clear();
|
|
55
|
+
self.pending.borrow_mut().take();
|
|
56
|
+
}
|
|
57
|
+
pub(super) fn initial(&self) -> Option<Report> {
|
|
58
|
+
self.initial.borrow().clone()
|
|
59
|
+
}
|
|
60
|
+
pub(super) fn request(&self, key: Value, report: Report) {
|
|
61
|
+
*self.pending.borrow_mut() = Some((key, report));
|
|
62
|
+
}
|
|
63
|
+
pub(super) fn result(&self, key: Value) -> Option<Report> {
|
|
64
|
+
self.completed.borrow().get(&key).cloned()
|
|
65
|
+
}
|
|
66
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
67
|
+
[&self.live, &self.boundary, &self.initial, &self.callback]
|
|
68
|
+
.iter()
|
|
69
|
+
.map(|cell| {
|
|
70
|
+
cell.try_borrow()
|
|
71
|
+
.map_or(0, |r| r.as_ref().map_or(0, Report::memory_usage))
|
|
72
|
+
})
|
|
73
|
+
.sum::<usize>()
|
|
74
|
+
+ self.effects.try_borrow().map_or(0, |effects| {
|
|
75
|
+
effects.values().map(Report::memory_usage).sum::<usize>()
|
|
76
|
+
})
|
|
77
|
+
+ self.replay.try_borrow().map_or(0, |effects| {
|
|
78
|
+
effects.iter().map(Report::memory_usage).sum::<usize>()
|
|
79
|
+
})
|
|
80
|
+
+ self
|
|
81
|
+
.pending
|
|
82
|
+
.try_borrow()
|
|
83
|
+
.map_or(0, |r| r.as_ref().map_or(0, |(_, r)| r.memory_usage()))
|
|
84
|
+
+ self.completed.try_borrow().map_or(0, |r| {
|
|
85
|
+
r.capacity() * std::mem::size_of::<(Value, Report)>()
|
|
86
|
+
+ r.values().map(Report::memory_usage).sum::<usize>()
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
pub(super) fn mark(&self) {
|
|
90
|
+
// C additionally roots every completed report and inherited seed.
|
|
91
|
+
// Pending snapshots use input/schema handles and runtime observations
|
|
92
|
+
// already owned by the runner while a stopped pass borrows this state.
|
|
93
|
+
if let Ok(boundary) = self.boundary.try_borrow()
|
|
94
|
+
&& let Some(report) = &*boundary
|
|
95
|
+
{
|
|
96
|
+
report.mark();
|
|
97
|
+
}
|
|
98
|
+
if let Ok(callback) = self.callback.try_borrow()
|
|
99
|
+
&& let Some(report) = &*callback
|
|
100
|
+
{
|
|
101
|
+
report.mark();
|
|
102
|
+
}
|
|
103
|
+
if let Ok(live) = self.live.try_borrow()
|
|
104
|
+
&& let Some(report) = &*live
|
|
105
|
+
{
|
|
106
|
+
report.mark();
|
|
107
|
+
}
|
|
108
|
+
if let Ok(effects) = self.effects.try_borrow() {
|
|
109
|
+
for report in effects.values() {
|
|
110
|
+
report.mark();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if let Ok(replay) = self.replay.try_borrow() {
|
|
114
|
+
for report in replay.iter() {
|
|
115
|
+
report.mark();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if let Ok(initial) = self.initial.try_borrow()
|
|
119
|
+
&& let Some(report) = &*initial
|
|
120
|
+
{
|
|
121
|
+
report.mark();
|
|
122
|
+
}
|
|
123
|
+
if let Ok(pending) = self.pending.try_borrow()
|
|
124
|
+
&& let Some((_, report)) = &*pending
|
|
125
|
+
{
|
|
126
|
+
report.mark();
|
|
127
|
+
}
|
|
128
|
+
if let Ok(completed) = self.completed.try_borrow() {
|
|
129
|
+
for report in completed.values() {
|
|
130
|
+
report.mark();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/// # Safety
|
|
136
|
+
/// Caches must be idle and own a pending native report request. The returned
|
|
137
|
+
/// report is owned by C TypedData and freed exactly once with sn_report_free.
|
|
138
|
+
#[unsafe(no_mangle)]
|
|
139
|
+
pub unsafe extern "C" fn sn_report_seed(caches: *const Caches) -> *mut Report {
|
|
140
|
+
Box::into_raw(Box::new(
|
|
141
|
+
unsafe { &*caches }
|
|
142
|
+
.reports
|
|
143
|
+
.pending
|
|
144
|
+
.borrow()
|
|
145
|
+
.as_ref()
|
|
146
|
+
.expect("pending native report")
|
|
147
|
+
.1
|
|
148
|
+
.clone(),
|
|
149
|
+
))
|
|
150
|
+
}
|
|
151
|
+
/// # Safety
|
|
152
|
+
/// Caches must be idle and report live and C-rooted through the invocation.
|
|
153
|
+
#[unsafe(no_mangle)]
|
|
154
|
+
pub unsafe extern "C" fn sn_report_inherit(caches: *const Caches, report: *const Report) {
|
|
155
|
+
let reports = &unsafe { &*caches }.reports;
|
|
156
|
+
*reports.initial.borrow_mut() = Some(unsafe { &*report }.clone());
|
|
157
|
+
*reports.callback.borrow_mut() = Some(unsafe { &*report }.clone());
|
|
158
|
+
}
|
|
159
|
+
/// # Safety
|
|
160
|
+
/// Caches must be idle with a pending report request; report must be live,
|
|
161
|
+
/// complete, and retained by the runner's completed-report roots before calling.
|
|
162
|
+
#[unsafe(no_mangle)]
|
|
163
|
+
pub unsafe extern "C" fn sn_report_accept(caches: *const Caches, report: *const Report) {
|
|
164
|
+
let reports = &unsafe { &*caches }.reports;
|
|
165
|
+
let key = reports
|
|
166
|
+
.pending
|
|
167
|
+
.borrow()
|
|
168
|
+
.as_ref()
|
|
169
|
+
.expect("pending native report")
|
|
170
|
+
.0;
|
|
171
|
+
reports
|
|
172
|
+
.completed
|
|
173
|
+
.borrow_mut()
|
|
174
|
+
.insert(key, unsafe { &*report }.clone());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// # Safety
|
|
178
|
+
/// Caches must be idle. A non-null result is an owned report rooted/freed by C.
|
|
179
|
+
#[unsafe(no_mangle)]
|
|
180
|
+
pub unsafe extern "C" fn sn_report_live(caches: *const Caches) -> *mut Report {
|
|
181
|
+
unsafe { &*caches }
|
|
182
|
+
.reports
|
|
183
|
+
.live
|
|
184
|
+
.borrow()
|
|
185
|
+
.as_ref()
|
|
186
|
+
.map_or(std::ptr::null_mut(), |report| {
|
|
187
|
+
Box::into_raw(Box::new(report.clone()))
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
/// # Safety
|
|
191
|
+
/// Caches and completed report must be idle, live and C-rooted.
|
|
192
|
+
#[unsafe(no_mangle)]
|
|
193
|
+
pub unsafe extern "C" fn sn_report_effect(caches: *const Caches, completed: *const Report) {
|
|
194
|
+
let reports = &unsafe { &*caches }.reports;
|
|
195
|
+
if let Some(key) = reports.runtime_key.get()
|
|
196
|
+
&& let Some(before) = &*reports.live.borrow()
|
|
197
|
+
&& let Some(effect) = unsafe { &*completed }.effect_since(before)
|
|
198
|
+
{
|
|
199
|
+
reports.effects.borrow_mut().insert(key, effect);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
impl Caches {
|
|
204
|
+
pub(crate) fn callback_tracked(&self, node: usize, value: Value) -> Option<Verdict> {
|
|
205
|
+
let mut report = self.reports.callback.borrow().clone()?;
|
|
206
|
+
self.reports.apply(&mut report);
|
|
207
|
+
let result = self
|
|
208
|
+
.report_child(node, value, &mut report, true)
|
|
209
|
+
.map_or_else(|verdict| verdict, |evaluation| evaluation.status);
|
|
210
|
+
self.reports.apply(&mut report);
|
|
211
|
+
if result == Unsupported {
|
|
212
|
+
self.reports.capture(&report);
|
|
213
|
+
}
|
|
214
|
+
*self.reports.callback.borrow_mut() = Some(report);
|
|
215
|
+
Some(result)
|
|
216
|
+
}
|
|
217
|
+
pub(crate) fn finish_callback_report(&self, verdict: Verdict) {
|
|
218
|
+
if let Some(report) = &mut *self.reports.callback.borrow_mut() {
|
|
219
|
+
self.reports.apply(report);
|
|
220
|
+
if verdict == Unsupported {
|
|
221
|
+
self.reports.capture(report);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/// # Safety
|
|
227
|
+
/// Caches must be idle after a successful seeded validity call. C owns the result.
|
|
228
|
+
#[unsafe(no_mangle)]
|
|
229
|
+
pub unsafe extern "C" fn sn_callback_report_result(
|
|
230
|
+
caches: *const Caches,
|
|
231
|
+
valid: i32,
|
|
232
|
+
) -> *mut Report {
|
|
233
|
+
let mut report = unsafe { &*caches }
|
|
234
|
+
.reports
|
|
235
|
+
.callback
|
|
236
|
+
.borrow()
|
|
237
|
+
.clone()
|
|
238
|
+
.expect("callback report");
|
|
239
|
+
report.complete_callback(valid != 0);
|
|
240
|
+
Box::into_raw(Box::new(report))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// # Safety
|
|
244
|
+
/// Caches are idle while C unwinds an interrupted runner. C owns a non-null result.
|
|
245
|
+
#[unsafe(no_mangle)]
|
|
246
|
+
pub unsafe extern "C" fn sn_report_interrupted(caches: *const Caches) -> *mut Report {
|
|
247
|
+
let reports = &unsafe { &*caches }.reports;
|
|
248
|
+
let mut report = reports
|
|
249
|
+
.boundary
|
|
250
|
+
.borrow()
|
|
251
|
+
.clone()
|
|
252
|
+
.or_else(|| reports.callback.borrow().clone());
|
|
253
|
+
if let Some(report) = &mut report {
|
|
254
|
+
if let Some(key) = reports.runtime_key.get()
|
|
255
|
+
&& let Some(live) = &*reports.live.borrow()
|
|
256
|
+
&& report.same_collection_policy(live)
|
|
257
|
+
&& let Some(effect) = reports.effects.borrow().get(&key)
|
|
258
|
+
{
|
|
259
|
+
report.apply_effect(effect.clone());
|
|
260
|
+
}
|
|
261
|
+
report.complete_callback(false);
|
|
262
|
+
}
|
|
263
|
+
report.map_or(std::ptr::null_mut(), |report| {
|
|
264
|
+
Box::into_raw(Box::new(report))
|
|
265
|
+
})
|
|
266
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
//! Invocation scope snapshots and scope effects of completed Ruby requests.
|
|
2
|
+
use super::*;
|
|
3
|
+
use std::cell::{Cell, RefCell};
|
|
4
|
+
|
|
5
|
+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
|
6
|
+
pub(super) enum Key {
|
|
7
|
+
Value(Value, Value, i32),
|
|
8
|
+
Message(Message),
|
|
9
|
+
Pointer(usize),
|
|
10
|
+
Rescue(StringError),
|
|
11
|
+
}
|
|
12
|
+
#[derive(Default)]
|
|
13
|
+
pub(super) struct Scopes {
|
|
14
|
+
enabled: Cell<bool>,
|
|
15
|
+
initial_references: RefCell<Vec<(Value, Value)>>,
|
|
16
|
+
vm_references: RefCell<Vec<(Value, Value)>>,
|
|
17
|
+
pending_references: RefCell<Vec<(Value, Value)>>,
|
|
18
|
+
reference: Cell<*const Reference>,
|
|
19
|
+
initial: RefCell<Vec<Value>>,
|
|
20
|
+
stack: RefCell<Vec<Value>>,
|
|
21
|
+
pending: RefCell<Vec<Value>>,
|
|
22
|
+
key: Cell<Option<Key>>,
|
|
23
|
+
effects: RefCell<HashMap<Key, Vec<Value>>>,
|
|
24
|
+
entered: Cell<usize>,
|
|
25
|
+
unwind: Cell<usize>,
|
|
26
|
+
}
|
|
27
|
+
pub(crate) struct Guard<'a> {
|
|
28
|
+
scopes: &'a Scopes,
|
|
29
|
+
entered: bool,
|
|
30
|
+
}
|
|
31
|
+
// Stack records are read only while their guards are live. No pointer survives
|
|
32
|
+
// native unwinding: requests copy the pairs before returning to C/Ruby.
|
|
33
|
+
pub(crate) struct Reference {
|
|
34
|
+
pair: (Value, Value),
|
|
35
|
+
parent: *const Reference,
|
|
36
|
+
}
|
|
37
|
+
pub(crate) struct ReferenceGuard<'a> {
|
|
38
|
+
scopes: &'a Scopes,
|
|
39
|
+
frame: &'a Reference,
|
|
40
|
+
}
|
|
41
|
+
impl Drop for ReferenceGuard<'_> {
|
|
42
|
+
fn drop(&mut self) {
|
|
43
|
+
self.scopes.reference.set(self.frame.parent);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
impl Drop for Guard<'_> {
|
|
47
|
+
fn drop(&mut self) {
|
|
48
|
+
if self.entered {
|
|
49
|
+
self.scopes.stack.borrow_mut().pop();
|
|
50
|
+
self.scopes.entered.set(self.scopes.entered.get() - 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
impl Scopes {
|
|
55
|
+
pub(super) fn enabled(&self) -> bool {
|
|
56
|
+
self.enabled.get()
|
|
57
|
+
}
|
|
58
|
+
pub(super) fn clear(&self) {
|
|
59
|
+
self.enabled.set(false);
|
|
60
|
+
self.initial_references.borrow_mut().clear();
|
|
61
|
+
self.vm_references.borrow_mut().clear();
|
|
62
|
+
self.pending_references.borrow_mut().clear();
|
|
63
|
+
self.reference.set(std::ptr::null());
|
|
64
|
+
self.initial.borrow_mut().clear();
|
|
65
|
+
self.stack.borrow_mut().clear();
|
|
66
|
+
self.pending.borrow_mut().clear();
|
|
67
|
+
*self.effects.borrow_mut() = HashMap::new();
|
|
68
|
+
self.key.set(None);
|
|
69
|
+
self.entered.set(0);
|
|
70
|
+
self.unwind.set(0);
|
|
71
|
+
}
|
|
72
|
+
pub(super) fn inherit(&self, values: Vec<Value>, enabled: bool) {
|
|
73
|
+
self.enabled.set(enabled);
|
|
74
|
+
*self.initial.borrow_mut() = values;
|
|
75
|
+
self.reset();
|
|
76
|
+
}
|
|
77
|
+
pub(super) fn reset(&self) {
|
|
78
|
+
if self.enabled() {
|
|
79
|
+
self.stack.borrow_mut().clone_from(&self.initial.borrow());
|
|
80
|
+
}
|
|
81
|
+
self.reference.set(std::ptr::null());
|
|
82
|
+
self.pending_references.borrow_mut().clear();
|
|
83
|
+
self.key.set(None);
|
|
84
|
+
self.unwind.set(0);
|
|
85
|
+
self.entered.set(0);
|
|
86
|
+
}
|
|
87
|
+
pub(super) fn enter(&self, resource: Value, enters: bool) -> Guard<'_> {
|
|
88
|
+
let entered = self.enabled() && enters && self.stack.borrow().last() != Some(&resource);
|
|
89
|
+
if entered {
|
|
90
|
+
self.stack.borrow_mut().push(resource);
|
|
91
|
+
self.entered.set(self.entered.get() + 1);
|
|
92
|
+
}
|
|
93
|
+
Guard {
|
|
94
|
+
scopes: self,
|
|
95
|
+
entered,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
pub(super) fn at(&self, index: usize) -> Option<Value> {
|
|
99
|
+
self.stack.borrow().get(index).copied()
|
|
100
|
+
}
|
|
101
|
+
pub(super) fn reference_frame(&self, program: Value, instance: Value) -> Reference {
|
|
102
|
+
Reference {
|
|
103
|
+
pair: (program, instance),
|
|
104
|
+
parent: self.reference.get(),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
pub(super) fn reference_guard<'a>(&'a self, frame: &'a Reference) -> ReferenceGuard<'a> {
|
|
108
|
+
if self.enabled() {
|
|
109
|
+
self.reference.set(frame);
|
|
110
|
+
}
|
|
111
|
+
ReferenceGuard {
|
|
112
|
+
scopes: self,
|
|
113
|
+
frame,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
pub(super) fn reference_active(&self, program: Value, instance: Value) -> bool {
|
|
117
|
+
self.enabled()
|
|
118
|
+
&& (self
|
|
119
|
+
.initial_references
|
|
120
|
+
.borrow()
|
|
121
|
+
.contains(&(program, instance))
|
|
122
|
+
|| self.vm_references.borrow().contains(&(program, instance)))
|
|
123
|
+
}
|
|
124
|
+
fn references(&self, initial: bool) -> std::cell::Ref<'_, Vec<(Value, Value)>> {
|
|
125
|
+
if !initial && self.key.get().is_some() {
|
|
126
|
+
self.pending_references.borrow()
|
|
127
|
+
} else {
|
|
128
|
+
self.initial_references.borrow()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
pub(super) fn request(&self, key: Key) {
|
|
132
|
+
if self.enabled() {
|
|
133
|
+
self.pending.borrow_mut().clone_from(&self.stack.borrow());
|
|
134
|
+
let mut pairs = self.pending_references.borrow_mut();
|
|
135
|
+
pairs.clone_from(&self.initial_references.borrow());
|
|
136
|
+
let mut reference = self.reference.get();
|
|
137
|
+
while !reference.is_null() {
|
|
138
|
+
// Each pointer belongs to a stack record borrowed by a live guard.
|
|
139
|
+
let frame = unsafe { &*reference };
|
|
140
|
+
pairs.push(frame.pair);
|
|
141
|
+
reference = frame.parent;
|
|
142
|
+
}
|
|
143
|
+
self.key.set(Some(key));
|
|
144
|
+
self.unwind.set(self.entered.get());
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
pub(super) fn apply(&self, key: Key) {
|
|
148
|
+
if self.enabled()
|
|
149
|
+
&& let Some(values) = self.effects.borrow().get(&key)
|
|
150
|
+
{
|
|
151
|
+
self.stack.borrow_mut().clone_from(values);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
pub(super) fn accept(&self, values: Vec<Value>) {
|
|
155
|
+
if let Some(key) = self.key.get() {
|
|
156
|
+
if *self.pending.borrow() == values {
|
|
157
|
+
self.effects.borrow_mut().remove(&key);
|
|
158
|
+
} else {
|
|
159
|
+
self.effects.borrow_mut().insert(key, values);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
pub(super) fn rescue(&self, error: StringError) {
|
|
164
|
+
if let Some(key) = self.key.get() {
|
|
165
|
+
let values = self.effects.borrow().get(&key).cloned();
|
|
166
|
+
if let Some(values) = values {
|
|
167
|
+
self.effects.borrow_mut().insert(Key::Rescue(error), values);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
pub(super) fn len(&self) -> usize {
|
|
172
|
+
if self.key.get().is_some() {
|
|
173
|
+
self.pending.borrow().len()
|
|
174
|
+
} else {
|
|
175
|
+
self.stack.borrow().len()
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
pub(super) fn value(&self, index: usize) -> Value {
|
|
179
|
+
if self.key.get().is_some() {
|
|
180
|
+
self.pending.borrow()[index]
|
|
181
|
+
} else {
|
|
182
|
+
self.stack.borrow()[index]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
pub(super) fn unwind(&self) -> usize {
|
|
186
|
+
self.unwind.replace(0)
|
|
187
|
+
}
|
|
188
|
+
pub(super) fn size(&self) -> usize {
|
|
189
|
+
let vectors = [&self.initial, &self.stack, &self.pending]
|
|
190
|
+
.iter()
|
|
191
|
+
.map(|v| {
|
|
192
|
+
v.try_borrow()
|
|
193
|
+
.map_or(0, |v| v.capacity() * std::mem::size_of::<Value>())
|
|
194
|
+
})
|
|
195
|
+
.sum::<usize>();
|
|
196
|
+
let references = [
|
|
197
|
+
&self.initial_references,
|
|
198
|
+
&self.vm_references,
|
|
199
|
+
&self.pending_references,
|
|
200
|
+
]
|
|
201
|
+
.iter()
|
|
202
|
+
.map(|v| {
|
|
203
|
+
v.try_borrow()
|
|
204
|
+
.map_or(0, |v| v.capacity() * std::mem::size_of::<(Value, Value)>())
|
|
205
|
+
})
|
|
206
|
+
.sum::<usize>();
|
|
207
|
+
vectors
|
|
208
|
+
+ references
|
|
209
|
+
+ self.effects.try_borrow().map_or(0, |effects| {
|
|
210
|
+
effects.capacity() * std::mem::size_of::<(Key, Vec<Value>)>()
|
|
211
|
+
+ effects
|
|
212
|
+
.values()
|
|
213
|
+
.map(|v| v.capacity() * std::mem::size_of::<Value>())
|
|
214
|
+
.sum::<usize>()
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
pub(super) fn mark(&self) {
|
|
218
|
+
// A stopped native pass may hold a scope-vector borrow. Resources also
|
|
219
|
+
// belong to the compiled registry; C roots completed request keys.
|
|
220
|
+
for values in [&self.initial, &self.stack, &self.pending] {
|
|
221
|
+
if let Ok(values) = values.try_borrow() {
|
|
222
|
+
for &value in values.iter() {
|
|
223
|
+
unsafe { sn_pin(value) };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for pairs in [
|
|
228
|
+
&self.initial_references,
|
|
229
|
+
&self.vm_references,
|
|
230
|
+
&self.pending_references,
|
|
231
|
+
] {
|
|
232
|
+
if let Ok(pairs) = pairs.try_borrow() {
|
|
233
|
+
for &(program, instance) in pairs.iter() {
|
|
234
|
+
unsafe {
|
|
235
|
+
sn_pin(program);
|
|
236
|
+
sn_pin(instance);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if let Ok(effects) = self.effects.try_borrow() {
|
|
242
|
+
for (key, values) in effects.iter() {
|
|
243
|
+
match *key {
|
|
244
|
+
Key::Value(source, value, _) => unsafe {
|
|
245
|
+
sn_pin(source);
|
|
246
|
+
sn_pin(value);
|
|
247
|
+
},
|
|
248
|
+
Key::Message(m) => unsafe {
|
|
249
|
+
sn_pin(m.operand);
|
|
250
|
+
sn_pin(m.instance);
|
|
251
|
+
sn_pin(m.frame);
|
|
252
|
+
},
|
|
253
|
+
Key::Rescue((frame, _, _)) => unsafe {
|
|
254
|
+
sn_pin(frame);
|
|
255
|
+
},
|
|
256
|
+
Key::Pointer(_) => {}
|
|
257
|
+
}
|
|
258
|
+
for &value in values {
|
|
259
|
+
unsafe { sn_pin(value) };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
fn values(array: Value) -> Vec<Value> {
|
|
266
|
+
(0..unsafe { sn_len(array) })
|
|
267
|
+
.map(|i| unsafe { sn_entry(array, i) })
|
|
268
|
+
.collect()
|
|
269
|
+
}
|
|
270
|
+
/// # Safety
|
|
271
|
+
/// Caches must be idle. Arrays must be live and runner-rooted; active must be
|
|
272
|
+
/// nil or the library-owned program/instance identity Hashes.
|
|
273
|
+
#[unsafe(no_mangle)]
|
|
274
|
+
pub unsafe extern "C" fn sn_scopes_inherit(
|
|
275
|
+
caches: *const Caches,
|
|
276
|
+
array: Value,
|
|
277
|
+
references: Value,
|
|
278
|
+
active: Value,
|
|
279
|
+
enabled: i32,
|
|
280
|
+
) {
|
|
281
|
+
unsafe { &*caches }.scopes.inherit(
|
|
282
|
+
if enabled != 0 { values(array) } else { vec![] },
|
|
283
|
+
enabled != 0,
|
|
284
|
+
);
|
|
285
|
+
if enabled != 0 {
|
|
286
|
+
let scopes = &unsafe { &*caches }.scopes;
|
|
287
|
+
*scopes.initial_references.borrow_mut() = (0..unsafe { sn_len(references) } / 2)
|
|
288
|
+
.map(|i| unsafe { (sn_entry(references, i * 2), sn_entry(references, i * 2 + 1)) })
|
|
289
|
+
.collect();
|
|
290
|
+
let mut pairs = scopes.vm_references.borrow_mut();
|
|
291
|
+
unsafe {
|
|
292
|
+
sn_references_read(active, (&mut *pairs as *mut Vec<(Value, Value)>).cast());
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
unsafe extern "C" {
|
|
297
|
+
fn sn_references_read(active: Value, pairs: *mut c_void);
|
|
298
|
+
}
|
|
299
|
+
/// # Safety
|
|
300
|
+
/// Pairs must be an exclusively borrowed reference vector; called by C's
|
|
301
|
+
/// allocation-free walk of library-owned identity Hashes.
|
|
302
|
+
#[unsafe(no_mangle)]
|
|
303
|
+
pub unsafe extern "C" fn sn_reference_pair(pairs: *mut c_void, program: Value, instance: Value) {
|
|
304
|
+
unsafe { &mut *pairs.cast::<Vec<(Value, Value)>>() }.push((program, instance));
|
|
305
|
+
}
|
|
306
|
+
/// # Safety
|
|
307
|
+
/// Caches must be idle and live.
|
|
308
|
+
#[unsafe(no_mangle)]
|
|
309
|
+
pub unsafe extern "C" fn sn_references_len(caches: *const Caches, initial: i32) -> usize {
|
|
310
|
+
unsafe { &*caches }.scopes.references(initial != 0).len() * 2
|
|
311
|
+
}
|
|
312
|
+
/// # Safety
|
|
313
|
+
/// Caches must be idle and index in range of sn_references_len.
|
|
314
|
+
#[unsafe(no_mangle)]
|
|
315
|
+
pub unsafe extern "C" fn sn_references_at(
|
|
316
|
+
caches: *const Caches,
|
|
317
|
+
index: usize,
|
|
318
|
+
initial: i32,
|
|
319
|
+
) -> Value {
|
|
320
|
+
let pairs = unsafe { &*caches }.scopes.references(initial != 0);
|
|
321
|
+
let pair = pairs[index / 2];
|
|
322
|
+
if index.is_multiple_of(2) {
|
|
323
|
+
pair.0
|
|
324
|
+
} else {
|
|
325
|
+
pair.1
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/// # Safety
|
|
329
|
+
/// Caches must be idle and live.
|
|
330
|
+
#[unsafe(no_mangle)]
|
|
331
|
+
pub unsafe extern "C" fn sn_scopes_enabled(caches: *const Caches) -> i32 {
|
|
332
|
+
i32::from(unsafe { &*caches }.scopes.enabled())
|
|
333
|
+
}
|
|
334
|
+
/// # Safety
|
|
335
|
+
/// Caches must be idle and live.
|
|
336
|
+
#[unsafe(no_mangle)]
|
|
337
|
+
pub unsafe extern "C" fn sn_scopes_len(caches: *const Caches) -> usize {
|
|
338
|
+
unsafe { &*caches }.scopes.len()
|
|
339
|
+
}
|
|
340
|
+
/// # Safety
|
|
341
|
+
/// Caches must be idle and index in range of sn_scopes_len.
|
|
342
|
+
#[unsafe(no_mangle)]
|
|
343
|
+
pub unsafe extern "C" fn sn_scopes_at(caches: *const Caches, index: usize) -> Value {
|
|
344
|
+
unsafe { &*caches }.scopes.value(index)
|
|
345
|
+
}
|
|
346
|
+
/// # Safety
|
|
347
|
+
/// Caches must be idle and array must retain the current Ruby scope contents.
|
|
348
|
+
#[unsafe(no_mangle)]
|
|
349
|
+
pub unsafe extern "C" fn sn_scopes_accept(caches: *const Caches, array: Value) {
|
|
350
|
+
if unsafe { &*caches }.scopes.enabled() {
|
|
351
|
+
unsafe { &*caches }.scopes.accept(values(array));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/// # Safety
|
|
355
|
+
/// Caches must be idle and live. Consume only during the runner ensure.
|
|
356
|
+
#[unsafe(no_mangle)]
|
|
357
|
+
pub unsafe extern "C" fn sn_scopes_unwind(caches: *const Caches) -> usize {
|
|
358
|
+
unsafe { &*caches }.scopes.unwind()
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#[cfg(test)]
|
|
362
|
+
mod tests {
|
|
363
|
+
use super::*;
|
|
364
|
+
|
|
365
|
+
#[test]
|
|
366
|
+
fn pending_scopes_survive_native_unwind_and_replay_ruby_clears() {
|
|
367
|
+
let scopes = Scopes::default();
|
|
368
|
+
let key = Key::Value(2, 3, RUNTIME_TYPE);
|
|
369
|
+
scopes.inherit(vec![10], true);
|
|
370
|
+
{
|
|
371
|
+
let _outer = scopes.enter(20, true);
|
|
372
|
+
let _same = scopes.enter(20, true);
|
|
373
|
+
scopes.request(key);
|
|
374
|
+
assert_eq!(scopes.unwind(), 1);
|
|
375
|
+
assert_eq!(scopes.len(), 2);
|
|
376
|
+
}
|
|
377
|
+
assert_eq!(scopes.at(0), Some(10));
|
|
378
|
+
assert_eq!(scopes.value(1), 20);
|
|
379
|
+
scopes.accept(vec![]);
|
|
380
|
+
scopes.reset();
|
|
381
|
+
{
|
|
382
|
+
let _outer = scopes.enter(20, true);
|
|
383
|
+
scopes.apply(key);
|
|
384
|
+
assert_eq!(scopes.at(0), None);
|
|
385
|
+
}
|
|
386
|
+
assert_eq!(scopes.at(0), None);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#[test]
|
|
390
|
+
fn reference_requests_copy_live_guards_and_keep_vm_ancestors_separate() {
|
|
391
|
+
let scopes = Scopes::default();
|
|
392
|
+
scopes.inherit(vec![], true);
|
|
393
|
+
scopes.initial_references.borrow_mut().push((10, 11));
|
|
394
|
+
scopes.vm_references.borrow_mut().push((20, 21));
|
|
395
|
+
assert!(scopes.reference_active(10, 11));
|
|
396
|
+
assert!(scopes.reference_active(20, 21));
|
|
397
|
+
assert!(!scopes.reference_active(10, 21));
|
|
398
|
+
{
|
|
399
|
+
let outer = scopes.reference_frame(30, 31);
|
|
400
|
+
let _outer = scopes.reference_guard(&outer);
|
|
401
|
+
{
|
|
402
|
+
let inner = scopes.reference_frame(40, 41);
|
|
403
|
+
let _inner = scopes.reference_guard(&inner);
|
|
404
|
+
scopes.request(Key::Value(2, 3, RUNTIME_TYPE));
|
|
405
|
+
}
|
|
406
|
+
assert_eq!(
|
|
407
|
+
*scopes.references(false),
|
|
408
|
+
vec![(10, 11), (40, 41), (30, 31)]
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
assert!(scopes.reference.get().is_null());
|
|
412
|
+
assert_eq!(*scopes.references(true), vec![(10, 11)]);
|
|
413
|
+
scopes.reset();
|
|
414
|
+
assert_eq!(*scopes.references(false), vec![(10, 11)]);
|
|
415
|
+
scopes.clear();
|
|
416
|
+
assert!(!scopes.reference_active(20, 21));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#[test]
|
|
420
|
+
fn unchanged_requests_do_not_restore_a_later_mutated_scope() {
|
|
421
|
+
let scopes = Scopes::default();
|
|
422
|
+
let key = Key::Value(2, 3, RUNTIME_TYPE);
|
|
423
|
+
scopes.inherit(vec![10], true);
|
|
424
|
+
scopes.request(key);
|
|
425
|
+
scopes.accept(vec![10]);
|
|
426
|
+
let _inner = scopes.enter(20, true);
|
|
427
|
+
scopes.apply(key);
|
|
428
|
+
assert_eq!(scopes.at(1), Some(20));
|
|
429
|
+
}
|
|
430
|
+
}
|