Читайте на GitHub: https://github.com/loredanacirstea/articles/blob/master/articles/Taylor-EVM_compiled.md
Тейлор начинал как интерпретируемый функциональный язык для EVM, рожденный в рамках нашего проекта Конвейер. Pipeline — это текстовый и визуальный язык для программирования на основе потока, в котором входы и выходы функций могут быть связаны для создания сложных атомарных операций. Эти атомарные операции интерпретируются нашим смарт-контрактом интерпретатора конвейерных графов, или графы могут быть развернуты как автономные смарт-контракты, оптимизированные для меньших затрат на транзакционный газ.
Вместе с Тейлором мы создали полный по Тьюрингу язык, который может интерпретироваться EVM, с операциями, которые могут выполняться как в стеке, так и во фреймах памяти. Это позволяет Тейлору выполнять рекурсивные операции, которые сейчас невозможны на Solidity или Юл (язык, на котором основана Solidity) (https://github.com/ethereum/solidity/issues/9622).
Например, Тейлор может интерпретировать эту рекурсивную реализацию ряда Фибоначчи с большим количеством итераций (ограниченным лимитом газа, а не лимитом стека), потому что, в отличие от Solidity или Yul, он может оставлять стек пустым между вызовами функций:
(def! fibonacci (fn* (n) (if (or (eq n 1) (eq n 2)) 1 (add(fibonacci (sub n 1)) (fibonacci (sub n 2)) ) )))
При создании интерпретатора Taylor и других интерпретаторов EVM ни Solidity, ни Yul не были достаточно гибкими, чтобы создавать их эффективно. Именно для этого мы создали макроязык — mASM. mASM производит сборку EVM.
Сегодня мы объявляем о следующем шаге Тейлора: в качестве компилируемого языка.
taylor-evm
— это промежуточный язык, который дает разработчикам доступ ко всем инструкциям EVM и к нескольким готовым композициям. Он компилируется в байт-код EVM, который можно использовать как смарт-контракт. Он заменяет mASM.
Его каноническая форма выглядит как (add 34 (add 3 4))
. Но он также имеет форму на основе отступов с необязательными скобками:
add 34 add 3 4
or
add 34 (add 3 4)
Вы можете напрямую управлять стеком:
(list 0x02 0x04 (dup 2) (swap 1) (add 5))
Вы можете получить доступ к потоку управления изменениями:
(if (eq 3 4) (add 0x03 0x04) (add 0x05 0x06)) switch 0x4444 (case 0x1000 (add 0x1 0x1)) (case 0x4444 (add 0x2 0x2)) (case 0x2000 (add 0x3 0x3)) (default 0x55)
Вы можете использовать петли. Ниже приведен эффективный нерекурсивный способ реализации функции Фибоначчи (fib):
(list 0x00 0x01 (loop 1 8 (list (add (dup 4) (dup 4)) (swap 3) (swap 4) (pop) ) ) (mem-store 0x00) (return 0x00 0x20) )
Следовательно, функцию Фибоначчи можно записать в виде смарт-контракта следующим образом:
define fib ["uint256"] ["uint256"] if (eq 0x0 (dup 1)) (pass 0x0) if (eq 0x1 (dup 1)) (pass 0x01) list add (fib (sub (dup 4) 1 )) (fib (sub (dup 3) 2 )) (swap 1) (pop) to-deploy list (include "fib") (fib (calldata-load 0x0)) (mem-store 0x00) (return 0x00 0x20)
Или, только с отступом, вот так:
if eq 0x1 dup 1 pass 0x01 list add fib sub dup 4 1 fib sub dup 3 2 swap 1 pop
Сгенерированный ассемблерный код (asm):
0x4b 0x0d 0x00 codecopy 0x4b 0x00 return stop after_do_fib_14 0x00 calldataload fib jump after_do_fib_14: 0x00 mstore 0x20 0x00 return fib: dup1 0x00 eq ifsource_9 jumpi elsesource_9: dup1 0x01 eq ifsource_8 jumpi elsesource_8: after_do_fib_13 0x02 dup3 sub fib jump after_do_fib_13: after_do_fib_12 0x01 dup4 sub fib jump after_do_fib_12: add swap1 pop endif_8 jump ifsource_8: fib_end jump endif_8: endif_9 jump ifsource_9: fib_end jump endif_9: fib_end: swap1 jump
Сравнение
Приведенный выше контракт функции Фибоначчи эквивалентен следующему контракту в Yul:
object "TestFib" { code { datacopy(0, dataoffset("Runtime"), datasize("Runtime")) return(0, datasize("Runtime")) } object "Runtime" { code { let input := calldataload(0x0) let res := fib(input) mstore(0x0, res) return(0x0, 0x20) function fib(nth) -> result { switch nth case 0 { result := 0 } case 1 { result := 1 } default { result := add(fib(sub(nth, 2)), fib(sub(nth, 1))) }}}}}
На данном этапе это статистика от нашего собственного отладчика Cometh EVM при вызове fib(16)
, fib(18)
, fib(19)
, fib(30)
:
Производительность скомпилированного Taylor по сравнению с Yul составляет стабильные 94% по газу и 89,7% по шагам выполнения с более важным запасом по использованию стека.
Скомпилированные и интерпретированные
Скомпилированная версия Taylor выполняет те же сценарии использования, что и Solidity и Yul, — оптимизация для выполнения транзакций в EVM.
Интерпретированная версия Taylor оптимизирована для обеспечения гибкости, возможности обновления и обработки вне сети, что подтверждено EVM.
Насколько нам известно внутреннее устройство Solidity, скомпилированный код генерируется Yul: он основан на Yul. Если нам удалось превзойти Yul, это единственное сравнение, которое нам нужно: производительность Solidity будет равна или ниже, чем у Yul. Для достижения максимальной производительности нам пришлось напрямую использовать язык ассемблера EVM (ASM), и для этой цели нам пришлось разработать собственный макроязык: mASM. И интерпретируемый Тейлор основан на mAsm, в то время как скомпилированный Тейлор построен (используя себя) прямо из ASM. Taylor в настоящее время используется как язык, интерпретируемый EVM, компилируемый для EVM и интерпретируемый JavaScript. Поэтому разработчик может использовать один и тот же язык для разработки приложений web3 dApp. Однако среди трех разновидностей Taylor скомпилированная версия (которую мы демонстрировали сегодня) является наиболее недоработанной: ведется работа над управлением стеком и памятью с намерением превзойти нашу текущую реализацию.
Демонстрационное видео
Эта технология была создана волонтерами Laurel Project и для них. Вы можете присоединиться к этой технической инициативе: https://forms.gle/WmSaSbxhHiiA2qZV7. https://www.reddit.com/r/provable_laurel/.