@hew-lang/sandbox-vm 0.6.0-rc7 → 0.6.1
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/README.md +6 -0
- package/cli/test-runner.mjs +84 -0
- package/dist/interpreter/parity-runner.js +9 -0
- package/dist/interpreter/run-program.d.ts +3 -2
- package/dist/interpreter/run-program.js +6 -4
- package/dist/interpreter/trace.d.ts +1 -0
- package/dist/interpreter/trace.js +4 -0
- package/dist/interpreter/types.d.ts +4 -1
- package/dist/interpreter/v1/codecs.d.ts +9 -0
- package/dist/interpreter/v1/codecs.js +266 -0
- package/dist/interpreter/v1/exec.js +392 -47
- package/dist/interpreter/v1/package.d.ts +45 -12
- package/dist/interpreter/v1/shims.js +292 -19
- package/dist/interpreter/v1/validate.js +33 -4
- package/dist/interpreter/v1/value-operations.js +94 -14
- package/dist/interpreter/values.d.ts +6 -1
- package/dist/interpreter/values.js +17 -3
- package/dist/interpreter/worker.js +22 -4
- package/package.json +8 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type Own = "none" | "owned" | "guaranteed";
|
|
2
2
|
export type BoundaryDecision = "borrow" | "borrow_mut" | "copy" | "move" | "snapshot";
|
|
3
|
-
export type TrapName = "integer_overflow" | "divide_by_zero" | "shift_out_of_range" | "vector_bounds";
|
|
3
|
+
export type TrapName = "integer_overflow" | "divide_by_zero" | "shift_out_of_range" | "vector_bounds" | "actor_refused";
|
|
4
4
|
export interface SpanV1 {
|
|
5
5
|
start: number;
|
|
6
6
|
end: number;
|
|
@@ -34,7 +34,7 @@ export interface VariantShape {
|
|
|
34
34
|
cases: VariantCase[];
|
|
35
35
|
runtime_tags?: Partial<Record<RuntimeVariantRole, number>>;
|
|
36
36
|
}
|
|
37
|
-
export type RuntimeVariantRole = "OptionSome" | "OptionNone" | "ResultOk" | "ResultErr" | "ActorErrorRejected" | "ActorErrorFailed" | "ActorErrorTrapped" | "ActorErrorDead" | "ActorErrorTimedOut" | "ActorErrorNodeNotRunning" | "ActorErrorRoutingFailed" | "ActorErrorEncodeFailed" | "ActorErrorConnectionDropped" | "ActorErrorPartition" | "SendErrorFull" | "SendErrorClosed" | "SendErrorPartition" | "SendErrorStaleRef" | "SendErrorLocalShutdown" | "SendErrorCancelled" | "SendErrorVersionMismatch" | "
|
|
37
|
+
export type RuntimeVariantRole = "OptionSome" | "OptionNone" | "ResultOk" | "ResultErr" | "ActorErrorRejected" | "ActorErrorFailed" | "ActorErrorTrapped" | "ActorErrorRefused" | "CallerActor" | "CallerTask" | "CallerTimer" | "CallerNode" | "ActorErrorDead" | "ActorErrorTimedOut" | "ActorErrorNodeNotRunning" | "ActorErrorRoutingFailed" | "ActorErrorEncodeFailed" | "ActorErrorConnectionDropped" | "ActorErrorPartition" | "SendErrorFull" | "SendErrorClosed" | "SendErrorPartition" | "SendErrorStaleRef" | "SendErrorLocalShutdown" | "SendErrorCancelled" | "SendErrorVersionMismatch" | "SendErrorBackpressure" | "SendErrorDead" | "SendErrorWriteTimedOut" | "DeliveryAccepted" | "DeliveryDiscarded" | "LinkErrorNoContext";
|
|
38
38
|
export declare function runtimeTag(shape: VariantShape, role: RuntimeVariantRole): number;
|
|
39
39
|
export interface RuntimeFamilyEntry {
|
|
40
40
|
id: number;
|
|
@@ -44,6 +44,7 @@ export interface RuntimeFamilyEntry {
|
|
|
44
44
|
export interface ExternEntry {
|
|
45
45
|
id: number;
|
|
46
46
|
symbol: string;
|
|
47
|
+
native_capability?: string;
|
|
47
48
|
}
|
|
48
49
|
export type PlaceDecl = {
|
|
49
50
|
id: number;
|
|
@@ -75,15 +76,27 @@ export type PlaceDecl = {
|
|
|
75
76
|
};
|
|
76
77
|
export interface ValueCapability {
|
|
77
78
|
id: number;
|
|
78
|
-
capability: "Eq" | "Hash";
|
|
79
|
+
capability: "Eq" | "Hash" | "Less" | "LessEqual" | "Greater" | "GreaterEqual";
|
|
79
80
|
ty: string;
|
|
80
81
|
callable?: number;
|
|
81
|
-
components?:
|
|
82
|
-
variants?:
|
|
82
|
+
components?: ValueComponentCapabilities[];
|
|
83
|
+
variants?: ValueComponentCapabilities[][];
|
|
84
|
+
}
|
|
85
|
+
export interface ValueComponentCapabilities {
|
|
86
|
+
selected: number;
|
|
87
|
+
ordering?: {
|
|
88
|
+
less: number;
|
|
89
|
+
less_equal: number;
|
|
90
|
+
greater: number;
|
|
91
|
+
greater_equal: number;
|
|
92
|
+
};
|
|
83
93
|
}
|
|
84
94
|
export interface ClosureShape {
|
|
85
95
|
id: number;
|
|
86
|
-
|
|
96
|
+
entries: Array<{
|
|
97
|
+
body: number;
|
|
98
|
+
specialization: number | null;
|
|
99
|
+
}>;
|
|
87
100
|
fields: number;
|
|
88
101
|
}
|
|
89
102
|
export interface VtableShape {
|
|
@@ -93,6 +106,7 @@ export interface VtableShape {
|
|
|
93
106
|
method: string;
|
|
94
107
|
callee: number;
|
|
95
108
|
receiver: BoundaryDecision;
|
|
109
|
+
receiver_handback?: boolean;
|
|
96
110
|
}>;
|
|
97
111
|
}
|
|
98
112
|
export interface FunctionV1 {
|
|
@@ -115,6 +129,9 @@ interface OpBase {
|
|
|
115
129
|
span?: SpanV1 | null;
|
|
116
130
|
}
|
|
117
131
|
export type OpV1 = (OpBase & {
|
|
132
|
+
op: "coverage.hit";
|
|
133
|
+
counter: number;
|
|
134
|
+
}) | (OpBase & {
|
|
118
135
|
op: "const.int";
|
|
119
136
|
dst: number;
|
|
120
137
|
value: string;
|
|
@@ -312,6 +329,7 @@ export type OpV1 = (OpBase & {
|
|
|
312
329
|
op: "callable.coerce";
|
|
313
330
|
dst: number;
|
|
314
331
|
source: Operand;
|
|
332
|
+
specialization: number | null;
|
|
315
333
|
}) | (OpBase & {
|
|
316
334
|
op: "dyn.make";
|
|
317
335
|
dst: number;
|
|
@@ -341,6 +359,11 @@ export type OpV1 = (OpBase & {
|
|
|
341
359
|
}) | (OpBase & {
|
|
342
360
|
op: "task_scope.close";
|
|
343
361
|
scope: number;
|
|
362
|
+
}) | (OpBase & {
|
|
363
|
+
op: "task.race";
|
|
364
|
+
dst: number;
|
|
365
|
+
scope: number;
|
|
366
|
+
members: Operand[];
|
|
344
367
|
}) | (OpBase & {
|
|
345
368
|
op: "task.spawn";
|
|
346
369
|
dst: number;
|
|
@@ -407,6 +430,7 @@ export type TermV1 = (TermBase & {
|
|
|
407
430
|
}) | (TermBase & CallShape & {
|
|
408
431
|
op: "indirect.call";
|
|
409
432
|
callee: BoundaryOperand;
|
|
433
|
+
specialization: number | null;
|
|
410
434
|
}) | (TermBase & CallShape & {
|
|
411
435
|
op: "dyn.call";
|
|
412
436
|
receiver: BoundaryOperand;
|
|
@@ -414,12 +438,25 @@ export type TermV1 = (TermBase & {
|
|
|
414
438
|
}) | (TermBase & CallShape & {
|
|
415
439
|
op: "value.call";
|
|
416
440
|
plan: number;
|
|
441
|
+
}) | (TermBase & CallShape & {
|
|
442
|
+
op: "wire.codec";
|
|
443
|
+
codec: number;
|
|
444
|
+
format: number;
|
|
445
|
+
direction: "encode" | "decode" | "test_input";
|
|
446
|
+
decoded: {
|
|
447
|
+
shape: number;
|
|
448
|
+
ok: number;
|
|
449
|
+
error: number;
|
|
450
|
+
error_root: number;
|
|
451
|
+
} | null;
|
|
452
|
+
callbacks: Record<string, number[]>;
|
|
417
453
|
}) | (TermBase & CallShape & {
|
|
418
454
|
op: "runtime.call";
|
|
419
455
|
family: number;
|
|
420
456
|
structural?: number | null;
|
|
421
457
|
callbacks?: number[];
|
|
422
458
|
releases_contents?: boolean;
|
|
459
|
+
keeps_inputs_on_failure?: boolean;
|
|
423
460
|
result_member_shapes?: Array<number | null>;
|
|
424
461
|
result_shape: number | null;
|
|
425
462
|
}) | (TermBase & CallShape & {
|
|
@@ -436,10 +473,6 @@ export type TermV1 = (TermBase & {
|
|
|
436
473
|
};
|
|
437
474
|
result_shape: number | null;
|
|
438
475
|
error_shape: number | null;
|
|
439
|
-
}) | (TermBase & CallShape & {
|
|
440
|
-
op: "wire.codec";
|
|
441
|
-
direction: string;
|
|
442
|
-
plan: number;
|
|
443
476
|
}) | (TermBase & {
|
|
444
477
|
op: "panic";
|
|
445
478
|
message: BoundaryOperand;
|
|
@@ -498,7 +531,7 @@ export interface EntryPlan {
|
|
|
498
531
|
export interface ActorProtocol {
|
|
499
532
|
actor: number;
|
|
500
533
|
message: number;
|
|
501
|
-
policy: "reject" | "wait" | "drop_newest"
|
|
534
|
+
policy: "reject" | "wait" | "drop_newest";
|
|
502
535
|
sealed: boolean;
|
|
503
536
|
}
|
|
504
537
|
export type ActorOperation = {
|
|
@@ -558,7 +591,6 @@ export interface ActorShape {
|
|
|
558
591
|
keys: Array<{
|
|
559
592
|
message: number;
|
|
560
593
|
param: number;
|
|
561
|
-
kind: string;
|
|
562
594
|
}>;
|
|
563
595
|
};
|
|
564
596
|
max_heap_bytes?: number;
|
|
@@ -582,6 +614,7 @@ export interface SupervisorShape {
|
|
|
582
614
|
}>;
|
|
583
615
|
}
|
|
584
616
|
export interface PackageV1 {
|
|
617
|
+
codecs?: string[];
|
|
585
618
|
resources?: Array<{
|
|
586
619
|
kind: string;
|
|
587
620
|
ty?: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/// reject list to keep in step with this one. `FileRead` is refused by its
|
|
7
7
|
/// absence, and a `NativeIo` suspension by an operation missing from
|
|
8
8
|
/// [`SUPPORTED_NATIVE_IO`].
|
|
9
|
-
import { UNIT, cloneValue, renderStdout } from "../values.js";
|
|
9
|
+
import { UNIT, cloneValue, compareUtf8, renderStdout, } from "../values.js";
|
|
10
10
|
import { seededMt } from "./mt19937.js";
|
|
11
11
|
/// A language-visible fault raised inside a shim. The executor routes it to the
|
|
12
12
|
/// call's `unwind` edge, or ends the program when the call names none.
|
|
@@ -29,6 +29,8 @@ export function resolveRuntimeShim(entry) {
|
|
|
29
29
|
throw new Error("SupervisorPool requires its resumable executor");
|
|
30
30
|
}
|
|
31
31
|
: undefined;
|
|
32
|
+
case "ActorCaller":
|
|
33
|
+
case "ActorRefusalLog":
|
|
32
34
|
case "StructuralFormat":
|
|
33
35
|
return () => {
|
|
34
36
|
throw new Error("StructuralFormat requires its resumable executor");
|
|
@@ -60,7 +62,11 @@ export function resolveRuntimeShim(entry) {
|
|
|
60
62
|
? () => {
|
|
61
63
|
throw new Error("array mutation requires its resumable executor");
|
|
62
64
|
}
|
|
63
|
-
:
|
|
65
|
+
: entry.detail === "IndexBorrow"
|
|
66
|
+
? (_host, args) => sequenceElement(args)
|
|
67
|
+
: ["Len", "Index"].includes(String(entry.detail))
|
|
68
|
+
? VECTOR_SHIMS[detailName(entry.detail)]
|
|
69
|
+
: undefined;
|
|
64
70
|
case "Map":
|
|
65
71
|
case "Set":
|
|
66
72
|
return [
|
|
@@ -85,6 +91,12 @@ export function resolveRuntimeShim(entry) {
|
|
|
85
91
|
: undefined;
|
|
86
92
|
case "MathIntrinsic":
|
|
87
93
|
return MATH_SHIMS[detailName(entry.detail)];
|
|
94
|
+
case "FloatMethod":
|
|
95
|
+
return FLOAT_METHOD_SHIMS[detailName(entry.detail)];
|
|
96
|
+
case "IntMethod":
|
|
97
|
+
return intMethodShim(entry.detail);
|
|
98
|
+
case "IntArith":
|
|
99
|
+
return intArithShim(entry.detail);
|
|
88
100
|
default:
|
|
89
101
|
return UNIT_FAMILY_SHIMS[entry.family];
|
|
90
102
|
}
|
|
@@ -105,10 +117,10 @@ export const SUPPORTED_SUSPEND_KINDS = new Set([
|
|
|
105
117
|
"StreamSend",
|
|
106
118
|
"StreamNext",
|
|
107
119
|
]);
|
|
108
|
-
/// The
|
|
109
|
-
/// the replay stdin; every file and socket operation stays native.
|
|
120
|
+
/// The VM serves replay standard input and in-memory sink closure.
|
|
110
121
|
export const SUPPORTED_NATIVE_IO = new Set([
|
|
111
122
|
"StdinReadLine",
|
|
123
|
+
"SinkFinish",
|
|
112
124
|
]);
|
|
113
125
|
// ── families ────────────────────────────────────────────────────────────────
|
|
114
126
|
/// `Print { kind, newline }`. The rendering follows the value's own
|
|
@@ -149,10 +161,6 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
149
161
|
host.closePipe(arg(args, 0));
|
|
150
162
|
return UNIT;
|
|
151
163
|
},
|
|
152
|
-
SinkFinish: (host, args) => {
|
|
153
|
-
host.pipes.close(arg(args, 0));
|
|
154
|
-
return UNIT;
|
|
155
|
-
},
|
|
156
164
|
StreamClose: (host, args) => {
|
|
157
165
|
host.closePipe(arg(args, 0));
|
|
158
166
|
return UNIT;
|
|
@@ -160,9 +168,20 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
160
168
|
StringConcat: (_host, args) => str(`${text(args, 0)}${text(args, 1)}`),
|
|
161
169
|
StringClone: (_host, args) => str(text(args, 0)),
|
|
162
170
|
StringEquals: (_host, args) => bool(text(args, 0) === text(args, 1)),
|
|
171
|
+
StringCompare: (_host, args) => int(BigInt(compareUtf8(text(args, 0), text(args, 1)))),
|
|
163
172
|
StringContains: (_host, args) => bool(text(args, 0).includes(text(args, 1))),
|
|
173
|
+
StringTrim: (_host, args) => str(text(args, 0).replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "")),
|
|
164
174
|
// Native string length counts codepoints, not bytes.
|
|
165
175
|
StringLen: (_host, args) => int(BigInt([...text(args, 0)].length)),
|
|
176
|
+
StringIndex: (_host, args) => {
|
|
177
|
+
const chars = [...text(args, 0)];
|
|
178
|
+
const at = index(args, 1);
|
|
179
|
+
if (at < 0 || at >= chars.length) {
|
|
180
|
+
throw new ShimFault("vector_bounds", `string index ${at} out of bounds for length ${chars.length}`);
|
|
181
|
+
}
|
|
182
|
+
return str(chars[at]);
|
|
183
|
+
},
|
|
184
|
+
StringIsEmpty: (_host, args) => bool(text(args, 0).length === 0),
|
|
166
185
|
StringReplace: (_host, args) => str(text(args, 0).split(text(args, 1)).join(text(args, 2))),
|
|
167
186
|
// Native slicing is by codepoint and clamps rather than trapping.
|
|
168
187
|
StringSlice: (_host, args) => {
|
|
@@ -183,8 +202,63 @@ const UNIT_FAMILY_SHIMS = {
|
|
|
183
202
|
// TextDecoder replaces each maximal invalid subsequence with U+FFFD, as
|
|
184
203
|
// native lossy decoding does.
|
|
185
204
|
BytesDecodeUtf8Lossy: (_host, args) => str(decodeUtf8(arg(args, 0))),
|
|
205
|
+
BytesNew: () => ({ kind: "vector", elementType: "u8", items: [] }),
|
|
186
206
|
BytesIsEmpty: (_host, args) => bool(vec(args, 0).items.length === 0),
|
|
187
207
|
BytesLen: (_host, args) => int(BigInt(vec(args, 0).items.length)),
|
|
208
|
+
BytesContains: (_host, args) => {
|
|
209
|
+
const byte = integer(args, 1);
|
|
210
|
+
return bool(vec(args, 0).items.some((item) => integer([item], 0) === byte));
|
|
211
|
+
},
|
|
212
|
+
BytesIndex: (_host, args) => {
|
|
213
|
+
const items = vec(args, 0).items;
|
|
214
|
+
const at = index(args, 1);
|
|
215
|
+
if (at < 0 || at >= items.length) {
|
|
216
|
+
throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${items.length}`);
|
|
217
|
+
}
|
|
218
|
+
return cloneValue(items[at]);
|
|
219
|
+
},
|
|
220
|
+
// A native bytes range shares the buffer; a VM value is never aliased, so
|
|
221
|
+
// a copy is observably the same.
|
|
222
|
+
BytesSlice: (_host, args) => {
|
|
223
|
+
const items = vec(args, 0).items;
|
|
224
|
+
const start = index(args, 1);
|
|
225
|
+
const end = index(args, 2);
|
|
226
|
+
checkSliceBounds(start, end, items.length);
|
|
227
|
+
return byteVector(items.slice(start, end));
|
|
228
|
+
},
|
|
229
|
+
BytesSliceFrom: (_host, args) => {
|
|
230
|
+
const items = vec(args, 0).items;
|
|
231
|
+
const start = index(args, 1);
|
|
232
|
+
checkSliceBounds(start, items.length, items.length);
|
|
233
|
+
return byteVector(items.slice(start));
|
|
234
|
+
},
|
|
235
|
+
BytesConcat: (_host, args) => byteVector([...vec(args, 0).items, ...vec(args, 1).items]),
|
|
236
|
+
BytesStartsWith: (_host, args) => bool(runAt(vec(args, 0).items, vec(args, 1).items, 0)),
|
|
237
|
+
BytesEndsWith: (_host, args) => {
|
|
238
|
+
const items = vec(args, 0).items;
|
|
239
|
+
const run = vec(args, 1).items;
|
|
240
|
+
return bool(runAt(items, run, items.length - run.length));
|
|
241
|
+
},
|
|
242
|
+
// A mutating call receives its receiver by `move` and returns it.
|
|
243
|
+
BytesPush: (_host, args) => {
|
|
244
|
+
const target = vec(args, 0);
|
|
245
|
+
target.items.push(cloneValue(arg(args, 1)));
|
|
246
|
+
return target;
|
|
247
|
+
},
|
|
248
|
+
BytesSet: (_host, args) => {
|
|
249
|
+
const target = vec(args, 0);
|
|
250
|
+
const at = index(args, 1);
|
|
251
|
+
if (at < 0 || at >= target.items.length) {
|
|
252
|
+
throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${target.items.length}`);
|
|
253
|
+
}
|
|
254
|
+
target.items[at] = cloneValue(arg(args, 2));
|
|
255
|
+
return target;
|
|
256
|
+
},
|
|
257
|
+
BytesClear: (_host, args) => {
|
|
258
|
+
const target = vec(args, 0);
|
|
259
|
+
target.items.length = 0;
|
|
260
|
+
return target;
|
|
261
|
+
},
|
|
188
262
|
BytesGet: (host, args, shape) => {
|
|
189
263
|
if (!shape) {
|
|
190
264
|
throw new TypeError("BytesGet names no result shape to build its Option against");
|
|
@@ -230,6 +304,15 @@ const VECTOR_SHIMS = {
|
|
|
230
304
|
target.items.push(arg(args, 1));
|
|
231
305
|
return target;
|
|
232
306
|
},
|
|
307
|
+
Remove: (_host, args) => {
|
|
308
|
+
const target = vec(args, 0);
|
|
309
|
+
const at = index(args, 1);
|
|
310
|
+
if (at < 0 || at >= target.items.length) {
|
|
311
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${target.items.length}`);
|
|
312
|
+
}
|
|
313
|
+
const [item] = target.items.splice(at, 1);
|
|
314
|
+
return { kind: "record", typeId: "", fields: [target, item] };
|
|
315
|
+
},
|
|
233
316
|
Get: (host, args, shape) => {
|
|
234
317
|
if (!shape) {
|
|
235
318
|
throw new TypeError("Vector::Get names no result shape to build its Option against");
|
|
@@ -240,19 +323,14 @@ const VECTOR_SHIMS = {
|
|
|
240
323
|
? host.enumValue(shape, "OptionSome", [cloneValue(items[at])])
|
|
241
324
|
: host.enumValue(shape, "OptionNone", []);
|
|
242
325
|
},
|
|
243
|
-
Index: (_host, args) =>
|
|
244
|
-
|
|
245
|
-
const at = index(args, 1);
|
|
246
|
-
if (at < 0 || at >= items.length) {
|
|
247
|
-
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${items.length}`);
|
|
248
|
-
}
|
|
249
|
-
return cloneValue(items[at]);
|
|
250
|
-
},
|
|
326
|
+
Index: (_host, args) => cloneValue(sequenceElement(args)),
|
|
327
|
+
IndexBorrow: (_host, args) => sequenceElement(args),
|
|
251
328
|
Len: (_host, args) => int(BigInt(vec(args, 0).items.length)),
|
|
252
329
|
Slice: (_host, args) => {
|
|
253
330
|
const items = vec(args, 0).items;
|
|
254
|
-
const start =
|
|
255
|
-
const end =
|
|
331
|
+
const start = index(args, 1);
|
|
332
|
+
const end = index(args, 2);
|
|
333
|
+
checkSliceBounds(start, end, items.length);
|
|
256
334
|
return {
|
|
257
335
|
kind: "vector",
|
|
258
336
|
elementType: "",
|
|
@@ -261,7 +339,8 @@ const VECTOR_SHIMS = {
|
|
|
261
339
|
},
|
|
262
340
|
SliceFrom: (_host, args) => {
|
|
263
341
|
const items = vec(args, 0).items;
|
|
264
|
-
const start =
|
|
342
|
+
const start = index(args, 1);
|
|
343
|
+
checkSliceBounds(start, items.length, items.length);
|
|
265
344
|
return {
|
|
266
345
|
kind: "vector",
|
|
267
346
|
elementType: "",
|
|
@@ -269,6 +348,30 @@ const VECTOR_SHIMS = {
|
|
|
269
348
|
};
|
|
270
349
|
},
|
|
271
350
|
};
|
|
351
|
+
function sequenceElement(args) {
|
|
352
|
+
const items = vec(args, 0).items;
|
|
353
|
+
const at = index(args, 1);
|
|
354
|
+
if (at < 0 || at >= items.length) {
|
|
355
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${items.length}`);
|
|
356
|
+
}
|
|
357
|
+
return items[at];
|
|
358
|
+
}
|
|
359
|
+
function byteVector(items) {
|
|
360
|
+
return { kind: "vector", elementType: "u8", items: items.map(cloneValue) };
|
|
361
|
+
}
|
|
362
|
+
/// Whether `run` occurs in `items` starting at `at`.
|
|
363
|
+
function runAt(items, run, at) {
|
|
364
|
+
return (at >= 0 &&
|
|
365
|
+
at + run.length <= items.length &&
|
|
366
|
+
run.every((byte, offset) => integer([items[at + offset]], 0) === integer([byte], 0)));
|
|
367
|
+
}
|
|
368
|
+
/// A slice outside `0 <= start <= end <= length` faults like native's
|
|
369
|
+
/// `IndexOutOfBounds` rather than clamping to the valid range.
|
|
370
|
+
function checkSliceBounds(start, end, length) {
|
|
371
|
+
if (start < 0 || end < start || end > length) {
|
|
372
|
+
throw new ShimFault("vector_bounds", `slice ${start}..${end} out of bounds for length ${length}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
272
375
|
/// `std.math`. Native emits the LLVM intrinsic for each of these, so the
|
|
273
376
|
/// mapping is to the matching IEEE operation rather than to whatever JS spells
|
|
274
377
|
/// similarly: `Round` is half away from zero, `MinF64`/`MaxF64` follow
|
|
@@ -379,6 +482,176 @@ function fma(a, b, c) {
|
|
|
379
482
|
const sumTail = product - (sum - bVirtual) + (c - bVirtual);
|
|
380
483
|
return sum + (sumTail + tail);
|
|
381
484
|
}
|
|
485
|
+
/// `f64` classification and bit methods. Native reads the IEEE bits
|
|
486
|
+
/// directly, so `IsSignNegative` tests the sign bit (true for `-0.0` and a
|
|
487
|
+
/// negative NaN) rather than comparing against zero.
|
|
488
|
+
const FLOAT_METHOD_SHIMS = {
|
|
489
|
+
ToBits: (_host, args) => int(toBits(real(args, 0))),
|
|
490
|
+
IsNan: (_host, args) => bool(Number.isNaN(real(args, 0))),
|
|
491
|
+
IsFinite: (_host, args) => bool(Number.isFinite(real(args, 0))),
|
|
492
|
+
IsInfinite: (_host, args) => {
|
|
493
|
+
const value = real(args, 0);
|
|
494
|
+
return bool(value === Infinity || value === -Infinity);
|
|
495
|
+
},
|
|
496
|
+
IsSignNegative: (_host, args) => bool(toBits(real(args, 0)) >> 63n === 1n),
|
|
497
|
+
};
|
|
498
|
+
/// The receiver width an `IntMethod`/`IntArith` detail names. The VM is a
|
|
499
|
+
/// 64-bit target, so the pointer-sized widths are 64 bits wide.
|
|
500
|
+
const INT_WIDTHS = {
|
|
501
|
+
I8: { bits: 8, signed: true },
|
|
502
|
+
I16: { bits: 16, signed: true },
|
|
503
|
+
I32: { bits: 32, signed: true },
|
|
504
|
+
I64: { bits: 64, signed: true },
|
|
505
|
+
Isize: { bits: 64, signed: true },
|
|
506
|
+
U8: { bits: 8, signed: false },
|
|
507
|
+
U16: { bits: 16, signed: false },
|
|
508
|
+
U32: { bits: 32, signed: false },
|
|
509
|
+
U64: { bits: 64, signed: false },
|
|
510
|
+
Usize: { bits: 64, signed: false },
|
|
511
|
+
};
|
|
512
|
+
/// `[op, width]`, as `RuntimeCallFamily` serializes a two-field variant.
|
|
513
|
+
function intDetail(detail) {
|
|
514
|
+
if (!Array.isArray(detail) || detail.length !== 2) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const [op, width] = detail;
|
|
518
|
+
const shape = typeof width === "string" ? INT_WIDTHS[width] : undefined;
|
|
519
|
+
return typeof op === "string" && shape ? [op, shape] : undefined;
|
|
520
|
+
}
|
|
521
|
+
function wrapTo(width, value) {
|
|
522
|
+
return width.signed
|
|
523
|
+
? BigInt.asIntN(width.bits, value)
|
|
524
|
+
: BigInt.asUintN(width.bits, value);
|
|
525
|
+
}
|
|
526
|
+
function intBounds(width) {
|
|
527
|
+
const bits = BigInt(width.bits);
|
|
528
|
+
return width.signed
|
|
529
|
+
? [-(1n << (bits - 1n)), (1n << (bits - 1n)) - 1n]
|
|
530
|
+
: [0n, (1n << bits) - 1n];
|
|
531
|
+
}
|
|
532
|
+
function popcount(value) {
|
|
533
|
+
let count = 0n;
|
|
534
|
+
for (let rest = value; rest !== 0n; rest >>= 1n) {
|
|
535
|
+
count += rest & 1n;
|
|
536
|
+
}
|
|
537
|
+
return count;
|
|
538
|
+
}
|
|
539
|
+
/// Integer bit methods. Native emits `ctpop`/`ctlz`/`cttz`/`bswap`/
|
|
540
|
+
/// `bitreverse`/`fshl`/`fshr` at the receiver's width, so every operation
|
|
541
|
+
/// works on the receiver's unsigned bit pattern and the rotate amount is taken
|
|
542
|
+
/// modulo the width.
|
|
543
|
+
function intMethodShim(detail) {
|
|
544
|
+
const parsed = intDetail(detail);
|
|
545
|
+
if (!parsed) {
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
const [op, width] = parsed;
|
|
549
|
+
const bits = BigInt(width.bits);
|
|
550
|
+
const unsigned = (args) => BigInt.asUintN(width.bits, integer(args, 0));
|
|
551
|
+
const result = (pattern) => int(wrapTo(width, pattern));
|
|
552
|
+
const rotate = (args, left) => {
|
|
553
|
+
const pattern = unsigned(args);
|
|
554
|
+
const amount = BigInt.asUintN(32, integer(args, 1)) % bits;
|
|
555
|
+
const shift = left ? amount : (bits - amount) % bits;
|
|
556
|
+
return result((pattern << shift) | (pattern >> ((bits - shift) % bits)));
|
|
557
|
+
};
|
|
558
|
+
switch (op) {
|
|
559
|
+
case "CountOnes":
|
|
560
|
+
return (_host, args) => int(popcount(unsigned(args)));
|
|
561
|
+
case "CountZeros":
|
|
562
|
+
return (_host, args) => int(bits - popcount(unsigned(args)));
|
|
563
|
+
case "LeadingZeros":
|
|
564
|
+
return (_host, args) => {
|
|
565
|
+
const pattern = unsigned(args);
|
|
566
|
+
return int(pattern === 0n ? bits : bits - BigInt(pattern.toString(2).length));
|
|
567
|
+
};
|
|
568
|
+
case "TrailingZeros":
|
|
569
|
+
return (_host, args) => {
|
|
570
|
+
let pattern = unsigned(args);
|
|
571
|
+
if (pattern === 0n) {
|
|
572
|
+
return int(bits);
|
|
573
|
+
}
|
|
574
|
+
let count = 0n;
|
|
575
|
+
while ((pattern & 1n) === 0n) {
|
|
576
|
+
pattern >>= 1n;
|
|
577
|
+
count += 1n;
|
|
578
|
+
}
|
|
579
|
+
return int(count);
|
|
580
|
+
};
|
|
581
|
+
case "SwapBytes":
|
|
582
|
+
return (_host, args) => {
|
|
583
|
+
let pattern = unsigned(args);
|
|
584
|
+
let swapped = 0n;
|
|
585
|
+
for (let byte = 0; byte < width.bits / 8; byte += 1) {
|
|
586
|
+
swapped = (swapped << 8n) | (pattern & 0xffn);
|
|
587
|
+
pattern >>= 8n;
|
|
588
|
+
}
|
|
589
|
+
return result(swapped);
|
|
590
|
+
};
|
|
591
|
+
case "ReverseBits":
|
|
592
|
+
return (_host, args) => {
|
|
593
|
+
let pattern = unsigned(args);
|
|
594
|
+
let reversed = 0n;
|
|
595
|
+
for (let bit = 0; bit < width.bits; bit += 1) {
|
|
596
|
+
reversed = (reversed << 1n) | (pattern & 1n);
|
|
597
|
+
pattern >>= 1n;
|
|
598
|
+
}
|
|
599
|
+
return result(reversed);
|
|
600
|
+
};
|
|
601
|
+
case "RotateLeft":
|
|
602
|
+
return (_host, args) => rotate(args, true);
|
|
603
|
+
case "RotateRight":
|
|
604
|
+
return (_host, args) => rotate(args, false);
|
|
605
|
+
default:
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/// Non-trapping integer arithmetic at the receiver's width: wrapping keeps
|
|
610
|
+
/// the low bits, saturating clamps to the width's bounds and checked answers
|
|
611
|
+
/// `None` where native's overflow flag is set.
|
|
612
|
+
function intArithShim(detail) {
|
|
613
|
+
const parsed = intDetail(detail);
|
|
614
|
+
if (!parsed) {
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
const [kind, width] = parsed;
|
|
618
|
+
const [min, max] = intBounds(width);
|
|
619
|
+
const operations = {
|
|
620
|
+
Add: (x, y) => x + y,
|
|
621
|
+
Sub: (x, y) => x - y,
|
|
622
|
+
Mul: (x, y) => x * y,
|
|
623
|
+
};
|
|
624
|
+
const mode = ["Wrapping", "Saturating", "Checked"].find((prefix) => kind.startsWith(prefix));
|
|
625
|
+
const apply = mode ? operations[kind.slice(mode.length)] : undefined;
|
|
626
|
+
if (!mode || !apply) {
|
|
627
|
+
return undefined;
|
|
628
|
+
}
|
|
629
|
+
const exact = (args) => apply(wrapTo(width, integer(args, 0)), wrapTo(width, integer(args, 1)));
|
|
630
|
+
switch (mode) {
|
|
631
|
+
case "Wrapping":
|
|
632
|
+
return (_host, args) => int(wrapTo(width, exact(args)));
|
|
633
|
+
case "Saturating":
|
|
634
|
+
return (_host, args) => {
|
|
635
|
+
const value = exact(args);
|
|
636
|
+
return int(value < min ? min : value > max ? max : value);
|
|
637
|
+
};
|
|
638
|
+
default:
|
|
639
|
+
return (host, args, shape) => {
|
|
640
|
+
if (!shape) {
|
|
641
|
+
throw new TypeError("checked arithmetic names no result shape to build its Option against");
|
|
642
|
+
}
|
|
643
|
+
const value = exact(args);
|
|
644
|
+
return value < min || value > max
|
|
645
|
+
? host.enumValue(shape, "OptionNone", [])
|
|
646
|
+
: host.enumValue(shape, "OptionSome", [int(value)]);
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function toBits(value) {
|
|
651
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
652
|
+
view.setFloat64(0, value);
|
|
653
|
+
return view.getBigUint64(0);
|
|
654
|
+
}
|
|
382
655
|
function fromBits(bits) {
|
|
383
656
|
const view = new DataView(new ArrayBuffer(8));
|
|
384
657
|
view.setBigUint64(0, BigInt.asUintN(64, bits));
|
|
@@ -13,6 +13,7 @@ const UNSUPPORTED = "sandbox.capability.unsupported";
|
|
|
13
13
|
const NATIVE_FAMILIES = new Set(["AsyncIo", "FileRead", "Tcp"]);
|
|
14
14
|
const NATIVE_SUSPENSIONS = new Set(["NativeIo", "RemoteAsk", "Read", "Accept"]);
|
|
15
15
|
const OPS = new Set([
|
|
16
|
+
"coverage.hit",
|
|
16
17
|
"const.int",
|
|
17
18
|
"const.bool",
|
|
18
19
|
"const.float",
|
|
@@ -63,6 +64,7 @@ const OPS = new Set([
|
|
|
63
64
|
"task_scope.enter",
|
|
64
65
|
"task_scope.close",
|
|
65
66
|
"task.spawn",
|
|
67
|
+
"task.race",
|
|
66
68
|
]);
|
|
67
69
|
const TERMS = new Set([
|
|
68
70
|
"goto",
|
|
@@ -77,6 +79,7 @@ const TERMS = new Set([
|
|
|
77
79
|
"runtime.call",
|
|
78
80
|
"extern.call",
|
|
79
81
|
"value.call",
|
|
82
|
+
"wire.codec",
|
|
80
83
|
"panic",
|
|
81
84
|
"checked_raise",
|
|
82
85
|
"trap",
|
|
@@ -151,11 +154,34 @@ export function admitPackage(pkg) {
|
|
|
151
154
|
for (const block of function_.blocks) {
|
|
152
155
|
if (block.ops.some((op) => op.op === "actor.ingress_adapter"))
|
|
153
156
|
return unavailable("actor.ingress_adapter");
|
|
154
|
-
if (
|
|
155
|
-
|
|
157
|
+
if (block.term.op === "wire.codec" &&
|
|
158
|
+
(!Number.isSafeInteger(block.term.codec) ||
|
|
159
|
+
block.term.codec < 0 ||
|
|
160
|
+
typeof pkg.codecs?.[block.term.codec] !== "string" ||
|
|
161
|
+
![0, 1, 2, 3, 4].includes(block.term.format) ||
|
|
162
|
+
!["encode", "decode", "test_input"].includes(block.term.direction)))
|
|
163
|
+
return {
|
|
164
|
+
category: "invalid_package",
|
|
165
|
+
code: "sandbox.package.invalid_codec",
|
|
166
|
+
capability: "wire.codec",
|
|
167
|
+
message: "codec call lacks its checked shape or format",
|
|
168
|
+
span: null,
|
|
169
|
+
};
|
|
156
170
|
if (block.term.op === "actor.call" &&
|
|
157
171
|
!ACTOR_OPS.has(block.term.operation.op))
|
|
158
172
|
return unavailable(`actor.${block.term.operation.op}`);
|
|
173
|
+
if (block.ops.some((op) => op.op === "coverage.hit" &&
|
|
174
|
+
(!Number.isSafeInteger(op.counter) ||
|
|
175
|
+
op.counter < 0 ||
|
|
176
|
+
op.counter > 0xffff_ffff))) {
|
|
177
|
+
return {
|
|
178
|
+
category: "invalid_package",
|
|
179
|
+
code: "sandbox.package.invalid_counter",
|
|
180
|
+
capability: "coverage.hit",
|
|
181
|
+
message: "coverage counter must be a u32",
|
|
182
|
+
span: null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
159
185
|
const unknown = block.ops.find((op) => !OPS.has(op.op))?.op ??
|
|
160
186
|
(!TERMS.has(block.term.op) ? block.term.op : null);
|
|
161
187
|
if (unknown)
|
|
@@ -208,11 +234,14 @@ export function admitPackage(pkg) {
|
|
|
208
234
|
}
|
|
209
235
|
for (const entry of pkg.externs) {
|
|
210
236
|
if (!resolveExternShim(entry.symbol)) {
|
|
237
|
+
const native = entry.native_capability;
|
|
211
238
|
return {
|
|
212
|
-
category: "not_implemented",
|
|
239
|
+
category: native === undefined ? "not_implemented" : "native_only",
|
|
213
240
|
code: UNSUPPORTED,
|
|
214
241
|
capability: entry.symbol,
|
|
215
|
-
message:
|
|
242
|
+
message: native === undefined
|
|
243
|
+
? "This external-library operation is not available in the browser runtime yet."
|
|
244
|
+
: `${native} require native execution.`,
|
|
216
245
|
span: null,
|
|
217
246
|
};
|
|
218
247
|
}
|