Escribí un contrato simple con una sola variable de estado con tipo uint
y el código ensamblador generado es bastante sencillo. Pero cuando cambié el tipo al uint32
ensamblaje generado se vuelve completamente diferente. Además uint32
, si marco la opción 'Habilitar Optimizador', el código de ensamblaje generado se modifica.
Preguntas:
Para su referencia, proporciono el contrato junto con el segmento de código ensamblador generado.
pragma solidity ^0.4.0;
contract Test
{
uint32 value = 10;
}
PUSH1 0x60
PUSH1 0x40
MSTORE
PUSH1 0xA
PUSH1 0x0
PUSH1 0x0
PUSH2 0x100
EXP
DUP2
SLOAD
DUP2
PUSH4 0xFFFFFFFF
MUL
NOT
AND
SWAP1
DUP4
PUSH4 0xFFFFFFFF
AND
MUL
OR
SWAP1
SSTORE
POP
CALLVALUE
PUSH1 0x0
JUMPI
PUSH1 0x60
PUSH1 0x40
MSTORE
PUSH1 0x0
DUP1
SLOAD
PUSH4 0xFFFFFFFF
NOT
AND
PUSH1 0xA
OR
SWAP1
SSTORE
CALLVALUE
PUSH1 0x0
JUMPI
Gracias, Shamik.
Otros han apuntado en la dirección correcta, pero permítanme tratar de responder específicamente a las preguntas.
En primer lugar, cada palabra de 256 bits de almacenamiento por contrato es muy costosa. Cuando un contrato utiliza variables más pequeñas, como uint32 (32 bits), Solidity intentará empaquetar múltiples variables en una sola palabra de almacenamiento. La mayor parte de lo que ve aquí es que el compilador primero inserta el aparato para empaquetar y desempaquetar las variables uint32 del almacenamiento, y luego el optimizador lo extrae todo nuevamente, ya que no es necesario en este caso realmente simple.
Voy a definir algunas etiquetas para hacer un seguimiento de las cosas:
VALUE = value, the uint32 in your contract = 0x0a (ten)
MASK = 0xFFFFFFFF This is 32 bits of ones, and matches the width of a uint32.
S[0] = the contract storage location zero: 256 bits wide.
WORD = the contents of S[0]: potentially packed storage of uint32s
N = the shift: number of bytes from the right-hand-side of WORD where VALUE is stored within S[0]
Gráficamente, aquí hay un ejemplo de 32 bytes de S[0] WORD, con el VALOR de almacenamiento marcado con VVVV, desplazado por N bytes (12 aquí) dentro de S[0].
0123456789abcdef0123456789abcdef
................VVVV............
<---- N ----
Primero, es solo el preámbulo de administración de memoria que el compilador siempre inserta. La parte superior de la memoria utilizada es inicialmente 0x60 y este valor se almacena en 0x40 para referencia posterior:
PUSH1 0x60
PUSH1 0x40
MSTORE
El siguiente es el valor de su asignación, 10 enuint32 value = 10;
PUSH1 0xA // VALUE
Ahora, el compilador sabe que tiene que almacenar la variable y que es más corta que una palabra completa: un uint32 en lugar de 256 bits. Por lo tanto, crea una máscara y desplaza la máscara en N bytes hasta donde se almacena la variable en la palabra de 256 bits. Ignora el hecho de que N=0 por ahora; podría ser diferente de 0 en general.
PUSH1 0x0 // the zero in S[0].
PUSH1 0x0 // the shift, N
PUSH2 0x100 // One byte shift left is 8 bits
EXP // Calculates 0x100 ^ N, i.e. 8*N-bits shifter
DUP2 // Get the storage slot number [0]
SLOAD // Load WORD from S[0]
DUP2 // Get the shifter we calculated earlier
PUSH4 0xFFFFFFFF // MASK
MUL // Shift the mask along by N bytes
NOT // Invert the mask - every bit is flipped.
AND // Apply the mask. All bits of WORD within the 32 bits of your variable are set to zero; all bits outside are unaffected.
Ahora tenemos la PALABRA original de S[0] en la memoria, con la ubicación de VALOR en cero. Debe ponerse a cero, ya que no podemos confiar en que sea cero si se ha establecido mediante una invocación previa del contrato.
SWAP1 // Retrieve the shifter we calculated earlier
DUP4 // VALUE is retrieved
PUSH4 0xFFFFFFFF // MASK again
AND // Ensure VALUE is truncated to 32 bits since it is uint32
MUL // Shift value left by N bytes
OR // logical or VALUE into WORD. The corresponding WORD bits are zero, so this just sets them to VALUE
SWAP1
SSTORE // Store WORD back into memory at S[0]
Ahora hemos terminado, con los 32 bits de VALUE insertados en el lugar correcto en S[0] y todos los demás bits de S[0] no afectados.
Ahora, lo anterior describe el caso general para cualquier cambio, N. Sin embargo, en este caso N==0 y esa es una situación mucho más fácil, por lo que el optimizador puede encontrar algunas simplificaciones.
El optimizador reconoce que 0x100 ^ 0 = 1, y que multiplicar por 1 no funciona. Por lo tanto, elimina todo el código asociado con el cambio de MÁSCARA o VALOR. No se requiere turno en esta ocasión ya que solo tenemos una variable.
El optimizador también reconoce que el VALOR de la pila insertada (0x0a) tiene menos de 32 bits de ancho, por lo que no necesita aplicarle MÁSCARA.
Esto es suficiente para producir el código optimizado final. Hace un buen trabajo en este caso y produce un código mucho más limpio.
FrenchieiSverige
Shamik
ética
uint
es equivalente auint256
pero incluso en ese caso el bytecode generado todavía parece diferente, interesante...Shamik
Badr Bellaj