jseption 0.0.2 → 0.0.4
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 +114 -3
- package/dist/bytecode.d.ts +6 -0
- package/dist/bytecode.js +1071 -0
- package/dist/bytecode.js.map +1 -0
- package/dist/interpreter.js +15 -4
- package/dist/interpreter.js.map +1 -1
- package/dist/jit.js +12 -4
- package/dist/jit.js.map +1 -1
- package/dist/jit_runtime.d.ts +2 -0
- package/dist/jit_runtime.js +11 -0
- package/dist/jit_runtime.js.map +1 -1
- package/dist/jseption.d.ts +6 -0
- package/dist/jseption.js +5 -0
- package/dist/jseption.js.map +1 -1
- package/dist/module.d.ts +28 -0
- package/dist/module.js +1166 -0
- package/dist/module.js.map +1 -0
- package/dist/opcodes.js +1 -1
- package/dist/opcodes.js.map +1 -1
- package/dist/parser.d.ts +31 -0
- package/dist/parser.js +2862 -62
- package/dist/parser.js.map +1 -1
- package/dist/qjs.js +86 -17
- package/dist/qjs.js.map +1 -1
- package/dist/quickjs.d.ts +8 -0
- package/dist/quickjs.js +21 -2
- package/dist/quickjs.js.map +1 -1
- package/dist/std.d.ts +2 -0
- package/dist/std.js +34 -0
- package/dist/std.js.map +1 -1
- package/dist/values.d.ts +78 -0
- package/dist/values.js +85 -0
- package/dist/values.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ JSEPTION_JIT=0 bun test # run the suite on the interpreter tier only
|
|
|
33
33
|
JSEPTION_JIT=always bun test # compile every function that runs (no lazy tier-up)
|
|
34
34
|
JSEPTION_JIT_DUMP=1 bun run src/qjs.ts file.js # print the generated host code
|
|
35
35
|
bun run bench/inproc.ts # host vs tier modes, incl. cold-code bundles
|
|
36
|
+
bun run bench/bytecode.ts file.js # compile vs read precompiled bytecode vs run
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
## Installing
|
|
@@ -72,6 +73,113 @@ engine metadata kept in a `WeakMap`). Engine code signals exceptions with the
|
|
|
72
73
|
the two conventions meet at the call boundary. See `docs/PORTING.md` and
|
|
73
74
|
`docs/HOST_VALUES.md` for the internals.
|
|
74
75
|
|
|
76
|
+
## Precompiled bytecode
|
|
77
|
+
|
|
78
|
+
A script can be compiled ahead of time and loaded without parsing, as with QuickJS's
|
|
79
|
+
`JS_WriteObject` / `JS_ReadObject`:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { JS_Eval, JS_EVAL_TYPE_GLOBAL, JS_EVAL_FLAG_COMPILE_ONLY, JS_WriteObject, JS_ReadObject,
|
|
83
|
+
JS_EvalFunction, JS_IsException } from 'jseption';
|
|
84
|
+
|
|
85
|
+
/* at build time */
|
|
86
|
+
const fn = JS_Eval(ctx, src, 'runtime.js', JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
87
|
+
const bytes = JS_WriteObject(ctx, fn); /* Uint8Array, or null with an exception pending */
|
|
88
|
+
|
|
89
|
+
/* at run time, in any runtime of the same jseption version */
|
|
90
|
+
const loaded = JS_ReadObject(ctx2, bytes); /* JS_EXCEPTION (a SyntaxError) on bad input */
|
|
91
|
+
if (!JS_IsException(loaded)) JS_EvalFunction(ctx2, loaded);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Reading is 18x (host JIT on) to 37x (JIT off, as on iOS) faster than parsing a 300 KB
|
|
95
|
+
bundle. Line numbers in error stacks are kept; function source text (for
|
|
96
|
+
`Function.prototype.toString`) is dropped unless `JS_WRITE_OBJ_SOURCE` is passed. The
|
|
97
|
+
bytes record the jseption version and only load in that version.
|
|
98
|
+
|
|
99
|
+
**Only load bytecode you produced yourself.** As in QuickJS, the reader checks the format
|
|
100
|
+
(version, bounds, tags, indices, instruction boundaries, a checksum) but not the meaning
|
|
101
|
+
of the byte code: hand-made bytecode can reach engine states the parser never produces,
|
|
102
|
+
so loading untrusted bytecode is not safe even though running untrusted source is. The
|
|
103
|
+
checksum only catches accidental corruption; bytecode that passes through storage or a
|
|
104
|
+
service you do not control needs a signature you verify before loading. See
|
|
105
|
+
`docs/BYTECODE.md` for the format and the measurements.
|
|
106
|
+
|
|
107
|
+
## Running TypeScript
|
|
108
|
+
|
|
109
|
+
TypeScript runs the way Node runs it with type stripping: the erasable syntax is
|
|
110
|
+
removed and the rest runs as JavaScript, with no type checking. Pass
|
|
111
|
+
`JS_EVAL_FLAG_STRIP_TYPES` to `JS_Eval`; the command line strips `.ts`, `.mts` and
|
|
112
|
+
`.cts` files, and `--strip-types` applies it to `-e`.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
JS_Eval(ctx, 'const id = <T,>(x: T): T => x; id<number>(41) + 1', 'app.ts',
|
|
116
|
+
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRIP_TYPES); // 42
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The stripping is built into the parser, so error positions and stacks are those
|
|
120
|
+
of the JavaScript with the types blanked, and plain JavaScript compiles as fast as
|
|
121
|
+
before. As in Node, `enum`, namespaces with values, parameter properties,
|
|
122
|
+
`import x = require(...)` and `export =` are rejected because they need generated
|
|
123
|
+
code. `docs/TYPESCRIPT.md` lists what is stripped and how ambiguous syntax is read.
|
|
124
|
+
|
|
125
|
+
## JSX and TSX
|
|
126
|
+
|
|
127
|
+
`JS_EVAL_FLAG_JSX` compiles JSX in the parser, as TypeScript compiles it. With
|
|
128
|
+
`JS_EVAL_FLAG_STRIP_TYPES` as well, it compiles TSX. There is no build step.
|
|
129
|
+
|
|
130
|
+
* **Modules** use the automatic runtime (`jsx: "react-jsx"`). Elements are calls of
|
|
131
|
+
`jsx`, `jsxs` and `Fragment`, imported from `react/jsx-runtime`. A
|
|
132
|
+
`/** @jsxImportSource x */` pragma imports them from `x/jsx-runtime` instead.
|
|
133
|
+
* **Scripts** use the classic runtime (`React.createElement`, or the `@jsx` and
|
|
134
|
+
`@jsxFrag` pragmas).
|
|
135
|
+
* **The module loader** compiles `.tsx` ids (or a `type: 'tsx'` result) as TSX and
|
|
136
|
+
`.jsx` ids as JSX.
|
|
137
|
+
* **The command line** runs `.tsx` and `.jsx` files as modules, and `--jsx` applies
|
|
138
|
+
the flag to `-e`.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
JS_SetModuleLoader(rt, { load: (id) => files[id] }); // '/app.tsx', 'react/jsx-runtime', ...
|
|
142
|
+
await guestPromiseToHost(ctx, JS_ImportModule(ctx, '/app.tsx'));
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`docs/JSX.md` covers the runtimes, pragmas, text and entity rules, and what is not
|
|
146
|
+
supported.
|
|
147
|
+
|
|
148
|
+
## Modules
|
|
149
|
+
|
|
150
|
+
ES modules work as in QuickJS: `import` and `export` in every form, live bindings,
|
|
151
|
+
cycles, `export *` with its ambiguity rules, namespace objects, top-level `await`,
|
|
152
|
+
dynamic `import()`, `import.meta`, and JSON modules (`with { type: "json" }`). The
|
|
153
|
+
embedder decides what a specifier means. With no loader set, nothing can be imported,
|
|
154
|
+
so the sandbox stays closed by default.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { JS_SetModuleLoader, JS_ImportModule, guestPromiseToHost, drainJobs } from 'jseption';
|
|
158
|
+
|
|
159
|
+
JS_SetModuleLoader(rt, {
|
|
160
|
+
// optional; the default resolves ./, ../ and / against the importing module, URL style
|
|
161
|
+
resolve: (specifier, referrer) => new URL(specifier, referrer ?? location.href).href,
|
|
162
|
+
// source text, module bytecode, or { source, url, type, exports }, or a promise of one
|
|
163
|
+
load: async (url) => ({ source: await (await fetch(url)).text(), url }),
|
|
164
|
+
});
|
|
165
|
+
const ns = await guestPromiseToHost(ctx, JS_ImportModule(ctx, 'https://example.test/app.js'));
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The loader may be asynchronous: the whole graph is fetched first, in parallel where
|
|
169
|
+
possible, then linked and evaluated. A synchronous loader keeps everything synchronous.
|
|
170
|
+
`load` can return a host module, `{ exports: { fetch } }`, whose values are marshalled
|
|
171
|
+
into the guest, so guest code can `import { fetch } from "host:net"`. It can also
|
|
172
|
+
return bytecode from `JS_WriteObject`, so a precompiled graph loads without parsing.
|
|
173
|
+
`JS_EvalModule(ctx, source, url)` evaluates source text as a module.
|
|
174
|
+
`JS_Eval` with `JS_EVAL_TYPE_MODULE` keeps QuickJS's behaviour and returns the
|
|
175
|
+
evaluation promise.
|
|
176
|
+
|
|
177
|
+
The command line runs `.mjs` and `.mts` files as modules, detects `.js` and `.ts` files
|
|
178
|
+
that start with `import` or `export`, and takes `-m` to force module mode or `--script`
|
|
179
|
+
to force a script. Its loader reads relative and absolute paths from disk; bare
|
|
180
|
+
specifiers are not resolved. `docs/MODULES.md` describes the design and the loader
|
|
181
|
+
contract.
|
|
182
|
+
|
|
75
183
|
## Passing values between host and guest
|
|
76
184
|
|
|
77
185
|
Guest values are host values, but the sandbox forbids handing the guest an object whose
|
|
@@ -105,7 +213,9 @@ src/atoms.ts atom table (JS_ATOM_*), tagged integer atoms, atom -> proper
|
|
|
105
213
|
src/values.ts JSValue, object metadata, bytecode & frame structures
|
|
106
214
|
src/opcodes.ts opcode table generated from quickjs-opcode.h
|
|
107
215
|
src/quickjs.ts runtime, context, property layer over Reflect, calls, conversions, jobs
|
|
108
|
-
src/parser.ts tokenizer, parser, bytecode emitter, variable/label resolution, eval
|
|
216
|
+
src/parser.ts tokenizer, parser (with TypeScript type stripping and JSX), bytecode emitter, variable/label resolution, eval
|
|
217
|
+
src/bytecode.ts bytecode serialization (JS_WriteObject / JS_ReadObject)
|
|
218
|
+
src/module.ts ES modules: loader, linking, evaluation, namespaces, import(), import.meta
|
|
109
219
|
src/interpreter.ts JS_CallInternal, closures, generators, async functions (fallback tier)
|
|
110
220
|
src/jit.ts bytecode -> host JavaScript compiler (stack-to-register, relooper)
|
|
111
221
|
src/jit_runtime.ts the helpers generated code calls into (the only engine touchpoint)
|
|
@@ -181,8 +291,9 @@ Differences from the C implementation:
|
|
|
181
291
|
* Memory management relies on the host garbage collector: there is no reference
|
|
182
292
|
counting, `JS_FreeValue`/`JS_DupValue` are no-ops, and `WeakRef` / `FinalizationRegistry`
|
|
183
293
|
use the host weak references.
|
|
184
|
-
*
|
|
185
|
-
|
|
294
|
+
* `Atomics` and `SharedArrayBuffer` threads are not ported. `JS_WriteObject` /
|
|
295
|
+
`JS_ReadObject` serialize compiled scripts and modules only, not arbitrary values,
|
|
296
|
+
in a jseption-specific format.
|
|
186
297
|
* Number formatting and parsing use the host conversions (they are specified by
|
|
187
298
|
ECMAScript, so the results are identical).
|
|
188
299
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { JSContext, JSValue } from './quickjs.ts';
|
|
2
|
+
export declare const JS_WRITE_OBJ_SOURCE: number;
|
|
3
|
+
export declare const BC_MAGIC: number[];
|
|
4
|
+
export declare const BC_FORMAT = 1;
|
|
5
|
+
export declare function JS_WriteObject(ctx: JSContext, obj: JSValue, flags?: number): Uint8Array | null;
|
|
6
|
+
export declare function JS_ReadObject(ctx: JSContext, buf: Uint8Array): JSValue;
|