import { stripOuterParens } from "./high-level-utils.js";
import { upperHex } from "./util.js";
function decodeSignedLeBigInt(bytes) {
if (bytes.length === 0) return 0n;
let magnitude = 0n;
for (let i = bytes.length - 1; i >= 0; i--) {
magnitude = (magnitude << 8n) | BigInt(bytes[i]);
}
const signBit = bytes[bytes.length - 1] & 0x80;
if (signBit !== 0) {
const range = 1n << BigInt(bytes.length * 8);
return magnitude - range;
}
return magnitude;
}
export function formatPushdata(bytes) {
if (bytes.length === 0) {
return '""';
}
let decodable = true;
let decoded = "";
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i];
if (b === 0x0A || b === 0x0D || b === 0x09 || (b >= 0x20 && b <= 0x7E)) {
decoded += String.fromCharCode(b);
} else {
decodable = false;
break;
}
}
if (!decodable) {
return `0x${upperHex(bytes)}`;
}
return `"${decoded.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
}
const PUSH_LIT_RE = /^PUSH(\d+|M1)$/u;
export function trySlotDeclarations(_statements, instruction) {
if (
instruction.opcode.mnemonic === "INITSLOT" &&
instruction.operand?.kind === "Bytes" &&
instruction.operand.value.length >= 2
) {
return true;
}
if (instruction.opcode.mnemonic === "INITSSLOT" && instruction.operand?.kind === "U8") {
return true;
}
return false;
}
export function pushImmediate(state, instruction) {
const { stack, pointerTargetsByExpression, packedValuesByExpression } = state;
const mnemonic = instruction.opcode.mnemonic;
if (mnemonic === "PUSHNULL") {
stack.push("null");
return true;
}
if (mnemonic === "PUSHT") {
stack.push("true");
return true;
}
if (mnemonic === "PUSHF") {
stack.push("false");
return true;
}
const match = PUSH_LIT_RE.exec(mnemonic);
if (match) {
stack.push(match[1] === "M1" ? "-1" : `${Number(match[1])}`);
return true;
}
if (instruction.operand !== null) {
if (instruction.operand.kind === "I32" && mnemonic === "PUSHA") {
const target = instruction.offset + instruction.operand.value;
if (target < 0) {
stack.push(`${instruction.operand.value}`);
return true;
}
const labelMap = state.context?.methodLabelsByOffset;
const resolved = labelMap?.get(target) ?? null;
const hex = target.toString(16).padStart(4, "0").toUpperCase();
const expression = resolved ? `&${resolved}` : `&fn_0x${hex}`;
stack.push(expression);
pointerTargetsByExpression.set(expression, target);
return true;
}
if (
instruction.operand.kind === "I8" ||
instruction.operand.kind === "I16" ||
instruction.operand.kind === "I32" ||
instruction.operand.kind === "I64"
) {
if (mnemonic.startsWith("PUSHINT")) {
stack.push(`${instruction.operand.value}`);
return true;
}
}
if (instruction.operand.kind === "Bytes" && mnemonic.startsWith("PUSHDATA")) {
const expression = formatPushdata(instruction.operand.value);
stack.push(expression);
packedValuesByExpression.delete(expression);
return true;
}
if (
instruction.operand.kind === "Bytes" &&
(mnemonic === "PUSHINT128" || mnemonic === "PUSHINT256")
) {
stack.push(`${decodeSignedLeBigInt(instruction.operand.value)}`);
return true;
}
}
return false;
}
export function tryLoadLocalOrArg(stack, mnemonic, parameterNames, instruction) {
const local = slotIndexFromMnemonic(mnemonic, "LDLOC");
if (local !== null) {
stack.push(`loc${local}`);
return true;
}
if (mnemonic === "LDLOC") {
const index = instruction.operand?.value;
if (typeof index === "number") {
stack.push(`loc${index}`);
return true;
}
}
const arg = slotIndexFromMnemonic(mnemonic, "LDARG");
if (arg !== null) {
stack.push(parameterNames[arg] ?? `arg${arg}`);
return true;
}
if (mnemonic === "LDARG") {
const index = instruction.operand?.value;
if (typeof index === "number") {
stack.push(parameterNames[index] ?? `arg${index}`);
return true;
}
}
return false;
}
export function tryLoadStatic(stack, mnemonic, instruction) {
const index = slotIndexFromMnemonic(mnemonic, "LDSFLD");
if (index !== null) {
stack.push(`static${index}`);
return true;
}
if (mnemonic === "LDSFLD") {
const index = instruction.operand?.value;
if (typeof index === "number") {
stack.push(`static${index}`);
return true;
}
}
return false;
}
export function tryStoreLocal(
statements,
stack,
initializedLocals,
pointerTargetsByExpression,
pointerTargetsBySlot,
packedValuesByExpression,
packedValuesBySlot,
mnemonic,
instruction,
) {
let local = slotIndexFromMnemonic(mnemonic, "STLOC");
if (local === null && mnemonic === "STLOC") {
local = instruction.operand?.value ?? null;
}
if (local === null) {
return false;
}
const value = stack.pop() ?? "???";
const name = `loc${local}`;
const stripped = stripOuterParens(value);
const pointerTarget = pointerTargetsByExpression.get(stripped);
if (pointerTarget !== undefined) {
pointerTargetsBySlot.set(name, pointerTarget);
} else {
pointerTargetsBySlot.delete(name);
}
const packedValue = packedValuesByExpression.get(stripped);
if (packedValue !== undefined) {
packedValuesBySlot.set(name, [...packedValue]);
} else {
packedValuesBySlot.delete(name);
}
if (initializedLocals.has(local)) {
statements.push(`${name} = ${stripped};`);
} else {
initializedLocals.add(local);
statements.push(`let ${name} = ${stripped};`);
}
return true;
}
export function tryStoreStatic(
statements,
stack,
initializedStatics,
pointerTargetsByExpression,
pointerTargetsBySlot,
packedValuesByExpression,
packedValuesBySlot,
mnemonic,
instruction,
) {
let index = slotIndexFromMnemonic(mnemonic, "STSFLD");
if (index === null && mnemonic === "STSFLD") {
index = instruction.operand?.value ?? null;
}
if (index === null) {
return false;
}
const value = stack.pop() ?? "???";
const name = `static${index}`;
const stripped = stripOuterParens(value);
const pointerTarget = pointerTargetsByExpression.get(stripped);
if (pointerTarget !== undefined) {
pointerTargetsBySlot.set(name, pointerTarget);
} else {
pointerTargetsBySlot.delete(name);
}
const packedValue = packedValuesByExpression.get(stripped);
if (packedValue !== undefined) {
packedValuesBySlot.set(name, [...packedValue]);
} else {
packedValuesBySlot.delete(name);
}
if (initializedStatics.has(index)) {
statements.push(`${name} = ${stripped};`);
} else {
initializedStatics.add(index);
statements.push(`let ${name} = ${stripped};`);
}
return true;
}
export function tryStoreArgument(statements, stack, parameterNames, mnemonic, instruction) {
let index = slotIndexFromMnemonic(mnemonic, "STARG");
if (index === null && mnemonic === "STARG") {
index = instruction.operand?.value ?? null;
}
if (index === null) {
return false;
}
const value = stripOuterParens(stack.pop() ?? "???");
const name = parameterNames[index] ?? `arg${index}`;
statements.push(`${name} = ${value};`);
return true;
}
export function slotIndexFromMnemonic(mnemonic, prefix) {
if (mnemonic.length <= prefix.length || !mnemonic.startsWith(prefix)) {
return null;
}
let value = 0;
for (let i = prefix.length; i < mnemonic.length; i++) {
const code = mnemonic.charCodeAt(i);
if (code < 48 || code > 57) return null;
value = value * 10 + (code - 48);
}
return value;
}