@earendil-works/djs 0.0.0-stage → 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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +88 -2
- package/dist/base.d.ts +33 -0
- package/dist/base.js +71 -0
- package/dist/dump-runtime.d.ts +16 -0
- package/dist/dump-runtime.js +107 -0
- package/dist/dump.d.ts +10 -0
- package/dist/dump.js +21 -0
- package/dist/env.d.ts +5 -0
- package/dist/env.js +17 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +21 -0
- package/dist/layout.generated.d.ts +921 -0
- package/dist/layout.generated.js +442 -0
- package/dist/reader.d.ts +47 -0
- package/dist/reader.js +599 -0
- package/dist/sandbox.d.ts +756 -0
- package/dist/sandbox.js +1958 -0
- package/dist/slim.d.ts +10 -0
- package/dist/slim.js +21 -0
- package/dist/trap.d.ts +16 -0
- package/dist/trap.js +23 -0
- package/dist/wire.d.ts +46 -0
- package/dist/wire.js +373 -0
- package/package.json +53 -4
- package/wasm/djs-dump.wasm +0 -0
- package/wasm/djs-slim.wasm +0 -0
- package/wasm/djs.wasm +0 -0
package/dist/slim.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Sandbox as Base } from "./sandbox.js";
|
|
2
|
+
export * from "./sandbox.js";
|
|
3
|
+
/** This entry point's module: the package's djs-slim.wasm. */
|
|
4
|
+
export declare function defaultWasmPath(): string | URL;
|
|
5
|
+
/** Compiles djs-wasm once per location and caches it; default: this entry point's module. */
|
|
6
|
+
export declare function loadModule(location?: string | URL): Promise<WebAssembly.Module>;
|
|
7
|
+
/** The Sandbox, with this entry point's module as the default. */
|
|
8
|
+
export declare class Sandbox extends Base {
|
|
9
|
+
static defaultModule(): string | URL;
|
|
10
|
+
}
|
package/dist/slim.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The package's `./slim` entry point: the Sandbox API with the slim module,
|
|
2
|
+
// wasm/djs-slim.wasm (without typed arrays, BigInt, the iterator helpers and the other
|
|
3
|
+
// families scripts rarely use, and without the Unicode tables), as its default (D3106).
|
|
4
|
+
// Each entry point loads only its own module, so a bundler copies only the modules an
|
|
5
|
+
// application imports.
|
|
6
|
+
import { Sandbox as Base, loadModule as load } from "./sandbox.js";
|
|
7
|
+
export * from "./sandbox.js";
|
|
8
|
+
/** This entry point's module: the package's djs-slim.wasm. */
|
|
9
|
+
export function defaultWasmPath() {
|
|
10
|
+
return new URL("../wasm/djs-slim.wasm", import.meta.url);
|
|
11
|
+
}
|
|
12
|
+
/** Compiles djs-wasm once per location and caches it; default: this entry point's module. */
|
|
13
|
+
export function loadModule(location = defaultWasmPath()) {
|
|
14
|
+
return load(location);
|
|
15
|
+
}
|
|
16
|
+
/** The Sandbox, with this entry point's module as the default. */
|
|
17
|
+
export class Sandbox extends Base {
|
|
18
|
+
static defaultModule() {
|
|
19
|
+
return defaultWasmPath();
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/trap.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The exports `trapText` reads. */
|
|
2
|
+
export interface TrapExports {
|
|
3
|
+
memory: WebAssembly.Memory;
|
|
4
|
+
djs_panic_ptr(): number;
|
|
5
|
+
djs_panic_len(): number;
|
|
6
|
+
/** The shadow stack pointer (djs-wasm exports it for this). */
|
|
7
|
+
__stack_pointer?: WebAssembly.Global;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The text of a wasm trap `e` of the instance with exports `x`: for a stack overflow, a
|
|
11
|
+
* message that says so; else the engine's panic message if it recorded one, else the
|
|
12
|
+
* runtime's message. The shadow stack lies at the bottom of memory and grows down: a
|
|
13
|
+
* frame that does not fit takes the stack pointer below 0, where it wraps around past the
|
|
14
|
+
* end of memory, and the first store there traps as "memory access out of bounds".
|
|
15
|
+
*/
|
|
16
|
+
export declare function trapText(x: TrapExports, e: Error): string;
|
package/dist/trap.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// What a trapped djs-wasm instance says about the trap (D2543).
|
|
2
|
+
const textDecoder = new TextDecoder();
|
|
3
|
+
/**
|
|
4
|
+
* The text of a wasm trap `e` of the instance with exports `x`: for a stack overflow, a
|
|
5
|
+
* message that says so; else the engine's panic message if it recorded one, else the
|
|
6
|
+
* runtime's message. The shadow stack lies at the bottom of memory and grows down: a
|
|
7
|
+
* frame that does not fit takes the stack pointer below 0, where it wraps around past the
|
|
8
|
+
* end of memory, and the first store there traps as "memory access out of bounds".
|
|
9
|
+
*/
|
|
10
|
+
export function trapText(x, e) {
|
|
11
|
+
// First: with the stack pointer wrapped, calling any export would trap again.
|
|
12
|
+
const sp = x.__stack_pointer ? x.__stack_pointer.value >>> 0 : 0;
|
|
13
|
+
if (sp > x.memory.buffer.byteLength) {
|
|
14
|
+
return `stack overflow: the engine's wasm stack is exhausted (${e.message})`;
|
|
15
|
+
}
|
|
16
|
+
const msg = textDecoder.decode(new Uint8Array(x.memory.buffer, x.djs_panic_ptr(), x.djs_panic_len()));
|
|
17
|
+
if (msg)
|
|
18
|
+
return msg;
|
|
19
|
+
if (/unreachable/.test(e.message)) {
|
|
20
|
+
return `engine panic (an engine bug; the release module has no panic messages, a debug or native build has them): ${e.message}`;
|
|
21
|
+
}
|
|
22
|
+
return e.message;
|
|
23
|
+
}
|
package/dist/wire.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const VERSION = 1;
|
|
2
|
+
export declare const Tag: {
|
|
3
|
+
readonly UNDEFINED: 0;
|
|
4
|
+
readonly NULL: 1;
|
|
5
|
+
readonly FALSE: 2;
|
|
6
|
+
readonly TRUE: 3;
|
|
7
|
+
readonly INT32: 4;
|
|
8
|
+
readonly FLOAT64: 5;
|
|
9
|
+
readonly HOLE: 6;
|
|
10
|
+
readonly INT8: 7;
|
|
11
|
+
readonly STR_UTF8: 16;
|
|
12
|
+
readonly STR_UTF16: 17;
|
|
13
|
+
readonly ARRAY: 32;
|
|
14
|
+
readonly OBJECT: 33;
|
|
15
|
+
readonly BYTES: 48;
|
|
16
|
+
readonly HOST_REF: 63;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Encodes `value` for djs' `wire::decode`. Writes into `buffer` if given and big enough,
|
|
20
|
+
* otherwise into a new, larger buffer; returns the bytes written (a view of that buffer).
|
|
21
|
+
*
|
|
22
|
+
* Accepts undefined, null, booleans, numbers, strings, arrays (holes included), Uint8Array
|
|
23
|
+
* and plain objects (prototype Object.prototype or null; own enumerable string keys).
|
|
24
|
+
* Anything else, and cycles, throw a TypeError naming the path. `toJSON` is not called.
|
|
25
|
+
*/
|
|
26
|
+
export declare function encode(value: unknown, buffer?: Uint8Array): Uint8Array;
|
|
27
|
+
/** Host strings from this length on go into the heap with `stringInPlace` (D2551). */
|
|
28
|
+
export declare const IN_PLACE_MIN = 1024;
|
|
29
|
+
/** The exports `stringInPlace` uses (djs-wasm; optional: older modules lack them). */
|
|
30
|
+
export interface InPlaceExports {
|
|
31
|
+
memory: WebAssembly.Memory;
|
|
32
|
+
djs_out_ptr(): number;
|
|
33
|
+
vm_arena_ptr(vm: number): number;
|
|
34
|
+
vm_handle_bits(vm: number, h: number): void;
|
|
35
|
+
vm_string_reserve?(vm: number, len: number): number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A handle to string `s` (`wire`, its `encode`d bytes) copied from the wire bytes straight
|
|
39
|
+
* into a reserved heap string when it is ASCII (D2551): no staging copy and no transcode,
|
|
40
|
+
* and the staging buffer does not grow to the string's size. 0 when it is not ASCII
|
|
41
|
+
* (the UTF-8 is longer than `s`), the module lacks the exports, or the reservation
|
|
42
|
+
* failed: then stage `wire` as usual. Deciding from the encoding costs non-ASCII strings
|
|
43
|
+
* nothing; encoding straight into the reservation (one copy fewer for ASCII) made them
|
|
44
|
+
* pay a reservation, a second encoding and a second arena growth (D2551).
|
|
45
|
+
*/
|
|
46
|
+
export declare function stringInPlace(x: InPlaceExports, vm: number, s: string, wire: Uint8Array): number;
|
package/dist/wire.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
// The inbound wire format encoder: host JS value -> bytes for djs' `wire::decode`.
|
|
2
|
+
// Format specification: docs/wire.md (version 1).
|
|
3
|
+
import { LAYOUT } from "./layout.generated.js";
|
|
4
|
+
export const VERSION = 1;
|
|
5
|
+
export const Tag = {
|
|
6
|
+
UNDEFINED: 0x00,
|
|
7
|
+
NULL: 0x01,
|
|
8
|
+
FALSE: 0x02,
|
|
9
|
+
TRUE: 0x03,
|
|
10
|
+
INT32: 0x04,
|
|
11
|
+
FLOAT64: 0x05,
|
|
12
|
+
HOLE: 0x06,
|
|
13
|
+
INT8: 0x07,
|
|
14
|
+
STR_UTF8: 0x10,
|
|
15
|
+
STR_UTF16: 0x11,
|
|
16
|
+
ARRAY: 0x20,
|
|
17
|
+
OBJECT: 0x21,
|
|
18
|
+
BYTES: 0x30,
|
|
19
|
+
HOST_REF: 0x3f,
|
|
20
|
+
};
|
|
21
|
+
/** Strings up to this length are tried as ASCII with a plain loop before TextEncoder. */
|
|
22
|
+
const SHORT_STRING = 64;
|
|
23
|
+
/** Cycles are looked for once nesting is this deep (a cycle eventually gets there). */
|
|
24
|
+
const CYCLE_CHECK_DEPTH = 32;
|
|
25
|
+
const textEncoder = new TextEncoder();
|
|
26
|
+
/** A value that cannot be encoded; `path` says where (`$.a[3]`). */
|
|
27
|
+
class EncodeError extends TypeError {
|
|
28
|
+
segments = [];
|
|
29
|
+
reason;
|
|
30
|
+
constructor(reason) {
|
|
31
|
+
super(reason);
|
|
32
|
+
this.reason = reason;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function describe(v) {
|
|
36
|
+
if (typeof v === "function")
|
|
37
|
+
return "a function";
|
|
38
|
+
if (typeof v === "symbol")
|
|
39
|
+
return "a symbol";
|
|
40
|
+
if (typeof v === "bigint")
|
|
41
|
+
return "a BigInt";
|
|
42
|
+
if (typeof v === "object" && v !== null) {
|
|
43
|
+
const name = v.constructor?.name;
|
|
44
|
+
return name ? name : "an object with a custom prototype";
|
|
45
|
+
}
|
|
46
|
+
return typeof v;
|
|
47
|
+
}
|
|
48
|
+
function formatPath(segments) {
|
|
49
|
+
let p = "$";
|
|
50
|
+
for (const s of segments) {
|
|
51
|
+
if (typeof s === "number")
|
|
52
|
+
p += `[${s}]`;
|
|
53
|
+
else if (/^[A-Za-z_$][\w$]*$/.test(s))
|
|
54
|
+
p += `.${s}`;
|
|
55
|
+
else
|
|
56
|
+
p += `[${JSON.stringify(s)}]`;
|
|
57
|
+
}
|
|
58
|
+
return p;
|
|
59
|
+
}
|
|
60
|
+
class Encoder {
|
|
61
|
+
buf;
|
|
62
|
+
view;
|
|
63
|
+
pos = 0;
|
|
64
|
+
keys = new Map();
|
|
65
|
+
stack = [];
|
|
66
|
+
depth = 0;
|
|
67
|
+
constructor(buffer) {
|
|
68
|
+
this.buf = buffer ?? new Uint8Array(4096);
|
|
69
|
+
this.view = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength);
|
|
70
|
+
}
|
|
71
|
+
ensure(n) {
|
|
72
|
+
if (this.pos + n <= this.buf.length)
|
|
73
|
+
return;
|
|
74
|
+
const size = Math.max(this.buf.length * 2, this.pos + n, 64);
|
|
75
|
+
const next = new Uint8Array(size);
|
|
76
|
+
next.set(this.buf.subarray(0, this.pos));
|
|
77
|
+
this.buf = next;
|
|
78
|
+
this.view = new DataView(next.buffer);
|
|
79
|
+
}
|
|
80
|
+
byte(b) {
|
|
81
|
+
this.ensure(1);
|
|
82
|
+
this.buf[this.pos++] = b;
|
|
83
|
+
}
|
|
84
|
+
/** LEB128; the caller has ensured 5 bytes of room. */
|
|
85
|
+
varint(v) {
|
|
86
|
+
const buf = this.buf;
|
|
87
|
+
let pos = this.pos;
|
|
88
|
+
while (v >= 0x80) {
|
|
89
|
+
buf[pos++] = (v & 0x7f) | 0x80;
|
|
90
|
+
v >>>= 7;
|
|
91
|
+
}
|
|
92
|
+
buf[pos++] = v;
|
|
93
|
+
this.pos = pos;
|
|
94
|
+
}
|
|
95
|
+
value(v) {
|
|
96
|
+
switch (typeof v) {
|
|
97
|
+
case "string":
|
|
98
|
+
this.string(v);
|
|
99
|
+
return;
|
|
100
|
+
case "number":
|
|
101
|
+
this.number(v);
|
|
102
|
+
return;
|
|
103
|
+
case "boolean":
|
|
104
|
+
this.byte(v ? Tag.TRUE : Tag.FALSE);
|
|
105
|
+
return;
|
|
106
|
+
case "undefined":
|
|
107
|
+
this.byte(Tag.UNDEFINED);
|
|
108
|
+
return;
|
|
109
|
+
case "object":
|
|
110
|
+
if (v === null) {
|
|
111
|
+
this.byte(Tag.NULL);
|
|
112
|
+
}
|
|
113
|
+
else if (Array.isArray(v)) {
|
|
114
|
+
this.array(v);
|
|
115
|
+
}
|
|
116
|
+
else if (v instanceof Uint8Array) {
|
|
117
|
+
this.bytes(v);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const proto = Object.getPrototypeOf(v);
|
|
121
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
122
|
+
throw new EncodeError(`cannot encode ${describe(v)}`);
|
|
123
|
+
}
|
|
124
|
+
this.object(v);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
default:
|
|
128
|
+
throw new EncodeError(`cannot encode ${describe(v)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
number(v) {
|
|
132
|
+
this.ensure(9);
|
|
133
|
+
const i = v | 0;
|
|
134
|
+
if (i === v && (i !== 0 || 1 / v > 0)) {
|
|
135
|
+
if (i >= -128 && i <= 127) {
|
|
136
|
+
this.buf[this.pos] = Tag.INT8;
|
|
137
|
+
this.buf[this.pos + 1] = i & 0xff;
|
|
138
|
+
this.pos += 2;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this.buf[this.pos] = Tag.INT32;
|
|
142
|
+
this.view.setInt32(this.pos + 1, i, true);
|
|
143
|
+
this.pos += 5;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
this.buf[this.pos] = Tag.FLOAT64;
|
|
148
|
+
this.view.setFloat64(this.pos + 1, v, true);
|
|
149
|
+
this.pos += 9;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
string(s) {
|
|
153
|
+
const n = s.length;
|
|
154
|
+
// tag, units, the reserved byte length, and at most 3 bytes per unit.
|
|
155
|
+
this.ensure(11 + 3 * n);
|
|
156
|
+
const buf = this.buf;
|
|
157
|
+
const start = this.pos;
|
|
158
|
+
buf[start] = Tag.STR_UTF8;
|
|
159
|
+
this.pos = start + 1;
|
|
160
|
+
this.varint(n);
|
|
161
|
+
// Reserve a byte length varint wide enough for the worst case; it is written padded.
|
|
162
|
+
const lenAt = this.pos;
|
|
163
|
+
const lenWidth = n < 43 ? 1 : n < 5462 ? 2 : n < 699051 ? 3 : n < 89478486 ? 4 : 5;
|
|
164
|
+
const data = lenAt + lenWidth;
|
|
165
|
+
let written = -1;
|
|
166
|
+
if (n <= SHORT_STRING) {
|
|
167
|
+
// By hand: cheaper than a TextEncoder call for short strings.
|
|
168
|
+
let p = data;
|
|
169
|
+
let i = 0;
|
|
170
|
+
for (; i < n; i++) {
|
|
171
|
+
let c = s.charCodeAt(i);
|
|
172
|
+
if (c < 0x80) {
|
|
173
|
+
buf[p++] = c;
|
|
174
|
+
}
|
|
175
|
+
else if (c < 0x800) {
|
|
176
|
+
buf[p++] = 0xc0 | (c >> 6);
|
|
177
|
+
buf[p++] = 0x80 | (c & 0x3f);
|
|
178
|
+
}
|
|
179
|
+
else if ((c & 0xf800) !== 0xd800) {
|
|
180
|
+
buf[p++] = 0xe0 | (c >> 12);
|
|
181
|
+
buf[p++] = 0x80 | ((c >> 6) & 0x3f);
|
|
182
|
+
buf[p++] = 0x80 | (c & 0x3f);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const d = i + 1 < n ? s.charCodeAt(i + 1) : 0;
|
|
186
|
+
if (c >= 0xdc00 || (d & 0xfc00) !== 0xdc00)
|
|
187
|
+
break; // lone surrogate
|
|
188
|
+
c = 0x10000 + ((c - 0xd800) << 10) + (d - 0xdc00);
|
|
189
|
+
i++;
|
|
190
|
+
buf[p++] = 0xf0 | (c >> 18);
|
|
191
|
+
buf[p++] = 0x80 | ((c >> 12) & 0x3f);
|
|
192
|
+
buf[p++] = 0x80 | ((c >> 6) & 0x3f);
|
|
193
|
+
buf[p++] = 0x80 | (c & 0x3f);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (i === n) {
|
|
197
|
+
written = p - data;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this.pos = start;
|
|
201
|
+
this.stringUtf16(s);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (written < 0) {
|
|
206
|
+
if (!s.isWellFormed()) {
|
|
207
|
+
this.pos = start;
|
|
208
|
+
this.stringUtf16(s);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
written = textEncoder.encodeInto(s, buf.subarray(data)).written;
|
|
212
|
+
}
|
|
213
|
+
// The padded varint: lenWidth bytes, continuation bits on all but the last.
|
|
214
|
+
let v = written;
|
|
215
|
+
for (let k = 0; k < lenWidth - 1; k++) {
|
|
216
|
+
buf[lenAt + k] = (v & 0x7f) | 0x80;
|
|
217
|
+
v >>>= 7;
|
|
218
|
+
}
|
|
219
|
+
buf[lenAt + lenWidth - 1] = v;
|
|
220
|
+
this.pos = data + written;
|
|
221
|
+
}
|
|
222
|
+
stringUtf16(s) {
|
|
223
|
+
const n = s.length;
|
|
224
|
+
this.ensure(6 + 2 * n);
|
|
225
|
+
this.buf[this.pos++] = Tag.STR_UTF16;
|
|
226
|
+
this.varint(n);
|
|
227
|
+
const view = this.view;
|
|
228
|
+
let pos = this.pos;
|
|
229
|
+
for (let i = 0; i < n; i++) {
|
|
230
|
+
view.setUint16(pos, s.charCodeAt(i), true);
|
|
231
|
+
pos += 2;
|
|
232
|
+
}
|
|
233
|
+
this.pos = pos;
|
|
234
|
+
}
|
|
235
|
+
enter(v) {
|
|
236
|
+
if (this.depth >= CYCLE_CHECK_DEPTH) {
|
|
237
|
+
for (let i = 0; i < this.depth; i++) {
|
|
238
|
+
if (this.stack[i] === v)
|
|
239
|
+
throw new EncodeError("cannot encode a cycle");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
this.stack[this.depth++] = v;
|
|
243
|
+
}
|
|
244
|
+
array(v) {
|
|
245
|
+
this.enter(v);
|
|
246
|
+
const n = v.length;
|
|
247
|
+
this.ensure(6);
|
|
248
|
+
this.buf[this.pos++] = Tag.ARRAY;
|
|
249
|
+
this.varint(n);
|
|
250
|
+
let i = 0;
|
|
251
|
+
try {
|
|
252
|
+
for (; i < n; i++) {
|
|
253
|
+
const x = v[i];
|
|
254
|
+
if (x === undefined && !(i in v)) {
|
|
255
|
+
this.byte(Tag.HOLE);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.value(x);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
if (e instanceof EncodeError)
|
|
264
|
+
e.segments.unshift(i);
|
|
265
|
+
throw e;
|
|
266
|
+
}
|
|
267
|
+
this.depth--;
|
|
268
|
+
}
|
|
269
|
+
object(v) {
|
|
270
|
+
this.enter(v);
|
|
271
|
+
const keys = Object.keys(v);
|
|
272
|
+
const n = keys.length;
|
|
273
|
+
this.ensure(6);
|
|
274
|
+
this.buf[this.pos++] = Tag.OBJECT;
|
|
275
|
+
this.varint(n);
|
|
276
|
+
const table = this.keys;
|
|
277
|
+
let key = "";
|
|
278
|
+
try {
|
|
279
|
+
for (let i = 0; i < n; i++) {
|
|
280
|
+
key = keys[i];
|
|
281
|
+
const k = table.get(key);
|
|
282
|
+
this.ensure(5);
|
|
283
|
+
if (k === undefined) {
|
|
284
|
+
table.set(key, table.size + 1);
|
|
285
|
+
this.buf[this.pos++] = 0;
|
|
286
|
+
this.string(key);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
this.varint(k);
|
|
290
|
+
}
|
|
291
|
+
this.value(v[key]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
if (e instanceof EncodeError)
|
|
296
|
+
e.segments.unshift(key);
|
|
297
|
+
throw e;
|
|
298
|
+
}
|
|
299
|
+
this.depth--;
|
|
300
|
+
}
|
|
301
|
+
bytes(v) {
|
|
302
|
+
this.ensure(6 + v.length);
|
|
303
|
+
this.buf[this.pos++] = Tag.BYTES;
|
|
304
|
+
this.varint(v.length);
|
|
305
|
+
this.buf.set(v, this.pos);
|
|
306
|
+
this.pos += v.length;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Encodes `value` for djs' `wire::decode`. Writes into `buffer` if given and big enough,
|
|
311
|
+
* otherwise into a new, larger buffer; returns the bytes written (a view of that buffer).
|
|
312
|
+
*
|
|
313
|
+
* Accepts undefined, null, booleans, numbers, strings, arrays (holes included), Uint8Array
|
|
314
|
+
* and plain objects (prototype Object.prototype or null; own enumerable string keys).
|
|
315
|
+
* Anything else, and cycles, throw a TypeError naming the path. `toJSON` is not called.
|
|
316
|
+
*/
|
|
317
|
+
export function encode(value, buffer) {
|
|
318
|
+
const e = new Encoder(buffer);
|
|
319
|
+
e.ensure(4);
|
|
320
|
+
e.buf.set([0x44, 0x4a, 0x57, VERSION], 0);
|
|
321
|
+
e.pos = 4;
|
|
322
|
+
try {
|
|
323
|
+
e.value(value);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
if (err instanceof EncodeError) {
|
|
327
|
+
throw new TypeError(`${err.reason} at ${formatPath(err.segments)}`);
|
|
328
|
+
}
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
return e.buf.subarray(0, e.pos);
|
|
332
|
+
}
|
|
333
|
+
/** Host strings from this length on go into the heap with `stringInPlace` (D2551). */
|
|
334
|
+
export const IN_PLACE_MIN = 1024;
|
|
335
|
+
/** Where a one-byte string cell's code units start. */
|
|
336
|
+
const STR8_DATA = LAYOUT.cells.Str8.tail.offset;
|
|
337
|
+
/** The varint at `buf[at]`: its value and the offset after it. */
|
|
338
|
+
function readVarint(buf, at) {
|
|
339
|
+
let v = 0;
|
|
340
|
+
for (let shift = 0;; shift += 7) {
|
|
341
|
+
const b = buf[at++];
|
|
342
|
+
v += (b & 0x7f) * 2 ** shift;
|
|
343
|
+
if (b < 0x80)
|
|
344
|
+
return [v, at];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A handle to string `s` (`wire`, its `encode`d bytes) copied from the wire bytes straight
|
|
349
|
+
* into a reserved heap string when it is ASCII (D2551): no staging copy and no transcode,
|
|
350
|
+
* and the staging buffer does not grow to the string's size. 0 when it is not ASCII
|
|
351
|
+
* (the UTF-8 is longer than `s`), the module lacks the exports, or the reservation
|
|
352
|
+
* failed: then stage `wire` as usual. Deciding from the encoding costs non-ASCII strings
|
|
353
|
+
* nothing; encoding straight into the reservation (one copy fewer for ASCII) made them
|
|
354
|
+
* pay a reservation, a second encoding and a second arena growth (D2551).
|
|
355
|
+
*/
|
|
356
|
+
export function stringInPlace(x, vm, s, wire) {
|
|
357
|
+
if (!x.vm_string_reserve || wire[4] !== Tag.STR_UTF8)
|
|
358
|
+
return 0;
|
|
359
|
+
const n = s.length;
|
|
360
|
+
const [units, lenAt] = readVarint(wire, 5);
|
|
361
|
+
const [bytes, data] = readVarint(wire, lenAt);
|
|
362
|
+
if (units !== n || bytes !== n)
|
|
363
|
+
return 0;
|
|
364
|
+
const h = x.vm_string_reserve(vm, n);
|
|
365
|
+
if (h === 0)
|
|
366
|
+
return 0;
|
|
367
|
+
// The string's offset is its value's low word; views after the calls (memory may grow).
|
|
368
|
+
x.vm_handle_bits(vm, h);
|
|
369
|
+
const cell = new DataView(x.memory.buffer, x.djs_out_ptr(), 4).getUint32(0, true);
|
|
370
|
+
const at = x.vm_arena_ptr(vm) + cell + STR8_DATA;
|
|
371
|
+
new Uint8Array(x.memory.buffer, at, n).set(wire.subarray(data, data + n));
|
|
372
|
+
return h;
|
|
373
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/djs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A small, sandboxed JavaScript and TypeScript engine in WebAssembly: runs untrusted scripts with fuel and memory limits, and suspends runs into dumps that continue later.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"javascript",
|
|
7
|
+
"typescript",
|
|
8
|
+
"sandbox",
|
|
9
|
+
"interpreter",
|
|
10
|
+
"webassembly",
|
|
11
|
+
"wasm",
|
|
12
|
+
"codemode"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"license": "MIT OR Apache-2.0",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/earendil-works/djs.git",
|
|
19
|
+
"directory": "js"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://earendil-works.github.io/djs/",
|
|
22
|
+
"bugs": "https://github.com/earendil-works/djs/issues",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./slim": {
|
|
35
|
+
"types": "./dist/slim.d.ts",
|
|
36
|
+
"default": "./dist/slim.js"
|
|
37
|
+
},
|
|
38
|
+
"./dump": {
|
|
39
|
+
"types": "./dist/dump.d.ts",
|
|
40
|
+
"default": "./dist/dump.js"
|
|
41
|
+
},
|
|
42
|
+
"./dump-runtime": {
|
|
43
|
+
"types": "./dist/dump-runtime.d.ts",
|
|
44
|
+
"default": "./dist/dump-runtime.js"
|
|
45
|
+
},
|
|
46
|
+
"./wasm/*.wasm": "./wasm/*.wasm",
|
|
47
|
+
"./package.json": "./package.json"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"wasm",
|
|
52
|
+
"LICENSE-MIT",
|
|
53
|
+
"LICENSE-APACHE"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
Binary file
|
|
Binary file
|
package/wasm/djs.wasm
ADDED
|
Binary file
|