Comprender el código ensamblador en línea de Solidity

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?

Respuestas (3)

Dentro del código ensamblador, _dataestá 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:

  1. En lugar de agregar 0x20 a la ubicación de memoria de _data, simplemente incremento ien add(i, 0x01). Esto se canaliza a la función multiplicadora mulque obtiene el índice de palabra correcto (recuerde que el ensamblaje solo funciona con variables de 32 bytes)
  2. Cargue de la memoria el número en la ithposición
  3. Finalmente, agregue el ithnumbera o_sumhaciendo una suma simple

Además, si comenzó a usar solidity ^0.5.0, tenga en cuenta que su _dataparámetro debe definirse así ahora:

function sumAsm(uint[] memory _data) public returns (uint o_sum) {
    ...
}

Hay una palabra clave adicional memoryentre 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

Hola, gracias por esto, pero puede ser un comentario a la respuesta relacionada, ya que no es en sí misma la respuesta a la pregunta original. Gracias.
Todavía no puedo agregar un comentario ya que no tengo 50 representantes, pero dado que el ensamblaje se trata de eficiencia, pensé que aún era relevante. Supongo que está relacionado con ambas respuestas, ya que los documentos de solidez también lo tienen todo en una línea. Creo que conté un costo de gasolina de 41 para Paul y 29 para el 1 liner. =) cosas pequeñas pero pueden hacer una gran mejora si está en un bucle