Почему расчет адреса перехода такой сложный в скомпилированном коде Solidity?

У меня есть следующий простой контракт Solidity:

pragma solidity ^0.4.0;
contract Test {

   function Test() {
       intfunc(5);
   }

   uint8 store;

   function intfunc (uint8 a) internal {
       store = a * 9;
   }
}

Я компилирую его с помощью Remix и получаю байт-код, который не могу объяснить между адресами 0x11 и 0x1E (для удобства я включаю шестнадцатеричные адреса слева):

//Standard preamble:
0x00: PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH1 0xB JUMPI INVALID

//Beginning of Test() constructor:
0x0B: JUMPDEST JUMPDEST PUSH1 0x20 PUSH1 0x5

//Here's the really strange code:
0x11: PUSH5 0x100000000
0x17: PUSH1 0x7
0x19: PUSH1 0x25
0x1B: DUP3
0x1C: MUL
0x1D: OR
0x1E: DIV

//Here we jump to the intfunc() function
0x1F: JUMP

//Here we come back from intfunc() and jump to rest of the Test() constructor
0x20: JUMPDEST JUMPDEST PUSH1 0x3B JUMP

//intfunc() itself:
0x25: JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH1 0xFF NOT AND PUSH1 0x9 DUP4 MUL PUSH1 0xFF AND OR SWAP1 SSTORE JUMPDEST POP JUMP

//The rest of the Test() constructor and the rest of the code is here...
0x3B: JUMPDEST ...
//(The rest isn't really relevant to this question)

Что делать с кодом между 0x11 и 0x1E - почему он такой громоздкий? Нельзя ли просто заменить его простым « PUSH1 0x25 »? Разве это не пустая трата газа — выполнять все эти странные шаги только для того, чтобы вычислить значение 0x25?

Более того, откуда цифра 7 в инструкции 0x17? Это кажется совершенно бессмысленным.

Обратите внимание, что я получаю одинаковый код как для «оптимизированного», так и для «неоптимизированного» режима Remix.

Любое понимание будет оценено!

Интересует ответ на это. Вы когда-нибудь продвигались дальше? :-)
К сожалению нет
Согласна со всеми вашими пунктами! Это очень своеобразно. Вы должны посмотреть на тот же код, скомпилированный компилятором версии 0.4.0 в Remix: гораздо лаконичнее, и никакой чепухи. Это одна из причин, по которой я изучаю LLL ; компилятор очень мало вмешивается.

Ответы (2)

Это поведение было представлено в этом Github PR . Похоже, это связано с оптимизацией хранения тегов для вызовов функций в контексте конструктора.

Сдвиг влево на 32 бита MUL 0x0100000000и ORоперации вставляются функцией pushCombinedFunctionEntryLabelв файле libsolidity/codegen/CompilerUtils.cpp . Последующий сдвиг вправо на 32 бита вставляется ExpressionCompiler.cpp , где он вызывает rightShiftNumberOnStack .

Вероятно, это действительно полезно в некоторых случаях для упаковки этикеток перехода, которые необходимо хранить в контрактном хранилище (и, таким образом, экономить много газа - хранение дорого). В случае с этим простым контрактом это просто кажется ненужным остатком.

если мы составим простой контракт, например:

    contract C {

   uint store=45;

    }

мы получим (я использую компилятор 0.4):

00 PUSH1 60
02 PUSH1 40
04 MSTORE

05 PUSH1 2d //value to store 45
07 PUSH1 00 //storage address
09 PUSH1 00 // useless
11 POP      //useless
12 SSTORE

однако, если мы изменим ситуацию на изменение uint. uint8вместо этого мы получим более длинный байт-код:

00 PUSH1 60
02 PUSH1 40
04 MSTORE

05 PUSH1 2d //value
07 PUSH1 00 //storage address
09 PUSH1 00//mask offset
11 PUSH2 0100// multiplier
14 EXP
15 DUP2
16 SLOAD
17 DUP2
18 PUSH1 ff
20 MUL
21 NOT
22 AND
23 SWAP1
24 DUP4
25 MUL
26 OR
27 SWAP1
28 SSTORE
29 POP

Так в чем проблема?
когда мы используем uint, мы используем непосредственно 32-байтовое слово, однако, когда мы используем uint8, нам нужен только первый байт в слове хранения, чтобы поместить его вместе с другими значениями в слот хранения, поэтому нам нужно выполнить некоторые манипуляции, чтобы избежать перезаписи данных.

Я думаю, что компилятор дополнит 0x2d до 32 байт, поэтому другие значения в слоте будут перезаписаны, и мы оставим только первый байт (2d). Чтобы избежать этой проблемы, мы используем sload, чтобы загрузить предыдущее значение, присутствующее в слове, и мы используем битовые операции (MUL NOT AND SWAP1 DUP4 MUL OR) для вычисления (используемые div и mul предназначены для сдвига значений) новое значение, которое будет сохранено в слот, объединяющий дополненное значение 0x2d00000...00000 и предыдущее значение, в конце мы вызываем sstoreсохранение результата.

Это не имеет ничего общего с вычислением адреса перехода в вопросе.
@benjaminion вы читали эти вопросы: Что делать с кодом между 0x11 и 0x1E - почему он такой громоздкий? Разве это не пустая трата газа - выполнять все эти странные шаги только для вычисления значения 0x25? Более того, откуда цифра 7 в инструкции 0x17? Это кажется совершенно бессмысленным.
Да, конечно, и я думаю, что мой ответ охватывает это. Это не имеет никакого отношения uint8- попробуйте скомпилировать код OP с uint16 или больше, это не меняет поведение этого расчета перехода между 0x11 и 01E.
@benjaminion вы были правы, я поторопился с ответом, потому что думал, что это связано с uint8 (в старом компиляторе), я не заметил адреса кода операции. Я награжу тебя наградой.
Спасибо, ценю. Было много работы по копанию в исходном коде компилятора и Github, чтобы исследовать это. По сути, это просто остатки неудачной оптимизации.