@hew-lang/sandbox-vm 0.9.3 → 0.10.0-rc1
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/dist/interpreter/run-program.js +1 -0
- package/dist/interpreter/strings.d.ts +6 -0
- package/dist/interpreter/strings.js +8 -0
- package/dist/interpreter/types.d.ts +1 -1
- package/dist/interpreter/v1/encoding-storage.d.ts +33 -0
- package/dist/interpreter/v1/encoding-storage.js +113 -0
- package/dist/interpreter/v1/encoding-values.js +28 -18
- package/dist/interpreter/v1/exec.js +1368 -90
- package/dist/interpreter/v1/instructions.generated.d.ts +94 -6
- package/dist/interpreter/v1/instructions.generated.js +1 -0
- package/dist/interpreter/v1/managed-budget.d.ts +88 -0
- package/dist/interpreter/v1/managed-budget.js +556 -0
- package/dist/interpreter/v1/package-graph.js +77 -2
- package/dist/interpreter/v1/package-validator.generated.js +20572 -15516
- package/dist/interpreter/v1/package.d.ts +4 -2
- package/dist/interpreter/v1/package.js +6 -0
- package/dist/interpreter/v1/pipes.d.ts +24 -1
- package/dist/interpreter/v1/pipes.js +91 -8
- package/dist/interpreter/v1/shims.d.ts +1 -0
- package/dist/interpreter/v1/shims.js +54 -28
- package/dist/interpreter/v1/validate.js +2 -1
- package/dist/interpreter/v1/value-operations.d.ts +3 -1
- package/dist/interpreter/v1/value-operations.js +23 -10
- package/dist/interpreter/values.d.ts +36 -11
- package/dist/interpreter/values.js +118 -6
- package/package.json +4 -4
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { TermV1, VariantShape, RuntimeVariantRole } from "./instructions.generated.js";
|
|
2
|
-
export type { Own, BoundaryDecision, TrapName, SpanV1, BoundaryOperand, Edge, ValueDef, CallResult, OpV1, OpName, TermV1, TermName, SwitchArm, CheckedFailure, RequestShapes, ActorProtocol, ActorOperation, RuntimeFamilyEntry, AggregateShape, VariantCase, VariantShape, RuntimeVariantRole, ExternEntry, PlaceDecl, ValueCapability, ValueComponentCapabilities, ClosureShape, VtableShape, FunctionV1, BlockV1, EntryPlan, ActorShape, SupervisorShape, PackageV1, Resource, StructuralRecipe, } from "./instructions.generated.js";
|
|
1
|
+
import type { TermV1, BlockV1, OpV1, VariantShape, RuntimeVariantRole } from "./instructions.generated.js";
|
|
2
|
+
export type { Own, BoundaryDecision, TrapName, SpanV1, BoundaryOperand, Edge, ValueDef, CallResult, OpV1, OpName, TermV1, TermName, SwitchArm, CheckedFailure, RequestShapes, ActorProtocol, ActorOperation, RuntimeFamilyEntry, AggregateShape, VariantCase, VariantShape, RuntimeVariantRole, ExternEntry, PlaceDecl, ValueCapability, ValueComponentCapabilities, ClosureShape, VtableShape, FunctionV1, BlockV1, EntryPlan, ActorShape, SupervisorShape, PackageV1, Resource, StructuralRecipe, ManagedType, } from "./instructions.generated.js";
|
|
3
3
|
export type Operand = number;
|
|
4
4
|
export type CallShape = Pick<Extract<TermV1, {
|
|
5
5
|
op: "call";
|
|
6
6
|
}>, "args" | "result" | "normal" | "unwind" | "request_shapes">;
|
|
7
7
|
export declare function runtimeTag(shape: VariantShape, role: RuntimeVariantRole): number;
|
|
8
|
+
/** Static inventory includes a checked operation; execution follows its edge. */
|
|
9
|
+
export declare function staticOperations(block: BlockV1): Iterable<OpV1>;
|
|
@@ -7,3 +7,9 @@ export function runtimeTag(shape, role) {
|
|
|
7
7
|
throw new Error(`variant shape ${shape.id} has no checked ${role} tag`);
|
|
8
8
|
return tag;
|
|
9
9
|
}
|
|
10
|
+
/** Static inventory includes a checked operation; execution follows its edge. */
|
|
11
|
+
export function* staticOperations(block) {
|
|
12
|
+
yield* block.ops;
|
|
13
|
+
if (block.term.op === "materialize")
|
|
14
|
+
yield block.term.operation;
|
|
15
|
+
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { type VmValue } from "../values.js";
|
|
2
2
|
interface PendingRead {
|
|
3
|
-
complete(value: VmValue | null, fault: string | null): void;
|
|
3
|
+
complete(value: VmValue | null, fault: string | null, released?: () => void): void;
|
|
4
|
+
}
|
|
5
|
+
interface Envelope {
|
|
6
|
+
value: VmValue;
|
|
7
|
+
release?: () => void;
|
|
8
|
+
}
|
|
9
|
+
interface Receiver {
|
|
10
|
+
owner: object;
|
|
11
|
+
acquire(envelope: Envelope): () => void;
|
|
12
|
+
refused(): void;
|
|
4
13
|
}
|
|
5
14
|
export declare class Pipes {
|
|
6
15
|
private readonly pairs;
|
|
7
16
|
private readonly halves;
|
|
8
17
|
private next;
|
|
18
|
+
private managedReceivers;
|
|
19
|
+
get hasManagedReceivers(): boolean;
|
|
9
20
|
create(id: string, capacity: number): VmValue;
|
|
10
21
|
freePair(pair: VmValue): void;
|
|
11
22
|
extract(pair: VmValue, kind: "sink" | "stream"): VmValue;
|
|
@@ -14,6 +25,11 @@ export declare class Pipes {
|
|
|
14
25
|
cloneSink(value: VmValue): VmValue;
|
|
15
26
|
isClosed(value: VmValue): boolean;
|
|
16
27
|
close(value: VmValue, fault?: string | null): VmValue[];
|
|
28
|
+
finishClose(value: VmValue): void;
|
|
29
|
+
/** Called only after ownership acquisition succeeds. The receiving half
|
|
30
|
+
* carries queued envelopes; producer aliases never acquire them. */
|
|
31
|
+
bind(value: VmValue, receiver?: Receiver): void;
|
|
32
|
+
private admit;
|
|
17
33
|
peerClosed(value: VmValue): boolean;
|
|
18
34
|
readable(value: VmValue): boolean;
|
|
19
35
|
observe(value: VmValue, changed: () => void): () => void;
|
|
@@ -21,5 +37,12 @@ export declare class Pipes {
|
|
|
21
37
|
receive(value: VmValue, park: boolean, complete: PendingRead["complete"]): () => void;
|
|
22
38
|
private flush;
|
|
23
39
|
private notify;
|
|
40
|
+
/** Only the unique receiving half owns admitted typed envelopes. */
|
|
41
|
+
managedRetained(value: VmValue): {
|
|
42
|
+
identity: object;
|
|
43
|
+
queued: readonly {
|
|
44
|
+
value: VmValue;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
24
47
|
}
|
|
25
48
|
export {};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { HeapExceeded } from "./managed-budget.js";
|
|
1
2
|
/// The VM's one bounded pipe substrate. Selection observes readiness; only
|
|
2
3
|
/// the winning receive removes an item. Values transfer without a snapshot.
|
|
3
4
|
export class Pipes {
|
|
4
5
|
pairs = new Map();
|
|
5
6
|
halves = new Map();
|
|
6
7
|
next = 0;
|
|
8
|
+
managedReceivers = 0;
|
|
9
|
+
get hasManagedReceivers() {
|
|
10
|
+
return this.managedReceivers !== 0;
|
|
11
|
+
}
|
|
7
12
|
create(id, capacity) {
|
|
8
13
|
this.pairs.set(id, {
|
|
9
14
|
capacity,
|
|
@@ -65,11 +70,58 @@ export class Pipes {
|
|
|
65
70
|
}
|
|
66
71
|
else {
|
|
67
72
|
half.pipe.readerOpen = false;
|
|
68
|
-
|
|
73
|
+
if (half.pipe.receiver)
|
|
74
|
+
this.managedReceivers--;
|
|
75
|
+
half.pipe.receiver = undefined;
|
|
76
|
+
for (const item of half.pipe.queue.splice(0)) {
|
|
77
|
+
discarded.push(item.value);
|
|
78
|
+
if (item.release)
|
|
79
|
+
(half.retiring ??= []).push(item.release);
|
|
80
|
+
}
|
|
69
81
|
}
|
|
70
82
|
this.flush(half.pipe);
|
|
71
83
|
return discarded;
|
|
72
84
|
}
|
|
85
|
+
finishClose(value) {
|
|
86
|
+
if (value.kind !== "stream" && value.kind !== "sink")
|
|
87
|
+
return;
|
|
88
|
+
for (const release of this.halves
|
|
89
|
+
.get(value.channelId)
|
|
90
|
+
?.retiring?.splice(0) ?? [])
|
|
91
|
+
release();
|
|
92
|
+
}
|
|
93
|
+
/** Called only after ownership acquisition succeeds. The receiving half
|
|
94
|
+
* carries queued envelopes; producer aliases never acquire them. */
|
|
95
|
+
bind(value, receiver) {
|
|
96
|
+
const half = this.get(value);
|
|
97
|
+
if (half.kind !== "stream" || half.pipe.receiver?.owner === receiver?.owner)
|
|
98
|
+
return;
|
|
99
|
+
this.managedReceivers += Number(!!receiver) - Number(!!half.pipe.receiver);
|
|
100
|
+
half.pipe.receiver = receiver;
|
|
101
|
+
for (const envelope of half.pipe.queue) {
|
|
102
|
+
const release = receiver?.acquire(envelope);
|
|
103
|
+
envelope.release?.();
|
|
104
|
+
envelope.release = release;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
admit(pipe, value) {
|
|
108
|
+
const envelope = { value };
|
|
109
|
+
try {
|
|
110
|
+
envelope.release = pipe.receiver?.acquire(envelope);
|
|
111
|
+
return envelope;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!(error instanceof HeapExceeded))
|
|
115
|
+
throw error;
|
|
116
|
+
// Acquisition is atomic. A refused recipient follows its existing
|
|
117
|
+
// checked failure path while the producer still owns the item.
|
|
118
|
+
pipe.readerOpen = false;
|
|
119
|
+
pipe.fault = "actor heap limit exceeded";
|
|
120
|
+
pipe.receiver?.refused();
|
|
121
|
+
this.flush(pipe);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
73
125
|
peerClosed(value) {
|
|
74
126
|
return !this.get(value).pipe.readerOpen;
|
|
75
127
|
}
|
|
@@ -94,11 +146,21 @@ export class Pipes {
|
|
|
94
146
|
return () => { };
|
|
95
147
|
}
|
|
96
148
|
if (pipe.reading.length > 0) {
|
|
97
|
-
|
|
149
|
+
const envelope = this.admit(pipe, item);
|
|
150
|
+
if (!envelope) {
|
|
151
|
+
complete(1);
|
|
152
|
+
return () => { };
|
|
153
|
+
}
|
|
154
|
+
pipe.reading.shift().complete(item, null, envelope.release);
|
|
98
155
|
complete(0);
|
|
99
156
|
}
|
|
100
157
|
else if (pipe.queue.length < pipe.capacity) {
|
|
101
|
-
|
|
158
|
+
const envelope = this.admit(pipe, item);
|
|
159
|
+
if (!envelope) {
|
|
160
|
+
complete(1);
|
|
161
|
+
return () => { };
|
|
162
|
+
}
|
|
163
|
+
pipe.queue.push(envelope);
|
|
102
164
|
complete(0);
|
|
103
165
|
}
|
|
104
166
|
else if (!park) {
|
|
@@ -120,13 +182,21 @@ export class Pipes {
|
|
|
120
182
|
receive(value, park, complete) {
|
|
121
183
|
const pipe = this.get(value).pipe;
|
|
122
184
|
if (pipe.queue.length > 0) {
|
|
123
|
-
|
|
185
|
+
const envelope = pipe.queue.shift();
|
|
186
|
+
complete(envelope.value, null, envelope.release);
|
|
124
187
|
this.flush(pipe);
|
|
125
188
|
}
|
|
126
189
|
else if (pipe.sending.length > 0) {
|
|
127
190
|
const sender = pipe.sending.shift();
|
|
128
|
-
|
|
129
|
-
|
|
191
|
+
const envelope = this.admit(pipe, sender.value);
|
|
192
|
+
if (envelope) {
|
|
193
|
+
complete(sender.value, null, envelope.release);
|
|
194
|
+
sender.complete(0);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
complete(null, pipe.fault);
|
|
198
|
+
sender.complete(1);
|
|
199
|
+
}
|
|
130
200
|
}
|
|
131
201
|
else if (pipe.fault !== null ||
|
|
132
202
|
pipe.sinks === 0 ||
|
|
@@ -155,11 +225,16 @@ export class Pipes {
|
|
|
155
225
|
while (pipe.sending.length > 0 &&
|
|
156
226
|
(pipe.reading.length > 0 || pipe.queue.length < pipe.capacity)) {
|
|
157
227
|
const sender = pipe.sending.shift();
|
|
228
|
+
const envelope = this.admit(pipe, sender.value);
|
|
229
|
+
if (!envelope) {
|
|
230
|
+
sender.complete(1);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
158
233
|
const reader = pipe.reading.shift();
|
|
159
234
|
if (reader)
|
|
160
|
-
reader.complete(sender.value, null);
|
|
235
|
+
reader.complete(sender.value, null, envelope.release);
|
|
161
236
|
else
|
|
162
|
-
pipe.queue.push(
|
|
237
|
+
pipe.queue.push(envelope);
|
|
163
238
|
sender.complete(0);
|
|
164
239
|
}
|
|
165
240
|
}
|
|
@@ -175,4 +250,12 @@ export class Pipes {
|
|
|
175
250
|
for (const observer of [...pipe.observers])
|
|
176
251
|
observer();
|
|
177
252
|
}
|
|
253
|
+
/** Only the unique receiving half owns admitted typed envelopes. */
|
|
254
|
+
managedRetained(value) {
|
|
255
|
+
const half = this.get(value);
|
|
256
|
+
return {
|
|
257
|
+
identity: half.kind === "stream" ? half.pipe : half,
|
|
258
|
+
queued: half.open && half.kind === "stream" ? half.pipe.queue : [],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
178
261
|
}
|
|
@@ -27,6 +27,7 @@ export interface ShimHost {
|
|
|
27
27
|
writeStdout(text: string): void;
|
|
28
28
|
writeStderr(text: string): void;
|
|
29
29
|
prng: Mt19937;
|
|
30
|
+
arenaInstanceId(): bigint;
|
|
30
31
|
regexPatterns: readonly string[];
|
|
31
32
|
enumValue(shape: VariantShape, role: RuntimeVariantRole, payload: VmValue[]): VmValue;
|
|
32
33
|
}
|
|
@@ -4,7 +4,7 @@ import { executeEncoding } from "./encoding-values.js";
|
|
|
4
4
|
import { charProperty, charCase } from "../unicode.js";
|
|
5
5
|
/// Runtime implementations and their host execution registration. Admission and
|
|
6
6
|
/// execution consume the same ordinary, resumable or refused disposition.
|
|
7
|
-
import { UNIT, cloneValue, compareUtf8, stringValue, renderStdout, } from "../values.js";
|
|
7
|
+
import { UNIT, byteVectorValue, clearByteVector, mutableVector, sliceByteVector, cloneValue, compareUtf8, stringValue, renderStdout, } from "../values.js";
|
|
8
8
|
import { seededMt } from "./mt19937.js";
|
|
9
9
|
/// A language-visible fault raised inside a shim. The executor routes it to the
|
|
10
10
|
/// call's `unwind` edge, or ends the program when the call names none.
|
|
@@ -122,7 +122,6 @@ export function resolveRuntime(entry) {
|
|
|
122
122
|
});
|
|
123
123
|
case "GetBorrow":
|
|
124
124
|
case "TakeFirst":
|
|
125
|
-
case "Append":
|
|
126
125
|
return refused(`Vector::${entry.detail}`);
|
|
127
126
|
default:
|
|
128
127
|
return ordinary(VECTOR_SHIMS[entry.detail]);
|
|
@@ -295,13 +294,19 @@ const UNIT_FAMILIES = {
|
|
|
295
294
|
host.writeStderr(text(args, 0));
|
|
296
295
|
return UNIT;
|
|
297
296
|
},
|
|
298
|
-
StringToBytes: (_host, args) => (
|
|
299
|
-
kind: "vector",
|
|
300
|
-
elementType: "u8",
|
|
301
|
-
items: [...indexed(args, 0).encoded()].map((byte) => int(BigInt(byte))),
|
|
302
|
-
}),
|
|
297
|
+
StringToBytes: (_host, args) => byteVectorValue([...indexed(args, 0).encoded()].map((byte) => int(BigInt(byte)))),
|
|
303
298
|
StringToUppercase: (_host, args) => str(text(args, 0).toUpperCase()),
|
|
304
299
|
StringToLowercase: (_host, args) => str(text(args, 0).toLowerCase()),
|
|
300
|
+
StreamPipe: (host, args) => {
|
|
301
|
+
const pair = host.newPipe(Number(integer(args, 0)));
|
|
302
|
+
const fields = [
|
|
303
|
+
host.pipes.extract(pair, "sink"),
|
|
304
|
+
host.pipes.extract(pair, "stream"),
|
|
305
|
+
bool(true),
|
|
306
|
+
];
|
|
307
|
+
host.pipes.freePair(pair);
|
|
308
|
+
return { kind: "record", typeId: "", fields };
|
|
309
|
+
},
|
|
305
310
|
StreamPairSink: (host, args) => host.pipes.extract(arg(args, 0), "sink"),
|
|
306
311
|
StreamPairStream: (host, args) => host.pipes.extract(arg(args, 0), "stream"),
|
|
307
312
|
StreamFromGenerator: (host, args) => host.generatorStream(arg(args, 0)),
|
|
@@ -440,7 +445,7 @@ const UNIT_FAMILIES = {
|
|
|
440
445
|
// TextDecoder replaces each maximal invalid subsequence with U+FFFD, as
|
|
441
446
|
// native lossy decoding does.
|
|
442
447
|
BytesDecodeUtf8Lossy: (_host, args) => str(decodeUtf8(arg(args, 0))),
|
|
443
|
-
BytesNew: () => (
|
|
448
|
+
BytesNew: () => byteVectorValue([]),
|
|
444
449
|
BytesIsEmpty: (_host, args) => bool(vec(args, 0).items.length === 0),
|
|
445
450
|
BytesLen: (_host, args) => int(BigInt(vec(args, 0).items.length)),
|
|
446
451
|
BytesContains: (_host, args) => {
|
|
@@ -455,20 +460,19 @@ const UNIT_FAMILIES = {
|
|
|
455
460
|
}
|
|
456
461
|
return cloneValue(items[at]);
|
|
457
462
|
},
|
|
458
|
-
//
|
|
459
|
-
// a copy is observably the same.
|
|
463
|
+
// Nonempty views retain the exact original allocation and payload capacity.
|
|
460
464
|
BytesSlice: (_host, args) => {
|
|
461
465
|
const items = vec(args, 0).items;
|
|
462
466
|
const start = index(args, 1);
|
|
463
467
|
const end = index(args, 2);
|
|
464
468
|
checkSliceBounds(start, end, items.length);
|
|
465
|
-
return
|
|
469
|
+
return sliceByteVector(vec(args, 0), start, end);
|
|
466
470
|
},
|
|
467
471
|
BytesSliceFrom: (_host, args) => {
|
|
468
472
|
const items = vec(args, 0).items;
|
|
469
473
|
const start = index(args, 1);
|
|
470
474
|
checkSliceBounds(start, items.length, items.length);
|
|
471
|
-
return
|
|
475
|
+
return sliceByteVector(vec(args, 0), start, items.length);
|
|
472
476
|
},
|
|
473
477
|
BytesConcat: (_host, args) => byteVector([...vec(args, 0).items, ...vec(args, 1).items]),
|
|
474
478
|
BytesStartsWith: (_host, args) => bool(runAt(vec(args, 0).items, vec(args, 1).items, 0)),
|
|
@@ -480,6 +484,7 @@ const UNIT_FAMILIES = {
|
|
|
480
484
|
// A mutating call receives its receiver by `move` and returns it.
|
|
481
485
|
BytesPush: (_host, args) => {
|
|
482
486
|
const target = vec(args, 0);
|
|
487
|
+
mutableVector(target, target.items.length + 1);
|
|
483
488
|
target.items.push(cloneValue(arg(args, 1)));
|
|
484
489
|
return target;
|
|
485
490
|
},
|
|
@@ -489,13 +494,13 @@ const UNIT_FAMILIES = {
|
|
|
489
494
|
if (at < 0 || at >= target.items.length) {
|
|
490
495
|
throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${target.items.length}`);
|
|
491
496
|
}
|
|
497
|
+
mutableVector(target);
|
|
492
498
|
target.items[at] = cloneValue(arg(args, 2));
|
|
493
499
|
return target;
|
|
494
500
|
},
|
|
495
501
|
BytesClear: (_host, args) => {
|
|
496
502
|
const target = vec(args, 0);
|
|
497
|
-
target
|
|
498
|
-
return target;
|
|
503
|
+
return clearByteVector(target);
|
|
499
504
|
},
|
|
500
505
|
BytesGet: (host, args, shape) => {
|
|
501
506
|
if (!shape) {
|
|
@@ -511,7 +516,9 @@ const UNIT_FAMILIES = {
|
|
|
511
516
|
// append, paired with the removed byte for a pop.
|
|
512
517
|
BytesAppend: (_host, args) => {
|
|
513
518
|
const target = vec(args, 0);
|
|
514
|
-
|
|
519
|
+
const added = vec(args, 1).items.map(cloneValue);
|
|
520
|
+
mutableVector(target, target.items.length + added.length);
|
|
521
|
+
target.items.push(...added);
|
|
515
522
|
return target;
|
|
516
523
|
},
|
|
517
524
|
BytesPop: (host, args, shape) => {
|
|
@@ -519,6 +526,8 @@ const UNIT_FAMILIES = {
|
|
|
519
526
|
throw new TypeError("BytesPop names no result shape to build its Option against");
|
|
520
527
|
}
|
|
521
528
|
const target = vec(args, 0);
|
|
529
|
+
if (target.items.length)
|
|
530
|
+
mutableVector(target);
|
|
522
531
|
const last = target.items.pop();
|
|
523
532
|
return {
|
|
524
533
|
kind: "record",
|
|
@@ -645,9 +654,7 @@ const UNIT_FAMILIES = {
|
|
|
645
654
|
ReplyPayloadFree: refused("ReplyPayloadFree"),
|
|
646
655
|
ReplyWait: refused("ReplyWait"),
|
|
647
656
|
SelectFirst: refused("SelectFirst"),
|
|
648
|
-
StreamChunks: refused("StreamChunks"),
|
|
649
657
|
StreamForward: refused("StreamForward"),
|
|
650
|
-
StreamLines: refused("StreamLines"),
|
|
651
658
|
StreamNextLayout: refused("StreamNextLayout"),
|
|
652
659
|
StreamSendLayout: refused("StreamSendLayout"),
|
|
653
660
|
StreamTake: refused("StreamTake"),
|
|
@@ -706,6 +713,7 @@ const VECTOR_SHIMS = {
|
|
|
706
713
|
right >= target.items.length) {
|
|
707
714
|
throw new ShimFault("vector_bounds", "vector swap index out of bounds");
|
|
708
715
|
}
|
|
716
|
+
mutableVector(target);
|
|
709
717
|
[target.items[left], target.items[right]] = [
|
|
710
718
|
target.items[right],
|
|
711
719
|
target.items[left],
|
|
@@ -717,6 +725,7 @@ const VECTOR_SHIMS = {
|
|
|
717
725
|
if (target.items.length === 0) {
|
|
718
726
|
throw new ShimFault("vector_bounds", "pop from empty vector");
|
|
719
727
|
}
|
|
728
|
+
mutableVector(target);
|
|
720
729
|
return {
|
|
721
730
|
kind: "record",
|
|
722
731
|
typeId: "",
|
|
@@ -729,15 +738,26 @@ const VECTOR_SHIMS = {
|
|
|
729
738
|
// ownership decision SIR proved, so it moves in as it arrives.
|
|
730
739
|
Push: (_host, args) => {
|
|
731
740
|
const target = vec(args, 0);
|
|
741
|
+
mutableVector(target, target.items.length + 1);
|
|
732
742
|
target.items.push(arg(args, 1));
|
|
733
743
|
return target;
|
|
734
744
|
},
|
|
745
|
+
Append: (_host, args) => {
|
|
746
|
+
const target = vec(args, 0);
|
|
747
|
+
const added = [...vec(args, 1).items];
|
|
748
|
+
if (added.length) {
|
|
749
|
+
mutableVector(target, target.items.length + added.length, added);
|
|
750
|
+
target.items.push(...added.map(cloneValue));
|
|
751
|
+
}
|
|
752
|
+
return target;
|
|
753
|
+
},
|
|
735
754
|
Remove: (_host, args) => {
|
|
736
755
|
const target = vec(args, 0);
|
|
737
756
|
const at = index(args, 1);
|
|
738
757
|
if (at < 0 || at >= target.items.length) {
|
|
739
758
|
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${target.items.length}`);
|
|
740
759
|
}
|
|
760
|
+
mutableVector(target);
|
|
741
761
|
const [item] = target.items.splice(at, 1);
|
|
742
762
|
return { kind: "record", typeId: "", fields: [target, item] };
|
|
743
763
|
},
|
|
@@ -765,23 +785,25 @@ const VECTOR_SHIMS = {
|
|
|
765
785
|
const start = index(args, 1);
|
|
766
786
|
const end = index(args, 2);
|
|
767
787
|
checkSliceBounds(start, end, items.length);
|
|
768
|
-
return
|
|
769
|
-
kind: "vector",
|
|
770
|
-
elementType: "",
|
|
771
|
-
items: items.slice(start, end).map(cloneValue),
|
|
772
|
-
};
|
|
788
|
+
return vectorSlice(items.slice(start, end));
|
|
773
789
|
},
|
|
774
790
|
SliceFrom: (_host, args) => {
|
|
775
791
|
const items = vec(args, 0).items;
|
|
776
792
|
const start = index(args, 1);
|
|
777
793
|
checkSliceBounds(start, items.length, items.length);
|
|
778
|
-
return
|
|
779
|
-
kind: "vector",
|
|
780
|
-
elementType: "",
|
|
781
|
-
items: items.slice(start).map(cloneValue),
|
|
782
|
-
};
|
|
794
|
+
return vectorSlice(items.slice(start));
|
|
783
795
|
},
|
|
784
796
|
};
|
|
797
|
+
function vectorSlice(items) {
|
|
798
|
+
const result = {
|
|
799
|
+
kind: "vector",
|
|
800
|
+
elementType: "",
|
|
801
|
+
items: [],
|
|
802
|
+
};
|
|
803
|
+
mutableVector(result, items.length);
|
|
804
|
+
result.items.push(...items.map(cloneValue));
|
|
805
|
+
return result;
|
|
806
|
+
}
|
|
785
807
|
function sequenceElement(args) {
|
|
786
808
|
const items = vec(args, 0).items;
|
|
787
809
|
const at = index(args, 1);
|
|
@@ -791,7 +813,7 @@ function sequenceElement(args) {
|
|
|
791
813
|
return items[at];
|
|
792
814
|
}
|
|
793
815
|
function byteVector(items) {
|
|
794
|
-
return
|
|
816
|
+
return byteVectorValue(items.map(cloneValue));
|
|
795
817
|
}
|
|
796
818
|
/// Whether `run` occurs in `items` starting at `at`.
|
|
797
819
|
function runAt(items, run, at) {
|
|
@@ -1092,6 +1114,10 @@ function saturatingI64(value) {
|
|
|
1092
1114
|
}
|
|
1093
1115
|
// ── externs ─────────────────────────────────────────────────────────────────
|
|
1094
1116
|
const EXTERN_SHIMS = {
|
|
1117
|
+
hew_arena_instance_id_new: (host) => ({
|
|
1118
|
+
kind: "i64",
|
|
1119
|
+
value: host.arenaInstanceId(),
|
|
1120
|
+
}),
|
|
1095
1121
|
hew_semaphore_new: (host, args) => host.semaphores.create(integer(args, 0)),
|
|
1096
1122
|
hew_semaphore_clone: (host, args) => host.semaphores.clone(arg(args, 0)),
|
|
1097
1123
|
hew_semaphore_try_acquire: (host, args) => bool(host.semaphores.tryAcquire(arg(args, 0))),
|
|
@@ -294,7 +294,8 @@ export function admitPackage(pkg, bridges = {}) {
|
|
|
294
294
|
};
|
|
295
295
|
if (actor.exit != null || actor.down != null)
|
|
296
296
|
return unavailable("actor.local_observation");
|
|
297
|
-
if (actor.max_heap_bytes != null
|
|
297
|
+
if (actor.max_heap_bytes != null &&
|
|
298
|
+
(actor.state_type == null || !pkg.managed_types?.length))
|
|
298
299
|
return unavailable("actor.heap_limit");
|
|
299
300
|
}
|
|
300
301
|
for (const supervisor of pkg.supervisors ?? []) {
|
|
@@ -12,6 +12,8 @@ export type ValueRequest = {
|
|
|
12
12
|
kind: "release";
|
|
13
13
|
value: VmValue;
|
|
14
14
|
onFault?: VmValue;
|
|
15
|
+
retained?: readonly VmValue[];
|
|
16
|
+
retainedType?: number;
|
|
15
17
|
};
|
|
16
18
|
export type ValueProgram = Generator<ValueRequest, VmValue, VmValue>;
|
|
17
19
|
/** Execute the exact checked plan, including every derived component selection.
|
|
@@ -25,4 +27,4 @@ export declare function sharedOperation(operation: Extract<RuntimeContinuation,
|
|
|
25
27
|
* borrow their owners until every fallible callback has completed. */
|
|
26
28
|
export declare function collectionOperation(pkg: PackageV1, request: Extract<RuntimeContinuation, {
|
|
27
29
|
handler: "collection";
|
|
28
|
-
}>, args: VmValue[], callbacks: number[], shape: number | null | undefined): ValueProgram;
|
|
30
|
+
}>, args: VmValue[], callbacks: number[], shape: number | null | undefined, elementType?: number): ValueProgram;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UNIT, cloneValue, compareUtf8, } from "../values.js";
|
|
1
|
+
import { UNIT, mutableVector, cloneValue, compareUtf8, } from "../values.js";
|
|
2
2
|
import { runtimeTag, } from "./package.js";
|
|
3
3
|
import { ShimFault } from "./shims.js";
|
|
4
4
|
import { actorIdentical } from "./actor-identity.js";
|
|
@@ -324,17 +324,20 @@ export function* sharedOperation(operation, args, owner, typeName, option) {
|
|
|
324
324
|
/** Open-addressed collection operations preserve native callback ordering:
|
|
325
325
|
* growth hashes occupied slots before probing the incoming key. Staged slots
|
|
326
326
|
* borrow their owners until every fallible callback has completed. */
|
|
327
|
-
export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
327
|
+
export function* collectionOperation(pkg, request, args, callbacks, shape, elementType) {
|
|
328
328
|
const { family, operation } = request;
|
|
329
329
|
if (family === "Vector") {
|
|
330
330
|
const receiver = args[0];
|
|
331
331
|
if (receiver?.kind !== "vector")
|
|
332
332
|
throw new Error("vector operation has no vector");
|
|
333
333
|
if (operation === "Clear") {
|
|
334
|
+
mutableVector(receiver);
|
|
334
335
|
const items = receiver.items.splice(0);
|
|
335
336
|
yield {
|
|
336
337
|
kind: "release",
|
|
337
338
|
value: { kind: "vector", elementType: receiver.elementType, items },
|
|
339
|
+
retained: items,
|
|
340
|
+
retainedType: elementType,
|
|
338
341
|
};
|
|
339
342
|
return receiver;
|
|
340
343
|
}
|
|
@@ -344,10 +347,11 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
344
347
|
throw new Error("vector mutation has no index");
|
|
345
348
|
const at = Number(index.value);
|
|
346
349
|
if (at < 0 || at >= receiver.items.length)
|
|
347
|
-
throw new ShimFault("vector_bounds",
|
|
350
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${receiver.items.length}`);
|
|
351
|
+
mutableVector(receiver);
|
|
348
352
|
const old = receiver.items[at];
|
|
349
353
|
receiver.items[at] = args[2];
|
|
350
|
-
yield { kind: "release", value: old };
|
|
354
|
+
yield { kind: "release", value: old, retainedType: elementType };
|
|
351
355
|
return receiver;
|
|
352
356
|
}
|
|
353
357
|
for (const item of receiver.items) {
|
|
@@ -377,6 +381,7 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
377
381
|
receiver.tombstones = new Set();
|
|
378
382
|
yield {
|
|
379
383
|
kind: "release",
|
|
384
|
+
retained: entries.flatMap((entry) => [entry.key, entry.value]),
|
|
380
385
|
value: {
|
|
381
386
|
kind: "vector",
|
|
382
387
|
elementType: "",
|
|
@@ -389,15 +394,22 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
389
394
|
operation === "Values" ||
|
|
390
395
|
operation === "Elements" ||
|
|
391
396
|
operation === "Entries") {
|
|
392
|
-
|
|
397
|
+
const result = {
|
|
393
398
|
kind: "vector",
|
|
394
399
|
elementType: "",
|
|
395
|
-
items: [
|
|
396
|
-
? cloneValue(value)
|
|
397
|
-
: operation === "Entries"
|
|
398
|
-
? tuple(cloneValue(key), cloneValue(value))
|
|
399
|
-
: cloneValue(key)),
|
|
400
|
+
items: [],
|
|
400
401
|
};
|
|
402
|
+
const items = [...receiver.entries.values()].map(({ key, value }) => operation === "Values"
|
|
403
|
+
? cloneValue(value)
|
|
404
|
+
: operation === "Entries"
|
|
405
|
+
? tuple(cloneValue(key), cloneValue(value))
|
|
406
|
+
: cloneValue(key));
|
|
407
|
+
// Native snapshots push into an initially empty Vec, retaining its normal
|
|
408
|
+
// growth capacity. A later semantic clone may use exact live length.
|
|
409
|
+
mutableVector(result, items.length);
|
|
410
|
+
for (const item of items)
|
|
411
|
+
result.items.push(item);
|
|
412
|
+
return result;
|
|
401
413
|
}
|
|
402
414
|
let staged = receiver;
|
|
403
415
|
if (operation === "Insert" &&
|
|
@@ -435,6 +447,7 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
435
447
|
return cloneValue(entry.value);
|
|
436
448
|
}
|
|
437
449
|
if (operation === "Insert") {
|
|
450
|
+
receiver.prepareInsert?.(staged, result.slot, entry?.key ?? args[1], family === "Set" ? UNIT : args[2], family !== "Set" && !entry);
|
|
438
451
|
receiver.entries = staged.entries;
|
|
439
452
|
receiver.capacity = staged.capacity;
|
|
440
453
|
receiver.tombstones = staged.tombstones;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { JsonValue } from "./types.js";
|
|
2
2
|
import { IndexedString } from "./strings.js";
|
|
3
|
-
import type { EncodingNode, EncodingFormat } from "./v1/encoding-values.js";
|
|
4
3
|
export { stringValue } from "./strings.js";
|
|
5
4
|
/** One creator-owned membership list; payload destruction uses the ordinary VM release path. */
|
|
6
5
|
export interface RcOwner {
|
|
@@ -8,6 +7,8 @@ export interface RcOwner {
|
|
|
8
7
|
closed: boolean;
|
|
9
8
|
}
|
|
10
9
|
export interface RcCell {
|
|
10
|
+
managedLease?: import("./v1/managed-budget.js").ManagedLease;
|
|
11
|
+
managedPayload?: number;
|
|
11
12
|
value: VmValue;
|
|
12
13
|
refs: number;
|
|
13
14
|
weak: number;
|
|
@@ -15,11 +16,25 @@ export interface RcCell {
|
|
|
15
16
|
owner: RcOwner | null;
|
|
16
17
|
typeName: string;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
/** The language payload allocation. JS arrays holding view metadata do not
|
|
20
|
+
* determine its capacity or identity. Bytes views retain this complete owner. */
|
|
21
|
+
export interface VectorStorage {
|
|
22
|
+
items: VmValue[];
|
|
23
|
+
capacity: number;
|
|
24
|
+
owners: number;
|
|
25
|
+
bytes: boolean;
|
|
26
|
+
shareOnCopy: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface VmVector {
|
|
29
|
+
kind: "vector";
|
|
30
|
+
elementType: string;
|
|
31
|
+
items: VmValue[];
|
|
32
|
+
storage?: VectorStorage;
|
|
33
|
+
offset?: number;
|
|
34
|
+
closed?: boolean;
|
|
35
|
+
prepareMutation?: (capacity: number, detached: boolean, appended?: readonly VmValue[]) => void;
|
|
36
|
+
}
|
|
37
|
+
export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | import("./v1/encoding-storage.js").EncodingOwner | {
|
|
23
38
|
kind: "rc";
|
|
24
39
|
cell: RcCell | null;
|
|
25
40
|
closed: boolean;
|
|
@@ -86,17 +101,14 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
86
101
|
typeId: string;
|
|
87
102
|
shape?: number;
|
|
88
103
|
fields: VmValue[];
|
|
104
|
+
managedCaptures?: readonly number[];
|
|
89
105
|
} | {
|
|
90
106
|
kind: "enum";
|
|
91
107
|
typeId: string;
|
|
92
108
|
shape: number;
|
|
93
109
|
tag: number;
|
|
94
110
|
payload: VmValue[];
|
|
95
|
-
} |
|
|
96
|
-
kind: "vector";
|
|
97
|
-
elementType: string;
|
|
98
|
-
items: VmValue[];
|
|
99
|
-
}
|
|
111
|
+
} | VmVector
|
|
100
112
|
/** A map or set owns its occupied slots. Selected Hash/Eq operations probe
|
|
101
113
|
* the table; tombstones preserve collision chains across removal. */
|
|
102
114
|
| {
|
|
@@ -107,6 +119,9 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
107
119
|
}>;
|
|
108
120
|
capacity?: number;
|
|
109
121
|
tombstones?: Set<number>;
|
|
122
|
+
prepareInsert?: (staged: Extract<VmValue, {
|
|
123
|
+
kind: "map";
|
|
124
|
+
}>, slot: string, key: VmValue, value: VmValue, copyKey: boolean) => void;
|
|
110
125
|
}
|
|
111
126
|
/** A trait object: the vtable the checker selected, and the value it wraps.
|
|
112
127
|
* A `dyn.call` resolves its method through `vtable`, never through the
|
|
@@ -115,6 +130,7 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
115
130
|
kind: "dyn";
|
|
116
131
|
vtable: number;
|
|
117
132
|
value: VmValue;
|
|
133
|
+
managedPayload?: number;
|
|
118
134
|
}
|
|
119
135
|
/** A first-class function reference materialised by `const.function`.
|
|
120
136
|
* `id` is the bytecode function id (e.g. `"fn:my_handler"`).
|
|
@@ -135,10 +151,19 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
135
151
|
}>;
|
|
136
152
|
specialization: number | null;
|
|
137
153
|
environment: VmValue;
|
|
154
|
+
managedCaptures?: readonly number[];
|
|
138
155
|
};
|
|
139
156
|
export declare const UNIT: VmValue;
|
|
140
157
|
export declare function compareUtf8(a: string, b: string): number;
|
|
141
158
|
export declare function cloneValue(value: VmValue): VmValue;
|
|
159
|
+
export declare function vectorStorage(value: VmVector): VectorStorage;
|
|
160
|
+
export declare function byteVectorValue(items: VmValue[]): VmVector;
|
|
161
|
+
export declare function sliceByteVector(value: VmVector, start: number, end: number): VmVector;
|
|
162
|
+
/** Establish uniqueness before any mutation, preserving native capacity rules. */
|
|
163
|
+
export declare function mutableVector(value: VmVector, needed?: number, appended?: readonly VmValue[]): VmVector;
|
|
164
|
+
/** Consume one handle; only the last backing owner releases its elements. */
|
|
165
|
+
export declare function releaseVector(value: VmVector): VmValue[];
|
|
166
|
+
export declare function clearByteVector(value: VmVector): VmVector;
|
|
142
167
|
export declare function valueFromLiteral(value: JsonValue): VmValue;
|
|
143
168
|
export declare function renderF64(value: number): string;
|
|
144
169
|
export declare function renderStdout(value: VmValue): string;
|