mustardscript 0.1.0
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.
- package/Cargo.lock +1579 -0
- package/Cargo.toml +40 -0
- package/LICENSE +201 -0
- package/README.md +828 -0
- package/SECURITY.md +34 -0
- package/crates/mustard/Cargo.toml +31 -0
- package/crates/mustard/src/cancellation.rs +28 -0
- package/crates/mustard/src/diagnostic.rs +145 -0
- package/crates/mustard/src/ir.rs +435 -0
- package/crates/mustard/src/lib.rs +21 -0
- package/crates/mustard/src/limits.rs +22 -0
- package/crates/mustard/src/parser/expressions.rs +723 -0
- package/crates/mustard/src/parser/mod.rs +115 -0
- package/crates/mustard/src/parser/operators.rs +105 -0
- package/crates/mustard/src/parser/patterns.rs +123 -0
- package/crates/mustard/src/parser/scope.rs +107 -0
- package/crates/mustard/src/parser/statements.rs +298 -0
- package/crates/mustard/src/parser/tests/acceptance.rs +339 -0
- package/crates/mustard/src/parser/tests/mod.rs +2 -0
- package/crates/mustard/src/parser/tests/rejections.rs +107 -0
- package/crates/mustard/src/runtime/accounting.rs +613 -0
- package/crates/mustard/src/runtime/api.rs +192 -0
- package/crates/mustard/src/runtime/async_runtime/mod.rs +5 -0
- package/crates/mustard/src/runtime/async_runtime/promises.rs +246 -0
- package/crates/mustard/src/runtime/async_runtime/reactions.rs +400 -0
- package/crates/mustard/src/runtime/async_runtime/scheduler.rs +224 -0
- package/crates/mustard/src/runtime/builtins/arrays.rs +1205 -0
- package/crates/mustard/src/runtime/builtins/collections.rs +573 -0
- package/crates/mustard/src/runtime/builtins/install.rs +501 -0
- package/crates/mustard/src/runtime/builtins/intl.rs +553 -0
- package/crates/mustard/src/runtime/builtins/mod.rs +25 -0
- package/crates/mustard/src/runtime/builtins/objects.rs +405 -0
- package/crates/mustard/src/runtime/builtins/primitives.rs +859 -0
- package/crates/mustard/src/runtime/builtins/promises.rs +335 -0
- package/crates/mustard/src/runtime/builtins/regexp.rs +356 -0
- package/crates/mustard/src/runtime/builtins/strings.rs +803 -0
- package/crates/mustard/src/runtime/builtins/support.rs +561 -0
- package/crates/mustard/src/runtime/bytecode.rs +123 -0
- package/crates/mustard/src/runtime/compiler/assignments.rs +690 -0
- package/crates/mustard/src/runtime/compiler/bindings.rs +92 -0
- package/crates/mustard/src/runtime/compiler/context.rs +46 -0
- package/crates/mustard/src/runtime/compiler/control.rs +342 -0
- package/crates/mustard/src/runtime/compiler/expressions.rs +372 -0
- package/crates/mustard/src/runtime/compiler/mod.rs +173 -0
- package/crates/mustard/src/runtime/compiler/statements.rs +459 -0
- package/crates/mustard/src/runtime/conversions/boundary.rs +293 -0
- package/crates/mustard/src/runtime/conversions/coercions.rs +217 -0
- package/crates/mustard/src/runtime/conversions/errors.rs +118 -0
- package/crates/mustard/src/runtime/conversions/mod.rs +14 -0
- package/crates/mustard/src/runtime/conversions/operators.rs +334 -0
- package/crates/mustard/src/runtime/env.rs +355 -0
- package/crates/mustard/src/runtime/exceptions.rs +377 -0
- package/crates/mustard/src/runtime/gc.rs +595 -0
- package/crates/mustard/src/runtime/mod.rs +318 -0
- package/crates/mustard/src/runtime/properties.rs +1762 -0
- package/crates/mustard/src/runtime/serialization.rs +127 -0
- package/crates/mustard/src/runtime/shared.rs +108 -0
- package/crates/mustard/src/runtime/snapshot_validation_tests.rs +93 -0
- package/crates/mustard/src/runtime/state.rs +652 -0
- package/crates/mustard/src/runtime/tests/async_host.rs +104 -0
- package/crates/mustard/src/runtime/tests/collections.rs +50 -0
- package/crates/mustard/src/runtime/tests/diagnostics.rs +36 -0
- package/crates/mustard/src/runtime/tests/exceptions.rs +122 -0
- package/crates/mustard/src/runtime/tests/execution.rs +553 -0
- package/crates/mustard/src/runtime/tests/gc.rs +533 -0
- package/crates/mustard/src/runtime/tests/mod.rs +56 -0
- package/crates/mustard/src/runtime/tests/serialization.rs +170 -0
- package/crates/mustard/src/runtime/validation/bytecode.rs +484 -0
- package/crates/mustard/src/runtime/validation/mod.rs +14 -0
- package/crates/mustard/src/runtime/validation/policy.rs +94 -0
- package/crates/mustard/src/runtime/validation/snapshot.rs +406 -0
- package/crates/mustard/src/runtime/validation/walk.rs +206 -0
- package/crates/mustard/src/runtime/vm.rs +1016 -0
- package/crates/mustard/src/span.rs +22 -0
- package/crates/mustard/src/structured.rs +107 -0
- package/crates/mustard-bridge/Cargo.toml +17 -0
- package/crates/mustard-bridge/src/codec.rs +46 -0
- package/crates/mustard-bridge/src/dto.rs +99 -0
- package/crates/mustard-bridge/src/lib.rs +12 -0
- package/crates/mustard-bridge/src/operations.rs +142 -0
- package/crates/mustard-node/Cargo.toml +24 -0
- package/crates/mustard-node/build.rs +3 -0
- package/crates/mustard-node/src/lib.rs +236 -0
- package/crates/mustard-sidecar/Cargo.toml +21 -0
- package/crates/mustard-sidecar/src/lib.rs +134 -0
- package/crates/mustard-sidecar/src/main.rs +36 -0
- package/dist/index.js +20 -0
- package/dist/install.js +117 -0
- package/dist/lib/cancellation.js +124 -0
- package/dist/lib/errors.js +46 -0
- package/dist/lib/executor.js +555 -0
- package/dist/lib/policy.js +292 -0
- package/dist/lib/progress.js +356 -0
- package/dist/lib/runtime.js +109 -0
- package/dist/lib/structured.js +286 -0
- package/dist/native-loader.js +227 -0
- package/index.d.ts +23 -0
- package/mustard.d.ts +220 -0
- package/package.json +97 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
#[test]
|
|
4
|
+
fn tracks_heap_growth_and_enforces_heap_limits() {
|
|
5
|
+
let program = lower_to_bytecode(&compile("1;").expect("source should compile"))
|
|
6
|
+
.expect("lowering should succeed");
|
|
7
|
+
let mut runtime = Runtime::new(program.clone(), ExecutionOptions::default())
|
|
8
|
+
.expect("runtime should initialize");
|
|
9
|
+
|
|
10
|
+
let baseline_heap = runtime.heap_bytes_used;
|
|
11
|
+
let array = runtime
|
|
12
|
+
.insert_array(vec![Value::String("payload".to_string())], IndexMap::new())
|
|
13
|
+
.expect("array allocation should succeed");
|
|
14
|
+
assert!(runtime.heap_bytes_used > baseline_heap);
|
|
15
|
+
|
|
16
|
+
let array_heap = runtime.heap_bytes_used;
|
|
17
|
+
runtime
|
|
18
|
+
.set_property(
|
|
19
|
+
Value::Array(array),
|
|
20
|
+
Value::String("extra".to_string()),
|
|
21
|
+
Value::String("more payload".to_string()),
|
|
22
|
+
)
|
|
23
|
+
.expect("array growth should succeed");
|
|
24
|
+
assert!(runtime.heap_bytes_used > array_heap);
|
|
25
|
+
|
|
26
|
+
let mut heap_limited = Runtime::new(program.clone(), ExecutionOptions::default())
|
|
27
|
+
.expect("runtime should initialize");
|
|
28
|
+
heap_limited.limits.heap_limit_bytes = heap_limited.heap_bytes_used;
|
|
29
|
+
let error = heap_limited
|
|
30
|
+
.insert_array(vec![Value::String("payload".to_string())], IndexMap::new())
|
|
31
|
+
.expect_err("next allocation should exceed the heap limit");
|
|
32
|
+
assert!(error.to_string().contains("heap limit exceeded"));
|
|
33
|
+
|
|
34
|
+
let mut allocation_limited =
|
|
35
|
+
Runtime::new(program, ExecutionOptions::default()).expect("runtime should initialize");
|
|
36
|
+
allocation_limited.limits.allocation_budget = allocation_limited.allocation_count;
|
|
37
|
+
let error = allocation_limited
|
|
38
|
+
.insert_object(IndexMap::new(), ObjectKind::Plain)
|
|
39
|
+
.expect_err("next allocation should exhaust the allocation budget");
|
|
40
|
+
assert!(error.to_string().contains("allocation budget exhausted"));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
fn iterators_participate_in_heap_accounting_and_gc() {
|
|
45
|
+
let program = lower_to_bytecode(&compile("0;").expect("source should compile"))
|
|
46
|
+
.expect("lowering should succeed");
|
|
47
|
+
let mut runtime = Runtime::new(program, ExecutionOptions::default()).expect("runtime init");
|
|
48
|
+
|
|
49
|
+
let baseline_heap = runtime.heap_bytes_used;
|
|
50
|
+
let kept_array = runtime
|
|
51
|
+
.insert_array(
|
|
52
|
+
vec![Value::Number(1.0), Value::Number(2.0)],
|
|
53
|
+
IndexMap::new(),
|
|
54
|
+
)
|
|
55
|
+
.expect("kept array should allocate");
|
|
56
|
+
let kept_iterator = runtime
|
|
57
|
+
.insert_iterator(IteratorState::Array(ArrayIteratorState {
|
|
58
|
+
array: kept_array,
|
|
59
|
+
next_index: 1,
|
|
60
|
+
}))
|
|
61
|
+
.expect("kept iterator should allocate");
|
|
62
|
+
assert!(runtime.heap_bytes_used > baseline_heap);
|
|
63
|
+
|
|
64
|
+
let frame_env = runtime
|
|
65
|
+
.new_env(Some(runtime.globals))
|
|
66
|
+
.expect("frame env should allocate");
|
|
67
|
+
let iterator_cell = runtime
|
|
68
|
+
.insert_cell(Value::Iterator(kept_iterator), true, true)
|
|
69
|
+
.expect("iterator cell should allocate");
|
|
70
|
+
runtime
|
|
71
|
+
.envs
|
|
72
|
+
.get_mut(frame_env)
|
|
73
|
+
.expect("frame env should exist")
|
|
74
|
+
.bindings
|
|
75
|
+
.insert("\0kept_iter".to_string(), iterator_cell);
|
|
76
|
+
runtime
|
|
77
|
+
.refresh_env_accounting(frame_env)
|
|
78
|
+
.expect("frame env accounting should refresh");
|
|
79
|
+
runtime.frames.push(Frame {
|
|
80
|
+
function_id: 0,
|
|
81
|
+
ip: 0,
|
|
82
|
+
env: frame_env,
|
|
83
|
+
scope_stack: Vec::new(),
|
|
84
|
+
stack: Vec::new(),
|
|
85
|
+
handlers: Vec::new(),
|
|
86
|
+
pending_exception: None,
|
|
87
|
+
pending_completions: Vec::new(),
|
|
88
|
+
active_finally: Vec::new(),
|
|
89
|
+
async_promise: None,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
let garbage_array = runtime
|
|
93
|
+
.insert_array(vec![Value::Number(9.0)], IndexMap::new())
|
|
94
|
+
.expect("garbage array should allocate");
|
|
95
|
+
let garbage_iterator = runtime
|
|
96
|
+
.insert_iterator(IteratorState::Array(ArrayIteratorState {
|
|
97
|
+
array: garbage_array,
|
|
98
|
+
next_index: 0,
|
|
99
|
+
}))
|
|
100
|
+
.expect("garbage iterator should allocate");
|
|
101
|
+
|
|
102
|
+
runtime.collect_garbage().expect("gc should succeed");
|
|
103
|
+
assert!(runtime.arrays.contains_key(kept_array));
|
|
104
|
+
assert!(runtime.iterators.contains_key(kept_iterator));
|
|
105
|
+
assert!(!runtime.arrays.contains_key(garbage_array));
|
|
106
|
+
assert!(!runtime.iterators.contains_key(garbage_iterator));
|
|
107
|
+
|
|
108
|
+
runtime.frames.clear();
|
|
109
|
+
runtime.collect_garbage().expect("gc should succeed");
|
|
110
|
+
assert!(!runtime.arrays.contains_key(kept_array));
|
|
111
|
+
assert!(!runtime.iterators.contains_key(kept_iterator));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
fn map_and_set_iterators_keep_keyed_collections_alive_for_gc() {
|
|
116
|
+
let program = lower_to_bytecode(&compile("0;").expect("source should compile"))
|
|
117
|
+
.expect("lowering should succeed");
|
|
118
|
+
let mut runtime = Runtime::new(program, ExecutionOptions::default()).expect("runtime init");
|
|
119
|
+
|
|
120
|
+
let kept_map = runtime.insert_map(Vec::new()).expect("map should allocate");
|
|
121
|
+
runtime
|
|
122
|
+
.map_set(
|
|
123
|
+
kept_map,
|
|
124
|
+
Value::String("alpha".to_string()),
|
|
125
|
+
Value::Number(1.0),
|
|
126
|
+
)
|
|
127
|
+
.expect("map entry should insert");
|
|
128
|
+
let kept_set = runtime.insert_set(Vec::new()).expect("set should allocate");
|
|
129
|
+
runtime
|
|
130
|
+
.set_add(kept_set, Value::String("beta".to_string()))
|
|
131
|
+
.expect("set entry should insert");
|
|
132
|
+
let kept_map_iterator = runtime
|
|
133
|
+
.insert_iterator(IteratorState::MapEntries(MapIteratorState {
|
|
134
|
+
map: kept_map,
|
|
135
|
+
next_index: 0,
|
|
136
|
+
}))
|
|
137
|
+
.expect("map iterator should allocate");
|
|
138
|
+
let kept_set_iterator = runtime
|
|
139
|
+
.insert_iterator(IteratorState::SetValues(SetIteratorState {
|
|
140
|
+
set: kept_set,
|
|
141
|
+
next_index: 0,
|
|
142
|
+
}))
|
|
143
|
+
.expect("set iterator should allocate");
|
|
144
|
+
|
|
145
|
+
let frame_env = runtime
|
|
146
|
+
.new_env(Some(runtime.globals))
|
|
147
|
+
.expect("frame env should allocate");
|
|
148
|
+
let map_iterator_cell = runtime
|
|
149
|
+
.insert_cell(Value::Iterator(kept_map_iterator), true, true)
|
|
150
|
+
.expect("map iterator cell should allocate");
|
|
151
|
+
let set_iterator_cell = runtime
|
|
152
|
+
.insert_cell(Value::Iterator(kept_set_iterator), true, true)
|
|
153
|
+
.expect("set iterator cell should allocate");
|
|
154
|
+
let env = runtime
|
|
155
|
+
.envs
|
|
156
|
+
.get_mut(frame_env)
|
|
157
|
+
.expect("frame env should exist");
|
|
158
|
+
env.bindings
|
|
159
|
+
.insert("\0kept_map_iter".to_string(), map_iterator_cell);
|
|
160
|
+
env.bindings
|
|
161
|
+
.insert("\0kept_set_iter".to_string(), set_iterator_cell);
|
|
162
|
+
runtime
|
|
163
|
+
.refresh_env_accounting(frame_env)
|
|
164
|
+
.expect("frame env accounting should refresh");
|
|
165
|
+
runtime.frames.push(Frame {
|
|
166
|
+
function_id: 0,
|
|
167
|
+
ip: 0,
|
|
168
|
+
env: frame_env,
|
|
169
|
+
scope_stack: Vec::new(),
|
|
170
|
+
stack: Vec::new(),
|
|
171
|
+
handlers: Vec::new(),
|
|
172
|
+
pending_exception: None,
|
|
173
|
+
pending_completions: Vec::new(),
|
|
174
|
+
active_finally: Vec::new(),
|
|
175
|
+
async_promise: None,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
let garbage_map = runtime.insert_map(Vec::new()).expect("map should allocate");
|
|
179
|
+
runtime
|
|
180
|
+
.map_set(
|
|
181
|
+
garbage_map,
|
|
182
|
+
Value::String("gamma".to_string()),
|
|
183
|
+
Value::Number(2.0),
|
|
184
|
+
)
|
|
185
|
+
.expect("garbage map entry should insert");
|
|
186
|
+
let garbage_set = runtime.insert_set(Vec::new()).expect("set should allocate");
|
|
187
|
+
runtime
|
|
188
|
+
.set_add(garbage_set, Value::String("delta".to_string()))
|
|
189
|
+
.expect("garbage set entry should insert");
|
|
190
|
+
let garbage_map_iterator = runtime
|
|
191
|
+
.insert_iterator(IteratorState::MapEntries(MapIteratorState {
|
|
192
|
+
map: garbage_map,
|
|
193
|
+
next_index: 0,
|
|
194
|
+
}))
|
|
195
|
+
.expect("garbage map iterator should allocate");
|
|
196
|
+
let garbage_set_iterator = runtime
|
|
197
|
+
.insert_iterator(IteratorState::SetValues(SetIteratorState {
|
|
198
|
+
set: garbage_set,
|
|
199
|
+
next_index: 0,
|
|
200
|
+
}))
|
|
201
|
+
.expect("garbage set iterator should allocate");
|
|
202
|
+
|
|
203
|
+
runtime.collect_garbage().expect("gc should succeed");
|
|
204
|
+
assert!(runtime.maps.contains_key(kept_map));
|
|
205
|
+
assert!(runtime.sets.contains_key(kept_set));
|
|
206
|
+
assert!(runtime.iterators.contains_key(kept_map_iterator));
|
|
207
|
+
assert!(runtime.iterators.contains_key(kept_set_iterator));
|
|
208
|
+
assert!(!runtime.maps.contains_key(garbage_map));
|
|
209
|
+
assert!(!runtime.sets.contains_key(garbage_set));
|
|
210
|
+
assert!(!runtime.iterators.contains_key(garbage_map_iterator));
|
|
211
|
+
assert!(!runtime.iterators.contains_key(garbage_set_iterator));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn promise_reactions_keep_target_promises_alive_for_gc() {
|
|
216
|
+
let program = lower_to_bytecode(&compile("0;").expect("source should compile"))
|
|
217
|
+
.expect("lowering should succeed");
|
|
218
|
+
let mut runtime = Runtime::new(program, ExecutionOptions::default()).expect("runtime init");
|
|
219
|
+
|
|
220
|
+
let kept_source = runtime
|
|
221
|
+
.insert_promise(PromiseState::Pending)
|
|
222
|
+
.expect("source promise should allocate");
|
|
223
|
+
let kept_target = runtime
|
|
224
|
+
.insert_promise(PromiseState::Pending)
|
|
225
|
+
.expect("target promise should allocate");
|
|
226
|
+
runtime
|
|
227
|
+
.attach_promise_reaction(
|
|
228
|
+
kept_source,
|
|
229
|
+
PromiseReaction::Then {
|
|
230
|
+
target: kept_target,
|
|
231
|
+
on_fulfilled: None,
|
|
232
|
+
on_rejected: None,
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
.expect("reaction should attach");
|
|
236
|
+
|
|
237
|
+
let frame_env = runtime
|
|
238
|
+
.new_env(Some(runtime.globals))
|
|
239
|
+
.expect("frame env should allocate");
|
|
240
|
+
let source_cell = runtime
|
|
241
|
+
.insert_cell(Value::Promise(kept_source), true, true)
|
|
242
|
+
.expect("promise cell should allocate");
|
|
243
|
+
runtime
|
|
244
|
+
.envs
|
|
245
|
+
.get_mut(frame_env)
|
|
246
|
+
.expect("frame env should exist")
|
|
247
|
+
.bindings
|
|
248
|
+
.insert("\0kept_promise".to_string(), source_cell);
|
|
249
|
+
runtime
|
|
250
|
+
.refresh_env_accounting(frame_env)
|
|
251
|
+
.expect("frame env accounting should refresh");
|
|
252
|
+
runtime.frames.push(Frame {
|
|
253
|
+
function_id: 0,
|
|
254
|
+
ip: 0,
|
|
255
|
+
env: frame_env,
|
|
256
|
+
scope_stack: Vec::new(),
|
|
257
|
+
stack: Vec::new(),
|
|
258
|
+
handlers: Vec::new(),
|
|
259
|
+
pending_exception: None,
|
|
260
|
+
pending_completions: Vec::new(),
|
|
261
|
+
active_finally: Vec::new(),
|
|
262
|
+
async_promise: None,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
let garbage_source = runtime
|
|
266
|
+
.insert_promise(PromiseState::Pending)
|
|
267
|
+
.expect("garbage promise should allocate");
|
|
268
|
+
let garbage_target = runtime
|
|
269
|
+
.insert_promise(PromiseState::Pending)
|
|
270
|
+
.expect("garbage target should allocate");
|
|
271
|
+
runtime
|
|
272
|
+
.attach_promise_reaction(
|
|
273
|
+
garbage_source,
|
|
274
|
+
PromiseReaction::Then {
|
|
275
|
+
target: garbage_target,
|
|
276
|
+
on_fulfilled: None,
|
|
277
|
+
on_rejected: None,
|
|
278
|
+
},
|
|
279
|
+
)
|
|
280
|
+
.expect("garbage reaction should attach");
|
|
281
|
+
|
|
282
|
+
runtime.collect_garbage().expect("gc should succeed");
|
|
283
|
+
assert!(runtime.promises.contains_key(kept_source));
|
|
284
|
+
assert!(runtime.promises.contains_key(kept_target));
|
|
285
|
+
assert!(!runtime.promises.contains_key(garbage_source));
|
|
286
|
+
assert!(!runtime.promises.contains_key(garbage_target));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#[test]
|
|
290
|
+
fn keyed_collections_participate_in_heap_accounting_and_gc() {
|
|
291
|
+
let program = lower_to_bytecode(&compile("0;").expect("source should compile"))
|
|
292
|
+
.expect("lowering should succeed");
|
|
293
|
+
let mut runtime = Runtime::new(program, ExecutionOptions::default()).expect("runtime init");
|
|
294
|
+
|
|
295
|
+
let baseline_heap = runtime.heap_bytes_used;
|
|
296
|
+
let kept_map = runtime.insert_map(Vec::new()).expect("map should allocate");
|
|
297
|
+
let kept_set = runtime.insert_set(Vec::new()).expect("set should allocate");
|
|
298
|
+
runtime
|
|
299
|
+
.map_set(
|
|
300
|
+
kept_map,
|
|
301
|
+
Value::String("set".to_string()),
|
|
302
|
+
Value::Set(kept_set),
|
|
303
|
+
)
|
|
304
|
+
.expect("map should store the set");
|
|
305
|
+
runtime
|
|
306
|
+
.set_add(kept_set, Value::Map(kept_map))
|
|
307
|
+
.expect("set should store the map");
|
|
308
|
+
assert!(runtime.heap_bytes_used > baseline_heap);
|
|
309
|
+
|
|
310
|
+
let frame_env = runtime
|
|
311
|
+
.new_env(Some(runtime.globals))
|
|
312
|
+
.expect("frame env should allocate");
|
|
313
|
+
let map_cell = runtime
|
|
314
|
+
.insert_cell(Value::Map(kept_map), true, true)
|
|
315
|
+
.expect("map cell should allocate");
|
|
316
|
+
runtime
|
|
317
|
+
.envs
|
|
318
|
+
.get_mut(frame_env)
|
|
319
|
+
.expect("frame env should exist")
|
|
320
|
+
.bindings
|
|
321
|
+
.insert("\0kept_map".to_string(), map_cell);
|
|
322
|
+
runtime
|
|
323
|
+
.refresh_env_accounting(frame_env)
|
|
324
|
+
.expect("frame env accounting should refresh");
|
|
325
|
+
runtime.frames.push(Frame {
|
|
326
|
+
function_id: 0,
|
|
327
|
+
ip: 0,
|
|
328
|
+
env: frame_env,
|
|
329
|
+
scope_stack: Vec::new(),
|
|
330
|
+
stack: Vec::new(),
|
|
331
|
+
handlers: Vec::new(),
|
|
332
|
+
pending_exception: None,
|
|
333
|
+
pending_completions: Vec::new(),
|
|
334
|
+
active_finally: Vec::new(),
|
|
335
|
+
async_promise: None,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
let garbage_map = runtime
|
|
339
|
+
.insert_map(Vec::new())
|
|
340
|
+
.expect("garbage map should allocate");
|
|
341
|
+
let garbage_set = runtime
|
|
342
|
+
.insert_set(Vec::new())
|
|
343
|
+
.expect("garbage set should allocate");
|
|
344
|
+
runtime
|
|
345
|
+
.map_set(
|
|
346
|
+
garbage_map,
|
|
347
|
+
Value::String("set".to_string()),
|
|
348
|
+
Value::Set(garbage_set),
|
|
349
|
+
)
|
|
350
|
+
.expect("garbage map should store the set");
|
|
351
|
+
runtime
|
|
352
|
+
.set_add(garbage_set, Value::Map(garbage_map))
|
|
353
|
+
.expect("garbage set should store the map");
|
|
354
|
+
|
|
355
|
+
runtime.collect_garbage().expect("gc should succeed");
|
|
356
|
+
assert!(runtime.maps.contains_key(kept_map));
|
|
357
|
+
assert!(runtime.sets.contains_key(kept_set));
|
|
358
|
+
assert!(!runtime.maps.contains_key(garbage_map));
|
|
359
|
+
assert!(!runtime.sets.contains_key(garbage_set));
|
|
360
|
+
|
|
361
|
+
runtime.frames.clear();
|
|
362
|
+
runtime.collect_garbage().expect("gc should succeed");
|
|
363
|
+
assert!(!runtime.maps.contains_key(kept_map));
|
|
364
|
+
assert!(!runtime.sets.contains_key(kept_set));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#[test]
|
|
368
|
+
fn garbage_collection_marks_runtime_roots_and_collects_cycles() {
|
|
369
|
+
let program = lower_to_bytecode(&compile("0;").expect("source should compile"))
|
|
370
|
+
.expect("lowering should succeed");
|
|
371
|
+
let mut runtime =
|
|
372
|
+
Runtime::new(program.clone(), ExecutionOptions::default()).expect("runtime init");
|
|
373
|
+
|
|
374
|
+
let closure_env = runtime
|
|
375
|
+
.new_env(Some(runtime.globals))
|
|
376
|
+
.expect("closure env should allocate");
|
|
377
|
+
let rooted_closure = runtime
|
|
378
|
+
.insert_closure(program.root, closure_env)
|
|
379
|
+
.expect("closure should allocate");
|
|
380
|
+
let rooted_object = runtime
|
|
381
|
+
.insert_object(
|
|
382
|
+
IndexMap::from([("closure".to_string(), Value::Closure(rooted_closure))]),
|
|
383
|
+
ObjectKind::Plain,
|
|
384
|
+
)
|
|
385
|
+
.expect("object should allocate");
|
|
386
|
+
let rooted_array = runtime
|
|
387
|
+
.insert_array(vec![Value::Object(rooted_object)], IndexMap::new())
|
|
388
|
+
.expect("array should allocate");
|
|
389
|
+
|
|
390
|
+
let frame_env = runtime
|
|
391
|
+
.new_env(Some(runtime.globals))
|
|
392
|
+
.expect("frame env should allocate");
|
|
393
|
+
let rooted_cell = runtime
|
|
394
|
+
.insert_cell(Value::Array(rooted_array), true, true)
|
|
395
|
+
.expect("cell should allocate");
|
|
396
|
+
runtime
|
|
397
|
+
.envs
|
|
398
|
+
.get_mut(frame_env)
|
|
399
|
+
.expect("frame env should exist")
|
|
400
|
+
.bindings
|
|
401
|
+
.insert("kept".to_string(), rooted_cell);
|
|
402
|
+
runtime
|
|
403
|
+
.refresh_env_accounting(frame_env)
|
|
404
|
+
.expect("frame env accounting should refresh");
|
|
405
|
+
runtime.frames.push(Frame {
|
|
406
|
+
function_id: program.root,
|
|
407
|
+
ip: 0,
|
|
408
|
+
env: frame_env,
|
|
409
|
+
scope_stack: vec![closure_env],
|
|
410
|
+
stack: vec![Value::Closure(rooted_closure)],
|
|
411
|
+
handlers: vec![ExceptionHandler {
|
|
412
|
+
catch: None,
|
|
413
|
+
finally: None,
|
|
414
|
+
env: closure_env,
|
|
415
|
+
scope_stack_len: 0,
|
|
416
|
+
stack_len: 0,
|
|
417
|
+
}],
|
|
418
|
+
pending_exception: Some(Value::Object(rooted_object)),
|
|
419
|
+
pending_completions: vec![
|
|
420
|
+
CompletionRecord::Return(Value::Array(rooted_array)),
|
|
421
|
+
CompletionRecord::Throw(Value::Closure(rooted_closure)),
|
|
422
|
+
],
|
|
423
|
+
active_finally: Vec::new(),
|
|
424
|
+
async_promise: None,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
let garbage_env = runtime.new_env(None).expect("garbage env should allocate");
|
|
428
|
+
let garbage_left = runtime
|
|
429
|
+
.insert_object(IndexMap::new(), ObjectKind::Plain)
|
|
430
|
+
.expect("garbage object should allocate");
|
|
431
|
+
let garbage_right = runtime
|
|
432
|
+
.insert_object(IndexMap::new(), ObjectKind::Plain)
|
|
433
|
+
.expect("garbage object should allocate");
|
|
434
|
+
let garbage_array = runtime
|
|
435
|
+
.insert_array(vec![Value::Object(garbage_left)], IndexMap::new())
|
|
436
|
+
.expect("garbage array should allocate");
|
|
437
|
+
let garbage_closure = runtime
|
|
438
|
+
.insert_closure(program.root, garbage_env)
|
|
439
|
+
.expect("garbage closure should allocate");
|
|
440
|
+
runtime
|
|
441
|
+
.set_property(
|
|
442
|
+
Value::Object(garbage_left),
|
|
443
|
+
Value::String("peer".to_string()),
|
|
444
|
+
Value::Object(garbage_right),
|
|
445
|
+
)
|
|
446
|
+
.expect("left cycle should update");
|
|
447
|
+
runtime
|
|
448
|
+
.set_property(
|
|
449
|
+
Value::Object(garbage_right),
|
|
450
|
+
Value::String("peer".to_string()),
|
|
451
|
+
Value::Object(garbage_left),
|
|
452
|
+
)
|
|
453
|
+
.expect("right cycle should update");
|
|
454
|
+
runtime
|
|
455
|
+
.set_property(
|
|
456
|
+
Value::Object(garbage_right),
|
|
457
|
+
Value::String("items".to_string()),
|
|
458
|
+
Value::Array(garbage_array),
|
|
459
|
+
)
|
|
460
|
+
.expect("array cycle should update");
|
|
461
|
+
runtime
|
|
462
|
+
.set_property(
|
|
463
|
+
Value::Object(garbage_left),
|
|
464
|
+
Value::String("closure".to_string()),
|
|
465
|
+
Value::Closure(garbage_closure),
|
|
466
|
+
)
|
|
467
|
+
.expect("closure cycle should update");
|
|
468
|
+
let garbage_cell = runtime
|
|
469
|
+
.insert_cell(Value::Object(garbage_left), true, true)
|
|
470
|
+
.expect("garbage cell should allocate");
|
|
471
|
+
runtime
|
|
472
|
+
.envs
|
|
473
|
+
.get_mut(garbage_env)
|
|
474
|
+
.expect("garbage env should exist")
|
|
475
|
+
.bindings
|
|
476
|
+
.insert("garbage".to_string(), garbage_cell);
|
|
477
|
+
runtime
|
|
478
|
+
.refresh_env_accounting(garbage_env)
|
|
479
|
+
.expect("garbage env accounting should refresh");
|
|
480
|
+
|
|
481
|
+
let stats = runtime.collect_garbage().expect("gc should succeed");
|
|
482
|
+
|
|
483
|
+
assert!(stats.reclaimed_allocations >= 5);
|
|
484
|
+
assert!(stats.reclaimed_bytes > 0);
|
|
485
|
+
assert!(runtime.envs.contains_key(frame_env));
|
|
486
|
+
assert!(runtime.envs.contains_key(closure_env));
|
|
487
|
+
assert!(runtime.cells.contains_key(rooted_cell));
|
|
488
|
+
assert!(runtime.objects.contains_key(rooted_object));
|
|
489
|
+
assert!(runtime.arrays.contains_key(rooted_array));
|
|
490
|
+
assert!(runtime.closures.contains_key(rooted_closure));
|
|
491
|
+
|
|
492
|
+
assert!(!runtime.envs.contains_key(garbage_env));
|
|
493
|
+
assert!(!runtime.cells.contains_key(garbage_cell));
|
|
494
|
+
assert!(!runtime.objects.contains_key(garbage_left));
|
|
495
|
+
assert!(!runtime.objects.contains_key(garbage_right));
|
|
496
|
+
assert!(!runtime.arrays.contains_key(garbage_array));
|
|
497
|
+
assert!(!runtime.closures.contains_key(garbage_closure));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#[test]
|
|
501
|
+
fn garbage_collection_reclaims_cyclic_garbage_under_execution_pressure() {
|
|
502
|
+
let program = compile(
|
|
503
|
+
r#"
|
|
504
|
+
let total = 0;
|
|
505
|
+
for (let i = 0; i < 120; i += 1) {
|
|
506
|
+
let left = {};
|
|
507
|
+
let right = {};
|
|
508
|
+
left.peer = right;
|
|
509
|
+
right.peer = left;
|
|
510
|
+
total += i;
|
|
511
|
+
}
|
|
512
|
+
total;
|
|
513
|
+
"#,
|
|
514
|
+
)
|
|
515
|
+
.expect("source should compile");
|
|
516
|
+
let value = execute(
|
|
517
|
+
&program,
|
|
518
|
+
ExecutionOptions {
|
|
519
|
+
limits: RuntimeLimits {
|
|
520
|
+
heap_limit_bytes: 24 * 1024,
|
|
521
|
+
allocation_budget: 256,
|
|
522
|
+
..RuntimeLimits::default()
|
|
523
|
+
},
|
|
524
|
+
cancellation_token: None,
|
|
525
|
+
..ExecutionOptions::default()
|
|
526
|
+
},
|
|
527
|
+
)
|
|
528
|
+
.expect("cyclic garbage should be reclaimed");
|
|
529
|
+
assert_eq!(
|
|
530
|
+
value,
|
|
531
|
+
StructuredValue::Number(StructuredNumber::Finite(7140.0))
|
|
532
|
+
);
|
|
533
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
use crate::{compile, limits::RuntimeLimits};
|
|
3
|
+
|
|
4
|
+
mod async_host;
|
|
5
|
+
mod collections;
|
|
6
|
+
mod diagnostics;
|
|
7
|
+
mod exceptions;
|
|
8
|
+
mod execution;
|
|
9
|
+
mod gc;
|
|
10
|
+
mod serialization;
|
|
11
|
+
|
|
12
|
+
fn test_function(code: Vec<Instruction>) -> FunctionPrototype {
|
|
13
|
+
FunctionPrototype {
|
|
14
|
+
name: None,
|
|
15
|
+
length: 0,
|
|
16
|
+
display_source: String::new(),
|
|
17
|
+
params: Vec::new(),
|
|
18
|
+
rest: None,
|
|
19
|
+
code,
|
|
20
|
+
is_async: false,
|
|
21
|
+
is_arrow: false,
|
|
22
|
+
span: SourceSpan::new(0, 0),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fn invalid_program(code: Vec<Instruction>) -> BytecodeProgram {
|
|
27
|
+
BytecodeProgram {
|
|
28
|
+
functions: vec![test_function(code)],
|
|
29
|
+
root: 0,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn number(value: f64) -> StructuredValue {
|
|
34
|
+
StructuredValue::Number(StructuredNumber::Finite(value))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn options_with_capabilities(capabilities: &[&str]) -> ExecutionOptions {
|
|
38
|
+
ExecutionOptions {
|
|
39
|
+
capabilities: capabilities.iter().map(|value| (*value).to_string()).collect(),
|
|
40
|
+
..ExecutionOptions::default()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn suspend(source: &str, capabilities: &[&str]) -> Suspension {
|
|
45
|
+
let program = compile(source).expect("source should compile");
|
|
46
|
+
match start(&program, options_with_capabilities(capabilities)).expect("execution should suspend")
|
|
47
|
+
{
|
|
48
|
+
ExecutionStep::Suspended(suspension) => *suspension,
|
|
49
|
+
other => panic!("expected suspension, got {other:?}"),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn run(source: &str) -> StructuredValue {
|
|
54
|
+
let program = compile(source).expect("source should compile");
|
|
55
|
+
execute(&program, ExecutionOptions::default()).expect("program should run")
|
|
56
|
+
}
|