Continuation
Первичная вычислительная сущность TVM: «продолжение» — указатель на отрезок кода + контекст исполнения. Заменяет привычные процедуры и функции — все control-flow примитивы TVM строятся на continuation'ах.
Синонимы: continuation tvm, продолжение tvm
Continuation — фундаментальная вычислительная сущность TVM, заменяющая привычные понятия «функция», «процедура», «return-address». Это структура, содержащая указатель на отрезок кода + контекст исполнения (стек, регистры).
Зачем
В TVM нет инструкции CALL в Z80/x86-смысле — есть только манипуляции с continuation’ами. IFJMPREF { ... }, CALLREF, RETALT и т.д. — все они работают на continuation-уровне. Это даёт:
- Высшие функции в FunC/Tact/Tolk «бесплатно»: continuation == first-class value.
- Try/catch через
c2-альтернативный continuation. - Loop’ы через recursion или через
WHILE { c1 } { c2 }-комбинаторы.
Особенность для приходящих с EVM
Для разработчиков, приходящих с EVM, это самый необычный концепт TVM. Рекомендуется сразу почитать TVM Whitepaper §4 («Control flow, continuations, exceptions»), прежде чем писать собственный нетривиальный контракт. Без понимания continuation’ов невозможно прочитать TASM моста или биржи.
Регистры c0-c5: где живут continuations
TVM держит continuation’ы в специальных control-регистрах:
| Регистр | Назначение |
|---|---|
| c0 | Return continuation — куда прыгает RET/RETALT после завершения |
| c1 | Alternative continuation — для RETALT, отвечает за исключения |
| c2 | Exception handler — THROW дёргает именно его |
| c3 | Dictionary с procedure-IDs — служит как «функциональная таблица» контракта |
| c4 | Persistent storage cell — то, что записывается обратно в state |
| c5 | Actions output — список исходящих сообщений |
При входе в get-method или receive-функцию runtime подкладывает c0 так, чтобы RET корректно завершил выполнение и вернул tuple результатов.
Пример: try/catch через c2
В FunC обработка ошибок выглядит как два continuation’а — нормальный поток и handler:
try {
throw_unless(error_code::not_owner, equal_slices(sender, owner));
do_owner_only_thing();
} catch (_, exno) {
send_text_message(sender, "Access denied");
}
Компилятор разворачивает это в TVM-инструкции: PUSHCONT { ... } PUSHCONT { ... } TRY — два continuation’а кладутся на стек, TRY ставит второй в c2 и выполняет первый.
Пример: get-method ABI на continuation’ах
c3 — это словарь, в котором ключ — method-ID (CRC16 от имени), значение — continuation тела метода. Когда tonapi вызывает runGetMethod, TVM делает:
PUSHINT <method_id>
PUSH c3
DICTPUSHCONT ; ищет в словаре, кладёт continuation
EXECUTE ; вызывает
Если хочется добавить custom method в собственный контракт — нужно добавить запись в c3-словарь на этапе компиляции (global_method_id(...) в FunC, get fun в Tact).
Сравнение с EVM/Move
| Концепт | EVM | Move | TVM |
|---|---|---|---|
| Вызов функции | CALL opcode, stack frame | Call bytecode, frame | PUSHCONT + CALLREF — continuation as value |
| Возврат | RETURN | Ret | RET → c0 |
| Исключения | REVERT | abort | THROW → c2 |
| Higher-order | Не поддерживается до Cancun (function pointer hack) | Generics + closures | Built-in: continuation == first-class |
EVM-разработчик привык к «методу с адресом в bytecode» — в TVM нужный кусок кода передаётся как параметр.
Где смотреть continuation’ы при дебаге
- TON Sandbox (
@ton/sandbox) — при execute step-by-step видны регистры c0/c1/c2 после каждой инструкции. - tonscan execution trace — показывает финальное значение c4 (storage delta) и c5 (out_msgs).
- func-js compiler —debug-info — помечает места, где
PUSHCONTсоответствует исходной функции.