@tsvm/transforms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +130 -0
- package/dist/index.js +2854 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2854 @@
|
|
|
1
|
+
// src/passes/preserve-type-illusions.ts
|
|
2
|
+
import { OpCode, IRType, OperandKind as OperandKind2, ConstantKind } from "@tsvm/shared";
|
|
3
|
+
|
|
4
|
+
// src/utils.ts
|
|
5
|
+
import { OperandKind } from "@tsvm/shared";
|
|
6
|
+
function getMaxRegister(func) {
|
|
7
|
+
let maxReg = -1;
|
|
8
|
+
const consider = (value) => {
|
|
9
|
+
if (!value) return;
|
|
10
|
+
const m = /^r(\d+)$/.exec(value);
|
|
11
|
+
if (m) {
|
|
12
|
+
maxReg = Math.max(maxReg, Number.parseInt(m[1], 10));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
for (const param of func.params) consider(param.register);
|
|
16
|
+
for (const local of func.locals) consider(local.register);
|
|
17
|
+
for (const block of func.blocks) {
|
|
18
|
+
for (const phi of block.phiNodes ?? []) {
|
|
19
|
+
consider(phi.result);
|
|
20
|
+
for (const incoming of phi.incoming) consider(incoming.register);
|
|
21
|
+
}
|
|
22
|
+
for (const inst of block.instructions) {
|
|
23
|
+
consider(inst.result);
|
|
24
|
+
for (const op of inst.operands) {
|
|
25
|
+
if (op.kind === OperandKind.Register) consider(op.value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
consider(block.terminator.condition);
|
|
29
|
+
consider(block.terminator.returnValue);
|
|
30
|
+
}
|
|
31
|
+
return maxReg + 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/passes/preserve-type-illusions.ts
|
|
35
|
+
var PreserveTypeIllusionsPass = class {
|
|
36
|
+
name = "PreserveTypeIllusionsPass";
|
|
37
|
+
priority = 10;
|
|
38
|
+
execute(ctx) {
|
|
39
|
+
let nodesTransformed = 0;
|
|
40
|
+
const newCP = [...ctx.module.constantPool];
|
|
41
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
42
|
+
if (!func.isVirtualized) return func;
|
|
43
|
+
if (func.params.length === 0) return func;
|
|
44
|
+
if (!func.blocks || func.blocks.length === 0) return func;
|
|
45
|
+
if (ctx.rng.nextFloat() >= 0.3) {
|
|
46
|
+
return func;
|
|
47
|
+
}
|
|
48
|
+
nodesTransformed++;
|
|
49
|
+
const nextReg = getMaxRegister(func);
|
|
50
|
+
const tempReg1 = `r${nextReg}`;
|
|
51
|
+
const tempReg2 = `r${nextReg + 1}`;
|
|
52
|
+
const tempReg3 = `r${nextReg + 2}`;
|
|
53
|
+
let functionConstIdx = newCP.findIndex((cp) => cp.kind === ConstantKind.String && cp.value === "function");
|
|
54
|
+
if (functionConstIdx === -1) {
|
|
55
|
+
functionConstIdx = newCP.length;
|
|
56
|
+
newCP.push({ index: functionConstIdx, kind: ConstantKind.String, value: "function" });
|
|
57
|
+
}
|
|
58
|
+
const paramReg = func.params[0].register;
|
|
59
|
+
const newInsts = [
|
|
60
|
+
{
|
|
61
|
+
opcode: OpCode.TypeOf,
|
|
62
|
+
operands: [{ kind: OperandKind2.Register, value: paramReg }],
|
|
63
|
+
result: tempReg1
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
opcode: OpCode.LoadConst,
|
|
67
|
+
operands: [{ kind: OperandKind2.ConstantIndex, value: functionConstIdx }],
|
|
68
|
+
result: tempReg2
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
opcode: OpCode.Eq,
|
|
72
|
+
operands: [
|
|
73
|
+
{ kind: OperandKind2.Register, value: tempReg1 },
|
|
74
|
+
{ kind: OperandKind2.Register, value: tempReg2 }
|
|
75
|
+
],
|
|
76
|
+
result: tempReg3
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
const newBlocks = func.blocks.map((block, idx) => {
|
|
80
|
+
if (idx === 0) {
|
|
81
|
+
return {
|
|
82
|
+
...block,
|
|
83
|
+
instructions: [...newInsts, ...block.instructions]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return block;
|
|
87
|
+
});
|
|
88
|
+
const addedLocals = [
|
|
89
|
+
{
|
|
90
|
+
name: `type_illusion_temp_${tempReg1}`,
|
|
91
|
+
register: tempReg1,
|
|
92
|
+
type: IRType.String,
|
|
93
|
+
isCaptured: false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: `type_illusion_temp_${tempReg2}`,
|
|
97
|
+
register: tempReg2,
|
|
98
|
+
type: IRType.String,
|
|
99
|
+
isCaptured: false
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: `type_illusion_temp_${tempReg3}`,
|
|
103
|
+
register: tempReg3,
|
|
104
|
+
type: IRType.Boolean,
|
|
105
|
+
isCaptured: false
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
return {
|
|
109
|
+
...func,
|
|
110
|
+
locals: [...func.locals, ...addedLocals],
|
|
111
|
+
blocks: newBlocks
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
module: { ...ctx.module, functions: newFunctions, constantPool: newCP },
|
|
116
|
+
symbolsRenamed: 0,
|
|
117
|
+
nodesTransformed,
|
|
118
|
+
diagnostics: []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/passes/generic-confusion.ts
|
|
124
|
+
import { OpCode as OpCode2, OperandKind as OperandKind3, IRType as IRType2 } from "@tsvm/shared";
|
|
125
|
+
var GenericConfusionPass = class {
|
|
126
|
+
name = "GenericConfusionPass";
|
|
127
|
+
priority = 20;
|
|
128
|
+
execute(ctx) {
|
|
129
|
+
let nodesTransformed = 0;
|
|
130
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
131
|
+
let changed = false;
|
|
132
|
+
const nextReg = getMaxRegister(func);
|
|
133
|
+
let tempIndex = 0;
|
|
134
|
+
const addedLocals = [];
|
|
135
|
+
const newBlocks = func.blocks.map((block) => {
|
|
136
|
+
const newInstructions = [];
|
|
137
|
+
for (const inst of block.instructions) {
|
|
138
|
+
if (inst.opcode === OpCode2.Call || inst.opcode === OpCode2.CallMethod) {
|
|
139
|
+
if (ctx.rng.nextFloat() < 0.1) {
|
|
140
|
+
changed = true;
|
|
141
|
+
nodesTransformed++;
|
|
142
|
+
const tempReg1 = `r${nextReg + tempIndex++}`;
|
|
143
|
+
const tempReg2 = `r${nextReg + tempIndex++}`;
|
|
144
|
+
const tempReg3 = `r${nextReg + tempIndex++}`;
|
|
145
|
+
addedLocals.push(
|
|
146
|
+
{ name: `generic_conf_temp_${tempReg1}`, register: tempReg1, type: IRType2.Any, isCaptured: false },
|
|
147
|
+
{ name: `generic_conf_temp_${tempReg2}`, register: tempReg2, type: IRType2.String, isCaptured: false },
|
|
148
|
+
{ name: `generic_conf_temp_${tempReg3}`, register: tempReg3, type: IRType2.Boolean, isCaptured: false }
|
|
149
|
+
);
|
|
150
|
+
newInstructions.push({
|
|
151
|
+
opcode: OpCode2.Move,
|
|
152
|
+
operands: [inst.operands[0], { kind: OperandKind3.Register, value: tempReg1 }],
|
|
153
|
+
metadata: { genericConfusion: true }
|
|
154
|
+
});
|
|
155
|
+
newInstructions.push({
|
|
156
|
+
opcode: OpCode2.TypeOf,
|
|
157
|
+
operands: [
|
|
158
|
+
{ kind: OperandKind3.Register, value: tempReg1 },
|
|
159
|
+
{ kind: OperandKind3.Register, value: tempReg2 }
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
newInstructions.push({
|
|
163
|
+
opcode: OpCode2.Eq,
|
|
164
|
+
operands: [
|
|
165
|
+
{ kind: OperandKind3.Register, value: tempReg1 },
|
|
166
|
+
{ kind: OperandKind3.Register, value: tempReg2 }
|
|
167
|
+
],
|
|
168
|
+
result: tempReg3
|
|
169
|
+
});
|
|
170
|
+
newInstructions.push(inst);
|
|
171
|
+
} else {
|
|
172
|
+
newInstructions.push(inst);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
newInstructions.push(inst);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return changed ? { ...block, instructions: newInstructions } : block;
|
|
179
|
+
});
|
|
180
|
+
return changed ? {
|
|
181
|
+
...func,
|
|
182
|
+
locals: [...func.locals, ...addedLocals],
|
|
183
|
+
blocks: newBlocks
|
|
184
|
+
} : func;
|
|
185
|
+
});
|
|
186
|
+
const newModule = {
|
|
187
|
+
...ctx.module,
|
|
188
|
+
functions: newFunctions
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
module: newModule,
|
|
192
|
+
symbolsRenamed: 0,
|
|
193
|
+
nodesTransformed,
|
|
194
|
+
diagnostics: []
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/passes/decorator-aware-lowering.ts
|
|
200
|
+
import { OpCode as OpCode3, OperandKind as OperandKind4, IRType as IRType3 } from "@tsvm/shared";
|
|
201
|
+
var DecoratorAwareLoweringPass = class {
|
|
202
|
+
name = "DecoratorAwareLoweringPass";
|
|
203
|
+
priority = 30;
|
|
204
|
+
execute(ctx) {
|
|
205
|
+
let nodesTransformed = 0;
|
|
206
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
207
|
+
if (!func.isVirtualized) return func;
|
|
208
|
+
const nextReg = getMaxRegister(func);
|
|
209
|
+
let tempIndex = 0;
|
|
210
|
+
const addedLocals = [];
|
|
211
|
+
const newBlocks = func.blocks.map((block) => {
|
|
212
|
+
const newInsts = block.instructions.flatMap((inst) => {
|
|
213
|
+
if ((inst.opcode === OpCode3.Call || inst.opcode === OpCode3.CallMethod) && inst.operands.length > 0) {
|
|
214
|
+
const firstOp = inst.operands[0];
|
|
215
|
+
if (firstOp.kind === OperandKind4.Register && ctx.rng.nextFloat() < 0.15) {
|
|
216
|
+
const tempReg = `r${nextReg + tempIndex}`;
|
|
217
|
+
tempIndex++;
|
|
218
|
+
nodesTransformed++;
|
|
219
|
+
addedLocals.push({
|
|
220
|
+
name: `decorator_temp_${tempReg}`,
|
|
221
|
+
register: tempReg,
|
|
222
|
+
type: IRType3.Any,
|
|
223
|
+
isCaptured: false
|
|
224
|
+
});
|
|
225
|
+
const moveInst = {
|
|
226
|
+
opcode: OpCode3.Move,
|
|
227
|
+
operands: [
|
|
228
|
+
{ kind: OperandKind4.Register, value: firstOp.value },
|
|
229
|
+
{ kind: OperandKind4.Register, value: tempReg }
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
const updatedInst = {
|
|
233
|
+
...inst,
|
|
234
|
+
operands: [{ kind: OperandKind4.Register, value: tempReg }, ...inst.operands.slice(1)]
|
|
235
|
+
};
|
|
236
|
+
return [moveInst, updatedInst];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return [inst];
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
...block,
|
|
243
|
+
instructions: newInsts
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
...func,
|
|
248
|
+
locals: [...func.locals, ...addedLocals],
|
|
249
|
+
blocks: newBlocks
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
module: { ...ctx.module, functions: newFunctions },
|
|
254
|
+
symbolsRenamed: 0,
|
|
255
|
+
nodesTransformed,
|
|
256
|
+
diagnostics: []
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/passes/namespace-virtualization.ts
|
|
262
|
+
import { OpCode as OpCode4, ConstantKind as ConstantKind3, OperandKind as OperandKind5, IRType as IRType4, FunctionAttribute } from "@tsvm/shared";
|
|
263
|
+
var NamespaceVirtualizationPass = class {
|
|
264
|
+
name = "NamespaceVirtualizationPass";
|
|
265
|
+
priority = 40;
|
|
266
|
+
execute(ctx) {
|
|
267
|
+
let nodesTransformed = 0;
|
|
268
|
+
const constantPool = [...ctx.module.constantPool];
|
|
269
|
+
function isThisRegister(func, reg) {
|
|
270
|
+
for (const block of func.blocks) {
|
|
271
|
+
for (const inst of block.instructions) {
|
|
272
|
+
if (inst.result === reg && inst.opcode === OpCode4.LoadThis) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
function getPropertyNameOfRegister(func, reg, cp) {
|
|
280
|
+
for (const block of func.blocks) {
|
|
281
|
+
for (const inst of block.instructions) {
|
|
282
|
+
if (inst.result === reg && inst.opcode === OpCode4.LoadConst) {
|
|
283
|
+
const op = inst.operands[0];
|
|
284
|
+
if (op && op.kind === OperandKind5.ConstantIndex) {
|
|
285
|
+
const entry = cp[op.value];
|
|
286
|
+
if (entry && entry.kind === ConstantKind3.String) {
|
|
287
|
+
return entry.value;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
const standardBuiltins = /* @__PURE__ */ new Set([
|
|
296
|
+
"prototype",
|
|
297
|
+
"constructor",
|
|
298
|
+
"length",
|
|
299
|
+
"name",
|
|
300
|
+
// Array & Object methods
|
|
301
|
+
"push",
|
|
302
|
+
"pop",
|
|
303
|
+
"shift",
|
|
304
|
+
"unshift",
|
|
305
|
+
"splice",
|
|
306
|
+
"slice",
|
|
307
|
+
"concat",
|
|
308
|
+
"join",
|
|
309
|
+
"forEach",
|
|
310
|
+
"map",
|
|
311
|
+
"filter",
|
|
312
|
+
"reduce",
|
|
313
|
+
"indexOf",
|
|
314
|
+
"includes",
|
|
315
|
+
"find",
|
|
316
|
+
"findIndex",
|
|
317
|
+
"keys",
|
|
318
|
+
"values",
|
|
319
|
+
"entries",
|
|
320
|
+
"toString",
|
|
321
|
+
"valueOf",
|
|
322
|
+
"toLocaleString",
|
|
323
|
+
"hasOwnProperty",
|
|
324
|
+
"isPrototypeOf",
|
|
325
|
+
"propertyIsEnumerable",
|
|
326
|
+
"apply",
|
|
327
|
+
"call",
|
|
328
|
+
"bind",
|
|
329
|
+
// Promise & Async / Iterator
|
|
330
|
+
"then",
|
|
331
|
+
"catch",
|
|
332
|
+
"finally",
|
|
333
|
+
"resolve",
|
|
334
|
+
"reject",
|
|
335
|
+
"next",
|
|
336
|
+
"throw",
|
|
337
|
+
"return",
|
|
338
|
+
"value",
|
|
339
|
+
"done",
|
|
340
|
+
// Error standard properties
|
|
341
|
+
"message",
|
|
342
|
+
"stack",
|
|
343
|
+
"cause",
|
|
344
|
+
// Console & System
|
|
345
|
+
"log",
|
|
346
|
+
"error",
|
|
347
|
+
"warn",
|
|
348
|
+
"info",
|
|
349
|
+
"dir",
|
|
350
|
+
"clear",
|
|
351
|
+
// NodeJS/Browser standard & VM
|
|
352
|
+
"exports",
|
|
353
|
+
"module",
|
|
354
|
+
"require",
|
|
355
|
+
"global",
|
|
356
|
+
"window",
|
|
357
|
+
"document",
|
|
358
|
+
"process",
|
|
359
|
+
"readFileSync",
|
|
360
|
+
"writeFileSync",
|
|
361
|
+
"readdirSync",
|
|
362
|
+
"statSync",
|
|
363
|
+
"mtime",
|
|
364
|
+
"getTime",
|
|
365
|
+
"exec",
|
|
366
|
+
"test",
|
|
367
|
+
"match",
|
|
368
|
+
"replace",
|
|
369
|
+
"split",
|
|
370
|
+
"trim",
|
|
371
|
+
"toLowerCase",
|
|
372
|
+
"toUpperCase",
|
|
373
|
+
// Symbol properties or other standard ones
|
|
374
|
+
"Symbol",
|
|
375
|
+
"iterator",
|
|
376
|
+
"asyncIterator",
|
|
377
|
+
"toStringTag",
|
|
378
|
+
// Reflect and Object proxy trap builtins
|
|
379
|
+
"setPrototypeOf",
|
|
380
|
+
"getPrototypeOf",
|
|
381
|
+
"defineProperty",
|
|
382
|
+
"defineProperties",
|
|
383
|
+
"getOwnPropertyDescriptor",
|
|
384
|
+
"getOwnPropertyNames",
|
|
385
|
+
"getOwnPropertySymbols",
|
|
386
|
+
"create",
|
|
387
|
+
"assign",
|
|
388
|
+
"freeze",
|
|
389
|
+
"seal",
|
|
390
|
+
"preventExtensions",
|
|
391
|
+
"isExtensible",
|
|
392
|
+
"isFrozen",
|
|
393
|
+
"isSealed",
|
|
394
|
+
"construct",
|
|
395
|
+
"has",
|
|
396
|
+
"get",
|
|
397
|
+
"set",
|
|
398
|
+
"deleteProperty",
|
|
399
|
+
"ownKeys"
|
|
400
|
+
]);
|
|
401
|
+
function hashString(str, seed) {
|
|
402
|
+
let hash = (seed ^ 2166136261) >>> 0;
|
|
403
|
+
const prime = 16777619;
|
|
404
|
+
for (let i = 0; i < str.length; i++) {
|
|
405
|
+
hash ^= str.charCodeAt(i);
|
|
406
|
+
hash = Math.imul(hash, prime) >>> 0;
|
|
407
|
+
}
|
|
408
|
+
const folded = hash >>> 16 ^ hash & 65535 ^ seed >>> 8 & 65535;
|
|
409
|
+
return folded.toString(16).padStart(4, "0") + ((hash ^ seed) >>> 0).toString(16);
|
|
410
|
+
}
|
|
411
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
412
|
+
let changed = false;
|
|
413
|
+
const nextReg = getMaxRegister(func);
|
|
414
|
+
let tempIndex = 0;
|
|
415
|
+
const addedLocals = [];
|
|
416
|
+
const paramRegs = /* @__PURE__ */ new Set();
|
|
417
|
+
const paramLocals = /* @__PURE__ */ new Set();
|
|
418
|
+
for (const p of func.params) {
|
|
419
|
+
paramRegs.add(p.register);
|
|
420
|
+
}
|
|
421
|
+
let sizeChanged = true;
|
|
422
|
+
while (sizeChanged) {
|
|
423
|
+
const oldRegSize = paramRegs.size;
|
|
424
|
+
const oldLocalSize = paramLocals.size;
|
|
425
|
+
for (const block of func.blocks) {
|
|
426
|
+
for (const inst of block.instructions) {
|
|
427
|
+
if (inst.opcode === OpCode4.Move) {
|
|
428
|
+
const srcOp = inst.operands[0];
|
|
429
|
+
const destOp = inst.operands[1];
|
|
430
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && paramRegs.has(srcOp.value)) {
|
|
431
|
+
if (destOp && destOp.kind === OperandKind5.Register && typeof destOp.value === "string") {
|
|
432
|
+
paramRegs.add(destOp.value);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else if (inst.opcode === OpCode4.StoreLocal) {
|
|
436
|
+
const localOp = inst.operands[0];
|
|
437
|
+
const srcOp = inst.operands[1];
|
|
438
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && paramRegs.has(srcOp.value)) {
|
|
439
|
+
if (localOp && localOp.kind === OperandKind5.Register && typeof localOp.value === "string") {
|
|
440
|
+
paramLocals.add(localOp.value);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
} else if (inst.opcode === OpCode4.LoadLocal && inst.result) {
|
|
444
|
+
const localOp = inst.operands[0];
|
|
445
|
+
if (localOp && localOp.kind === OperandKind5.Register && typeof localOp.value === "string") {
|
|
446
|
+
if (paramLocals.has(localOp.value) || paramRegs.has(localOp.value)) {
|
|
447
|
+
paramRegs.add(inst.result);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} else if (inst.result) {
|
|
451
|
+
const srcOp = inst.operands[0];
|
|
452
|
+
const isSrcReg = srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string";
|
|
453
|
+
if ((inst.opcode === OpCode4.PropGet || inst.opcode === OpCode4.ComputedGet) && isSrcReg && paramRegs.has(srcOp.value)) {
|
|
454
|
+
paramRegs.add(inst.result);
|
|
455
|
+
} else if (inst.opcode === OpCode4.Call || inst.opcode === OpCode4.CallMethod || inst.opcode === OpCode4.CallWithArray || inst.opcode === OpCode4.CallMethodWithArray) {
|
|
456
|
+
paramRegs.add(inst.result);
|
|
457
|
+
} else if (inst.opcode === OpCode4.RestArgs) {
|
|
458
|
+
paramRegs.add(inst.result);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
sizeChanged = paramRegs.size !== oldRegSize || paramLocals.size !== oldLocalSize;
|
|
464
|
+
}
|
|
465
|
+
const lexicalThisRegs = /* @__PURE__ */ new Set();
|
|
466
|
+
const lexicalThisLocals = /* @__PURE__ */ new Set();
|
|
467
|
+
for (const block of func.blocks) {
|
|
468
|
+
for (const inst of block.instructions) {
|
|
469
|
+
if (inst.opcode === OpCode4.EnvGet && inst.result) {
|
|
470
|
+
const op = inst.operands[0];
|
|
471
|
+
if (op && op.kind === OperandKind5.Immediate && typeof op.value === "number") {
|
|
472
|
+
const capVar = func.capturedVariables[op.value];
|
|
473
|
+
if (capVar === "$$vm_lexical_this") {
|
|
474
|
+
lexicalThisRegs.add(inst.result);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
let lexicalSizeChanged = true;
|
|
481
|
+
while (lexicalSizeChanged) {
|
|
482
|
+
const oldRegSize = lexicalThisRegs.size;
|
|
483
|
+
const oldLocalSize = lexicalThisLocals.size;
|
|
484
|
+
for (const block of func.blocks) {
|
|
485
|
+
for (const inst of block.instructions) {
|
|
486
|
+
if (inst.opcode === OpCode4.Move) {
|
|
487
|
+
const srcOp = inst.operands[0];
|
|
488
|
+
const destOp = inst.operands[1];
|
|
489
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && lexicalThisRegs.has(srcOp.value)) {
|
|
490
|
+
if (destOp && destOp.kind === OperandKind5.Register && typeof destOp.value === "string") {
|
|
491
|
+
lexicalThisRegs.add(destOp.value);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
} else if (inst.opcode === OpCode4.StoreLocal) {
|
|
495
|
+
const localOp = inst.operands[0];
|
|
496
|
+
const srcOp = inst.operands[1];
|
|
497
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && lexicalThisRegs.has(srcOp.value)) {
|
|
498
|
+
if (localOp && localOp.kind === OperandKind5.Register && typeof localOp.value === "string") {
|
|
499
|
+
lexicalThisLocals.add(localOp.value);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} else if (inst.opcode === OpCode4.LoadLocal && inst.result) {
|
|
503
|
+
const localOp = inst.operands[0];
|
|
504
|
+
if (localOp && localOp.kind === OperandKind5.Register && typeof localOp.value === "string") {
|
|
505
|
+
if (lexicalThisLocals.has(localOp.value) || lexicalThisRegs.has(localOp.value)) {
|
|
506
|
+
lexicalThisRegs.add(inst.result);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else if (inst.result) {
|
|
510
|
+
if (inst.opcode === OpCode4.CellGet || inst.opcode === OpCode4.CellNew) {
|
|
511
|
+
const srcOp = inst.operands[0];
|
|
512
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && lexicalThisRegs.has(srcOp.value)) {
|
|
513
|
+
lexicalThisRegs.add(inst.result);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
lexicalSizeChanged = lexicalThisRegs.size !== oldRegSize || lexicalThisLocals.size !== oldLocalSize;
|
|
520
|
+
}
|
|
521
|
+
const localObjects = /* @__PURE__ */ new Set();
|
|
522
|
+
const localObjectLocals = /* @__PURE__ */ new Set();
|
|
523
|
+
let localObjSizeChanged = true;
|
|
524
|
+
while (localObjSizeChanged) {
|
|
525
|
+
const oldRegSize = localObjects.size;
|
|
526
|
+
const oldLocalSize = localObjectLocals.size;
|
|
527
|
+
for (const block of func.blocks) {
|
|
528
|
+
for (const inst of block.instructions) {
|
|
529
|
+
if (inst.opcode === OpCode4.ObjectNew && inst.result) {
|
|
530
|
+
localObjects.add(inst.result);
|
|
531
|
+
} else if (inst.opcode === OpCode4.Move) {
|
|
532
|
+
const srcOp = inst.operands[0];
|
|
533
|
+
const destOp = inst.operands[1];
|
|
534
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && localObjects.has(srcOp.value)) {
|
|
535
|
+
if (destOp && destOp.kind === OperandKind5.Register && typeof destOp.value === "string") {
|
|
536
|
+
localObjects.add(destOp.value);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} else if (inst.opcode === OpCode4.StoreLocal) {
|
|
540
|
+
const localOp = inst.operands[0];
|
|
541
|
+
const srcOp = inst.operands[1];
|
|
542
|
+
if (srcOp && srcOp.kind === OperandKind5.Register && typeof srcOp.value === "string" && localObjects.has(srcOp.value)) {
|
|
543
|
+
if (localOp && localOp.kind === OperandKind5.Register && typeof localOp.value === "string") {
|
|
544
|
+
localObjectLocals.add(localOp.value);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
} else if (inst.opcode === OpCode4.LoadLocal && inst.result) {
|
|
548
|
+
const localOp = inst.operands[0];
|
|
549
|
+
if (localOp && localOp.kind === OperandKind5.Register && typeof localOp.value === "string") {
|
|
550
|
+
if (localObjectLocals.has(localOp.value) || localObjects.has(localOp.value)) {
|
|
551
|
+
localObjects.add(inst.result);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
localObjSizeChanged = localObjects.size !== oldRegSize || localObjectLocals.size !== oldLocalSize;
|
|
558
|
+
}
|
|
559
|
+
const newBlocks = func.blocks.map((block) => {
|
|
560
|
+
const newInstructions = [];
|
|
561
|
+
for (const inst of block.instructions) {
|
|
562
|
+
if (inst.opcode === OpCode4.PropGet || inst.opcode === OpCode4.PropSet) {
|
|
563
|
+
const objOp = inst.operands[0];
|
|
564
|
+
const keyOp = inst.operands[1];
|
|
565
|
+
const isStaticContext = func.attributes && func.attributes.indexOf(FunctionAttribute.Static) >= 0;
|
|
566
|
+
const isObjThis = !isStaticContext && objOp && objOp.kind === OperandKind5.Register && typeof objOp.value === "string" && (isThisRegister(func, objOp.value) || lexicalThisRegs.has(objOp.value));
|
|
567
|
+
const isObjParam = objOp && objOp.kind === OperandKind5.Register && typeof objOp.value === "string" && paramRegs.has(objOp.value);
|
|
568
|
+
const isLocalObj = objOp && objOp.kind === OperandKind5.Register && typeof objOp.value === "string" && localObjects.has(objOp.value);
|
|
569
|
+
const propName = keyOp && keyOp.kind === OperandKind5.Register && typeof keyOp.value === "string" ? getPropertyNameOfRegister(func, keyOp.value, constantPool) : void 0;
|
|
570
|
+
const isBuiltin = propName && (standardBuiltins.has(propName) || ctx.module.exports.some((e) => e.exportedName === propName || e.localName === propName) || ctx.module.imports.some((i) => i.localName === propName || i.importedName === propName));
|
|
571
|
+
if (!isObjThis && !isObjParam && !isLocalObj && propName && !isBuiltin) {
|
|
572
|
+
changed = true;
|
|
573
|
+
nodesTransformed++;
|
|
574
|
+
const propHash = hashString(propName, ctx.profile.seed);
|
|
575
|
+
const hashedName = `hash_${propHash}`;
|
|
576
|
+
let cpIndex = constantPool.findIndex((c) => c.kind === ConstantKind3.String && c.value === hashedName);
|
|
577
|
+
if (cpIndex === -1) {
|
|
578
|
+
cpIndex = constantPool.length;
|
|
579
|
+
constantPool.push({
|
|
580
|
+
index: cpIndex,
|
|
581
|
+
kind: ConstantKind3.String,
|
|
582
|
+
value: hashedName
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
const tempReg = `r${nextReg + tempIndex++}`;
|
|
586
|
+
addedLocals.push({
|
|
587
|
+
name: `ns_virt_temp_${tempReg}`,
|
|
588
|
+
register: tempReg,
|
|
589
|
+
type: IRType4.String,
|
|
590
|
+
isCaptured: false
|
|
591
|
+
});
|
|
592
|
+
newInstructions.push({
|
|
593
|
+
opcode: OpCode4.LoadConst,
|
|
594
|
+
operands: [{ kind: OperandKind5.ConstantIndex, value: cpIndex }],
|
|
595
|
+
result: tempReg,
|
|
596
|
+
sourceLocation: inst.sourceLocation
|
|
597
|
+
});
|
|
598
|
+
const ops = [...inst.operands];
|
|
599
|
+
if (ops.length >= 2) {
|
|
600
|
+
ops[1] = { kind: OperandKind5.Register, value: tempReg };
|
|
601
|
+
}
|
|
602
|
+
newInstructions.push({
|
|
603
|
+
...inst,
|
|
604
|
+
opcode: inst.opcode === OpCode4.PropGet ? OpCode4.ComputedGet : OpCode4.ComputedSet,
|
|
605
|
+
operands: ops,
|
|
606
|
+
metadata: { namespaceVirtualization: true }
|
|
607
|
+
});
|
|
608
|
+
} else {
|
|
609
|
+
newInstructions.push(inst);
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
newInstructions.push(inst);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return changed ? { ...block, instructions: newInstructions } : block;
|
|
616
|
+
});
|
|
617
|
+
return changed ? {
|
|
618
|
+
...func,
|
|
619
|
+
locals: [...func.locals, ...addedLocals],
|
|
620
|
+
blocks: newBlocks
|
|
621
|
+
} : func;
|
|
622
|
+
});
|
|
623
|
+
const newModule = {
|
|
624
|
+
...ctx.module,
|
|
625
|
+
functions: newFunctions,
|
|
626
|
+
constantPool
|
|
627
|
+
};
|
|
628
|
+
return {
|
|
629
|
+
module: newModule,
|
|
630
|
+
symbolsRenamed: 0,
|
|
631
|
+
nodesTransformed,
|
|
632
|
+
diagnostics: []
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// src/passes/type-level-fake-path.ts
|
|
638
|
+
import { OpCode as OpCode5, ConstantKind as ConstantKind4, OperandKind as OperandKind6, IRType as IRType5 } from "@tsvm/shared";
|
|
639
|
+
var TypeLevelFakePathPass = class {
|
|
640
|
+
name = "TypeLevelFakePathPass";
|
|
641
|
+
priority = 30;
|
|
642
|
+
execute(ctx) {
|
|
643
|
+
let nodesTransformed = 0;
|
|
644
|
+
const newCP = [...ctx.module.constantPool];
|
|
645
|
+
function collectDecoyInstructions(func, junkRegs, rng) {
|
|
646
|
+
const realBlocks = func.blocks.filter((b) => !b.id.startsWith("__fake_path_") && b.instructions.length >= 2);
|
|
647
|
+
if (realBlocks.length === 0) return [];
|
|
648
|
+
const sourceBlock = rng.pick(realBlocks);
|
|
649
|
+
const count = Math.min(rng.nextRange(2, 3), sourceBlock.instructions.length);
|
|
650
|
+
const startIdx = rng.nextRange(0, Math.max(0, sourceBlock.instructions.length - count));
|
|
651
|
+
const decoys = [];
|
|
652
|
+
for (let d = 0; d < count; d++) {
|
|
653
|
+
const origInst = sourceBlock.instructions[startIdx + d];
|
|
654
|
+
if (!origInst) break;
|
|
655
|
+
if (origInst.opcode === OpCode5.Jmp || origInst.opcode === OpCode5.JmpIf || origInst.opcode === OpCode5.JmpIfNot || origInst.opcode === OpCode5.Return || origInst.opcode === OpCode5.ReturnVoid || origInst.opcode === OpCode5.Throw || origInst.opcode === OpCode5.Trap || origInst.opcode === OpCode5.Halt || origInst.opcode === OpCode5.Call || origInst.opcode === OpCode5.CallMethod || origInst.opcode === OpCode5.New) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const junkReg = junkRegs[d % junkRegs.length];
|
|
659
|
+
decoys.push({
|
|
660
|
+
...origInst,
|
|
661
|
+
result: origInst.result ? junkReg : origInst.result,
|
|
662
|
+
sourceLocation: void 0,
|
|
663
|
+
// Strip source location from decoys
|
|
664
|
+
metadata: void 0
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
return decoys;
|
|
668
|
+
}
|
|
669
|
+
function buildFakeBlockEnding(rng, junkTemp1, junkTemp2, regX, fortyTwoIdx, undefinedIdx) {
|
|
670
|
+
const endingType = rng.nextRange(0, 2);
|
|
671
|
+
if (endingType === 0) {
|
|
672
|
+
return {
|
|
673
|
+
instructions: [{ opcode: OpCode5.Trap, operands: [] }],
|
|
674
|
+
terminatorKind: "unreachable"
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
if (endingType === 1) {
|
|
678
|
+
return {
|
|
679
|
+
instructions: [
|
|
680
|
+
{
|
|
681
|
+
opcode: OpCode5.LoadConst,
|
|
682
|
+
operands: [{ kind: OperandKind6.ConstantIndex, value: undefinedIdx }],
|
|
683
|
+
result: junkTemp1
|
|
684
|
+
}
|
|
685
|
+
],
|
|
686
|
+
terminatorKind: "return"
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
instructions: [
|
|
691
|
+
{
|
|
692
|
+
opcode: OpCode5.LoadConst,
|
|
693
|
+
operands: [{ kind: OperandKind6.ConstantIndex, value: fortyTwoIdx }],
|
|
694
|
+
result: junkTemp1
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
opcode: OpCode5.Add,
|
|
698
|
+
operands: [
|
|
699
|
+
{ kind: OperandKind6.Register, value: junkTemp1 },
|
|
700
|
+
{ kind: OperandKind6.Register, value: regX }
|
|
701
|
+
],
|
|
702
|
+
result: junkTemp2
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
opcode: OpCode5.Move,
|
|
706
|
+
operands: [
|
|
707
|
+
{ kind: OperandKind6.Register, value: junkTemp2 },
|
|
708
|
+
{ kind: OperandKind6.Register, value: junkTemp1 }
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
],
|
|
712
|
+
terminatorKind: "return"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
716
|
+
if (!func.isVirtualized) return func;
|
|
717
|
+
if (func.blocks.length < 3) return func;
|
|
718
|
+
const isParanoid = ctx.profile.vm?.runtimeHardening === "paranoid";
|
|
719
|
+
if (!isParanoid && ctx.rng.nextFloat() >= 0.25) {
|
|
720
|
+
return func;
|
|
721
|
+
}
|
|
722
|
+
let eligibleBlocks = func.blocks.filter((b) => b.terminator.kind === "jump");
|
|
723
|
+
if (eligibleBlocks.length === 0) {
|
|
724
|
+
return func;
|
|
725
|
+
}
|
|
726
|
+
const numFakes = isParanoid ? ctx.rng.nextRange(1, 3) : 1;
|
|
727
|
+
let currentFunc = { ...func };
|
|
728
|
+
for (let f = 0; f < numFakes; f++) {
|
|
729
|
+
eligibleBlocks = currentFunc.blocks.filter((b) => b.terminator.kind === "jump" && !b.id.startsWith("__fake_path_"));
|
|
730
|
+
if (eligibleBlocks.length === 0) break;
|
|
731
|
+
const blockToModify = ctx.rng.pick(eligibleBlocks);
|
|
732
|
+
if (!blockToModify.terminator.targets || blockToModify.terminator.targets.length === 0) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
nodesTransformed++;
|
|
736
|
+
const nextReg = getMaxRegister(currentFunc);
|
|
737
|
+
const tempA = `r${nextReg}`;
|
|
738
|
+
const tempB = `r${nextReg + 1}`;
|
|
739
|
+
const tempC = `r${nextReg + 2}`;
|
|
740
|
+
const tempD = `r${nextReg + 3}`;
|
|
741
|
+
const tempE = `r${nextReg + 4}`;
|
|
742
|
+
const tempF = `r${nextReg + 5}`;
|
|
743
|
+
const tempG = `r${nextReg + 6}`;
|
|
744
|
+
const tempP = `r${nextReg + 7}`;
|
|
745
|
+
const tempQ = `r${nextReg + 8}`;
|
|
746
|
+
const junkTemp1 = `r${nextReg + 9}`;
|
|
747
|
+
const junkTemp2 = `r${nextReg + 10}`;
|
|
748
|
+
const junkTemp3 = `r${nextReg + 11}`;
|
|
749
|
+
const regX = currentFunc.params.length > 0 ? currentFunc.params[0].register : "r0";
|
|
750
|
+
let zeroIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 0);
|
|
751
|
+
if (zeroIdx === -1) {
|
|
752
|
+
zeroIdx = newCP.length;
|
|
753
|
+
newCP.push({ index: zeroIdx, kind: ConstantKind4.Number, value: 0 });
|
|
754
|
+
}
|
|
755
|
+
let threeIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 3);
|
|
756
|
+
if (threeIdx === -1) {
|
|
757
|
+
threeIdx = newCP.length;
|
|
758
|
+
newCP.push({ index: threeIdx, kind: ConstantKind4.Number, value: 3 });
|
|
759
|
+
}
|
|
760
|
+
let fiveIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 5);
|
|
761
|
+
if (fiveIdx === -1) {
|
|
762
|
+
fiveIdx = newCP.length;
|
|
763
|
+
newCP.push({ index: fiveIdx, kind: ConstantKind4.Number, value: 5 });
|
|
764
|
+
}
|
|
765
|
+
let sevenIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 7);
|
|
766
|
+
if (sevenIdx === -1) {
|
|
767
|
+
sevenIdx = newCP.length;
|
|
768
|
+
newCP.push({ index: sevenIdx, kind: ConstantKind4.Number, value: 7 });
|
|
769
|
+
}
|
|
770
|
+
let fourIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 4);
|
|
771
|
+
if (fourIdx === -1) {
|
|
772
|
+
fourIdx = newCP.length;
|
|
773
|
+
newCP.push({ index: fourIdx, kind: ConstantKind4.Number, value: 4 });
|
|
774
|
+
}
|
|
775
|
+
let fortyTwoIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 42);
|
|
776
|
+
if (fortyTwoIdx === -1) {
|
|
777
|
+
fortyTwoIdx = newCP.length;
|
|
778
|
+
newCP.push({ index: fortyTwoIdx, kind: ConstantKind4.Number, value: 42 });
|
|
779
|
+
}
|
|
780
|
+
let undefinedIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Undefined);
|
|
781
|
+
if (undefinedIdx === -1) {
|
|
782
|
+
undefinedIdx = newCP.length;
|
|
783
|
+
newCP.push({ index: undefinedIdx, kind: ConstantKind4.Undefined, value: null });
|
|
784
|
+
}
|
|
785
|
+
let processIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.String && cp.value === "process");
|
|
786
|
+
if (processIdx === -1) {
|
|
787
|
+
processIdx = newCP.length;
|
|
788
|
+
newCP.push({ index: processIdx, kind: ConstantKind4.String, value: "process" });
|
|
789
|
+
}
|
|
790
|
+
let windowIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.String && cp.value === "window");
|
|
791
|
+
if (windowIdx === -1) {
|
|
792
|
+
windowIdx = newCP.length;
|
|
793
|
+
newCP.push({ index: windowIdx, kind: ConstantKind4.String, value: "window" });
|
|
794
|
+
}
|
|
795
|
+
let lengthIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.String && cp.value === "length");
|
|
796
|
+
if (lengthIdx === -1) {
|
|
797
|
+
lengthIdx = newCP.length;
|
|
798
|
+
newCP.push({ index: lengthIdx, kind: ConstantKind4.String, value: "length" });
|
|
799
|
+
}
|
|
800
|
+
let thirtyOneIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 31);
|
|
801
|
+
if (thirtyOneIdx === -1) {
|
|
802
|
+
thirtyOneIdx = newCP.length;
|
|
803
|
+
newCP.push({ index: thirtyOneIdx, kind: ConstantKind4.Number, value: 31 });
|
|
804
|
+
}
|
|
805
|
+
let fifteenIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 15);
|
|
806
|
+
if (fifteenIdx === -1) {
|
|
807
|
+
fifteenIdx = newCP.length;
|
|
808
|
+
newCP.push({ index: fifteenIdx, kind: ConstantKind4.Number, value: 15 });
|
|
809
|
+
}
|
|
810
|
+
let eightIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 8);
|
|
811
|
+
if (eightIdx === -1) {
|
|
812
|
+
eightIdx = newCP.length;
|
|
813
|
+
newCP.push({ index: eightIdx, kind: ConstantKind4.Number, value: 8 });
|
|
814
|
+
}
|
|
815
|
+
let errorIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.String && cp.value === "Error");
|
|
816
|
+
if (errorIdx === -1) {
|
|
817
|
+
errorIdx = newCP.length;
|
|
818
|
+
newCP.push({ index: errorIdx, kind: ConstantKind4.String, value: "Error" });
|
|
819
|
+
}
|
|
820
|
+
let stackIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.String && cp.value === "stack");
|
|
821
|
+
if (stackIdx === -1) {
|
|
822
|
+
stackIdx = newCP.length;
|
|
823
|
+
newCP.push({ index: stackIdx, kind: ConstantKind4.String, value: "stack" });
|
|
824
|
+
}
|
|
825
|
+
let stringIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.String && cp.value === "string");
|
|
826
|
+
if (stringIdx === -1) {
|
|
827
|
+
stringIdx = newCP.length;
|
|
828
|
+
newCP.push({ index: stringIdx, kind: ConstantKind4.String, value: "string" });
|
|
829
|
+
}
|
|
830
|
+
const templateId = isParanoid ? ctx.rng.nextRange(0, 4) : 0;
|
|
831
|
+
let opaqueInsts = [];
|
|
832
|
+
if (templateId === 0) {
|
|
833
|
+
opaqueInsts = [
|
|
834
|
+
// 1. typeof process length
|
|
835
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: processIdx }], result: tempA },
|
|
836
|
+
{ opcode: OpCode5.LoadGlobal, operands: [{ kind: OperandKind6.Register, value: tempA }], result: tempA },
|
|
837
|
+
{ opcode: OpCode5.TypeOf, operands: [{ kind: OperandKind6.Register, value: tempA }], result: tempB },
|
|
838
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: lengthIdx }], result: tempC },
|
|
839
|
+
{
|
|
840
|
+
opcode: OpCode5.PropGet,
|
|
841
|
+
operands: [
|
|
842
|
+
{ kind: OperandKind6.Register, value: tempB },
|
|
843
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
844
|
+
],
|
|
845
|
+
result: tempD
|
|
846
|
+
},
|
|
847
|
+
// len1
|
|
848
|
+
// 2. typeof window length
|
|
849
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: windowIdx }], result: tempA },
|
|
850
|
+
{ opcode: OpCode5.LoadGlobal, operands: [{ kind: OperandKind6.Register, value: tempA }], result: tempA },
|
|
851
|
+
{ opcode: OpCode5.TypeOf, operands: [{ kind: OperandKind6.Register, value: tempA }], result: tempE },
|
|
852
|
+
{
|
|
853
|
+
opcode: OpCode5.PropGet,
|
|
854
|
+
operands: [
|
|
855
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
856
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
857
|
+
],
|
|
858
|
+
result: tempF
|
|
859
|
+
},
|
|
860
|
+
// len2
|
|
861
|
+
// 3. hash = (len1 * 31 + len2) & 0xF
|
|
862
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: thirtyOneIdx }], result: tempA },
|
|
863
|
+
{
|
|
864
|
+
opcode: OpCode5.Mul,
|
|
865
|
+
operands: [
|
|
866
|
+
{ kind: OperandKind6.Register, value: tempD },
|
|
867
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
868
|
+
],
|
|
869
|
+
result: tempG
|
|
870
|
+
},
|
|
871
|
+
// len1 * 31
|
|
872
|
+
{
|
|
873
|
+
opcode: OpCode5.Add,
|
|
874
|
+
operands: [
|
|
875
|
+
{ kind: OperandKind6.Register, value: tempG },
|
|
876
|
+
{ kind: OperandKind6.Register, value: tempF }
|
|
877
|
+
],
|
|
878
|
+
result: tempG
|
|
879
|
+
},
|
|
880
|
+
// len1 * 31 + len2
|
|
881
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: fifteenIdx }], result: tempA },
|
|
882
|
+
{
|
|
883
|
+
opcode: OpCode5.BitAnd,
|
|
884
|
+
operands: [
|
|
885
|
+
{ kind: OperandKind6.Register, value: tempG },
|
|
886
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
887
|
+
],
|
|
888
|
+
result: tempG
|
|
889
|
+
},
|
|
890
|
+
// hash = tempG & 15
|
|
891
|
+
// 4. (hash * hash + 5) % 8 !== 0
|
|
892
|
+
{
|
|
893
|
+
opcode: OpCode5.Mul,
|
|
894
|
+
operands: [
|
|
895
|
+
{ kind: OperandKind6.Register, value: tempG },
|
|
896
|
+
{ kind: OperandKind6.Register, value: tempG }
|
|
897
|
+
],
|
|
898
|
+
result: tempE
|
|
899
|
+
},
|
|
900
|
+
// hash * hash
|
|
901
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: fiveIdx }], result: tempA },
|
|
902
|
+
{
|
|
903
|
+
opcode: OpCode5.Add,
|
|
904
|
+
operands: [
|
|
905
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
906
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
907
|
+
],
|
|
908
|
+
result: tempE
|
|
909
|
+
},
|
|
910
|
+
// hash * hash + 5
|
|
911
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: eightIdx }], result: tempA },
|
|
912
|
+
{
|
|
913
|
+
opcode: OpCode5.Mod,
|
|
914
|
+
operands: [
|
|
915
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
916
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
917
|
+
],
|
|
918
|
+
result: tempE
|
|
919
|
+
},
|
|
920
|
+
// (hash*hash+5) % 8
|
|
921
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: zeroIdx }], result: tempA },
|
|
922
|
+
{
|
|
923
|
+
opcode: OpCode5.StrictEq,
|
|
924
|
+
operands: [
|
|
925
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
926
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
927
|
+
],
|
|
928
|
+
result: tempE
|
|
929
|
+
},
|
|
930
|
+
// hashMod === 0
|
|
931
|
+
{ opcode: OpCode5.Not, operands: [{ kind: OperandKind6.Register, value: tempE }], result: tempP },
|
|
932
|
+
// envOpaque = !hashModEqualsZero
|
|
933
|
+
// 5. stack = new Error().stack, typeof stack === 'string'
|
|
934
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: errorIdx }], result: tempA },
|
|
935
|
+
{ opcode: OpCode5.LoadGlobal, operands: [{ kind: OperandKind6.Register, value: tempA }], result: tempA },
|
|
936
|
+
{ opcode: OpCode5.New, operands: [{ kind: OperandKind6.Register, value: tempA }], result: tempB },
|
|
937
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: stackIdx }], result: tempC },
|
|
938
|
+
{
|
|
939
|
+
opcode: OpCode5.PropGet,
|
|
940
|
+
operands: [
|
|
941
|
+
{ kind: OperandKind6.Register, value: tempB },
|
|
942
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
943
|
+
],
|
|
944
|
+
result: tempD
|
|
945
|
+
},
|
|
946
|
+
// stack
|
|
947
|
+
{ opcode: OpCode5.TypeOf, operands: [{ kind: OperandKind6.Register, value: tempD }], result: tempE },
|
|
948
|
+
// typeof stack
|
|
949
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: stringIdx }], result: tempA },
|
|
950
|
+
{
|
|
951
|
+
opcode: OpCode5.StrictEq,
|
|
952
|
+
operands: [
|
|
953
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
954
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
955
|
+
],
|
|
956
|
+
result: tempF
|
|
957
|
+
},
|
|
958
|
+
// typeof stack === 'string'
|
|
959
|
+
// 6. envPredicate = (rEnvOpaque === rIsString)
|
|
960
|
+
{
|
|
961
|
+
opcode: OpCode5.StrictEq,
|
|
962
|
+
operands: [
|
|
963
|
+
{ kind: OperandKind6.Register, value: tempP },
|
|
964
|
+
{ kind: OperandKind6.Register, value: tempF }
|
|
965
|
+
],
|
|
966
|
+
result: tempQ
|
|
967
|
+
}
|
|
968
|
+
// tempQ is envPredicate (always true)
|
|
969
|
+
];
|
|
970
|
+
} else if (templateId === 1) {
|
|
971
|
+
opaqueInsts = [
|
|
972
|
+
{ opcode: OpCode5.GetEntropy, operands: [], result: tempA },
|
|
973
|
+
// tempA = x
|
|
974
|
+
{
|
|
975
|
+
opcode: OpCode5.Mul,
|
|
976
|
+
operands: [
|
|
977
|
+
{ kind: OperandKind6.Register, value: tempA },
|
|
978
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
979
|
+
],
|
|
980
|
+
result: tempB
|
|
981
|
+
},
|
|
982
|
+
// tempB = x^2
|
|
983
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: threeIdx }], result: tempC },
|
|
984
|
+
// tempC = 3
|
|
985
|
+
{
|
|
986
|
+
opcode: OpCode5.BitAnd,
|
|
987
|
+
operands: [
|
|
988
|
+
{ kind: OperandKind6.Register, value: tempB },
|
|
989
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
990
|
+
],
|
|
991
|
+
result: tempD
|
|
992
|
+
},
|
|
993
|
+
// tempD = x^2 & 3
|
|
994
|
+
{
|
|
995
|
+
opcode: OpCode5.StrictEq,
|
|
996
|
+
operands: [
|
|
997
|
+
{ kind: OperandKind6.Register, value: tempD },
|
|
998
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
999
|
+
],
|
|
1000
|
+
result: tempP
|
|
1001
|
+
},
|
|
1002
|
+
// tempP = (x^2 & 3 === 3), always false
|
|
1003
|
+
{ opcode: OpCode5.Not, operands: [{ kind: OperandKind6.Register, value: tempP }], result: tempQ }
|
|
1004
|
+
// tempQ = true
|
|
1005
|
+
];
|
|
1006
|
+
} else if (templateId === 2) {
|
|
1007
|
+
let thirtyOneIdx2 = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 31);
|
|
1008
|
+
if (thirtyOneIdx2 === -1) {
|
|
1009
|
+
thirtyOneIdx2 = newCP.length;
|
|
1010
|
+
newCP.push({ index: thirtyOneIdx2, kind: ConstantKind4.Number, value: 31 });
|
|
1011
|
+
}
|
|
1012
|
+
let twoIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 2);
|
|
1013
|
+
if (twoIdx === -1) {
|
|
1014
|
+
twoIdx = newCP.length;
|
|
1015
|
+
newCP.push({ index: twoIdx, kind: ConstantKind4.Number, value: 2 });
|
|
1016
|
+
}
|
|
1017
|
+
opaqueInsts = [
|
|
1018
|
+
{ opcode: OpCode5.GetEntropy, operands: [], result: tempA },
|
|
1019
|
+
// tempA = x
|
|
1020
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: thirtyOneIdx2 }], result: tempC },
|
|
1021
|
+
// tempC = 31
|
|
1022
|
+
{
|
|
1023
|
+
opcode: OpCode5.Mul,
|
|
1024
|
+
operands: [
|
|
1025
|
+
{ kind: OperandKind6.Register, value: tempC },
|
|
1026
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
1027
|
+
],
|
|
1028
|
+
result: tempB
|
|
1029
|
+
},
|
|
1030
|
+
// tempB = 31*x
|
|
1031
|
+
{
|
|
1032
|
+
opcode: OpCode5.Mul,
|
|
1033
|
+
operands: [
|
|
1034
|
+
{ kind: OperandKind6.Register, value: tempB },
|
|
1035
|
+
{ kind: OperandKind6.Register, value: tempB }
|
|
1036
|
+
],
|
|
1037
|
+
result: tempD
|
|
1038
|
+
},
|
|
1039
|
+
// tempD = (31*x)^2
|
|
1040
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: threeIdx }], result: tempC },
|
|
1041
|
+
// tempC = 3
|
|
1042
|
+
{
|
|
1043
|
+
opcode: OpCode5.Mod,
|
|
1044
|
+
operands: [
|
|
1045
|
+
{ kind: OperandKind6.Register, value: tempD },
|
|
1046
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
1047
|
+
],
|
|
1048
|
+
result: tempE
|
|
1049
|
+
},
|
|
1050
|
+
// tempE = (31*x)^2 % 3
|
|
1051
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: twoIdx }], result: tempC },
|
|
1052
|
+
// tempC = 2
|
|
1053
|
+
{
|
|
1054
|
+
opcode: OpCode5.StrictEq,
|
|
1055
|
+
operands: [
|
|
1056
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
1057
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
1058
|
+
],
|
|
1059
|
+
result: tempP
|
|
1060
|
+
},
|
|
1061
|
+
// tempP = false always
|
|
1062
|
+
{ opcode: OpCode5.Not, operands: [{ kind: OperandKind6.Register, value: tempP }], result: tempQ }
|
|
1063
|
+
// tempQ = true
|
|
1064
|
+
];
|
|
1065
|
+
} else if (templateId === 3) {
|
|
1066
|
+
let twoIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 2);
|
|
1067
|
+
if (twoIdx === -1) {
|
|
1068
|
+
twoIdx = newCP.length;
|
|
1069
|
+
newCP.push({ index: twoIdx, kind: ConstantKind4.Number, value: 2 });
|
|
1070
|
+
}
|
|
1071
|
+
opaqueInsts = [
|
|
1072
|
+
{ opcode: OpCode5.GetEntropy, operands: [], result: tempA },
|
|
1073
|
+
// tempA = x
|
|
1074
|
+
{
|
|
1075
|
+
opcode: OpCode5.Mul,
|
|
1076
|
+
operands: [
|
|
1077
|
+
{ kind: OperandKind6.Register, value: tempA },
|
|
1078
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
1079
|
+
],
|
|
1080
|
+
result: tempB
|
|
1081
|
+
},
|
|
1082
|
+
// tempB = x^2
|
|
1083
|
+
{
|
|
1084
|
+
opcode: OpCode5.Add,
|
|
1085
|
+
operands: [
|
|
1086
|
+
{ kind: OperandKind6.Register, value: tempB },
|
|
1087
|
+
{ kind: OperandKind6.Register, value: tempA }
|
|
1088
|
+
],
|
|
1089
|
+
result: tempC
|
|
1090
|
+
},
|
|
1091
|
+
// tempC = x^2 + x
|
|
1092
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: twoIdx }], result: tempD },
|
|
1093
|
+
// tempD = 2
|
|
1094
|
+
{
|
|
1095
|
+
opcode: OpCode5.Mod,
|
|
1096
|
+
operands: [
|
|
1097
|
+
{ kind: OperandKind6.Register, value: tempC },
|
|
1098
|
+
{ kind: OperandKind6.Register, value: tempD }
|
|
1099
|
+
],
|
|
1100
|
+
result: tempE
|
|
1101
|
+
},
|
|
1102
|
+
// tempE = (x^2+x) % 2
|
|
1103
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: zeroIdx }], result: tempD },
|
|
1104
|
+
// tempD = 0
|
|
1105
|
+
{
|
|
1106
|
+
opcode: OpCode5.StrictEq,
|
|
1107
|
+
operands: [
|
|
1108
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
1109
|
+
{ kind: OperandKind6.Register, value: tempD }
|
|
1110
|
+
],
|
|
1111
|
+
result: tempP
|
|
1112
|
+
},
|
|
1113
|
+
// always true
|
|
1114
|
+
{ opcode: OpCode5.Move, operands: [{ kind: OperandKind6.Register, value: tempP }], result: tempQ }
|
|
1115
|
+
];
|
|
1116
|
+
} else {
|
|
1117
|
+
let twoIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 2);
|
|
1118
|
+
if (twoIdx === -1) {
|
|
1119
|
+
twoIdx = newCP.length;
|
|
1120
|
+
newCP.push({ index: twoIdx, kind: ConstantKind4.Number, value: 2 });
|
|
1121
|
+
}
|
|
1122
|
+
let oneIdx = newCP.findIndex((cp) => cp.kind === ConstantKind4.Number && cp.value === 1);
|
|
1123
|
+
if (oneIdx === -1) {
|
|
1124
|
+
oneIdx = newCP.length;
|
|
1125
|
+
newCP.push({ index: oneIdx, kind: ConstantKind4.Number, value: 1 });
|
|
1126
|
+
}
|
|
1127
|
+
opaqueInsts = [
|
|
1128
|
+
{ opcode: OpCode5.GetEntropy, operands: [], result: tempA },
|
|
1129
|
+
// tempA = x
|
|
1130
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: oneIdx }], result: tempC },
|
|
1131
|
+
// tempC = 1
|
|
1132
|
+
{
|
|
1133
|
+
opcode: OpCode5.Add,
|
|
1134
|
+
operands: [
|
|
1135
|
+
{ kind: OperandKind6.Register, value: tempA },
|
|
1136
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
1137
|
+
],
|
|
1138
|
+
result: tempB
|
|
1139
|
+
},
|
|
1140
|
+
// tempB = x+1
|
|
1141
|
+
{
|
|
1142
|
+
opcode: OpCode5.Mul,
|
|
1143
|
+
operands: [
|
|
1144
|
+
{ kind: OperandKind6.Register, value: tempA },
|
|
1145
|
+
{ kind: OperandKind6.Register, value: tempB }
|
|
1146
|
+
],
|
|
1147
|
+
result: tempD
|
|
1148
|
+
},
|
|
1149
|
+
// tempD = x*(x+1)
|
|
1150
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: twoIdx }], result: tempC },
|
|
1151
|
+
// tempC = 2
|
|
1152
|
+
{
|
|
1153
|
+
opcode: OpCode5.Mod,
|
|
1154
|
+
operands: [
|
|
1155
|
+
{ kind: OperandKind6.Register, value: tempD },
|
|
1156
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
1157
|
+
],
|
|
1158
|
+
result: tempE
|
|
1159
|
+
},
|
|
1160
|
+
// tempE = x*(x+1) % 2
|
|
1161
|
+
{ opcode: OpCode5.LoadConst, operands: [{ kind: OperandKind6.ConstantIndex, value: zeroIdx }], result: tempC },
|
|
1162
|
+
// tempC = 0
|
|
1163
|
+
{
|
|
1164
|
+
opcode: OpCode5.StrictEq,
|
|
1165
|
+
operands: [
|
|
1166
|
+
{ kind: OperandKind6.Register, value: tempE },
|
|
1167
|
+
{ kind: OperandKind6.Register, value: tempC }
|
|
1168
|
+
],
|
|
1169
|
+
result: tempP
|
|
1170
|
+
},
|
|
1171
|
+
// always true
|
|
1172
|
+
{ opcode: OpCode5.Move, operands: [{ kind: OperandKind6.Register, value: tempP }], result: tempQ }
|
|
1173
|
+
];
|
|
1174
|
+
}
|
|
1175
|
+
const fakeBlockId = `__fake_path_${ctx.rng.identifier(6)}`;
|
|
1176
|
+
const decoyInsts = collectDecoyInstructions(currentFunc, [junkTemp1, junkTemp2, junkTemp3], ctx.rng);
|
|
1177
|
+
const ending = buildFakeBlockEnding(ctx.rng, junkTemp1, junkTemp2, regX, fortyTwoIdx, undefinedIdx);
|
|
1178
|
+
const fakeBlock = {
|
|
1179
|
+
id: fakeBlockId,
|
|
1180
|
+
label: "fake_path",
|
|
1181
|
+
instructions: [...decoyInsts, ...ending.instructions],
|
|
1182
|
+
terminator: {
|
|
1183
|
+
kind: ending.terminatorKind,
|
|
1184
|
+
targets: []
|
|
1185
|
+
},
|
|
1186
|
+
predecessors: [blockToModify.id],
|
|
1187
|
+
successors: [],
|
|
1188
|
+
phiNodes: []
|
|
1189
|
+
};
|
|
1190
|
+
const originalTarget = blockToModify.terminator.targets[0];
|
|
1191
|
+
const newBlocks = currentFunc.blocks.flatMap((block) => {
|
|
1192
|
+
if (block.id === blockToModify.id) {
|
|
1193
|
+
const updatedBlock = {
|
|
1194
|
+
...block,
|
|
1195
|
+
instructions: [...block.instructions, ...opaqueInsts],
|
|
1196
|
+
terminator: {
|
|
1197
|
+
kind: "branch",
|
|
1198
|
+
targets: [originalTarget, fakeBlock.id],
|
|
1199
|
+
condition: tempQ
|
|
1200
|
+
},
|
|
1201
|
+
successors: [originalTarget, fakeBlock.id]
|
|
1202
|
+
};
|
|
1203
|
+
return [updatedBlock, fakeBlock];
|
|
1204
|
+
}
|
|
1205
|
+
return [block];
|
|
1206
|
+
});
|
|
1207
|
+
const addedLocals = [
|
|
1208
|
+
{ name: `fake_path_entropy_${tempA}`, register: tempA, type: IRType5.Number, isCaptured: false },
|
|
1209
|
+
{ name: `fake_path_temp_${tempB}`, register: tempB, type: IRType5.Number, isCaptured: false },
|
|
1210
|
+
{ name: `fake_path_temp_${tempC}`, register: tempC, type: IRType5.Number, isCaptured: false },
|
|
1211
|
+
{ name: `fake_path_temp_${tempD}`, register: tempD, type: IRType5.Number, isCaptured: false },
|
|
1212
|
+
{ name: `fake_path_temp_${tempE}`, register: tempE, type: IRType5.Number, isCaptured: false },
|
|
1213
|
+
{ name: `fake_path_temp_${tempF}`, register: tempF, type: IRType5.Number, isCaptured: false },
|
|
1214
|
+
{ name: `fake_path_temp_${tempG}`, register: tempG, type: IRType5.Number, isCaptured: false },
|
|
1215
|
+
{ name: `fake_path_pred_${tempP}`, register: tempP, type: IRType5.Boolean, isCaptured: false },
|
|
1216
|
+
{ name: `fake_path_cond_${tempQ}`, register: tempQ, type: IRType5.Boolean, isCaptured: false },
|
|
1217
|
+
{ name: `fake_path_junk_${junkTemp1}`, register: junkTemp1, type: IRType5.Number, isCaptured: false },
|
|
1218
|
+
{ name: `fake_path_junk_${junkTemp2}`, register: junkTemp2, type: IRType5.Number, isCaptured: false },
|
|
1219
|
+
{ name: `fake_path_junk_${junkTemp3}`, register: junkTemp3, type: IRType5.Any, isCaptured: false }
|
|
1220
|
+
];
|
|
1221
|
+
currentFunc = {
|
|
1222
|
+
...currentFunc,
|
|
1223
|
+
locals: [...currentFunc.locals, ...addedLocals],
|
|
1224
|
+
blocks: newBlocks
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
return currentFunc;
|
|
1228
|
+
});
|
|
1229
|
+
return {
|
|
1230
|
+
module: { ...ctx.module, functions: newFunctions, constantPool: newCP },
|
|
1231
|
+
symbolsRenamed: 0,
|
|
1232
|
+
nodesTransformed,
|
|
1233
|
+
diagnostics: []
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
// src/passes/symbol-indirection.ts
|
|
1239
|
+
var SymbolIndirectionPass = class {
|
|
1240
|
+
name = "SymbolIndirectionPass";
|
|
1241
|
+
priority = 50;
|
|
1242
|
+
execute(ctx) {
|
|
1243
|
+
let symbolsRenamed = 0;
|
|
1244
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
1245
|
+
if (func.isExported) {
|
|
1246
|
+
return func;
|
|
1247
|
+
}
|
|
1248
|
+
if (ctx.profile.reactSafe && func.name.startsWith("use")) {
|
|
1249
|
+
return func;
|
|
1250
|
+
}
|
|
1251
|
+
const newName = `fn_${ctx.rng.identifier(8)}`;
|
|
1252
|
+
symbolsRenamed++;
|
|
1253
|
+
const newLocals = func.locals.map((local) => {
|
|
1254
|
+
symbolsRenamed++;
|
|
1255
|
+
return {
|
|
1256
|
+
...local,
|
|
1257
|
+
name: `v_${ctx.rng.identifier(6)}`
|
|
1258
|
+
};
|
|
1259
|
+
});
|
|
1260
|
+
return {
|
|
1261
|
+
...func,
|
|
1262
|
+
name: newName,
|
|
1263
|
+
locals: newLocals
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
const newGlobals = ctx.module.globals.map((g) => {
|
|
1267
|
+
if (g.isExported && ctx.profile.preserveExports) return g;
|
|
1268
|
+
symbolsRenamed++;
|
|
1269
|
+
return {
|
|
1270
|
+
...g,
|
|
1271
|
+
name: `g_${ctx.rng.identifier(8)}`
|
|
1272
|
+
};
|
|
1273
|
+
});
|
|
1274
|
+
const newModule = {
|
|
1275
|
+
...ctx.module,
|
|
1276
|
+
functions: newFunctions,
|
|
1277
|
+
globals: newGlobals
|
|
1278
|
+
};
|
|
1279
|
+
return {
|
|
1280
|
+
module: newModule,
|
|
1281
|
+
symbolsRenamed,
|
|
1282
|
+
nodesTransformed: 0,
|
|
1283
|
+
diagnostics: []
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
// src/passes/string-pool-encoding.ts
|
|
1289
|
+
import { OpCode as OpCode6, ConstantKind as ConstantKind5, OperandKind as OperandKind7, IRType as IRType6 } from "@tsvm/shared";
|
|
1290
|
+
var StringPoolEncodingPass = class {
|
|
1291
|
+
name = "StringPoolEncodingPass";
|
|
1292
|
+
priority = 70;
|
|
1293
|
+
execute(ctx) {
|
|
1294
|
+
const config = ctx.profile.transforms.find((t) => t.name === this.name);
|
|
1295
|
+
const fragmentStrings = config?.options?.["fragmentStrings"] ?? true;
|
|
1296
|
+
const minLength = config?.options?.["minLength"] ?? 3;
|
|
1297
|
+
if (!fragmentStrings) {
|
|
1298
|
+
return {
|
|
1299
|
+
module: ctx.module,
|
|
1300
|
+
symbolsRenamed: 0,
|
|
1301
|
+
nodesTransformed: 0,
|
|
1302
|
+
diagnostics: []
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
let nextConstantIndex = ctx.module.constantPool.length;
|
|
1306
|
+
const newConstantPool = [...ctx.module.constantPool];
|
|
1307
|
+
const getOrAddStringConstant = (val) => {
|
|
1308
|
+
const existing = newConstantPool.find((c) => c.kind === ConstantKind5.String && c.value === val);
|
|
1309
|
+
if (existing) {
|
|
1310
|
+
return existing.index;
|
|
1311
|
+
}
|
|
1312
|
+
const newIdx = nextConstantIndex++;
|
|
1313
|
+
newConstantPool.push({
|
|
1314
|
+
index: newIdx,
|
|
1315
|
+
kind: ConstantKind5.String,
|
|
1316
|
+
value: val
|
|
1317
|
+
});
|
|
1318
|
+
return newIdx;
|
|
1319
|
+
};
|
|
1320
|
+
const getOrAddNumberConstant = (val) => {
|
|
1321
|
+
const existing = newConstantPool.find((c) => c.kind === ConstantKind5.Number && c.value === val);
|
|
1322
|
+
if (existing) {
|
|
1323
|
+
return existing.index;
|
|
1324
|
+
}
|
|
1325
|
+
const newIdx = nextConstantIndex++;
|
|
1326
|
+
newConstantPool.push({
|
|
1327
|
+
index: newIdx,
|
|
1328
|
+
kind: ConstantKind5.Number,
|
|
1329
|
+
value: val
|
|
1330
|
+
});
|
|
1331
|
+
return newIdx;
|
|
1332
|
+
};
|
|
1333
|
+
let nodesTransformed = 0;
|
|
1334
|
+
const newFunctions = ctx.module.functions.map((fn) => {
|
|
1335
|
+
let nextTempRegId = getMaxRegister(fn);
|
|
1336
|
+
const addedLocals = [];
|
|
1337
|
+
const newBlocks = fn.blocks.map((block) => {
|
|
1338
|
+
const newInstructions = [];
|
|
1339
|
+
block.instructions.forEach((inst) => {
|
|
1340
|
+
if (inst.opcode === OpCode6.LoadConst && inst.operands.length > 0 && inst.result) {
|
|
1341
|
+
const op = inst.operands[0];
|
|
1342
|
+
if (op.kind === OperandKind7.ConstantIndex) {
|
|
1343
|
+
const constIndex = op.value;
|
|
1344
|
+
const constant = ctx.module.constantPool[constIndex];
|
|
1345
|
+
if (constant && constant.kind === ConstantKind5.String && typeof constant.value === "string" && constant.value.length >= minLength) {
|
|
1346
|
+
const fullStr = constant.value;
|
|
1347
|
+
const baseKey = ctx.rng.nextRange(1, 255);
|
|
1348
|
+
const r_arr = `r${nextTempRegId++}`;
|
|
1349
|
+
const r_val = `r${nextTempRegId++}`;
|
|
1350
|
+
const r_key = `r${nextTempRegId++}`;
|
|
1351
|
+
const r_dec = `r${nextTempRegId++}`;
|
|
1352
|
+
const r_idx = `r${nextTempRegId++}`;
|
|
1353
|
+
const r_string_str = `r${nextTempRegId++}`;
|
|
1354
|
+
const r_string = `r${nextTempRegId++}`;
|
|
1355
|
+
const r_from_char_code_str = `r${nextTempRegId++}`;
|
|
1356
|
+
const r_from_char_code = `r${nextTempRegId++}`;
|
|
1357
|
+
addedLocals.push(
|
|
1358
|
+
{ name: `str_pool_temp_${r_arr}`, register: r_arr, type: IRType6.Any, isCaptured: false },
|
|
1359
|
+
{ name: `str_pool_temp_${r_val}`, register: r_val, type: IRType6.Number, isCaptured: false },
|
|
1360
|
+
{ name: `str_pool_temp_${r_key}`, register: r_key, type: IRType6.Number, isCaptured: false },
|
|
1361
|
+
{ name: `str_pool_temp_${r_dec}`, register: r_dec, type: IRType6.Number, isCaptured: false },
|
|
1362
|
+
{ name: `str_pool_temp_${r_idx}`, register: r_idx, type: IRType6.Number, isCaptured: false },
|
|
1363
|
+
{ name: `str_pool_temp_${r_string_str}`, register: r_string_str, type: IRType6.String, isCaptured: false },
|
|
1364
|
+
{ name: `str_pool_temp_${r_string}`, register: r_string, type: IRType6.Any, isCaptured: false },
|
|
1365
|
+
{ name: `str_pool_temp_${r_from_char_code_str}`, register: r_from_char_code_str, type: IRType6.String, isCaptured: false },
|
|
1366
|
+
{ name: `str_pool_temp_${r_from_char_code}`, register: r_from_char_code, type: IRType6.Any, isCaptured: false }
|
|
1367
|
+
);
|
|
1368
|
+
const stringNameIdx = getOrAddStringConstant("String");
|
|
1369
|
+
const fromCharCodeIdx = getOrAddStringConstant("fromCharCode");
|
|
1370
|
+
const stringPoolInsts = [
|
|
1371
|
+
{
|
|
1372
|
+
opcode: OpCode6.ArrayNew,
|
|
1373
|
+
operands: [],
|
|
1374
|
+
result: r_arr,
|
|
1375
|
+
sourceLocation: inst.sourceLocation
|
|
1376
|
+
}
|
|
1377
|
+
];
|
|
1378
|
+
let prevEncoded = baseKey;
|
|
1379
|
+
for (let i = 0; i < fullStr.length; i++) {
|
|
1380
|
+
const charKey = (baseKey * (i + 1) ^ prevEncoded ^ i * 37) & 255;
|
|
1381
|
+
const effectiveKey = charKey === 0 ? 1 : charKey;
|
|
1382
|
+
const encoded = fullStr.charCodeAt(i) ^ effectiveKey;
|
|
1383
|
+
prevEncoded = encoded & 255;
|
|
1384
|
+
const valIdx = getOrAddNumberConstant(encoded);
|
|
1385
|
+
const keyIdx = getOrAddNumberConstant(effectiveKey);
|
|
1386
|
+
const idxIdx = getOrAddNumberConstant(i);
|
|
1387
|
+
stringPoolInsts.push(
|
|
1388
|
+
{
|
|
1389
|
+
opcode: OpCode6.LoadConst,
|
|
1390
|
+
operands: [{ kind: OperandKind7.ConstantIndex, value: valIdx }],
|
|
1391
|
+
result: r_val,
|
|
1392
|
+
sourceLocation: inst.sourceLocation
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
opcode: OpCode6.LoadConst,
|
|
1396
|
+
operands: [{ kind: OperandKind7.ConstantIndex, value: keyIdx }],
|
|
1397
|
+
result: r_key,
|
|
1398
|
+
sourceLocation: inst.sourceLocation
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
opcode: OpCode6.BitXor,
|
|
1402
|
+
operands: [
|
|
1403
|
+
{ kind: OperandKind7.Register, value: r_val },
|
|
1404
|
+
{ kind: OperandKind7.Register, value: r_key }
|
|
1405
|
+
],
|
|
1406
|
+
result: r_dec,
|
|
1407
|
+
sourceLocation: inst.sourceLocation
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
opcode: OpCode6.LoadConst,
|
|
1411
|
+
operands: [{ kind: OperandKind7.ConstantIndex, value: idxIdx }],
|
|
1412
|
+
result: r_idx,
|
|
1413
|
+
sourceLocation: inst.sourceLocation
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
opcode: OpCode6.ComputedSet,
|
|
1417
|
+
operands: [
|
|
1418
|
+
{ kind: OperandKind7.Register, value: r_arr },
|
|
1419
|
+
{ kind: OperandKind7.Register, value: r_idx },
|
|
1420
|
+
{ kind: OperandKind7.Register, value: r_dec }
|
|
1421
|
+
],
|
|
1422
|
+
sourceLocation: inst.sourceLocation
|
|
1423
|
+
}
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
stringPoolInsts.push(
|
|
1427
|
+
{
|
|
1428
|
+
opcode: OpCode6.LoadConst,
|
|
1429
|
+
operands: [{ kind: OperandKind7.ConstantIndex, value: stringNameIdx }],
|
|
1430
|
+
result: r_string_str,
|
|
1431
|
+
sourceLocation: inst.sourceLocation
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
opcode: OpCode6.LoadGlobal,
|
|
1435
|
+
operands: [{ kind: OperandKind7.Register, value: r_string_str }],
|
|
1436
|
+
result: r_string,
|
|
1437
|
+
sourceLocation: inst.sourceLocation
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
opcode: OpCode6.LoadConst,
|
|
1441
|
+
operands: [{ kind: OperandKind7.ConstantIndex, value: fromCharCodeIdx }],
|
|
1442
|
+
result: r_from_char_code_str,
|
|
1443
|
+
sourceLocation: inst.sourceLocation
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
opcode: OpCode6.PropGet,
|
|
1447
|
+
operands: [
|
|
1448
|
+
{ kind: OperandKind7.Register, value: r_string },
|
|
1449
|
+
{ kind: OperandKind7.Register, value: r_from_char_code_str }
|
|
1450
|
+
],
|
|
1451
|
+
result: r_from_char_code,
|
|
1452
|
+
sourceLocation: inst.sourceLocation
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
opcode: OpCode6.CallWithArray,
|
|
1456
|
+
operands: [
|
|
1457
|
+
{ kind: OperandKind7.Register, value: r_from_char_code },
|
|
1458
|
+
{ kind: OperandKind7.Register, value: r_arr }
|
|
1459
|
+
],
|
|
1460
|
+
result: inst.result,
|
|
1461
|
+
sourceLocation: inst.sourceLocation
|
|
1462
|
+
}
|
|
1463
|
+
);
|
|
1464
|
+
newInstructions.push(...stringPoolInsts);
|
|
1465
|
+
nodesTransformed++;
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
newInstructions.push(inst);
|
|
1471
|
+
});
|
|
1472
|
+
return {
|
|
1473
|
+
...block,
|
|
1474
|
+
instructions: newInstructions
|
|
1475
|
+
};
|
|
1476
|
+
});
|
|
1477
|
+
return {
|
|
1478
|
+
...fn,
|
|
1479
|
+
locals: [...fn.locals, ...addedLocals],
|
|
1480
|
+
blocks: newBlocks
|
|
1481
|
+
};
|
|
1482
|
+
});
|
|
1483
|
+
const newModule = {
|
|
1484
|
+
...ctx.module,
|
|
1485
|
+
constantPool: newConstantPool,
|
|
1486
|
+
functions: newFunctions
|
|
1487
|
+
};
|
|
1488
|
+
return {
|
|
1489
|
+
module: newModule,
|
|
1490
|
+
symbolsRenamed: 0,
|
|
1491
|
+
nodesTransformed,
|
|
1492
|
+
diagnostics: []
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
// src/passes/function-virtualization.ts
|
|
1498
|
+
import { FunctionAttribute as FunctionAttribute2 } from "@tsvm/shared";
|
|
1499
|
+
var FunctionVirtualizationPass = class {
|
|
1500
|
+
name = "FunctionVirtualizationPass";
|
|
1501
|
+
priority = 80;
|
|
1502
|
+
execute(ctx) {
|
|
1503
|
+
const newModule = {
|
|
1504
|
+
...ctx.module,
|
|
1505
|
+
functions: ctx.module.functions.map((fn) => {
|
|
1506
|
+
if (fn.attributes.includes(FunctionAttribute2.ReactComponent)) {
|
|
1507
|
+
return { ...fn, isVirtualized: false };
|
|
1508
|
+
}
|
|
1509
|
+
return fn;
|
|
1510
|
+
})
|
|
1511
|
+
};
|
|
1512
|
+
return {
|
|
1513
|
+
module: newModule,
|
|
1514
|
+
symbolsRenamed: 0,
|
|
1515
|
+
nodesTransformed: 0,
|
|
1516
|
+
diagnostics: []
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
// src/passes/dead-code-injection.ts
|
|
1522
|
+
import { OpCode as OpCode7, OperandKind as OperandKind8, ConstantKind as ConstantKind6 } from "@tsvm/shared";
|
|
1523
|
+
var DeadCodeInjectionPass = class {
|
|
1524
|
+
name = "DeadCodeInjectionPass";
|
|
1525
|
+
priority = 25;
|
|
1526
|
+
execute(ctx) {
|
|
1527
|
+
let nodesTransformed = 0;
|
|
1528
|
+
const newCP = [...ctx.module.constantPool];
|
|
1529
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
1530
|
+
if (!func.isVirtualized) return func;
|
|
1531
|
+
let changed = false;
|
|
1532
|
+
const newBlocks = [];
|
|
1533
|
+
const maxReg = getMaxRegister(func) - 1;
|
|
1534
|
+
for (const block of func.blocks) {
|
|
1535
|
+
let currentBlockId = block.id;
|
|
1536
|
+
let currentLabel = block.label;
|
|
1537
|
+
let currentInstructions = [];
|
|
1538
|
+
let currentPredecessors = [...block.predecessors];
|
|
1539
|
+
let isFirstSubBlock = true;
|
|
1540
|
+
for (const inst of block.instructions) {
|
|
1541
|
+
if (ctx.rng.nextFloat() < 0.2) {
|
|
1542
|
+
changed = true;
|
|
1543
|
+
nodesTransformed++;
|
|
1544
|
+
const rStart = maxReg + 5;
|
|
1545
|
+
const junkReg1 = `r${ctx.rng.nextRange(rStart, rStart + 3)}`;
|
|
1546
|
+
const junkReg2 = `r${ctx.rng.nextRange(rStart + 4, rStart + 7)}`;
|
|
1547
|
+
const junkReg3 = `r${ctx.rng.nextRange(rStart + 8, rStart + 11)}`;
|
|
1548
|
+
if (newCP.length === 0) {
|
|
1549
|
+
newCP.push({ index: 0, kind: ConstantKind6.Number, value: 0 });
|
|
1550
|
+
}
|
|
1551
|
+
const cpMax = newCP.length - 1;
|
|
1552
|
+
const randCpIdx = ctx.rng.nextRange(0, cpMax);
|
|
1553
|
+
const patternChoice = ctx.rng.nextRange(0, 3);
|
|
1554
|
+
const deadCodeInsts = [];
|
|
1555
|
+
if (patternChoice === 0) {
|
|
1556
|
+
deadCodeInsts.push({
|
|
1557
|
+
opcode: OpCode7.LoadConst,
|
|
1558
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: randCpIdx }],
|
|
1559
|
+
result: junkReg1
|
|
1560
|
+
});
|
|
1561
|
+
deadCodeInsts.push({
|
|
1562
|
+
opcode: OpCode7.Move,
|
|
1563
|
+
operands: [
|
|
1564
|
+
{ kind: OperandKind8.Register, value: junkReg1 },
|
|
1565
|
+
{ kind: OperandKind8.Register, value: junkReg2 }
|
|
1566
|
+
]
|
|
1567
|
+
});
|
|
1568
|
+
} else if (patternChoice === 1) {
|
|
1569
|
+
deadCodeInsts.push({
|
|
1570
|
+
opcode: OpCode7.LoadConst,
|
|
1571
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: randCpIdx }],
|
|
1572
|
+
result: junkReg1
|
|
1573
|
+
});
|
|
1574
|
+
deadCodeInsts.push({
|
|
1575
|
+
opcode: OpCode7.LoadConst,
|
|
1576
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: ctx.rng.nextRange(0, cpMax) }],
|
|
1577
|
+
result: junkReg2
|
|
1578
|
+
});
|
|
1579
|
+
const mathOps = [OpCode7.Add, OpCode7.Sub, OpCode7.Mul, OpCode7.BitXor, OpCode7.BitAnd];
|
|
1580
|
+
const randomOp = mathOps[ctx.rng.nextRange(0, mathOps.length - 1)];
|
|
1581
|
+
deadCodeInsts.push({
|
|
1582
|
+
opcode: randomOp,
|
|
1583
|
+
operands: [
|
|
1584
|
+
{ kind: OperandKind8.Register, value: junkReg1 },
|
|
1585
|
+
{ kind: OperandKind8.Register, value: junkReg2 }
|
|
1586
|
+
],
|
|
1587
|
+
result: junkReg3
|
|
1588
|
+
});
|
|
1589
|
+
} else if (patternChoice === 2) {
|
|
1590
|
+
deadCodeInsts.push({
|
|
1591
|
+
opcode: OpCode7.LoadConst,
|
|
1592
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: randCpIdx }],
|
|
1593
|
+
result: junkReg1
|
|
1594
|
+
});
|
|
1595
|
+
deadCodeInsts.push({
|
|
1596
|
+
opcode: OpCode7.Move,
|
|
1597
|
+
operands: [
|
|
1598
|
+
{ kind: OperandKind8.Register, value: junkReg1 },
|
|
1599
|
+
{ kind: OperandKind8.Register, value: junkReg2 }
|
|
1600
|
+
]
|
|
1601
|
+
});
|
|
1602
|
+
deadCodeInsts.push({
|
|
1603
|
+
opcode: OpCode7.TypeOf,
|
|
1604
|
+
operands: [{ kind: OperandKind8.Register, value: junkReg2 }],
|
|
1605
|
+
result: junkReg3
|
|
1606
|
+
});
|
|
1607
|
+
deadCodeInsts.push({
|
|
1608
|
+
opcode: OpCode7.Move,
|
|
1609
|
+
operands: [
|
|
1610
|
+
{ kind: OperandKind8.Register, value: junkReg3 },
|
|
1611
|
+
{ kind: OperandKind8.Register, value: junkReg1 }
|
|
1612
|
+
]
|
|
1613
|
+
});
|
|
1614
|
+
} else {
|
|
1615
|
+
deadCodeInsts.push({
|
|
1616
|
+
opcode: OpCode7.LoadConst,
|
|
1617
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: randCpIdx }],
|
|
1618
|
+
result: junkReg1
|
|
1619
|
+
});
|
|
1620
|
+
deadCodeInsts.push({
|
|
1621
|
+
opcode: OpCode7.LoadConst,
|
|
1622
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: ctx.rng.nextRange(0, cpMax) }],
|
|
1623
|
+
result: junkReg2
|
|
1624
|
+
});
|
|
1625
|
+
const compOps = [OpCode7.Lt, OpCode7.Gt, OpCode7.Eq, OpCode7.StrictEq, OpCode7.LtEq, OpCode7.GtEq];
|
|
1626
|
+
const compOp = compOps[ctx.rng.nextRange(0, compOps.length - 1)];
|
|
1627
|
+
deadCodeInsts.push({
|
|
1628
|
+
opcode: compOp,
|
|
1629
|
+
operands: [
|
|
1630
|
+
{ kind: OperandKind8.Register, value: junkReg1 },
|
|
1631
|
+
{ kind: OperandKind8.Register, value: junkReg2 }
|
|
1632
|
+
],
|
|
1633
|
+
result: junkReg3
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
if (ctx.rng.nextFloat() < 0.1) {
|
|
1637
|
+
let zeroIdx = newCP.findIndex((cp) => cp.kind === ConstantKind6.Number && cp.value === 0);
|
|
1638
|
+
if (zeroIdx === -1) {
|
|
1639
|
+
zeroIdx = newCP.length;
|
|
1640
|
+
newCP.push({ index: zeroIdx, kind: ConstantKind6.Number, value: 0 });
|
|
1641
|
+
}
|
|
1642
|
+
const randIdx = ctx.rng.nextRange(0, cpMax);
|
|
1643
|
+
const condReg = `r${maxReg + 12}`;
|
|
1644
|
+
currentInstructions.push({
|
|
1645
|
+
opcode: OpCode7.LoadConst,
|
|
1646
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: randIdx }],
|
|
1647
|
+
result: junkReg1
|
|
1648
|
+
});
|
|
1649
|
+
currentInstructions.push({
|
|
1650
|
+
opcode: OpCode7.Mul,
|
|
1651
|
+
operands: [
|
|
1652
|
+
{ kind: OperandKind8.Register, value: junkReg1 },
|
|
1653
|
+
{ kind: OperandKind8.Register, value: junkReg1 }
|
|
1654
|
+
],
|
|
1655
|
+
result: junkReg2
|
|
1656
|
+
});
|
|
1657
|
+
currentInstructions.push({
|
|
1658
|
+
opcode: OpCode7.LoadConst,
|
|
1659
|
+
operands: [{ kind: OperandKind8.ConstantIndex, value: zeroIdx }],
|
|
1660
|
+
result: junkReg3
|
|
1661
|
+
});
|
|
1662
|
+
currentInstructions.push({
|
|
1663
|
+
opcode: OpCode7.Lt,
|
|
1664
|
+
operands: [
|
|
1665
|
+
{ kind: OperandKind8.Register, value: junkReg2 },
|
|
1666
|
+
{ kind: OperandKind8.Register, value: junkReg3 }
|
|
1667
|
+
],
|
|
1668
|
+
result: condReg
|
|
1669
|
+
});
|
|
1670
|
+
const deadBlockId = `__dci_opaque_dead_${ctx.rng.identifier(6)}`;
|
|
1671
|
+
const nextBlockId = `__dci_opaque_next_${ctx.rng.identifier(6)}`;
|
|
1672
|
+
newBlocks.push({
|
|
1673
|
+
id: currentBlockId,
|
|
1674
|
+
label: currentLabel,
|
|
1675
|
+
instructions: currentInstructions,
|
|
1676
|
+
terminator: {
|
|
1677
|
+
kind: "branch",
|
|
1678
|
+
targets: [deadBlockId, nextBlockId],
|
|
1679
|
+
condition: condReg
|
|
1680
|
+
},
|
|
1681
|
+
predecessors: currentPredecessors,
|
|
1682
|
+
successors: [deadBlockId, nextBlockId],
|
|
1683
|
+
phiNodes: isFirstSubBlock ? block.phiNodes : []
|
|
1684
|
+
});
|
|
1685
|
+
isFirstSubBlock = false;
|
|
1686
|
+
newBlocks.push({
|
|
1687
|
+
id: deadBlockId,
|
|
1688
|
+
label: "opaque_dead",
|
|
1689
|
+
instructions: deadCodeInsts,
|
|
1690
|
+
terminator: { kind: "jump", targets: [nextBlockId] },
|
|
1691
|
+
predecessors: [currentBlockId],
|
|
1692
|
+
successors: [nextBlockId],
|
|
1693
|
+
phiNodes: []
|
|
1694
|
+
});
|
|
1695
|
+
const origBlockId = currentBlockId;
|
|
1696
|
+
currentBlockId = nextBlockId;
|
|
1697
|
+
currentLabel = `${currentLabel}_opaque`;
|
|
1698
|
+
currentInstructions = [];
|
|
1699
|
+
currentPredecessors = [origBlockId, deadBlockId];
|
|
1700
|
+
} else {
|
|
1701
|
+
currentInstructions.push(...deadCodeInsts);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
currentInstructions.push(inst);
|
|
1705
|
+
}
|
|
1706
|
+
newBlocks.push({
|
|
1707
|
+
id: currentBlockId,
|
|
1708
|
+
label: currentLabel,
|
|
1709
|
+
instructions: currentInstructions,
|
|
1710
|
+
terminator: block.terminator,
|
|
1711
|
+
predecessors: currentPredecessors,
|
|
1712
|
+
successors: block.successors,
|
|
1713
|
+
phiNodes: isFirstSubBlock ? block.phiNodes : []
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
return changed ? { ...func, blocks: newBlocks } : func;
|
|
1717
|
+
});
|
|
1718
|
+
return {
|
|
1719
|
+
module: { ...ctx.module, functions: newFunctions, constantPool: newCP },
|
|
1720
|
+
symbolsRenamed: 0,
|
|
1721
|
+
nodesTransformed,
|
|
1722
|
+
diagnostics: []
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
// src/passes/control-flow-flattening.ts
|
|
1728
|
+
import { OpCode as OpCode8, OperandKind as OperandKind9, ConstantKind as ConstantKind7 } from "@tsvm/shared";
|
|
1729
|
+
var ControlFlowFlatteningPass = class {
|
|
1730
|
+
name = "ControlFlowFlatteningPass";
|
|
1731
|
+
priority = 26;
|
|
1732
|
+
execute(ctx) {
|
|
1733
|
+
let nodesTransformed = 0;
|
|
1734
|
+
const constantPool = [...ctx.module.constantPool];
|
|
1735
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
1736
|
+
if (!func.isVirtualized || func.blocks.length < 3) return func;
|
|
1737
|
+
const hasTryCatch = func.blocks.some(
|
|
1738
|
+
(block) => block.instructions.some((inst) => inst.opcode === OpCode8.TryCatchBegin || inst.opcode === OpCode8.TryCatchEnd)
|
|
1739
|
+
);
|
|
1740
|
+
if (hasTryCatch) return func;
|
|
1741
|
+
if (ctx.rng.nextFloat() < 0.4) return func;
|
|
1742
|
+
nodesTransformed++;
|
|
1743
|
+
const stateMap = /* @__PURE__ */ new Map();
|
|
1744
|
+
const blockOrder = ctx.rng.shuffle([...func.blocks]);
|
|
1745
|
+
for (const block of func.blocks) {
|
|
1746
|
+
stateMap.set(block.id, ctx.rng.nextRange(1e3, 2147483647));
|
|
1747
|
+
}
|
|
1748
|
+
const maxReg = getMaxRegister(func) - 1;
|
|
1749
|
+
const stateReg = `r${maxReg + 1}`;
|
|
1750
|
+
const tempReg = `r${maxReg + 2}`;
|
|
1751
|
+
const tableReg = `r${maxReg + 3}`;
|
|
1752
|
+
const primeReg = `r${maxReg + 4}`;
|
|
1753
|
+
const sizeReg = `r${maxReg + 5}`;
|
|
1754
|
+
const mulReg = `r${maxReg + 6}`;
|
|
1755
|
+
const idxReg = `r${maxReg + 7}`;
|
|
1756
|
+
const targetReg = `r${maxReg + 8}`;
|
|
1757
|
+
const constIdxReg = `r${maxReg + 9}`;
|
|
1758
|
+
const constLblReg = `r${maxReg + 10}`;
|
|
1759
|
+
const stateConstants = /* @__PURE__ */ new Map();
|
|
1760
|
+
for (const [blockId, stateId] of stateMap) {
|
|
1761
|
+
const cpIdx = constantPool.length;
|
|
1762
|
+
constantPool.push({ index: cpIdx, kind: ConstantKind7.Number, value: stateId });
|
|
1763
|
+
stateConstants.set(stateId, cpIdx);
|
|
1764
|
+
}
|
|
1765
|
+
const getOrAddNumberConstant = (val) => {
|
|
1766
|
+
let idx = constantPool.findIndex((c) => c.kind === ConstantKind7.Number && c.value === val);
|
|
1767
|
+
if (idx === -1) {
|
|
1768
|
+
idx = constantPool.length;
|
|
1769
|
+
constantPool.push({ index: idx, kind: ConstantKind7.Number, value: val });
|
|
1770
|
+
}
|
|
1771
|
+
return idx;
|
|
1772
|
+
};
|
|
1773
|
+
const dispatcherId = `__cff_dispatch_${ctx.rng.identifier(6)}`;
|
|
1774
|
+
const exitId = `__cff_exit_${ctx.rng.identifier(4)}`;
|
|
1775
|
+
const entryStateId = stateMap.get(func.blocks[0].id);
|
|
1776
|
+
const caseBlocks = [];
|
|
1777
|
+
for (const block of blockOrder) {
|
|
1778
|
+
const stateId = stateMap.get(block.id);
|
|
1779
|
+
const caseLabelId = `__cff_case_${block.id}_${ctx.rng.identifier(4)}`;
|
|
1780
|
+
const caseInstructions = [...block.instructions];
|
|
1781
|
+
let caseTerminator;
|
|
1782
|
+
if (block.terminator.kind === "return" || block.terminator.kind === "throw") {
|
|
1783
|
+
caseTerminator = block.terminator;
|
|
1784
|
+
} else if (block.terminator.kind === "jump") {
|
|
1785
|
+
const targetState = stateMap.get(block.terminator.targets[0]);
|
|
1786
|
+
caseInstructions.push({
|
|
1787
|
+
opcode: OpCode8.LoadConst,
|
|
1788
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: stateConstants.get(targetState) }],
|
|
1789
|
+
result: stateReg
|
|
1790
|
+
});
|
|
1791
|
+
caseTerminator = { kind: "jump", targets: [dispatcherId] };
|
|
1792
|
+
} else if (block.terminator.kind === "branch") {
|
|
1793
|
+
const trueState = stateMap.get(block.terminator.targets[0]);
|
|
1794
|
+
const falseState = stateMap.get(block.terminator.targets[1]);
|
|
1795
|
+
const trueBlockId = `__cff_br_t_${ctx.rng.identifier(4)}`;
|
|
1796
|
+
const falseBlockId = `__cff_br_f_${ctx.rng.identifier(4)}`;
|
|
1797
|
+
caseBlocks.push({
|
|
1798
|
+
id: trueBlockId,
|
|
1799
|
+
label: "cff_br_true",
|
|
1800
|
+
instructions: [
|
|
1801
|
+
{
|
|
1802
|
+
opcode: OpCode8.LoadConst,
|
|
1803
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: stateConstants.get(trueState) }],
|
|
1804
|
+
result: stateReg
|
|
1805
|
+
}
|
|
1806
|
+
],
|
|
1807
|
+
terminator: { kind: "jump", targets: [dispatcherId] },
|
|
1808
|
+
predecessors: [caseLabelId],
|
|
1809
|
+
successors: [dispatcherId],
|
|
1810
|
+
phiNodes: []
|
|
1811
|
+
});
|
|
1812
|
+
caseBlocks.push({
|
|
1813
|
+
id: falseBlockId,
|
|
1814
|
+
label: "cff_br_false",
|
|
1815
|
+
instructions: [
|
|
1816
|
+
{
|
|
1817
|
+
opcode: OpCode8.LoadConst,
|
|
1818
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: stateConstants.get(falseState) }],
|
|
1819
|
+
result: stateReg
|
|
1820
|
+
}
|
|
1821
|
+
],
|
|
1822
|
+
terminator: { kind: "jump", targets: [dispatcherId] },
|
|
1823
|
+
predecessors: [caseLabelId],
|
|
1824
|
+
successors: [dispatcherId],
|
|
1825
|
+
phiNodes: []
|
|
1826
|
+
});
|
|
1827
|
+
caseTerminator = {
|
|
1828
|
+
kind: "branch",
|
|
1829
|
+
targets: [trueBlockId, falseBlockId],
|
|
1830
|
+
condition: block.terminator.condition
|
|
1831
|
+
};
|
|
1832
|
+
} else {
|
|
1833
|
+
caseTerminator = block.terminator;
|
|
1834
|
+
}
|
|
1835
|
+
caseBlocks.push({
|
|
1836
|
+
id: caseLabelId,
|
|
1837
|
+
label: `cff_case_${block.id}`,
|
|
1838
|
+
instructions: caseInstructions,
|
|
1839
|
+
terminator: caseTerminator,
|
|
1840
|
+
predecessors: [dispatcherId],
|
|
1841
|
+
successors: caseTerminator.targets || [],
|
|
1842
|
+
phiNodes: block.phiNodes || []
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
const orderedCases = [...caseBlocks.filter((b) => b.label?.startsWith("cff_case_"))];
|
|
1846
|
+
let tableSize = 1;
|
|
1847
|
+
while (tableSize < orderedCases.length) {
|
|
1848
|
+
tableSize *= 2;
|
|
1849
|
+
}
|
|
1850
|
+
const primes = [
|
|
1851
|
+
31,
|
|
1852
|
+
37,
|
|
1853
|
+
41,
|
|
1854
|
+
43,
|
|
1855
|
+
47,
|
|
1856
|
+
53,
|
|
1857
|
+
59,
|
|
1858
|
+
61,
|
|
1859
|
+
67,
|
|
1860
|
+
71,
|
|
1861
|
+
73,
|
|
1862
|
+
79,
|
|
1863
|
+
83,
|
|
1864
|
+
89,
|
|
1865
|
+
97,
|
|
1866
|
+
101,
|
|
1867
|
+
103,
|
|
1868
|
+
107,
|
|
1869
|
+
109,
|
|
1870
|
+
113,
|
|
1871
|
+
127,
|
|
1872
|
+
131,
|
|
1873
|
+
137,
|
|
1874
|
+
139,
|
|
1875
|
+
149,
|
|
1876
|
+
151,
|
|
1877
|
+
157,
|
|
1878
|
+
163,
|
|
1879
|
+
167,
|
|
1880
|
+
173,
|
|
1881
|
+
179,
|
|
1882
|
+
181,
|
|
1883
|
+
191,
|
|
1884
|
+
193,
|
|
1885
|
+
197,
|
|
1886
|
+
199,
|
|
1887
|
+
211,
|
|
1888
|
+
223,
|
|
1889
|
+
227,
|
|
1890
|
+
229,
|
|
1891
|
+
233,
|
|
1892
|
+
239,
|
|
1893
|
+
241,
|
|
1894
|
+
251,
|
|
1895
|
+
257,
|
|
1896
|
+
263,
|
|
1897
|
+
269,
|
|
1898
|
+
271,
|
|
1899
|
+
277,
|
|
1900
|
+
281,
|
|
1901
|
+
283,
|
|
1902
|
+
293,
|
|
1903
|
+
307,
|
|
1904
|
+
311,
|
|
1905
|
+
313,
|
|
1906
|
+
317,
|
|
1907
|
+
331,
|
|
1908
|
+
337,
|
|
1909
|
+
347,
|
|
1910
|
+
349,
|
|
1911
|
+
353,
|
|
1912
|
+
359,
|
|
1913
|
+
367,
|
|
1914
|
+
373,
|
|
1915
|
+
379,
|
|
1916
|
+
383,
|
|
1917
|
+
389,
|
|
1918
|
+
397
|
|
1919
|
+
];
|
|
1920
|
+
let selectedPrime = 31;
|
|
1921
|
+
let collisionFree = false;
|
|
1922
|
+
while (!collisionFree) {
|
|
1923
|
+
for (const prime of primes) {
|
|
1924
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1925
|
+
let collision = false;
|
|
1926
|
+
for (const block of orderedCases) {
|
|
1927
|
+
const stateId = stateMap.get(block.label.replace("cff_case_", ""));
|
|
1928
|
+
const idx = stateId * prime % tableSize;
|
|
1929
|
+
if (seen.has(idx)) {
|
|
1930
|
+
collision = true;
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
seen.add(idx);
|
|
1934
|
+
}
|
|
1935
|
+
if (!collision) {
|
|
1936
|
+
selectedPrime = prime;
|
|
1937
|
+
collisionFree = true;
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
if (!collisionFree) {
|
|
1942
|
+
tableSize *= 2;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
const tableSlots = /* @__PURE__ */ new Map();
|
|
1946
|
+
for (const caseBlock of orderedCases) {
|
|
1947
|
+
const stateId = stateMap.get(caseBlock.label.replace("cff_case_", ""));
|
|
1948
|
+
const slotIdx = stateId * selectedPrime % tableSize;
|
|
1949
|
+
tableSlots.set(slotIdx, caseBlock.id);
|
|
1950
|
+
}
|
|
1951
|
+
const entryBlockId = `__cff_entry_${ctx.rng.identifier(4)}`;
|
|
1952
|
+
const entryBlockInstructions = [
|
|
1953
|
+
{
|
|
1954
|
+
opcode: OpCode8.LoadConst,
|
|
1955
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: stateConstants.get(entryStateId) }],
|
|
1956
|
+
result: stateReg
|
|
1957
|
+
},
|
|
1958
|
+
// Initialize jump table array
|
|
1959
|
+
{
|
|
1960
|
+
opcode: OpCode8.ArrayNew,
|
|
1961
|
+
operands: [],
|
|
1962
|
+
result: tableReg
|
|
1963
|
+
}
|
|
1964
|
+
];
|
|
1965
|
+
for (let i = 0; i < tableSize; i++) {
|
|
1966
|
+
const targetBlockId = tableSlots.get(i) || exitId;
|
|
1967
|
+
const valIdx = getOrAddNumberConstant(i);
|
|
1968
|
+
entryBlockInstructions.push(
|
|
1969
|
+
{
|
|
1970
|
+
opcode: OpCode8.LoadConst,
|
|
1971
|
+
operands: [{ kind: OperandKind9.BlockLabel, value: targetBlockId }],
|
|
1972
|
+
result: constLblReg
|
|
1973
|
+
},
|
|
1974
|
+
{
|
|
1975
|
+
opcode: OpCode8.LoadConst,
|
|
1976
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: valIdx }],
|
|
1977
|
+
result: constIdxReg
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
opcode: OpCode8.ComputedSet,
|
|
1981
|
+
operands: [
|
|
1982
|
+
{ kind: OperandKind9.Register, value: tableReg },
|
|
1983
|
+
{ kind: OperandKind9.Register, value: constIdxReg },
|
|
1984
|
+
{ kind: OperandKind9.Register, value: constLblReg }
|
|
1985
|
+
]
|
|
1986
|
+
}
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
const entryBlock = {
|
|
1990
|
+
id: entryBlockId,
|
|
1991
|
+
label: "cff_entry",
|
|
1992
|
+
instructions: entryBlockInstructions,
|
|
1993
|
+
terminator: { kind: "jump", targets: [dispatcherId] },
|
|
1994
|
+
predecessors: [],
|
|
1995
|
+
successors: [dispatcherId],
|
|
1996
|
+
phiNodes: []
|
|
1997
|
+
};
|
|
1998
|
+
const dispatcherBlock = {
|
|
1999
|
+
id: dispatcherId,
|
|
2000
|
+
label: "cff_dispatcher",
|
|
2001
|
+
instructions: [
|
|
2002
|
+
// 1. Load prime
|
|
2003
|
+
{
|
|
2004
|
+
opcode: OpCode8.LoadConst,
|
|
2005
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: getOrAddNumberConstant(selectedPrime) }],
|
|
2006
|
+
result: primeReg
|
|
2007
|
+
},
|
|
2008
|
+
// 2. Mul: stateReg * primeReg
|
|
2009
|
+
{
|
|
2010
|
+
opcode: OpCode8.Mul,
|
|
2011
|
+
operands: [
|
|
2012
|
+
{ kind: OperandKind9.Register, value: stateReg },
|
|
2013
|
+
{ kind: OperandKind9.Register, value: primeReg }
|
|
2014
|
+
],
|
|
2015
|
+
result: mulReg
|
|
2016
|
+
},
|
|
2017
|
+
// 3. Load table size
|
|
2018
|
+
{
|
|
2019
|
+
opcode: OpCode8.LoadConst,
|
|
2020
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: getOrAddNumberConstant(tableSize) }],
|
|
2021
|
+
result: sizeReg
|
|
2022
|
+
},
|
|
2023
|
+
// 4. Mod: mulReg % sizeReg
|
|
2024
|
+
{
|
|
2025
|
+
opcode: OpCode8.Mod,
|
|
2026
|
+
operands: [
|
|
2027
|
+
{ kind: OperandKind9.Register, value: mulReg },
|
|
2028
|
+
{ kind: OperandKind9.Register, value: sizeReg }
|
|
2029
|
+
],
|
|
2030
|
+
result: idxReg
|
|
2031
|
+
},
|
|
2032
|
+
// 5. ComputedGet: tableReg[idxReg] -> targetReg
|
|
2033
|
+
{
|
|
2034
|
+
opcode: OpCode8.ComputedGet,
|
|
2035
|
+
operands: [
|
|
2036
|
+
{ kind: OperandKind9.Register, value: tableReg },
|
|
2037
|
+
{ kind: OperandKind9.Register, value: idxReg }
|
|
2038
|
+
],
|
|
2039
|
+
result: targetReg
|
|
2040
|
+
},
|
|
2041
|
+
// 6. Jmp targetReg
|
|
2042
|
+
{
|
|
2043
|
+
opcode: OpCode8.Jmp,
|
|
2044
|
+
operands: [{ kind: OperandKind9.Register, value: targetReg }]
|
|
2045
|
+
}
|
|
2046
|
+
],
|
|
2047
|
+
terminator: { kind: "dynamic_jmp", targets: [] },
|
|
2048
|
+
predecessors: [entryBlockId, ...caseBlocks.map((b) => b.id)],
|
|
2049
|
+
successors: [],
|
|
2050
|
+
phiNodes: []
|
|
2051
|
+
};
|
|
2052
|
+
const decoyBlocks = [];
|
|
2053
|
+
for (let i = 0; i < 2; i++) {
|
|
2054
|
+
const decoyCmpBlockId = `__cff_decoy_cmp_${i}_${ctx.rng.identifier(4)}`;
|
|
2055
|
+
const decoyCaseBlockId = `__cff_decoy_case_${i}_${ctx.rng.identifier(4)}`;
|
|
2056
|
+
const decoyNextBlockId = i === 0 ? `__cff_decoy_cmp_1_${ctx.rng.identifier(4)}` : exitId;
|
|
2057
|
+
const decoyStateId = ctx.rng.nextRange(1e3, 2147483647);
|
|
2058
|
+
const decoyStateIdx = getOrAddNumberConstant(decoyStateId);
|
|
2059
|
+
decoyBlocks.push(
|
|
2060
|
+
{
|
|
2061
|
+
id: decoyCmpBlockId,
|
|
2062
|
+
label: `cff_decoy_cmp_${i}`,
|
|
2063
|
+
instructions: [
|
|
2064
|
+
{
|
|
2065
|
+
opcode: OpCode8.LoadConst,
|
|
2066
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: decoyStateIdx }],
|
|
2067
|
+
result: tempReg
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
opcode: OpCode8.Eq,
|
|
2071
|
+
operands: [
|
|
2072
|
+
{ kind: OperandKind9.Register, value: stateReg },
|
|
2073
|
+
{ kind: OperandKind9.Register, value: tempReg }
|
|
2074
|
+
],
|
|
2075
|
+
result: tempReg
|
|
2076
|
+
}
|
|
2077
|
+
],
|
|
2078
|
+
terminator: {
|
|
2079
|
+
kind: "branch",
|
|
2080
|
+
targets: [decoyCaseBlockId, decoyNextBlockId],
|
|
2081
|
+
condition: tempReg
|
|
2082
|
+
},
|
|
2083
|
+
predecessors: [],
|
|
2084
|
+
successors: [decoyCaseBlockId, decoyNextBlockId],
|
|
2085
|
+
phiNodes: []
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
id: decoyCaseBlockId,
|
|
2089
|
+
label: `cff_decoy_case_${i}`,
|
|
2090
|
+
instructions: [
|
|
2091
|
+
{
|
|
2092
|
+
opcode: OpCode8.LoadConst,
|
|
2093
|
+
operands: [{ kind: OperandKind9.ConstantIndex, value: decoyStateIdx }],
|
|
2094
|
+
result: stateReg
|
|
2095
|
+
}
|
|
2096
|
+
],
|
|
2097
|
+
terminator: {
|
|
2098
|
+
kind: "jump",
|
|
2099
|
+
targets: [dispatcherId]
|
|
2100
|
+
},
|
|
2101
|
+
predecessors: [decoyCmpBlockId],
|
|
2102
|
+
successors: [dispatcherId],
|
|
2103
|
+
phiNodes: []
|
|
2104
|
+
}
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
const exitBlock = {
|
|
2108
|
+
id: exitId,
|
|
2109
|
+
label: "cff_exit",
|
|
2110
|
+
instructions: [],
|
|
2111
|
+
terminator: { kind: "return", targets: [] },
|
|
2112
|
+
predecessors: [],
|
|
2113
|
+
successors: [],
|
|
2114
|
+
phiNodes: []
|
|
2115
|
+
};
|
|
2116
|
+
const allBlocks = ctx.rng.shuffle([entryBlock, dispatcherBlock, ...decoyBlocks, ...caseBlocks, exitBlock]);
|
|
2117
|
+
const entryIdx = allBlocks.findIndex((b) => b.id === entryBlockId);
|
|
2118
|
+
if (entryIdx > 0) {
|
|
2119
|
+
[allBlocks[0], allBlocks[entryIdx]] = [allBlocks[entryIdx], allBlocks[0]];
|
|
2120
|
+
}
|
|
2121
|
+
return { ...func, blocks: allBlocks };
|
|
2122
|
+
});
|
|
2123
|
+
return {
|
|
2124
|
+
module: { ...ctx.module, functions: newFunctions, constantPool },
|
|
2125
|
+
symbolsRenamed: 0,
|
|
2126
|
+
nodesTransformed,
|
|
2127
|
+
diagnostics: []
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
|
|
2132
|
+
// src/passes/strip-debug.ts
|
|
2133
|
+
import { OpCode as OpCode9, ConstantKind as ConstantKind8 } from "@tsvm/shared";
|
|
2134
|
+
var StripDebugPass = class {
|
|
2135
|
+
name = "StripDebugPass";
|
|
2136
|
+
priority = 5;
|
|
2137
|
+
// Run very early
|
|
2138
|
+
execute(ctx) {
|
|
2139
|
+
let nodesTransformed = 0;
|
|
2140
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
2141
|
+
if (!func.isVirtualized) return func;
|
|
2142
|
+
let changed = false;
|
|
2143
|
+
const newBlocks = func.blocks.map((block) => {
|
|
2144
|
+
const stringRegs = /* @__PURE__ */ new Map();
|
|
2145
|
+
const globalRegs = /* @__PURE__ */ new Map();
|
|
2146
|
+
const newInstructions = [];
|
|
2147
|
+
for (const inst of block.instructions) {
|
|
2148
|
+
if (inst.opcode === OpCode9.LoadConst) {
|
|
2149
|
+
const cpIdx = inst.operands[0].value;
|
|
2150
|
+
const cpEntry = ctx.module.constantPool.find((c) => c.index === cpIdx);
|
|
2151
|
+
if (cpEntry && cpEntry.kind === ConstantKind8.String) {
|
|
2152
|
+
stringRegs.set(inst.result, cpEntry.value);
|
|
2153
|
+
}
|
|
2154
|
+
} else if (inst.opcode === OpCode9.LoadGlobal) {
|
|
2155
|
+
const propReg = inst.operands[0].value;
|
|
2156
|
+
const propName = stringRegs.get(propReg);
|
|
2157
|
+
if (propName) {
|
|
2158
|
+
globalRegs.set(inst.result, propName);
|
|
2159
|
+
}
|
|
2160
|
+
} else if (inst.opcode === OpCode9.CallMethod) {
|
|
2161
|
+
const objReg = inst.operands[0].value;
|
|
2162
|
+
if (globalRegs.get(objReg) === "console") {
|
|
2163
|
+
changed = true;
|
|
2164
|
+
nodesTransformed++;
|
|
2165
|
+
if (inst.result) {
|
|
2166
|
+
newInstructions.push({
|
|
2167
|
+
...inst,
|
|
2168
|
+
opcode: OpCode9.Nop,
|
|
2169
|
+
operands: [],
|
|
2170
|
+
result: void 0
|
|
2171
|
+
// drop result
|
|
2172
|
+
});
|
|
2173
|
+
} else {
|
|
2174
|
+
newInstructions.push({ ...inst, opcode: OpCode9.Nop, operands: [] });
|
|
2175
|
+
}
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
newInstructions.push(inst);
|
|
2180
|
+
}
|
|
2181
|
+
return changed ? { ...block, instructions: newInstructions } : block;
|
|
2182
|
+
});
|
|
2183
|
+
return changed ? { ...func, blocks: newBlocks } : func;
|
|
2184
|
+
});
|
|
2185
|
+
return {
|
|
2186
|
+
module: { ...ctx.module, functions: newFunctions },
|
|
2187
|
+
symbolsRenamed: 0,
|
|
2188
|
+
nodesTransformed,
|
|
2189
|
+
diagnostics: []
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
|
|
2194
|
+
// src/passes/ir-validation.ts
|
|
2195
|
+
import { DiagnosticSeverity, OperandKind as OperandKind10 } from "@tsvm/shared";
|
|
2196
|
+
function registerValue(val) {
|
|
2197
|
+
if (typeof val === "string" && /^r\d+$/.test(val)) return val;
|
|
2198
|
+
return void 0;
|
|
2199
|
+
}
|
|
2200
|
+
var IRValidationPass = class {
|
|
2201
|
+
name = "IRValidationPass";
|
|
2202
|
+
priority = 999;
|
|
2203
|
+
execute(ctx) {
|
|
2204
|
+
const diagnostics = [];
|
|
2205
|
+
let nodesTransformed = 0;
|
|
2206
|
+
for (const func of ctx.module.functions) {
|
|
2207
|
+
if (func.isVirtualized) {
|
|
2208
|
+
const fnDiagnostics = this.validateFunction(func, ctx.module);
|
|
2209
|
+
diagnostics.push(...fnDiagnostics);
|
|
2210
|
+
nodesTransformed += fnDiagnostics.length;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
module: ctx.module,
|
|
2215
|
+
symbolsRenamed: 0,
|
|
2216
|
+
nodesTransformed,
|
|
2217
|
+
diagnostics
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
validateFunction(func, module) {
|
|
2221
|
+
const diagnostics = [];
|
|
2222
|
+
const blockIds = new Set(func.blocks.map((b) => b.id));
|
|
2223
|
+
const blockMap = new Map(func.blocks.map((b) => [b.id, b]));
|
|
2224
|
+
const maxReg = getMaxRegister(func);
|
|
2225
|
+
for (const block of func.blocks) {
|
|
2226
|
+
for (const pred of block.predecessors) {
|
|
2227
|
+
if (!blockIds.has(pred)) {
|
|
2228
|
+
diagnostics.push({
|
|
2229
|
+
severity: DiagnosticSeverity.Warning,
|
|
2230
|
+
code: "IR_INVALID_PREDECESSOR",
|
|
2231
|
+
message: `Block "${block.id}" has non-existent predecessor "${pred}"`
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
for (const succ of block.successors) {
|
|
2236
|
+
if (!blockIds.has(succ)) {
|
|
2237
|
+
diagnostics.push({
|
|
2238
|
+
severity: DiagnosticSeverity.Warning,
|
|
2239
|
+
code: "IR_INVALID_SUCCESSOR",
|
|
2240
|
+
message: `Block "${block.id}" has non-existent successor "${succ}"`
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
for (const target of block.terminator.targets) {
|
|
2245
|
+
if (!blockIds.has(target)) {
|
|
2246
|
+
diagnostics.push({
|
|
2247
|
+
severity: DiagnosticSeverity.Warning,
|
|
2248
|
+
code: "IR_INVALID_TARGET",
|
|
2249
|
+
message: `Block "${block.id}" references non-existent target block "${target}"`
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
for (const phi of block.phiNodes ?? []) {
|
|
2254
|
+
const regPhi = registerValue(phi.result);
|
|
2255
|
+
if (regPhi) {
|
|
2256
|
+
const idx = Number.parseInt(regPhi.substring(1), 10);
|
|
2257
|
+
if (idx >= maxReg) {
|
|
2258
|
+
diagnostics.push({
|
|
2259
|
+
severity: DiagnosticSeverity.Warning,
|
|
2260
|
+
code: "IR_PHI_REGISTER_BOUNDS",
|
|
2261
|
+
message: `Phi node result "${phi.result}" exceeds max register ${maxReg - 1} in function "${func.name}"`
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
for (const inc of phi.incoming) {
|
|
2266
|
+
if (!blockIds.has(inc.blockId)) {
|
|
2267
|
+
diagnostics.push({
|
|
2268
|
+
severity: DiagnosticSeverity.Warning,
|
|
2269
|
+
code: "IR_INVALID_PHI_BLOCK",
|
|
2270
|
+
message: `Phi node in "${block.id}" references non-existent incoming block "${inc.blockId}"`
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
const regInc = registerValue(inc.register);
|
|
2274
|
+
if (regInc) {
|
|
2275
|
+
const idx = Number.parseInt(regInc.substring(1), 10);
|
|
2276
|
+
if (idx >= maxReg) {
|
|
2277
|
+
diagnostics.push({
|
|
2278
|
+
severity: DiagnosticSeverity.Warning,
|
|
2279
|
+
code: "IR_PHI_REGISTER_BOUNDS",
|
|
2280
|
+
message: `Phi node incoming register "${inc.register}" exceeds max register ${maxReg - 1} in function "${func.name}"`
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
for (const inst of block.instructions) {
|
|
2287
|
+
const regResult = registerValue(inst.result);
|
|
2288
|
+
if (regResult) {
|
|
2289
|
+
const idx = Number.parseInt(regResult.substring(1), 10);
|
|
2290
|
+
if (idx >= maxReg) {
|
|
2291
|
+
diagnostics.push({
|
|
2292
|
+
severity: DiagnosticSeverity.Warning,
|
|
2293
|
+
code: "IR_INSTRUCTION_REGISTER_BOUNDS",
|
|
2294
|
+
message: `Instruction result "${inst.result}" exceeds max register ${maxReg - 1} in function "${func.name}"`
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
for (const op of inst.operands) {
|
|
2299
|
+
if (op.kind === OperandKind10.Register) {
|
|
2300
|
+
const regVal = registerValue(op.value);
|
|
2301
|
+
if (regVal) {
|
|
2302
|
+
const idx = Number.parseInt(regVal.substring(1), 10);
|
|
2303
|
+
if (idx >= maxReg) {
|
|
2304
|
+
diagnostics.push({
|
|
2305
|
+
severity: DiagnosticSeverity.Warning,
|
|
2306
|
+
code: "IR_OPERAND_REGISTER_BOUNDS",
|
|
2307
|
+
message: `Operand "${op.value}" exceeds max register ${maxReg - 1} in function "${func.name}"`
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
const regCond = registerValue(block.terminator.condition);
|
|
2315
|
+
if (regCond) {
|
|
2316
|
+
const idx = Number.parseInt(regCond.substring(1), 10);
|
|
2317
|
+
if (idx >= maxReg) {
|
|
2318
|
+
diagnostics.push({
|
|
2319
|
+
severity: DiagnosticSeverity.Warning,
|
|
2320
|
+
code: "IR_TERMINATOR_REGISTER_BOUNDS",
|
|
2321
|
+
message: `Terminator condition "${block.terminator.condition}" exceeds max register ${maxReg - 1} in function "${func.name}"`
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
const regRet = registerValue(block.terminator.returnValue);
|
|
2326
|
+
if (regRet) {
|
|
2327
|
+
const idx = Number.parseInt(regRet.substring(1), 10);
|
|
2328
|
+
if (idx >= maxReg) {
|
|
2329
|
+
diagnostics.push({
|
|
2330
|
+
severity: DiagnosticSeverity.Warning,
|
|
2331
|
+
code: "IR_TERMINATOR_REGISTER_BOUNDS",
|
|
2332
|
+
message: `Terminator returnValue "${block.terminator.returnValue}" exceeds max register ${maxReg - 1} in function "${func.name}"`
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
for (const block of func.blocks) {
|
|
2338
|
+
for (const succ of block.successors) {
|
|
2339
|
+
const succBlock = blockMap.get(succ);
|
|
2340
|
+
if (succBlock && !succBlock.predecessors.includes(block.id)) {
|
|
2341
|
+
diagnostics.push({
|
|
2342
|
+
severity: DiagnosticSeverity.Warning,
|
|
2343
|
+
code: "IR_INCONSISTENT_EDGE",
|
|
2344
|
+
message: `Block "${block.id}" lists "${succ}" as successor but "${succ}" does not list it as predecessor`
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
for (const pred of block.predecessors) {
|
|
2349
|
+
const predBlock = blockMap.get(pred);
|
|
2350
|
+
if (predBlock && !predBlock.successors.includes(block.id)) {
|
|
2351
|
+
diagnostics.push({
|
|
2352
|
+
severity: DiagnosticSeverity.Warning,
|
|
2353
|
+
code: "IR_INCONSISTENT_EDGE",
|
|
2354
|
+
message: `Block "${block.id}" lists "${pred}" as predecessor but "${pred}" does not list it as successor`
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
return diagnostics;
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
|
|
2363
|
+
// src/passes/register-compacting.ts
|
|
2364
|
+
import { OperandKind as OperandKind11 } from "@tsvm/shared";
|
|
2365
|
+
function collectUsedRegisters(func) {
|
|
2366
|
+
const regs = /* @__PURE__ */ new Set();
|
|
2367
|
+
const add = (val) => {
|
|
2368
|
+
if (!val) return;
|
|
2369
|
+
const m = /^r(\d+)$/.exec(val);
|
|
2370
|
+
if (m) regs.add(Number.parseInt(m[1], 10));
|
|
2371
|
+
};
|
|
2372
|
+
for (const param of func.params) add(param.register);
|
|
2373
|
+
for (const local of func.locals) add(local.register);
|
|
2374
|
+
for (const block of func.blocks) {
|
|
2375
|
+
for (const phi of block.phiNodes ?? []) {
|
|
2376
|
+
add(phi.result);
|
|
2377
|
+
for (const inc of phi.incoming) add(inc.register);
|
|
2378
|
+
}
|
|
2379
|
+
for (const inst of block.instructions) {
|
|
2380
|
+
add(inst.result);
|
|
2381
|
+
for (const op of inst.operands) {
|
|
2382
|
+
if (op.kind === OperandKind11.Register) add(op.value);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
add(block.terminator.condition);
|
|
2386
|
+
add(block.terminator.returnValue);
|
|
2387
|
+
}
|
|
2388
|
+
return regs;
|
|
2389
|
+
}
|
|
2390
|
+
function buildCompactMap(usedRegs) {
|
|
2391
|
+
const sorted = Array.from(usedRegs).sort((a, b) => a - b);
|
|
2392
|
+
let hasGap = false;
|
|
2393
|
+
const map = /* @__PURE__ */ new Map();
|
|
2394
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
2395
|
+
const oldReg = sorted[i];
|
|
2396
|
+
if (oldReg !== i) hasGap = true;
|
|
2397
|
+
map.set(`r${oldReg}`, `r${i}`);
|
|
2398
|
+
}
|
|
2399
|
+
return hasGap ? map : null;
|
|
2400
|
+
}
|
|
2401
|
+
function mapReg(val, map) {
|
|
2402
|
+
if (!val) return void 0;
|
|
2403
|
+
return map.get(val) ?? val;
|
|
2404
|
+
}
|
|
2405
|
+
function mapOperandValue(val, map) {
|
|
2406
|
+
if (typeof val === "string") return map.get(val) ?? val;
|
|
2407
|
+
return val;
|
|
2408
|
+
}
|
|
2409
|
+
var RegisterCompactingPass = class {
|
|
2410
|
+
name = "RegisterCompactingPass";
|
|
2411
|
+
priority = 90;
|
|
2412
|
+
execute(ctx) {
|
|
2413
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
2414
|
+
if (!func.isVirtualized) return func;
|
|
2415
|
+
const usedRegs = collectUsedRegisters(func);
|
|
2416
|
+
const compactMap = buildCompactMap(usedRegs);
|
|
2417
|
+
if (!compactMap) return func;
|
|
2418
|
+
return this.rewriteRegisters(func, compactMap);
|
|
2419
|
+
});
|
|
2420
|
+
return {
|
|
2421
|
+
module: { ...ctx.module, functions: newFunctions },
|
|
2422
|
+
symbolsRenamed: 0,
|
|
2423
|
+
nodesTransformed: newFunctions.filter((f, i) => f !== ctx.module.functions[i]).length,
|
|
2424
|
+
diagnostics: []
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
rewriteRegisters(func, map) {
|
|
2428
|
+
return {
|
|
2429
|
+
...func,
|
|
2430
|
+
params: func.params.map((p) => ({
|
|
2431
|
+
...p,
|
|
2432
|
+
register: mapReg(p.register, map)
|
|
2433
|
+
})),
|
|
2434
|
+
locals: func.locals.map((l) => ({
|
|
2435
|
+
...l,
|
|
2436
|
+
register: mapReg(l.register, map)
|
|
2437
|
+
})),
|
|
2438
|
+
blocks: func.blocks.map((block) => ({
|
|
2439
|
+
...block,
|
|
2440
|
+
phiNodes: (block.phiNodes ?? []).map((phi) => ({
|
|
2441
|
+
...phi,
|
|
2442
|
+
result: mapReg(phi.result, map),
|
|
2443
|
+
incoming: phi.incoming.map((inc) => ({
|
|
2444
|
+
...inc,
|
|
2445
|
+
register: mapReg(inc.register, map)
|
|
2446
|
+
}))
|
|
2447
|
+
})),
|
|
2448
|
+
instructions: block.instructions.map((inst) => ({
|
|
2449
|
+
...inst,
|
|
2450
|
+
result: mapReg(inst.result, map),
|
|
2451
|
+
operands: inst.operands.map((op) => ({
|
|
2452
|
+
...op,
|
|
2453
|
+
value: op.kind === OperandKind11.Register ? mapOperandValue(op.value, map) : op.value
|
|
2454
|
+
}))
|
|
2455
|
+
})),
|
|
2456
|
+
terminator: {
|
|
2457
|
+
...block.terminator,
|
|
2458
|
+
condition: mapReg(block.terminator.condition, map),
|
|
2459
|
+
returnValue: mapReg(block.terminator.returnValue, map)
|
|
2460
|
+
}
|
|
2461
|
+
}))
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
|
|
2466
|
+
// src/passes/instruction-substitution.ts
|
|
2467
|
+
import { OpCode as OpCode10, OperandKind as OperandKind12, ConstantKind as ConstantKind9, IRType as IRType7 } from "@tsvm/shared";
|
|
2468
|
+
function inferRegisterTypes(blocks, params, locals, constantPool) {
|
|
2469
|
+
const types = /* @__PURE__ */ new Map();
|
|
2470
|
+
for (const param of params) {
|
|
2471
|
+
types.set(param.register, param.type);
|
|
2472
|
+
}
|
|
2473
|
+
for (const local of locals) {
|
|
2474
|
+
types.set(local.register, local.type);
|
|
2475
|
+
}
|
|
2476
|
+
const getConstantType = (idx) => {
|
|
2477
|
+
const entry = constantPool[idx];
|
|
2478
|
+
if (!entry) return IRType7.Any;
|
|
2479
|
+
switch (entry.kind) {
|
|
2480
|
+
case ConstantKind9.Number:
|
|
2481
|
+
return IRType7.Number;
|
|
2482
|
+
case ConstantKind9.String:
|
|
2483
|
+
case ConstantKind9.Template:
|
|
2484
|
+
return IRType7.String;
|
|
2485
|
+
case ConstantKind9.Boolean:
|
|
2486
|
+
return IRType7.Boolean;
|
|
2487
|
+
case ConstantKind9.Null:
|
|
2488
|
+
return IRType7.Null;
|
|
2489
|
+
case ConstantKind9.Undefined:
|
|
2490
|
+
return IRType7.Undefined;
|
|
2491
|
+
case ConstantKind9.BigInt:
|
|
2492
|
+
return IRType7.BigInt;
|
|
2493
|
+
default:
|
|
2494
|
+
return IRType7.Any;
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
const getOperandType = (op) => {
|
|
2498
|
+
if (op.kind === OperandKind12.Register) {
|
|
2499
|
+
return types.get(op.value) ?? IRType7.Any;
|
|
2500
|
+
}
|
|
2501
|
+
if (op.kind === OperandKind12.ConstantIndex) {
|
|
2502
|
+
return getConstantType(op.value);
|
|
2503
|
+
}
|
|
2504
|
+
return IRType7.Any;
|
|
2505
|
+
};
|
|
2506
|
+
let changed = true;
|
|
2507
|
+
let iterations = 0;
|
|
2508
|
+
while (changed && iterations < 5) {
|
|
2509
|
+
changed = false;
|
|
2510
|
+
for (const block of blocks) {
|
|
2511
|
+
for (const inst of block.instructions) {
|
|
2512
|
+
if (!inst.result) continue;
|
|
2513
|
+
let newType = IRType7.Any;
|
|
2514
|
+
switch (inst.opcode) {
|
|
2515
|
+
case OpCode10.LoadConst: {
|
|
2516
|
+
const op = inst.operands[0];
|
|
2517
|
+
if (op && op.kind === OperandKind12.ConstantIndex) {
|
|
2518
|
+
newType = getConstantType(op.value);
|
|
2519
|
+
}
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
case OpCode10.Move: {
|
|
2523
|
+
const op = inst.operands[0];
|
|
2524
|
+
if (op) {
|
|
2525
|
+
newType = getOperandType(op);
|
|
2526
|
+
}
|
|
2527
|
+
break;
|
|
2528
|
+
}
|
|
2529
|
+
case OpCode10.Add: {
|
|
2530
|
+
const opA = inst.operands[0];
|
|
2531
|
+
const opB = inst.operands[1];
|
|
2532
|
+
if (opA && opB) {
|
|
2533
|
+
const typeA = getOperandType(opA);
|
|
2534
|
+
const typeB = getOperandType(opB);
|
|
2535
|
+
if (typeA === IRType7.Number && typeB === IRType7.Number) {
|
|
2536
|
+
newType = IRType7.Number;
|
|
2537
|
+
} else if (typeA === IRType7.String || typeB === IRType7.String) {
|
|
2538
|
+
newType = IRType7.String;
|
|
2539
|
+
} else {
|
|
2540
|
+
newType = IRType7.Any;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
break;
|
|
2544
|
+
}
|
|
2545
|
+
case OpCode10.Sub:
|
|
2546
|
+
case OpCode10.Mul:
|
|
2547
|
+
case OpCode10.Div:
|
|
2548
|
+
case OpCode10.Mod:
|
|
2549
|
+
case OpCode10.BitAnd:
|
|
2550
|
+
case OpCode10.BitOr:
|
|
2551
|
+
case OpCode10.BitXor:
|
|
2552
|
+
case OpCode10.Shl:
|
|
2553
|
+
case OpCode10.Shr:
|
|
2554
|
+
case OpCode10.UShr:
|
|
2555
|
+
case OpCode10.Neg: {
|
|
2556
|
+
newType = IRType7.Number;
|
|
2557
|
+
break;
|
|
2558
|
+
}
|
|
2559
|
+
case OpCode10.Lt:
|
|
2560
|
+
case OpCode10.Gt:
|
|
2561
|
+
case OpCode10.LtEq:
|
|
2562
|
+
case OpCode10.GtEq:
|
|
2563
|
+
case OpCode10.Eq:
|
|
2564
|
+
case OpCode10.StrictEq:
|
|
2565
|
+
case OpCode10.In:
|
|
2566
|
+
case OpCode10.InstanceOf:
|
|
2567
|
+
case OpCode10.Not: {
|
|
2568
|
+
newType = IRType7.Boolean;
|
|
2569
|
+
break;
|
|
2570
|
+
}
|
|
2571
|
+
case OpCode10.LoadLocal: {
|
|
2572
|
+
const op = inst.operands[0];
|
|
2573
|
+
if (op && op.kind === OperandKind12.Register) {
|
|
2574
|
+
newType = types.get(op.value) ?? IRType7.Any;
|
|
2575
|
+
}
|
|
2576
|
+
break;
|
|
2577
|
+
}
|
|
2578
|
+
default:
|
|
2579
|
+
newType = IRType7.Any;
|
|
2580
|
+
break;
|
|
2581
|
+
}
|
|
2582
|
+
const oldType = types.get(inst.result);
|
|
2583
|
+
if (oldType !== newType) {
|
|
2584
|
+
types.set(inst.result, newType);
|
|
2585
|
+
changed = true;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
iterations++;
|
|
2590
|
+
}
|
|
2591
|
+
return types;
|
|
2592
|
+
}
|
|
2593
|
+
var InstructionSubstitutionPass = class {
|
|
2594
|
+
name = "InstructionSubstitutionPass";
|
|
2595
|
+
priority = 20;
|
|
2596
|
+
execute(ctx) {
|
|
2597
|
+
let nodesTransformed = 0;
|
|
2598
|
+
const constantPool = [...ctx.module.constantPool];
|
|
2599
|
+
const getOrAddNumberConstant = (val) => {
|
|
2600
|
+
let idx = constantPool.findIndex((c) => c.kind === ConstantKind9.Number && c.value === val);
|
|
2601
|
+
if (idx === -1) {
|
|
2602
|
+
idx = constantPool.length;
|
|
2603
|
+
constantPool.push({ index: idx, kind: ConstantKind9.Number, value: val });
|
|
2604
|
+
}
|
|
2605
|
+
return idx;
|
|
2606
|
+
};
|
|
2607
|
+
const newFunctions = ctx.module.functions.map((func) => {
|
|
2608
|
+
if (!func.isVirtualized) return func;
|
|
2609
|
+
const regTypes = inferRegisterTypes(func.blocks, func.params, func.locals, constantPool);
|
|
2610
|
+
let nextReg = getMaxRegister(func);
|
|
2611
|
+
let changed = false;
|
|
2612
|
+
const newBlocks = func.blocks.map((block) => {
|
|
2613
|
+
const newInstructions = [];
|
|
2614
|
+
for (const inst of block.instructions) {
|
|
2615
|
+
if (ctx.rng.nextFloat() >= 0.4) {
|
|
2616
|
+
newInstructions.push(inst);
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
const getOperandType = (op) => {
|
|
2620
|
+
if (op.kind === OperandKind12.Register) {
|
|
2621
|
+
return regTypes.get(op.value) ?? IRType7.Any;
|
|
2622
|
+
}
|
|
2623
|
+
if (op.kind === OperandKind12.ConstantIndex) {
|
|
2624
|
+
const entry = constantPool[op.value];
|
|
2625
|
+
if (entry) {
|
|
2626
|
+
if (entry.kind === ConstantKind9.Number) return IRType7.Number;
|
|
2627
|
+
if (entry.kind === ConstantKind9.String || entry.kind === ConstantKind9.Template) return IRType7.String;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
return IRType7.Any;
|
|
2631
|
+
};
|
|
2632
|
+
if (inst.opcode === OpCode10.Add && inst.operands.length === 2 && inst.result) {
|
|
2633
|
+
const opA = inst.operands[0];
|
|
2634
|
+
const opB = inst.operands[1];
|
|
2635
|
+
const typeA = getOperandType(opA);
|
|
2636
|
+
const typeB = getOperandType(opB);
|
|
2637
|
+
if (typeA === IRType7.Number && typeB === IRType7.Number) {
|
|
2638
|
+
const temp1 = `r${nextReg++}`;
|
|
2639
|
+
const temp2 = `r${nextReg++}`;
|
|
2640
|
+
const tempConst2 = `r${nextReg++}`;
|
|
2641
|
+
const temp3 = `r${nextReg++}`;
|
|
2642
|
+
newInstructions.push(
|
|
2643
|
+
{ opcode: OpCode10.BitXor, operands: [opA, opB], result: temp1 },
|
|
2644
|
+
{ opcode: OpCode10.BitAnd, operands: [opA, opB], result: temp2 },
|
|
2645
|
+
{
|
|
2646
|
+
opcode: OpCode10.LoadConst,
|
|
2647
|
+
operands: [{ kind: OperandKind12.ConstantIndex, value: getOrAddNumberConstant(2) }],
|
|
2648
|
+
result: tempConst2
|
|
2649
|
+
},
|
|
2650
|
+
{
|
|
2651
|
+
opcode: OpCode10.Mul,
|
|
2652
|
+
operands: [
|
|
2653
|
+
{ kind: OperandKind12.Register, value: temp2 },
|
|
2654
|
+
{ kind: OperandKind12.Register, value: tempConst2 }
|
|
2655
|
+
],
|
|
2656
|
+
result: temp3
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
opcode: OpCode10.Add,
|
|
2660
|
+
operands: [
|
|
2661
|
+
{ kind: OperandKind12.Register, value: temp1 },
|
|
2662
|
+
{ kind: OperandKind12.Register, value: temp3 }
|
|
2663
|
+
],
|
|
2664
|
+
result: inst.result
|
|
2665
|
+
}
|
|
2666
|
+
);
|
|
2667
|
+
nodesTransformed++;
|
|
2668
|
+
changed = true;
|
|
2669
|
+
} else {
|
|
2670
|
+
newInstructions.push(inst);
|
|
2671
|
+
}
|
|
2672
|
+
} else if (inst.opcode === OpCode10.BitAnd && inst.operands.length === 2 && inst.result) {
|
|
2673
|
+
const opA = inst.operands[0];
|
|
2674
|
+
const opB = inst.operands[1];
|
|
2675
|
+
const typeA = getOperandType(opA);
|
|
2676
|
+
const typeB = getOperandType(opB);
|
|
2677
|
+
if (typeA === IRType7.Number && typeB === IRType7.Number) {
|
|
2678
|
+
const temp1 = `r${nextReg++}`;
|
|
2679
|
+
const temp2 = `r${nextReg++}`;
|
|
2680
|
+
newInstructions.push(
|
|
2681
|
+
{ opcode: OpCode10.BitOr, operands: [opA, opB], result: temp1 },
|
|
2682
|
+
{ opcode: OpCode10.BitXor, operands: [opA, opB], result: temp2 },
|
|
2683
|
+
{
|
|
2684
|
+
opcode: OpCode10.Sub,
|
|
2685
|
+
operands: [
|
|
2686
|
+
{ kind: OperandKind12.Register, value: temp1 },
|
|
2687
|
+
{ kind: OperandKind12.Register, value: temp2 }
|
|
2688
|
+
],
|
|
2689
|
+
result: inst.result
|
|
2690
|
+
}
|
|
2691
|
+
);
|
|
2692
|
+
nodesTransformed++;
|
|
2693
|
+
changed = true;
|
|
2694
|
+
} else {
|
|
2695
|
+
newInstructions.push(inst);
|
|
2696
|
+
}
|
|
2697
|
+
} else if (inst.opcode === OpCode10.BitOr && inst.operands.length === 2 && inst.result) {
|
|
2698
|
+
const opA = inst.operands[0];
|
|
2699
|
+
const opB = inst.operands[1];
|
|
2700
|
+
const typeA = getOperandType(opA);
|
|
2701
|
+
const typeB = getOperandType(opB);
|
|
2702
|
+
if (typeA === IRType7.Number && typeB === IRType7.Number) {
|
|
2703
|
+
const temp1 = `r${nextReg++}`;
|
|
2704
|
+
const temp2 = `r${nextReg++}`;
|
|
2705
|
+
newInstructions.push(
|
|
2706
|
+
{ opcode: OpCode10.BitAnd, operands: [opA, opB], result: temp1 },
|
|
2707
|
+
{ opcode: OpCode10.BitXor, operands: [opA, opB], result: temp2 },
|
|
2708
|
+
{
|
|
2709
|
+
opcode: OpCode10.Add,
|
|
2710
|
+
operands: [
|
|
2711
|
+
{ kind: OperandKind12.Register, value: temp1 },
|
|
2712
|
+
{ kind: OperandKind12.Register, value: temp2 }
|
|
2713
|
+
],
|
|
2714
|
+
result: inst.result
|
|
2715
|
+
}
|
|
2716
|
+
);
|
|
2717
|
+
nodesTransformed++;
|
|
2718
|
+
changed = true;
|
|
2719
|
+
} else {
|
|
2720
|
+
newInstructions.push(inst);
|
|
2721
|
+
}
|
|
2722
|
+
} else if (inst.opcode === OpCode10.BitXor && inst.operands.length === 2 && inst.result) {
|
|
2723
|
+
const opA = inst.operands[0];
|
|
2724
|
+
const opB = inst.operands[1];
|
|
2725
|
+
const typeA = getOperandType(opA);
|
|
2726
|
+
const typeB = getOperandType(opB);
|
|
2727
|
+
if (typeA === IRType7.Number && typeB === IRType7.Number) {
|
|
2728
|
+
const temp1 = `r${nextReg++}`;
|
|
2729
|
+
const temp2 = `r${nextReg++}`;
|
|
2730
|
+
newInstructions.push(
|
|
2731
|
+
{ opcode: OpCode10.BitOr, operands: [opA, opB], result: temp1 },
|
|
2732
|
+
{ opcode: OpCode10.BitAnd, operands: [opA, opB], result: temp2 },
|
|
2733
|
+
{
|
|
2734
|
+
opcode: OpCode10.Sub,
|
|
2735
|
+
operands: [
|
|
2736
|
+
{ kind: OperandKind12.Register, value: temp1 },
|
|
2737
|
+
{ kind: OperandKind12.Register, value: temp2 }
|
|
2738
|
+
],
|
|
2739
|
+
result: inst.result
|
|
2740
|
+
}
|
|
2741
|
+
);
|
|
2742
|
+
nodesTransformed++;
|
|
2743
|
+
changed = true;
|
|
2744
|
+
} else {
|
|
2745
|
+
newInstructions.push(inst);
|
|
2746
|
+
}
|
|
2747
|
+
} else if (inst.opcode === OpCode10.Neg && inst.operands.length === 1 && inst.result) {
|
|
2748
|
+
const opA = inst.operands[0];
|
|
2749
|
+
const typeA = getOperandType(opA);
|
|
2750
|
+
if (typeA === IRType7.Number) {
|
|
2751
|
+
const tempConstNeg1 = `r${nextReg++}`;
|
|
2752
|
+
newInstructions.push(
|
|
2753
|
+
{
|
|
2754
|
+
opcode: OpCode10.LoadConst,
|
|
2755
|
+
operands: [{ kind: OperandKind12.ConstantIndex, value: getOrAddNumberConstant(-1) }],
|
|
2756
|
+
result: tempConstNeg1
|
|
2757
|
+
},
|
|
2758
|
+
{
|
|
2759
|
+
opcode: OpCode10.Mul,
|
|
2760
|
+
operands: [opA, { kind: OperandKind12.Register, value: tempConstNeg1 }],
|
|
2761
|
+
result: inst.result
|
|
2762
|
+
}
|
|
2763
|
+
);
|
|
2764
|
+
nodesTransformed++;
|
|
2765
|
+
changed = true;
|
|
2766
|
+
} else {
|
|
2767
|
+
newInstructions.push(inst);
|
|
2768
|
+
}
|
|
2769
|
+
} else {
|
|
2770
|
+
newInstructions.push(inst);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
return { ...block, instructions: newInstructions };
|
|
2774
|
+
});
|
|
2775
|
+
if (!changed) return func;
|
|
2776
|
+
const newLocals = [...func.locals];
|
|
2777
|
+
const maxOriginalReg = getMaxRegister(func);
|
|
2778
|
+
for (let reg = maxOriginalReg; reg < nextReg; reg++) {
|
|
2779
|
+
newLocals.push({
|
|
2780
|
+
name: `isub_temp_r${reg}`,
|
|
2781
|
+
register: `r${reg}`,
|
|
2782
|
+
type: IRType7.Any,
|
|
2783
|
+
isCaptured: false
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
return { ...func, blocks: newBlocks, locals: newLocals };
|
|
2787
|
+
});
|
|
2788
|
+
return {
|
|
2789
|
+
module: { ...ctx.module, functions: newFunctions, constantPool },
|
|
2790
|
+
symbolsRenamed: 0,
|
|
2791
|
+
nodesTransformed,
|
|
2792
|
+
diagnostics: []
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
|
|
2797
|
+
// src/registry.ts
|
|
2798
|
+
var TransformRegistry = class {
|
|
2799
|
+
passes = /* @__PURE__ */ new Map();
|
|
2800
|
+
constructor(profile) {
|
|
2801
|
+
const availablePasses = {
|
|
2802
|
+
PreserveTypeIllusionsPass,
|
|
2803
|
+
GenericConfusionPass,
|
|
2804
|
+
DecoratorAwareLoweringPass,
|
|
2805
|
+
NamespaceVirtualizationPass,
|
|
2806
|
+
TypeLevelFakePathPass,
|
|
2807
|
+
SymbolIndirectionPass,
|
|
2808
|
+
StringPoolEncodingPass,
|
|
2809
|
+
FunctionVirtualizationPass,
|
|
2810
|
+
DeadCodeInjectionPass,
|
|
2811
|
+
ControlFlowFlatteningPass,
|
|
2812
|
+
StripDebugPass,
|
|
2813
|
+
IRValidationPass,
|
|
2814
|
+
RegisterCompactingPass,
|
|
2815
|
+
InstructionSubstitutionPass
|
|
2816
|
+
};
|
|
2817
|
+
for (const passConfig of profile.transforms) {
|
|
2818
|
+
if (passConfig.enabled && availablePasses[passConfig.name]) {
|
|
2819
|
+
this.addPass(new availablePasses[passConfig.name]());
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
addPass(pass) {
|
|
2824
|
+
this.passes.set(pass.name, pass);
|
|
2825
|
+
}
|
|
2826
|
+
removePass(name) {
|
|
2827
|
+
this.passes.delete(name);
|
|
2828
|
+
}
|
|
2829
|
+
getPass(name) {
|
|
2830
|
+
return this.passes.get(name);
|
|
2831
|
+
}
|
|
2832
|
+
getOrderedPasses() {
|
|
2833
|
+
return Array.from(this.passes.values()).sort((a, b) => a.priority - b.priority);
|
|
2834
|
+
}
|
|
2835
|
+
};
|
|
2836
|
+
function createTransformRegistry(profile) {
|
|
2837
|
+
return new TransformRegistry(profile);
|
|
2838
|
+
}
|
|
2839
|
+
export {
|
|
2840
|
+
ControlFlowFlatteningPass,
|
|
2841
|
+
DeadCodeInjectionPass,
|
|
2842
|
+
DecoratorAwareLoweringPass,
|
|
2843
|
+
FunctionVirtualizationPass,
|
|
2844
|
+
GenericConfusionPass,
|
|
2845
|
+
InstructionSubstitutionPass,
|
|
2846
|
+
NamespaceVirtualizationPass,
|
|
2847
|
+
PreserveTypeIllusionsPass,
|
|
2848
|
+
StringPoolEncodingPass,
|
|
2849
|
+
StripDebugPass,
|
|
2850
|
+
SymbolIndirectionPass,
|
|
2851
|
+
TransformRegistry,
|
|
2852
|
+
TypeLevelFakePathPass,
|
|
2853
|
+
createTransformRegistry
|
|
2854
|
+
};
|