jsguardian 1.2.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/adversarial-tokens.js +176 -0
- package/ai-antipattern.js +235 -0
- package/ai-callgraph-poison.js +331 -0
- package/ai-confusion.js +644 -0
- package/ai-semantic-poison.js +276 -0
- package/canary.js +158 -0
- package/cne.js +686 -0
- package/index.js +248 -0
- package/integrity.js +47 -0
- package/jsobf-config.js +38 -0
- package/krak-compiler.js +1480 -0
- package/krak-vm-core.js +892 -0
- package/layers.js +136 -0
- package/opaque-pred.js +32 -0
- package/package.json +32 -0
- package/pipeline.js +327 -0
- package/prng.js +28 -0
- package/signature-break.js +101 -0
- package/temporal-keys.js +194 -0
- package/timing-oracle.js +129 -0
- package/transform-vm.js +266 -0
- package/transforms.js +371 -0
- package/vm-poison.js +247 -0
package/krak-compiler.js
ADDED
|
@@ -0,0 +1,1480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KrakCompiler = exports.KrakBailError = void 0;
|
|
4
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
5
|
+
class KrakBailError extends Error {
|
|
6
|
+
}
|
|
7
|
+
exports.KrakBailError = KrakBailError;
|
|
8
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
9
|
+
// 32-bit LCG: full 32-bit state, keystream byte = key >>> 24
|
|
10
|
+
function computeKeyAtAddress(initialKey, address, lcgMul, lcgInc) {
|
|
11
|
+
let key = initialKey >>> 0;
|
|
12
|
+
for (let i = 0; i < address; i++)
|
|
13
|
+
key = ((Math.imul(key, lcgMul) + lcgInc) | 0) >>> 0;
|
|
14
|
+
return key;
|
|
15
|
+
}
|
|
16
|
+
// ── Compiler ──────────────────────────────────────────────────────────────────
|
|
17
|
+
class KrakCompiler {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.bytecodeArr = [];
|
|
20
|
+
this.labels = {};
|
|
21
|
+
this.variables = {};
|
|
22
|
+
this.nextVarSlot = 4;
|
|
23
|
+
this.pendingJumps = [];
|
|
24
|
+
this.functions = {};
|
|
25
|
+
this.loopStack = [];
|
|
26
|
+
// Inline-callback return context: ReturnStatement inside map/filter/forEach
|
|
27
|
+
// callback must jump to a per-callback exit label rather than emitting RET
|
|
28
|
+
// (which would exit the whole VM function).
|
|
29
|
+
this.inlineCallbackStack = [];
|
|
30
|
+
// Hardening: randomise temp reg pool
|
|
31
|
+
this.hardeningEnabled = false;
|
|
32
|
+
this.hardeningSalt = 0;
|
|
33
|
+
this.tempRegMap = {};
|
|
34
|
+
this.tempRegUsed = {};
|
|
35
|
+
this.config = config;
|
|
36
|
+
this.opcodes = config.opcodes;
|
|
37
|
+
this.layouts = config.argLayouts;
|
|
38
|
+
}
|
|
39
|
+
// ── Var / reg management ──────────────────────────────────────────────────
|
|
40
|
+
allocVar(name) {
|
|
41
|
+
if (this.variables[name] === undefined)
|
|
42
|
+
this.variables[name] = this.nextVarSlot++;
|
|
43
|
+
return this.variables[name];
|
|
44
|
+
}
|
|
45
|
+
getVarReg(name) {
|
|
46
|
+
const slot = this.variables[name];
|
|
47
|
+
if (slot === undefined)
|
|
48
|
+
throw new KrakBailError("Undefined variable: " + name);
|
|
49
|
+
return slot % 256;
|
|
50
|
+
}
|
|
51
|
+
getTempReg(index) {
|
|
52
|
+
if (!this.hardeningEnabled)
|
|
53
|
+
return 200 + index;
|
|
54
|
+
if (this.tempRegMap[index] !== undefined)
|
|
55
|
+
return this.tempRegMap[index];
|
|
56
|
+
const poolStart = 200, poolSize = 54;
|
|
57
|
+
const start = ((index * 7) + this.hardeningSalt) % poolSize;
|
|
58
|
+
for (let probe = 0; probe < poolSize; probe++) {
|
|
59
|
+
const reg = poolStart + ((start + probe) % poolSize);
|
|
60
|
+
if (!this.tempRegUsed[reg]) {
|
|
61
|
+
this.tempRegUsed[reg] = true;
|
|
62
|
+
this.tempRegMap[index] = reg;
|
|
63
|
+
return reg;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const fb = poolStart + (index % poolSize);
|
|
67
|
+
this.tempRegMap[index] = fb;
|
|
68
|
+
return fb;
|
|
69
|
+
}
|
|
70
|
+
// ── Bytecode emission ─────────────────────────────────────────────────────
|
|
71
|
+
emit(bytes) {
|
|
72
|
+
for (const b of bytes)
|
|
73
|
+
this.bytecodeArr.push(b & 0xFF);
|
|
74
|
+
}
|
|
75
|
+
currentAddress() { return this.bytecodeArr.length; }
|
|
76
|
+
setLabel(name) { this.labels[name] = this.currentAddress(); }
|
|
77
|
+
// Encode args according to the per-build shuffled layout.
|
|
78
|
+
encodeArgs(layoutName, argValues) {
|
|
79
|
+
if (layoutName === "FUNC") {
|
|
80
|
+
if (argValues["k"] === undefined)
|
|
81
|
+
argValues["k"] = 0;
|
|
82
|
+
if (argValues["n"] === undefined)
|
|
83
|
+
argValues["n"] = 0;
|
|
84
|
+
}
|
|
85
|
+
const layout = this.layouts[layoutName] ?? [];
|
|
86
|
+
const result = [];
|
|
87
|
+
for (const argDef of layout) {
|
|
88
|
+
const value = argValues[argDef.name];
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
throw new KrakBailError(`Missing arg: ${argDef.name} for ${layoutName}`);
|
|
91
|
+
if (argDef.type === "BYTE") {
|
|
92
|
+
result.push(value & 0xFF);
|
|
93
|
+
}
|
|
94
|
+
else if (argDef.type === "INT") {
|
|
95
|
+
result.push(value & 0xFF);
|
|
96
|
+
result.push((value >> 8) & 0xFF);
|
|
97
|
+
result.push((value >> 16) & 0xFF);
|
|
98
|
+
result.push((value >> 24) & 0xFF);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
// ── Emit helpers ──────────────────────────────────────────────────────────
|
|
104
|
+
emitMovInt(reg, value) {
|
|
105
|
+
if (reg === 1 && value === 0)
|
|
106
|
+
return;
|
|
107
|
+
this.emit([this.opcodes["MOV"]]);
|
|
108
|
+
this.emit(this.encodeArgs("MOV", { r: reg, v: value }));
|
|
109
|
+
}
|
|
110
|
+
emitMovBool(reg, val) {
|
|
111
|
+
this.emit([this.opcodes["BOOL"]]);
|
|
112
|
+
this.emit(this.encodeArgs("BOOL", { d: reg, v: val ? 1 : 0 }));
|
|
113
|
+
}
|
|
114
|
+
emitMovReg(dest, src) {
|
|
115
|
+
if (dest === src)
|
|
116
|
+
return;
|
|
117
|
+
this.emit([this.opcodes["MOVR"]]);
|
|
118
|
+
this.emit(this.encodeArgs("MOVR", { d: dest, s: src }));
|
|
119
|
+
}
|
|
120
|
+
emitBinaryOp(opName, dest, src) {
|
|
121
|
+
this.emit([this.opcodes[opName]]);
|
|
122
|
+
this.emit(this.encodeArgs(opName, { d: dest, s: src }));
|
|
123
|
+
}
|
|
124
|
+
emitPush(reg) {
|
|
125
|
+
this.emit([this.opcodes["PUSH"]]);
|
|
126
|
+
this.emit(this.encodeArgs("PUSH", { r: reg }));
|
|
127
|
+
}
|
|
128
|
+
emitPop(reg) {
|
|
129
|
+
this.emit([this.opcodes["POP"]]);
|
|
130
|
+
this.emit(this.encodeArgs("POP", { r: reg }));
|
|
131
|
+
}
|
|
132
|
+
emitNewStr(destReg, str) {
|
|
133
|
+
this.emit([this.opcodes["STR"]]);
|
|
134
|
+
this.emit(this.encodeArgs("STR", { d: destReg }));
|
|
135
|
+
const len = str.length;
|
|
136
|
+
this.emit([len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF]);
|
|
137
|
+
for (let i = 0; i < str.length; i++)
|
|
138
|
+
this.emit([str.charCodeAt(i) & 0xFF]);
|
|
139
|
+
}
|
|
140
|
+
emitGetGlobal(destReg, nameReg) {
|
|
141
|
+
this.emit([this.opcodes["GGLO"]]);
|
|
142
|
+
this.emit(this.encodeArgs("GGLO", { d: destReg, n: nameReg }));
|
|
143
|
+
}
|
|
144
|
+
emitGetProp(destReg, objReg, propReg) {
|
|
145
|
+
this.emit([this.opcodes["GPRP"]]);
|
|
146
|
+
this.emit(this.encodeArgs("GPRP", { d: destReg, o: objReg, p: propReg }));
|
|
147
|
+
}
|
|
148
|
+
emitSetProp(objReg, propReg, valueReg) {
|
|
149
|
+
this.emit([this.opcodes["SPRP"]]);
|
|
150
|
+
this.emit(this.encodeArgs("SPRP", { o: objReg, p: propReg, v: valueReg }));
|
|
151
|
+
}
|
|
152
|
+
emitCallMethod(destReg, objReg, methodReg, argCount) {
|
|
153
|
+
this.emit([this.opcodes["METH"]]);
|
|
154
|
+
this.emit(this.encodeArgs("METH", { d: destReg, o: objReg, m: methodReg, c: argCount }));
|
|
155
|
+
}
|
|
156
|
+
emitFromNum(destReg, srcReg) {
|
|
157
|
+
this.emit([this.opcodes["FNUM"]]);
|
|
158
|
+
this.emit(this.encodeArgs("FNUM", { d: destReg, s: srcReg }));
|
|
159
|
+
}
|
|
160
|
+
emitToNum(destReg, srcReg) {
|
|
161
|
+
this.emit([this.opcodes["TNUM"]]);
|
|
162
|
+
this.emit(this.encodeArgs("TNUM", { d: destReg, s: srcReg }));
|
|
163
|
+
}
|
|
164
|
+
emitCallAddr(targetLabel) {
|
|
165
|
+
this.emit([this.opcodes["CADR"]]);
|
|
166
|
+
this.pendingJumps.push({ type: "CADR", address: this.currentAddress(), label: targetLabel });
|
|
167
|
+
this.emit(this.encodeArgs("CADR", { k: 0, t: 0 }));
|
|
168
|
+
}
|
|
169
|
+
emitNew(destReg, constructorReg, argCount) {
|
|
170
|
+
this.emit([this.opcodes["NEW"]]);
|
|
171
|
+
this.emit(this.encodeArgs("NEW", { d: destReg, c: constructorReg, n: argCount }));
|
|
172
|
+
}
|
|
173
|
+
emitInstanceOf(destReg, objReg, constrReg) {
|
|
174
|
+
this.emit([this.opcodes["IOF"]]);
|
|
175
|
+
this.emit(this.encodeArgs("IOF", { d: destReg, o: objReg, c: constrReg }));
|
|
176
|
+
}
|
|
177
|
+
emitTypeof(destReg, srcReg) {
|
|
178
|
+
this.emit([this.opcodes["TYP"]]);
|
|
179
|
+
this.emit(this.encodeArgs("TYP", { d: destReg, s: srcReg }));
|
|
180
|
+
}
|
|
181
|
+
emitRet() { this.emit([this.opcodes["RET"]]); }
|
|
182
|
+
emitJz(reg, targetLabel) {
|
|
183
|
+
this.emit([this.opcodes["JZ"]]);
|
|
184
|
+
this.pendingJumps.push({ type: "JZ", address: this.currentAddress(), label: targetLabel, reg });
|
|
185
|
+
this.emit(this.encodeArgs("JZ", { r: reg, k: 0, t: 0 }));
|
|
186
|
+
}
|
|
187
|
+
emitJnz(reg, targetLabel) {
|
|
188
|
+
this.emit([this.opcodes["JNZ"]]);
|
|
189
|
+
this.pendingJumps.push({ type: "JNZ", address: this.currentAddress(), label: targetLabel, reg });
|
|
190
|
+
this.emit(this.encodeArgs("JNZ", { r: reg, k: 0, t: 0 }));
|
|
191
|
+
}
|
|
192
|
+
emitJmp(targetLabel) {
|
|
193
|
+
this.emit([this.opcodes["JMP"]]);
|
|
194
|
+
this.pendingJumps.push({ type: "JMP", address: this.currentAddress(), label: targetLabel });
|
|
195
|
+
this.emit(this.encodeArgs("JMP", { k: 0, t: 0 }));
|
|
196
|
+
}
|
|
197
|
+
emitJgt(targetLabel) {
|
|
198
|
+
this.emit([this.opcodes["JGT"]]);
|
|
199
|
+
this.pendingJumps.push({ type: "JGT", address: this.currentAddress(), label: targetLabel });
|
|
200
|
+
this.emit(this.encodeArgs("JGT", { k: 0, t: 0 }));
|
|
201
|
+
}
|
|
202
|
+
emitJlt(targetLabel) {
|
|
203
|
+
this.emit([this.opcodes["JLT"]]);
|
|
204
|
+
this.pendingJumps.push({ type: "JLT", address: this.currentAddress(), label: targetLabel });
|
|
205
|
+
this.emit(this.encodeArgs("JLT", { k: 0, t: 0 }));
|
|
206
|
+
}
|
|
207
|
+
// emitCallReg: check if fn is internal (number type) or external (function).
|
|
208
|
+
emitCallReg(destReg, funcReg, argCount) {
|
|
209
|
+
const typeReg = this.getTempReg(46); // reg 246 — safe, below flags reg 255
|
|
210
|
+
const numTypeReg = this.getTempReg(47); // reg 247
|
|
211
|
+
this.emitTypeof(typeReg, funcReg);
|
|
212
|
+
this.emitNewStr(numTypeReg, "number");
|
|
213
|
+
this.emit([this.opcodes["CMP"]]);
|
|
214
|
+
this.emit(this.encodeArgs("CMP", { a: typeReg, b: numTypeReg }));
|
|
215
|
+
const externalLabel = `__creg_ext_${this.currentAddress()}`;
|
|
216
|
+
const endLabel = `__creg_end_${this.currentAddress()}`;
|
|
217
|
+
this.emitJnz(255, externalLabel);
|
|
218
|
+
this.emit([this.opcodes["CREGI"]]);
|
|
219
|
+
this.emit(this.encodeArgs("CREGI", { f: funcReg, n: argCount }));
|
|
220
|
+
if (destReg !== 0)
|
|
221
|
+
this.emitMovReg(destReg, 0);
|
|
222
|
+
this.emitJmp(endLabel);
|
|
223
|
+
this.setLabel(externalLabel);
|
|
224
|
+
this.emit([this.opcodes["CREGE"]]);
|
|
225
|
+
this.emit(this.encodeArgs("CREGE", { d: destReg, f: funcReg, n: argCount }));
|
|
226
|
+
this.setLabel(endLabel);
|
|
227
|
+
}
|
|
228
|
+
// ── compileMemberBase ─────────────────────────────────────────────────────
|
|
229
|
+
compileMemberBase(node, objReg, propReg) {
|
|
230
|
+
if (node.object.type === "Identifier" && this.variables[node.object.name] === undefined) {
|
|
231
|
+
this.emitNewStr(this.getTempReg(0), node.object.name);
|
|
232
|
+
this.emitGetGlobal(objReg, this.getTempReg(0));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.compileExpression(node.object, objReg);
|
|
236
|
+
}
|
|
237
|
+
if (node.computed) {
|
|
238
|
+
this.compileExpression(node.property, propReg);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
const propName = node.property.name ?? node.property.value;
|
|
242
|
+
this.emitNewStr(propReg, String(propName));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
compileMemberExpression(node, targetReg) {
|
|
246
|
+
const objReg = this.getTempReg(20);
|
|
247
|
+
const propReg = this.getTempReg(21);
|
|
248
|
+
this.compileMemberBase(node, objReg, propReg);
|
|
249
|
+
this.emitGetProp(targetReg, objReg, propReg);
|
|
250
|
+
}
|
|
251
|
+
// ── tryCompileConditionExit ───────────────────────────────────────────────
|
|
252
|
+
tryCompileConditionExit(node, exitLabel) {
|
|
253
|
+
if (node.type !== "BinaryExpression")
|
|
254
|
+
return false;
|
|
255
|
+
const op = node.operator;
|
|
256
|
+
if (!["<", "<=", ">", ">=", "==", "===", "!=", "!=="].includes(op))
|
|
257
|
+
return false;
|
|
258
|
+
let leftReg;
|
|
259
|
+
if (node.left.type === "Identifier" && this.variables[node.left.name] !== undefined) {
|
|
260
|
+
leftReg = this.getVarReg(node.left.name);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
leftReg = this.getTempReg(48); // reg 248 — was 254 (too close to flags)
|
|
264
|
+
this.compileExpression(node.left, leftReg);
|
|
265
|
+
}
|
|
266
|
+
let rightReg;
|
|
267
|
+
if (node.right.type === "Identifier" && this.variables[node.right.name] !== undefined) {
|
|
268
|
+
rightReg = this.getVarReg(node.right.name);
|
|
269
|
+
}
|
|
270
|
+
else if (node.right.type === "NumericLiteral" && node.right.value === 0) {
|
|
271
|
+
rightReg = 1;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
rightReg = this.getTempReg(49); // reg 249 — was 255 (CMP flags reg, conflicting)
|
|
275
|
+
this.compileExpression(node.right, rightReg);
|
|
276
|
+
}
|
|
277
|
+
this.emit([this.opcodes["CMP"]]);
|
|
278
|
+
this.emit(this.encodeArgs("CMP", { a: leftReg, b: rightReg }));
|
|
279
|
+
switch (op) {
|
|
280
|
+
case "<":
|
|
281
|
+
this.emitJgt(exitLabel);
|
|
282
|
+
this.emitJz(255, exitLabel);
|
|
283
|
+
break;
|
|
284
|
+
case "<=":
|
|
285
|
+
this.emitJgt(exitLabel);
|
|
286
|
+
break;
|
|
287
|
+
case ">":
|
|
288
|
+
this.emitJlt(exitLabel);
|
|
289
|
+
this.emitJz(255, exitLabel);
|
|
290
|
+
break;
|
|
291
|
+
case ">=":
|
|
292
|
+
this.emitJlt(exitLabel);
|
|
293
|
+
break;
|
|
294
|
+
case "==":
|
|
295
|
+
case "===":
|
|
296
|
+
this.emitJlt(exitLabel);
|
|
297
|
+
this.emitJgt(exitLabel);
|
|
298
|
+
break;
|
|
299
|
+
case "!=":
|
|
300
|
+
case "!==":
|
|
301
|
+
this.emitJz(255, exitLabel);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
// ── compileCallExpression ─────────────────────────────────────────────────
|
|
307
|
+
compileCallExpression(node, targetReg) {
|
|
308
|
+
// Handle array methods with inline callbacks
|
|
309
|
+
if (node.callee.type === "MemberExpression" && !node.callee.computed && node.callee.property.name) {
|
|
310
|
+
const methodName = node.callee.property.name;
|
|
311
|
+
const isCbMethod = ["map", "filter", "forEach", "reduce"].includes(methodName);
|
|
312
|
+
if (isCbMethod) {
|
|
313
|
+
if (node.arguments.length < 1)
|
|
314
|
+
throw new KrakBailError(`Array.${methodName} requires a callback`);
|
|
315
|
+
const callbackArg = node.arguments[0];
|
|
316
|
+
const cbType = callbackArg.type;
|
|
317
|
+
const isInlineCallback = cbType === "ArrowFunctionExpression" || cbType === "FunctionExpression";
|
|
318
|
+
// ── reduce ── compile as: acc = init; for (i=0; i<len; i++) acc = cb(acc, arr[i], i, arr)
|
|
319
|
+
if (methodName === "reduce") {
|
|
320
|
+
const uid = this.currentAddress();
|
|
321
|
+
const colReg = this.allocVar(`__rd_col_${uid}`) % 256;
|
|
322
|
+
const lenReg = this.allocVar(`__rd_len_${uid}`) % 256;
|
|
323
|
+
const idxReg = this.allocVar(`__rd_idx_${uid}`) % 256;
|
|
324
|
+
const accReg = this.allocVar(`__rd_acc_${uid}`) % 256;
|
|
325
|
+
const elemReg = this.allocVar(`__rd_el_${uid}`) % 256;
|
|
326
|
+
this.compileExpression(node.callee.object, colReg);
|
|
327
|
+
this.emitNewStr(this.getTempReg(4), "length");
|
|
328
|
+
this.emitGetProp(lenReg, colReg, this.getTempReg(4));
|
|
329
|
+
this.emitToNum(lenReg, lenReg);
|
|
330
|
+
// initial accumulator and start index
|
|
331
|
+
let startIdx = 0;
|
|
332
|
+
if (node.arguments.length >= 2) {
|
|
333
|
+
this.compileExpression(node.arguments[1], accReg);
|
|
334
|
+
startIdx = 0;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// no initial value: acc = arr[0], start loop at 1
|
|
338
|
+
this.emitMovInt(this.getTempReg(4), 0);
|
|
339
|
+
this.emitFromNum(this.getTempReg(4), this.getTempReg(4));
|
|
340
|
+
this.emitGetProp(accReg, colReg, this.getTempReg(4));
|
|
341
|
+
startIdx = 1;
|
|
342
|
+
}
|
|
343
|
+
this.emitMovInt(idxReg, startIdx);
|
|
344
|
+
const loopStart = `__rd_loop_${uid}`, loopEnd = `__rd_end_${uid}`, loopBody = `__rd_body_${uid}`;
|
|
345
|
+
this.setLabel(loopStart);
|
|
346
|
+
this.emit([this.opcodes["CMP"]]);
|
|
347
|
+
this.emit(this.encodeArgs("CMP", { a: idxReg, b: lenReg }));
|
|
348
|
+
this.emit([this.opcodes["JLT"]]);
|
|
349
|
+
this.pendingJumps.push({ type: "JLT", address: this.currentAddress(), label: loopBody });
|
|
350
|
+
this.emit(this.encodeArgs("JLT", { k: 0, t: 0 }));
|
|
351
|
+
this.emitJmp(loopEnd);
|
|
352
|
+
this.setLabel(loopBody);
|
|
353
|
+
this.emitFromNum(this.getTempReg(4), idxReg);
|
|
354
|
+
this.emitGetProp(elemReg, colReg, this.getTempReg(4));
|
|
355
|
+
if (isInlineCallback) {
|
|
356
|
+
const callbackArity = callbackArg.params.length;
|
|
357
|
+
const savedVars = this.variables;
|
|
358
|
+
const savedNext = this.nextVarSlot;
|
|
359
|
+
this.variables = Object.assign({}, this.variables);
|
|
360
|
+
if (callbackArity >= 1) {
|
|
361
|
+
const p0 = this.allocVar(callbackArg.params[0].name) % 256;
|
|
362
|
+
this.emitMovReg(p0, accReg);
|
|
363
|
+
}
|
|
364
|
+
if (callbackArity >= 2) {
|
|
365
|
+
const p1 = this.allocVar(callbackArg.params[1].name) % 256;
|
|
366
|
+
this.emitMovReg(p1, elemReg);
|
|
367
|
+
}
|
|
368
|
+
if (callbackArity >= 3) {
|
|
369
|
+
const p2 = this.allocVar(callbackArg.params[2].name) % 256;
|
|
370
|
+
this.emitMovReg(p2, idxReg);
|
|
371
|
+
}
|
|
372
|
+
if (callbackArity >= 4) {
|
|
373
|
+
const p3 = this.allocVar(callbackArg.params[3].name) % 256;
|
|
374
|
+
this.emitMovReg(p3, colReg);
|
|
375
|
+
}
|
|
376
|
+
if (callbackArg.body.type === "BlockStatement") {
|
|
377
|
+
const cbRetLabel = `__rd_ret_${uid}_${this.currentAddress()}`;
|
|
378
|
+
this.inlineCallbackStack.push({ cbResReg: accReg, returnLabel: cbRetLabel });
|
|
379
|
+
this.compileStatement(callbackArg.body);
|
|
380
|
+
this.inlineCallbackStack.pop();
|
|
381
|
+
this.setLabel(cbRetLabel);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
this.compileExpression(callbackArg.body, accReg);
|
|
385
|
+
}
|
|
386
|
+
this.variables = savedVars;
|
|
387
|
+
this.nextVarSlot = savedNext;
|
|
388
|
+
}
|
|
389
|
+
else if (cbType === "Identifier") {
|
|
390
|
+
const callReg = this.getVarReg(callbackArg.name);
|
|
391
|
+
this.emitPush(accReg);
|
|
392
|
+
this.emitPush(elemReg);
|
|
393
|
+
this.emitPush(idxReg);
|
|
394
|
+
this.emitPush(colReg);
|
|
395
|
+
this.emitCallReg(accReg, callReg, 4);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
throw new KrakBailError("unsupported reduce callback type: " + cbType);
|
|
399
|
+
}
|
|
400
|
+
this.emit([this.opcodes["INC"]]);
|
|
401
|
+
this.emit(this.encodeArgs("INC", { r: idxReg }));
|
|
402
|
+
this.emitJmp(loopStart);
|
|
403
|
+
this.setLabel(loopEnd);
|
|
404
|
+
if (targetReg !== accReg)
|
|
405
|
+
this.emitMovReg(targetReg, accReg);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// ── map / filter / forEach ────────────────────────────────────────────
|
|
409
|
+
const uid = this.currentAddress();
|
|
410
|
+
const colReg = this.allocVar(`__cb_col_${uid}`) % 256;
|
|
411
|
+
const lenReg = this.allocVar(`__cb_len_${uid}`) % 256;
|
|
412
|
+
const idxReg = this.allocVar(`__cb_idx_${uid}`) % 256;
|
|
413
|
+
const resReg = this.allocVar(`__cb_res_${uid}`) % 256;
|
|
414
|
+
const elemReg = this.allocVar(`__cb_el_${uid}`) % 256;
|
|
415
|
+
const cbResReg = this.allocVar(`__cb_val_${uid}`) % 256;
|
|
416
|
+
this.compileExpression(node.callee.object, colReg);
|
|
417
|
+
this.emitNewStr(this.getTempReg(4), "length");
|
|
418
|
+
this.emitGetProp(lenReg, colReg, this.getTempReg(4));
|
|
419
|
+
this.emitToNum(lenReg, lenReg);
|
|
420
|
+
if (methodName === "map" || methodName === "filter") {
|
|
421
|
+
const arrayConstReg = this.getTempReg(36);
|
|
422
|
+
this.emitNewStr(this.getTempReg(37), "Array");
|
|
423
|
+
this.emitGetGlobal(arrayConstReg, this.getTempReg(37));
|
|
424
|
+
this.emitNew(resReg, arrayConstReg, 0);
|
|
425
|
+
}
|
|
426
|
+
this.emitMovInt(idxReg, 0);
|
|
427
|
+
const loopStart = `__cb_loop_${uid}`, loopEnd = `__cb_end_${uid}`, loopBody = `__cb_body_${uid}`;
|
|
428
|
+
this.setLabel(loopStart);
|
|
429
|
+
this.emit([this.opcodes["CMP"]]);
|
|
430
|
+
this.emit(this.encodeArgs("CMP", { a: idxReg, b: lenReg }));
|
|
431
|
+
this.emit([this.opcodes["JLT"]]);
|
|
432
|
+
this.pendingJumps.push({ type: "JLT", address: this.currentAddress(), label: loopBody });
|
|
433
|
+
this.emit(this.encodeArgs("JLT", { k: 0, t: 0 }));
|
|
434
|
+
this.emitJmp(loopEnd);
|
|
435
|
+
this.setLabel(loopBody);
|
|
436
|
+
this.emitFromNum(this.getTempReg(4), idxReg);
|
|
437
|
+
this.emitGetProp(elemReg, colReg, this.getTempReg(4));
|
|
438
|
+
if (isInlineCallback) {
|
|
439
|
+
const callbackArity = callbackArg.params.length;
|
|
440
|
+
const savedVars = this.variables;
|
|
441
|
+
const savedNext = this.nextVarSlot;
|
|
442
|
+
this.variables = Object.assign({}, this.variables);
|
|
443
|
+
if (callbackArity >= 1) {
|
|
444
|
+
const p0 = this.allocVar(callbackArg.params[0].name) % 256;
|
|
445
|
+
this.emitMovReg(p0, elemReg);
|
|
446
|
+
}
|
|
447
|
+
if (callbackArity >= 2) {
|
|
448
|
+
const p1 = this.allocVar(callbackArg.params[1].name) % 256;
|
|
449
|
+
this.emitMovReg(p1, idxReg);
|
|
450
|
+
}
|
|
451
|
+
if (callbackArity >= 3) {
|
|
452
|
+
const p2 = this.allocVar(callbackArg.params[2].name) % 256;
|
|
453
|
+
this.emitMovReg(p2, colReg);
|
|
454
|
+
}
|
|
455
|
+
if (callbackArg.body.type === "BlockStatement") {
|
|
456
|
+
// Each `return` in the block must NOT emit RET (that exits the whole
|
|
457
|
+
// VM function). Instead we push a context so ReturnStatement jumps
|
|
458
|
+
// to cbRetLabel, writing the value into cbResReg.
|
|
459
|
+
const cbRetLabel = `__cb_ret_${uid}_${this.currentAddress()}`;
|
|
460
|
+
this.inlineCallbackStack.push({ cbResReg, returnLabel: cbRetLabel });
|
|
461
|
+
this.compileStatement(callbackArg.body);
|
|
462
|
+
this.inlineCallbackStack.pop();
|
|
463
|
+
this.emitMovInt(cbResReg, 0); // fallthrough: callback body had no return
|
|
464
|
+
this.setLabel(cbRetLabel); // all `return expr` paths jump here
|
|
465
|
+
}
|
|
466
|
+
else
|
|
467
|
+
this.compileExpression(callbackArg.body, cbResReg);
|
|
468
|
+
this.variables = savedVars;
|
|
469
|
+
this.nextVarSlot = savedNext;
|
|
470
|
+
}
|
|
471
|
+
else if (cbType === "Identifier") {
|
|
472
|
+
const callReg = this.getVarReg(callbackArg.name);
|
|
473
|
+
this.emitPush(elemReg);
|
|
474
|
+
this.emitPush(idxReg);
|
|
475
|
+
this.emitPush(colReg);
|
|
476
|
+
this.emitCallReg(cbResReg, callReg, 3);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
throw new KrakBailError("unsupported callback type: " + cbType);
|
|
480
|
+
}
|
|
481
|
+
if (methodName === "map") {
|
|
482
|
+
this.emitPush(cbResReg);
|
|
483
|
+
this.emitNewStr(this.getTempReg(38), "push");
|
|
484
|
+
this.emitCallMethod(this.getTempReg(39), resReg, this.getTempReg(38), 1);
|
|
485
|
+
}
|
|
486
|
+
else if (methodName === "filter") {
|
|
487
|
+
const skipPush = `__cb_skip_${uid}_${this.currentAddress()}`;
|
|
488
|
+
this.emitJz(cbResReg, skipPush);
|
|
489
|
+
this.emitPush(elemReg);
|
|
490
|
+
this.emitNewStr(this.getTempReg(38), "push");
|
|
491
|
+
this.emitCallMethod(this.getTempReg(39), resReg, this.getTempReg(38), 1);
|
|
492
|
+
this.setLabel(skipPush);
|
|
493
|
+
}
|
|
494
|
+
this.emit([this.opcodes["INC"]]);
|
|
495
|
+
this.emit(this.encodeArgs("INC", { r: idxReg }));
|
|
496
|
+
this.emitJmp(loopStart);
|
|
497
|
+
this.setLabel(loopEnd);
|
|
498
|
+
if (methodName === "map" || methodName === "filter") {
|
|
499
|
+
if (targetReg !== resReg)
|
|
500
|
+
this.emitMovReg(targetReg, resReg);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
this.emitMovInt(targetReg, 0);
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// Named function call (direct)
|
|
509
|
+
if (node.callee.type === "Identifier" && this.functions[node.callee.name]) {
|
|
510
|
+
for (let i = 0; i < node.arguments.length; i++) {
|
|
511
|
+
const argReg = this.getTempReg(40 + i);
|
|
512
|
+
this.compileExpression(node.arguments[i], argReg);
|
|
513
|
+
this.emitPush(argReg);
|
|
514
|
+
}
|
|
515
|
+
const thisPlaceholder = this.getTempReg(0);
|
|
516
|
+
this.emitMovInt(thisPlaceholder, 0);
|
|
517
|
+
this.emitPush(thisPlaceholder);
|
|
518
|
+
this.emitCallAddr(`__func_${node.callee.name}`);
|
|
519
|
+
if (targetReg !== 0)
|
|
520
|
+
this.emitMovReg(targetReg, 0);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// Identifier call
|
|
524
|
+
if (node.callee.type === "Identifier") {
|
|
525
|
+
for (let i = 0; i < node.arguments.length; i++) {
|
|
526
|
+
const argReg = this.getTempReg(40 + i);
|
|
527
|
+
this.compileExpression(node.arguments[i], argReg);
|
|
528
|
+
this.emitPush(argReg);
|
|
529
|
+
}
|
|
530
|
+
let funcReg;
|
|
531
|
+
if (this.variables[node.callee.name] !== undefined) {
|
|
532
|
+
funcReg = this.getVarReg(node.callee.name);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
funcReg = this.getTempReg(8);
|
|
536
|
+
this.emitNewStr(this.getTempReg(7), node.callee.name);
|
|
537
|
+
this.emitGetGlobal(funcReg, this.getTempReg(7));
|
|
538
|
+
}
|
|
539
|
+
this.emitCallReg(targetReg, funcReg, node.arguments.length);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// General call
|
|
543
|
+
const argCount = node.arguments.length;
|
|
544
|
+
for (let i = 0; i < argCount; i++) {
|
|
545
|
+
const argReg = this.getTempReg(40 + i);
|
|
546
|
+
this.compileExpression(node.arguments[i], argReg);
|
|
547
|
+
this.emitPush(argReg);
|
|
548
|
+
}
|
|
549
|
+
if (node.callee.type === "MemberExpression") {
|
|
550
|
+
const objReg = this.getTempReg(30);
|
|
551
|
+
const methodReg = this.getTempReg(31);
|
|
552
|
+
this.compileMemberBase(node.callee, objReg, methodReg);
|
|
553
|
+
this.emitCallMethod(targetReg, objReg, methodReg, argCount);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
const funcReg = this.getTempReg(8);
|
|
557
|
+
this.compileExpression(node.callee, funcReg);
|
|
558
|
+
this.emitCallReg(targetReg, funcReg, argCount);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// ── compileExpression (Babel nodes) ──────────────────────────────────────
|
|
562
|
+
compileExpression(node, targetReg) {
|
|
563
|
+
switch (node.type) {
|
|
564
|
+
case "NumericLiteral": {
|
|
565
|
+
const v = node.value;
|
|
566
|
+
if (Number.isInteger(v) && v >= -2147483648 && v <= 2147483647) {
|
|
567
|
+
this.emitMovInt(targetReg, v | 0);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
this.emitNewStr(targetReg, String(v));
|
|
571
|
+
this.emitToNum(targetReg, targetReg);
|
|
572
|
+
}
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
case "StringLiteral":
|
|
576
|
+
this.emitNewStr(targetReg, node.value);
|
|
577
|
+
break;
|
|
578
|
+
case "BooleanLiteral":
|
|
579
|
+
this.emitMovBool(targetReg, node.value);
|
|
580
|
+
break;
|
|
581
|
+
case "NullLiteral":
|
|
582
|
+
this.emitMovInt(targetReg, 0);
|
|
583
|
+
break;
|
|
584
|
+
case "Identifier":
|
|
585
|
+
if (node.name === "undefined") {
|
|
586
|
+
this.emitMovInt(targetReg, 0);
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
if (this.variables[node.name] !== undefined) {
|
|
590
|
+
const srcReg = this.getVarReg(node.name);
|
|
591
|
+
if (srcReg !== targetReg)
|
|
592
|
+
this.emitMovReg(targetReg, srcReg);
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
this.emitNewStr(this.getTempReg(2), node.name);
|
|
596
|
+
this.emitGetGlobal(targetReg, this.getTempReg(2));
|
|
597
|
+
}
|
|
598
|
+
break;
|
|
599
|
+
case "LogicalExpression": {
|
|
600
|
+
const endL = `__log_end_${this.currentAddress()}`;
|
|
601
|
+
this.compileExpression(node.left, targetReg);
|
|
602
|
+
if (node.operator === "||")
|
|
603
|
+
this.emitJnz(targetReg, endL);
|
|
604
|
+
else if (node.operator === "&&")
|
|
605
|
+
this.emitJz(targetReg, endL);
|
|
606
|
+
else if (node.operator === "??")
|
|
607
|
+
this.emitJnz(targetReg, endL);
|
|
608
|
+
else
|
|
609
|
+
throw new KrakBailError("unsupported logical: " + node.operator);
|
|
610
|
+
this.compileExpression(node.right, targetReg);
|
|
611
|
+
this.setLabel(endL);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
case "BinaryExpression": {
|
|
615
|
+
this.compileExpression(node.left, targetReg);
|
|
616
|
+
let tempReg;
|
|
617
|
+
if (node.right.type === "Identifier" && this.variables[node.right.name] !== undefined) {
|
|
618
|
+
tempReg = this.getVarReg(node.right.name);
|
|
619
|
+
}
|
|
620
|
+
else if (node.right.type === "NumericLiteral" && node.right.value === 0) {
|
|
621
|
+
tempReg = 1;
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
tempReg = (targetReg === this.getTempReg(0)) ? this.getTempReg(1) : this.getTempReg(0);
|
|
625
|
+
if (node.right.type === "NumericLiteral" || node.right.type === "StringLiteral" || node.right.type === "BooleanLiteral") {
|
|
626
|
+
this.compileExpression(node.right, tempReg);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
this.emitPush(targetReg);
|
|
630
|
+
this.compileExpression(node.right, tempReg);
|
|
631
|
+
this.emitPop(targetReg);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const opMap = {
|
|
635
|
+
"+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV", "%": "MOD",
|
|
636
|
+
"&": "AND", "|": "OR", "^": "XOR", "<<": "SHL", ">>": "SAR", ">>>": "SHR",
|
|
637
|
+
};
|
|
638
|
+
const opName = opMap[node.operator];
|
|
639
|
+
if (opName) {
|
|
640
|
+
this.emitBinaryOp(opName, targetReg, tempReg);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
this.emit([this.opcodes["CMP"]]);
|
|
644
|
+
this.emit(this.encodeArgs("CMP", { a: targetReg, b: tempReg }));
|
|
645
|
+
const skipL = `__cmp_skip_${this.currentAddress()}`;
|
|
646
|
+
const skip2L = `__cmp_skip2_${this.currentAddress()}`;
|
|
647
|
+
switch (node.operator) {
|
|
648
|
+
case "==":
|
|
649
|
+
case "===":
|
|
650
|
+
this.emitMovBool(targetReg, false);
|
|
651
|
+
this.emitJnz(255, skipL);
|
|
652
|
+
this.emitMovBool(targetReg, true);
|
|
653
|
+
this.setLabel(skipL);
|
|
654
|
+
break;
|
|
655
|
+
case "!=":
|
|
656
|
+
case "!==":
|
|
657
|
+
this.emitMovBool(targetReg, false);
|
|
658
|
+
this.emitJz(255, skipL);
|
|
659
|
+
this.emitMovBool(targetReg, true);
|
|
660
|
+
this.setLabel(skipL);
|
|
661
|
+
break;
|
|
662
|
+
case "<":
|
|
663
|
+
this.emitMovBool(targetReg, false);
|
|
664
|
+
this.emitJgt(skipL);
|
|
665
|
+
this.emitJz(255, skip2L);
|
|
666
|
+
this.emitMovBool(targetReg, true);
|
|
667
|
+
this.setLabel(skipL);
|
|
668
|
+
this.setLabel(skip2L);
|
|
669
|
+
break;
|
|
670
|
+
case "<=":
|
|
671
|
+
this.emitMovBool(targetReg, false);
|
|
672
|
+
this.emitJgt(skipL);
|
|
673
|
+
this.emitMovBool(targetReg, true);
|
|
674
|
+
this.setLabel(skipL);
|
|
675
|
+
break;
|
|
676
|
+
case ">":
|
|
677
|
+
this.emitMovBool(targetReg, false);
|
|
678
|
+
this.emitJlt(skipL);
|
|
679
|
+
this.emitJz(255, skip2L);
|
|
680
|
+
this.emitMovBool(targetReg, true);
|
|
681
|
+
this.setLabel(skipL);
|
|
682
|
+
this.setLabel(skip2L);
|
|
683
|
+
break;
|
|
684
|
+
case ">=":
|
|
685
|
+
this.emitMovBool(targetReg, false);
|
|
686
|
+
this.emitJlt(skipL);
|
|
687
|
+
this.emitMovBool(targetReg, true);
|
|
688
|
+
this.setLabel(skipL);
|
|
689
|
+
break;
|
|
690
|
+
case "instanceof":
|
|
691
|
+
this.emitInstanceOf(targetReg, targetReg, tempReg);
|
|
692
|
+
break;
|
|
693
|
+
default:
|
|
694
|
+
throw new KrakBailError("unsupported binary: " + node.operator);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "UnaryExpression":
|
|
700
|
+
if (node.operator === "typeof") {
|
|
701
|
+
const srcReg = this.getTempReg(0);
|
|
702
|
+
this.compileExpression(node.argument, srcReg);
|
|
703
|
+
this.emitTypeof(targetReg, srcReg);
|
|
704
|
+
}
|
|
705
|
+
else if (node.operator === "!") {
|
|
706
|
+
const testReg = (targetReg === this.getTempReg(0)) ? this.getTempReg(1) : this.getTempReg(0);
|
|
707
|
+
this.compileExpression(node.argument, testReg);
|
|
708
|
+
const trueL = `__not_true_${this.currentAddress()}`, endL = `__not_end_${this.currentAddress()}`;
|
|
709
|
+
this.emitJz(testReg, trueL);
|
|
710
|
+
this.emitMovBool(targetReg, false);
|
|
711
|
+
this.emitJmp(endL);
|
|
712
|
+
this.setLabel(trueL);
|
|
713
|
+
this.emitMovBool(targetReg, true);
|
|
714
|
+
this.setLabel(endL);
|
|
715
|
+
}
|
|
716
|
+
else if (node.operator === "-") {
|
|
717
|
+
const valReg = (targetReg === this.getTempReg(0)) ? this.getTempReg(1) : this.getTempReg(0);
|
|
718
|
+
this.compileExpression(node.argument, valReg);
|
|
719
|
+
this.emitMovInt(targetReg, 0);
|
|
720
|
+
this.emitBinaryOp("SUB", targetReg, valReg);
|
|
721
|
+
}
|
|
722
|
+
else if (node.operator === "+") {
|
|
723
|
+
this.compileExpression(node.argument, targetReg);
|
|
724
|
+
}
|
|
725
|
+
else if (node.operator === "void") {
|
|
726
|
+
this.compileExpression(node.argument, this.getTempReg(0));
|
|
727
|
+
this.emitMovInt(targetReg, 0);
|
|
728
|
+
}
|
|
729
|
+
else if (node.operator === "~") {
|
|
730
|
+
this.compileExpression(node.argument, targetReg);
|
|
731
|
+
this.emit([this.opcodes["NOT"]]);
|
|
732
|
+
this.emit(this.encodeArgs("NOT", { r: targetReg }));
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
throw new KrakBailError("unsupported unary: " + node.operator);
|
|
736
|
+
}
|
|
737
|
+
break;
|
|
738
|
+
case "AssignmentExpression": {
|
|
739
|
+
if (node.operator === "=") {
|
|
740
|
+
this.compileExpression(node.right, targetReg);
|
|
741
|
+
if (node.left.type === "Identifier") {
|
|
742
|
+
const destReg = this.allocVar(node.left.name) % 256;
|
|
743
|
+
if (destReg !== targetReg)
|
|
744
|
+
this.emitMovReg(destReg, targetReg);
|
|
745
|
+
}
|
|
746
|
+
else if (node.left.type === "MemberExpression") {
|
|
747
|
+
this.emitPush(targetReg);
|
|
748
|
+
const objR = this.getTempReg(10), propR = this.getTempReg(11);
|
|
749
|
+
this.compileMemberBase(node.left, objR, propR);
|
|
750
|
+
const valR = this.getTempReg(12);
|
|
751
|
+
this.emitPop(valR);
|
|
752
|
+
this.emitSetProp(objR, propR, valR);
|
|
753
|
+
if (targetReg !== valR)
|
|
754
|
+
this.emitMovReg(targetReg, valR);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
const op = node.operator.slice(0, -1);
|
|
759
|
+
const opMap = { "+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV", "%": "MOD", "&": "AND", "|": "OR", "^": "XOR", "<<": "SHL", ">>": "SAR", ">>>": "SHR" };
|
|
760
|
+
const opName = opMap[op];
|
|
761
|
+
if (!opName)
|
|
762
|
+
throw new KrakBailError("unsupported compound assignment: " + node.operator);
|
|
763
|
+
if (node.left.type === "Identifier") {
|
|
764
|
+
const destReg = this.allocVar(node.left.name) % 256;
|
|
765
|
+
const tempR = this.getTempReg(0);
|
|
766
|
+
this.compileExpression(node.right, tempR);
|
|
767
|
+
this.emitBinaryOp(opName, destReg, tempR);
|
|
768
|
+
if (targetReg !== destReg)
|
|
769
|
+
this.emitMovReg(targetReg, destReg);
|
|
770
|
+
}
|
|
771
|
+
else if (node.left.type === "MemberExpression") {
|
|
772
|
+
const objR = this.getTempReg(20), propR = this.getTempReg(21);
|
|
773
|
+
this.compileMemberBase(node.left, objR, propR);
|
|
774
|
+
const curR = this.getTempReg(19);
|
|
775
|
+
this.emitGetProp(curR, objR, propR);
|
|
776
|
+
const rightR = this.getTempReg(0);
|
|
777
|
+
this.compileExpression(node.right, rightR);
|
|
778
|
+
this.emitBinaryOp(opName, curR, rightR);
|
|
779
|
+
this.emitSetProp(objR, propR, curR);
|
|
780
|
+
if (targetReg !== 0)
|
|
781
|
+
this.emitMovReg(targetReg, curR);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
case "UpdateExpression": {
|
|
787
|
+
if (node.argument.type === "Identifier") {
|
|
788
|
+
const varReg = this.getVarReg(node.argument.name);
|
|
789
|
+
const op = node.operator === "++" ? "INC" : "DEC";
|
|
790
|
+
if (node.prefix) {
|
|
791
|
+
this.emit([this.opcodes[op]]);
|
|
792
|
+
this.emit(this.encodeArgs(op, { r: varReg }));
|
|
793
|
+
if (targetReg !== varReg)
|
|
794
|
+
this.emitMovReg(targetReg, varReg);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
if (targetReg !== varReg)
|
|
798
|
+
this.emitMovReg(targetReg, varReg);
|
|
799
|
+
this.emit([this.opcodes[op]]);
|
|
800
|
+
this.emit(this.encodeArgs(op, { r: varReg }));
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
else if (node.argument.type === "MemberExpression") {
|
|
804
|
+
const objR = this.getTempReg(20), propR = this.getTempReg(21);
|
|
805
|
+
this.compileMemberBase(node.argument, objR, propR);
|
|
806
|
+
const valR = this.getTempReg(19);
|
|
807
|
+
this.emitGetProp(valR, objR, propR);
|
|
808
|
+
if (!node.prefix)
|
|
809
|
+
this.emitMovReg(targetReg, valR);
|
|
810
|
+
const oneReg = this.getTempReg(0);
|
|
811
|
+
this.emitMovInt(oneReg, 1);
|
|
812
|
+
if (node.operator === "++")
|
|
813
|
+
this.emitBinaryOp("ADD", valR, oneReg);
|
|
814
|
+
else
|
|
815
|
+
this.emitBinaryOp("SUB", valR, oneReg);
|
|
816
|
+
if (node.prefix)
|
|
817
|
+
this.emitMovReg(targetReg, valR);
|
|
818
|
+
this.emitSetProp(objR, propR, valR);
|
|
819
|
+
}
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
case "ConditionalExpression": {
|
|
823
|
+
const elseL = `__cond_else_${this.currentAddress()}`, endL = `__cond_end_${this.currentAddress()}`;
|
|
824
|
+
const testR = (targetReg === this.getTempReg(0)) ? this.getTempReg(1) : this.getTempReg(0);
|
|
825
|
+
this.compileExpression(node.test, testR);
|
|
826
|
+
this.emitJz(testR, elseL);
|
|
827
|
+
this.compileExpression(node.consequent, targetReg);
|
|
828
|
+
this.emitJmp(endL);
|
|
829
|
+
this.setLabel(elseL);
|
|
830
|
+
this.compileExpression(node.alternate, targetReg);
|
|
831
|
+
this.setLabel(endL);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
case "MemberExpression":
|
|
835
|
+
this.compileMemberExpression(node, targetReg);
|
|
836
|
+
break;
|
|
837
|
+
case "CallExpression":
|
|
838
|
+
this.compileCallExpression(node, targetReg);
|
|
839
|
+
break;
|
|
840
|
+
case "NewExpression": {
|
|
841
|
+
for (let i = 0; i < node.arguments.length; i++) {
|
|
842
|
+
const argR = this.getTempReg(1);
|
|
843
|
+
this.compileExpression(node.arguments[i], argR);
|
|
844
|
+
this.emitPush(argR);
|
|
845
|
+
}
|
|
846
|
+
const conR = this.getTempReg(0);
|
|
847
|
+
this.compileExpression(node.callee, conR);
|
|
848
|
+
this.emitNew(targetReg, conR, node.arguments.length);
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
case "ArrayExpression": {
|
|
852
|
+
// Build array in a fresh var-slot register so that sub-expression
|
|
853
|
+
// compilation (which reuses getTempReg slots for inner calls) cannot
|
|
854
|
+
// overwrite the array reference.
|
|
855
|
+
const arrVarSlot = this.nextVarSlot++ % 256; // guaranteed unique var slot
|
|
856
|
+
const arrNR = this.getTempReg(32); // "Array" string (high temp, safe)
|
|
857
|
+
const arrCR = this.getTempReg(33); // Array constructor ref
|
|
858
|
+
const pushNR = this.getTempReg(34); // "push" string — set once before loop
|
|
859
|
+
const callTR = this.getTempReg(35); // discard return of push()
|
|
860
|
+
this.emitNewStr(arrNR, "Array");
|
|
861
|
+
this.emitGetGlobal(arrCR, arrNR);
|
|
862
|
+
this.emitNew(arrVarSlot, arrCR, 0); // array lives in stable var slot
|
|
863
|
+
this.emitNewStr(pushNR, "push"); // set once before loop
|
|
864
|
+
for (let i = 0; i < node.elements.length; i++) {
|
|
865
|
+
if (!node.elements[i])
|
|
866
|
+
continue;
|
|
867
|
+
// Compile element into a fresh temp — must not be arrVarSlot
|
|
868
|
+
const elTR = this.getTempReg(36);
|
|
869
|
+
this.compileExpression(node.elements[i], elTR);
|
|
870
|
+
this.emitPush(elTR);
|
|
871
|
+
// Re-emit pushNR in case element sub-expression clobbered it
|
|
872
|
+
this.emitNewStr(pushNR, "push");
|
|
873
|
+
this.emitCallMethod(callTR, arrVarSlot, pushNR, 1);
|
|
874
|
+
}
|
|
875
|
+
if (targetReg !== arrVarSlot)
|
|
876
|
+
this.emitMovReg(targetReg, arrVarSlot);
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case "ObjectExpression": {
|
|
880
|
+
// Use fresh var-slot for the object so sub-expressions don't clobber it
|
|
881
|
+
const objVarSlot = this.nextVarSlot++ % 256;
|
|
882
|
+
const objCR = this.getTempReg(40);
|
|
883
|
+
this.emitNewStr(this.getTempReg(41), "Object");
|
|
884
|
+
this.emitGetGlobal(objCR, this.getTempReg(41));
|
|
885
|
+
this.emitNew(objVarSlot, objCR, 0);
|
|
886
|
+
for (const prop of node.properties) {
|
|
887
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "Property")
|
|
888
|
+
throw new KrakBailError("spread");
|
|
889
|
+
const keyName = prop.key.name ?? prop.key.value;
|
|
890
|
+
// propR must survive the compileExpression call, so use stable high regs
|
|
891
|
+
const propR = this.getTempReg(42);
|
|
892
|
+
const valR = this.getTempReg(43);
|
|
893
|
+
this.emitNewStr(propR, String(keyName));
|
|
894
|
+
this.compileExpression(prop.value, valR);
|
|
895
|
+
// Re-emit propR key in case sub-expression smashed it
|
|
896
|
+
this.emitNewStr(propR, String(keyName));
|
|
897
|
+
this.emitSetProp(objVarSlot, propR, valR);
|
|
898
|
+
}
|
|
899
|
+
if (targetReg !== objVarSlot)
|
|
900
|
+
this.emitMovReg(targetReg, objVarSlot);
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
case "ArrowFunctionExpression":
|
|
904
|
+
case "FunctionExpression": {
|
|
905
|
+
const labelName = `anon_${this.currentAddress()}`;
|
|
906
|
+
const startL = `__func_${labelName}`, endL = `__func_end_${labelName}`;
|
|
907
|
+
this.emitJmp(endL);
|
|
908
|
+
this.setLabel(startL);
|
|
909
|
+
const savedVars = this.variables, savedNext = this.nextVarSlot;
|
|
910
|
+
this.variables = Object.assign({}, this.variables);
|
|
911
|
+
this.allocVar("this");
|
|
912
|
+
for (const p of node.params)
|
|
913
|
+
this.allocVar(p.name);
|
|
914
|
+
const thisR = this.getVarReg("this");
|
|
915
|
+
this.emitPop(thisR);
|
|
916
|
+
for (let p = node.params.length - 1; p >= 0; p--) {
|
|
917
|
+
const paramR = this.getVarReg(node.params[p].name);
|
|
918
|
+
this.emitPop(paramR);
|
|
919
|
+
}
|
|
920
|
+
if (node.body.type === "BlockStatement") {
|
|
921
|
+
this.compileStatement(node.body);
|
|
922
|
+
this.emitMovInt(0, 0);
|
|
923
|
+
this.emitRet();
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
this.compileExpression(node.body, 0);
|
|
927
|
+
this.emitRet();
|
|
928
|
+
}
|
|
929
|
+
this.variables = savedVars;
|
|
930
|
+
this.nextVarSlot = savedNext;
|
|
931
|
+
this.setLabel(endL);
|
|
932
|
+
this.emit([this.opcodes["FUNC"]]);
|
|
933
|
+
this.pendingJumps.push({ type: "FUNC", address: this.currentAddress(), label: startL });
|
|
934
|
+
this.emit(this.encodeArgs("FUNC", { d: targetReg, a: 0, k: 0, n: node.params.length }));
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
case "SequenceExpression":
|
|
938
|
+
for (const expr of node.expressions)
|
|
939
|
+
this.compileExpression(expr, targetReg);
|
|
940
|
+
break;
|
|
941
|
+
case "TemplateLiteral":
|
|
942
|
+
if (node.quasis.length === 1 && node.expressions.length === 0) {
|
|
943
|
+
this.emitNewStr(targetReg, node.quasis[0].value.cooked);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
this.emitNewStr(targetReg, node.quasis[0].value.cooked);
|
|
947
|
+
for (let i = 0; i < node.expressions.length; i++) {
|
|
948
|
+
this.emitPush(targetReg);
|
|
949
|
+
const exprR = this.getTempReg(0);
|
|
950
|
+
this.compileExpression(node.expressions[i], exprR);
|
|
951
|
+
this.emitPop(targetReg);
|
|
952
|
+
this.emitBinaryOp("ADD", targetReg, exprR);
|
|
953
|
+
const nextQ = node.quasis[i + 1]?.value.cooked ?? "";
|
|
954
|
+
if (nextQ.length > 0) {
|
|
955
|
+
this.emitPush(targetReg);
|
|
956
|
+
this.emitNewStr(this.getTempReg(0), nextQ);
|
|
957
|
+
this.emitPop(targetReg);
|
|
958
|
+
this.emitBinaryOp("ADD", targetReg, this.getTempReg(0));
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
break;
|
|
963
|
+
case "ThisExpression":
|
|
964
|
+
if (this.variables["this"] !== undefined)
|
|
965
|
+
this.emitMovReg(targetReg, this.getVarReg("this"));
|
|
966
|
+
else
|
|
967
|
+
this.emitMovInt(targetReg, 0);
|
|
968
|
+
break;
|
|
969
|
+
default:
|
|
970
|
+
throw new KrakBailError("unsupported expression: " + node.type);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
// ── compileStatement ──────────────────────────────────────────────────────
|
|
974
|
+
compileStatement(node) {
|
|
975
|
+
switch (node.type) {
|
|
976
|
+
case "VariableDeclaration":
|
|
977
|
+
for (const decl of node.declarations) {
|
|
978
|
+
if (decl.id.type === "Identifier") {
|
|
979
|
+
const reg = this.allocVar(decl.id.name) % 256;
|
|
980
|
+
if (decl.init)
|
|
981
|
+
this.compileExpression(decl.init, reg);
|
|
982
|
+
else
|
|
983
|
+
this.emitMovInt(reg, 0);
|
|
984
|
+
}
|
|
985
|
+
else if (decl.id.type === "ArrayPattern") {
|
|
986
|
+
// const [a, b, c] = expr → tmp = expr; a = tmp[0]; b = tmp[1]; ...
|
|
987
|
+
const tmpR = this.getTempReg(10);
|
|
988
|
+
if (decl.init)
|
|
989
|
+
this.compileExpression(decl.init, tmpR);
|
|
990
|
+
else
|
|
991
|
+
this.emitMovInt(tmpR, 0);
|
|
992
|
+
for (let _ai = 0; _ai < decl.id.elements.length; _ai++) {
|
|
993
|
+
const elem = decl.id.elements[_ai];
|
|
994
|
+
if (!elem)
|
|
995
|
+
continue; // sparse hole: skip
|
|
996
|
+
if (elem.type !== "Identifier")
|
|
997
|
+
throw new KrakBailError("nested destructuring");
|
|
998
|
+
const idxNumR = this.getTempReg(11);
|
|
999
|
+
const idxStrR = this.getTempReg(12);
|
|
1000
|
+
const elR = this.allocVar(elem.name) % 256;
|
|
1001
|
+
this.emitMovInt(idxNumR, _ai);
|
|
1002
|
+
this.emitFromNum(idxStrR, idxNumR); // convert int→JS value for GPRP
|
|
1003
|
+
this.emitGetProp(elR, tmpR, idxStrR);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
else if (decl.id.type === "ObjectPattern") {
|
|
1007
|
+
// const { a, b } = expr → tmp = expr; a = tmp.a; b = tmp.b;
|
|
1008
|
+
const tmpR = this.getTempReg(10);
|
|
1009
|
+
if (decl.init)
|
|
1010
|
+
this.compileExpression(decl.init, tmpR);
|
|
1011
|
+
else
|
|
1012
|
+
this.emitMovInt(tmpR, 0);
|
|
1013
|
+
for (const prop of decl.id.properties) {
|
|
1014
|
+
if (prop.type !== "ObjectProperty")
|
|
1015
|
+
throw new KrakBailError("rest destructuring");
|
|
1016
|
+
const keyName = prop.key.name ?? prop.key.value;
|
|
1017
|
+
const valNode = prop.value;
|
|
1018
|
+
if (valNode.type !== "Identifier")
|
|
1019
|
+
throw new KrakBailError("nested obj destructuring");
|
|
1020
|
+
const propR = this.allocVar(valNode.name) % 256;
|
|
1021
|
+
const kR = this.getTempReg(11);
|
|
1022
|
+
this.emitNewStr(kR, String(keyName));
|
|
1023
|
+
this.emitGetProp(propR, tmpR, kR);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
throw new KrakBailError("destructuring: " + decl.id.type);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
break;
|
|
1031
|
+
case "ExpressionStatement":
|
|
1032
|
+
this.compileExpression(node.expression, this.getTempReg(3));
|
|
1033
|
+
break;
|
|
1034
|
+
case "IfStatement": {
|
|
1035
|
+
const endL = `__if_end_${this.currentAddress()}`;
|
|
1036
|
+
const elseL = `__if_else_${this.currentAddress()}`;
|
|
1037
|
+
if (node.alternate) {
|
|
1038
|
+
if (!this.tryCompileConditionExit(node.test, elseL)) {
|
|
1039
|
+
this.compileExpression(node.test, 254);
|
|
1040
|
+
this.emitJz(254, elseL);
|
|
1041
|
+
}
|
|
1042
|
+
this.compileStatement(node.consequent);
|
|
1043
|
+
this.emitJmp(endL);
|
|
1044
|
+
this.setLabel(elseL);
|
|
1045
|
+
this.compileStatement(node.alternate);
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
if (!this.tryCompileConditionExit(node.test, endL)) {
|
|
1049
|
+
this.compileExpression(node.test, 254);
|
|
1050
|
+
this.emitJz(254, endL);
|
|
1051
|
+
}
|
|
1052
|
+
this.compileStatement(node.consequent);
|
|
1053
|
+
}
|
|
1054
|
+
this.setLabel(endL);
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
case "WhileStatement": {
|
|
1058
|
+
const loopStart = `__while_start_${this.currentAddress()}`;
|
|
1059
|
+
const loopEnd = `__while_end_${this.currentAddress()}`;
|
|
1060
|
+
this.setLabel(loopStart);
|
|
1061
|
+
if (!this.tryCompileConditionExit(node.test, loopEnd)) {
|
|
1062
|
+
this.compileExpression(node.test, 254);
|
|
1063
|
+
this.emitJz(254, loopEnd);
|
|
1064
|
+
}
|
|
1065
|
+
this.loopStack.push({ continueLabel: loopStart, breakLabel: loopEnd });
|
|
1066
|
+
this.compileStatement(node.body);
|
|
1067
|
+
this.loopStack.pop();
|
|
1068
|
+
this.emitJmp(loopStart);
|
|
1069
|
+
this.setLabel(loopEnd);
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
case "DoWhileStatement": {
|
|
1073
|
+
const doStart = `__do_start_${this.currentAddress()}`;
|
|
1074
|
+
const doEnd = `__do_end_${this.currentAddress()}`;
|
|
1075
|
+
const doCond = `__do_cond_${this.currentAddress()}`;
|
|
1076
|
+
this.setLabel(doStart);
|
|
1077
|
+
this.loopStack.push({ continueLabel: doCond, breakLabel: doEnd });
|
|
1078
|
+
this.compileStatement(node.body);
|
|
1079
|
+
this.loopStack.pop();
|
|
1080
|
+
this.setLabel(doCond);
|
|
1081
|
+
this.compileExpression(node.test, 254);
|
|
1082
|
+
this.emitJnz(254, doStart);
|
|
1083
|
+
this.setLabel(doEnd);
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
case "ForStatement": {
|
|
1087
|
+
const forStart = `__for_start_${this.currentAddress()}`;
|
|
1088
|
+
const forEnd = `__for_end_${this.currentAddress()}`;
|
|
1089
|
+
const forUpdate = `__for_update_${this.currentAddress()}`;
|
|
1090
|
+
if (node.init) {
|
|
1091
|
+
if (node.init.type === "VariableDeclaration")
|
|
1092
|
+
this.compileStatement(node.init);
|
|
1093
|
+
else
|
|
1094
|
+
this.compileExpression(node.init, this.getTempReg(3));
|
|
1095
|
+
}
|
|
1096
|
+
this.setLabel(forStart);
|
|
1097
|
+
if (node.test) {
|
|
1098
|
+
if (!this.tryCompileConditionExit(node.test, forEnd)) {
|
|
1099
|
+
this.compileExpression(node.test, 254);
|
|
1100
|
+
this.emitJz(254, forEnd);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
this.loopStack.push({ continueLabel: forUpdate, breakLabel: forEnd });
|
|
1104
|
+
this.compileStatement(node.body);
|
|
1105
|
+
this.loopStack.pop();
|
|
1106
|
+
this.setLabel(forUpdate);
|
|
1107
|
+
if (node.update)
|
|
1108
|
+
this.compileExpression(node.update, this.getTempReg(3));
|
|
1109
|
+
this.emitJmp(forStart);
|
|
1110
|
+
this.setLabel(forEnd);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
case "BlockStatement":
|
|
1114
|
+
for (const s of node.body)
|
|
1115
|
+
this.compileStatement(s);
|
|
1116
|
+
break;
|
|
1117
|
+
case "FunctionDeclaration": {
|
|
1118
|
+
if (this.functions[node.id.name])
|
|
1119
|
+
break;
|
|
1120
|
+
const funcVarReg = this.allocVar(node.id.name) % 256;
|
|
1121
|
+
this.compileExpression({
|
|
1122
|
+
type: "FunctionExpression",
|
|
1123
|
+
params: node.params,
|
|
1124
|
+
body: node.body,
|
|
1125
|
+
}, funcVarReg);
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
case "ReturnStatement": {
|
|
1129
|
+
const cbCtx = this.inlineCallbackStack.length > 0
|
|
1130
|
+
? this.inlineCallbackStack[this.inlineCallbackStack.length - 1]
|
|
1131
|
+
: null;
|
|
1132
|
+
if (cbCtx) {
|
|
1133
|
+
// Inside an inline callback — write return value into cbResReg and
|
|
1134
|
+
// jump to the callback exit label (do NOT emit RET here).
|
|
1135
|
+
if (node.argument)
|
|
1136
|
+
this.compileExpression(node.argument, cbCtx.cbResReg);
|
|
1137
|
+
else
|
|
1138
|
+
this.emitMovInt(cbCtx.cbResReg, 0);
|
|
1139
|
+
this.emitJmp(cbCtx.returnLabel);
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
if (node.argument)
|
|
1143
|
+
this.compileExpression(node.argument, 0);
|
|
1144
|
+
else
|
|
1145
|
+
this.emitMovInt(0, 0);
|
|
1146
|
+
this.emitRet();
|
|
1147
|
+
}
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
case "BreakStatement":
|
|
1151
|
+
if (!this.loopStack.length)
|
|
1152
|
+
throw new KrakBailError("break outside loop");
|
|
1153
|
+
this.emitJmp(this.loopStack[this.loopStack.length - 1].breakLabel);
|
|
1154
|
+
break;
|
|
1155
|
+
case "ContinueStatement":
|
|
1156
|
+
if (!this.loopStack.length)
|
|
1157
|
+
throw new KrakBailError("continue outside loop");
|
|
1158
|
+
{
|
|
1159
|
+
const cl = this.loopStack[this.loopStack.length - 1].continueLabel;
|
|
1160
|
+
if (!cl)
|
|
1161
|
+
throw new KrakBailError("continue in switch");
|
|
1162
|
+
this.emitJmp(cl);
|
|
1163
|
+
}
|
|
1164
|
+
break;
|
|
1165
|
+
case "EmptyStatement":
|
|
1166
|
+
break;
|
|
1167
|
+
case "ThrowStatement": {
|
|
1168
|
+
const errR = this.getTempReg(0);
|
|
1169
|
+
this.compileExpression(node.argument, errR);
|
|
1170
|
+
this.emitPush(errR);
|
|
1171
|
+
this.emitNewStr(this.getTempReg(1), "__krak_throw");
|
|
1172
|
+
this.emitGetGlobal(this.getTempReg(2), this.getTempReg(1));
|
|
1173
|
+
this.emitCallReg(this.getTempReg(3), this.getTempReg(2), 1);
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
case "TryStatement":
|
|
1177
|
+
this.compileStatement(node.block);
|
|
1178
|
+
break;
|
|
1179
|
+
case "SwitchStatement": {
|
|
1180
|
+
const discR = this.getTempReg(14);
|
|
1181
|
+
this.compileExpression(node.discriminant, discR);
|
|
1182
|
+
const switchEndL = `__switch_end_${this.currentAddress()}`;
|
|
1183
|
+
const caseLabels = [];
|
|
1184
|
+
let defaultLabel = null;
|
|
1185
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
1186
|
+
const c = node.cases[i];
|
|
1187
|
+
const cLabel = `__switch_case_${this.currentAddress()}_${i}`;
|
|
1188
|
+
caseLabels.push(cLabel);
|
|
1189
|
+
if (c.test === null) {
|
|
1190
|
+
defaultLabel = cLabel;
|
|
1191
|
+
}
|
|
1192
|
+
else {
|
|
1193
|
+
const testR = this.getTempReg(15);
|
|
1194
|
+
this.compileExpression(c.test, testR);
|
|
1195
|
+
this.emit([this.opcodes["CMP"]]);
|
|
1196
|
+
this.emit(this.encodeArgs("CMP", { a: discR, b: testR }));
|
|
1197
|
+
this.emitJz(255, cLabel);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (defaultLabel)
|
|
1201
|
+
this.emitJmp(defaultLabel);
|
|
1202
|
+
else
|
|
1203
|
+
this.emitJmp(switchEndL);
|
|
1204
|
+
this.loopStack.push({ breakLabel: switchEndL, continueLabel: null });
|
|
1205
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
1206
|
+
this.setLabel(caseLabels[i]);
|
|
1207
|
+
for (const stmt of node.cases[i].consequent)
|
|
1208
|
+
this.compileStatement(stmt);
|
|
1209
|
+
}
|
|
1210
|
+
this.loopStack.pop();
|
|
1211
|
+
this.setLabel(switchEndL);
|
|
1212
|
+
break;
|
|
1213
|
+
}
|
|
1214
|
+
case "ForOfStatement": {
|
|
1215
|
+
// for (const x of iterable) { body }
|
|
1216
|
+
// Lowered to: idx=0; len=arr.length; while(idx<len){ x=arr[idx]; body; idx++ }
|
|
1217
|
+
// Covers arrays and array-likes only. True iterators bail.
|
|
1218
|
+
if (node.left.type !== "VariableDeclaration")
|
|
1219
|
+
throw new KrakBailError("for-of non-var");
|
|
1220
|
+
const foDecl = node.left.declarations[0];
|
|
1221
|
+
if (!foDecl || foDecl.id.type !== "Identifier")
|
|
1222
|
+
throw new KrakBailError("for-of destructuring");
|
|
1223
|
+
const foVarReg = this.allocVar(foDecl.id.name) % 256;
|
|
1224
|
+
const foArrR = this.getTempReg(6);
|
|
1225
|
+
const foIdxR = this.allocVar(`__fo_idx_${this.currentAddress()}`) % 256;
|
|
1226
|
+
const foLenR = this.getTempReg(7);
|
|
1227
|
+
const foLenKey = this.getTempReg(8);
|
|
1228
|
+
const foIdxStrR = this.getTempReg(13); // idx converted to JS value for GPRP
|
|
1229
|
+
this.compileExpression(node.right, foArrR);
|
|
1230
|
+
this.emitMovInt(foIdxR, 0);
|
|
1231
|
+
this.emitNewStr(foLenKey, "length");
|
|
1232
|
+
this.emitGetProp(foLenR, foArrR, foLenKey);
|
|
1233
|
+
this.emitToNum(foLenR, foLenR); // ensure numeric
|
|
1234
|
+
const foStart = `__fo_start_${this.currentAddress()}`;
|
|
1235
|
+
const foEnd = `__fo_end_${this.currentAddress()}`;
|
|
1236
|
+
this.setLabel(foStart);
|
|
1237
|
+
// CMP reg[255]: 0=equal, 1=a>b, -1=a<b; jump if idx >= len (equal OR greater)
|
|
1238
|
+
this.emit([this.opcodes["CMP"]]);
|
|
1239
|
+
this.emit(this.encodeArgs("CMP", { a: foIdxR, b: foLenR }));
|
|
1240
|
+
this.emitJgt(foEnd); // idx > len (reg[255] = 1)
|
|
1241
|
+
this.emitJz(255, foEnd); // idx == len (reg[255] = 0)
|
|
1242
|
+
// x = arr[idx] (GPRP needs JS-value key, so convert int via FNUM)
|
|
1243
|
+
this.emitFromNum(foIdxStrR, foIdxR);
|
|
1244
|
+
this.emitGetProp(foVarReg, foArrR, foIdxStrR);
|
|
1245
|
+
this.loopStack.push({ continueLabel: foStart, breakLabel: foEnd });
|
|
1246
|
+
this.compileStatement(node.body);
|
|
1247
|
+
this.loopStack.pop();
|
|
1248
|
+
this.emit([this.opcodes["INC"]]);
|
|
1249
|
+
this.emit(this.encodeArgs("INC", { r: foIdxR }));
|
|
1250
|
+
this.emitJmp(foStart);
|
|
1251
|
+
this.setLabel(foEnd);
|
|
1252
|
+
break;
|
|
1253
|
+
}
|
|
1254
|
+
case "ForInStatement": {
|
|
1255
|
+
// for (const k in obj) { body } — lowered to Object.keys(obj) array loop
|
|
1256
|
+
if (node.left.type !== "VariableDeclaration")
|
|
1257
|
+
throw new KrakBailError("for-in non-var");
|
|
1258
|
+
const fiDecl = node.left.declarations[0];
|
|
1259
|
+
if (!fiDecl || fiDecl.id.type !== "Identifier")
|
|
1260
|
+
throw new KrakBailError("for-in destructuring");
|
|
1261
|
+
const fiVarReg = this.allocVar(fiDecl.id.name) % 256;
|
|
1262
|
+
const fiKeysR = this.getTempReg(6);
|
|
1263
|
+
const fiObjNR = this.getTempReg(9); // name reg for "Object" global
|
|
1264
|
+
const fiObjR = this.getTempReg(5); // the right-hand object
|
|
1265
|
+
const fiIdxR = this.allocVar(`__fi_idx_${this.currentAddress()}`) % 256;
|
|
1266
|
+
const fiLenR = this.getTempReg(7);
|
|
1267
|
+
const fiLenKeyR = this.getTempReg(8);
|
|
1268
|
+
const fiIdxStrR = this.getTempReg(13);
|
|
1269
|
+
// fiKeysR = Object.keys(right)
|
|
1270
|
+
this.emitNewStr(fiObjNR, "Object");
|
|
1271
|
+
this.emitGetGlobal(fiObjR, fiObjNR); // fiObjR = globalThis.Object
|
|
1272
|
+
this.compileExpression(node.right, fiObjNR);
|
|
1273
|
+
this.emitPush(fiObjNR);
|
|
1274
|
+
this.emitNewStr(fiLenKeyR, "keys");
|
|
1275
|
+
this.emitCallMethod(fiKeysR, fiObjR, fiLenKeyR, 1);
|
|
1276
|
+
this.emitMovInt(fiIdxR, 0);
|
|
1277
|
+
this.emitNewStr(fiLenKeyR, "length");
|
|
1278
|
+
this.emitGetProp(fiLenR, fiKeysR, fiLenKeyR);
|
|
1279
|
+
this.emitToNum(fiLenR, fiLenR);
|
|
1280
|
+
const fiStart = `__fi_start_${this.currentAddress()}`;
|
|
1281
|
+
const fiEnd = `__fi_end_${this.currentAddress()}`;
|
|
1282
|
+
this.setLabel(fiStart);
|
|
1283
|
+
this.emit([this.opcodes["CMP"]]);
|
|
1284
|
+
this.emit(this.encodeArgs("CMP", { a: fiIdxR, b: fiLenR }));
|
|
1285
|
+
this.emitJgt(fiEnd);
|
|
1286
|
+
this.emitJz(255, fiEnd);
|
|
1287
|
+
this.emitFromNum(fiIdxStrR, fiIdxR);
|
|
1288
|
+
this.emitGetProp(fiVarReg, fiKeysR, fiIdxStrR);
|
|
1289
|
+
this.loopStack.push({ continueLabel: fiStart, breakLabel: fiEnd });
|
|
1290
|
+
this.compileStatement(node.body);
|
|
1291
|
+
this.loopStack.pop();
|
|
1292
|
+
this.emit([this.opcodes["INC"]]);
|
|
1293
|
+
this.emit(this.encodeArgs("INC", { r: fiIdxR }));
|
|
1294
|
+
this.emitJmp(fiStart);
|
|
1295
|
+
this.setLabel(fiEnd);
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
default:
|
|
1299
|
+
throw new KrakBailError("unsupported statement: " + node.type);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
// ── resolveJumps ─────────────────────────────────────────────────────────
|
|
1303
|
+
resolveJumps() {
|
|
1304
|
+
const { lcgMul, lcgInc, initialKey } = this.config;
|
|
1305
|
+
for (const jump of this.pendingJumps) {
|
|
1306
|
+
const targetAddr = this.labels[jump.label];
|
|
1307
|
+
if (targetAddr === undefined)
|
|
1308
|
+
throw new KrakBailError("Unknown label: " + jump.label);
|
|
1309
|
+
const resetKey = computeKeyAtAddress(initialKey, targetAddr, lcgMul, lcgInc);
|
|
1310
|
+
const layout = this.layouts[jump.type] ?? [];
|
|
1311
|
+
let addr = jump.address;
|
|
1312
|
+
for (const argDef of layout) {
|
|
1313
|
+
if (["t", "addr", "a"].includes(argDef.name)) {
|
|
1314
|
+
this.bytecodeArr[addr] = targetAddr & 0xFF;
|
|
1315
|
+
this.bytecodeArr[addr + 1] = (targetAddr >> 8) & 0xFF;
|
|
1316
|
+
this.bytecodeArr[addr + 2] = (targetAddr >> 16) & 0xFF;
|
|
1317
|
+
this.bytecodeArr[addr + 3] = (targetAddr >> 24) & 0xFF;
|
|
1318
|
+
addr += 4;
|
|
1319
|
+
}
|
|
1320
|
+
else if (["k", "key"].includes(argDef.name)) {
|
|
1321
|
+
// k is now a 32-bit LCG checkpoint — patch 4 bytes little-endian
|
|
1322
|
+
this.bytecodeArr[addr] = resetKey & 0xFF;
|
|
1323
|
+
this.bytecodeArr[addr + 1] = (resetKey >> 8) & 0xFF;
|
|
1324
|
+
this.bytecodeArr[addr + 2] = (resetKey >> 16) & 0xFF;
|
|
1325
|
+
this.bytecodeArr[addr + 3] = (resetKey >> 24) & 0xFF;
|
|
1326
|
+
addr += 4;
|
|
1327
|
+
}
|
|
1328
|
+
else if (["r", "reg"].includes(argDef.name)) {
|
|
1329
|
+
addr += 1;
|
|
1330
|
+
}
|
|
1331
|
+
else if (argDef.type === "INT") {
|
|
1332
|
+
addr += 4;
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
addr += 1;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
// ── encrypt ───────────────────────────────────────────────────────────────
|
|
1341
|
+
encryptBytecode() {
|
|
1342
|
+
const { initialKey, lcgMul, lcgInc } = this.config;
|
|
1343
|
+
const encrypted = [];
|
|
1344
|
+
let key = initialKey >>> 0;
|
|
1345
|
+
for (const b of this.bytecodeArr) {
|
|
1346
|
+
encrypted.push((b ^ (key >>> 24)) & 0xFF);
|
|
1347
|
+
key = ((Math.imul(key, lcgMul) + lcgInc) | 0) >>> 0;
|
|
1348
|
+
}
|
|
1349
|
+
return encrypted;
|
|
1350
|
+
}
|
|
1351
|
+
// ── compile: top-level entry ──────────────────────────────────────────────
|
|
1352
|
+
/**
|
|
1353
|
+
* Compile a Babel Program AST body (array of statements) as the main script.
|
|
1354
|
+
* All function declarations in the body are pre-collected and emitted before main.
|
|
1355
|
+
*/
|
|
1356
|
+
compileProgram(stmts) {
|
|
1357
|
+
// Pre-collect top-level vars + functions
|
|
1358
|
+
for (const node of stmts) {
|
|
1359
|
+
if (node.type === "VariableDeclaration") {
|
|
1360
|
+
for (const d of node.declarations) {
|
|
1361
|
+
if (d.id.type === "Identifier")
|
|
1362
|
+
this.allocVar(d.id.name);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
this.collectFunctions(stmts);
|
|
1367
|
+
const globalVars = Object.assign({}, this.variables);
|
|
1368
|
+
const globalNextSlot = this.nextVarSlot;
|
|
1369
|
+
const hasFunctions = Object.keys(this.functions).length > 0;
|
|
1370
|
+
const mainLabel = "__main_start";
|
|
1371
|
+
if (hasFunctions)
|
|
1372
|
+
this.emitJmp(mainLabel);
|
|
1373
|
+
// Emit functions
|
|
1374
|
+
let runningSlot = globalNextSlot;
|
|
1375
|
+
for (const [funcName, funcInfo] of Object.entries(this.functions)) {
|
|
1376
|
+
this.setLabel(`__func_${funcName}`);
|
|
1377
|
+
const savedVars = this.variables, savedNext = this.nextVarSlot;
|
|
1378
|
+
this.variables = Object.assign({}, globalVars);
|
|
1379
|
+
this.nextVarSlot = runningSlot;
|
|
1380
|
+
this.variables["this"] = this.nextVarSlot++;
|
|
1381
|
+
for (const p of funcInfo.params)
|
|
1382
|
+
this.variables[p] = this.nextVarSlot++;
|
|
1383
|
+
const thisR = this.getVarReg("this");
|
|
1384
|
+
this.emitPop(thisR);
|
|
1385
|
+
for (let p = funcInfo.params.length - 1; p >= 0; p--) {
|
|
1386
|
+
this.emitPop(this.getVarReg(funcInfo.params[p]));
|
|
1387
|
+
}
|
|
1388
|
+
this.compileStatement(funcInfo.body);
|
|
1389
|
+
this.emitMovInt(0, 0);
|
|
1390
|
+
this.emitRet();
|
|
1391
|
+
runningSlot = this.nextVarSlot;
|
|
1392
|
+
this.variables = savedVars;
|
|
1393
|
+
this.nextVarSlot = savedNext;
|
|
1394
|
+
}
|
|
1395
|
+
if (hasFunctions)
|
|
1396
|
+
this.setLabel(mainLabel);
|
|
1397
|
+
for (const stmt of stmts)
|
|
1398
|
+
this.compileStatement(stmt);
|
|
1399
|
+
this.emit([this.opcodes["HALT"]]);
|
|
1400
|
+
this.resolveJumps();
|
|
1401
|
+
const encrypted = this.encryptBytecode();
|
|
1402
|
+
const base64 = Buffer.from(encrypted).toString("base64");
|
|
1403
|
+
return { bytecode: encrypted, base64 };
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Compile a single Babel FunctionDeclaration node.
|
|
1407
|
+
* Used by transform-vm.ts to virtualise individual functions.
|
|
1408
|
+
*/
|
|
1409
|
+
compileFunction(fnNode) {
|
|
1410
|
+
if (!fnNode.id)
|
|
1411
|
+
throw new KrakBailError("anonymous function");
|
|
1412
|
+
// Pre-collect params + body vars
|
|
1413
|
+
for (const p of fnNode.params) {
|
|
1414
|
+
if (p.type !== "Identifier")
|
|
1415
|
+
throw new KrakBailError("param-pattern");
|
|
1416
|
+
this.allocVar(p.name);
|
|
1417
|
+
}
|
|
1418
|
+
this.collectFunctions(fnNode.body.body);
|
|
1419
|
+
const mainLabel = "__main_start";
|
|
1420
|
+
const globalVars = Object.assign({}, this.variables);
|
|
1421
|
+
const hasFunctions = Object.keys(this.functions).length > 0;
|
|
1422
|
+
if (hasFunctions)
|
|
1423
|
+
this.emitJmp(mainLabel);
|
|
1424
|
+
// Sub-functions declared inside this function
|
|
1425
|
+
let runningSlot = this.nextVarSlot;
|
|
1426
|
+
for (const [fname, finfo] of Object.entries(this.functions)) {
|
|
1427
|
+
this.setLabel(`__func_${fname}`);
|
|
1428
|
+
const savedVars = this.variables, savedNext = this.nextVarSlot;
|
|
1429
|
+
this.variables = Object.assign({}, globalVars);
|
|
1430
|
+
this.nextVarSlot = runningSlot;
|
|
1431
|
+
this.variables["this"] = this.nextVarSlot++;
|
|
1432
|
+
for (const p of finfo.params)
|
|
1433
|
+
this.variables[p] = this.nextVarSlot++;
|
|
1434
|
+
const thisR = this.getVarReg("this");
|
|
1435
|
+
this.emitPop(thisR);
|
|
1436
|
+
for (let p = finfo.params.length - 1; p >= 0; p--) {
|
|
1437
|
+
this.emitPop(this.getVarReg(finfo.params[p]));
|
|
1438
|
+
}
|
|
1439
|
+
this.compileStatement(finfo.body);
|
|
1440
|
+
this.emitMovInt(0, 0);
|
|
1441
|
+
this.emitRet();
|
|
1442
|
+
runningSlot = this.nextVarSlot;
|
|
1443
|
+
this.variables = savedVars;
|
|
1444
|
+
this.nextVarSlot = savedNext;
|
|
1445
|
+
}
|
|
1446
|
+
if (hasFunctions)
|
|
1447
|
+
this.setLabel(mainLabel);
|
|
1448
|
+
// Pop params in reverse order (caller pushed them in order)
|
|
1449
|
+
const thisR2 = this.allocVar("this") % 256;
|
|
1450
|
+
this.emitPop(thisR2);
|
|
1451
|
+
for (let p = fnNode.params.length - 1; p >= 0; p--) {
|
|
1452
|
+
this.emitPop(this.getVarReg(fnNode.params[p].name));
|
|
1453
|
+
}
|
|
1454
|
+
this.compileStatement(fnNode.body);
|
|
1455
|
+
this.emitMovInt(0, 0);
|
|
1456
|
+
this.emitRet();
|
|
1457
|
+
this.resolveJumps();
|
|
1458
|
+
const encrypted = this.encryptBytecode();
|
|
1459
|
+
const base64 = Buffer.from(encrypted).toString("base64");
|
|
1460
|
+
return { bytecode: encrypted, base64 };
|
|
1461
|
+
}
|
|
1462
|
+
collectFunctions(stmts) {
|
|
1463
|
+
for (const node of stmts) {
|
|
1464
|
+
if (!node)
|
|
1465
|
+
continue;
|
|
1466
|
+
if (node.type === "FunctionDeclaration") {
|
|
1467
|
+
this.allocVar(node.id.name);
|
|
1468
|
+
this.functions[node.id.name] = {
|
|
1469
|
+
params: node.params.map((p) => p.name),
|
|
1470
|
+
body: node.body,
|
|
1471
|
+
};
|
|
1472
|
+
this.collectFunctions(node.body.body);
|
|
1473
|
+
}
|
|
1474
|
+
else if (node.type === "BlockStatement") {
|
|
1475
|
+
this.collectFunctions(node.body);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
exports.KrakCompiler = KrakCompiler;
|