EIP-5656: опкод копирования памяти, которого у EVM никогда не было
Копирование диапазона памяти — одна из самых базовых вещей, которые делает программа, — и всё же годами у EVM не было для этого инструкции. Контракты либо гоняли многословный цикл MLOAD/MSTORE, либо злоупотребляли identity-прекомпайлом как окольным memcpy. EIP-5656 наконец добавляет MCOPY: один опкод, одно копирование.
- модель памяти EVM
- прекомпайлы
Cancun / Deneb, март 2024. Вот отрезвляющий пробел. Почти в любой среде программирования копирование блока байт из одного места в другое — единый, дешёвый примитив — memcpy. У EVM есть память — плоский, расширяемый массив байт, — и контракты копируют её диапазоны постоянно: собирая calldata, ABI-кодируя возвращаемые значения, перетасовывая буферы в хеш-функции. И тем не менее за всю свою историю у EVM не было инструкции копирования память-в-память. Разработчики обходили эту дыру двумя неуклюжими способами, оба расточительны. EIP-5656 закрывает её одним новым опкодом. Давайте его выведем.
Проблема: у EVM были все способы копировать память, кроме хорошего
Чтобы скопировать N байт памяти, у контракта было два варианта, ни один не хорош. Первый — цикл MLOAD/MSTORE (MLOAD/MSTORE loop) Копирование памяти слово за словом: MLOAD читает 32 байта в стек, MSTORE записывает их в место назначения, повторяется для каждого слова. Работает, но это много опкодов на копирование — лишний газ и лишний байткод ради тривиальной операции. : прочитать 32 байта через MLOAD, записать их через MSTORE и повторять, слово за словом. Корректно, но сжигает кучу опкодов и газа и раздувает байткод контракта. Второй — печально известный хак: прекомпайл идентичности (identity precompile) Прекомпайл по адресу 0x04, возвращающий свой вход без изменений. Поскольку вызов к нему копирует вход в выход, контракты злоупотребляли им как memcpy — платя полные накладные расходы вызова за копирование и скрывая намерение. по адресу 0x04 возвращает всё, что вы ему отправите, так что его можно было вызвать и использовать возвращённые данные как копию — платя накладные расходы вызова, чтобы протащить memcpy из прекомпайла, никогда для этого не предназначавшегося. То, что экосистема опиралась на такие трюки, — тревожный знак: не хватало фундаментального примитива.
→ Шаг 2: добавляем ровно эту инструкцию.
EIP-5656: один опкод, одно копирование
Исправление такое же прямое, как и проблема. EIP-5656 добавляет опкод MCOPY Опкод 0x5e. Снимает со стека три элемента — смещение назначения, смещение источника и длину — и копирует столько байт внутри памяти за одну инструкцию, с корректной обработкой перекрывающихся источника и назначения (как memmove). (0x5e). Он снимает со стека три значения — destination, source и length — и копирует столько байт памяти из источника в назначение за одну инструкцию. Он корректно обрабатывает перекрывающиеся регионы (так же, как memmove), и оценивается, как другие операции копирования EVM: небольшая базовая стоимость плюс 3 газа за 32-байтовое слово, плюс любая стоимость расширения памяти. Ни цикла, ни вызова, ни хака — просто примитив по своей честной цене.
→ Шаг 3: существующие контракты дешевеют, не пошевелив и пальцем.
Выигрыш: маленький опкод с широким охватом
Поскольку копирование памяти повсюду — ABI-кодирование (ABI encoding) Стандартный способ, которым контракты раскладывают аргументы функций и возвращаемые значения в памяти. Он постоянно вовлекает копирование и перемещение диапазонов байт, так что более дешёвое копирование памяти удешевляет большую долю обычного выполнения контракта. , работа со строками и массивами и особенно криптографический код, перетасовывающий буферы, — более дешёвое копирование незаметно удешевляет и уменьшает большую долю выполнения контракта. И выигрыш почти автоматический: компиляторы (Solidity, Vyper) эмитируют MCOPY там, где раньше эмитировали цикл копирования, так что контракты дешевеют и становятся компактнее просто от перекомпиляции под более новый тулчейн. Хак с identity-прекомпайлом уходит в отставку, намерение становится явным в байткоде, и у EVM наконец есть memcpy, который у неё всегда должен был быть. Это тот же дух, что и у PUSH0: дать машине маленький, очевидный примитив, которого ей не хватало, — и всё, что построено сверху, становится чуть стройнее.