BitrootBlog
Volver al sitio ↗
© 2026 Bitroot · El contenido es solo informativo y no constituye asesoramiento financiero, de inversión, legal ni fiscal.
Estándares editorialesVolver al sitio
← Todos los artículos
Fundamentos de EVM·2026/09/20·Aprox. 16 min

Anatomía de la máquina de pila: palabras de 256 bits, profundidad de pila de 1024 y bucle de ejecución

La EVM es una máquina de pila sin registros: palabras de 256 bits, una pila de 1024 elementos y un contador de programa que solo puede saltar a JUMPDEST. El ciclo de captación, decodificación y ejecución explica por qué el subdesbordamiento y el desbordamiento de pila consumen todo el gas, y también qué gana y qué paga una máquina de pila frente a una máquina de registros.

Estos cinco bytes, 0x6001600201, hacen una sola cosa en la EVM: sumar 1 y 2 y dejar 3 en la cima de la pila. No referencian ningún registro ni indican dónde guardar el resultado; los dos operandos se colocan de forma implícita en la pila y el resultado también vuelve a ella. Toda la aritmética y todo el flujo de control de la EVM se asientan sobre este modelo.

Los artículos anteriores han tratado la máquina de estados y las cuentas; este mira una capa hacia dentro: de qué se compone esta máquina, cómo funciona una vuelta del bucle de ejecución, de dónde vienen la palabra de 256 bits y la profundidad de pila de 1024 elementos, y qué compra y qué paga este diseño de máquina de pila.

La palabra de 256 bits: pensada para la criptografía, no para la aritmética

El Yellow Paper dedica una sola frase a explicar el ancho de palabra: la palabra de máquina (es decir, el ancho de los elementos de la pila) es de 256 bits, y la elección busca facilitar el hash Keccak-256 y las operaciones con curvas elípticas. Desplegada, hay tres razones concretas.

La salida de Keccak-256 es de 256 bits, de modo que ni los valores hash ni las claves de almacenamiento necesitan truncarse o extenderse. Ethereum firma con ECDSA sobre secp256k1; la clave privada y los componentes de la firma caen en el espacio de 256 bits, y la capacidad correspondiente del lado de la EVM la aporta ecrecover, un contrato precompilado en la dirección 0x01 con un precio de 3000 gas. Las direcciones son de 160 bits y encajan de forma natural en una palabra de 256 bits.

Hay aquí un detalle de precisión que se pasa por alto con frecuencia: el Keccak-256 que usa Ethereum no es el SHA3-256 posterior a la estandarización del NIST. Ambos tienen el mismo ancho de salida y la misma función de permutación, pero el byte de dominio que se usa en el relleno es distinto: el Keccak original usa 0x01 y SHA3-256 usa 0x06. Al verificar un hash hay que elegir una biblioteca que indique explícitamente Keccak-256; con SHA3-256 se obtiene un resultado completamente distinto.

El precio del ancho de palabra también es claro. Toda la aritmética se realiza módulo 2^256, el desbordamiento da la vuelta en silencio y no genera excepciones; un valor booleano ocupa igualmente 32 bytes completos; el calldata y los slots de almacenamiento se alinean a 32 bytes, y los tipos cortos tienen que empaquetarse en la capa de codificación, que es justo lo que hace el storage packing de Solidity (véase el 0.19 de esta serie, «0.19 Storage Layout: cómo caen las variables de estado de Solidity en los slots»). La semántica de envolvimiento es además el origen de una gran cantidad de vulnerabilidades de desbordamiento de entero a lo largo de la historia. Desde Solidity 0.8 se insertan comprobaciones por defecto y se revierte con Panic(0x11); es un remedio en la capa del lenguaje, la EVM en sí no ha cambiado.

El estado de máquina: qué gestiona cada uno de pila, memoria y contador de programa

El Yellow Paper escribe el estado de máquina como la séxtupla μ = (g, pc, m, i, s, o): gas disponible, contador de programa, contenido de memoria, número de palabras de memoria activas en ese momento, contenido de la pila y búfer de datos de retorno. El reparto de tareas entre los cuatro componentes se ve mejor en paralelo:

