openzoo 0.51.2 → 0.51.3
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/bin/openzoo.js +12 -8
- package/package.json +2 -2
- package/transmute/README.md +42 -9
- package/transmute/lib/build.js +72 -2
- package/transmute/lib/cli.js +73 -17
- package/transmute/lib/compile/bytecode.js +591 -0
- package/transmute/lib/compile/hostids.js +11 -0
- package/transmute/lib/compile/index.js +16 -3
- package/transmute/lib/deploy.js +149 -1
- package/transmute/lib/gateway.js +14 -8
- package/transmute/lib/hub.js +13 -3
- package/transmute/lib/solana.js +67 -21
- package/transmute/lib/wire.js +42 -1
- package/transmute/prebuilt/zoo_vm.v0.so +0 -0
- package/transmute/runtime/zoo-host/examples/vmrun.rs +43 -0
- package/transmute/runtime/zoo-host/src/assets.rs +53 -10
- package/transmute/runtime/zoo-host/src/ctx.rs +15 -0
- package/transmute/runtime/zoo-host/src/helpers.rs +245 -0
- package/transmute/runtime/zoo-host/src/kv.rs +29 -8
- package/transmute/runtime/zoo-host/src/lib.rs +13 -0
- package/transmute/runtime/zoo-host/src/site.rs +94 -0
- package/transmute/runtime/zoo-host/src/vm.rs +902 -0
- package/transmute/runtime/zoo-vm/Cargo.lock +596 -0
- package/transmute/runtime/zoo-vm/Cargo.toml +23 -0
- package/transmute/runtime/zoo-vm/src/lib.rs +92 -0
- package/transmute/site/package.json +6 -0
- package/transmute/site/public/index.html +4 -4
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
// IR → bytecode for the shared runtime (runtime/zoo-host/src/vm.rs).
|
|
2
|
+
//
|
|
3
|
+
// Same IR the Rust printer consumes, same semantics: every expression leaves
|
|
4
|
+
// one `Val` on the stack; statements leave nothing. Callbacks, local closures,
|
|
5
|
+
// IIFEs and try bodies are inline regions of their parent function sharing its
|
|
6
|
+
// local slots and getting their own `args` — which is exactly how the Rust
|
|
7
|
+
// backend's closures see the enclosing bindings.
|
|
8
|
+
import { helperIds, hostIds } from './hostids.js';
|
|
9
|
+
|
|
10
|
+
export const MAGIC = Buffer.from('ZOOB');
|
|
11
|
+
export const VERSION = 1;
|
|
12
|
+
|
|
13
|
+
export const OP = {
|
|
14
|
+
NOP: 0, PUSH_CONST: 1, PUSH_UNDEF: 2, PUSH_NULL: 3, DUP: 4, POP: 5, SWAP: 6, LOAD: 7, STORE: 8, LOAD_ARG: 9, ARGS_FROM: 10,
|
|
15
|
+
NEW_OBJ: 11, OBJ_SET_K: 12, OBJ_SET: 13, OBJ_SPREAD: 14, NEW_ARR: 15, ARR_PUSH: 16, ARR_SPREAD: 17, GET_K: 18, GET: 19,
|
|
16
|
+
TEMPLATE: 20, BIN: 21, CMP: 22, UNARY: 23, IN: 24, JMP: 25, JF: 26, JT: 27, JF_KEEP: 28, JT_KEEP: 29, JNN_KEEP: 30,
|
|
17
|
+
JNULLISH_UNDEF: 31, CALLM: 32, HOST: 33, HELPER: 34, MATH: 35, GLOBAL: 36, JSON_PARSE: 37, JSON_STRINGIFY: 38, KEYS: 39,
|
|
18
|
+
VALUES: 40, ENTRIES: 41, ISARRAY: 42, NEW_ERROR: 43, LOG: 44, RESP: 45, PARAMS: 46, PARAM: 47, CALL_FN: 48, CALL_INLINE: 49,
|
|
19
|
+
RET: 50, SEND: 51, THROW: 52, TRY_PUSH: 53, TRY_POP: 54, CAUGHT: 55, ITER_INIT: 56, ITER_NEXT: 57, STORE_PATH: 58,
|
|
20
|
+
STORE_PATH_DISCARD: 59, DELETE_PATH: 60, TRUTHY: 61, NULLISH: 62, STRICT_EQ_KEEP: 63,
|
|
21
|
+
};
|
|
22
|
+
const BIN = { add: 0, sub: 1, mul: 2, div: 3, rem: 4, pow: 5, bit_and: 6, bit_or: 7, bit_xor: 8, shl: 9, shr: 10, ushr: 11 };
|
|
23
|
+
const CMP = { strict_eq: 0, strict_ne: 1, loose_eq: 2, loose_ne: 3, lt: 4, le: 5, gt: 6, ge: 7 };
|
|
24
|
+
const UNARY = { neg: 0, plus: 1, not: 2, bitnot: 3, typeof: 4, void: 5 };
|
|
25
|
+
const CALLM_CB = 1, CALLM_DEFAULT_SORT = 2, CALLM_MUT = 4;
|
|
26
|
+
const HOST_Q = 1, HOST_VOID = 2;
|
|
27
|
+
const METHOD_INDEX = { GET: 0, POST: 1, PUT: 2, DELETE: 3, PATCH: 4, OPTIONS: 5, HEAD: 6 };
|
|
28
|
+
|
|
29
|
+
export class BytecodeError extends Error {}
|
|
30
|
+
|
|
31
|
+
/** Module-wide constant pool. */
|
|
32
|
+
class Consts {
|
|
33
|
+
constructor() { this.list = []; this.index = new Map(); }
|
|
34
|
+
key(v) { return typeof v === 'number' ? (Number.isNaN(v) ? 'n:NaN' : `n:${Object.is(v, -0) ? '-0' : v}`) : `${typeof v}:${v}`; }
|
|
35
|
+
add(v) {
|
|
36
|
+
const k = this.key(v);
|
|
37
|
+
if (this.index.has(k)) return this.index.get(k);
|
|
38
|
+
const i = this.list.length;
|
|
39
|
+
if (i >= 65535) throw new BytecodeError('too many constants');
|
|
40
|
+
this.list.push(v); this.index.set(k, i);
|
|
41
|
+
return i;
|
|
42
|
+
}
|
|
43
|
+
encode() {
|
|
44
|
+
const parts = [u16(this.list.length)];
|
|
45
|
+
for (const v of this.list) {
|
|
46
|
+
if (v === undefined) parts.push(Buffer.from([0]));
|
|
47
|
+
else if (v === null) parts.push(Buffer.from([1]));
|
|
48
|
+
else if (v === false) parts.push(Buffer.from([2]));
|
|
49
|
+
else if (v === true) parts.push(Buffer.from([3]));
|
|
50
|
+
else if (typeof v === 'number') { const b = Buffer.alloc(9); b[0] = 4; b.writeDoubleLE(v, 1); parts.push(b); }
|
|
51
|
+
else { const s = Buffer.from(String(v), 'utf8'); parts.push(Buffer.from([5]), u32(s.length), s); }
|
|
52
|
+
}
|
|
53
|
+
return Buffer.concat(parts);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function u16(n) { const b = Buffer.alloc(2); b.writeUInt16LE(n); return b; }
|
|
57
|
+
function u32(n) { const b = Buffer.alloc(4); b.writeUInt32LE(n); return b; }
|
|
58
|
+
|
|
59
|
+
/** One top-level function's code: slot table, jumps, labels. */
|
|
60
|
+
class Fn {
|
|
61
|
+
constructor(consts, mod) {
|
|
62
|
+
this.consts = consts; this.mod = mod;
|
|
63
|
+
this.code = []; // bytes
|
|
64
|
+
this.slots = new Map(); // name -> slot
|
|
65
|
+
this.nlocals = 0;
|
|
66
|
+
this.labels = []; // loop/switch/chain label contexts
|
|
67
|
+
this.tries = []; // active try contexts: { finalizer, phase, tryDepthAtLoop? }
|
|
68
|
+
this.inlines = new Map(); // letfn name -> code offset
|
|
69
|
+
this.tmp = 0;
|
|
70
|
+
}
|
|
71
|
+
slot(name) {
|
|
72
|
+
if (!this.slots.has(name)) { this.slots.set(name, this.nlocals++); if (this.nlocals > 65535) throw new BytecodeError('too many locals'); }
|
|
73
|
+
return this.slots.get(name);
|
|
74
|
+
}
|
|
75
|
+
temp() { return this.slot(`__t${this.tmp++}`); }
|
|
76
|
+
emit(...bytes) { for (const b of bytes) this.code.push(b & 0xff); }
|
|
77
|
+
u16(n) { this.emit(n & 0xff, (n >> 8) & 0xff); }
|
|
78
|
+
u32(n) { this.emit(n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >>> 24) & 0xff); }
|
|
79
|
+
i32(n) { this.u32(n >>> 0); }
|
|
80
|
+
pos() { return this.code.length; }
|
|
81
|
+
/** Emit a jump with a patchable 4-byte offset; returns the patch site. */
|
|
82
|
+
jmp(op) { this.emit(op); const at = this.pos(); this.i32(0); return at; }
|
|
83
|
+
/** Point the jump at `at` to the current position (offset relative to the end of the operand). */
|
|
84
|
+
patch(at, target = this.pos()) { const rel = target - (at + 4); this.code[at] = rel & 0xff; this.code[at + 1] = (rel >> 8) & 0xff; this.code[at + 2] = (rel >> 16) & 0xff; this.code[at + 3] = (rel >>> 24) & 0xff; }
|
|
85
|
+
jumpTo(op, target) { this.emit(op); const at = this.pos(); this.i32(0); this.patch(at, target); }
|
|
86
|
+
k(v) { return this.consts.add(v); }
|
|
87
|
+
pushConst(v) { this.emit(OP.PUSH_CONST); this.u16(this.k(v)); }
|
|
88
|
+
|
|
89
|
+
// ---- expressions
|
|
90
|
+
|
|
91
|
+
E(ir) {
|
|
92
|
+
switch (ir.k) {
|
|
93
|
+
case 'undef': this.emit(OP.PUSH_UNDEF); return;
|
|
94
|
+
case 'null': this.emit(OP.PUSH_NULL); return;
|
|
95
|
+
case 'bool': this.pushConst(!!ir.v); return;
|
|
96
|
+
case 'num': this.pushConst(ir.v); return;
|
|
97
|
+
case 'str': this.pushConst(String(ir.v)); return;
|
|
98
|
+
case 'raw': return this.raw(ir.code);
|
|
99
|
+
case 'var': this.emit(OP.LOAD); this.u16(this.slot(ir.name)); return;
|
|
100
|
+
case 'template': {
|
|
101
|
+
for (const p of ir.parts) { if (p.s !== undefined) this.pushConst(p.s); else this.E(p.e); }
|
|
102
|
+
this.emit(OP.TEMPLATE, Math.min(255, ir.parts.length));
|
|
103
|
+
if (ir.parts.length > 255) throw new BytecodeError('template too long');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
case 'obj': {
|
|
107
|
+
this.emit(OP.NEW_OBJ);
|
|
108
|
+
for (const p of ir.props) {
|
|
109
|
+
if (p.spread) { this.E(p.spread); this.emit(OP.OBJ_SPREAD); continue; }
|
|
110
|
+
if (typeof p.key === 'string') { this.E(p.value); this.emit(OP.OBJ_SET_K); this.u16(this.k(p.key)); }
|
|
111
|
+
else { this.E(p.key.e); this.E(p.value); this.emit(OP.OBJ_SET); }
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
case 'arr': {
|
|
116
|
+
this.emit(OP.NEW_ARR);
|
|
117
|
+
for (const i of ir.items) {
|
|
118
|
+
if (i.spread) { this.E(i.spread); this.emit(OP.ARR_SPREAD); }
|
|
119
|
+
else { if (i.hole) this.emit(OP.PUSH_UNDEF); else this.E(i.e); this.emit(OP.ARR_PUSH); }
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
case 'get': this.E(ir.obj); this.emit(OP.GET_K); this.u16(this.k(ir.key)); return;
|
|
124
|
+
case 'getc': this.E(ir.obj); this.E(ir.key); this.emit(OP.GET); return;
|
|
125
|
+
case 'chain': {
|
|
126
|
+
const ctx = { kind: 'chain', name: ir.label, breaks: [] };
|
|
127
|
+
this.labels.push(ctx);
|
|
128
|
+
this.E(ir.e);
|
|
129
|
+
this.labels.pop();
|
|
130
|
+
for (const at of ctx.breaks) this.patch(at);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
case 'optcheck': {
|
|
134
|
+
this.E(ir.e);
|
|
135
|
+
const ctx = [...this.labels].reverse().find((l) => l.kind === 'chain' && l.name === ir.label);
|
|
136
|
+
if (!ctx) throw new BytecodeError(`optcheck outside its chain ${ir.label}`);
|
|
137
|
+
ctx.breaks.push(this.jmp(OP.JNULLISH_UNDEF));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
case 'bin': this.E(ir.l); this.E(ir.r); this.emit(OP.BIN, BIN[ir.op] ?? this.bad(`binary ${ir.op}`)); return;
|
|
141
|
+
case 'cmp': this.E(ir.l); this.E(ir.r); this.emit(OP.CMP, CMP[ir.op] ?? this.bad(`compare ${ir.op}`)); return;
|
|
142
|
+
case 'logical': {
|
|
143
|
+
this.E(ir.l);
|
|
144
|
+
const at = this.jmp(ir.op === 'and' ? OP.JF_KEEP : ir.op === 'or' ? OP.JT_KEEP : OP.JNN_KEEP);
|
|
145
|
+
this.E(ir.r);
|
|
146
|
+
this.patch(at);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
case 'cond': {
|
|
150
|
+
this.C(ir.test);
|
|
151
|
+
const toElse = this.jmp(OP.JF);
|
|
152
|
+
this.E(ir.then);
|
|
153
|
+
const toEnd = this.jmp(OP.JMP);
|
|
154
|
+
this.patch(toElse);
|
|
155
|
+
this.E(ir.else);
|
|
156
|
+
this.patch(toEnd);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
case 'unary': this.E(ir.e); this.emit(OP.UNARY, UNARY[ir.op] ?? this.bad(`unary ${ir.op}`)); return;
|
|
160
|
+
case 'in': this.E(ir.key); this.E(ir.obj); this.emit(OP.IN); return;
|
|
161
|
+
case 'delete': {
|
|
162
|
+
if (ir.lv.k !== 'lv-member') { this.pushConst(true); return; }
|
|
163
|
+
const path = this.lvPath(ir.lv);
|
|
164
|
+
if (!path) { this.E(ir.lv.obj.e ?? { k: 'undef' }); this.emit(OP.POP); this.pushConst(true); return; }
|
|
165
|
+
const keys = this.hoistKeys(path.keys);
|
|
166
|
+
this.pushKeys(keys);
|
|
167
|
+
this.emit(OP.DELETE_PATH); this.u16(this.slot(path.root)); this.emit(keys.length);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
case 'assign': return this.assign(ir);
|
|
171
|
+
case 'update': return this.update(ir);
|
|
172
|
+
case 'seq': ir.exprs.forEach((e, i) => { this.E(e); if (i < ir.exprs.length - 1) this.emit(OP.POP); }); return;
|
|
173
|
+
case 'callm': return this.callm(ir);
|
|
174
|
+
case 'host': {
|
|
175
|
+
const id = hostIds[ir.fn];
|
|
176
|
+
if (id === undefined) this.bad(`host ${ir.fn}`);
|
|
177
|
+
for (const a of ir.args) this.E(a);
|
|
178
|
+
this.emit(OP.HOST, id, ir.args.length, (ir.q ? HOST_Q : 0) | (ir.void ? HOST_VOID : 0));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
case 'pre': for (const s of ir.stmts) this.S(s); this.E(ir.e); return;
|
|
182
|
+
case 'math': for (const a of ir.args) this.E(a); this.emit(OP.MATH); this.u16(this.k(ir.name)); this.emit(ir.args.length); return;
|
|
183
|
+
case 'global': for (const a of ir.args) this.E(a); this.emit(OP.GLOBAL); this.u16(this.k(ir.name)); this.emit(ir.args.length); return;
|
|
184
|
+
case 'jsonparse': this.E(ir.e); this.emit(OP.JSON_PARSE); return;
|
|
185
|
+
case 'jsonstringify': this.E(ir.e); this.emit(OP.JSON_STRINGIFY); return;
|
|
186
|
+
case 'keys': this.E(ir.e); this.emit(OP.KEYS); return;
|
|
187
|
+
case 'values': this.E(ir.e); this.emit(OP.VALUES); return;
|
|
188
|
+
case 'entries': this.E(ir.e); this.emit(OP.ENTRIES); return;
|
|
189
|
+
case 'isarray': this.E(ir.e); this.emit(OP.ISARRAY); return;
|
|
190
|
+
case 'newerror': for (const a of ir.args) this.E(a); this.emit(OP.NEW_ERROR); this.u16(this.k(ir.name)); this.emit(ir.args.length); return;
|
|
191
|
+
case 'log': for (const a of ir.args) this.E(a); this.emit(OP.LOG, ir.args.length); return;
|
|
192
|
+
case 'helper': {
|
|
193
|
+
const id = helperIds[ir.name];
|
|
194
|
+
if (id === undefined) this.bad(`helper ${ir.name}`);
|
|
195
|
+
for (const a of ir.args) this.E(a);
|
|
196
|
+
this.emit(OP.HELPER, id, ir.args.length);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
case 'resp': this.E(ir.body); this.E(ir.init); this.emit(OP.RESP); this.u16(this.k(ir.kind)); return;
|
|
200
|
+
case 'params':
|
|
201
|
+
if (ir.fn === 'query') { this.emit(OP.PARAMS, 0); return; }
|
|
202
|
+
if (ir.fn === 'params') { this.emit(OP.PARAMS, 1); return; }
|
|
203
|
+
this.emit(OP.PARAM); this.u16(this.k(ir.name)); this.emit(ir.catchAll ? 1 : 0); return;
|
|
204
|
+
case 'callh': {
|
|
205
|
+
for (const a of ir.args) this.E(a);
|
|
206
|
+
if (this.inlines.has(ir.name)) { this.emit(OP.CALL_INLINE); this.u32(this.inlines.get(ir.name)); this.emit(ir.args.length); return; }
|
|
207
|
+
const fi = this.mod.fnIndex.get(ir.name);
|
|
208
|
+
if (fi === undefined) this.bad(`call of unknown function ${ir.name}`);
|
|
209
|
+
this.emit(OP.CALL_FN); this.u16(fi); this.emit(ir.args.length);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
case 'constref': {
|
|
213
|
+
const fi = this.mod.fnIndex.get(ir.name);
|
|
214
|
+
if (fi === undefined) this.bad(`unknown const ${ir.name}`);
|
|
215
|
+
this.emit(OP.CALL_FN); this.u16(fi); this.emit(0);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
case 'iife': {
|
|
219
|
+
for (const a of ir.args) this.E(a);
|
|
220
|
+
const off = this.region(ir.fn);
|
|
221
|
+
this.emit(OP.CALL_INLINE); this.u32(off); this.emit(ir.args.length);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
default: this.bad(`IR expression ${ir.k}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
bad(what) { throw new BytecodeError(`bytecode: unsupported ${what}`); }
|
|
229
|
+
|
|
230
|
+
/** The handful of literal Rust snippets the lowering emits. */
|
|
231
|
+
raw(code) {
|
|
232
|
+
let m;
|
|
233
|
+
if ((m = code.match(/^__args\.get\((\d+)\)\.cloned\(\)\.unwrap_or\(Val::Undef\)$/))) { this.emit(OP.LOAD_ARG, Number(m[1])); return; }
|
|
234
|
+
if (code === 'Val::Arr(__args.to_vec())') { this.emit(OP.ARGS_FROM, 0); return; }
|
|
235
|
+
if (code === 'Val::Num(f64::NAN)') { this.pushConst(NaN); return; }
|
|
236
|
+
if (code === 'Val::Num(f64::INFINITY)') { this.pushConst(Infinity); return; }
|
|
237
|
+
if (code === 'Val::Num(f64::NEG_INFINITY)') { this.pushConst(-Infinity); return; }
|
|
238
|
+
this.bad(`raw Rust "${code}"`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Emit an inline region (callback / closure / iife body) out of line; returns its offset. */
|
|
242
|
+
region(fn) {
|
|
243
|
+
const over = this.jmp(OP.JMP);
|
|
244
|
+
const off = this.pos();
|
|
245
|
+
const savedTries = this.tries; this.tries = [];
|
|
246
|
+
const savedLabels = this.labels; this.labels = [];
|
|
247
|
+
for (const s of fn.params) this.S(s);
|
|
248
|
+
for (const s of fn.body) this.S(s);
|
|
249
|
+
this.emit(OP.PUSH_UNDEF, OP.RET);
|
|
250
|
+
this.tries = savedTries; this.labels = savedLabels;
|
|
251
|
+
this.patch(over);
|
|
252
|
+
return off;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---- conditions
|
|
256
|
+
|
|
257
|
+
C(c) {
|
|
258
|
+
switch (c.k) {
|
|
259
|
+
case 'c-truthy': this.E(c.e); return;
|
|
260
|
+
case 'c-not': this.C(c.c); this.emit(OP.UNARY, UNARY.not); return;
|
|
261
|
+
case 'c-and': { this.C(c.l); const at = this.jmp(OP.JF_KEEP); this.C(c.r); this.patch(at); return; }
|
|
262
|
+
case 'c-or': { this.C(c.l); const at = this.jmp(OP.JT_KEEP); this.C(c.r); this.patch(at); return; }
|
|
263
|
+
case 'c-cmp': this.E(c.l); this.E(c.r); this.emit(OP.CMP, CMP[c.op] ?? this.bad(`compare ${c.op}`)); return;
|
|
264
|
+
case 'c-nullish': this.E(c.e); this.emit(OP.NULLISH); return;
|
|
265
|
+
case 'c-undef': this.E(c.e); this.emit(OP.PUSH_UNDEF); this.emit(OP.CMP, CMP.strict_eq); return;
|
|
266
|
+
default: this.bad(`condition ${c.k}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---- lvalues
|
|
271
|
+
|
|
272
|
+
/** Flatten an lvalue into { root: localName, keys: [...] } or null when the base is an expression. */
|
|
273
|
+
lvPath(lv) {
|
|
274
|
+
const keys = [];
|
|
275
|
+
let cur = lv;
|
|
276
|
+
while (cur.k === 'lv-member') { keys.unshift(cur.key); cur = cur.obj; }
|
|
277
|
+
if (cur.k === 'lv-var') return { root: cur.name, keys };
|
|
278
|
+
return null; // lv-expr base
|
|
279
|
+
}
|
|
280
|
+
/** Evaluate computed keys once, left to right, into temps. Returns descriptors. */
|
|
281
|
+
hoistKeys(keys) {
|
|
282
|
+
return keys.map((key) => {
|
|
283
|
+
if (typeof key === 'string') return { s: key };
|
|
284
|
+
const t = this.temp();
|
|
285
|
+
this.E(key); this.emit(OP.STORE); this.u16(t);
|
|
286
|
+
return { t };
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
pushKeys(keys) { for (const k of keys) { if (k.s !== undefined) this.pushConst(k.s); else { this.emit(OP.LOAD); this.u16(k.t); } } }
|
|
290
|
+
readPath(root, keys) {
|
|
291
|
+
this.emit(OP.LOAD); this.u16(this.slot(root));
|
|
292
|
+
for (const k of keys) { if (k.s !== undefined) { this.emit(OP.GET_K); this.u16(this.k(k.s)); } else { this.emit(OP.LOAD); this.u16(k.t); this.emit(OP.GET); } }
|
|
293
|
+
}
|
|
294
|
+
/** Read an lvalue whose base is an expression (lv-expr chain). */
|
|
295
|
+
readLvExpr(lv) {
|
|
296
|
+
if (lv.k === 'lv-var') { this.emit(OP.LOAD); this.u16(this.slot(lv.name)); return; }
|
|
297
|
+
if (lv.k === 'lv-expr') { this.E(lv.e); return; }
|
|
298
|
+
this.readLvExpr(lv.obj);
|
|
299
|
+
if (typeof lv.key === 'string') { this.emit(OP.GET_K); this.u16(this.k(lv.key)); } else { this.E(lv.key); this.emit(OP.GET); }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
assign(ir) {
|
|
303
|
+
const v = this.temp();
|
|
304
|
+
const path = this.lvPath(ir.lv);
|
|
305
|
+
if (ir.lv.k === 'lv-var') {
|
|
306
|
+
if (ir.op) { this.emit(OP.LOAD); this.u16(this.slot(ir.lv.name)); this.E(ir.value); this.emit(OP.BIN, BIN[ir.op]); }
|
|
307
|
+
else this.E(ir.value);
|
|
308
|
+
this.emit(OP.DUP); this.emit(OP.STORE); this.u16(this.slot(ir.lv.name));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (path) {
|
|
312
|
+
const keys = this.hoistKeys(path.keys);
|
|
313
|
+
if (ir.op) { this.readPath(path.root, keys); this.E(ir.value); this.emit(OP.BIN, BIN[ir.op]); } else this.E(ir.value);
|
|
314
|
+
this.emit(OP.STORE); this.u16(v);
|
|
315
|
+
this.emit(OP.LOAD); this.u16(v);
|
|
316
|
+
this.pushKeys(keys);
|
|
317
|
+
this.emit(OP.STORE_PATH); this.u16(this.slot(path.root)); this.emit(keys.length);
|
|
318
|
+
this.emit(OP.LOAD); this.u16(v);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// expression base: evaluate for effects, the write is lost (as in the Rust backend)
|
|
322
|
+
if (ir.lv.k === 'lv-expr') { this.E(ir.value); return; }
|
|
323
|
+
if (ir.op) { this.readLvExpr(ir.lv); this.E(ir.value); this.emit(OP.BIN, BIN[ir.op]); } else this.E(ir.value);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
update(ir) {
|
|
328
|
+
const path = this.lvPath(ir.lv);
|
|
329
|
+
const old = this.temp(), nw = this.temp();
|
|
330
|
+
const keys = path ? this.hoistKeys(path.keys) : [];
|
|
331
|
+
if (path) this.readPath(path.root, keys); else this.readLvExpr(ir.lv);
|
|
332
|
+
this.emit(OP.UNARY, UNARY.plus); this.emit(OP.STORE); this.u16(old);
|
|
333
|
+
this.emit(OP.LOAD); this.u16(old); this.pushConst(1); this.emit(OP.BIN, ir.delta > 0 ? BIN.add : BIN.sub); this.emit(OP.STORE); this.u16(nw);
|
|
334
|
+
if (path) { this.emit(OP.LOAD); this.u16(nw); this.pushKeys(keys); this.emit(OP.STORE_PATH); this.u16(this.slot(path.root)); this.emit(keys.length); }
|
|
335
|
+
this.emit(OP.LOAD); this.u16(ir.prefix ? nw : old);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
callm(ir) {
|
|
339
|
+
const path = ir.lv ? this.lvPath(ir.lv) : null;
|
|
340
|
+
let keys = [];
|
|
341
|
+
if (path) { keys = this.hoistKeys(path.keys); this.readPath(path.root, keys); }
|
|
342
|
+
else if (ir.lv) this.readLvExpr(ir.lv);
|
|
343
|
+
else this.E(ir.recv);
|
|
344
|
+
for (const a of ir.args) this.E(a);
|
|
345
|
+
let flags = 0;
|
|
346
|
+
let cbOff = null;
|
|
347
|
+
if (ir.cb) {
|
|
348
|
+
if (ir.cb.kind === 'fn') { flags |= CALLM_CB; cbOff = this.region(ir.cb.fn); }
|
|
349
|
+
else if (ir.cb.kind === 'ref') {
|
|
350
|
+
flags |= CALLM_CB;
|
|
351
|
+
if (this.inlines.has(ir.cb.name)) cbOff = this.inlines.get(ir.cb.name);
|
|
352
|
+
else {
|
|
353
|
+
const fi = this.mod.fnIndex.get(ir.cb.name);
|
|
354
|
+
if (fi === undefined) this.bad(`callback ${ir.cb.name}`);
|
|
355
|
+
// wrap the module function in a tiny inline trampoline: args -> CALL_FN
|
|
356
|
+
const over = this.jmp(OP.JMP);
|
|
357
|
+
cbOff = this.pos();
|
|
358
|
+
this.emit(OP.ARGS_FROM, 0);
|
|
359
|
+
this.emit(OP.CALL_FN_SPREAD ?? OP.NOP); // placeholder, replaced below
|
|
360
|
+
this.code.length -= 2;
|
|
361
|
+
// simple: pass up to 3 positional args
|
|
362
|
+
this.code.length -= 0;
|
|
363
|
+
this.emit(OP.LOAD_ARG, 0, OP.LOAD_ARG, 1, OP.LOAD_ARG, 2, OP.CALL_FN); this.u16(fi); this.emit(3, OP.RET);
|
|
364
|
+
this.patch(over);
|
|
365
|
+
}
|
|
366
|
+
} else flags |= CALLM_DEFAULT_SORT;
|
|
367
|
+
}
|
|
368
|
+
if (path) flags |= CALLM_MUT;
|
|
369
|
+
this.emit(OP.CALLM); this.u16(this.k(ir.name)); this.emit(ir.args.length, flags);
|
|
370
|
+
if (flags & CALLM_CB) this.u32(cbOff);
|
|
371
|
+
if (path) { this.pushKeys(keys); this.emit(OP.STORE_PATH); this.u16(this.slot(path.root)); this.emit(keys.length); }
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---- statements
|
|
375
|
+
|
|
376
|
+
S(s) {
|
|
377
|
+
switch (s.k) {
|
|
378
|
+
case 'let': if (s.init) this.E(s.init); else this.emit(OP.PUSH_UNDEF); this.emit(OP.STORE); this.u16(this.slot(s.name)); return;
|
|
379
|
+
case 'hoistvar': this.emit(OP.PUSH_UNDEF); this.emit(OP.STORE); this.u16(this.slot(s.name)); return;
|
|
380
|
+
case 'expr': this.E(s.e); this.emit(OP.POP); return;
|
|
381
|
+
case 'block': for (const x of s.body) this.S(x); return;
|
|
382
|
+
case 'if': {
|
|
383
|
+
this.C(s.test);
|
|
384
|
+
const toElse = this.jmp(OP.JF);
|
|
385
|
+
for (const x of s.then) this.S(x);
|
|
386
|
+
if (s.else) { const toEnd = this.jmp(OP.JMP); this.patch(toElse); for (const x of s.else) this.S(x); this.patch(toEnd); }
|
|
387
|
+
else this.patch(toElse);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
case 'while': {
|
|
391
|
+
const ctx = this.loop(s.label);
|
|
392
|
+
const top = this.pos(); ctx.continueTarget = top;
|
|
393
|
+
this.C(s.test); const exit = this.jmp(OP.JF);
|
|
394
|
+
for (const x of s.body) this.S(x);
|
|
395
|
+
this.jumpTo(OP.JMP, top);
|
|
396
|
+
this.patch(exit); this.endLoop(ctx);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
case 'dowhile': {
|
|
400
|
+
const ctx = this.loop(s.label);
|
|
401
|
+
const top = this.pos();
|
|
402
|
+
for (const x of s.body) this.S(x);
|
|
403
|
+
ctx.continueTarget = this.pos(); this.patchContinues(ctx);
|
|
404
|
+
this.C(s.test); this.jumpTo(OP.JT, top);
|
|
405
|
+
this.endLoop(ctx);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
case 'for': {
|
|
409
|
+
for (const x of s.init) this.S(x);
|
|
410
|
+
const ctx = this.loop(s.label);
|
|
411
|
+
const top = this.pos();
|
|
412
|
+
let exit = null;
|
|
413
|
+
if (s.test) { this.C(s.test); exit = this.jmp(OP.JF); }
|
|
414
|
+
for (const x of s.body) this.S(x);
|
|
415
|
+
ctx.continueTarget = this.pos(); this.patchContinues(ctx);
|
|
416
|
+
for (const x of s.update) this.S(x);
|
|
417
|
+
this.jumpTo(OP.JMP, top);
|
|
418
|
+
if (exit !== null) this.patch(exit);
|
|
419
|
+
this.endLoop(ctx);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
case 'forof':
|
|
423
|
+
case 'forin': {
|
|
424
|
+
const arr = this.temp(), idx = this.temp();
|
|
425
|
+
this.E(s.iter);
|
|
426
|
+
if (s.k === 'forin') this.emit(OP.KEYS);
|
|
427
|
+
this.emit(OP.ITER_INIT); this.u16(arr); this.u16(idx);
|
|
428
|
+
const ctx = this.loop(s.label);
|
|
429
|
+
const top = this.pos(); ctx.continueTarget = top;
|
|
430
|
+
this.emit(OP.ITER_NEXT); this.u16(arr); this.u16(idx); this.u16(this.slot(s.tmp)); const exit = this.pos(); this.i32(0);
|
|
431
|
+
for (const x of s.body) this.S(x);
|
|
432
|
+
this.jumpTo(OP.JMP, top);
|
|
433
|
+
this.patch(exit); this.endLoop(ctx);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
case 'break': { const ctx = this.findLabel(s.label); this.unwindTo(ctx.tryDepth); ctx.breaks.push(this.jmp(OP.JMP)); return; }
|
|
437
|
+
case 'continue': {
|
|
438
|
+
const ctx = this.findLabel(s.label); this.unwindTo(ctx.tryDepth);
|
|
439
|
+
if (ctx.continueTarget != null) this.jumpTo(OP.JMP, ctx.continueTarget); else ctx.continues.push(this.jmp(OP.JMP));
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
case 'return': return this.ret(s);
|
|
443
|
+
case 'throw': {
|
|
444
|
+
this.E(s.e);
|
|
445
|
+
// a throw from a catch handler still runs that try's finalizer
|
|
446
|
+
for (let i = this.tries.length - 1; i >= 0; i--) { const t = this.tries[i]; if (t.phase === 'handler' && t.finalizer) this.finalizerCopy(t); }
|
|
447
|
+
this.emit(OP.THROW);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
case 'try': return this.tryStmt(s);
|
|
451
|
+
case 'switch': {
|
|
452
|
+
const d = this.temp();
|
|
453
|
+
this.E(s.disc); this.emit(OP.STORE); this.u16(d);
|
|
454
|
+
const ctx = this.loop(s.label); ctx.continueTarget = null; ctx.isSwitch = true;
|
|
455
|
+
const jumps = [];
|
|
456
|
+
let defaultIdx = -1;
|
|
457
|
+
s.cases.forEach((c, i) => {
|
|
458
|
+
if (c.test === null) { defaultIdx = i; return; }
|
|
459
|
+
this.emit(OP.LOAD); this.u16(d); this.E(c.test); this.emit(OP.CMP, CMP.strict_eq);
|
|
460
|
+
jumps.push([i, this.jmp(OP.JT)]);
|
|
461
|
+
});
|
|
462
|
+
const toDefault = this.jmp(OP.JMP);
|
|
463
|
+
const bodyStarts = [];
|
|
464
|
+
s.cases.forEach((c, i) => { bodyStarts[i] = this.pos(); for (const x of c.body) this.S(x); });
|
|
465
|
+
for (const [i, at] of jumps) this.patch(at, bodyStarts[i]);
|
|
466
|
+
this.patch(toDefault, defaultIdx >= 0 ? bodyStarts[defaultIdx] : this.pos());
|
|
467
|
+
this.endLoop(ctx);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
case 'letfn': {
|
|
471
|
+
// captures are shared slots (the region reads the live locals)
|
|
472
|
+
const off = this.region(s.fn);
|
|
473
|
+
this.inlines.set(s.name, off);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
default: this.bad(`IR statement ${s.k}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
loop(label) { const ctx = { kind: 'loop', name: label, breaks: [], continues: [], continueTarget: null, tryDepth: this.tries.length }; this.labels.push(ctx); return ctx; }
|
|
481
|
+
patchContinues(ctx) { for (const at of ctx.continues) this.patch(at, ctx.continueTarget); ctx.continues = []; }
|
|
482
|
+
endLoop(ctx) { this.labels.pop(); for (const at of ctx.breaks) this.patch(at); if (ctx.continues.length) { if (ctx.continueTarget == null) this.bad('continue in switch'); this.patchContinues(ctx); } }
|
|
483
|
+
findLabel(name) {
|
|
484
|
+
for (let i = this.labels.length - 1; i >= 0; i--) { const l = this.labels[i]; if (l.kind === 'loop' && (!name || l.name === name)) return l; }
|
|
485
|
+
throw new BytecodeError(`bytecode: no loop for ${name || 'break/continue'}`);
|
|
486
|
+
}
|
|
487
|
+
/** Leaving `try` blocks on the way out of a loop/function: pop handlers, run finalizers. */
|
|
488
|
+
unwindTo(depth) {
|
|
489
|
+
for (let i = this.tries.length - 1; i >= depth; i--) {
|
|
490
|
+
const t = this.tries[i];
|
|
491
|
+
if (t.phase === 'body') this.emit(OP.TRY_POP);
|
|
492
|
+
if (t.phase !== 'final' && t.finalizer) this.finalizerCopy(t);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
finalizerCopy(t) {
|
|
496
|
+
const saved = t.phase; t.phase = 'final';
|
|
497
|
+
for (const x of t.finalizer) this.S(x);
|
|
498
|
+
t.phase = saved;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
ret(s) {
|
|
502
|
+
// value first (may itself throw inside the try), then unwind, then return
|
|
503
|
+
if (s.mode === 'node') { if (s.value) { this.E(s.value); this.emit(OP.POP); } this.unwindTo(0); this.emit(OP.PUSH_UNDEF, OP.RET); return; }
|
|
504
|
+
if (s.mode === 'web') {
|
|
505
|
+
if (!s.value) { this.unwindTo(0); this.emit(OP.PUSH_UNDEF, OP.RET); return; }
|
|
506
|
+
const t = this.temp();
|
|
507
|
+
this.E(s.value); this.emit(OP.STORE); this.u16(t);
|
|
508
|
+
this.unwindTo(0);
|
|
509
|
+
this.emit(OP.LOAD); this.u16(t); this.emit(OP.SEND, OP.PUSH_UNDEF, OP.RET);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const t = this.temp();
|
|
513
|
+
if (s.value) this.E(s.value); else this.emit(OP.PUSH_UNDEF);
|
|
514
|
+
this.emit(OP.STORE); this.u16(t);
|
|
515
|
+
this.unwindTo(0);
|
|
516
|
+
this.emit(OP.LOAD); this.u16(t); this.emit(OP.RET);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
tryStmt(s) {
|
|
520
|
+
const t = { finalizer: s.finalizer || null, phase: 'body' };
|
|
521
|
+
this.tries.push(t);
|
|
522
|
+
const toHandler = this.jmp(OP.TRY_PUSH);
|
|
523
|
+
for (const x of s.body) this.S(x);
|
|
524
|
+
this.emit(OP.TRY_POP);
|
|
525
|
+
t.phase = 'final';
|
|
526
|
+
if (s.finalizer) this.finalizerCopy(t);
|
|
527
|
+
const toEnd = this.jmp(OP.JMP);
|
|
528
|
+
this.patch(toHandler);
|
|
529
|
+
// exception value is on the stack
|
|
530
|
+
if (s.handler) {
|
|
531
|
+
t.phase = 'handler';
|
|
532
|
+
if (s.param) { this.emit(OP.CAUGHT); this.emit(OP.STORE); this.u16(this.slot(s.param)); } else this.emit(OP.POP);
|
|
533
|
+
for (const x of s.handler) this.S(x);
|
|
534
|
+
t.phase = 'final';
|
|
535
|
+
if (s.finalizer) this.finalizerCopy(t);
|
|
536
|
+
} else {
|
|
537
|
+
const e = this.temp();
|
|
538
|
+
this.emit(OP.STORE); this.u16(e);
|
|
539
|
+
t.phase = 'final';
|
|
540
|
+
if (s.finalizer) this.finalizerCopy(t);
|
|
541
|
+
this.emit(OP.LOAD); this.u16(e); this.emit(OP.THROW);
|
|
542
|
+
}
|
|
543
|
+
this.patch(toEnd);
|
|
544
|
+
this.tries.pop();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
finish() { return { nlocals: this.nlocals, code: Buffer.from(this.code) }; }
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Emit a module from lowered routes.
|
|
552
|
+
* @param {{ routes: Array<{ir: object}>, env: [string,string][] }} o routes as produced by lowerRoute (the `ir` field)
|
|
553
|
+
* @returns {Buffer}
|
|
554
|
+
*/
|
|
555
|
+
export function emitModule({ routes, env = [] }) {
|
|
556
|
+
const consts = new Consts();
|
|
557
|
+
const mod = { fnIndex: new Map(), fns: [] };
|
|
558
|
+
const routeDefs = [];
|
|
559
|
+
// Pass 1: assign function indexes (helpers + consts + route fns) so calls can be forward.
|
|
560
|
+
const plan = [];
|
|
561
|
+
for (const r of routes) {
|
|
562
|
+
const ir = r.ir;
|
|
563
|
+
for (const h of ir.helpers) { mod.fnIndex.set(h.name, mod.fns.length); mod.fns.push(null); plan.push({ kind: 'helper', fn: h.fn }); }
|
|
564
|
+
for (const c of ir.consts) { mod.fnIndex.set(c.name, mod.fns.length); mod.fns.push(null); plan.push({ kind: 'const', e: c.e }); }
|
|
565
|
+
const def = { style: ir.style === 'node' ? 0 : 1, params: (ir.params || []).map((p) => [consts.add(p.name), p.catchAll ? 1 : 0]), nodeFn: 0, methods: [] };
|
|
566
|
+
if (ir.style === 'node') { def.nodeFn = mod.fns.length; mod.fns.push(null); plan.push({ kind: 'route', fn: ir.node }); }
|
|
567
|
+
else for (const m of Object.keys(ir.methods)) { if (!(m in METHOD_INDEX)) continue; def.methods.push([METHOD_INDEX[m], mod.fns.length]); mod.fns.push(null); plan.push({ kind: 'route', fn: ir.methods[m] }); }
|
|
568
|
+
routeDefs.push(def);
|
|
569
|
+
}
|
|
570
|
+
// Pass 2: emit.
|
|
571
|
+
plan.forEach((p, i) => {
|
|
572
|
+
const f = new Fn(consts, mod);
|
|
573
|
+
if (p.kind === 'const') { f.E(p.e); f.emit(OP.RET); }
|
|
574
|
+
else { for (const s of p.fn.params) f.S(s); for (const s of p.fn.body) f.S(s); f.emit(OP.PUSH_UNDEF, OP.RET); }
|
|
575
|
+
mod.fns[i] = f.finish();
|
|
576
|
+
});
|
|
577
|
+
const envPairs = env.map(([k, v]) => [consts.add(String(k)), consts.add(String(v))]);
|
|
578
|
+
const parts = [MAGIC, Buffer.from([VERSION]), consts.encode()];
|
|
579
|
+
parts.push(u16(envPairs.length));
|
|
580
|
+
for (const [k, v] of envPairs) parts.push(u16(k), u16(v));
|
|
581
|
+
parts.push(u16(mod.fns.length));
|
|
582
|
+
for (const fn of mod.fns) parts.push(u16(fn.nlocals), u32(fn.code.length), fn.code);
|
|
583
|
+
parts.push(Buffer.from([routeDefs.length]));
|
|
584
|
+
for (const d of routeDefs) {
|
|
585
|
+
parts.push(Buffer.from([d.style, d.params.length]));
|
|
586
|
+
for (const [n, ca] of d.params) parts.push(u16(n), Buffer.from([ca]));
|
|
587
|
+
if (d.style === 0) parts.push(u16(d.nodeFn));
|
|
588
|
+
else { parts.push(Buffer.from([d.methods.length])); for (const [m, fi] of d.methods) parts.push(Buffer.from([m]), u16(fi)); }
|
|
589
|
+
}
|
|
590
|
+
return Buffer.concat(parts);
|
|
591
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Host-call and helper ids shared with runtime/zoo-host/src/vm.rs and helpers.rs.
|
|
2
|
+
export const hostIds = {
|
|
3
|
+
req_method: 0, req_path: 1, req_url: 2, req_full_url: 3, req_query: 4, req_query_get: 5, req_headers: 6, req_header: 7,
|
|
4
|
+
req_text: 8, req_body: 9, req_json: 10, res_status: 11, res_header: 12, res_json: 13, res_send: 14, res_end: 15,
|
|
5
|
+
res_redirect: 16, env: 17, env_obj: 18, now_ms: 19, now_iso: 20, kv_get: 21, kv_exists: 22, kv_set: 23, kv_incrby: 24,
|
|
6
|
+
kv_del: 25, slot: 26, payer_address: 27, program_address: 28,
|
|
7
|
+
};
|
|
8
|
+
export const helperIds = {
|
|
9
|
+
cookies: 0, cookie_get: 1, cookie_has: 2, cookie_all: 3, header_has: 4, query_has: 5, query_get_all: 6, search: 7,
|
|
10
|
+
assign: 8, omit: 9, from_entries: 10, array_from: 11, slice_from: 12, url: 13, num: 14, set_headers: 15,
|
|
11
|
+
};
|
|
@@ -8,10 +8,12 @@ import { Ineligible, functionMetaReason, deploymentWarnings, displayFile } from
|
|
|
8
8
|
import { readModule } from './parse.js';
|
|
9
9
|
import { lowerRoute } from './ir.js';
|
|
10
10
|
import { emitRoute, emitCrate, RustPrinter, DEFAULT_RUNTIME_PATH, sanitizeCrateName } from './rust.js';
|
|
11
|
+
import { emitModule } from './bytecode.js';
|
|
11
12
|
|
|
12
13
|
export { readModule, parseModule, stripTypes } from './parse.js';
|
|
13
14
|
export { lowerRoute, IR } from './ir.js';
|
|
14
15
|
export { emitRoute, emitCrate, RustPrinter, DEFAULT_RUNTIME_PATH, sanitizeCrateName, rustStr } from './rust.js';
|
|
16
|
+
export { emitModule, OP } from './bytecode.js';
|
|
15
17
|
export { Ineligible } from '../eligibility.js';
|
|
16
18
|
|
|
17
19
|
export const MANIFEST_VERSION = 1;
|
|
@@ -65,6 +67,7 @@ export function transmute(deployment, opts = {}) {
|
|
|
65
67
|
const report = { eligible: [], ineligible: [], warnings: deploymentWarnings(deployment) };
|
|
66
68
|
const routes = [];
|
|
67
69
|
const routeSources = [];
|
|
70
|
+
const loweredIrs = [];
|
|
68
71
|
const mergedEnv = {};
|
|
69
72
|
const referencedEnv = new Set();
|
|
70
73
|
let envDynamic = false;
|
|
@@ -92,6 +95,7 @@ export function transmute(deployment, opts = {}) {
|
|
|
92
95
|
continue;
|
|
93
96
|
}
|
|
94
97
|
routeSources.push(c.rust);
|
|
98
|
+
loweredIrs.push(c.ir);
|
|
95
99
|
for (const k of c.env) referencedEnv.add(k);
|
|
96
100
|
if (c.envDynamic) envDynamic = true;
|
|
97
101
|
Object.assign(mergedEnv, fn.environment || {});
|
|
@@ -118,8 +122,16 @@ export function transmute(deployment, opts = {}) {
|
|
|
118
122
|
`${deployment.source || 'deployment'}${deployment.framework ? ` (${deployment.framework})` : ''} — ${routes.length} route(s), ${(deployment.staticFiles || []).length} static file(s)`,
|
|
119
123
|
...routes.map((r) => `route ${r.index}: ${r.routePath}${r.methods ? ' [' + r.methods.join(',') + ']' : ''} ← ${r.name}`),
|
|
120
124
|
].join('\n');
|
|
121
|
-
const
|
|
122
|
-
|
|
125
|
+
const target = opts.target === 'shared' ? 'shared' : 'program';
|
|
126
|
+
let crate = null;
|
|
127
|
+
let code = null;
|
|
128
|
+
if (target === 'shared') {
|
|
129
|
+
// The shared runtime: a bytecode module (a few KB) instead of a crate.
|
|
130
|
+
code = emitModule({ routes: loweredIrs.map((ir) => ({ ir })), env });
|
|
131
|
+
} else {
|
|
132
|
+
const crateFiles = emitCrate({ name, runtimePath, env, routes: routeSources, routeCount: routes.length, header });
|
|
133
|
+
crate = { 'Cargo.toml': crateFiles['Cargo.toml'], 'src/lib.rs': crateFiles['src/lib.rs'] };
|
|
134
|
+
}
|
|
123
135
|
|
|
124
136
|
const manifest = {
|
|
125
137
|
version: MANIFEST_VERSION,
|
|
@@ -128,8 +140,9 @@ export function transmute(deployment, opts = {}) {
|
|
|
128
140
|
static: (deployment.staticFiles || []).map((f) => ({ path: f.path, contentType: f.contentType, size: f.size })),
|
|
129
141
|
env: envNames,
|
|
130
142
|
config: deployment.config,
|
|
143
|
+
target,
|
|
131
144
|
};
|
|
132
|
-
return { crate, manifest, report, crateName:
|
|
145
|
+
return { crate, code, manifest, report, crateName: crate ? name : null, target };
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
/** Write a transmuted crate to `dir` (creates src/). Returns the paths written. */
|