Aquí hay un código de la documentación de solidez:
function sumAsm(uint[] _data) public returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
¿Alguien puede explicar este código? _data es una matriz de uint. ¿Cómo podemos agregar 0x20 a una matriz? Si el nombre de la matriz representa la dirección del primer elemento, ¿por qué lo compensamos? ¿Cómo da esta línea (add(add(_data, 0x20), mul(i, 0x20)))
la dirección del primer y subsiguiente elemento de la matriz?
Dentro del código ensamblador, _data
está la dirección de memoria del inicio de los datos de la matriz.
Sin embargo, la primera palabra de memoria (32 bytes = 0x20 bytes) está reservada para la longitud de la matriz, por lo que debemos pasar por alto esto. Por lo tanto, _data[0]
está en la dirección de memoria _data + 0x20
. En el código, esto parece add(_data, 0x20)
.
Los elementos de la matriz siguen consecutivamente, numerados desde 0, cada uno ocupando una palabra de 32 bytes (0x20 bytes). Por lo tanto, el elemento [N] se desplaza N * 0x20 desde el inicio de los datos de la matriz que encontramos arriba. En el código, este desplazamiento es mul(i, 0x20)
.
Juntando todo, _data[i]
se encuentra en (_data + 0x20) + (i * 0x20)
, que es la expresión (add(add(_data, 0x20), mul(i, 0x20)))
.
El efecto neto del bucle es sumar todos los elementos de la matriz en o_sum
. Es probable que se realice en ensamblaje para evitar la sobrecarga de los límites de la matriz al verificar que Solidity siempre se inserta.
La respuesta de @ benjaminion es fantástica, pero hice que su código ensamblador fuera más fácil de entender:
assembly {
let ithpos := mul(add(i, 0x01), 0x20)
let ithnumber := mload(add(_data, ithpos))
o_sum := add(o_sum, ithnumber)
}
Lo que hice:
_data
, simplemente incremento i
en add(i, 0x01)
. Esto se canaliza a la función multiplicadora mul
que obtiene el índice de palabra correcto (recuerde que el ensamblaje solo funciona con variables de 32 bytes)ith
posiciónithnumber
a o_sum
haciendo una suma simpleAdemás, si comenzó a usar solidity ^0.5.0, tenga en cuenta que su _data
parámetro debe definirse así ahora:
function sumAsm(uint[] memory _data) public returns (uint o_sum) {
...
}
Hay una palabra clave adicional memory
entre el tipo y el nombre.
para la respuesta de pablo:
o_sum := add(o_sum, mload(add(_data, mul(add(i, 0x01), 0x20))))
es en realidad más barato de usar ya que no asigna memoria para 2 variables y almacena un valor para ambos. tampoco tiene que usar un mload para cada una de esas variables
Nicolás Massart
Bobdabear