ComponenteDireccionamiento y unidadÁmbito de vidaCosto de gas asociado
Pila (stack)Solo visible la cima; cada elemento de 256 bits; límite de 1024 elementosUn único marco de llamada2 a 3 gas por el propio opcode
Memoria (memory)Direccionada por bytes; la ampliación se cuenta por palabras de 32 bytesUn único marco de llamada; los marcos nuevos empiezan a ceros3a + ⌊a²/512⌋, donde a es el número de palabras activas
Contador de programa (PC)Desplazamiento en bytes dentro del códigoUn único marco de llamadaJUMP 8 gas, JUMPI 10 gas, JUMPDEST 1 gas
Contador de gasGas disponible, un entero no negativoToda la transacción; entre marcos de llamada, el gas restante se transfiere según los parámetros de la llamadaSe descuenta en cada instrucción según la tabla de tarifas

La pila (stack) es la única zona de operandos implícita. Solo expone su cima: la inmensa mayoría de las instrucciones sacan los parámetros de la cima y devuelven el resultado a la cima, y los elementos intermedios son invisibles para las instrucciones. Su límite es de 1024 elementos, de 256 bits cada uno.

La memoria (memory) se direcciona por bytes, todas sus posiciones empiezan a cero y se accede a ella con MLOAD/MSTORE (lectura y escritura por palabras de 32 bytes) y MSTORE8 (escritura de un byte). Su costo es dinámico: la ampliación se cobra por número de palabras activas, y la fórmula que da el Yellow Paper es que, al ocupar a palabras, el costo total de memoria es 3a + ⌊a²/512⌋ gas; el término cuadrático hace que acceder a un desplazamiento enorme agote el gas directamente. Por eso en la memoria de la EVM no existen arrays grandes gratuitos ni el problema de «leer datos no inicializados»: las posiciones que nadie ha escrito valen siempre 0.

El contador de programa (program counter, PC) es el desplazamiento en bytes de la instrucción siguiente dentro del código. El flujo de control solo puede cambiar mediante JUMP/JUMPI, y el Yellow Paper define el conjunto de destinos de salto válidos como las posiciones donde aparece la instrucción JUMPDEST en el código. El conjunto de destinos es, por tanto, enumerable de forma estática, y en tiempo de ejecución no se puede calcular una dirección arbitraria y saltar a ella. Esta restricción es la base sobre la que puede sostenerse el análisis de flujo de control sin ejecución.

El código en sí no está en la pila, la memoria ni el almacenamiento. El Yellow Paper señala explícitamente que la máquina no sigue la arquitectura de von Neumann: el código se guarda aparte, en una ROM virtual a la que solo se accede con instrucciones dedicadas. Por eso el código de un contrato ya desplegado no puede reescribirse a sí mismo y no existe la posibilidad de sustituir el flujo de instrucciones a mitad de ejecución. El almacenamiento y la memoria son mutables; el código, no.

Captación, decodificación, ejecución: una vuelta del bucle

El Yellow Paper define «la instrucción que toca ejecutar ahora» mediante una función por tramos: si el contador de programa es menor que la longitud del código, la instrucción es el byte en esa posición; en caso contrario, la instrucción equivale a STOP. Es decir, leer más allá del final del código no constituye un error, y la especificación define así el final natural.

Para que una instrucción se ejecute hay que saber primero tres cosas: cuántos elementos saca (δ), cuántos mete (α) y cuánto gas cuesta (la función de costo C). Estas tres cantidades las determina la propia instrucción y aparecen en la fila correspondiente de la tabla de opcodes. El bucle puede escribirse entonces así, omitiendo el substate, la lista de acceso y la devolución de gas:

# Bucle de ejecución simplificado; se conserva el orden de decisión de la especificación
pc, gas, stack, memory = 0, gas_limit, [], bytearray()

while True:
    # Captación: pasarse del final del código equivale a STOP
    op = code[pc] if pc < len(code) else STOP
    # Decodificación: la tabla da cuántos elementos se sacan y se meten, y el costo
    delta, alpha, cost = OPCODE_TABLE[op]
    # La validación ocurre antes de ejecutar: gas insuficiente, pila insuficiente y desbordamiento de pila son paradas excepcionales
    if gas < cost or len(stack) < delta or len(stack) - delta + alpha > 1024:
        raise ExceptionalHalt()
    gas -= cost
    # Ejecución: se toman los operandos de la cima y el resultado vuelve a la cima
    args = [stack.pop() for _ in range(delta)]
    stack.extend(dispatch(op, args, memory, pc))
    # El PC avanza; las instrucciones PUSH además saltan su inmediato
    pc += 1 + immediates(op)

