ton-assembly
Version:
TON assembler and disassembler
979 lines • 31.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fIF = exports.fTHROWIFNOT = exports.fTHROWIF = exports.fTHROW = exports.fPREPAREDICT = exports.fJMPDICT = exports.fCALLDICT = exports.fCALLXARGS = exports.fSDBEGINSQ = exports.fSDBEGINS = exports.fPUSHINTX = exports.fXCHG = exports.fSTSLICECONST = exports.fPUSHCONT = exports.fPUSHSLICE = exports.fPOP = exports.fPUSH = exports.fPUSHINT = void 0;
const types_1 = require("./types");
const instr_1 = require("./instr");
const util_1 = require("./util");
const builder_1 = require("./builder");
const compile_1 = require("./compile");
const fits = (val, bits) => val >= BigInt(-Math.pow(2, bits - 1)) && val <= BigInt(Math.pow(2, bits - 1) - 1);
exports.fPUSHINT = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "PUSHINT_4" || loaded.$ === "PUSHINT_8" || loaded.$ === "PUSHINT_16") {
return {
$: "fPUSHINT",
arg0: BigInt(loaded.arg0),
loc: loaded.loc,
};
}
if (loaded.$ === "PUSHINT_LONG") {
return {
$: "fPUSHINT",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected PUSHINT variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= -5 && arg <= 10) {
types_1.PUSHINT_4.store(b, {
...val,
arg0: Number(arg),
$: "PUSHINT_4",
}, options);
return;
}
if (fits(arg, 8)) {
types_1.PUSHINT_8.store(b, {
...val,
arg0: Number(arg),
$: "PUSHINT_8",
}, options);
return;
}
if (fits(arg, 16)) {
types_1.PUSHINT_16.store(b, {
...val,
arg0: Number(arg),
$: "PUSHINT_16",
}, options);
return;
}
types_1.PUSHINT_LONG.store(b, {
...val,
$: "PUSHINT_LONG",
}, options);
},
};
exports.fPUSH = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "PUSH" || loaded.$ === "PUSH_LONG") {
return {
$: "fPUSH",
arg0: loaded.arg0,
kind: "stack",
loc: loaded.loc,
};
}
if (loaded.$ === "PUSHCTR") {
return {
$: "fPUSH",
arg0: loaded.arg0,
kind: "control",
loc: loaded.loc,
};
}
throw new Error("unexpected PUSH variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (val.kind === "control") {
types_1.PUSHCTR.store(b, {
...val,
arg0: arg,
$: "PUSHCTR",
}, options);
return;
}
if (arg < 16) {
types_1.PUSH.store(b, {
...val,
arg0: arg,
$: "PUSH",
}, options);
return;
}
types_1.PUSH_LONG.store(b, {
...val,
arg0: arg,
$: "PUSH_LONG",
}, options);
},
};
exports.fPOP = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "POP" || loaded.$ === "POP_LONG") {
return {
$: "fPOP",
arg0: loaded.arg0,
kind: "stack",
loc: loaded.loc,
};
}
if (loaded.$ === "POPCTR") {
return {
$: "fPOP",
arg0: loaded.arg0,
kind: "control",
loc: loaded.loc,
};
}
throw new Error("unexpected POP variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (val.kind === "control") {
types_1.POPCTR.store(b, {
...val,
arg0: arg,
$: "POPCTR",
}, options);
return;
}
if (arg < 16) {
types_1.POP.store(b, {
...val,
arg0: arg,
$: "POP",
}, options);
return;
}
types_1.POP_LONG.store(b, {
...val,
arg0: arg,
$: "POP_LONG",
}, options);
},
};
exports.fPUSHSLICE = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "PUSHSLICE" ||
loaded.$ === "PUSHSLICE_REFS" ||
loaded.$ === "PUSHSLICE_LONG") {
return {
$: "fPUSHSLICE",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected PUSHSLICE variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (!b.canFit(val.arg0.remainingBits + 26)) {
// cannot place slice and instruction inline
types_1.PUSHREFSLICE.store(b, {
...val,
arg0: (0, util_1.rawCode)(arg),
$: "PUSHREFSLICE",
}, options);
return;
}
if (arg.remainingRefs === 0 && arg.remainingBits <= 123) {
types_1.PUSHSLICE.store(b, {
...val,
arg0: arg,
$: "PUSHSLICE",
}, options);
return;
}
if (arg.remainingRefs > 0 && arg.remainingRefs < 3 && arg.remainingBits <= 248) {
types_1.PUSHSLICE_REFS.store(b, {
...val,
arg0: arg,
$: "PUSHSLICE_REFS",
}, options);
return;
}
types_1.PUSHSLICE_LONG.store(b, {
...val,
arg0: arg,
$: "PUSHSLICE_LONG",
}, options);
},
};
exports.fPUSHCONT = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "PUSHCONT" ||
loaded.$ === "PUSHCONT_SHORT" ||
loaded.$ === "PUSHREFCONT") {
return {
$: "fPUSHCONT",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected PUSHCONT variant");
},
store: (b, val, options) => {
const arg = val.arg0;
const b2 = new builder_1.CodeBuilder();
if (arg.$ === "Instructions") {
(0, compile_1.compileInstructions)(b2, arg.instructions, options);
}
else {
(0, util_1.codeSlice)((0, util_1.uint)(2), (0, util_1.uint)(7)).store(b2, arg, options);
}
const codeAsSlice = b2.asSlice();
if (!b.canFit(codeAsSlice.remainingBits)) {
types_1.PUSHREFCONT.store(b, {
...val,
arg0: arg,
$: "PUSHREFCONT",
}, options);
return;
}
if (codeAsSlice.remainingRefs === 0 && codeAsSlice.remainingBits <= 120) {
types_1.PUSHCONT_SHORT.store(b, {
...val,
arg0: arg,
$: "PUSHCONT_SHORT",
}, options);
return;
}
types_1.PUSHCONT.store(b, {
...val,
arg0: arg,
$: "PUSHCONT",
}, options);
},
};
exports.fSTSLICECONST = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "STSLICECONST") {
return {
$: "fSTSLICECONST",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected STSLICECONST variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (!b.canFit(arg.remainingBits + 22)) {
// cannot place slice and instruction inline
exports.fPUSHSLICE.store(b, {
...val,
$: "fPUSHSLICE",
}, options);
types_1.STSLICER.store(b, {
$: "STSLICER",
loc: val.loc,
}, options);
return;
}
if (arg.remainingBits <= 57 && arg.remainingRefs <= 3) {
types_1.STSLICECONST.store(b, {
...val,
$: "STSLICECONST",
}, options);
return;
}
exports.fPUSHSLICE.store(b, {
...val,
$: "fPUSHSLICE",
}, options);
types_1.STSLICER.store(b, {
$: "STSLICER",
loc: val.loc,
}, options);
},
};
exports.fXCHG = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "XCHG_0I" || loaded.$ === "XCHG_0I_LONG") {
return {
$: "fXCHG",
arg0: 0,
arg1: loaded.arg0,
loc: loaded.loc,
};
}
if (loaded.$ === "XCHG_IJ") {
return {
$: "fXCHG",
arg0: loaded.arg0,
arg1: loaded.arg1,
loc: loaded.loc,
};
}
throw new Error("unexpected XCHG variant");
},
store: (b, val, options) => {
const i = val.arg0;
const j = val.arg1;
if (i === j) {
// XCHG with same indices = NOP, just return
return;
}
// Ensure i <= j for consistent representation
const [arg0, arg1] = i <= j ? [i, j] : [j, i];
if (arg1 === 0) {
// One of arguments is 0, this shouldn't happen in normalized form
throw new Error("XCHG with zero argument should use XCHG_0I");
}
if (arg0 === 0) {
// XCHG s0, sj
if (arg1 < 16) {
types_1.XCHG_0I.store(b, {
...val,
arg0: arg1,
$: "XCHG_0I",
}, options);
return;
}
else {
types_1.XCHG_0I_LONG.store(b, {
...val,
arg0: arg1,
$: "XCHG_0I_LONG",
}, options);
return;
}
}
// XCHG s1 s2
if (arg0 === 1 && arg1 < 16) {
types_1.XCHG_1I.store(b, {
...val,
arg0: arg0,
arg1: arg1,
$: "XCHG_1I",
}, options);
return;
}
// XCHG si, sj where both i,j > 0
if (arg0 < 16 && arg1 < 16) {
types_1.XCHG_IJ.store(b, {
...val,
arg0: arg0,
arg1: arg1,
$: "XCHG_IJ",
}, options);
return;
}
// For large indices, we need to emit multiple XCHG_0I_LONG operations
// XCHG si, sj = XCHG_0I_LONG si, XCHG_0I_LONG sj, XCHG_0I_LONG si
types_1.XCHG_0I_LONG.store(b, {
$: "XCHG_0I_LONG",
arg0: arg0,
loc: val.loc,
}, options);
types_1.XCHG_0I_LONG.store(b, {
$: "XCHG_0I_LONG",
arg0: arg1,
loc: val.loc,
}, options);
types_1.XCHG_0I_LONG.store(b, {
$: "XCHG_0I_LONG",
arg0: arg0,
loc: val.loc,
}, options);
},
};
const pow2Decomp = (n) => {
let powers = 0;
let remainder = n;
while (remainder % 2n === 0n && remainder !== 0n) {
powers++;
remainder = remainder / 2n;
}
return [remainder, powers];
};
exports.fPUSHINTX = {
load: s => {
// This will load any PUSHINT* variant and convert to fPUSHINTX
const loaded = instr_1.instr.load(s);
if (loaded.$ === "PUSHINT_4" ||
loaded.$ === "PUSHINT_8" ||
loaded.$ === "PUSHINT_16" ||
loaded.$ === "PUSHINT_LONG" ||
loaded.$ === "PUSHPOW2" ||
loaded.$ === "PUSHNEGPOW2" ||
loaded.$ === "PUSHPOW2DEC") {
return {
$: "fPUSHINTX",
arg0: loaded.$ === "PUSHINT_LONG" ? loaded.arg0 : BigInt(loaded.arg0),
loc: loaded.loc,
};
}
throw new Error("unexpected PUSHINTX variant");
},
store: (b, val, options) => {
const arg = val.arg0;
// Try power of 2 optimizations first
const [remainder, powers] = pow2Decomp(arg);
if (remainder === 1n && arg > 0n) {
// arg = 2^powers
if (arg === 256n) {
throw new Error("use PUSHNAN instead of 256 PUSHPOW2");
}
types_1.PUSHPOW2.store(b, {
...val,
arg0: powers,
$: "PUSHPOW2",
}, options);
return;
}
if (remainder === -1n) {
// arg = -2^powers
types_1.PUSHNEGPOW2.store(b, {
...val,
arg0: powers,
$: "PUSHNEGPOW2",
}, options);
return;
}
// Try 2^n - 1 optimization
const plusOne = arg + 1n;
const [remainderPlus, powersPlus] = pow2Decomp(plusOne);
if (remainderPlus === 1n) {
types_1.PUSHPOW2DEC.store(b, {
...val,
arg0: powersPlus,
$: "PUSHPOW2DEC",
}, options);
return;
}
// Check if it fits in 8 bits (basic PUSHINT logic)
if (fits(arg, 8)) {
exports.fPUSHINT.store(b, {
...val,
$: "fPUSHINT",
}, options);
return;
}
// For large powers, use PUSHINT + LSHIFT
if (powers >= 20) {
exports.fPUSHINT.store(b, {
...val,
arg0: remainder,
$: "fPUSHINT",
}, options);
types_1.LSHIFT.store(b, {
$: "LSHIFT",
arg0: powers,
loc: val.loc,
}, options);
return;
}
// Fall back to regular PUSHINT
exports.fPUSHINT.store(b, {
...val,
$: "fPUSHINT",
}, options);
},
};
exports.fSDBEGINS = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "SDBEGINSX") {
// This is a fallback case, we don't have the original slice
throw new Error("Cannot load SDBEGINSX back to fSDBEGINS");
}
throw new Error("unexpected SDBEGINS variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg.remainingRefs > 0) {
throw new Error("no references allowed in slice");
}
const sliceBits = arg.remainingBits;
const opcodeBits = Math.floor((sliceBits + 5) / 8) * 8 + 3 + 21;
if (b.canFit(opcodeBits)) {
types_1.SDBEGINS.store(b, {
...val,
$: "SDBEGINS",
}, options);
}
else {
exports.fPUSHSLICE.store(b, {
...val,
$: "fPUSHSLICE",
}, options);
types_1.SDBEGINSX.store(b, {
$: "SDBEGINSX",
loc: val.loc,
}, options);
}
},
};
exports.fSDBEGINSQ = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "SDBEGINSXQ") {
// This is a fallback case, we don't have the original slice
throw new Error("Cannot load SDBEGINSXQ back to fSDBEGINSQ");
}
throw new Error("unexpected SDBEGINSQ variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg.remainingRefs > 0) {
throw new Error("no references allowed in slice");
}
const sliceBits = arg.remainingBits;
const opcodeBits = Math.floor((sliceBits + 5) / 8) * 8 + 3 + 21;
if (b.canFit(opcodeBits)) {
types_1.SDBEGINSQ.store(b, {
...val,
$: "SDBEGINSQ",
}, options);
}
else {
exports.fPUSHSLICE.store(b, {
...val,
$: "fPUSHSLICE",
}, options);
types_1.SDBEGINSXQ.store(b, {
$: "SDBEGINSXQ",
loc: val.loc,
}, options);
}
},
};
exports.fCALLXARGS = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "CALLXARGS" || loaded.$ === "CALLXARGS_1") {
return {
$: "fCALLXARGS",
arg0: loaded.arg0,
arg1: loaded.arg1,
loc: loaded.loc,
};
}
throw new Error("unexpected CALLXARGS variant");
},
store: (b, val, options) => {
if (val.arg1 === -1) {
types_1.CALLXARGS.store(b, {
...val,
$: "CALLXARGS",
}, options);
}
else {
types_1.CALLXARGS_1.store(b, {
...val,
$: "CALLXARGS_1",
}, options);
}
},
};
exports.fCALLDICT = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "CALLDICT" || loaded.$ === "CALLDICT_LONG") {
return {
$: "fCALLDICT",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected CALLDICT variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= 0 && arg <= 255) {
types_1.CALLDICT.store(b, {
...val,
$: "CALLDICT",
}, options);
}
else if (arg >= 0 && arg <= 16383) {
types_1.CALLDICT_LONG.store(b, {
...val,
$: "CALLDICT_LONG",
}, options);
}
else {
exports.fPUSHINT.store(b, {
$: "fPUSHINT",
arg0: BigInt(arg),
loc: val.loc,
}, options);
types_1.PUSHCTR.store(b, {
$: "PUSHCTR",
arg0: 3,
loc: val.loc,
}, options);
types_1.EXECUTE.store(b, {
$: "EXECUTE",
loc: val.loc,
}, options);
}
},
};
exports.fJMPDICT = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "JMPDICT") {
return {
$: "fJMPDICT",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected JMPDICT variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= 0 && arg <= 16383) {
types_1.JMPDICT.store(b, {
...val,
$: "JMPDICT",
}, options);
}
else {
exports.fPUSHINT.store(b, {
$: "fPUSHINT",
arg0: BigInt(arg),
loc: val.loc,
}, options);
types_1.PUSHCTR.store(b, {
$: "PUSHCTR",
arg0: 3,
loc: val.loc,
}, options);
types_1.JMPX.store(b, {
$: "JMPX",
loc: val.loc,
}, options);
}
},
};
exports.fPREPAREDICT = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "PREPAREDICT") {
return {
$: "fPREPAREDICT",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected PREPAREDICT variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= 0 && arg <= 16383) {
types_1.PREPAREDICT.store(b, {
...val,
$: "PREPAREDICT",
}, options);
}
else {
exports.fPUSHINT.store(b, {
$: "fPUSHINT",
arg0: BigInt(arg),
loc: val.loc,
}, options);
types_1.PUSHCTR.store(b, {
$: "PUSHCTR",
arg0: 3,
loc: val.loc,
}, options);
}
},
};
exports.fTHROW = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "THROW_SHORT" || loaded.$ === "THROW") {
return {
$: "fTHROW",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected THROW variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= 0 && arg <= 63) {
types_1.THROW_SHORT.store(b, {
...val,
$: "THROW_SHORT",
}, options);
}
else if (arg >= 0 && arg <= 2047) {
types_1.THROW.store(b, {
...val,
$: "THROW",
}, options);
}
else {
throw new Error("THROW argument out of range");
}
},
};
exports.fTHROWIF = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "THROWIF_SHORT" || loaded.$ === "THROWIF") {
return {
$: "fTHROWIF",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected THROWIF variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= 0 && arg <= 63) {
types_1.THROWIF_SHORT.store(b, {
...val,
$: "THROWIF_SHORT",
}, options);
}
else if (arg >= 0 && arg <= 2047) {
types_1.THROWIF.store(b, {
...val,
$: "THROWIF",
}, options);
}
else {
throw new Error("THROWIF argument out of range");
}
},
};
exports.fTHROWIFNOT = {
load: s => {
const loaded = instr_1.instr.load(s);
if (loaded.$ === "THROWIFNOT_SHORT" || loaded.$ === "THROWIFNOT") {
return {
$: "fTHROWIFNOT",
arg0: loaded.arg0,
loc: loaded.loc,
};
}
throw new Error("unexpected THROWIFNOT variant");
},
store: (b, val, options) => {
const arg = val.arg0;
if (arg >= 0 && arg <= 63) {
types_1.THROWIFNOT_SHORT.store(b, {
$: "THROWIFNOT_SHORT",
arg0: arg,
loc: val.loc,
}, options);
}
else if (arg >= 0 && arg <= 2047) {
types_1.THROWIFNOT.store(b, {
$: "THROWIFNOT",
arg0: arg,
loc: val.loc,
}, options);
}
else {
throw new Error("THROWIFNOT argument out of range");
}
},
};
const isCodeEmpty = (code) => {
if (code.$ === "Instructions") {
return code.instructions.length === 0;
}
return code.slice.remainingBits === 0 && code.slice.remainingRefs === 0;
};
const canCodeFit = (b, code, options) => {
if (isCodeEmpty(code)) {
return true;
}
const b2 = new builder_1.CodeBuilder();
if (code.$ === "Instructions") {
(0, compile_1.compileInstructions)(b2, code.instructions, options);
}
else {
(0, util_1.codeSlice)((0, util_1.uint)(2), (0, util_1.uint)(7)).store(b2, code, options);
}
const codeAsSlice = b2.asSlice();
return b.canFit(codeAsSlice.remainingBits + 16) && b.refs + codeAsSlice.remainingRefs <= 4;
};
const canCodesFit = (b, code, code2, options) => {
const b2 = new builder_1.CodeBuilder();
if (code.$ === "Instructions") {
(0, compile_1.compileInstructions)(b2, code.instructions, options);
}
else {
(0, util_1.codeSlice)((0, util_1.uint)(2), (0, util_1.uint)(7)).store(b2, code, options);
}
if (code2.$ === "Instructions") {
(0, compile_1.compileInstructions)(b2, code2.instructions, options);
}
else {
(0, util_1.codeSlice)((0, util_1.uint)(2), (0, util_1.uint)(7)).store(b2, code2, options);
}
const codeAsSlice = b2.asSlice();
return b.canFit(codeAsSlice.remainingBits + 16) && b.refs + codeAsSlice.remainingRefs <= 4;
};
const canCodeFitAsRef = (b) => {
// Check if we have space for reference (16 + 1 bits for ref header)
return b.canFit(17);
};
exports.fIF = {
load: _s => {
throw new Error("fIF load is not implemented - use specific IF instructions");
},
store: (b, val, options) => {
const { kind, trueBranch, falseBranch, loc } = val;
if (kind === "IFELSE") {
if (!falseBranch) {
throw new Error("IFELSE requires falseBranch");
}
// Handle IFELSE with complex logic from IFELSE-cont2
const trueEmpty = isCodeEmpty(trueBranch);
const falseEmpty = isCodeEmpty(falseBranch);
if (trueEmpty) {
// Simplify to IFNOT
exports.fIF.store(b, { ...val, kind: "IFNOT", trueBranch: falseBranch, falseBranch: undefined }, options);
return;
}
if (falseEmpty) {
// Simplify to IF
exports.fIF.store(b, { ...val, kind: "IF", falseBranch: undefined }, options);
return;
}
const trueFits = canCodeFit(b, trueBranch, options);
const falseFits = canCodeFit(b, falseBranch, options);
const bothFits = canCodesFit(b, trueBranch, falseBranch, options);
if (bothFits) {
// Both fit inline - use PUSHCONT + PUSHCONT + IFELSE
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: trueBranch, loc }, options);
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: falseBranch, loc }, options);
types_1.IFELSE.store(b, { $: "IFELSE", loc }, options);
return;
}
if (trueFits && falseFits && canCodeFitAsRef(b)) {
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: falseBranch, loc }, options);
types_1.IFREFELSE.store(b, {
$: "IFREFELSE",
arg0: trueBranch,
loc,
}, options);
return;
}
if (!trueFits && falseFits && canCodeFitAsRef(b)) {
// True as ref, false inline - use IFREFELSE
// falseBranch will be inline, trueBranch as ref
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: falseBranch, loc }, options);
types_1.IFREFELSE.store(b, {
$: "IFREFELSE",
arg0: trueBranch,
loc,
}, options);
return;
}
if (trueFits && !falseFits && canCodeFitAsRef(b)) {
// True inline, false as ref - use IFELSEREF
// trueBranch will be inline, falseBranch as ref
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: trueBranch, loc }, options);
types_1.IFELSEREF.store(b, {
$: "IFELSEREF",
arg0: falseBranch,
loc,
}, options);
return;
}
if (!trueFits && !falseFits && canCodeFitAsRef(b) && canCodeFitAsRef(b)) {
// Both as refs - use IFREFELSEREF
types_1.IFREFELSEREF.store(b, {
$: "IFREFELSEREF",
arg0: trueBranch,
arg1: falseBranch,
loc,
}, options);
return;
}
// PSEUDO_PUSHREF_ALWAYS.store(
// b,
// PSEUDO_PUSHREF(code([{$: "fIF", kind, trueBranch, falseBranch, loc}])),
// options,
// )
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: trueBranch, loc }, options);
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: falseBranch, loc }, options);
types_1.IFELSE.store(b, { $: "IFELSE", loc }, options);
return;
}
// Handle simple IF/IFNOT/IFJMP/IFNOTJMP
const isEmpty = isCodeEmpty(trueBranch);
if (isEmpty) {
// Empty continuation optimizations
if (kind === "IFJMP") {
types_1.IFRET.store(b, { $: "IFRET", loc }, options);
return;
}
if (kind === "IFNOTJMP") {
types_1.IFNOTRET.store(b, { $: "IFNOTRET", loc }, options);
return;
}
// For IF/IFNOT with empty continuation, do nothing
return;
}
const fits = canCodeFit(b, trueBranch, options);
if (fits) {
// Fits inline - use PUSHCONT + instruction
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: trueBranch, loc }, options);
switch (kind) {
case "IF":
types_1.IF.store(b, { $: "IF", loc }, options);
break;
case "IFNOT":
types_1.IFNOT.store(b, { $: "IFNOT", loc }, options);
break;
case "IFJMP":
types_1.IFJMP.store(b, { $: "IFJMP", loc }, options);
break;
case "IFNOTJMP":
types_1.IFNOTJMP.store(b, { $: "IFNOTJMP", loc }, options);
break;
}
}
else if (canCodeFitAsRef(b)) {
// Use reference version
switch (kind) {
case "IF":
types_1.IFREF.store(b, { $: "IFREF", arg0: trueBranch, loc }, options);
break;
case "IFNOT":
types_1.IFNOTREF.store(b, { $: "IFNOTREF", arg0: trueBranch, loc }, options);
break;
case "IFJMP":
types_1.IFJMPREF.store(b, { $: "IFJMPREF", arg0: trueBranch, loc }, options);
break;
case "IFNOTJMP":
types_1.IFNOTJMPREF.store(b, { $: "IFNOTJMPREF", arg0: trueBranch, loc }, options);
break;
}
}
else {
// Fallback - force PUSHCONT even if it doesn't fit well
exports.fPUSHCONT.store(b, { $: "fPUSHCONT", arg0: trueBranch, loc }, options);
switch (kind) {
case "IF":
types_1.IF.store(b, { $: "IF", loc }, options);
break;
case "IFNOT":
types_1.IFNOT.store(b, { $: "IFNOT", loc }, options);
break;
case "IFJMP":
types_1.IFJMP.store(b, { $: "IFJMP", loc }, options);
break;
case "IFNOTJMP":
types_1.IFNOTJMP.store(b, { $: "IFNOTJMP", loc }, options);
break;
}
}
},
};
//# sourceMappingURL=fift-instr.js.map