ton-assembly
Version:
TON assembler and disassembler
611 lines • 22.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.compile = compile;
exports.processAst = processAst;
exports.compileDefinition = compileDefinition;
const $ = __importStar(require("@tonstudio/parser-runtime"));
const text_1 = require("../../text");
const util_1 = require("../../runtime/util");
const parse_1 = require("../parse/parse");
const runtime_1 = require("../../runtime");
function compile(sourceName, content) {
const result = (0, parse_1.parse)(sourceName, content);
if (result.$ === "ParseFailure") {
const pos = result.error.position;
console.error(`Parse error: ${result.error.message}${pos === undefined ? "" : ` at position ${pos.start.line}:${pos.start.column}`}`);
process.exit(1);
}
const ctx = processAst(result.ast);
const methods = result.ast.program.definitions.map(def => {
const definition = compileDefinition(def, ctx);
ctx.compiledFunctions.set(def.def.name.name, definition);
return definition.compiled;
});
const usedMethods = methods.filter(method => {
const func = ctx.functions.entries().find(([, id]) => method.id === id);
if (!func)
return true;
const [name] = func;
const usageCount = ctx.usedFunctions.get(name) ?? 0;
return usageCount > 0;
});
const mainDictionary = {
$: "DecompiledDict",
methods: usedMethods,
};
const toplevel = [
(0, runtime_1.SETCP)(0),
(0, runtime_1.DICTPUSHCONST)(19, mainDictionary),
(0, runtime_1.DICTIGETJMPZ)(),
(0, runtime_1.THROWARG)(11),
];
return toplevel;
}
function processAst(ast) {
const functions = new Map();
const globals = new Map();
const compiledFunctions = new Map();
const usedFunctions = new Map();
let functionIdx = 1;
let globalIdx = 1;
for (const decl of ast.program.declarations) {
const name = decl.decl.name.name;
if (decl.decl.$ === "ProcDeclaration") {
if (name === "recv_internal") {
functions.set(name, 0);
usedFunctions.set(name, 1);
continue;
}
if (name === "recv_external") {
functions.set(name, -1);
usedFunctions.set(name, 1);
continue;
}
if (name === "run_ticktock") {
functions.set(name, -2);
usedFunctions.set(name, 1);
continue;
}
if (name === "split_prepare") {
functions.set(name, -3);
usedFunctions.set(name, 1);
continue;
}
if (name === "split_install") {
functions.set(name, -4);
usedFunctions.set(name, 1);
continue;
}
functions.set(name, functionIdx);
functionIdx++;
}
if (decl.decl.$ === "MethodDeclaration") {
functions.set(name, Number.parseInt(decl.decl.method_id.value));
usedFunctions.set(name, 1);
}
if (decl.decl.$ === "GlobalVar") {
globals.set(name, globalIdx);
globalIdx++;
}
}
const ctx = {
functions,
globals,
usedFunctions,
compiledFunctions,
};
return ctx;
}
function compileDefinition(def, ctx) {
const rawInstructions = def.def.instructions.flatMap(it => compileInstruction(ctx, it));
const id = ctx.functions.get(def.def.name.name);
if (id === undefined) {
throw new Error("Add function declaration");
}
const parserCtx = { lines: [], filepath: "" };
const instructions = (0, text_1.processInstructions)(parserCtx, rawInstructions);
const compiled = {
$: "DecompiledMethod",
id,
instructions,
};
const inline = def.def.$ === "ProcInlineDefinition"
? "inline"
: def.def.$ === "ProcRefDefinition"
? "inline_ref"
: "unspecified";
return { inline, compiled, processed: rawInstructions };
}
function integerArgument(value) {
return {
$: "Argument",
expression: {
$: "IntegerLiteral",
value: {
$: "IntegerLiteralDec",
digits: value.toString(),
loc: $.emptyLoc(0),
},
op: undefined,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
}
function newInstruction(name, args) {
return {
$: "Instruction",
args,
name: {
$: "Id",
name,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
}
function compileInstruction(ctx, raw) {
const instr = raw.instr;
switch (instr.$) {
case "AsmExpression": {
const name = instr.name.value;
const args = instr.arguments?.primitives.map(it => convertPrimitive(ctx, it)) ?? [];
if (name === "INLINECALLDICT") {
const nameNode = instr.arguments?.primitives[0]?.prim;
const name = nameNode?.$ === "ArgIdentifier" ? nameNode.name : "";
const code = ctx.compiledFunctions.get(name);
if (!code) {
return [newInstruction("CALLDICT", args)];
}
if (code.inline === "inline") {
// remove this usage
const prevValue = ctx.usedFunctions.get(name) ?? 0;
ctx.usedFunctions.set(name, prevValue - 1);
return code.processed;
}
if (code.inline === "inline_ref") {
// remove this usage
const prevValue = ctx.usedFunctions.get(name) ?? 0;
ctx.usedFunctions.set(name, prevValue - 1);
return [
newInstruction("CALLREF", [
{
$: "Argument",
expression: {
$: "Code",
instructions: code.processed,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
]),
];
}
return code.processed;
}
// special Fift instructions with special (smart) handling
if (name === "PUSHINT" ||
name === "PUSHSLICE" ||
name === "PUSHCONT" ||
name === "STSLICECONST" ||
name === "XCHG" ||
name === "PUSHINTX" ||
name === "SDBEGINS" ||
name === "SDBEGINSQ" ||
name === "CALLXARGS" ||
name === "CALLDICT" ||
name === "JMPDICT" ||
name === "PREPAREDICT" ||
name === "THROW" ||
name === "THROWIF" ||
name === "THROWIFNOT") {
return [newInstruction(`f${name}`, args)];
}
if (name === "PUSH" || name === "POP" || name === "SAVE") {
const [arg] = args;
if (!arg) {
throw new Error("Expected argument");
}
if (arg.expression.$ === "ControlRegister") {
return [newInstruction(`${name}CTR`, args)];
}
// TODO: POP_LONG/PUSH_LONG
return [newInstruction(name, args)];
}
switch (name) {
case "-ROT": {
return [newInstruction("ROTREV", args)];
}
case "-ROLL": {
return [newInstruction("BLKSWAP", [...args, integerArgument(1)])];
}
case "ROLL": {
return [newInstruction("BLKSWAP", [integerArgument(1), ...args])];
}
case "FALSE": {
return [newInstruction("PUSHINT_4", [integerArgument(0)])];
}
case "TRUE": {
return [newInstruction("PUSHINT_4", [integerArgument(-1)])];
}
case "ONE": {
return [newInstruction("PUSHINT_4", [integerArgument(1)])];
}
case "FIRST": {
return [newInstruction("INDEX", [integerArgument(0)])];
}
case "SECOND": {
return [newInstruction("INDEX", [integerArgument(1)])];
}
case "THIRD": {
return [newInstruction("INDEX", [integerArgument(2)])];
}
case "SKIPOPTREF": {
return [newInstruction("SKIPDICT", args)];
}
case "LDOPTREF": {
return [newInstruction("LDDICT", args)];
}
case "PLDOPTREF": {
return [newInstruction("PLDDICT", args)];
}
case "STOPTREF": {
return [newInstruction("STDICT", args)];
}
case "PLDREF": {
return [newInstruction("PLDREFIDX", [integerArgument(0)])];
}
case "SETCONTMANY": {
return [newInstruction("SETCONTCTRMANY", args)];
}
case "COMPOSALT": {
return [newInstruction("BOOLOR", args)];
}
case "COMPOS": {
return [newInstruction("BOOLAND", args)];
}
case "LDVARUINT16": {
return [newInstruction("LDGRAMS", args)];
}
case "STVARUINT16": {
return [newInstruction("STGRAMS", args)];
}
case "NEWDICT": {
return [newInstruction("PUSHNULL", args)];
}
case "PAIR":
case "CONS": {
return [newInstruction("TUPLE", [integerArgument(2)])];
}
}
if (name.startsWith("HASHEXT_")) {
const hashName = name.slice("HASHEXT_".length);
const hashId = hashName === "SHA256"
? util_1.Hash.SHA256
: hashName === "SHA512"
? util_1.Hash.SHA512
: hashName === "BLAKE2B"
? util_1.Hash.BLAKE2B
: hashName === "KECCAK256"
? util_1.Hash.KECCAK256
: hashName === "KECCAK512"
? util_1.Hash.KECCAK512
: 1;
return [newInstruction("HASHEXT", [integerArgument(hashId)])];
}
return [newInstruction(name, args)];
}
case "IfStatement": {
const hasElse = instr.else_block !== undefined;
const negated = instr.kind === "IFNOT:<{";
const trueBranchCode = {
$: "Code",
instructions: instr.instructions.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
};
let falseBranchCode = undefined;
if (instr.else_block) {
falseBranchCode = {
$: "Code",
instructions: instr.else_block.instructions.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
};
}
const kind = hasElse ? "IFELSE" : negated ? "IFNOT" : "IF";
const bodies = [
{
$: "Argument",
expression: trueBranchCode,
loc: $.emptyLoc(0),
},
...(falseBranchCode
? [
{
$: "Argument",
expression: falseBranchCode,
loc: $.emptyLoc(0),
},
]
: []),
];
if (instr.kind === "IFNOT:<{" && hasElse) {
// For IFNOT:<{ true }>ELSE<{ false }> we need to swap the branches
// and change kind to IFELSE
bodies.reverse();
}
const IF = newInstruction("fIF", [
{
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "StringLiteral",
value: kind,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
...bodies,
]);
return [IF];
}
case "IfjmpStatement": {
const negated = instr.kind === "IFNOTJMP:<{";
const kind = negated ? "IFNOTJMP" : "IFJMP";
const trueBranchCode = {
$: "Code",
instructions: instr.instructions.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
};
const IFJMP = newInstruction("fIF", [
{
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "StringLiteral",
value: kind,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
{
$: "Argument",
expression: trueBranchCode,
loc: $.emptyLoc(0),
},
]);
return [IFJMP];
}
case "WhileStatement": {
const WHILE = newInstruction("WHILE", []);
const condition = newInstruction("fPUSHCONT", [
{
$: "Argument",
expression: {
$: "Code",
instructions: instr.condition.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
]);
const body = newInstruction("fPUSHCONT", [
{
$: "Argument",
expression: {
$: "Code",
instructions: instr.body.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
]);
return [condition, body, WHILE];
}
case "RepeatStatement": {
const REPEAT = newInstruction("REPEAT", []);
const body = newInstruction("fPUSHCONT", [
{
$: "Argument",
expression: {
$: "Code",
instructions: instr.instructions.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
]);
return [body, REPEAT];
}
case "UntilStatement": {
const UNTIL = newInstruction("UNTIL", []);
const body = newInstruction("fPUSHCONT", [
{
$: "Argument",
expression: {
$: "Code",
instructions: instr.instructions.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
]);
return [body, UNTIL];
}
}
throw new Error("Unexpected error");
}
function convertPrimitive(ctx, raw) {
const primitive = raw.prim;
switch (primitive.$) {
case "InstructionBlock":
return {
$: "Argument",
expression: {
$: "Code",
instructions: primitive.instructions.flatMap(it => compileInstruction(ctx, it)),
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "String":
return {
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "StringLiteral",
value: primitive.content,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "HexBitString":
return {
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "HexLiteral",
content: primitive.content,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "BinBitString":
return {
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "BinLiteral",
content: primitive.content,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "FiftAddressNone":
return {
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "BinLiteral",
content: "00",
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "BocHex":
return {
$: "Argument",
expression: {
$: "DataLiteral",
value: {
$: "BocLiteral",
content: primitive.content,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "StackRegister":
return {
$: "Argument",
expression: {
$: "StackElement",
value: primitive.value,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "ControlRegister":
return {
$: "Argument",
expression: {
$: "ControlRegister",
value: primitive.value,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "Integer":
return {
$: "Argument",
expression: {
$: "IntegerLiteral",
value: {
$: primitive.value.startsWith("0b")
? "IntegerLiteralBin"
: primitive.value.startsWith("0x")
? "IntegerLiteralHex"
: primitive.value.startsWith("0o")
? "IntegerLiteralOct"
: "IntegerLiteralDec",
digits: primitive.value,
loc: $.emptyLoc(0),
},
op: undefined,
loc: $.emptyLoc(0),
},
loc: $.emptyLoc(0),
};
case "ArgIdentifier":
const prevValue = ctx.usedFunctions.get(primitive.name) ?? 0;
ctx.usedFunctions.set(primitive.name, prevValue + 1);
const declIdx = ctx.functions.get(primitive.name) ?? ctx.globals.get(primitive.name);
if (declIdx === undefined) {
throw new Error(`Unknown name ${primitive.name}`);
}
return integerArgument(declIdx);
}
throw new Error("Unexpected error");
}
//# sourceMappingURL=compile.js.map