Los comentarios corresponden a tres puntos en los que es fácil equivocarse: la semántica de la captación fuera de límites, que la validación debe preceder a la ejecución y que el inmediato de PUSH ocupa espacio en el código.

El último punto merece desarrollarse. PUSH1 a PUSH32 codifican la constante justo después del opcode; por ejemplo, PUSH1 0x2a ocupa dos bytes. Esto trae una consecuencia contraintuitiva: no todos los bytes del bytecode son instrucciones, y el escaneo de destinos de salto tiene que reconocer y saltar los datos inmediatos; de lo contrario podría tomar por JUMPDEST algún byte dentro de una constante.

Ver los cinco bytes 60 2a 60 5b 56 desplegados lo aclara. 60 2a es una instrucción con inmediato; el segundo byte de 60 5b es la constante 0x5b, cuyo valor es exactamente igual que el opcode JUMPDEST, pero no es una instrucción; 56 es el que sí es JUMP. Al escanear los destinos de salto válidos hay que reconocer primero el 0x60 y saltar después el byte que lo sigue. La regla no es complicada en sí; solo exige que cualquier implementación que haga análisis de flujo de control calcule bien la longitud de PUSH, porque de lo contrario el conjunto de destinos queda contaminado por bytes de constantes.

Esto explica también por qué hace falta un 0x5b dedicado como marca de salto, en lugar de permitir saltar a cualquier desplazamiento.

Subdesbordamiento y desbordamiento de pila: dos excepciones, un mismo desenlace

La función Z de parada excepcional del Yellow Paper enumera todas las condiciones que hacen que la ejecución se detenga de inmediato; las dos relacionadas con la pila son: que haya menos elementos en la pila que los que la instrucción va a sacar, es decir, subdesbordamiento (stack underflow), y que la altura de la pila supere 1024 después de ejecutar, es decir, desbordamiento (stack overflow).

Ambos recorren el mismo camino: parada excepcional, consumo de todo el gas restante y descarte de todos los cambios de estado del marco de pila de esta llamada. Los vectores de prueba normativos de EIP-3855 ofrecen un contraste limpio: 1024 PUSH0 consecutivos se ejecutan con éxito y 1025 consecutivos se detienen por desbordamiento de pila.

Conviene distinguirlo de otro tipo de «fallo». La instrucción REVERT (0xfd, desde Byzantium, EIP-140) también revierte el estado, pero no consume el gas restante y además puede devolver al llamante un fragmento de bytes de la memoria como datos de error. Un fallo de require de Solidity suele compilarse como REVERT: el mensaje de error puede volver al llamante y el gas restante tampoco se consume; el subdesbordamiento de pila es una excepción dura, y al depurar se manifiesta como gas agotado y ningún dato devuelto. En la experiencia de depuración, cuando una transacción consume todo el gas y no se obtienen datos de retorno, la causa habitual es que el bytecode ha tropezado con una excepción dura, y el volumen de cálculo en sí puede no ser grande.

El límite también hay que dejarlo claro: si REVERT no tiene gas suficiente por sí mismo, o si al ejecutarse encuentra un subdesbordamiento de pila, degenera en una excepción normal y consume igualmente todo el gas.

Lo que gana la máquina de pila: reducir al mínimo la complejidad de implementación

La explicación de ethereum.org es que la estructura de pila es la arquitectura preferida para una máquina virtual porque es fácil de implementar, y con ello baja la probabilidad de errores y de vulnerabilidades de seguridad. Esta ganancia puede desglosarse en varios puntos.

El decodificador es mínimal. El opcode es un byte, la posición de los operandos la determina de forma implícita el orden de la pila y el bytecode no necesita codificar un número de registro para cada instrucción. Salvo las instrucciones de la familia PUSH, que llevan un inmediato, la longitud de las instrucciones es básicamente fija, así que decodificar es una simple consulta a la tabla.

