import { SYSCALLS } from "./generated/syscalls.js";
import { applyIndexedStackInstruction } from "./entry-stack-indexed.js";
import {
BINARY_VALUE_MNEMONICS,
LOAD_SLOT_MNEMONICS,
ONE_POP_NO_PUSH_MNEMONICS,
STORE_SLOT_MNEMONICS,
UNARY_VALUE_MNEMONICS,
UNKNOWN_PUSH_MNEMONICS,
ZERO_EFFECT_MNEMONICS,
} from "./entry-stack-opcodes.js";
import { MAX_SIMULATED_POPS, popCount } from "./entry-stack-state.js";
export function applyInstructionForArgInference(instruction, state) {
const mnemonic = instruction.opcode.mnemonic;
if (["PICK", "ROLL", "XDROP", "REVERSEN"].includes(mnemonic)) {
return applyIndexedStackInstruction(mnemonic, state);
}
const effect = stackEffectForArgInference(instruction, state.stack);
if (!effect) {
return false;
}
popCount(state, effect.pops);
state.stack.push(...effect.pushes);
return true;
}
function stackEffectForArgInference(instruction, stack) {
const mnemonic = instruction.opcode.mnemonic;
const literal = literalPushValue(instruction);
if (literal !== undefined) {
return { pops: 0, pushes: [literal] };
}
if (ZERO_EFFECT_MNEMONICS.has(mnemonic)) {
return { pops: 0, pushes: [] };
}
if (UNKNOWN_PUSH_MNEMONICS.has(mnemonic) || LOAD_SLOT_MNEMONICS.test(mnemonic)) {
return { pops: 0, pushes: [null] };
}
if (STORE_SLOT_MNEMONICS.test(mnemonic) || ONE_POP_NO_PUSH_MNEMONICS.has(mnemonic)) {
return { pops: 1, pushes: [] };
}
if (UNARY_VALUE_MNEMONICS.has(mnemonic)) {
return { pops: 1, pushes: [null] };
}
switch (mnemonic) {
case "DUP": {
const top = stack.at(-1) ?? null;
return { pops: 1, pushes: [top, top] };
}
case "NIP":
return { pops: 2, pushes: [null] };
case "OVER":
case "TUCK":
return { pops: 2, pushes: [null, null, null] };
case "SWAP":
return { pops: 2, pushes: [null, null] };
case "ROT":
case "REVERSE3":
return { pops: 3, pushes: [null, null, null] };
case "REVERSE4":
return { pops: 4, pushes: [null, null, null, null] };
case "APPEND":
case "REMOVE":
return { pops: 2, pushes: [] };
case "SUBSTR":
case "MODMUL":
case "MODPOW":
case "WITHIN":
return { pops: 3, pushes: [null] };
case "SETITEM":
return { pops: 3, pushes: [] };
case "MEMCPY":
return { pops: 5, pushes: [] };
case "PACK":
case "PACKSTRUCT":
return packEffect(stack, 1);
case "PACKMAP":
return packEffect(stack, 2);
case "SYSCALL":
return syscallEffect(instruction);
default:
break;
}
if (BINARY_VALUE_MNEMONICS.has(mnemonic)) {
return { pops: 2, pushes: [null] };
}
return null;
}
function packEffect(stack, itemWidth) {
const count = stack.at(-1);
if (!Number.isInteger(count) || count < 0) {
return null;
}
return {
pops: Math.min(count * itemWidth + 1, MAX_SIMULATED_POPS),
pushes: [null],
};
}
function syscallEffect(instruction) {
if (instruction.operand?.kind !== "Syscall") {
return null;
}
const info = SYSCALLS.get(instruction.operand.value) ?? null;
if (!info) {
return null;
}
return {
pops: info.param_count ?? 0,
pushes: info.returns_value ?? true ? [null] : [],
};
}
function literalPushValue(instruction) {
const mnemonic = instruction.opcode.mnemonic;
if (mnemonic === "PUSHM1") {
return null;
}
const small = /^PUSH(\d+)$/u.exec(mnemonic);
if (small) {
return Number(small[1]);
}
if (!["PUSHINT8", "PUSHINT16", "PUSHINT32", "PUSHINT64"].includes(mnemonic)) {
return undefined;
}
const value = integerOperandValue(instruction.operand);
return value === null ? null : value;
}
function integerOperandValue(operand) {
if (!operand || !["I8", "I16", "I32", "I64"].includes(operand.kind)) {
return null;
}
const value = Number(operand.value);
return Number.isSafeInteger(value) && value >= 0 ? value : null;
}