@tsvm/bytecode 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.
@@ -0,0 +1,14 @@
1
+ import { OpcodeMapping, IRModule, VMBuildConfig, BytecodeModule, Instruction, SeededRandom, ConstantPoolEntry, ConstantEncodingScheme, EncodedConstant } from '@tsvm/shared';
2
+
3
+ declare function generateRemappedOpcodes(seed: number): OpcodeMapping;
4
+
5
+ declare function compileToBytecode(irModule: IRModule, config: VMBuildConfig): BytecodeModule;
6
+
7
+ declare function encodeBytecode(instructions: Instruction[], mapping: OpcodeMapping, config: VMBuildConfig, rng?: SeededRandom): Uint8Array;
8
+ declare function encodeConstantPool(constants: readonly ConstantPoolEntry[], scheme: ConstantEncodingScheme, seed: number): EncodedConstant[];
9
+
10
+ declare function decodeBytecode(bytes: Uint8Array, mapping: OpcodeMapping, config: VMBuildConfig): Instruction[];
11
+ declare function decodeConstantPool(encoded: EncodedConstant[], scheme: ConstantEncodingScheme, seed: number): ConstantPoolEntry[];
12
+ declare function disassemble(module: BytecodeModule): string;
13
+
14
+ export { compileToBytecode, decodeBytecode, decodeConstantPool, disassemble, encodeBytecode, encodeConstantPool, generateRemappedOpcodes };
package/dist/index.js ADDED
@@ -0,0 +1,1021 @@
1
+ // src/opcodes.ts
2
+ import { OpCode, SeededRandom } from "@tsvm/shared";
3
+ function generateRemappedOpcodes(seed) {
4
+ const forward = /* @__PURE__ */ new Map();
5
+ const reverse = /* @__PURE__ */ new Map();
6
+ const rng = new SeededRandom(seed);
7
+ const opcodes = [];
8
+ for (const key in OpCode) {
9
+ if (!Number.isNaN(Number(key))) {
10
+ opcodes.push(Number(key));
11
+ }
12
+ }
13
+ const availableSlots = [];
14
+ for (let i = 0; i < 256; i++) {
15
+ availableSlots.push(i);
16
+ }
17
+ rng.shuffle(availableSlots);
18
+ for (let i = 0; i < opcodes.length; i++) {
19
+ const canonical = opcodes[i];
20
+ const slot = availableSlots.pop();
21
+ const mapped = [slot];
22
+ reverse.set(slot, canonical);
23
+ forward.set(canonical, mapped);
24
+ }
25
+ while (availableSlots.length > 0) {
26
+ const slot = availableSlots.pop();
27
+ const randomOpcode = opcodes[rng.nextRange(0, opcodes.length - 1)];
28
+ const mapped = forward.get(randomOpcode);
29
+ mapped.push(slot);
30
+ reverse.set(slot, randomOpcode);
31
+ }
32
+ return { seed, forward, reverse };
33
+ }
34
+
35
+ // src/compiler.ts
36
+ import { FunctionAttribute, OpCode as OpCode3, OperandKind as OperandKind2, SeededRandom as SeededRandom2 } from "@tsvm/shared";
37
+
38
+ // src/encoder.ts
39
+ import { ImmediateEncodingScheme, ConstantEncodingScheme, OperandKind, OpCode as OpCode2 } from "@tsvm/shared";
40
+ function isVariableLengthOpcode(opcode) {
41
+ return opcode === OpCode2.Call || opcode === OpCode2.CallMethod || opcode === OpCode2.New || opcode === OpCode2.ArrayNew || opcode === OpCode2.ObjectNew || opcode === OpCode2.Spread || opcode === OpCode2.SpreadIntoArray || opcode === OpCode2.SuperCall || opcode === OpCode2.SuperInstruction;
42
+ }
43
+ function isTerminator(opcode) {
44
+ return opcode === OpCode2.Jmp || opcode === OpCode2.JmpIf || opcode === OpCode2.JmpIfNot || opcode === OpCode2.Return || opcode === OpCode2.ReturnVoid || opcode === OpCode2.Throw || opcode === OpCode2.Yield || opcode === OpCode2.YieldStar || opcode === OpCode2.Await || opcode === OpCode2.Halt || opcode === OpCode2.Trap;
45
+ }
46
+ function encodeBytecode(instructions, mapping, config, rng) {
47
+ const bytes = [];
48
+ let currentHandlerIdx = 0;
49
+ for (const inst of instructions) {
50
+ let mappedOp = inst.mappedOp ?? inst.opcode;
51
+ if (inst.mappedOp === void 0) {
52
+ const forward = mapping.forward.get(inst.opcode);
53
+ if (Array.isArray(forward)) {
54
+ if (!rng) throw new Error("SeededRandom required when opcodeMap is provided");
55
+ mappedOp = forward[Math.floor(rng.next() * forward.length)];
56
+ } else if (forward !== void 0) {
57
+ mappedOp = forward;
58
+ }
59
+ }
60
+ if (config.stealthDispatch) {
61
+ const delta = mappedOp - currentHandlerIdx + 256 & 255;
62
+ bytes.push(delta);
63
+ currentHandlerIdx = isTerminator(inst.opcode) ? 0 : mappedOp;
64
+ } else {
65
+ bytes.push(mappedOp);
66
+ }
67
+ if (config.junkInsertion) {
68
+ const numJunk = (inst.opcode * 7 + config.seed) % 4;
69
+ for (let j = 0; j < numJunk; j++) {
70
+ if (!rng) throw new Error("SeededRandom required for junk byte injection");
71
+ const junk = Math.floor(rng.next() * 256);
72
+ bytes.push(junk);
73
+ }
74
+ }
75
+ const ops = [...inst.operands || []];
76
+ if (inst.result) {
77
+ ops.push({ kind: OperandKind.Register, value: inst.result });
78
+ }
79
+ const isVarLength = isVariableLengthOpcode(inst.opcode);
80
+ if (isVarLength) {
81
+ bytes.push(ops.length);
82
+ }
83
+ for (const op of ops) {
84
+ const kindNum = operandKindToNum(op.kind);
85
+ bytes.push(kindNum);
86
+ let val = 0;
87
+ if (typeof op.value === "string" && op.value.startsWith("r")) {
88
+ val = Number.parseInt(op.value.substring(1), 10);
89
+ } else if (typeof op.value === "number") {
90
+ val = op.value;
91
+ }
92
+ if (config.immediateEncoding === ImmediateEncodingScheme.VariableLength) {
93
+ let v = val;
94
+ do {
95
+ let byte = v & 127;
96
+ v >>>= 7;
97
+ if (v !== 0) byte |= 128;
98
+ bytes.push(byte);
99
+ } while (v !== 0);
100
+ } else {
101
+ const b0 = val & 255;
102
+ const b1 = val >> 8 & 255;
103
+ const b2 = val >> 16 & 255;
104
+ const b3 = val >> 24 & 255;
105
+ bytes.push(b0, b1, b2, b3);
106
+ }
107
+ }
108
+ }
109
+ const resultBytes = new Uint8Array(bytes);
110
+ if (config.rollingKeys) {
111
+ for (let pc = 0; pc < resultBytes.length; pc++) {
112
+ const rawVal = resultBytes[pc];
113
+ const offsetVal = rawVal + pc & 255;
114
+ const rollingKey = (config.seed ^ pc * 2654435769) >>> 8 & 255;
115
+ resultBytes[pc] = offsetVal ^ rollingKey;
116
+ }
117
+ }
118
+ return resultBytes;
119
+ }
120
+ function operandKindToNum(kind) {
121
+ if (typeof kind === "number") return kind;
122
+ switch (kind) {
123
+ case OperandKind.Register:
124
+ case "register":
125
+ return 0;
126
+ case OperandKind.Immediate:
127
+ case "immediate":
128
+ return 1;
129
+ case OperandKind.ConstantIndex:
130
+ case "constant_index":
131
+ return 2;
132
+ case OperandKind.BlockLabel:
133
+ case "block_label":
134
+ return 3;
135
+ case OperandKind.FunctionRef:
136
+ case "function_ref":
137
+ return 4;
138
+ default:
139
+ throw new Error(`Unrecognized operand kind: ${kind}`);
140
+ }
141
+ }
142
+ function encodeConstantPool(constants, scheme, seed) {
143
+ return constants.map((c, index) => {
144
+ if (scheme === ConstantEncodingScheme.XorRotate && typeof c.value === "string") {
145
+ let stringSeed = (seed ^ index * 2654435769) & 4294967295;
146
+ if (c.expectedPathHash !== void 0) {
147
+ stringSeed = (stringSeed ^ c.expectedPathHash) & 4294967295;
148
+ }
149
+ const keyBytes = new Uint8Array(16);
150
+ let s = stringSeed;
151
+ for (let i = 0; i < 16; i++) {
152
+ s = Math.imul(s, 1664525) + 1013904223;
153
+ keyBytes[i] = s >>> 16 & 255;
154
+ }
155
+ const S = new Uint8Array(256);
156
+ for (let i = 0; i < 256; i++) S[i] = i;
157
+ let j = 0;
158
+ for (let i = 0; i < 256; i++) {
159
+ j = j + S[i] + keyBytes[i % 16] & 255;
160
+ const temp = S[i];
161
+ S[i] = S[j];
162
+ S[j] = temp;
163
+ }
164
+ let ri = 0;
165
+ j = 0;
166
+ for (let skip = 0; skip < 256; skip++) {
167
+ ri = ri + 1 & 255;
168
+ j = j + S[ri] & 255;
169
+ const temp = S[ri];
170
+ S[ri] = S[j];
171
+ S[j] = temp;
172
+ }
173
+ let encoded = "";
174
+ for (let i = 0; i < c.value.length; i++) {
175
+ ri = ri + 1 & 255;
176
+ j = j + S[ri] & 255;
177
+ const temp = S[ri];
178
+ S[ri] = S[j];
179
+ S[j] = temp;
180
+ const keystreamByte = S[S[ri] + S[j] & 255];
181
+ encoded += String.fromCharCode(c.value.charCodeAt(i) ^ keystreamByte);
182
+ }
183
+ return { index, kind: c.kind, value: encoded, encodedBytes: new Uint8Array(), decodingKey: 0 };
184
+ }
185
+ return { index, kind: c.kind, value: c.value, encodedBytes: new Uint8Array(), decodingKey: 0 };
186
+ });
187
+ }
188
+
189
+ // src/compiler.ts
190
+ function collectMaxRegisterIndex(irFn) {
191
+ let maxRegister = -1;
192
+ const consider = (value) => {
193
+ if (typeof value !== "string") {
194
+ return;
195
+ }
196
+ const match = /^r(\d+)$/.exec(value);
197
+ if (!match) {
198
+ return;
199
+ }
200
+ maxRegister = Math.max(maxRegister, Number.parseInt(match[1], 10));
201
+ };
202
+ for (const param of irFn.params) {
203
+ consider(param.register);
204
+ }
205
+ for (const local of irFn.locals) {
206
+ consider(local.register);
207
+ }
208
+ for (const block of irFn.blocks) {
209
+ if (block.phiNodes) {
210
+ for (const phi of block.phiNodes) {
211
+ consider(phi.result);
212
+ for (const incoming of phi.incoming) {
213
+ consider(incoming.register);
214
+ }
215
+ }
216
+ }
217
+ for (const inst of block.instructions) {
218
+ consider(inst.result);
219
+ for (const operand of inst.operands) {
220
+ if (operand.kind === OperandKind2.Register) {
221
+ consider(operand.value);
222
+ }
223
+ }
224
+ }
225
+ consider(block.terminator.condition);
226
+ consider(block.terminator.returnValue);
227
+ }
228
+ return maxRegister + 1;
229
+ }
230
+ function computeSourceHash(irModule) {
231
+ let hash = 2166136261;
232
+ const update = (val) => {
233
+ hash ^= val & 255;
234
+ hash = Math.imul(hash, 16777619);
235
+ };
236
+ for (const fn of irModule.functions) {
237
+ for (const block of fn.blocks) {
238
+ for (const inst of block.instructions) {
239
+ update(inst.opcode);
240
+ for (const op of inst.operands) {
241
+ if (typeof op.kind === "number") update(op.kind);
242
+ if (typeof op.value === "number") {
243
+ update(op.value & 255);
244
+ update(op.value >> 8 & 255);
245
+ update(op.value >> 16 & 255);
246
+ update(op.value >> 24 & 255);
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ return (hash >>> 0).toString(16).padStart(8, "0");
253
+ }
254
+ function isVariableLengthOpcode2(opcode) {
255
+ return opcode === 64 || // OpCode.Call
256
+ opcode === 65 || // OpCode.CallMethod
257
+ opcode === 66 || // OpCode.New
258
+ opcode === 84 || // OpCode.ArrayNew
259
+ opcode === 85 || // OpCode.ObjectNew
260
+ opcode === 86 || // OpCode.Spread
261
+ opcode === 87 || // OpCode.SpreadIntoArray
262
+ opcode === 94 || // OpCode.SuperCall
263
+ opcode === 254;
264
+ }
265
+ function isTerminator2(opcode) {
266
+ return opcode === OpCode3.Jmp || opcode === OpCode3.JmpIf || opcode === OpCode3.JmpIfNot || opcode === OpCode3.Return || opcode === OpCode3.ReturnVoid || opcode === OpCode3.Throw || opcode === OpCode3.Yield || opcode === OpCode3.YieldStar || opcode === OpCode3.Await || opcode === OpCode3.Halt || opcode === OpCode3.Trap;
267
+ }
268
+ function fuseInstructions(insts, config) {
269
+ if (!config.superInstructions) return insts;
270
+ const rng = new SeededRandom2(config.seed);
271
+ const ids = rng.shuffle([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
272
+ const loadConstMulId = ids[0];
273
+ const getEntropyMulId = ids[1];
274
+ const loadConstAddId = ids[2];
275
+ const fused = [];
276
+ let i = 0;
277
+ while (i < insts.length) {
278
+ const inst1 = insts[i];
279
+ const inst2 = insts[i + 1];
280
+ if (inst2) {
281
+ if (inst1.opcode === OpCode3.LoadConst && inst2.opcode === OpCode3.Mul && inst1.operands[0]?.kind === OperandKind2.ConstantIndex && inst1.result && inst2.result) {
282
+ let matched = false;
283
+ let regX;
284
+ if (inst2.operands[0]?.kind === OperandKind2.Register && inst2.operands[0].value === inst1.result) {
285
+ regX = inst2.operands[1];
286
+ matched = true;
287
+ } else if (inst2.operands[1]?.kind === OperandKind2.Register && inst2.operands[1].value === inst1.result) {
288
+ regX = inst2.operands[0];
289
+ matched = true;
290
+ }
291
+ if (matched && regX) {
292
+ fused.push({
293
+ opcode: OpCode3.SuperInstruction,
294
+ operands: [
295
+ { kind: OperandKind2.Immediate, value: loadConstMulId },
296
+ inst1.operands[0],
297
+ // constIdx
298
+ { kind: OperandKind2.Register, value: inst1.result },
299
+ // regT
300
+ regX,
301
+ // regX
302
+ { kind: OperandKind2.Register, value: inst2.result }
303
+ // regR
304
+ ]
305
+ });
306
+ i += 2;
307
+ continue;
308
+ }
309
+ }
310
+ if (inst1.opcode === OpCode3.GetEntropy && inst2.opcode === OpCode3.Mul && inst1.result && inst2.operands[0]?.kind === OperandKind2.Register && inst2.operands[0].value === inst1.result && inst2.operands[1]?.kind === OperandKind2.Register && inst2.operands[1].value === inst1.result && inst2.result) {
311
+ fused.push({
312
+ opcode: OpCode3.SuperInstruction,
313
+ operands: [
314
+ { kind: OperandKind2.Immediate, value: getEntropyMulId },
315
+ { kind: OperandKind2.Register, value: inst1.result },
316
+ // regT
317
+ { kind: OperandKind2.Register, value: inst2.result }
318
+ // regR
319
+ ]
320
+ });
321
+ i += 2;
322
+ continue;
323
+ }
324
+ if (inst1.opcode === OpCode3.LoadConst && inst2.opcode === OpCode3.Add && inst1.operands[0]?.kind === OperandKind2.ConstantIndex && inst1.result && inst2.operands[0]?.kind === OperandKind2.Register && inst2.operands[0].value === inst1.result && inst2.operands[1]?.kind === OperandKind2.Register && inst2.result) {
325
+ fused.push({
326
+ opcode: OpCode3.SuperInstruction,
327
+ operands: [
328
+ { kind: OperandKind2.Immediate, value: loadConstAddId },
329
+ inst1.operands[0],
330
+ // constIdx
331
+ { kind: OperandKind2.Register, value: inst1.result },
332
+ // regT
333
+ inst2.operands[1],
334
+ // regX (second operand)
335
+ { kind: OperandKind2.Register, value: inst2.result }
336
+ // regR
337
+ ]
338
+ });
339
+ i += 2;
340
+ continue;
341
+ }
342
+ }
343
+ fused.push(inst1);
344
+ i++;
345
+ }
346
+ return fused;
347
+ }
348
+ function compileToBytecode(irModule, config) {
349
+ const mapping = generateRemappedOpcodes(config.seed);
350
+ const functions = [];
351
+ const rng = new SeededRandom2(config.seed);
352
+ const duplicatedCP = [];
353
+ const tempFunctions = [];
354
+ for (const irFn of irModule.functions) {
355
+ if (irFn.isVirtualized) {
356
+ const paramCount = irFn.params.length;
357
+ const maxRegs = Math.max(collectMaxRegisterIndex(irFn), paramCount);
358
+ const regIds = Array.from({ length: maxRegs - paramCount }, (_, i) => i + paramCount);
359
+ const shuffledRegIds = rng.shuffle([...regIds]);
360
+ const originalToShuffledReg = /* @__PURE__ */ new Map();
361
+ regIds.forEach((origId, shuffledIdx) => {
362
+ originalToShuffledReg.set(origId, shuffledRegIds[shuffledIdx]);
363
+ });
364
+ const mapReg = (regStr) => {
365
+ if (typeof regStr !== "string") return regStr;
366
+ const match = /^r(\d+)$/.exec(regStr);
367
+ if (match) {
368
+ const origId = Number.parseInt(match[1], 10);
369
+ if (origId < paramCount) {
370
+ return regStr;
371
+ }
372
+ const newId = originalToShuffledReg.get(origId) ?? origId;
373
+ return `r${newId}`;
374
+ }
375
+ return regStr;
376
+ };
377
+ const blockInstIndices = /* @__PURE__ */ new Map();
378
+ const flatInsts = [];
379
+ for (const block of irFn.blocks) {
380
+ blockInstIndices.set(block.id, flatInsts.length);
381
+ const mappedInstructions = block.instructions.map((inst) => {
382
+ const mappedOperands = inst.operands.map((op) => {
383
+ if (op.kind === OperandKind2.Register) {
384
+ return { ...op, value: mapReg(op.value) };
385
+ }
386
+ if (op.kind === OperandKind2.ConstantIndex) {
387
+ return { ...op };
388
+ }
389
+ return op;
390
+ });
391
+ return {
392
+ ...inst,
393
+ result: inst.result ? mapReg(inst.result) : void 0,
394
+ operands: mappedOperands
395
+ };
396
+ });
397
+ const fusedInstructions = fuseInstructions(mappedInstructions, config);
398
+ flatInsts.push(...fusedInstructions);
399
+ if (block.terminator) {
400
+ if (block.terminator.kind === "jump") {
401
+ flatInsts.push({
402
+ opcode: OpCode3.Jmp,
403
+ operands: [{ kind: OperandKind2.BlockLabel, value: block.terminator.targets[0] }]
404
+ });
405
+ } else if (block.terminator.kind === "branch") {
406
+ flatInsts.push({
407
+ opcode: OpCode3.JmpIf,
408
+ operands: [
409
+ { kind: OperandKind2.Register, value: mapReg(block.terminator.condition) },
410
+ { kind: OperandKind2.BlockLabel, value: block.terminator.targets[0] },
411
+ { kind: OperandKind2.BlockLabel, value: block.terminator.targets[1] }
412
+ ]
413
+ });
414
+ } else if (block.terminator.kind === "return") {
415
+ const ops = block.terminator.returnValue ? [{ kind: OperandKind2.Register, value: mapReg(block.terminator.returnValue) }] : [];
416
+ flatInsts.push({
417
+ opcode: OpCode3.Return,
418
+ operands: ops
419
+ });
420
+ } else if (block.terminator.kind === "throw") {
421
+ const ops = block.terminator.returnValue ? [{ kind: OperandKind2.Register, value: mapReg(block.terminator.returnValue) }] : [];
422
+ flatInsts.push({
423
+ opcode: OpCode3.Throw,
424
+ operands: ops
425
+ });
426
+ }
427
+ }
428
+ }
429
+ for (const inst of flatInsts) {
430
+ let mappedOp = inst.opcode;
431
+ const forward = mapping.forward.get(inst.opcode);
432
+ if (Array.isArray(forward)) {
433
+ mappedOp = forward[Math.floor(rng.next() * forward.length)];
434
+ } else if (forward !== void 0) {
435
+ mappedOp = forward;
436
+ }
437
+ inst.mappedOp = mappedOp;
438
+ }
439
+ let expectedPathHash = 0;
440
+ for (const inst of flatInsts) {
441
+ expectedPathHash = Math.imul(expectedPathHash, 31) + inst.mappedOp & 4294967295;
442
+ for (const op of inst.operands) {
443
+ if (op.kind === OperandKind2.ConstantIndex) {
444
+ const origIdx = op.value;
445
+ const origEntry = irModule.constantPool[origIdx];
446
+ const newIdx = duplicatedCP.length;
447
+ duplicatedCP.push({
448
+ ...origEntry,
449
+ index: newIdx,
450
+ expectedPathHash: config.rollingKeys ? expectedPathHash : void 0
451
+ });
452
+ op.value = newIdx;
453
+ }
454
+ }
455
+ if (isTerminator2(inst.opcode)) {
456
+ expectedPathHash = 0;
457
+ }
458
+ }
459
+ tempFunctions.push({
460
+ irFn,
461
+ flatInsts,
462
+ blockInstIndices,
463
+ maxRegs
464
+ });
465
+ }
466
+ }
467
+ const cpLength = duplicatedCP.length;
468
+ const originalIndices = Array.from({ length: cpLength }, (_, i) => i);
469
+ const shuffledIndices = rng.shuffle([...originalIndices]);
470
+ const duplicatedToShuffled = /* @__PURE__ */ new Map();
471
+ shuffledIndices.forEach((origIdx, shuffledIdx) => {
472
+ duplicatedToShuffled.set(origIdx, shuffledIdx);
473
+ });
474
+ const shuffledCP = new Array(cpLength);
475
+ shuffledIndices.forEach((origIdx, shuffledIdx) => {
476
+ shuffledCP[shuffledIdx] = {
477
+ ...duplicatedCP[origIdx],
478
+ index: shuffledIdx
479
+ };
480
+ });
481
+ for (const temp of tempFunctions) {
482
+ const { irFn, flatInsts, blockInstIndices, maxRegs } = temp;
483
+ for (const inst of flatInsts) {
484
+ for (const op of inst.operands) {
485
+ if (op.kind === OperandKind2.ConstantIndex) {
486
+ const dupIdx = op.value;
487
+ op.value = duplicatedToShuffled.get(dupIdx) ?? dupIdx;
488
+ }
489
+ }
490
+ }
491
+ const instByteOffset = new Array(flatInsts.length).fill(0);
492
+ let changed = true;
493
+ while (changed) {
494
+ changed = false;
495
+ let currentOffset = 0;
496
+ for (let i = 0; i < flatInsts.length; i++) {
497
+ if (instByteOffset[i] !== currentOffset) {
498
+ instByteOffset[i] = currentOffset;
499
+ changed = true;
500
+ }
501
+ const inst = flatInsts[i];
502
+ let mappedOp = inst.mappedOp !== void 0 ? inst.mappedOp : inst.opcode;
503
+ const forward = mapping.forward.get(inst.opcode);
504
+ if (inst.mappedOp === void 0) {
505
+ if (Array.isArray(forward)) mappedOp = forward[0];
506
+ else if (forward !== void 0) mappedOp = forward;
507
+ }
508
+ let size = 1;
509
+ if (config.junkInsertion) {
510
+ const numJunk = (inst.opcode * 7 + config.seed) % 4;
511
+ size += numJunk;
512
+ }
513
+ const ops = [...inst.operands || []];
514
+ if (inst.result) {
515
+ ops.push({ kind: OperandKind2.Register, value: inst.result });
516
+ }
517
+ const isVarLength = isVariableLengthOpcode2(inst.opcode);
518
+ if (isVarLength) {
519
+ size += 1;
520
+ }
521
+ for (let opIdx = 0; opIdx < ops.length; opIdx++) {
522
+ const op = ops[opIdx];
523
+ size += 1;
524
+ let val = 0;
525
+ if (op.kind === OperandKind2.BlockLabel) {
526
+ const targetIdx = blockInstIndices.get(op.value);
527
+ val = instByteOffset[targetIdx] || 0;
528
+ } else if (typeof op.value === "string" && op.value.startsWith("r")) {
529
+ val = Number.parseInt(op.value.substring(1), 10);
530
+ } else if (typeof op.value === "number") {
531
+ val = op.value;
532
+ }
533
+ if (config.immediateEncoding === 1) {
534
+ let v = val;
535
+ do {
536
+ v >>>= 7;
537
+ size++;
538
+ } while (v !== 0);
539
+ } else {
540
+ size += 4;
541
+ }
542
+ }
543
+ currentOffset += size;
544
+ }
545
+ }
546
+ for (const inst of flatInsts) {
547
+ if (inst.opcode === OpCode3.Jmp || inst.opcode === OpCode3.JmpIf || inst.opcode === OpCode3.JmpIfNot) {
548
+ const ops = inst.operands;
549
+ if (inst.opcode === OpCode3.Jmp) {
550
+ if (ops[0].kind === OperandKind2.BlockLabel || typeof ops[0].value === "string" && blockInstIndices.has(ops[0].value)) {
551
+ const targetIdx = blockInstIndices.get(ops[0].value);
552
+ ops[0].value = instByteOffset[targetIdx];
553
+ ops[0].kind = OperandKind2.Immediate;
554
+ }
555
+ } else if (inst.opcode === OpCode3.JmpIf || inst.opcode === OpCode3.JmpIfNot) {
556
+ const targetTrueIdx = blockInstIndices.get(ops[1].value);
557
+ const targetFalseIdx = blockInstIndices.get(ops[2].value);
558
+ ops[1].value = instByteOffset[targetTrueIdx];
559
+ ops[1].kind = OperandKind2.Immediate;
560
+ ops[2].value = instByteOffset[targetFalseIdx];
561
+ ops[2].kind = OperandKind2.Immediate;
562
+ }
563
+ } else {
564
+ for (const op of inst.operands) {
565
+ if (op.kind === OperandKind2.BlockLabel) {
566
+ const targetIdx = blockInstIndices.get(op.value);
567
+ op.value = instByteOffset[targetIdx];
568
+ op.kind = OperandKind2.Immediate;
569
+ }
570
+ }
571
+ }
572
+ }
573
+ const bytecode = encodeBytecode(flatInsts, mapping, config, rng);
574
+ functions.push({
575
+ id: irFn.id,
576
+ name: irFn.name,
577
+ bytecode,
578
+ paramCount: irFn.params.length,
579
+ localCount: irFn.locals.length,
580
+ maxRegisters: maxRegs,
581
+ attributes: irFn.attributes,
582
+ isEntryPoint: !irFn.attributes.includes(FunctionAttribute.Nested) && (irFn.isVirtualized || irFn.isExported)
583
+ });
584
+ }
585
+ const shuffledFunctions = rng.shuffle([...functions]);
586
+ const constantPool = encodeConstantPool(shuffledCP, config.constantPoolEncoding, config.seed);
587
+ const sourceFileName = irModule.sourceFile.split(/[\\/]/).pop()?.replace(/[^a-zA-Z0-9]/g, "_") ?? "unknown";
588
+ return {
589
+ magic: 1414745922,
590
+ version: 1,
591
+ buildId: `build_${config.seed}_${sourceFileName}`,
592
+ functions: shuffledFunctions,
593
+ constantPool,
594
+ opcodeMapping: mapping,
595
+ entryPointIndex: shuffledFunctions.findIndex((f) => f.isEntryPoint),
596
+ metadata: {
597
+ buildTimestamp: Date.now(),
598
+ buildId: `build_${config.seed}`,
599
+ sourceHash: computeSourceHash(irModule),
600
+ profile: config.profile ?? "generic",
601
+ deterministicSeed: config.seed
602
+ }
603
+ };
604
+ }
605
+
606
+ // src/decoder.ts
607
+ import { ImmediateEncodingScheme as ImmediateEncodingScheme2, ConstantEncodingScheme as ConstantEncodingScheme2, OperandKind as OperandKind3, OpCode as OpCode4, ConstantKind } from "@tsvm/shared";
608
+ function numToOperandKind(kind) {
609
+ switch (kind) {
610
+ case 0:
611
+ return OperandKind3.Register;
612
+ case 1:
613
+ return OperandKind3.Immediate;
614
+ case 2:
615
+ return OperandKind3.ConstantIndex;
616
+ case 3:
617
+ return OperandKind3.BlockLabel;
618
+ case 4:
619
+ return OperandKind3.FunctionRef;
620
+ default:
621
+ throw new Error(`Unknown operand kind: ${kind}`);
622
+ }
623
+ }
624
+ function isVariableLengthOpcode3(opcode) {
625
+ return opcode === OpCode4.Call || opcode === OpCode4.CallMethod || opcode === OpCode4.New || opcode === OpCode4.ArrayNew || opcode === OpCode4.ObjectNew || opcode === OpCode4.Spread || opcode === OpCode4.SpreadIntoArray || opcode === OpCode4.SuperCall || opcode === OpCode4.SuperInstruction;
626
+ }
627
+ function isTerminator3(opcode) {
628
+ return opcode === OpCode4.Jmp || opcode === OpCode4.JmpIf || opcode === OpCode4.JmpIfNot || opcode === OpCode4.Return || opcode === OpCode4.ReturnVoid || opcode === OpCode4.Throw || opcode === OpCode4.Yield || opcode === OpCode4.YieldStar || opcode === OpCode4.Await || opcode === OpCode4.Halt || opcode === OpCode4.Trap;
629
+ }
630
+ function unrollKeys(bytes, seed) {
631
+ const result = new Uint8Array(bytes.length);
632
+ for (let pc = 0; pc < bytes.length; pc++) {
633
+ const encoded = bytes[pc];
634
+ const rollingKey = (seed ^ pc * 2654435769) >>> 8 & 255;
635
+ const offsetVal = encoded ^ rollingKey;
636
+ result[pc] = offsetVal - pc & 255;
637
+ }
638
+ return result;
639
+ }
640
+ function readLEB128(bytes, offset) {
641
+ let val = 0;
642
+ let shift = 0;
643
+ let b = 0;
644
+ do {
645
+ b = bytes[offset.pos++];
646
+ val |= (b & 127) << shift;
647
+ shift += 7;
648
+ } while (b & 128);
649
+ return val;
650
+ }
651
+ function readFixed32(bytes, offset) {
652
+ const b0 = bytes[offset.pos++];
653
+ const b1 = bytes[offset.pos++];
654
+ const b2 = bytes[offset.pos++];
655
+ const b3 = bytes[offset.pos++];
656
+ return b3 << 24 | b2 << 16 | b1 << 8 | b0;
657
+ }
658
+ var OPCODE_DEFAULT_LAYOUT = { inputCount: 0, hasResult: false };
659
+ var opcodeLayout = {
660
+ [OpCode4.Nop]: { inputCount: 0, hasResult: false },
661
+ [OpCode4.Halt]: { inputCount: 0, hasResult: false },
662
+ [OpCode4.Trap]: { inputCount: 0, hasResult: false },
663
+ [OpCode4.LoadConst]: { inputCount: 1, hasResult: true },
664
+ [OpCode4.LoadLocal]: { inputCount: 1, hasResult: true },
665
+ [OpCode4.StoreLocal]: { inputCount: 2, hasResult: false },
666
+ [OpCode4.Move]: { inputCount: 2, hasResult: false },
667
+ [OpCode4.LoadGlobal]: { inputCount: 1, hasResult: true },
668
+ [OpCode4.StoreGlobal]: { inputCount: 2, hasResult: false },
669
+ [OpCode4.LoadThis]: { inputCount: 0, hasResult: true },
670
+ [OpCode4.LoadNewTarget]: { inputCount: 0, hasResult: true },
671
+ [OpCode4.Add]: { inputCount: 2, hasResult: true },
672
+ [OpCode4.Sub]: { inputCount: 2, hasResult: true },
673
+ [OpCode4.Mul]: { inputCount: 2, hasResult: true },
674
+ [OpCode4.Div]: { inputCount: 2, hasResult: true },
675
+ [OpCode4.Mod]: { inputCount: 2, hasResult: true },
676
+ [OpCode4.Neg]: { inputCount: 1, hasResult: true },
677
+ [OpCode4.BitAnd]: { inputCount: 2, hasResult: true },
678
+ [OpCode4.BitOr]: { inputCount: 2, hasResult: true },
679
+ [OpCode4.BitXor]: { inputCount: 2, hasResult: true },
680
+ [OpCode4.Shl]: { inputCount: 2, hasResult: true },
681
+ [OpCode4.Shr]: { inputCount: 2, hasResult: true },
682
+ [OpCode4.UShr]: { inputCount: 2, hasResult: true },
683
+ [OpCode4.Eq]: { inputCount: 2, hasResult: true },
684
+ [OpCode4.StrictEq]: { inputCount: 2, hasResult: true },
685
+ [OpCode4.Lt]: { inputCount: 2, hasResult: true },
686
+ [OpCode4.LtEq]: { inputCount: 2, hasResult: true },
687
+ [OpCode4.Gt]: { inputCount: 2, hasResult: true },
688
+ [OpCode4.GtEq]: { inputCount: 2, hasResult: true },
689
+ [OpCode4.Not]: { inputCount: 1, hasResult: true },
690
+ [OpCode4.TypeOf]: { inputCount: 1, hasResult: true },
691
+ [OpCode4.InstanceOf]: { inputCount: 2, hasResult: true },
692
+ [OpCode4.In]: { inputCount: 2, hasResult: true },
693
+ [OpCode4.Jmp]: { inputCount: 1, hasResult: false },
694
+ [OpCode4.JmpIf]: { inputCount: 3, hasResult: false },
695
+ [OpCode4.JmpIfNot]: { inputCount: 3, hasResult: false },
696
+ [OpCode4.Call]: { inputCount: 1, hasResult: true },
697
+ [OpCode4.CallMethod]: { inputCount: 2, hasResult: true },
698
+ [OpCode4.New]: { inputCount: 1, hasResult: true },
699
+ [OpCode4.Return]: { inputCount: 1, hasResult: false },
700
+ [OpCode4.ReturnVoid]: { inputCount: 0, hasResult: false },
701
+ [OpCode4.ClosureNew]: { inputCount: 1, hasResult: true },
702
+ [OpCode4.CellNew]: { inputCount: 0, hasResult: true },
703
+ [OpCode4.CellGet]: { inputCount: 1, hasResult: true },
704
+ [OpCode4.CellSet]: { inputCount: 2, hasResult: false },
705
+ [OpCode4.EnvGet]: { inputCount: 1, hasResult: true },
706
+ [OpCode4.CallWithArray]: { inputCount: 1, hasResult: true },
707
+ [OpCode4.CallMethodWithArray]: { inputCount: 2, hasResult: true },
708
+ [OpCode4.NewWithArray]: { inputCount: 1, hasResult: true },
709
+ [OpCode4.RestArgs]: { inputCount: 0, hasResult: true },
710
+ [OpCode4.PropGet]: { inputCount: 2, hasResult: true },
711
+ [OpCode4.PropSet]: { inputCount: 3, hasResult: false },
712
+ [OpCode4.ComputedGet]: { inputCount: 2, hasResult: true },
713
+ [OpCode4.ComputedSet]: { inputCount: 3, hasResult: false },
714
+ [OpCode4.ArrayNew]: { inputCount: 1, hasResult: true },
715
+ [OpCode4.ObjectNew]: { inputCount: 0, hasResult: true },
716
+ [OpCode4.Spread]: { inputCount: 1, hasResult: true },
717
+ [OpCode4.SpreadIntoArray]: { inputCount: 1, hasResult: true },
718
+ [OpCode4.Delete]: { inputCount: 1, hasResult: true },
719
+ [OpCode4.PrivateGet]: { inputCount: 1, hasResult: true },
720
+ [OpCode4.PrivateSet]: { inputCount: 2, hasResult: false },
721
+ [OpCode4.PrivateIn]: { inputCount: 1, hasResult: true },
722
+ [OpCode4.SuperPropGet]: { inputCount: 2, hasResult: true },
723
+ [OpCode4.SuperPropSet]: { inputCount: 3, hasResult: false },
724
+ [OpCode4.SuperCall]: { inputCount: 1, hasResult: true },
725
+ [OpCode4.SuperCallWithArray]: { inputCount: 1, hasResult: true },
726
+ [OpCode4.Throw]: { inputCount: 1, hasResult: false },
727
+ [OpCode4.TryCatchBegin]: { inputCount: 0, hasResult: false },
728
+ [OpCode4.TryCatchEnd]: { inputCount: 0, hasResult: false },
729
+ [OpCode4.Yield]: { inputCount: 0, hasResult: true },
730
+ [OpCode4.Await]: { inputCount: 0, hasResult: true },
731
+ [OpCode4.YieldStar]: { inputCount: 0, hasResult: true },
732
+ [OpCode4.GetEntropy]: { inputCount: 0, hasResult: true },
733
+ [OpCode4.SuperInstruction]: { inputCount: 1, hasResult: true }
734
+ };
735
+ function decodeBytecode(bytes, mapping, config) {
736
+ const raw = config.rollingKeys ? unrollKeys(bytes, config.seed) : bytes;
737
+ const instructions = [];
738
+ const offset = { pos: 0 };
739
+ let currentHandlerIdx = 0;
740
+ while (offset.pos < raw.length) {
741
+ const opcodeStart = offset.pos;
742
+ let mappedOp;
743
+ if (config.stealthDispatch) {
744
+ const delta = raw[offset.pos++];
745
+ mappedOp = delta + currentHandlerIdx & 255;
746
+ } else {
747
+ mappedOp = raw[offset.pos++];
748
+ }
749
+ const canonicalOp = mapping.reverse.get(mappedOp);
750
+ const opcode = canonicalOp ?? mappedOp;
751
+ const numJunk = config.junkInsertion ? (opcode * 7 + config.seed) % 4 : 0;
752
+ offset.pos += numJunk;
753
+ const layout = opcodeLayout[opcode] ?? OPCODE_DEFAULT_LAYOUT;
754
+ const isVarLength = isVariableLengthOpcode3(opcode);
755
+ let totalOps;
756
+ if (isVarLength) {
757
+ totalOps = raw[offset.pos++];
758
+ } else {
759
+ totalOps = layout.inputCount + (layout.hasResult ? 1 : 0);
760
+ }
761
+ const allOperands = [];
762
+ for (let opsRead = 0; opsRead < totalOps; opsRead++) {
763
+ const kindNum = raw[offset.pos++];
764
+ let val;
765
+ if (config.immediateEncoding === ImmediateEncodingScheme2.VariableLength) {
766
+ val = readLEB128(raw, offset);
767
+ } else {
768
+ val = readFixed32(raw, offset);
769
+ }
770
+ const kind = numToOperandKind(kindNum);
771
+ if (kind === OperandKind3.Register) {
772
+ allOperands.push({ kind, value: `r${val}` });
773
+ } else if (kind === OperandKind3.BlockLabel) {
774
+ allOperands.push({ kind, value: val });
775
+ } else {
776
+ allOperands.push({ kind, value: val });
777
+ }
778
+ }
779
+ let result;
780
+ const operands = [];
781
+ if (layout.hasResult && allOperands.length > layout.inputCount) {
782
+ const lastOp = allOperands[allOperands.length - 1];
783
+ result = lastOp.value;
784
+ for (let i = 0; i < allOperands.length - 1; i++) {
785
+ operands.push(allOperands[i]);
786
+ }
787
+ } else {
788
+ operands.push(...allOperands);
789
+ }
790
+ if (isTerminator3(opcode)) {
791
+ currentHandlerIdx = 0;
792
+ } else {
793
+ currentHandlerIdx = mappedOp;
794
+ }
795
+ instructions.push({
796
+ opcode,
797
+ result,
798
+ mappedOp,
799
+ operands
800
+ });
801
+ }
802
+ return instructions;
803
+ }
804
+ function decodeConstantPool(encoded, scheme, seed) {
805
+ return encoded.map((entry, index) => {
806
+ if (scheme === ConstantEncodingScheme2.XorRotate && entry.kind === ConstantKind.String && typeof entry.value === "string") {
807
+ let stringSeed = (seed ^ index * 2654435769) & 4294967295;
808
+ if (entry.decodingKey !== 0) {
809
+ stringSeed = (stringSeed ^ entry.decodingKey) & 4294967295;
810
+ }
811
+ const keyBytes = new Uint8Array(16);
812
+ let s = stringSeed;
813
+ for (let i = 0; i < 16; i++) {
814
+ s = Math.imul(s, 1664525) + 1013904223;
815
+ keyBytes[i] = s >>> 16 & 255;
816
+ }
817
+ const S = new Uint8Array(256);
818
+ for (let i = 0; i < 256; i++) S[i] = i;
819
+ let j = 0;
820
+ for (let i = 0; i < 256; i++) {
821
+ j = j + S[i] + keyBytes[i % 16] & 255;
822
+ const temp = S[i];
823
+ S[i] = S[j];
824
+ S[j] = temp;
825
+ }
826
+ let ri = 0;
827
+ j = 0;
828
+ for (let skip = 0; skip < 256; skip++) {
829
+ ri = ri + 1 & 255;
830
+ j = j + S[ri] & 255;
831
+ const temp = S[ri];
832
+ S[ri] = S[j];
833
+ S[j] = temp;
834
+ }
835
+ let decoded = "";
836
+ for (let i = 0; i < entry.value.length; i++) {
837
+ ri = ri + 1 & 255;
838
+ j = j + S[ri] & 255;
839
+ const temp = S[ri];
840
+ S[ri] = S[j];
841
+ S[j] = temp;
842
+ const keystreamByte = S[S[ri] + S[j] & 255];
843
+ decoded += String.fromCharCode(entry.value.charCodeAt(i) ^ keystreamByte);
844
+ }
845
+ return { index, kind: entry.kind, value: decoded };
846
+ }
847
+ return { index, kind: entry.kind, value: entry.value };
848
+ });
849
+ }
850
+ var opcodeNames = {
851
+ [OpCode4.LoadConst]: "LoadConst",
852
+ [OpCode4.LoadLocal]: "LoadLocal",
853
+ [OpCode4.StoreLocal]: "StoreLocal",
854
+ [OpCode4.Move]: "Move",
855
+ [OpCode4.LoadGlobal]: "LoadGlobal",
856
+ [OpCode4.StoreGlobal]: "StoreGlobal",
857
+ [OpCode4.LoadThis]: "LoadThis",
858
+ [OpCode4.LoadNewTarget]: "LoadNewTarget",
859
+ [OpCode4.Add]: "Add",
860
+ [OpCode4.Sub]: "Sub",
861
+ [OpCode4.Mul]: "Mul",
862
+ [OpCode4.Div]: "Div",
863
+ [OpCode4.Mod]: "Mod",
864
+ [OpCode4.Neg]: "Neg",
865
+ [OpCode4.BitAnd]: "BitAnd",
866
+ [OpCode4.BitOr]: "BitOr",
867
+ [OpCode4.BitXor]: "BitXor",
868
+ [OpCode4.Shl]: "Shl",
869
+ [OpCode4.Shr]: "Shr",
870
+ [OpCode4.UShr]: "UShr",
871
+ [OpCode4.Eq]: "Eq",
872
+ [OpCode4.StrictEq]: "StrictEq",
873
+ [OpCode4.Lt]: "Lt",
874
+ [OpCode4.LtEq]: "LtEq",
875
+ [OpCode4.Gt]: "Gt",
876
+ [OpCode4.GtEq]: "GtEq",
877
+ [OpCode4.Not]: "Not",
878
+ [OpCode4.TypeOf]: "TypeOf",
879
+ [OpCode4.InstanceOf]: "InstanceOf",
880
+ [OpCode4.In]: "In",
881
+ [OpCode4.Jmp]: "Jmp",
882
+ [OpCode4.JmpIf]: "JmpIf",
883
+ [OpCode4.JmpIfNot]: "JmpIfNot",
884
+ [OpCode4.Call]: "Call",
885
+ [OpCode4.CallMethod]: "CallMethod",
886
+ [OpCode4.New]: "New",
887
+ [OpCode4.Return]: "Return",
888
+ [OpCode4.ReturnVoid]: "ReturnVoid",
889
+ [OpCode4.ClosureNew]: "ClosureNew",
890
+ [OpCode4.CellNew]: "CellNew",
891
+ [OpCode4.CellGet]: "CellGet",
892
+ [OpCode4.CellSet]: "CellSet",
893
+ [OpCode4.EnvGet]: "EnvGet",
894
+ [OpCode4.CallWithArray]: "CallWithArray",
895
+ [OpCode4.CallMethodWithArray]: "CallMethodWithArray",
896
+ [OpCode4.NewWithArray]: "NewWithArray",
897
+ [OpCode4.RestArgs]: "RestArgs",
898
+ [OpCode4.PropGet]: "PropGet",
899
+ [OpCode4.PropSet]: "PropSet",
900
+ [OpCode4.ComputedGet]: "ComputedGet",
901
+ [OpCode4.ComputedSet]: "ComputedSet",
902
+ [OpCode4.ArrayNew]: "ArrayNew",
903
+ [OpCode4.ObjectNew]: "ObjectNew",
904
+ [OpCode4.Spread]: "Spread",
905
+ [OpCode4.SpreadIntoArray]: "SpreadIntoArray",
906
+ [OpCode4.Delete]: "Delete",
907
+ [OpCode4.PrivateGet]: "PrivateGet",
908
+ [OpCode4.PrivateSet]: "PrivateSet",
909
+ [OpCode4.PrivateIn]: "PrivateIn",
910
+ [OpCode4.SuperPropGet]: "SuperPropGet",
911
+ [OpCode4.SuperPropSet]: "SuperPropSet",
912
+ [OpCode4.SuperCall]: "SuperCall",
913
+ [OpCode4.SuperCallWithArray]: "SuperCallWithArray",
914
+ [OpCode4.Throw]: "Throw",
915
+ [OpCode4.TryCatchBegin]: "TryCatchBegin",
916
+ [OpCode4.TryCatchEnd]: "TryCatchEnd",
917
+ [OpCode4.Yield]: "Yield",
918
+ [OpCode4.Await]: "Await",
919
+ [OpCode4.YieldStar]: "YieldStar",
920
+ [OpCode4.Nop]: "Nop",
921
+ [OpCode4.Halt]: "Halt",
922
+ [OpCode4.Trap]: "Trap",
923
+ [OpCode4.GetEntropy]: "GetEntropy",
924
+ [OpCode4.SuperInstruction]: "SuperInstruction"
925
+ };
926
+ function disassemble(module) {
927
+ const config = {
928
+ opcodeRemapping: true,
929
+ immediateEncoding: ImmediateEncodingScheme2.XorMasked,
930
+ superInstructions: true,
931
+ handlerLayoutRandom: true,
932
+ constantPoolEncoding: ConstantEncodingScheme2.XorRotate,
933
+ traceMode: false,
934
+ deterministicReplay: false,
935
+ seed: module.metadata.deterministicSeed ?? 0,
936
+ profile: module.metadata.profile,
937
+ threadedDispatch: true,
938
+ tamperDetection: true,
939
+ antiDebug: true,
940
+ opcodeAliasing: true,
941
+ junkInsertion: true,
942
+ rollingKeys: true,
943
+ stealthDispatch: true
944
+ };
945
+ const lines = [];
946
+ lines.push(`; Bytecode Module: ${module.buildId}`);
947
+ lines.push(`; Version: ${module.version}, Functions: ${module.functions.length}`);
948
+ lines.push(`; Profile: ${module.metadata.profile}, Seed: ${module.metadata.deterministicSeed}`);
949
+ lines.push("");
950
+ for (const fn of module.functions) {
951
+ lines.push(`; --- Function: ${fn.name} (id: ${fn.id}) ---`);
952
+ lines.push(`; paramCount: ${fn.paramCount}, localCount: ${fn.localCount}, maxRegisters: ${fn.maxRegisters}`);
953
+ lines.push(`; bytecodeSize: ${fn.bytecode.length} bytes, entryPoint: ${fn.isEntryPoint}`);
954
+ lines.push("");
955
+ try {
956
+ const instructions = decodeBytecode(fn.bytecode, module.opcodeMapping, config);
957
+ let byteOffset = 0;
958
+ for (let i = 0; i < instructions.length; i++) {
959
+ const inst = instructions[i];
960
+ const opName = opcodeNames[inst.opcode] ?? `OP_${inst.opcode.toString(16).toUpperCase().padStart(2, "0")}`;
961
+ const mappedStr = inst.mappedOp !== void 0 && inst.mappedOp !== inst.opcode ? ` (mapped: 0x${inst.mappedOp.toString(16).toUpperCase().padStart(2, "0")})` : "";
962
+ const opsStr = inst.operands.map((op) => {
963
+ const kindStr = op.kind === OperandKind3.Register ? "r" + op.value : op.kind === OperandKind3.Immediate ? "#" + op.value : op.kind === OperandKind3.ConstantIndex ? "@" + op.value : op.kind === OperandKind3.BlockLabel ? "L" + op.value : op.kind === OperandKind3.FunctionRef ? "fn" + op.value : String(op.value);
964
+ return kindStr;
965
+ }).join(", ");
966
+ lines.push(` ${byteOffset.toString(10).padStart(4, " ")}: ${opName}${mappedStr} ${opsStr}`);
967
+ byteOffset += estimateInstructionSize(inst, config, module.metadata.deterministicSeed ?? 0);
968
+ }
969
+ } catch (e) {
970
+ const err = e;
971
+ lines.push(` ; DECODE ERROR: ${err.message}`);
972
+ }
973
+ lines.push("");
974
+ }
975
+ lines.push(`; Constant Pool (${module.constantPool.length} entries):`);
976
+ for (const entry of module.constantPool) {
977
+ const valStr = typeof entry.value === "string" ? JSON.stringify(entry.value.substring(0, 40)) : String(entry.value);
978
+ lines.push(`; [${entry.index}] ${entry.kind}: ${valStr}`);
979
+ }
980
+ return lines.join("\n");
981
+ }
982
+ function estimateInstructionSize(inst, config, seed) {
983
+ let size = 1;
984
+ const mappedOp = inst.mappedOp ?? inst.opcode;
985
+ if (config.junkInsertion) {
986
+ size += (inst.opcode * 7 + seed) % 4;
987
+ }
988
+ const ops = [...inst.operands || []];
989
+ const isVarLength = isVariableLengthOpcode3(inst.opcode);
990
+ if (isVarLength) {
991
+ size += 1;
992
+ }
993
+ for (const op of ops) {
994
+ size += 1;
995
+ let val = 0;
996
+ if (typeof op.value === "string" && op.value.startsWith("r")) {
997
+ val = Number.parseInt(op.value.substring(1), 10);
998
+ } else if (typeof op.value === "number") {
999
+ val = op.value;
1000
+ }
1001
+ if (config.immediateEncoding === ImmediateEncodingScheme2.VariableLength) {
1002
+ let v = val;
1003
+ do {
1004
+ v >>>= 7;
1005
+ size++;
1006
+ } while (v !== 0);
1007
+ } else {
1008
+ size += 4;
1009
+ }
1010
+ }
1011
+ return size;
1012
+ }
1013
+ export {
1014
+ compileToBytecode,
1015
+ decodeBytecode,
1016
+ decodeConstantPool,
1017
+ disassemble,
1018
+ encodeBytecode,
1019
+ encodeConstantPool,
1020
+ generateRemappedOpcodes
1021
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@tsvm/bytecode",
3
+ "version": "0.1.0",
4
+ "files": [
5
+ "dist",
6
+ "LICENSE"
7
+ ],
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "tsup ./src/index.ts --format esm --dts --clean",
23
+ "test": "pnpm --dir ../.. exec vitest run packages/bytecode/tests/**/*.test.ts --config vitest.config.mjs",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "dependencies": {
28
+ "@tsvm/shared": "workspace:*"
29
+ },
30
+ "devDependencies": {
31
+ "tsup": "^8.4.0",
32
+ "typescript": "^5.8.0"
33
+ }
34
+ }