import { scanSlotCounts, scanStaticSlotCount, slotIndex } from "./util.js";
export function inferTypes(instructions, methodGroups, manifest = null) {
const staticCount = scanStaticSlotCount(instructions);
const statics = Array.from({ length: staticCount }, () => "unknown");
const methods = methodGroups.map((group) => {
const [localCount, argCount] = scanSlotCounts(group.instructions);
const locals = Array.from({ length: localCount }, () => "unknown");
const argumentsTypes = Array.from({ length: argCount }, () => "unknown");
if (group.source?.parameters) {
while (argumentsTypes.length < group.source.parameters.length) {
argumentsTypes.push("unknown");
}
for (let index = 0; index < group.source.parameters.length; index += 1) {
argumentsTypes[index] = manifestType(group.source.parameters[index].kind);
}
}
for (let index = 0; index < group.instructions.length; index += 1) {
const instruction = group.instructions[index];
const next = group.instructions[index + 1];
const store = next?.opcode?.mnemonic;
switch (instruction.opcode.mnemonic) {
case "NEWMAP":
case "PACKMAP":
assignStoredType(locals, statics, store, next, "map");
break;
case "NEWSTRUCT0":
case "NEWSTRUCT":
case "PACKSTRUCT":
assignStoredType(locals, statics, store, next, "struct");
break;
case "NEWARRAY0":
case "NEWARRAY":
case "NEWARRAY_T":
assignStoredType(locals, statics, store, next, "array");
break;
case "NEWBUFFER":
assignStoredType(locals, statics, store, next, "buffer");
break;
case "CONVERT": {
const target = convertTargetType(instruction.operand);
if (target) {
assignStoredType(locals, statics, store, next, target);
}
break;
}
}
}
return {
method: { offset: group.start, name: group.name },
arguments: argumentsTypes,
locals,
};
});
return { methods, statics };
}
function assignStoredType(locals, statics, store, instruction, type) {
if (store?.startsWith("STLOC")) {
locals[slotIndex(store, instruction)] = type;
} else if (store?.startsWith("STSFLD")) {
statics[slotIndex(store, instruction)] = type;
}
}
function manifestType(kind) {
const normalized = String(kind).toLowerCase();
if (normalized === "boolean") return "bool";
if (normalized === "integer") return "integer";
if (
normalized === "string" ||
normalized === "bytearray" ||
normalized === "signature" ||
normalized === "hash160" ||
normalized === "hash256"
) {
return "bytestring";
}
if (normalized === "array") return "array";
if (normalized === "map") return "map";
if (normalized === "interopinterface") return "interopinterface";
return "unknown";
}
const CONVERT_TARGET_MAP = new Map([
[0x00, "any"],
[0x10, "pointer"],
[0x20, "bool"],
[0x21, "integer"],
[0x28, "bytestring"],
[0x30, "buffer"],
[0x40, "array"],
[0x41, "struct"],
[0x48, "map"],
[0x60, "interopinterface"],
]);
function convertTargetType(operand) {
if (!operand || (operand.kind !== "U8" && operand.kind !== "I8")) {
return null;
}
const byte = operand.kind === "U8" ? operand.value : operand.value & 0xff;
return CONVERT_TARGET_MAP.get(byte) ?? null;
}