En la especificación no existe la capa de asignación de registros. La estrategia de asignación es por naturaleza una libertad del compilador, pero en cuanto entra en la especificación de la máquina virtual se convierte en un comportamiento que todas las implementaciones deben reproducir bit a bit. La máquina de pila borra de la especificación el «dónde se guardan los valores», y con ello se reduce la descripción de estado que el consenso necesita fijar.

La semántica de las operaciones sobre la pila se define de forma casi independiente instrucción a instrucción, el costo de gas puede colgarse directamente del opcode y el espacio de búsqueda del análisis estático, el fuzzing y la verificación formal es menor. Que la EVM pueda tener varias implementaciones independientes (geth, revm, evmone, etc.) y mantenerse idéntica a nivel de byte es una de las razones.

Lo que cuesta la máquina de pila: DUP, SWAP y la ausencia de acceso aleatorio

El precio de que la pila solo exponga su cima se nota en el número de instrucciones del bytecode.

Para usar un elemento cercano a la cima se puede copiar a la cima con DUP1 a DUP16, o intercambiarlo hasta la cima con SWAP1 a SWAP16. Dieciséis es el límite duro: si el elemento que se quiere copiar está en el nivel 17, hay que llevarlo primero al rango copiable con algo como SWAP16 y seguir operando después; cuanto más profunda es la pila, más larga es la cadena de acarreo. Un mismo cálculo que en una máquina de registros suele resolverse con una sola instrucción aquí se despliega en una serie de apilado, copia, intercambio y desapilado.

Un caso concreto. Un contrato quiere calcular f(a, b, c, d) y d está en el cuarto nivel de la pila. Una máquina de registros puede referenciar directamente el registro donde está d; en una máquina de pila, el compilador o bien copia d de antemano a una posición más cercana a la cima, o bien lo sube con la familia SWAP antes de la llamada y lo vuelve a bajar al terminar. Por eso el compilador de Solidity genera tantas instrucciones de reordenación cuando hay muchos parámetros de función.

El compilador también tiene que mantener el equilibrio de la pila. Al final de cada bloque básico la altura de la pila debe ser predecible; de lo contrario, el código posterior a un salto no puede localizar sus operandos. Por eso la fase de compilación de Solidity hace una planificación de pila específica, insertando DUP, SWAP y POP para mover operandos cuando hay muchos parámetros y variables locales. Estas instrucciones no son caras en sí —la mayoría está en la franja de 3 gas—, pero alargan la ruta de ejecución del intérprete.

Para saber qué paga de más una máquina de pila frente a una de registros hay una referencia indirecta en la JVM. Davis y otros tradujeron bytecode de JVM a una máquina de registros virtual y describieron el compromiso de menos instrucciones ejecutadas y más captaciones de bytecode (Davis et al., 2003). Esta comparación viene de la JVM y no puede trasladarse sin más a la EVM: mide el costo de despacho de la ejecución interpretada, mientras que la tarificación de gas de la EVM ya externaliza la mayor parte de ese costo. La única afirmación direccional que puede sostenerse es esta: para un mismo cálculo, una máquina de pila suele necesitar más instrucciones ejecutadas, y una de registros cambia más captaciones por menos instrucciones de ejecución.

Los diseñadores de la EVM aceptaron ese precio a cambio de una implementación sencilla y una especificación determinada. En los últimos años ha habido también mejoras incrementales pequeñas, como PUSH0 (EIP-3855, Shanghai), que sustituyó el PUSH1 0x00 de 2 bytes y 3 gas por una instrucción de 1 byte y 2 gas.

En qué casos este diseño se convierte en una carga

Cuando las expresiones están muy anidadas y las funciones tienen muchos parámetros, sube la proporción de instrucciones de reordenación de pila y con ella el consumo de gas de la ejecución del contrato. En el tratamiento del ancho de bits, la EVM no tiene tipos nativos de 8, 32 o 64 bits: todo hay que truncarlo o enmascararlo de forma explícita, algo a lo que hay que prestar especial atención al portar entre lenguajes.

