import { SYSCALLS } from "./generated/syscalls.js";
import { handleSlotInstruction } from "./type-slot-ops.js";
import { handleStackInstruction } from "./type-stack-ops.js";
import { asCount, constrainOrigins, convertTargetType, directCallTarget, intLiteral, pushaTarget } from "./type-support.js";
export function inferInSlice(instructions, locals, args, statics, methodTokens = [], directCallEffects = new Map()) {
const stack = [];
const aliases = { locals: [], args: [], statics: [] };
const pointerTargets = { locals: [], args: [], statics: [] };
const unk = () => ({ ty: "unknown", lit: null, ptr: null, origins: [] });
const cloneValue = (value) => ({ ...value, origins: [...(value.origins ?? [])] });
const popU = () => stack.pop() ?? unk();
const popExpected = (expected) => {
const value = popU();
constrainOrigins(value.origins, expected, locals, args, statics);
return value;
};
for (const ins of instructions) {
const m = ins.opcode.mnemonic;
if (handleSlotInstruction(ins, stack, locals, args, statics, aliases, pointerTargets, unk)) {
continue;
}
if (handleStackInstruction(ins, stack, unk, cloneValue, popU)) {
continue;
}
switch (m) {
case "PUSHNULL":
stack.push({ ty: "null", lit: null, ptr: null, origins: [] });
break;
case "PUSHT":
case "PUSHF":
stack.push({ ty: "bool", lit: null, ptr: null, origins: [] });
break;
case "PUSHDATA1":
case "PUSHDATA2":
case "PUSHDATA4":
stack.push({ ty: "bytestring", lit: null, ptr: null, origins: [] });
break;
case "PUSHINT8":
case "PUSHINT16":
case "PUSHINT32":
case "PUSHINT64":
case "PUSHINT128":
case "PUSHINT256":
case "PUSHM1":
case "PUSH0":
case "PUSH1":
case "PUSH2":
case "PUSH3":
case "PUSH4":
case "PUSH5":
case "PUSH6":
case "PUSH7":
case "PUSH8":
case "PUSH9":
case "PUSH10":
case "PUSH11":
case "PUSH12":
case "PUSH13":
case "PUSH14":
case "PUSH15":
case "PUSH16":
stack.push({ ty: "integer", lit: intLiteral(ins.operand), ptr: null, origins: [] });
break;
case "PUSHA":
stack.push({ ty: "pointer", lit: null, ptr: pushaTarget(ins), origins: [] });
break;
case "NEWARRAY0":
stack.push({ ty: "array", lit: null, origins: [] });
break;
case "NEWARRAY":
case "NEWARRAY_T":
popU();
stack.push({ ty: "array", lit: null, origins: [] });
break;
case "NEWMAP":
stack.push({ ty: "map", lit: null, origins: [] });
break;
case "NEWSTRUCT0":
stack.push({ ty: "struct", lit: null, origins: [] });
break;
case "NEWSTRUCT":
popU();
stack.push({ ty: "struct", lit: null, origins: [] });
break;
case "NEWBUFFER":
popU();
stack.push({ ty: "buffer", lit: null, origins: [] });
break;
case "MEMCPY":
popU(); popU(); popU(); popU(); popU(); break;
case "CAT":
popExpected("bytestring");
popExpected("bytestring");
stack.push({ ty: "bytestring", lit: null, origins: [] });
break;
case "SUBSTR":
popExpected("integer");
popExpected("integer");
popExpected("bytestring");
stack.push({ ty: "bytestring", lit: null, origins: [] });
break;
case "LEFT":
case "RIGHT":
popExpected("integer");
popExpected("bytestring");
stack.push({ ty: "bytestring", lit: null, origins: [] });
break;
case "PACK": {
const count = asCount(popU().lit);
if (count !== null) for (let k = 0; k < Math.min(count, stack.length); k += 1) popU();
stack.push({ ty: "array", lit: null, origins: [] });
break;
}
case "PACKMAP": {
const count = asCount(popU().lit);
if (count !== null) for (let k = 0; k < Math.min(count * 2, stack.length); k += 1) popU();
stack.push({ ty: "map", lit: null, origins: [] });
break;
}
case "PACKSTRUCT": {
const count = asCount(popU().lit);
if (count !== null) for (let k = 0; k < Math.min(count, stack.length); k += 1) popU();
stack.push({ ty: "struct", lit: null, origins: [] });
break;
}
case "UNPACK":
popU();
stack.push({ ty: "integer", lit: null, ptr: null, origins: [] });
break;
case "PICKITEM":
popU();
popU();
stack.push(unk());
break;
case "SETITEM":
popU();
popU();
popU();
break;
case "APPEND":
case "REMOVE":
popU();
popU();
break;
case "CLEARITEMS":
popU();
break;
case "REVERSEITEMS":
popU();
break;
case "POPITEM":
popU();
stack.push(unk());
break;
case "SIZE":
popU();
stack.push({ ty: "integer", lit: null, origins: [] });
break;
case "HASKEY":
popU();
popU();
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "KEYS":
case "VALUES":
popU();
stack.push({ ty: "array", lit: null, origins: [] });
break;
case "ISNULL":
popU();
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "ISTYPE":
popU();
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "CONVERT":
popU();
stack.push({ ty: convertTargetType(ins.operand) ?? "any", lit: null, origins: [] });
break;
case "SYSCALL": {
const hash = ins.operand?.kind === "Syscall" ? ins.operand.value : null;
if (hash !== null) {
const info = SYSCALLS.get(hash) ?? null;
const argCount = info?.param_count ?? 0;
for (let index = 0; index < argCount; index += 1) {
popU();
}
if (info?.returns_value ?? true) {
stack.push({ ty: "unknown", lit: null, origins: [] });
}
}
break;
}
case "CALLT": {
const index = ins.operand?.kind === "U16" ? ins.operand.value : null;
if (index !== null) {
const token = methodTokens[index] ?? null;
if (token) {
for (let argIndex = 0; argIndex < token.parametersCount; argIndex += 1) {
popU();
}
if (token.hasReturnValue) {
stack.push({ ty: "unknown", lit: null, origins: [] });
}
} else {
stack.push({ ty: "unknown", lit: null, origins: [] });
}
}
break;
}
case "CALL":
case "CALL_L": {
const target = directCallTarget(ins);
const effect = target !== null ? directCallEffects.get(target) ?? null : null;
if (effect) {
for (let argIndex = 0; argIndex < effect.paramCount; argIndex += 1) {
popU();
}
if (effect.returnsValue) {
stack.push({ ty: "unknown", lit: null, origins: [] });
}
} else {
stack.push({ ty: "unknown", lit: null, origins: [] });
}
break;
}
case "CALLA": {
const pointer = popU();
const target = pointer.ptr ?? null;
const effect = target !== null ? directCallEffects.get(target) ?? null : null;
if (effect) {
for (let argIndex = 0; argIndex < effect.paramCount; argIndex += 1) {
popU();
}
if (effect.returnsValue) {
stack.push({ ty: "unknown", lit: null, ptr: null, origins: [] });
}
} else {
stack.push({ ty: "unknown", lit: null, ptr: null, origins: [] });
}
break;
}
case "JMPIF":
case "JMPIF_L":
case "JMPIFNOT":
case "JMPIFNOT_L":
popExpected("bool");
break;
case "JMPEQ":
case "JMPEQ_L":
case "JMPNE":
case "JMPNE_L":
popU();
popU();
break;
case "JMPGT":
case "JMPGT_L":
case "JMPGE":
case "JMPGE_L":
case "JMPLT":
case "JMPLT_L":
case "JMPLE":
case "JMPLE_L":
popExpected("integer");
popExpected("integer");
break;
case "ASSERT":
popExpected("bool");
break;
case "ASSERTMSG":
popU(); popExpected("bool");
break;
case "ABORT":
stack.length = 0;
break;
case "ABORTMSG":
popU(); stack.length = 0;
break;
case "THROW":
popU();
stack.length = 0;
break;
case "ADD":
case "SUB":
case "MUL":
case "DIV":
case "MOD":
case "POW":
case "MIN":
case "MAX":
case "SHL":
case "SHR":
case "AND":
case "OR":
case "XOR":
popExpected("integer");
popExpected("integer");
stack.push({ ty: "integer", lit: null, origins: [] });
break;
case "MODMUL":
case "MODPOW":
popExpected("integer");
popExpected("integer");
popExpected("integer");
stack.push({ ty: "integer", lit: null, origins: [] });
break;
case "WITHIN":
popExpected("integer");
popExpected("integer");
popExpected("integer");
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "SQRT":
case "ABS":
case "SIGN":
case "INC":
case "DEC":
case "NEGATE":
case "INVERT":
popExpected("integer");
stack.push({ ty: "integer", lit: null, origins: [] });
break;
case "NOT":
popExpected("bool");
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "BOOLAND":
case "BOOLOR":
popExpected("bool");
popExpected("bool");
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "EQUAL":
case "NOTEQUAL":
popU();
popU();
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "NUMEQUAL":
case "NUMNOTEQUAL":
case "GT":
case "GE":
case "LT":
case "LE":
popExpected("integer");
popExpected("integer");
stack.push({ ty: "bool", lit: null, origins: [] });
break;
case "NZ":
popExpected("integer");
stack.push({ ty: "bool", lit: null, origins: [] });
break;
default:
break;
}
}
}