@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,5 +1,5 @@
|
|
|
1
|
-
import { UNIT, cloneValue } from "../values.js";
|
|
2
|
-
import { runtimeTag } from "./package.js";
|
|
1
|
+
import { UNIT, cloneValue, compareUtf8 } from "../values.js";
|
|
2
|
+
import { runtimeTag, } from "./package.js";
|
|
3
3
|
import { ShimFault } from "./shims.js";
|
|
4
4
|
const OFFSET = 0xcbf29ce484222325n;
|
|
5
5
|
const PRIME = 0x100000001b3n;
|
|
@@ -20,6 +20,38 @@ function bytesHash(bytes) {
|
|
|
20
20
|
state = mix(state, BigInt(byte));
|
|
21
21
|
return state;
|
|
22
22
|
}
|
|
23
|
+
function orderingResult(capability, comparison) {
|
|
24
|
+
switch (capability) {
|
|
25
|
+
case "Less":
|
|
26
|
+
return boolean(comparison < 0);
|
|
27
|
+
case "LessEqual":
|
|
28
|
+
return boolean(comparison <= 0);
|
|
29
|
+
case "Greater":
|
|
30
|
+
return boolean(comparison > 0);
|
|
31
|
+
case "GreaterEqual":
|
|
32
|
+
return boolean(comparison >= 0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function scalarOrdering(left, right, ty, capability) {
|
|
36
|
+
if (left.kind === "f64") {
|
|
37
|
+
if (right?.kind !== "f64" ||
|
|
38
|
+
Number.isNaN(left.value) ||
|
|
39
|
+
Number.isNaN(right.value))
|
|
40
|
+
return boolean(false);
|
|
41
|
+
return orderingResult(capability, left.value < right.value ? -1 : left.value > right.value ? 1 : 0);
|
|
42
|
+
}
|
|
43
|
+
if (!right || right.kind !== left.kind)
|
|
44
|
+
return boolean(false);
|
|
45
|
+
const signed = /^(?:i(?:8|16|32|64)|isize|duration)$/.test(ty);
|
|
46
|
+
const width = Number(ty.match(/^[iu](8|16|32|64)$/)?.[1] ?? 64);
|
|
47
|
+
const lhs = left.kind === "i64" && signed
|
|
48
|
+
? BigInt.asIntN(width, left.value)
|
|
49
|
+
: bits(left, ty);
|
|
50
|
+
const rhs = right.kind === "i64" && signed
|
|
51
|
+
? BigInt.asIntN(width, right.value)
|
|
52
|
+
: bits(right, ty);
|
|
53
|
+
return orderingResult(capability, lhs < rhs ? -1 : lhs > rhs ? 1 : 0);
|
|
54
|
+
}
|
|
23
55
|
function bits(value, ty) {
|
|
24
56
|
switch (value.kind) {
|
|
25
57
|
case "unit":
|
|
@@ -55,22 +87,35 @@ export function* selectedValue(pkg, id, args) {
|
|
|
55
87
|
if (!left)
|
|
56
88
|
throw new Error("selected value operation has no receiver");
|
|
57
89
|
const hash = plan.capability === "Hash";
|
|
90
|
+
const equality = plan.capability === "Eq";
|
|
91
|
+
const ordering = plan.capability === "Less" ||
|
|
92
|
+
plan.capability === "LessEqual" ||
|
|
93
|
+
plan.capability === "Greater" ||
|
|
94
|
+
plan.capability === "GreaterEqual"
|
|
95
|
+
? plan.capability
|
|
96
|
+
: undefined;
|
|
58
97
|
if (plan.ty === "string") {
|
|
59
98
|
if (left.kind !== "string")
|
|
60
99
|
throw new Error("string capability has no string");
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
100
|
+
if (hash)
|
|
101
|
+
return integer(bytesHash(new TextEncoder().encode(left.value)));
|
|
102
|
+
if (right?.kind !== "string")
|
|
103
|
+
return boolean(false);
|
|
104
|
+
if (equality)
|
|
105
|
+
return boolean(left.value === right.value);
|
|
106
|
+
return orderingResult(ordering, compareUtf8(left.value, right.value));
|
|
64
107
|
}
|
|
65
108
|
if (plan.ty === "bytes") {
|
|
66
109
|
if (left.kind !== "vector")
|
|
67
110
|
throw new Error("bytes capability has no bytes");
|
|
68
111
|
const bytes = left.items.map((value) => Number(bits(value, "u8")));
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
if (hash)
|
|
113
|
+
return integer(bytesHash(bytes));
|
|
114
|
+
if (equality)
|
|
115
|
+
return boolean(right?.kind === "vector" &&
|
|
72
116
|
bytes.length === right.items.length &&
|
|
73
117
|
bytes.every((value, i) => BigInt(value) === bits(right.items[i], "u8")));
|
|
118
|
+
throw new Error("bytes ordering is outside the checked capability domain");
|
|
74
119
|
}
|
|
75
120
|
const components = plan.components ?? [];
|
|
76
121
|
let fields;
|
|
@@ -88,7 +133,11 @@ export function* selectedValue(pkg, id, args) {
|
|
|
88
133
|
else if (left.kind === "enum") {
|
|
89
134
|
if (hash)
|
|
90
135
|
throw new Error("derived enum Hash is outside the checked capability domain");
|
|
91
|
-
if (right?.kind !== "enum"
|
|
136
|
+
if (right?.kind !== "enum")
|
|
137
|
+
return boolean(false);
|
|
138
|
+
if (ordering && left.tag !== right.tag)
|
|
139
|
+
return orderingResult(ordering, left.tag < right.tag ? -1 : 1);
|
|
140
|
+
if (left.tag !== right.tag)
|
|
92
141
|
return boolean(false);
|
|
93
142
|
fields = left.payload;
|
|
94
143
|
others = right.payload;
|
|
@@ -104,13 +153,40 @@ export function* selectedValue(pkg, id, args) {
|
|
|
104
153
|
selections = fields.map(() => components[0]);
|
|
105
154
|
}
|
|
106
155
|
else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
if (hash)
|
|
157
|
+
return integer(bits(left, plan.ty));
|
|
158
|
+
if (equality)
|
|
159
|
+
return boolean(!!right && bits(left, plan.ty) === bits(right, plan.ty));
|
|
160
|
+
return scalarOrdering(left, right, plan.ty, ordering);
|
|
110
161
|
}
|
|
111
162
|
let state = OFFSET;
|
|
112
163
|
for (const [at, field] of fields.entries()) {
|
|
113
|
-
const
|
|
164
|
+
const selection = selections[at];
|
|
165
|
+
if (!selection)
|
|
166
|
+
throw new Error("derived value operation lacks a component selection");
|
|
167
|
+
if (ordering) {
|
|
168
|
+
const selected = selection.ordering;
|
|
169
|
+
if (!selected)
|
|
170
|
+
throw new Error("derived ordering lacks exact component predicates");
|
|
171
|
+
const pair = [field, others[at]];
|
|
172
|
+
const less = yield* selectedValue(pkg, selected.less, pair);
|
|
173
|
+
if (less.kind !== "bool")
|
|
174
|
+
throw new Error("Less returned a non-boolean value");
|
|
175
|
+
if (less.value)
|
|
176
|
+
return boolean(ordering === "Less" || ordering === "LessEqual");
|
|
177
|
+
const greater = yield* selectedValue(pkg, selected.greater, pair);
|
|
178
|
+
if (greater.kind !== "bool")
|
|
179
|
+
throw new Error("Greater returned a non-boolean value");
|
|
180
|
+
if (greater.value)
|
|
181
|
+
return boolean(ordering === "Greater" || ordering === "GreaterEqual");
|
|
182
|
+
const comparable = yield* selectedValue(pkg, selected.less_equal, pair);
|
|
183
|
+
if (comparable.kind !== "bool")
|
|
184
|
+
throw new Error("LessEqual returned a non-boolean value");
|
|
185
|
+
if (!comparable.value)
|
|
186
|
+
return boolean(false);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const result = yield* selectedValue(pkg, selection.selected, hash ? [field] : [field, others[at]]);
|
|
114
190
|
if (hash) {
|
|
115
191
|
if (result.kind !== "i64")
|
|
116
192
|
throw new Error("Hash returned a non-integer value");
|
|
@@ -123,7 +199,11 @@ export function* selectedValue(pkg, id, args) {
|
|
|
123
199
|
return boolean(false);
|
|
124
200
|
}
|
|
125
201
|
}
|
|
126
|
-
|
|
202
|
+
if (hash)
|
|
203
|
+
return integer(state);
|
|
204
|
+
if (equality)
|
|
205
|
+
return boolean(true);
|
|
206
|
+
return boolean(ordering === "LessEqual" || ordering === "GreaterEqual");
|
|
127
207
|
}
|
|
128
208
|
function ordered(entries) {
|
|
129
209
|
return new Map([...entries].sort(([a], [b]) => Number(a) - Number(b)));
|
|
@@ -102,10 +102,15 @@ export type VmValue = {
|
|
|
102
102
|
* owned by the closure, so `cloneValue` clones it. */
|
|
103
103
|
| {
|
|
104
104
|
kind: "closure";
|
|
105
|
-
|
|
105
|
+
entries: ReadonlyArray<{
|
|
106
|
+
body: number;
|
|
107
|
+
specialization: number | null;
|
|
108
|
+
}>;
|
|
109
|
+
specialization: number | null;
|
|
106
110
|
environment: VmValue;
|
|
107
111
|
};
|
|
108
112
|
export declare const UNIT: VmValue;
|
|
113
|
+
export declare function compareUtf8(a: string, b: string): number;
|
|
109
114
|
export declare function cloneValue(value: VmValue): VmValue;
|
|
110
115
|
export declare function valueFromLiteral(value: JsonValue): VmValue;
|
|
111
116
|
export declare function renderF64(value: number): string;
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export const UNIT = { kind: "unit" };
|
|
2
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
3
|
+
export function compareUtf8(a, b) {
|
|
4
|
+
const left = UTF8_ENCODER.encode(a);
|
|
5
|
+
const right = UTF8_ENCODER.encode(b);
|
|
6
|
+
const length = Math.min(left.length, right.length);
|
|
7
|
+
for (let i = 0; i < length; i++) {
|
|
8
|
+
if (left[i] < right[i])
|
|
9
|
+
return -1;
|
|
10
|
+
if (left[i] > right[i])
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
return left.length < right.length ? -1 : left.length > right.length ? 1 : 0;
|
|
14
|
+
}
|
|
2
15
|
const I64_LITERAL_DECIMAL = /^(?:0|-?[1-9]\d*)$/;
|
|
3
16
|
export function cloneValue(value) {
|
|
4
17
|
switch (value.kind) {
|
|
@@ -61,7 +74,8 @@ export function cloneValue(value) {
|
|
|
61
74
|
case "closure":
|
|
62
75
|
return {
|
|
63
76
|
kind: "closure",
|
|
64
|
-
|
|
77
|
+
entries: value.entries,
|
|
78
|
+
specialization: value.specialization,
|
|
65
79
|
environment: cloneValue(value.environment),
|
|
66
80
|
};
|
|
67
81
|
case "map":
|
|
@@ -226,7 +240,7 @@ export function renderStdout(value) {
|
|
|
226
240
|
case "function":
|
|
227
241
|
return value.id;
|
|
228
242
|
case "closure":
|
|
229
|
-
return `closure:${value.body}`;
|
|
243
|
+
return `closure:${value.entries.find((entry) => entry.specialization === value.specialization)?.body ?? "generic"}`;
|
|
230
244
|
}
|
|
231
245
|
}
|
|
232
246
|
export function toJsonValue(value) {
|
|
@@ -274,7 +288,7 @@ export function toJsonValue(value) {
|
|
|
274
288
|
case "function":
|
|
275
289
|
return value.id;
|
|
276
290
|
case "closure":
|
|
277
|
-
return `closure:${value.body}`;
|
|
291
|
+
return `closure:${value.entries.find((entry) => entry.specialization === value.specialization)?.body ?? "generic"}`;
|
|
278
292
|
}
|
|
279
293
|
}
|
|
280
294
|
/// A map renders as its entries in insertion order, each a `[key, value]` pair.
|
|
@@ -12,14 +12,32 @@ worker.addEventListener("message", (event) => {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
const { bytecode, options } = event.data;
|
|
15
|
-
const trace = runBytecode(bytecode,
|
|
16
|
-
|
|
15
|
+
const trace = runBytecode(bytecode, {
|
|
16
|
+
...options,
|
|
17
|
+
codecBridge: globalThis.__hewSandboxExecuteCodec,
|
|
18
|
+
});
|
|
19
|
+
worker.postMessage({
|
|
20
|
+
id,
|
|
21
|
+
ok: true,
|
|
22
|
+
trace,
|
|
23
|
+
playground: buildPlaygroundState(trace),
|
|
24
|
+
});
|
|
17
25
|
}
|
|
18
26
|
catch (error) {
|
|
19
27
|
if (error instanceof BytecodeValidationError) {
|
|
20
|
-
worker.postMessage({
|
|
28
|
+
worker.postMessage({
|
|
29
|
+
id,
|
|
30
|
+
ok: false,
|
|
31
|
+
error: "bytecode_validation",
|
|
32
|
+
diagnostics: error.errors,
|
|
33
|
+
});
|
|
21
34
|
return;
|
|
22
35
|
}
|
|
23
|
-
worker.postMessage({
|
|
36
|
+
worker.postMessage({
|
|
37
|
+
id,
|
|
38
|
+
ok: false,
|
|
39
|
+
error: "interpreter_error",
|
|
40
|
+
message: String(error),
|
|
41
|
+
});
|
|
24
42
|
}
|
|
25
43
|
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hew-lang/sandbox-vm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"hewSource": {
|
|
5
|
-
"commit": "
|
|
6
|
-
"tag": "v0.6.
|
|
5
|
+
"commit": "6a446f773391f45d2c0ce0a799240b8b9afef54e",
|
|
6
|
+
"tag": "v0.6.1",
|
|
7
7
|
"dirty": false
|
|
8
8
|
},
|
|
9
9
|
"description": "Deterministic TypeScript interpreter for the Hew educational sandbox",
|
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist/"
|
|
20
|
+
"dist/",
|
|
21
|
+
"cli/"
|
|
21
22
|
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@hew-lang/wasm": "0.6.1"
|
|
25
|
+
},
|
|
22
26
|
"keywords": [
|
|
23
27
|
"hew",
|
|
24
28
|
"sandbox",
|