La profundidad de pila es 1024, pero este 1024 no es el mismo que otro 1024 con el que se confunde a menudo. En la definición de CALL/CREATE, el Yellow Paper limita igualmente la profundidad de llamada a 1024. El primero limita el número de operandos dentro de un solo marco; el segundo, la longitud de la cadena de llamadas. Tropezar con el primero es un error del programa; tropezar con el segundo suele significar que la recursión está mal escrita.

Y hay que trazar una frontera más: la máquina de pila no es un obstáculo para la ejecución paralela. Lo que el paralelismo tiene que resolver son los conflictos de lectura y escritura sobre un estado global mutable, mientras que la posición de un operando en la pila la determina de forma implícita el orden de la pila, algo que no tiene relación ni con la detección de conflictos ni con la determinación de la ejecución. La restricción real está en la capa de estado y pertenece a los artículos siguientes.

Fuentes

  • Ethereum Yellow Paper (la séxtupla del estado de máquina, el límite de pila de 1024, la fórmula del costo de memoria, el conjunto de destinos JUMPDEST, la función de parada excepcional Z, la tabla de tarifas de gas y la tabla de opcodes, el precio del precompilado ECREC): https://ethereum.github.io/yellowpaper/paper.pdf
  • ethereum.org, Ethereum Virtual Machine (EVM) (profundidad de pila de 1024, relación entre la palabra de 256 bits y Keccak-256 / secp256k1): https://ethereum.org/developers/docs/evm/
  • ethereum.org, Understanding the Yellow Paper's EVM Specifications (la máquina de pila es fácil de implementar y por eso produce menos defectos; razones de la elección de la palabra de 256 bits): https://ethereum.org/developers/tutorials/yellow-paper-evm/
  • Keccak Team, Keccak specifications summary (el bit de sufijo 0x06 de SHA3-256 y el proceso de relleno): https://keccak.team/keccak_specs_summary.html
  • Keccak Team, The Keccak reference, version 3.0 (el relleno multi-rate pad10*1 del Keccak original): https://keccak.team/files/Keccak-reference-3.0.pdf
  • NIST, FIPS 202 (relleno 0x06 de SHA-3): https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
  • EIP-3855, PUSH0 instruction (0x5f, 2 gas, vectores de prueba de 1024 y 1025 PUSH0): https://eips.ethereum.org/EIPS/eip-3855
  • EIP-140, REVERT instruction (revierte sin consumir todo el gas restante, y el límite en el que degenera en excepción): https://eips.ethereum.org/EIPS/eip-140
  • Davis, Beatty, Casey, Gregg, Waldron, The Case for Virtual Register Machines, 2003 (traducen bytecode de JVM a una máquina de registros virtual y describen menos instrucciones ejecutadas y más captaciones de bytecode): https://mural.maynoothuniversity.ie/id/eprint/10191/1/KC-Case-2003.pdf
  • Solidity 0.8.0 Release Announcement (las operaciones aritméticas se comprueban por defecto y se revierten con Panic(0x11)): https://www.soliditylang.org/blog/2020/12/16/solidity-v0.8.0-release-announcement/
  • ethereum/execution-spec-tests (pruebas normativas de PUSH0 y del desbordamiento de pila): https://github.com/ethereum/execution-spec-tests

Lecturas relacionadas

Qué significa realmente la compatibilidad con EVM: bytecode, precompilados, JSON-RPC y herramientasRelacionadoParalelización optimista de Bitroot: detección, reejecución y determinismoRelacionadoPor qué una EVM de un solo hilo limita el TPS: historia de la congestión y el modelo de ejecuciónSiguiente
← AnteriorQué es realmente la EVM: del libro contable distribuido a la máquina de estados distribuidaSiguiente →Tres clases de almacenamiento que no conviene confundir: Memory, Storage y Transient Storage
Contenido
La palabra de 256 bits: pensada para la criptografía, no para la aritméticaEl estado de máquina: qué gestiona cada uno de pila, memoria y contador de programaCaptación, decodificación, ejecución: una vuelta del bucleSubdesbordamiento y desbordamiento de pila: dos excepciones, un mismo desenlaceLo que gana la máquina de pila: reducir al mínimo la complejidad de implementaciónLo que cuesta la máquina de pila: DUP, SWAP y la ausencia de acceso aleatorioEn qué casos este diseño se convierte en una cargaFuentesLecturas relacionadas
Ajustes de lectura