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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/ext/schemurai_native/Cargo.lock +55 -1
  4. data/ext/schemurai_native/Cargo.toml +4 -1
  5. data/ext/schemurai_native/extconf.rb +1 -1
  6. data/ext/schemurai_native/glue.c +1679 -62
  7. data/ext/schemurai_native/protocol.def +45 -0
  8. data/ext/schemurai_native/src/budget.rs +49 -0
  9. data/ext/schemurai_native/src/container.rs +460 -0
  10. data/ext/schemurai_native/src/content.rs +74 -27
  11. data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
  12. data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
  13. data/ext/schemurai_native/src/diagnostics.rs +1087 -307
  14. data/ext/schemurai_native/src/enumeration.rs +6 -1
  15. data/ext/schemurai_native/src/evaluation.rs +49 -20
  16. data/ext/schemurai_native/src/format.rs +126 -14
  17. data/ext/schemurai_native/src/graph.rs +37 -0
  18. data/ext/schemurai_native/src/instance_graph.rs +90 -0
  19. data/ext/schemurai_native/src/integer_scan.rs +132 -0
  20. data/ext/schemurai_native/src/lib.rs +917 -238
  21. data/ext/schemurai_native/src/memo.rs +11 -38
  22. data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
  23. data/ext/schemurai_native/src/number.rs +250 -59
  24. data/ext/schemurai_native/src/pattern.rs +147 -109
  25. data/ext/schemurai_native/src/pointer.rs +97 -0
  26. data/ext/schemurai_native/src/runtime/properties.rs +162 -0
  27. data/ext/schemurai_native/src/runtime/reports.rs +266 -0
  28. data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
  29. data/ext/schemurai_native/src/runtime.rs +1020 -0
  30. data/ext/schemurai_native/src/scope.rs +60 -16
  31. data/ext/schemurai_native/src/string.rs +68 -0
  32. data/ext/schemurai_native/src/tracking.rs +244 -174
  33. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  34. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  35. data/lib/schemurai/container_checks.rb +534 -0
  36. data/lib/schemurai/decimal.rb +11 -0
  37. data/lib/schemurai/dialect.rb +0 -12
  38. data/lib/schemurai/ecma_regexp.rb +46 -0
  39. data/lib/schemurai/evaluation_pool.rb +66 -0
  40. data/lib/schemurai/evaluator.rb +9 -48
  41. data/lib/schemurai/json_pointer.rb +11 -0
  42. data/lib/schemurai/native/diagnostics.rb +4 -3
  43. data/lib/schemurai/native/evaluator.rb +38 -10
  44. data/lib/schemurai/native/null_numeric.rb +23 -0
  45. data/lib/schemurai/number_checks.rb +128 -0
  46. data/lib/schemurai/string_checks.rb +166 -0
  47. data/lib/schemurai/type_checks.rb +59 -0
  48. data/lib/schemurai/value_equality.rb +72 -0
  49. data/lib/schemurai/version.rb +1 -1
  50. data/lib/schemurai/vm/compiler.rb +18 -26
  51. data/lib/schemurai/vm/evaluator.rb +87 -545
  52. data/lib/schemurai/vm/limited_evaluator.rb +7 -22
  53. 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
+ }