Conjuntos de instrucciones y lenguaje ensamblador

Aprende cómo se comunica el software con el hardware mediante un conjunto de instrucciones. Con RISC-V, lee y escribe lenguaje ensamblador con registros, operandos en memoria, constantes, operaciones aritméticas y lógicas y saltos condicionales, y traduce expresiones, decisiones, bucles, punteros y cadenas de C a instrucciones. Codifica instrucciones como código máquina de 32 bits y vuelve a decodificarlas, construye llamadas a procedimientos con la pila y sigue un programa durante la compilación, el ensamblado, el enlace y la carga. Por último, compara RISC-V con conjuntos complejos de instrucciones como x86 y evalúa qué implica el diseño del conjunto de instrucciones para el rendimiento.

IntermedioUnidad 2 de 6 del curso Curso de Organización y arquitectura de computadores

Lo que aprenderás

  1. Explica qué es un conjunto de instrucciones, lee lenguaje ensamblador de RISC-V y usa registros, memoria y constantes como operandos de las instrucciones.
    • Explica qué es una arquitectura del conjunto de instrucciones
      Describe el conjunto de instrucciones como contrato entre software y hardware, la idea de programa almacenado y la relación entre el lenguaje ensamblador, el código máquina y el conjunto base de RISC-V.
    • Usa registros para evaluar expresiones
      Identifica los 32 registros de enteros de RISC-V y sus funciones convencionales, y traduce expresiones aritméticas a secuencias de instrucciones que usan registros.
    • Carga y almacena datos en memoria
      Usa direcciones de bytes, tamaños de datos, alineación y orden de bytes para transferir palabras, medias palabras y bytes entre la memoria y los registros, incluidos elementos de arreglos.
    • Trabaja con constantes y operandos inmediatos
      Usa operandos inmediatos para constantes pequeñas y construye constantes y direcciones completas de 32 bits con lui, auipc, addi y las pseudoinstrucciones li y la.
  2. Usa instrucciones aritméticas, lógicas, de desplazamiento y de comparación, y traduce decisiones y bucles de C a saltos condicionales e incondicionales.
    • Usa instrucciones aritméticas, lógicas y de desplazamiento
      Calcula con suma, resta, operaciones lógicas bit a bit, desplazamientos y puesta a uno si es menor, incluidos el desbordamiento, la interpretación con y sin signo, las máscaras y la multiplicación o división por potencias de 2.
    • Realiza saltos con instrucciones condicionales e incondicionales
      Usa instrucciones de comparación y salto, condiciones con y sin signo, saltos y sus pseudoinstrucciones para cambiar la siguiente instrucción que se ejecuta.
    • Traduce decisiones y bucles de C a lenguaje ensamblador
      Convierte sentencias if-else, condiciones compuestas, bucles while y for y sentencias switch en saltos, y sigue el código resultante con valores reales.
  3. Convierte entre instrucciones de ensamblador y código máquina de 32 bits usando los seis formatos de instrucciones de RISC-V, y explica por qué los formatos están diseñados de ese modo.
    • Codifica instrucciones de registro, de inmediato y de almacenamiento
      Organiza los campos de las instrucciones de tipo R, I y S y convierte instrucciones como add, sub, addi, desplazamientos, lw y sw a binario y hexadecimal.
    • Codifica saltos condicionales, saltos incondicionales e inmediatos superiores y decodifica código máquina
      Codifica instrucciones de tipo B, J y U con desplazamientos relativos al pc e inmediatos superiores, y decodifica cualquier palabra RV32I de 32 bits para obtener lenguaje ensamblador.
  4. Sigue la convención de llamada de RISC-V para pasar argumentos, devolver resultados y preservar registros, usa marcos de pila para procedimientos anidados y recursivos y traduce punteros, arreglos y cadenas de C.
    • Llama a procedimientos y retorna de ellos siguiendo la convención de llamada
      Pasa argumentos y resultados en registros, llama con jal y retorna con ret, y decide qué registros debe preservar el procedimiento llamador o el llamado.
    • Construye marcos de pila para procedimientos anidados y recursivos
      Reserva y libera marcos de pila, guarda la dirección de retorno y los registros en llamadas anidadas y recursivas, y sitúa la pila en la distribución de un programa en memoria.
    • Traduce punteros, arreglos y cadenas de C a lenguaje ensamblador
      Convierte desreferenciaciones de punteros, aritmética de punteros, campos de estructuras, parámetros de arreglos y cadenas terminadas en nulo en cargas, almacenamientos y cálculos de direcciones.
  5. Explica cómo un compilador, un ensamblador, un enlazador y un cargador convierten el código fuente de C en un programa en ejecución en memoria, incluidos los archivos objeto, la reubicación y el enlace dinámico.
    • Explica cómo los compiladores y ensambladores generan archivos objeto
      Describe la jerarquía de traducción, qué hacen los compiladores y ensambladores, las directivas y símbolos del ensamblador y las partes de un archivo objeto.
    • Explica cómo los enlazadores y cargadores crean e inician un ejecutable
      Describe cómo un enlazador coloca módulos y ajusta direcciones, compara el enlace estático y el dinámico, y explica cómo un cargador y las llamadas al sistema inician y terminan un programa.
  6. Contrasta RISC-V con conjuntos complejos de instrucciones como x86 y evalúa los compromisos de diseño del conjunto de instrucciones en cuanto a rendimiento, tamaño del código y compatibilidad.
    • Contrasta las características de diseño de RISC y CISC
      Describe las características definitorias de los conjuntos de instrucciones RISC y CISC y compara RISC-V con x86 en cuanto a operandos, codificación, modos de direccionamiento, registros e indicadores de condición.
    • Evalúa los compromisos de diseño del conjunto de instrucciones en cuanto a rendimiento, tamaño del código y compatibilidad
      Usa la ecuación de rendimiento para comparar diseños, explica cómo los procesadores modernos difuminan la línea entre RISC y CISC y evalúa las ideas erróneas frecuentes sobre los conjuntos de instrucciones.

Guía de estudio de Conjunto de instrucciones

6 capítulos

1

Instrucciones, registros y memoria

Un conjunto de instrucciones conecta un programa con un procesador. Comienza leyendo el destino y los operandos de origen de una instrucción RISC-V y después sigue los valores a través de los registros y la memoria. Las direcciones de bytes explican cómo se accede a arreglos y estructuras; los campos inmediatos explican por qué algunas constantes necesitan más de una instrucción. Distinguir los valores, las direcciones y el contenido de la memoria facilita el seguimiento de cada línea de ensamblador.

1.1 El conjunto de instrucciones como contrato de software

Un procesador ejecuta instrucciones representadas por bits. Una arquitectura del conjunto de instrucciones (ISA) da a esos bits un significado compartido, de modo que el software puede solicitar las mismas operaciones en diferentes implementaciones.

Qué define la ISA

La ISA especifica las instrucciones, los registros visibles, los tamaños de datos, el comportamiento de la memoria y las codificaciones de instrucciones. Una instrucción como add a0, a1, a2 debe producir el resultado definido en todos los procesadores que la admitan. Los circuitos internos y la planificación pertenecen a la microarquitectura. Dos chips pueden diferir mucho en velocidad y consumo de potencia aunque implementen la misma ISA.

La compatibilidad binaria también necesita que coincida el entorno de software. Un programa que llama a un servicio del sistema operativo o a una biblioteca depende de algo más que del significado de las instrucciones. Las convenciones de registros y la distribución de los datos forman parte de la interfaz binaria de aplicación, o ABI.

Del código legible a los bits

Para C, un compilador selecciona instrucciones de ensamblador; un ensamblador las codifica como código máquina. El ensamblador es texto con nombres de instrucciones, registros, comentarios y etiquetas. El código máquina es la representación binaria que busca el procesador. Una etiqueta como Start: da nombre a la dirección de una instrucción; no es una instrucción que se ejecute durante la ejecución. Un comentario empieza con #.

Start:
    add a0, a1, a2   # a0 receives a1 + a2

El primer registro es el destino. Si a1 contiene 14 y a2 contiene 5, la instrucción escribe 19 en a0; deja los operandos de origen sin cambios. Las expresiones más complejas necesitan varias operaciones. Sumar cuatro valores de registros requiere tres sumas, con los resultados intermedios almacenados en registros.

La mayoría de los nombres de instrucciones corresponden a una instrucción máquina. Una pseudoinstrucción es una abreviatura del ensamblador: mv solicita una copia de registro y li solicita una constante. Una pseudoinstrucción puede expandirse en una o varias instrucciones reales, por lo que contar líneas de ensamblador no siempre indica el tamaño del código.

Instrucciones almacenadas en memoria

Según el concepto de programa almacenado, tanto las instrucciones como los datos se representan como números en memoria. El contador de programa, pc, identifica la instrucción que se debe buscar. Para una secuencia de instrucciones de 32 bits, la ejecución normal lo avanza 4 bytes. Los saltos condicionales e incondicionales eligen una dirección siguiente diferente. El procesador ejecuta la codificación, no la escritura en ensamblador que la produjo originalmente.

Bases y extensiones de RISC-V

RISC-V es un estándar abierto de ISA, libre de regalías y mantenido por RISC-V International. RV32I proporciona 40 instrucciones base para enteros y registros de enteros de 32 bits; RV64I usa registros de enteros de 64 bits. Ambos proporcionan 32 registros de enteros. Las extensiones opcionales añaden capacidades: M proporciona multiplicación y división de enteros, A operaciones atómicas, F y D coma flotante, C instrucciones comprimidas y V vectores. Los ejemplos usan RV32I y usan M donde aparece mul.

El ensamblador de RISC-V es específico de RISC-V. Un programa en C puede volver a compilarse para x86 o ARM, pero su código máquina de RISC-V no puede ejecutarse directamente como el de esas ISA. Un simulador puede ejecutar instrucciones RISC-V en un sistema anfitrión con una ISA diferente y mostrar los cambios en registros y memoria paso a paso.

Puntos clave

  • La ISA define el comportamiento visible; la microarquitectura lo implementa.
  • Un ensamblador codifica el texto ensamblador; las pseudoinstrucciones pueden expandirse.
  • Con instrucciones de 32 bits, la ejecución secuencial avanza el pc 4 bytes.
  • RISC-V combina una ISA base con extensiones opcionales.

1.2 Registros y resultados de expresiones

Los registros contienen los valores que una instrucción puede usar inmediatamente. En RV32I, cada registro de enteros contiene 32 bits, y el nombre de un registro identifica un espacio de almacenamiento, no una variable de C ni un tipo de dato.

Nombres y funciones de los registros

Hay 32 registros de enteros, de x0 a x31. El contador de programa es independiente. Cinco bits identifican un registro de enteros porque hay 32 patrones posibles de 5 bits. Un banco de registros pequeño también limita el coste de acceso y el espacio que ocupan los campos de registro en las instrucciones.

RegistrosNombres ABIFunción convencional
x0zeroSiempre se lee como 0
x1, x2ra, spDirección de retorno y puntero de pila
x3, x4gp, tpPunteros global y de hilo
x5–x7, x28–x31t0–t6Valores temporales
x8–x9, x18–x27s0–s11Valores preservados entre llamadas
x10–x17a0–a7Argumentos; a0–a1 también devuelven resultados

El registro s0 también puede servir como fp, el puntero de marco. Las escrituras en zero se descartan, por lo que puede proporcionar un operando cero sin cargar antes una constante. Por ejemplo, add a0, a1, zero copia a1.

Estos nombres expresan un acuerdo de software. En particular, el hardware no preserva automáticamente un registro s. Un procedimiento que lo modifica debe encargarse de restaurar el valor que tenía al entrar. La ABI estándar también reserva gp y tp para sus funciones en el entorno; no son registros temporales de uso general.

Divide una expresión en operaciones

Traduce f = (g + h) - (i + j); con g, h, i, j en s1–s4 y el resultado en s0:

add t0, s1, s2
add t1, s3, s4
sub s0, t0, t1

Con los valores 9, 4, 6 y 1, la primera instrucción deja 13 en t0. La segunda deja 7 en t1. La resta escribe 6 en s0. Cada registro temporal conserva un resultado hasta que la siguiente instrucción lo necesita.

El orden de los dos operandos de origen importa en la resta: sub s0, t1, t0 daría −6. El destino siempre va primero, por lo que sub t0, s0, t1 sobrescribiría un registro temporal y leería el valor anterior de s0. Al seguir la ejecución, anota el destino después de cada línea; no actualices ambos registros de origen.

Los bits adquieren significado al usarse

Un registro no tiene una marca de con signo o sin signo. El patrón 0xFFFFFFFF puede significar −1 con signo o 4 294 967 295 sin signo. Por tanto, una comparación con signo y una sin signo pueden dar resultados distintos para los mismos bits almacenados. Las direcciones también son patrones de bits en registros; una carga interpreta su base como una dirección.

Conserva los valores mientras sean necesarios

Un compilador asigna a registros los valores que se usan con frecuencia. Si necesita más valores activos de los que caben en los registros, almacena algunos en memoria, un proceso llamado derrame de registros a memoria, y los carga de nuevo más tarde. Un derrame supone un coste real de almacenamiento y carga.

Las llamadas crean otro límite. Un procedimiento llamado puede sobrescribir t0–t6 y a0–a7. Conserva un valor necesario después de una llamada en un registro s correctamente preservado o guárdalo en la pila antes de la llamada. Elegir t0 solo porque estaba libre no garantiza que su valor se conserve.

Puntos clave

  • RV32I tiene 32 registros de enteros de 32 bits; el pc es independiente.
  • Las escrituras en zero se descartan y las lecturas siempre dan 0.
  • La aritmética de registros escribe el primer operando y lee los operandos siguientes.
  • Los resultados temporales deben permanecer disponibles hasta su último uso.
  • Los registros preservados por el llamador pueden cambiar durante una llamada a un procedimiento.

1.3 Transfiere valores entre la memoria y los registros

Las direcciones de memoria identifican bytes. Las cargas llevan datos a los registros y los almacenamientos copian los bits de los registros de vuelta a la memoria. En una arquitectura de carga y almacenamiento, las operaciones aritméticas no leen sus operandos directamente de la memoria.

Tamaños y desplazamientos en bytes

Un byte ocupa una dirección, una media palabra dos y una palabra de 32 bits cuatro. RV32 tiene un espacio de direcciones de 32 bits de hasta 4 GiB. En la ABI de C ILP32, char usa 1 byte, short 2, e int, long y los punteros 4.

Las instrucciones lw y sw transfieren palabras. Las cargas de media palabra son lh y lhu, y las cargas de byte son lb y lbu. Los almacenamientos usan sh y sb para los tamaños menores. Un almacenamiento copia los bits inferiores de su origen; sb escribe solo los 8 bits inferiores.

Una dirección es un registro base más un desplazamiento en bytes con signo de 12 bits:

lw t0, 32(s3)
sw t0, 48(s3)

La carga lee la palabra en la dirección contenida en s3 más 32. No suma 32 al valor cargado ni modifica s3. El almacenamiento escribe ese valor en la base más 48. El rango de desplazamiento va de −2048 a 2047.

Traduce un acceso a un arreglo

Para un arreglo de tipo int cuya base está en s3, el elemento A[i] comienza en la base más 4 veces el índice. Para implementar A[12] = h + A[8];, con h en s1:

lw t0, 32(s3)      # A[8]
add t0, s1, t0
sw t0, 48(s3)      # A[12]

Si A[8] es 80 y h es 3, A[12] pasa a valer 83. Un desplazamiento de 8 seleccionaría A[2], ya que la dirección cuenta bytes. Para un índice variable en s4, forma primero la dirección:

slli t1, s4, 2
add t1, s3, t1
lw t0, 0(t1)

El escalado sigue el tamaño del elemento, no el nombre del procesador. Un puntero o long de RV64 LP64 ocupa 8 bytes y usa ld/sd; un int de RV64 sigue ocupando 4.

Extiende una carga pequeña

Un registro debe contener 32 bits incluso cuando una carga lee solo 8. lb copia el bit de signo del byte en cada bit superior; lbu rellena los bits superiores con ceros. Así, el byte 0xF0 se convierte en 0xFFFFFFF0 (−16) con lb, pero en 240 con lbu. La misma distinción se aplica a lh y lhu. Los bytes sin signo, incluidos los bytes de cadenas, usan lbu.

Lee el orden de bytes

En la memoria con el byte menos significativo primero, este ocupa la dirección más baja. Al almacenar 0x12345678 en 0x1000, se colocan 0x78, 0x56, 0x34, 0x12 en las direcciones de 0x1000 a 0x1003. Una carga de byte desde la primera dirección lee 0x78. El orden con el byte más significativo primero coloca 0x12 primero; los sistemas RISC-V pueden definir cualquiera de los dos órdenes, aunque los sistemas habituales de enseñanza usan el byte menos significativo primero.

Una dirección con alineación natural es múltiplo del tamaño del acceso: 4 para una palabra y 2 para una media palabra. Los accesos con alineación natural tienen soporte de la arquitectura. Un acceso desalineado puede funcionar, tardar más o producir una excepción, según el entorno de ejecución. Coloca las palabras en límites de 4 bytes y avanza 4 para llegar a la siguiente palabra.

Puntos clave

  • Los desplazamientos de carga y almacenamiento cuentan bytes, no elementos de arreglos.
  • Calcula la dirección de un índice variable antes de ejecutar la carga o el almacenamiento.
  • Las cargas con signo extienden el signo; las cargas sin signo extienden con ceros.
  • La memoria con el byte menos significativo primero coloca ese byte en primer lugar.
  • La alineación natural depende del tamaño del acceso.
  • RV64 LP64 amplía los punteros y los valores long, mientras que int sigue ocupando 4 bytes.

1.4 Ajusta las constantes a los campos de instrucción

Un operando inmediato es una constante codificada dentro de una instrucción. Permite que una operación común, como incrementar un contador, evite una carga independiente.

Constantes pequeñas

addi t0, t0, 1 suma 1 a t0. Su inmediato es un número con signo de 12 bits en complemento a dos, de −2048 a 2047. El procesador aplica extensión de signo hasta 32 bits antes de la suma. Resta una constante pequeña con un inmediato negativo, como en addi t0, t0, -5; RV32I no tiene una instrucción de resta con inmediato.

Las cargas, jalr y las instrucciones ordinarias de inmediato aritméticas y lógicas también usan inmediatos con signo de 12 bits. En particular, andi, ori y xori no extienden sus constantes con ceros. Las instrucciones de desplazamiento con inmediato, en cambio, codifican una cantidad de desplazamiento de RV32 de 0 a 31.

Entre las abreviaturas útiles del ensamblador están:

TextoInstrucción base o expansión
mv rd, rsaddi rd, rs, 0
nopaddi x0, x0, 0
not rd, rsxori rd, rs, -1
neg rd, rssub rd, x0, rs
li rd, c, para un c pequeñoaddi rd, x0, c

La constante −1 se convierte en bits todos a uno al aplicar extensión de signo, por lo que xori con −1 invierte cada bit.

Construye los 32 bits

lui coloca un campo de 20 bits en los bits 31–12 y pone a cero los 12 bits inferiores. Para 0x12345678:

lui t0, 0x12345
addi t0, t0, 0x678

El valor intermedio es 0x12345000; sumar 1656 proporciona la parte inferior. Una parte inferior con el bit 11 a uno exige más cuidado. Su patrón de 12 bits representa un sumando negativo, por lo que debes aumentar la parte superior antes de sumarlo.

Para 0x00001800, el patrón inferior 0x800 significa −2048. Usa:

lui t0, 0x2
addi t0, t0, -2048

El primer resultado es 0x2000. Restar 0x800 produce 0x1800. Usar el campo superior 0x1 produciría 0x800, un bloque de 4096 por debajo.

Para cualquier constante de 32 bits, interpreta sus 12 bits inferiores como un valor con signo. Resta ese valor con signo de la constante deseada, divide entre 4096 y conserva los 20 bits inferiores para lui. Para 0xDEADBEEF, la parte inferior con signo es −273, por lo que se usa el campo superior 0xDEADC y después addi con −273. Los ensambladores realizan esta división para li.

Constantes, direcciones y contenidos

li t0, 100 coloca el número 100 en un registro. lw t0, 100(zero) lee el contenido de la memoria en la dirección 100. Una dirección puede cargarse como valor: la t0, Item pide al ensamblador la dirección denominada Item.

auipc suma un inmediato superior desplazado 12 bits a la izquierda a la dirección de esa instrucción. Combinado con una operación de inmediato o una carga, proporciona acceso relativo al pc en un alcance aproximado de ±2 GiB. Una instrucción la local suele usar auipc más addi; otras expansiones permiten el acceso a bibliotecas compartidas.

Una constante como 5000 no puede ser un operando de addi. Cárgala en un segundo registro con li y después usa add. Un valor mayor no obtiene un inmediato más ancho solo porque esté escrito en hexadecimal.

Puntos clave

  • Un inmediato con signo de 12 bits va de −2048 a 2047.
  • lui proporciona los 20 bits superiores con 12 ceros inferiores.
  • Si la parte inferior de 12 bits es negativa, compénsala en el campo superior.
  • li carga un valor, la forma una dirección y lw lee la memoria.
  • auipc construye una dirección relativa a su propio pc.
2

Aritmética y flujo de control

Una vez que los valores están en los registros, las instrucciones calculan resultados y deciden qué se ejecuta a continuación. Las operaciones lógicas bit a bit y los desplazamientos actúan sobre las representaciones; las comparaciones con signo y sin signo dan distintos significados a esos patrones. Los saltos condicionales e incondicionales convierten las decisiones y los bucles de C en rutas explícitas. Seguir una ruta significa registrar tanto los valores actuales como la dirección de la siguiente instrucción, incluida la comprobación final fallida del bucle.

2.1 Calcula con bits y valores con signo

Una instrucción aritmética lee bits de registros y escribe un nuevo patrón de bits. Para entender ese patrón se necesitan tanto la operación como la interpretación prevista, con o sin signo.

Suma, resta y desbordamiento

En RV32I, add, sub y addi conservan los 32 bits inferiores del resultado. Sumar 1 a 0x7FFFFFFF produce 0x80000000, que representa −2 147 483 648 como entero con signo. La instrucción no produce ninguna excepción de desbordamiento.

Para la suma sin signo, el software puede detectar el desbordamiento:

add t0, a0, a1
bltu t0, a0, Overflow

Una suma sin signo desbordada es menor que uno de los operandos. Esta comprobación sin signo no es la regla para el desbordamiento con signo, donde importan los signos de los operandos y del resultado.

La extensión M proporciona mul, mulh, mulhu, div, divu, rem y remu. mul conserva los bits inferiores del producto; las instrucciones que obtienen la parte superior del producto distinguen entre operandos con signo y sin signo. La división por cero devuelve un cociente con todos sus bits a uno y el dividendo original como resto, sin producir una excepción de división.

Selecciona, activa e invierte bits

and, or y xor operan de forma independiente en cada bit. AND con 1 conserva un bit; AND con 0 lo pone a cero. OR con 1 pone un bit a uno y XOR con 1 lo invierte.

srli t0, a0, 4
andi t0, t0, 0xF

Con a0 = 0xABCD, el desplazamiento a la derecha da 0xABC. Aplicar la máscara 0xF deja 0xC, los bits originales 7–4. El desplazamiento coloca el campo en la parte inferior; la máscara elimina todo lo que queda por encima. Para poner el bit 4 a uno sin cambiar los demás, usa ori a0, a0, 0x10. xori con −1 invierte los 32 bits.

Elige un desplazamiento según su regla de relleno

Un desplazamiento a la izquierda, sll o slli, rellena las posiciones inferiores con ceros. Un desplazamiento lógico a la derecha, srl o srli, rellena las posiciones superiores con ceros. Un desplazamiento aritmético a la derecha, sra o srai, copia el bit de signo en las posiciones superiores. Los desplazamientos con registro de RV32 solo usan los 5 bits inferiores del registro que contiene la cantidad de desplazamiento.

Desplazar 13 a la izquierda 3 posiciones produce 104. Esto multiplica por 8 porque no se pierde ningún bit necesario. Los desplazamientos a la izquierda que descartan bits significativos conservan solo el resultado que cabe en el registro.

Para valores sin signo, desplazar a la derecha divide por una potencia de 2 y redondea hacia abajo. Para valores con signo, el desplazamiento aritmético a la derecha redondea hacia menos infinito: desplazar −7 a la derecha 1 posición da −4. La división con signo de C trunca hacia cero y da −3 para -7 / 2. Un compilador necesita una corrección antes de sustituir esa división con signo por un desplazamiento. Un desplazamiento lógico del mismo patrón negativo rellena con ceros y da un resultado positivo grande.

Compara para producir un valor

slt escribe 1 cuando su primer operando de origen con signo es menor, y 0 en caso contrario. sltu usa una interpretación sin signo. Con 0xFFFFFFFF y 1 como operandos, slt escribe 1, mientras que sltu escribe 0. Las versiones con inmediato son slti y sltiu; esta última también aplica extensión de signo a su inmediato antes de la comparación sin signo.

AND bit a bit es diferente de && de C. Un resultado bit a bit puede contener muchos bits a uno; AND lógico produce 0 o 1 y puede omitir la evaluación de la segunda expresión.

Puntos clave

  • La aritmética de RV32 conserva los 32 bits inferiores y no produce una excepción por desbordamiento.
  • AND selecciona bits, OR los pone a uno y XOR los invierte.
  • El desplazamiento aritmético a la derecha preserva el signo; el lógico rellena con ceros.
  • srai y la división con signo de C redondean de forma distinta los valores negativos que no son múltiplos exactos.
  • slt y sltu interpretan de forma distinta los mismos patrones de registro.
  • La multiplicación y la división de la extensión M son independientes de RV32I.

2.2 Elige la siguiente instrucción

Un salto condicional compara dos registros y cambia el contador de programa cuando se cumple la condición. Cuando no se cumple, la ejecución continúa con la siguiente instrucción.

Elige la comparación
InstrucciónCondición de salto
beq, bneIgual, distinto
blt, bgeMenor que con signo, mayor o igual que con signo
bltu, bgeuMenor que sin signo, mayor o igual que sin signo

RISC-V combina la comparación y el salto en una sola instrucción. No tiene indicadores de condición de enteros que deban establecerse primero. Para comparar con cero, usa zero como uno de los operandos de origen. beqz a0, L se expande en beq a0, zero, L.

Las formas mayor que y menor o igual que son abreviaturas del ensamblador con los operandos de origen intercambiados. bgt a0, a1, L se expande en blt a1, a0, L. Intercambiar el orden de los operandos importa tanto como elegir el mnemónico correcto.

Flujo de control con signo y sin signo

Si a0 contiene 0xFFFFFFFF y a1 contiene 1, blt a0, a1, L realiza el salto porque −1 con signo es menor que 1. bltu no salta, porque el primer patrón es un valor sin signo grande. Usa comparaciones coherentes con el significado de los datos, incluida la comparación sin signo para las direcciones.

Una comprobación de límites sin signo puede rechazar índices negativos con signo, así como índices iguales o superiores a una longitud no negativa pequeña:

bgeu a0, a1, OutOfBounds

Para una longitud de 20, un índice de −1 aparece como 4 294 967 295 sin signo y provoca el salto. Un índice de 19 no lo provoca; uno de 20 sí. La longitud debe representar la longitud no negativa real del arreglo.

Saltos y direcciones de retorno

jal ra, f escribe la dirección de la siguiente instrucción, pc + 4, en ra y después salta a f. Un j L simple se expande en jal zero, L; su valor de enlace se descarta. Esto es apropiado para el salto de vuelta de un bucle, pero no establece una dirección de retorno para una llamada.

jalr rd, offset(rs1) suma un desplazamiento con signo de 12 bits a un valor de registro, pone a cero el bit 0 del destino y escribe su propio pc + 4 en rd. ret se expande en jalr zero, 0(ra), que retorna a la dirección guardada sin conservar un nuevo enlace.

Los saltos condicionales y jal almacenan desplazamientos relativos a las direcciones de sus propias instrucciones. Sus alcances son de aproximadamente ±4 KiB y ±1 MiB, respectivamente. jalr usa un destino basado en un registro y puede alcanzar todo el espacio de direcciones. Una transferencia condicional lejana puede invertir la condición para omitir un salto más largo:

bne a0, a1, Skip
jal zero, Far
Skip:

Aquí Far está dentro del alcance de jal. Si se cumple la igualdad, el salto corto continúa secuencialmente hacia el salto incondicional; en caso contrario, lo omite.

Reconoce un bloque básico

Un bloque básico tiene una entrada al principio y ninguna transferencia de control antes del final. Dentro de él, las instrucciones se ejecutan en secuencia. Los destinos de salto inician nuevos bloques; los saltos condicionales e incondicionales los terminan. Esto permite razonar qué asignaciones siempre se ejecutan antes de una comprobación o salida determinada.

Puntos clave

  • Elige saltos con signo o sin signo según el significado de los operandos.
  • Los saltos condicionales de RISC-V comparan registros directamente sin indicadores de condición.
  • jal guarda un enlace; j lo descarta.
  • jalr usa un registro más un inmediato y pone a cero el bit 0 del destino.
  • Un desplazamiento condicional es relativo a la propia instrucción de salto.

2.3 Convierte el flujo de control de C en saltos

El ensamblador hace explícita cada ruta de una decisión o un bucle. Las etiquetas marcan destinos, los saltos condicionales eligen rutas y los saltos incondicionales evitan continuaciones secuenciales no deseadas.

Un if-else necesita una vía de salida

Traduce if (i == j) f = g + h; else f = g - h;, con f, g, h, i, j en s0–s4:

bne s3, s4, Else
add s0, s1, s2
j Exit
Else:
sub s0, s1, s2
Exit:

El primer salto comprueba la condición opuesta a la de C para que, si hay igualdad, la ejecución continúe hacia la suma. El salto después de la suma impide ejecutar la resta. Con g = 10 y h = 3, los índices iguales dan 13; los distintos dan 7. Quitar j Exit hace que la resta también se ejecute después de la suma.

Para a < b && b < c, salta a la parte else en cuanto falle cualquiera de las comparaciones. Para a < b || b < c, salta a la parte then en cuanto se cumpla cualquiera de ellas. Esto implementa la evaluación en cortocircuito, incluida la omisión de una segunda expresión cuyo valor no es necesario.

Un bucle while comprueba antes de su cuerpo

Para while (save[i] == k) i++;, mantén i en s3, k en s5 y la base del arreglo en s6:

Loop:
slli t1, s3, 2
add t1, t1, s6
lw t0, 0(t1)
bne t0, s5, Exit
addi s3, s3, 1
j Loop
Exit:

Cada pasada forma la dirección del elemento actual, lo carga y lo comprueba. Con el arreglo {7, 7, 7, 2, 7}, índice inicial 0 y k = 7, el bucle termina con índice 3. La comprobación fallida no incrementa el índice. El arreglo debe contener un elemento que detenga el bucle dentro de su rango válido.

Suma un arreglo con un número de elementos dado

Para un número no negativo de elementos en a1 y la base del arreglo en a0:

li t1, 0
li t0, 0
Loop:
bge t0, a1, Done
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
j Loop
Done:
mv a0, t1
ret

La comprobación inicial gestiona una longitud cero sin realizar ninguna carga. Con cuatro elementos {5, -2, 10, 7}, la suma acumulada es 5, 3, 13 y después 20. La secuencia ejecuta 33 instrucciones: 2 de preparación, 7 por cada una de las cuatro iteraciones que cumplen la condición, una comprobación fallida y 2 instrucciones finales.

Una versión con puntero forma una dirección final una sola vez y avanza la dirección actual en 4. Para un rango válido del arreglo cuya dirección final no se desborde, puede usar comparaciones de direcciones sin signo:

li t1, 0
slli t0, a1, 2
add t0, a0, t0
bgeu a0, t0, Done
Loop:
lw t3, 0(a0)
add t1, t1, t3
addi a0, a0, 4
bltu a0, t0, Loop
Done:
mv a0, t1
ret

Hay 4 instrucciones de preparación, 4 por iteración y 2 instrucciones finales, un total de 22 para cuatro elementos. El salto inicial omite el cuerpo si el rango está vacío; el puntero avanza un elemento después de cada carga.

Decisiones con varias opciones

Una sentencia switch puede usar comparaciones repetidas o una tabla de saltos cuando los valores de los casos son densos. El despacho mediante tabla comprueba el rango del caso, multiplica un índice por el tamaño de 4 bytes de la entrada, carga la dirección del caso y salta a ella con jalr zero, 0(t0). La comprobación del rango debe preceder a la carga de la tabla para que un caso inválido no pueda seleccionar una dirección ajena.

Puntos clave

  • Para saltar por encima de la parte then se suele usar la condición opuesta.
  • Salta por encima de la parte else después de completar la parte then.
  • Las condiciones compuestas deben preservar la evaluación en cortocircuito.
  • Comprueba un bucle con contador antes de leer su primer elemento.
  • Los bucles con punteros pueden evitar el escalado repetido de índices.
  • Una tabla de saltos requiere comprobar el rango antes del despacho.
3

Campos de instrucciones y código máquina

Los nombres del ensamblador se convierten en campos fijos dentro de las instrucciones máquina. Parte de la operación para determinar su formato y después asigna los registros y los inmediatos a sus posiciones de bits. Las cargas, los almacenamientos y los saltos condicionales e incondicionales usan distribuciones diferentes porque contienen información diferente. Codificar y decodificar en ambos sentidos revela errores de desplazamiento, signo y orden de campos que el ensamblador legible puede ocultar.

3.1 Codifica operaciones de registro, cargas y almacenamientos

Una instrucción máquina es una palabra de 32 bits dividida en campos. Codificar significa elegir la distribución correcta, asignar cada operando a un campo y escribir cada campo con su anchura completa.

Las posiciones fijas simplifican la decodificación

Los 7 bits inferiores, 6–0, contienen el código de operación. Cuando están presentes, rd ocupa los bits 11–7, funct3 los bits 14–12, rs1 los bits 19–15 y rs2 los bits 24–20. Los campos de registro de cinco bits identifican de x0 a x31. Los campos de función distinguen las operaciones que comparten un código de operación.

RV32I usa los formatos R, I, S, B, U y J. Estos acomodan operandos diferentes en los mismos 32 bits, manteniendo estables las posiciones de los registros. Para los inmediatos con signo, el bit 31 contiene el signo.

Tipo R: dos operandos de origen y un destino

Desde el bit 31 hasta el bit 0, la distribución R es:

funct7 | rs2 | rs1 | funct3 | rd | opcode
   7      5     5       3     5       7 bits

Para add s0, a0, a1, rd = 8, rs1 = 10, rs2 = 11. ADD usa el código de operación 0110011, funct3 = 000, funct7 = 0000000:

0000000 | 01011 | 01010 | 000 | 01000 | 0110011
0000 0000 1011 0101 0000 0100 0011 0011

La palabra hexadecimal es 0x00B50433. SUB solo cambia funct7 a 0100000, lo que da 0x40B50433 para sub s0, a0, a1. Los campos de origen aparecen en un orden distinto al de los operandos de ensamblador, por lo que no debes limitarte a concatenar los nombres de registro escritos.

Tipo I: un operando de origen y un inmediato

La distribución I es imm[11:0] | rs1 | funct3 | rd | opcode. addi usa el código de operación 0010011 y funct3 = 000. En addi sp, sp, -16, ambos campos de registro contienen 00010. El inmediato de 12 bits es 111111110000 (0xFF0), lo que produce 0xFF010113.

Una carga también usa el tipo I, con la base de la dirección como rs1 y el registro cargado como rd. lw t0, 32(s3) tiene el inmediato 32, la base x19, el destino x5, el código de operación 0000011 y funct3 = 010, lo que da 0x0209A283.

Las operaciones de desplazamiento con inmediato usan los 5 bits inferiores de la región del inmediato como cantidad de desplazamiento. Los bits de control superiores distinguen los desplazamientos: srli t0, t1, 3 es 0x00335293, mientras que srai t0, t1, 3 es 0x40335293. El bit 30 selecciona la regla de relleno aritmético.

Tipo S: almacena un valor de origen

Un almacenamiento no tiene registro de destino. La distribución S usa ese espacio para parte de su inmediato:

imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode

Para sw t0, 48(s3), rs2 = 5 proporciona el valor almacenado y rs1 = 19 proporciona la base. El inmediato de 12 bits 48 se divide en 0000001 y 10000. Con funct3 = 010 y el código de operación 0100011, el resultado es 0x0259A823. No hay ningún rd que deba llenarse con t0.

Después de unir los campos, agrupa la cadena completa de 32 bits en ocho cuartetos de 4 bits y convierte cada uno en un dígito hexadecimal. Después decodifica el resultado: confirma el código de operación, los números de registro y el inmediato con signo. Rellenar un campo de registro hasta 5 bits es esencial; omitir un cero inicial cambia las posiciones de los campos posteriores.

Puntos clave

  • Identifica el formato a partir de la operación antes de asignar los campos de operandos.
  • Los campos de registro tienen 5 bits y ocupan posiciones estables.
  • El tipo R usa funct7 para distinguir add de sub.
  • El tipo I mantiene unido un inmediato de 12 bits.
  • El tipo S divide el desplazamiento y tiene rs2, pero no rd.
  • Comprueba una palabra codificada decodificándola de nuevo en operandos.

3.2 Reconstruye desplazamientos y decodifica palabras máquina

Las codificaciones de saltos condicionales e incondicionales ahorran espacio al omitir un bit del desplazamiento que siempre es cero. Sus campos siguen representando distancias en bytes, y el signo debe reconstruirse antes de calcular el destino.

Desplazamientos de saltos de tipo B

Calcula el desplazamiento como la dirección de destino menos la dirección del propio salto condicional. Una instrucción bne en 0x1010 cuyo destino es 0x1000 usa −16 bytes, no −20 ni −4 instrucciones.

El tipo B usa el código de operación 1100011. Su desplazamiento con signo tiene 13 bits, incluido un bit 0 que vale cero y no se almacena. El rango va de −4096 a +4094 bytes, en incrementos de 2. La distribución es:

Bits del desplazamientoBits de la instrucción
imm[12]31
imm[10:5]30–25
imm[4:1]11–8
imm[11]7

Los campos de origen permanecen en sus posiciones habituales. Los valores de funct3 son 000 para beq, 001 para bne, 100 para blt, 101 para bge, 110 para bltu y 111 para bgeu.

Para bne t0, t1, -16, el desplazamiento de 13 bits es 1 1111 1111 0000. Sus partes dispersas son 1, 111111, 1000 y 1 en el orden de la tabla. Con rs1 = 5 y rs2 = 6, la instrucción es 0xFE6298E3. Sustituir el desplazamiento por +12 da 0x00629663.

Los desplazamientos admiten incrementos de 2 bytes porque las instrucciones comprimidas pueden tener una longitud de 2 bytes. Un destino al que se salta debe seguir cumpliendo la alineación de instrucciones activa: RV32I sin instrucciones comprimidas requiere una alineación de 4 bytes.

Tipos J y U

jal usa el código de operación 1101111. Su desplazamiento en bytes con signo de 21 bits omite el bit 0 y coloca los bits en el orden imm[20|10:1|11|19:12] a lo largo de los bits 31–12 de la instrucción. El rango va de −1 048 576 a +1 048 574 bytes. jal ra, 192 se convierte en 0x0C0000EF.

El tipo U coloca su valor superior de 20 bits directamente en los bits 31–12 de la instrucción, seguido de rd y el código de operación. lui usa 0110111; auipc usa 0010111. Así, lui t0, 0x12345 es 0x123452B7.

Decodifica primero por el código de operación

Para 0x00A12423, los 7 bits inferiores son 0100011: un almacenamiento. funct3 = 010 selecciona sw. Los campos de origen dan la base x2 (sp) y el valor x10 (a0). Los dos fragmentos del inmediato se unen para dar 8, por lo que el ensamblador es sw a0, 8(sp).

Para un salto condicional, une las partes dispersas en el orden de los bits del desplazamiento, añade el cero implícito y aplica extensión de signo al resultado completo de 13 bits. Tratar los 12 bits almacenados como un inmediato ordinario de tipo I pierde tanto la disposición correcta como la escala. La posición fija del bit de signo y la reutilización de las posiciones de otros campos reducen el trabajo de decodificación, aunque la distribución escrita es menos cómoda para leer a mano.

La reubicación preserva las relaciones

Si un salto condicional y su destino se desplazan la misma distancia, al restar sus nuevas direcciones se obtiene el desplazamiento anterior. Una referencia a otra sección puede cambiar cuando las secciones se distribuyen por separado. Por tanto, un enlazador puede preservar un salto relativo interno mientras ajusta una referencia a un símbolo externo.

Puntos clave

  • Los desplazamientos de los saltos condicionales y de jal cuentan bytes desde la dirección de la instrucción.
  • Los tipos B y J omiten el bit 0, que es cero.
  • Reconstruye un desplazamiento antes de interpretar su signo.
  • El código de operación determina cómo se dividen los bits restantes.
  • Desplazar una instrucción y su destino juntos preserva su distancia relativa.
4

Llamadas a procedimientos y almacenamiento en la pila

Una llamada debe entregar argumentos, preservar el estado útil y retornar al lugar correcto. Las convenciones de registros permiten cooperar a procedimientos independientes; los marcos de pila mantienen separado el estado de las llamadas anidadas y recursivas. Los mismos cálculos de direcciones usados para operandos en memoria también implementan punteros, campos de estructuras y cadenas. Sigue un valor durante una llamada con el mismo cuidado que durante una instrucción aritmética.

4.1 Pasa argumentos y preserva valores de registros

Una llamada a un procedimiento transfiere control y datos entre partes de código escritas de forma independiente. La convención de llamada indica a ambas partes dónde van los argumentos y resultados y qué valores de registros deben conservarse.

La secuencia de llamada y retorno

El llamador coloca los primeros ocho argumentos enteros en a0–a7; los argumentos adicionales van en la pila. El procedimiento devuelve un resultado entero en a0, usando también a1 para un resultado de 64 bits en RV32.

jal ra, f guarda su propia dirección más 4 en ra y después salta a f. jal f es su forma abreviada. La pseudoinstrucción call f normalmente se expande en auipc más jalr para lograr un mayor alcance; el enlazador puede reducirla a una instrucción jal. En ambos casos, la dirección de retorno sigue a la secuencia de llamada real.

ret es jalr zero, 0(ra). Salta mediante la dirección guardada y descarta un nuevo enlace. Un j f simple también descarta su enlace, por lo que no establece una llamada y retorno ordinarios.

Decide quién guarda un valor

Los registros preservados por el llamador incluyen t0–t6, a0–a7 y ra. El procedimiento llamado puede modificarlos. Si el llamador necesita más tarde uno de sus valores actuales, lo guarda antes de la llamada.

Los registros preservados por el procedimiento llamado incluyen s0–s11 y sp. Un procedimiento llamado que modifica un registro s debe guardar el valor recibido y restaurarlo antes de retornar. Restaurar sp devuelve al llamador a su propio marco. El procesador no impone estas obligaciones; los módulos compilados cooperan porque su software sigue la ABI.

Supón que un llamador tiene un contador de bucle en t0 y llama a un procedimiento auxiliar. El auxiliar puede usar t0 para su propio cálculo según la convención. Trasladar el contador a s1 puede hacer que se conserve durante las llamadas a auxiliares, pero el procedimiento que lo contiene debe entonces preservar el valor de s1 que recibió de su propio llamador.

Un procedimiento hoja puede no necesitar marco

Un procedimiento hoja no llama a ningún otro procedimiento. Esta función calcula (g + h) - (i + j) a partir de argumentos en a0–a3:

leaf:
add t0, a0, a1
add t1, a2, a3
sub a0, t0, t1
ret

Solo usa registros de trabajo preservados por el llamador y deja ra sin cambios. Para los valores de entrada 9, 4, 6 y 1, devuelve 6. No necesita un marco de pila.

Si el cálculo usa s0, debe preservar ese registro:

addi sp, sp, -16
sw s0, 12(sp)
add t0, a0, a1
add t1, a2, a3
sub s0, t0, t1
mv a0, s0
lw s0, 12(sp)
addi sp, sp, 16
ret

El resultado se traslada a a0 antes de restaurar el valor anterior de s0. Si el valor recibido en s0 era 1234, sigue siendo 1234 después. El marco reservado es de 16 bytes para preservar la alineación estándar de la pila aunque la palabra guardada solo use 4.

Una llamada puede entenderse como seis acciones: colocar argumentos, transferir el control, obtener almacenamiento, realizar la tarea, colocar el resultado y retornar. En cada límite, distingue el valor que se devuelve de los valores anteriores que la convención exige que el procedimiento preserve.

Puntos clave

  • Los argumentos enteros comienzan en a0–a7 y el primer resultado está en a0.
  • ra apunta a la dirección posterior a la instrucción de llamada real o a la secuencia de llamada expandida.
  • Los llamadores protegen los valores activos de los registros que deben preservar; los procedimientos llamados restauran los registros preservados que modifican.
  • Un procedimiento hoja que solo usa registros preservados por el llamador puede no necesitar marco.
  • Traslada el resultado antes de restaurar el registro preservado que lo contenía.

4.2 Da a cada llamada su propio marco de pila

Las llamadas anidadas necesitan un lugar donde guardar los datos de la llamada exterior. La pila proporciona almacenamiento de tipo último en entrar, primero en salir: la llamada más reciente termina primero y libera su propio marco.

Reserva espacio por debajo del puntero de pila anterior

La pila crece hacia direcciones más bajas. addi sp, sp, -16 reserva 16 bytes; addi sp, sp, 16 los libera. La ABI estándar mantiene sp como múltiplo de 16 durante toda la ejecución. Un marco puede contener registros guardados, la dirección de retorno, valores derramados a memoria, objetos locales y espacio para argumentos salientes en la pila.

Un patrón habitual para un procedimiento que no es hoja es:

addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
# body, including calls
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret

Los desplazamientos se refieren al nuevo sp. Si es 0x2000, el ra guardado ocupa de 0x200C a 0x200F. El epílogo correspondiente restaura los valores mientras el marco aún existe y después lo libera. Cada vía de retorno debe compensar la reserva.

Un puntero de marco, convencionalmente s0, puede dar desplazamientos fijos a los objetos locales cuando sp cambia dentro de un procedimiento. Su valor recibido debe preservarse como en cualquier otro uso de s0.

La recursión conserva copias separadas

El factorial necesita el n original después de calcular fact(n - 1). También necesita su propia dirección de retorno después de que el jal recursivo sobrescriba ra:

fact:
addi sp, sp, -16
sw ra, 12(sp)
sw a0, 8(sp)
li t0, 2
blt a0, t0, Base
addi a0, a0, -1
jal ra, fact
lw t1, 8(sp)
mul a0, t1, a0
j Done
Base:
li a0, 1
Done:
lw ra, 12(sp)
addi sp, sp, 16
ret

La instrucción mul de la extensión M multiplica el resultado interior devuelto por el n original. Para fact(3), coexisten los marcos de 3, 2 y 1. La llamada base devuelve 1; la siguiente devuelve 2; la llamada exterior devuelve 6. Con sp = 0x7FFFFF00 inicial, tres marcos lo reducen en 48 bytes hasta 0x7FFFFED0. Los retornos los liberan en orden inverso.

La comprobación base trata los valores no negativos 0 y 1. Para este resultado de 32 bits, el factorial es exacto hasta 12!: 479 001 600. Los bits inferiores del producto ya no representan el resultado completo en 13!, que es 6 227 020 800.

Sitúa la pila en la memoria del programa

Una distribución típica tiene el código en texto, las variables globales en datos estáticos, los objetos reservados dinámicamente en el montículo y los marcos de llamadas en la pila. El montículo suele crecer hacia arriba y la pila hacia abajo. La ubicación real depende del entorno de ejecución.

Un puntero a un objeto local de la pila deja de ser válido cuando el tiempo de vida de ese objeto termina al retornar. Restaurar sp no borra los bytes anteriores, pero las llamadas posteriores pueden reutilizarlos. Del mismo modo, cambiar sp y seguir usando los desplazamientos antiguos de los valores guardados puede hacer que se lean bytes del marco equivocado.

Puntos clave

  • Resta de sp para reservar espacio; suma la misma cantidad para liberarlo.
  • La ABI estándar requiere una alineación de pila de 16 bytes.
  • Una llamada anidada sobrescribe ra, por lo que debes preservar la dirección de retorno exterior.
  • Cada activación recursiva tiene sus propios argumentos guardados y estado de retorno.
  • Todas las vías de salida deben restaurar el marco y los registros requeridos.
  • Un objeto local no sobrevive a su marco de pila.

4.3 Usa direcciones para arreglos, estructuras y cadenas

Un puntero es un valor de dirección. El ensamblador implementa una desreferenciación usando esa dirección como base de una carga o un almacenamiento, e implementa la aritmética de punteros sumando una distancia en bytes.

Sigue el puntero y después accede al valor

Con int *p en a0, lw t0, 0(a0) lee *p, mientras que sw t0, 0(a0) lo escribe. El registro contiene la dirección; la memoria en esa dirección contiene el entero. Si una variable de tipo puntero está almacenada en memoria, carga primero el valor del puntero y después úsalo como base de una segunda carga.

Obtener la dirección de un objeto local de C requiere almacenamiento direccionable, normalmente una posición de la pila. Un registro de hardware no tiene una dirección de memoria que pueda pasarse como &x.

La aritmética de punteros de C escala automáticamente. Para RV32 ILP32, incrementar int *p suma 4 bytes, mientras que incrementar char *p suma 1. El ensamblador no tiene un tipo de puntero que proporcione la escala:

addi a0, a0, 4    # next int
addi a1, a1, 1    # next char

Un argumento de arreglo es su dirección base, no una copia de sus elementos. Pasa el número de elementos por separado cuando un procedimiento lo necesite; el puntero por sí solo no codifica la longitud del arreglo.

Los campos de estructuras tienen desplazamientos en bytes

Para struct item { int id; int score; };, id ocupa los bytes 0–3 y score los bytes 4–7. Con la dirección del objeto en a0, lw t0, 4(a0) lee la puntuación. En general, los desplazamientos de los campos siguen las reglas de distribución y relleno de la ABI. No deduzcas todos los desplazamientos de campos solo contando las declaraciones del código fuente.

Para un arreglo de estas estructuras de 8 bytes, el siguiente elemento comienza 8 bytes más adelante. Dentro de cada elemento, la puntuación sigue estando a 4 bytes del comienzo de ese elemento. El paso entre elementos y el desplazamiento del campo son partes separadas del cálculo de la dirección.

Copia una cadena terminada en nulo

Una cadena de bytes de C termina con un byte cero. Para copiarla, lee y almacena un byte antes de decidir si la copia ha terminado:

Loop:
lbu t0, 0(a1)
sb t0, 0(a0)
addi a0, a0, 1
addi a1, a1, 1
bnez t0, Loop
ret

Aquí a1 es el origen y a0 el destino. El destino debe tener espacio suficiente. lbu lee un byte sin signo; sb almacena solo sus 8 bits inferiores. Ambos punteros avanzan 1, incluso en la última iteración. El salto comprueba el byte copiado, por lo que el cero terminador también se almacena.

Copiar «RISC» requiere cinco iteraciones para cuatro letras y el byte cero. Con cinco instrucciones por iteración más ret, ejecuta 26 instrucciones. Una cadena vacía también necesita una iteración para copiar su terminador. Una carga de palabra leería cuatro bytes a la vez y no implementaría este algoritmo que procesa un byte cada vez.

UTF-8 puede codificar un carácter en varios bytes. Este bucle copia esos bytes sin cambios; no cuenta los caracteres mostrados. Los bucles de arreglos basados en punteros pueden avanzar igualmente según el paso entre elementos sin volver a calcular un índice escalado cada vez. Los compiladores con optimización pueden realizar esa transformación incluso cuando el programa en C usa índices de arreglos ordinarios.

Puntos clave

  • La desreferenciación usa la dirección contenida en el puntero.
  • La aritmética de punteros en ensamblador debe incluir el tamaño del elemento.
  • Los parámetros de arreglos necesitan una longitud independiente cuando el procedimiento usa un número de elementos.
  • El paso entre estructuras y el desplazamiento de un campo corresponden a partes diferentes de una dirección.
  • Un bucle de copia de cadenas debe almacenar el byte cero terminador.
5

Del código fuente a la ejecución

Un programa pasa por herramientas que conocen distintos aspectos de su forma final. Un compilador elige instrucciones, un ensamblador las codifica, un enlazador une módulos y un cargador prepara la ejecución. Los símbolos y las reubicaciones transportan referencias pendientes entre etapas. Seguir una llamada desde el código fuente hasta su dirección final hace concretas las funciones de los archivos objeto, las bibliotecas compartidas y el código de inicio.

5.1 Traduce el código fuente a un objeto reubicable

Un archivo fuente se convierte en instrucciones ejecutables mediante varias etapas de traducción. Cada etapa tiene una responsabilidad diferente, y los archivos intermedios conservan la información necesaria para las etapas posteriores.

El compilador elige instrucciones

Para C, el camino habitual es código fuente, ensamblador, archivo objeto, ejecutable y después un proceso en ejecución. Un compilador analiza el código fuente, lo optimiza, asigna registros y selecciona instrucciones para una ISA de destino. Un controlador de compilación puede ejecutar varias etapas con un solo comando.

gcc -S program.c se detiene después de producir el ensamblador. gcc -c program.c produce un archivo objeto sin enlazar. El compilador de destino determina qué ISA usa ese ensamblador; un programa RISC-V necesita una cadena de herramientas dirigida a RISC-V.

Con -O0, los valores suelen pasar por posiciones de la pila y el ensamblador sigue de cerca las sentencias individuales del código fuente. Con -O2, la asignación de registros, el trabajo eliminado y los bucles reorganizados pueden ocultar esa correspondencia. Una sentencia de C no tiene una única traducción fija a ensamblador.

El ensamblador codifica y registra

El ensamblador convierte mnemónicos y nombres de registros en bits, expande pseudoinstrucciones y resuelve las etiquetas que puede localizar. Un salto hacia delante hace referencia a una etiqueta que aparece más tarde. Un ensamblador puede recopilar primero las posiciones y después codificar las referencias, o registrar los lugares que debe completar una vez que se conozca el destino.

Las directivas indican al ensamblador cómo organizar la salida; el procesador no las ejecuta:

.data
.align 2
count:
.word 5
message:
.string "hi"
.text
.globl main
main:
# instructions follow

.word 5 genera un valor de datos de 4 bytes. .string "hi" genera los bytes de los caracteres y un cero terminador. En el ensamblador GNU de RISC-V, .align 2 alinea el siguiente elemento con un límite de 4 bytes. .globl hace que un símbolo sea visible para otros archivos objeto. Las etiquetas dan nombre a posiciones; por sí mismas no reservan bytes.

Qué conserva un archivo objeto

Un objeto reubicable suele contener una cabecera, código en secciones de texto, datos estáticos, una tabla de símbolos e información de reubicación. La tabla de símbolos distingue los nombres definidos aquí de las referencias definidas en otros lugares. La información de depuración opcional conecta las instrucciones con las líneas fuente; -g la solicita.

Los formatos habituales de objeto incluyen ELF, Mach-O y PE/COFF. El formato organiza el código máquina y los metadatos; es distinto de la ISA que define los bits de cada instrucción.

Para call printf, el ensamblador puede generar una secuencia auipc/jalr sin conocer la dirección final de la función. Registra un símbolo no definido y entradas de reubicación para que el enlazador pueda completar la referencia. La distancia de un salto local puede conocerse ya cuando su destino se desplaza con la misma sección, aunque los cambios posteriores de distribución, como la relajación, pueden requerir ajustes.

Otras vías de ejecución

Un intérprete realiza operaciones mediante otro programa en ejecución. Un compilador en tiempo de ejecución genera código máquina durante la ejecución, a menudo después de identificar el código utilizado con frecuencia. Java suele usar código de bytes seguido de interpretación y compilación JIT; Python suele usar un intérprete. Un JIT intercambia trabajo de compilación durante la ejecución por una ejecución repetida más rápida.

Por tanto, un archivo objeto es más que una lista de instrucciones en bruto, pero aún no es un proceso en ejecución completo. Los símbolos sin resolver y la distribución final todavía requieren la intervención del enlazador y el cargador.

Puntos clave

  • El compilador selecciona y optimiza instrucciones para una ISA de destino.
  • El ensamblador codifica instrucciones y expande pseudoinstrucciones.
  • Las directivas organizan la salida; las etiquetas dan nombre a posiciones.
  • Los símbolos y las reubicaciones conservan referencias que las etapas posteriores deben resolver.
  • -S se detiene en el ensamblador, mientras que -c se detiene en un archivo objeto.
  • Los intérpretes y los compiladores JIT proporcionan vías diferentes de ejecución.

5.2 Enlaza módulos e inicia un proceso

Un enlazador construye un archivo ejecutable a partir de módulos objeto y bibliotecas. Un cargador usa ese archivo para crear un proceso en ejecución. Saber qué etapa se ocupa de una dirección ayuda a explicar tanto la reubicación como los errores de inicio.

Distribuye módulos y ajusta referencias

El enlazador coloca las secciones de entrada en el espacio de direcciones del ejecutable, resuelve las definiciones de símbolos y usa entradas de reubicación para ajustar las referencias. Un nombre que ningún módulo enlazado define produce un error de «referencia no definida».

Supón que el texto comienza en 0x00010000 y los datos en 0x10000000. El objeto A aporta 0x100 bytes de texto y 0x20 bytes de datos; B va inmediatamente después. El texto de B comienza en 0x00010100 y sus datos en 0x10000020.

Una instrucción jal de A en el desplazamiento de texto 0x40 tiene la dirección 0x00010040. Si procB comienza al inicio del texto de B, el desplazamiento de la llamada es 0xC0, o 192 bytes, codificado con ra como 0x0C0000EF. Una variable en el desplazamiento 0x10 de los datos de B queda en 0x10000030. Con gp = 0x10000800, su desplazamiento es −2000, que cabe en un desplazamiento de carga con signo de 12 bits. Son cálculos independientes de texto y datos.

Elige cómo se proporciona el código de biblioteca

El enlace estático copia el código objeto de biblioteca necesario en el ejecutable. El programa lleva ese código, lo que aumenta el tamaño del archivo; aplicar una corrección de la biblioteca requiere volver a enlazarlo. Las bibliotecas estáticas no eliminan la dependencia del programa respecto de su interfaz con el sistema operativo.

El enlace dinámico mantiene el código de biblioteca en archivos compartidos, como .so, .dll o .dylib. Los procesos pueden compartir páginas de código de biblioteca compatibles. Una biblioteca actualizada compatible puede servir a los programas sin volver a enlazar cada ejecutable, pero la versión requerida debe estar disponible cuando se necesite.

Con la vinculación diferida de ELF, la primera llamada pasa por una rutina intermediaria de la tabla de enlace de procedimientos (PLT). El enlazador dinámico resuelve el destino y lo registra en la tabla global de desplazamientos (GOT); las llamadas posteriores usan esa dirección registrada. Otras configuraciones resuelven los símbolos durante la carga.

El código independiente de la posición alcanza destinos locales mediante direccionamiento relativo, incluidas secuencias auipc, y usa indirección para destinos externos. Las distancias relativas internas de una biblioteca siguen siendo válidas cuando esta se desplaza como una unidad. La vinculación de referencias externas todavía puede requerir trabajo de reubicación.

El cargador crea el estado de ejecución

El cargador lee los metadatos del ejecutable, crea un espacio de direcciones, mapea o copia el código y los datos, prepara los argumentos y la pila inicial, inicializa el estado requerido de los registros y transfiere el control al código de inicio. El código de inicio llama a main; después de que retorne, el código de ejecución solicita la salida del proceso. Los sistemas modernos pueden mapear páginas del archivo y cargarlas en memoria cuando se necesiten.

ecall solicita un servicio del entorno. En RISC-V Linux, a7 contiene el número de llamada al sistema y a0–a5 contienen los argumentos. li a7, 93, li a0, 0, ecall solicita la salida con estado 0; el servicio 64 es write. Los números de servicio de un simulador proceden de su propia interfaz.

Una biblioteca que falta durante el enlace causa un fallo de enlace. Una biblioteca compartida necesaria que se elimina después del enlace puede, en cambio, impedir la carga o causar un fallo durante la vinculación en tiempo de ejecución. El ejecutable puede haberse construido correctamente aunque ahora no pueda iniciarse.

Puntos clave

  • El enlazador distribuye un ejecutable y resuelve y ajusta las referencias a símbolos.
  • El cargador mapea ese ejecutable y crea el estado inicial del proceso.
  • El enlace estático copia código de biblioteca; el dinámico necesita bibliotecas compartidas compatibles.
  • La vinculación diferida registra un destino resuelto para llamadas posteriores.
  • La independencia de la posición preserva las referencias relativas locales cuando se desplaza el código.
  • Los números de servicio de ecall pertenecen al entorno de ejecución.
6

Compromisos de diseño del conjunto de instrucciones

Los conjuntos de instrucciones toman decisiones diferentes sobre cuánto trabajo expresa una instrucción. Compara RISC-V y x86 mediante sus operandos, codificaciones y cálculos de direcciones, y después relaciona esas decisiones con restricciones reales. El rendimiento requiere considerar conjuntamente el número de instrucciones ejecutadas, el CPI y la duración del ciclo de reloj; la densidad del código y la compatibilidad del software responden a preguntas diferentes. Una evaluación útil del diseño identifica la carga de trabajo y la implementación que se comparan.

6.1 Compara instrucciones de RISC-V y x86

RISC y CISC describen enfoques generales de diseño de conjuntos de instrucciones. La comparación útil es cómo representa cada uno el trabajo: ubicaciones de operandos, longitud de instrucciones, cálculo de direcciones y flujo de control.

Operaciones regulares o instrucciones más complejas

Los diseños RISC favorecen las operaciones simples, muchos registros y el acceso mediante carga y almacenamiento. El enfoque se desarrolló a través del trabajo de IBM en el 801 a mediados de la década de 1970 y de RISC de Berkeley y MIPS de Stanford a principios de la década de 1980. RISC-V, ARM AArch64, MIPS y Power siguen esta tradición general.

Los diseños CISC ofrecen instrucciones de longitud variable, un direccionamiento más elaborado y operaciones que pueden combinar varios pasos. x86 surgió del 8086 presentado en 1978; entre las familias CISC anteriores están VAX y Motorola 68000.

En la sintaxis x86 de Intel, add eax, [ebx+8] lee un valor de 32 bits de la memoria y lo suma a eax. Una operación equivalente en RISC-V necesita una carga seguida de una operación aritmética:

lw t0, 8(s1)
add a0, a0, t0

La instrucción x86 combina un acceso a memoria con una suma. RISC-V los presenta como instrucciones separadas. x86 llama palabra doble a un valor de 32 bits; su palabra tiene 16 bits. Una palabra en los ejemplos de RV32 tiene 32 bits.

Codificación y direccionamiento

Una instrucción x86 tiene una longitud de 1 a 15 bytes. Las instrucciones base de RISC-V ocupan 4 bytes, y la extensión C proporciona formas comprimidas de 2 bytes. Las posiciones regulares de los campos simplifican la decodificación. Las longitudes variables pueden ahorrar espacio al usar codificaciones cortas para operaciones frecuentes.

RISC-V usa operandos de registro, inmediatos, direcciones de base más desplazamiento y direcciones relativas al pc. jalr pertenece al direccionamiento con base en un registro, mientras que los saltos condicionales, jal y auipc usan el pc.

x86 también combina una base, un índice escalado y un desplazamiento. mov eax, [ebx + esi*4 + 8] lee un elemento indexado de 4 bytes con un desplazamiento adicional en una instrucción. RISC-V forma el índice escalado y la dirección efectiva con instrucciones separadas antes de cargar. En ambos procesadores, el trabajo debe realizarse internamente.

Registros y comparaciones

RISC-V tiene 32 registros de enteros, incluido su registro cero fijo. El x86-64 convencional tiene 16 registros de propósito general; x86 de 32 bits tiene 8. Muchas instrucciones aritméticas x86 usan un destino que también es un operando de origen: add eax, ebx modifica eax. La instrucción add de tres operandos de RISC-V puede conservar ambos valores de origen mientras escribe en un tercer registro.

Las operaciones aritméticas y cmp de x86 establecen indicadores de condición; un salto posterior como jl los comprueba. RISC-V usa blt para comparar y saltar directamente. ARM AArch64 también usa indicadores, por lo que su presencia por sí sola no permite clasificar una ISA como CISC.

Qué significa reducido

RISC no significa que una ISA moderna deba tener un número total de instrucciones diminuto. RV32I tiene 40 instrucciones base, pero las extensiones opcionales añaden muchas más. La idea central son operaciones y codificaciones regulares y relativamente simples. Las codificaciones comprimidas también muestran que la longitud fija de las instrucciones es una tendencia de diseño, no una definición que deba cumplir cada instrucción RISC.

Puntos clave

  • RISC-V separa el acceso a memoria de las operaciones aritméticas.
  • x86 puede combinar operandos en memoria y operaciones aritméticas en una instrucción.
  • La longitud de las instrucciones y los modos de direccionamiento afectan a la decodificación y a la densidad del código.
  • RISC-V compara dentro de un salto condicional; x86 suele usar indicadores de condición.
  • El número de instrucciones por sí solo no define un diseño RISC.

6.2 Evalúa la velocidad, el tamaño del código y la compatibilidad

Un conjunto de instrucciones determina cómo expresa un programa su trabajo, pero la velocidad de ejecución también depende del procesador que lo implementa. Compara el comportamiento medido o modelado del programa, no el número de operaciones que promete una instrucción.

Combina los tres factores de rendimiento

El tiempo de CPU es igual al número de instrucciones ejecutadas multiplicado por los ciclos medios por instrucción (CPI) y por la duración del ciclo de reloj. De forma equivalente, divide el número de instrucciones ejecutadas multiplicado por el CPI entre la frecuencia de reloj. Un menor número de instrucciones puede verse contrarrestado por un CPI mayor o un reloj más lento.

A 2 GHz, cada ciclo dura 0,5 ns. Una versión con 1,0 miles de millones de instrucciones y un CPI de 2,0 usa 2,0 miles de millones de ciclos y tarda 1,0 s. Otra con 1,3 miles de millones de instrucciones y un CPI de 1,2 usa 1,56 miles de millones de ciclos y tarda 0,78 s. La segunda ejecuta un 30 % más de instrucciones, pero es aproximadamente 1,28 veces más rápida. El CPI es una media para ese programa en esa implementación, no una propiedad universal de una ISA.

Las instrucciones complejas siguen necesitando trabajo interno

Muchos procesadores x86 modernos traducen la mayoría de las instrucciones en microoperaciones internas más sencillas. Estas pueden entonces usar técnicas de ejecución similares a las de los núcleos RISC modernos. La ISA externa sigue siendo relevante para la codificación, la decodificación y la compatibilidad, incluso cuando las operaciones internas parecen similares.

Una instrucción compleja no garantiza un número fijo de ciclos. Una instrucción de copia de bloques y un bucle de copia sencillo pueden intercambiar su posición en rendimiento según los procesadores y las cargas de trabajo. La versión del procesador, el tamaño de los datos y el comportamiento de la memoria importan; un listado fuente de una sola instrucción no puede determinar el resultado.

La densidad del código es una medida independiente

Las secuencias de código máquina más cortas pueden reducir el almacenamiento y la presión sobre la caché de instrucciones. Las longitudes variables de CISC suelen mejorar la densidad. La extensión C de RISC-V proporciona codificaciones de 16 bits para operaciones frecuentes junto a instrucciones de 32 bits. La especificación indica un uso típico para el 50–60 % de las instrucciones, lo que da un código aproximadamente un 25–30 % más pequeño.

Para 100 instrucciones con la mitad comprimidas, 50 usan 2 bytes y 50 usan 4: 300 bytes en lugar de 400. El número de instrucciones ejecutadas sigue siendo 100. Una reducción del tamaño no determina por sí sola un factor de aceleración, porque el tiempo de ejecución depende de algo más que los bytes obtenidos.

La compatibilidad y las herramientas influyen en la elección

Los archivos binarios y las bibliotecas x86 existentes dan un valor considerable a la compatibilidad. RISC-V fija los conjuntos base y añade extensiones. El código RV32I puede ejecutarse en una implementación compatible con RV32I cuando coinciden la ABI y el entorno operativo; usar M o V añade esos requisitos. RV32E es una base diferente con un conjunto reducido de registros y no promete compatibilidad con RV32I.

ARM se usa ampliamente en dispositivos móviles, así como en portátiles y servidores; x86 se usa ampliamente en computadoras personales y servidores; RISC-V se usa en sistemas integrados, aceleradores, investigación y enseñanza. La disponibilidad de software, las licencias y la calidad de chips concretos importan al elegir una plataforma.

El ensamblador escrito a mano puede ayudar en una pequeña rutina crítica o permitir usar una instrucción especial, pero vincula el código a una ISA y aumenta el trabajo de mantenimiento. Los compiladores con optimización gestionan bien la asignación de registros y la reorganización del código. Usa mediciones para justificar la sustitución de código fuente claro por ensamblador.

Puntos clave

  • Compara conjuntamente el número de instrucciones ejecutadas, el CPI y la duración del ciclo.
  • Las microoperaciones no cambian la ISA visible para el software.
  • Las codificaciones comprimidas reducen los bytes sin reducir necesariamente las instrucciones ejecutadas.
  • La compatibilidad binaria también requiere que coincidan la ABI y el entorno.
  • Mide un programa y una implementación concretos antes de afirmar que una ISA es más rápida.

Ejemplos de Conjunto de instrucciones

6 Lecciones Interactivas

1

Sigue los operandos desde una expresión hasta la memoria

15 pasos
Lee una instrucción, sigue los valores temporales, actualiza un arreglo y construye una constante mayor.
  1. 1
    IntroducciónSigue un valor cada vez

    Una instrucción de ensamblador nombra tanto su operación como sus operandos. Comienza con un cálculo legible en registros, después trae un valor de la memoria y almacena un resultado actualizado. Por último, construye una constante demasiado grande para un solo inmediato. En cada paso, sigue exactamente qué cambia: un registro de destino, un grupo de bytes de memoria o la dirección de la siguiente instrucción.

  2. 2
    ExplicaciónLee el contrato de la instrucción

    La ISA define qué debe hacer add a0, a1, a2: leer los dos operandos de origen y escribir su suma en el destino. La microarquitectura determina cómo lleva a cabo el chip esa petición. El ensamblador da nombres legibles a la petición; el ensamblador produce sus bits codificados. Los distintos chips que implementan el mismo contrato de instrucciones deben coincidir en el resultado visible.

  3. 3
    DesafíoCuenta las instrucciones después del ensamblado

    Un ensamblador expande una instrucción li en dos instrucciones máquina, seguidas de una instrucción add. Con solo codificaciones de 32 bits, ¿cuántos bytes de instrucciones producen estas dos líneas de ensamblador?

    ---reveal---

    Hay tres instrucciones reales, cada una de 4 bytes, para un total de 12 bytes. El número de líneas de texto es dos, pero la expansión determina el tamaño del código máquina. Las etiquetas y los comentarios no añaden instrucciones ejecutadas.

  4. 4
    EjemploConserva la suma intermedia

    Calcula r = (u + v) - w con u = 18, v = 7, w = 6 en a1, a2, a3:

    add t0, a1, a2
    sub a0, t0, a3
    

    Después de la primera instrucción, t0 contiene 25. Después de la segunda, a0 contiene 19. Las tres entradas siguen disponibles porque ninguna de las instrucciones escribe en sus registros. t0 tiene una función: preservar el primer resultado hasta que la resta lo lea.

  5. 5
    DesafíoCambia el destino y conserva los operandos de origen

    Con las mismas entradas, ahora se ejecuta sub a3, t0, a3 después de la suma. ¿Qué cambia respecto a escribir el resultado en a0?

    ---reveal---

    El resultado numérico sigue siendo 19, pero sustituye el valor anterior 6 de a3. La instrucción lee los valores de origen antes de escribir en su destino. a0 no cambia, por lo que un llamador que espere allí el resultado no recibirá este nuevo resultado.

  6. 6
    ExplicaciónConvierte un índice en una dirección

    Un arreglo de enteros comienza en la dirección contenida en s1. Cada int de RV32 ILP32 ocupa 4 bytes, por lo que el elemento 2 comienza 8 bytes después de la base y el elemento 5 comienza 20 bytes después. La carga lw t0, 8(s1) lee el valor del elemento 2; no coloca la propia dirección en t0. El registro base permanece sin cambios.

  7. 7
    EjemploCarga, calcula y almacena

    Implementa A[5] = A[2] + r con r = 19 en a0:

    lw t0, 8(s1)
    add t0, t0, a0
    sw t0, 20(s1)
    

    Si A[2] es 11, la carga da 11, la suma da 30 y el almacenamiento escribe 30 en A[5]. Leer y escribir memoria requiere instrucciones separadas alrededor de la operación aritmética. El valor de A[2] sigue siendo 11.

  8. 8
    DesafíoDetecta la unidad incorrecta de un desplazamiento

    Alguien cambia la primera línea a lw t0, 2(s1) porque la expresión nombra A[2]. ¿Qué ha fallado?

    ---reveal---

    El desplazamiento es de 2 bytes, no de dos elementos. El elemento 2 necesita 8 bytes desde la base. La dirección modificada tampoco tiene alineación natural para una palabra cuando la base está alineada, por lo que el acceso puede fallar o requerir un tratamiento especial antes incluso de poder proporcionar datos útiles.

  9. 9
    EjemploLee un byte de una palabra almacenada

    Almacena 0x7193D2A6 en una dirección alineada de una máquina con el byte menos significativo primero. Sus bytes en orden de direcciones crecientes son 0xA6, 0xD2, 0x93, 0x71. Una carga de byte en el desplazamiento 1 selecciona 0xD2. lbu da 210, mientras que lb da −46 al extender el bit 7 a las posiciones superiores del registro. El byte en memoria es idéntico; la instrucción de carga proporciona la interpretación.

  10. 10
    ExplicaciónEncuentra la parte inferior con signo

    Ahora construye 0x00003D20 en un registro. Sus 12 bits inferiores son 0xD20, que representan −736 como inmediato con signo de 12 bits. Por tanto, el paso superior debe comenzar un bloque de 4096 bytes más arriba de lo que sugiere el 0x3 visible. Elegir 0x4 da 0x4000, desde donde restar 0x2E0 alcanza el valor deseado.

  11. 11
    EjemploConstruye y comprueba la constante

    Usa:

    lui t1, 0x4
    addi t1, t1, -736
    

    La primera instrucción escribe 16 384; la segunda resta 736 y deja 15 648, o 0x3D20. La parte inferior es un sumando con signo, no solo dígitos que se pegan en su lugar. li t1, 0x3D20 permite al ensamblador elegir la construcción, pero su expansión sigue ocupando instrucciones reales.

  12. 12
    DesafíoCorrige la división de una constante

    Una división usa lui t1, 0x3 y addi t1, t1, -736. ¿Cuánto se aleja su resultado de 0x3D20 y qué campo necesita corregirse?

    ---reveal---

    Produce 0x2D20, exactamente 4096 menos. Aumenta el campo superior de 0x3 a 0x4 y conserva el sumando inferior con signo. Cambiar el sumando al 0xD20 positivo superaría el rango positivo con signo de addi.

  13. 13
    ConsejoSigue un registro hasta su último uso

    Al seguir una secuencia, escribe un valor junto al registro que lo contiene en ese momento. Táchalo solo cuando se sobrescriba ese registro. Una llamada posterior a un procedimiento supone un riesgo de sobrescritura para los registros temporales y de argumentos, aunque no aparezca ninguna asignación explícita junto a la llamada. Guarda un valor que aún necesites antes de cruzar ese límite.

  14. 14
    DesafíoCombina los pasos de dirección y valor

    Con la base del arreglo en s1, el elemento A[6] es 17. t1 contiene la constante 0x3D20 construida correctamente. ¿Qué cambia lw t0, 24(s1); add t0, t0, t1; sw t0, 4(s1)?

    ---reveal---

    Lee A[6], suma 17 a 15 648 y almacena 15 665 en A[1]. Los dos desplazamientos cuentan bytes, mientras que la constante es un valor aritmético. El elemento original en el índice 6 y la dirección base en s1 permanecen sin cambios.

  15. 15
    RepasoSigue tú mismo la siguiente secuencia

    Para una nueva secuencia de ensamblador, marca los registros que se leen y se escriben, expande las pseudoinstrucciones cuyo tamaño importe y traduce cada desplazamiento de memoria a bytes. Comprueba la extensión de cargas pequeñas por separado del orden de bytes. Al construir una constante, verifica el valor superior intermedio más su sumando inferior con signo antes de usar el resultado como dato o dirección.

2

Sigue la aritmética hasta las decisiones y los bucles

14 pasos
Extrae un campo, elige comparaciones con signo y sigue cada ruta de un bucle con contador.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónHaz visible la ruta de ejecución
  2. 2
    EjemploDesplaza el campo a su posición
  3. 3
    EjemploConserva solo la anchura del campo
  4. 4
    DesafíoPredice un desplazamiento con signo
  5. 5
    ExplicaciónCompara usando la interpretación correcta
  6. 6
    EjemploDistingue un salto de una llamada
  7. 7
    DesafíoComprueba un patrón de dos maneras
  8. 8
    EjemploDibuja las dos rutas de un if
  9. 9
    ExplicaciónColoca la comprobación del límite antes de la carga
  10. 10
    EjemploSigue tres ejecuciones del cuerpo
  11. 11
    DesafíoCuenta toda la ruta ejecutada
  12. 12
    ConexiónSustituye el escalado repetido por una dirección que avanza
  13. 13
    DesafíoProtege una carga con control en cortocircuito
  14. 14
    RepasoComprueba valores y rutas conjuntamente
3

Codifica una instrucción y reconstruye un salto condicional

12 pasos
Coloca operandos en campos, comprueba una palabra codificada y reconstruye un desplazamiento de salto condicional disperso.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónTraduce en ambos sentidos
  2. 2
    ExplicaciónIdentifica el formato y los operandos
  3. 3
    EjemploEscribe los campos con su anchura completa
  4. 4
    EjemploConvierte y decodifica la palabra
  5. 5
    DesafíoLocaliza el origen del valor de un almacenamiento
  6. 6
    ExplicaciónMide la distancia hacia atrás
  7. 7
    EjemploDivide el inmediato del salto condicional
  8. 8
    EjemploConstruye el salto condicional completo
  9. 9
    DesafíoRecupera el bit cero que falta
  10. 10
    ConexiónConserva la distancia relativa cuando se desplaza el código
  11. 11
    DesafíoUsa el código de operación para descartar una decodificación incorrecta
  12. 12
    RepasoComprueba una palabra máquina en sentido inverso
4

Conserva valores durante llamadas anidadas

15 pasos
Construye un procedimiento envolvente correcto, sigue marcos recursivos y sigue una cadena de bytes por la memoria.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónProtege lo que el llamador aún necesita
  2. 2
    EjemploEspecifica el resultado del procedimiento envolvente
  3. 3
    ExplicaciónAplica los compromisos de preservación
  4. 4
    DesafíoElige el estado que debe conservarse durante la llamada
  5. 5
    EjemploReserva y llena el marco
  6. 6
    EjemploCombina los resultados antes de liberar el almacenamiento
  7. 7
    ExplicaciónRepite el patrón de forma recursiva
  8. 8
    DesafíoRecupera los argumentos guardados al retornar
  9. 9
    ExplicaciónDistingue una dirección de su objeto
  10. 10
    EjemploElige operaciones de byte para una cadena
  11. 11
    DesafíoTermina en las direcciones correctas
  12. 12
    ConsejoRestaura el valor guardado, no el nuevo resultado
  13. 13
    ConexiónEl tiempo de vida del objeto sigue al del marco
  14. 14
    DesafíoConserva un puntero de destino durante una llamada a un auxiliar
  15. 15
    RepasoRevisa una llamada completa
5

Sigue una llamada desde el código fuente hasta el inicio

11 pasos
Sigue un símbolo durante la compilación, el ensamblado, el enlace, la resolución dinámica y la carga del proceso.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónConsidera qué sabe cada herramienta
  2. 2
    ExplicaciónCompila para una ISA de destino
  3. 3
    EjemploDeja sin completar la llamada externa
  4. 4
    DesafíoDistingue la generación de datos de la ejecución
  5. 5
    EjemploColoca el módulo de destino
  6. 6
    EjemploAjusta la referencia relativa
  7. 7
    ExplicaciónElige cuándo proporcionar el código de biblioteca
  8. 8
    DesafíoLocaliza la etapa del fallo
  9. 9
    ConexiónCrea un proceso antes de llamar a main
  10. 10
    DesafíoExplica por qué un objeto no es el programa terminado
  11. 11
    RepasoSigue un nuevo error hasta su etapa
6

Compara diseños con la medida adecuada

10 pasos
Distingue las instrucciones visibles, los bytes codificados y el tiempo de ejecución al evaluar una elección de ISA.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónMantén separadas tres mediciones
  2. 2
    EjemploCompara la operación visible de memoria
  3. 3
    ExplicaciónRelaciona la codificación con la decisión de diseño
  4. 4
    DesafíoClasifica una característica con cuidado
  5. 5
    EjemploCalcula el primer tiempo de ejecución
  6. 6
    EjemploCompara un número mayor de instrucciones
  7. 7
    DesafíoSepara el ahorro de bytes del factor de aceleración
  8. 8
    ConexiónLa compatibilidad va más allá de la ISA
  9. 9
    DesafíoEvalúa una afirmación sobre optimización en ensamblador
  10. 10
    RepasoIndica las pruebas que respaldan la comparación

Ejercicios de Conjunto de instrucciones

60 Preguntas de Práctica en 6 sets

Set 1: Registros, memoria y constantes10 Preguntas de Práctica

Pregunta 1 de 10

Un byte que contiene 0xB2 se carga una vez con lb y otra con lbu. ¿Qué resultados decimales producen las dos cargas, en ese orden?
  1. −78 y 178
  2. 178 y 178
  3. −78 y −78
  4. −50 y 178
Mostrar respuestaOcultar respuesta
Respuesta: A
Como valor con signo de 8 bits, 0xB2 es 178 menos 256, o −78. lb extiende el signo de ese valor, mientras que lbu produce 178 sin signo. Hacer positivos ambos resultados ignora la regla de extensión de signo de la carga con signo.

Pregunta 2 de 10

Dos procesadores implementan RV32I usando cauces de segmentación internos diferentes. ¿Qué especificación compartida determina el resultado de una instrucción add de RV32I?
  1. La distribución de procesos del sistema operativo
  2. La arquitectura del conjunto de instrucciones
  3. La asignación de registros del compilador
  4. La frecuencia de reloj del procesador
Mostrar respuestaOcultar respuesta
Respuesta: B
La ISA define el efecto visible de add, por lo que ambas implementaciones deben producir ese resultado. Sus cauces de segmentación internos pueden ser diferentes. La frecuencia de reloj afecta al tiempo, pero no redefine lo que significa la suma para un programa.

Pregunta 3 de 10

Antes de sub a2, a0, a1, a0 contiene 28 y a1 contiene 9. ¿Qué valor se escribe en a2?
  1. 37
  2. −19
  3. 9
  4. 19
Mostrar respuestaOcultar respuesta
Respuesta: D
El primer operando es el destino y la resta calcula el segundo operando menos el tercero. Por tanto, la instrucción escribe 28 menos 9, o 19, en a2. Invertir los operandos de origen daría −19, pero ese no es su orden aquí.

Pregunta 4 de 10

Un arreglo de RV32 ILP32 contiene enteros de 4 bytes. ¿Cuántos bytes después de la base comienza el elemento A[7]?
  1. 7 bytes
  2. 11 bytes
  3. 28 bytes
  4. 24 bytes
Mostrar respuestaOcultar respuesta
Respuesta: C
Cada elemento anterior ocupa 4 bytes, por lo que el índice 7 comienza a 28 bytes de la base. Usar 7 como desplazamiento en bytes confundiría un índice de elemento con un desplazamiento de dirección. El índice cero ya está en la base y no añade otro elemento al recuento.

Pregunta 5 de 10

En memoria con el byte menos significativo primero, sw almacena 0x86C4217D en 0x3000. ¿Qué valor produce lbu t0, 2(s0) cuando s0 = 0x3000?
  1. 0x000000C4
  2. 0x00000021
  3. 0xFFFFFFC4
  4. 0x00000086
Mostrar respuestaOcultar respuesta
Respuesta: A
Los bytes almacenados son 0x7D, 0x21, 0xC4 y 0x86 en direcciones crecientes. El desplazamiento 2 selecciona 0xC4, y lbu lo extiende con ceros hasta 0x000000C4. El resultado con extensión de signo requeriría lb, no la carga de byte sin signo utilizada aquí.

Pregunta 6 de 10

¿Qué campo superior de lui e inmediato con signo de addi construyen 0x00002ABC en RV32?
  1. 0x2 y +2748
  2. 0x2 y −1348
  3. 0x3 y −1348
  4. 0x3 y +2748
Mostrar respuestaOcultar respuesta
Respuesta: C
El patrón inferior de 12 bits 0xABC representa −1348. Partir de 0x3000 y restar 1348 alcanza 0x2ABC, por lo que el campo superior es 0x3. Tratar 2748 como operando positivo de addi falla porque supera el límite positivo con signo de 12 bits.

Pregunta 7 de 10

Un procedimiento llamador conserva un valor en t3, llama a un auxiliar que cumple la convención y después necesita el valor original. ¿Qué debe hacer el llamador?
  1. Restaurar t3 solo dentro del auxiliar
  2. Tratar t3 como un registro preservado por el llamado
  3. Guardar t3 después de que retorne el auxiliar
  4. Proteger el valor antes de la llamada
Mostrar respuestaOcultar respuesta
Respuesta: D
t3 es un registro preservado por el llamador, por lo que un auxiliar que cumple la convención puede sobrescribirlo. El llamador debe guardar el valor activo o disponer de una ubicación preservada equivalente antes de llamar. El auxiliar no está obligado a restaurar los registros temporales solo porque su llamador vaya a necesitarlos.

Pregunta 8 de 10

En RV32, se ejecuta auipc t0, 0x2 en 0x4000, seguido de addi t0, t0, -24. ¿Qué valor final queda en t0?
  1. 0x00005FEC
  2. 0x00005FE8
  3. 0x00006018
  4. 0x00002000
Mostrar respuestaOcultar respuesta
Respuesta: B
auipc suma 0x2000 a su propia dirección y produce 0x6000. Restar 24 deja 0x5FE8. Usar la dirección de la siguiente instrucción como base del pc incrementaría incorrectamente el resultado en 4 bytes.

Pregunta 9 de 10

Un arreglo de estructuras usa struct rec { int key; int value; }; en RV32 ILP32, sin relleno adicional. Su base es 0x5000. ¿Qué dirección se lee para A[3].value y qué byte con signo devuelve lb desde esa dirección si el campo contiene 0x000000E9 en memoria con el byte menos significativo primero?
  1. 0x5018 y −23
  2. 0x501C y 233
  3. 0x5010 y −23
  4. 0x501C y −23
Mostrar respuestaOcultar respuesta
Respuesta: D
Cada estructura ocupa 8 bytes, por lo que el elemento 3 comienza a 24 bytes de la base; su segundo campo añade 4, lo que da 0x501C. El primer byte allí es 0xE9, que lb extiende con signo hasta −23. Devolver 233 aplicaría la regla de extensión sin signo.

Pregunta 10 de 10

En RV32, la siguiente secuencia se ejecuta con t1 = 0x7000; la palabra en 0x7030 es 6. ¿Qué valor se almacena en 0x7034?

lui t0, 0x4
addi t0, t0, -1984
lw t2, 48(t1)
add t2, t2, t0
sw t2, 52(t1)
  1. 0x00003840
  2. 0x00003846
  3. 0x00004846
  4. 0x00004006
Mostrar respuestaOcultar respuesta
Respuesta: B
La constante se convierte en 0x4000 menos 0x7C0, o 0x3840. La carga proporciona 6, por lo que la suma produce 0x3846, que escribe el almacenamiento final. 0x3840 es solo la constante construida y omite el operando cargado.
Set 2: Aritmética y rutas por un programa10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

¿Qué resultado hexadecimal de 32 bits produce RV32I cuando add suma 1 a 0xFFFFFFFF?
  1. 0xFFFFFFFF
  2. 0x80000000
  3. 0x00000000
  4. 0x7FFFFFFF
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué instrucción real implementa bgt a2, a3, Done?
  1. blt a2, a3, Done
  2. blt a3, a2, Done
  3. bge a3, a2, Done
  4. bge a2, a3, Done
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Después de srli t0, a0, 8 y andi t0, t0, 0x1F, ¿qué valor hay en t0 si a0 = 0x0000D6B9?
  1. 0x00000016
  2. 0x000000D6
  3. 0x00000019
  4. 0x00000006
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Para a0 = -11 con signo, ¿qué resultados producen srai t0, a0, 2 y la división entera de C -11 / 4, respectivamente?
  1. −3 y −2
  2. −2 y −3
  3. −3 y −3
  4. −2 y −2
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

a0 = 0xFFFFFFFE y a1 = 0x00000003. ¿Cuál de las instrucciones blt a0, a1, L y bltu a0, a1, L realiza el salto?
  1. Solo bltu
  2. Ambos saltos
  3. Ninguno de los saltos
  4. Solo blt
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una traducción de if (x != y) r = 8; else r = 3; salta a Else cuando se cumple la igualdad. ¿Qué se coloca después de la asignación de 8 para que r siga siendo 8 en la ruta de desigualdad?
  1. Un salto de vuelta a la comparación
  2. Un salto a después de la parte else
  3. Una segunda asignación de 3
  4. Una nueva carga de x antes de Else
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

El bucle while (A[i] == 6) i++; comienza con i = 1 y A = {6, 6, 6, 4, 6}. ¿Cuánto vale i cuando sale del bucle?
  1. 2
  2. 4
  3. 3
  4. 5
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un procedimiento hoja ejecuta el siguiente código sobre un arreglo de enteros de 4 bytes {2, -5, 9}, con su base en a0 y el número de elementos 3 en a1. Cada instrucción mostrada es una instrucción máquina. ¿Qué resultado y número dinámico de instrucciones ejecutadas produce?

addi t0, zero, 0
addi t1, zero, 0
L: bge t0, a1, D
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
jal zero, L
D: addi a0, t1, 0
jalr zero, 0(ra)
  1. 6 y 25
  2. 9 y 26
  3. 6 y 24
  4. 6 y 26
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

En 0x8000, se ejecuta jalr ra, 7(t0) con t0 = 0x903A. ¿Qué destino se usa, qué se guarda en ra y qué instrucción puede retornar mediante ese enlace?
  1. 0x9041; 0x8004; jalr zero, 0(ra)
  2. 0x9040; 0x8004; jalr zero, 0(ra)
  3. 0x9040; 0x8000; jalr zero, 0(ra)
  4. 0x9040; 0x8004; jal zero, 0(ra)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

La condición de C es p != 0 && *p == 9, con p en a0. Para la ruta then, ¿qué orden de flujo de control preserva la condición y evita desreferenciar un puntero nulo?
  1. Rechazar p nulo; cargar *p; rechazar si no es 9
  2. Cargar *p; rechazar p nulo; comparar con 9
  3. Rechazar p no nulo; cargar *p; rechazar si no es 9
  4. Rechazar p nulo; comparar p con 9; cargar *p
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Campos de instrucciones y destinos relativos10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

En sw a2, 20(sp), ¿qué registro proporciona el campo rs1?
  1. a2 (x12)
  2. zero (x0)
  3. ra (x1)
  4. sp (x2)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Cuál es el patrón del inmediato de 12 bits para addi t2, s1, -36?
  1. 1111 1101 1100
  2. 0000 0010 0100
  3. 1111 1101 1011
  4. 1111 1110 1100
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Qué único cambio convierte un ADD de tipo R en SUB con los mismos registros?
  1. Establecer opcode en 0100011
  2. Establecer funct3 en 100
  3. Establecer funct7 en 0100000
  4. Establecer rd como el primer origen
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un salto condicional en 0x308C tiene como destino 0x3068. ¿Qué desplazamiento en bytes con signo se codifica?
  1. −40
  2. −36
  3. −9
  4. +36
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una instrucción jal en 0x1200 tiene un desplazamiento con signo +84 y destino ra. ¿Qué dirección de destino y dirección de retorno resultan?
  1. 0x1258 y 0x1204
  2. 0x1254 y 0x1204
  3. 0x1254 y 0x1258
  4. 0x1350 y 0x1204
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Usando campos de tipo I, ¿qué palabra codifica addi t2, s1, -36? Aquí t2 = x7, s1 = x9, el código de operación de ADDI es 0x13 y funct3 = 000.
  1. 0xFDC48393
  2. 0x02448393
  3. 0xFDC38493
  4. 0xFDC483B3
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Decodifica 0xFEC12623. El código de operación 0x23 selecciona un almacenamiento y funct3 = 010 selecciona una palabra. ¿Qué instrucción de ensamblador representa?
  1. sw a2, -12(sp)
  2. lw a2, -20(sp)
  3. sw sp, -20(a2)
  4. sw a2, -20(sp)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una instrucción de tipo B bne a2, zero, Loop está en 0x4018, con Loop en 0x400C. Usando el código de operación 0x63 y funct3 = 001, ¿qué palabra codificada alcanza Loop?
  1. 0xFE0618E3
  2. 0x00061663
  3. 0xFE061AE3
  4. 0xFE061CE3
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

En la dirección 0x6000, la palabra 0x000022B7 está seguida de 0xFE028293 en 0x6004. ¿Qué valor queda en t0? Estas palabras se decodifican como LUI de tipo U y ADDI de tipo I, respectivamente.
  1. 0x00002020
  2. 0x00001FDC
  3. 0x00007FE0
  4. 0x00001FE0
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un salto condicional y su destino comienzan en la misma sección con desplazamientos 0x30 y 0x54. La sección se mueve de 0x9000 a 0xB000 sin cambiar su distribución interna. ¿Qué desplazamiento de salto y dirección de destino resultan?
  1. +0x24 y 0x9054
  2. +0x2024 y 0xB054
  3. +0x24 y 0xB054
  4. +0x20 y 0xB054
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Llamadas, recursión y acceso mediante punteros10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un procedimiento RISC-V que cumple la convención modifica s4. ¿Qué valor debe contener s4 cuando retorne?
  1. El valor de s4 recibido del llamador
  2. El primer resultado del procedimiento
  3. La dirección posterior a la llamada
  4. La dirección actual de la cima de la pila
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

En RV32 ILP32, ¿qué instrucción avanza a0 al siguiente elemento cuando contiene un int *?
  1. addi a0, a0, 1
  2. addi a0, a0, 2
  3. addi a0, a0, 8
  4. addi a0, a0, 4
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un procedimiento entra con sp = 0x8000, reserva 32 bytes y después ejecuta sw ra, 28(sp). ¿En qué dirección comienza la palabra guardada?
  1. 0x7FE0
  2. 0x801C
  3. 0x7FFC
  4. 0x7FF0
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un procedimiento hoja recibe un argumento en a0, calcula en s1 y después restaura el s1 recibido antes de retornar. ¿Qué debe hacer antes de esa restauración para devolver el entero calculado?
  1. Guardar el a0 del llamador en cada salida
  2. Mover el valor calculado de s1 a a0
  3. Restaurar s1 y después copiar s1 en a0
  4. Retornar con el valor calculado en s1
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un bucle de cadenas que procesa un byte cada vez almacena un byte, incrementa ambos punteros y salta de vuelta si ese byte no es cero. ¿Cuántas iteraciones copian «logic», incluido su terminador de C?
  1. 5
  2. 6
  3. 7
  4. 10
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un procedimiento que no es hoja y sigue la ABI estándar solo debe guardar ra y s2, cada uno de una palabra. ¿Cuál es el tamaño positivo mínimo de marco que preserva la alineación requerida de la pila?
  1. 4 bytes
  2. 8 bytes
  3. 16 bytes
  4. 12 bytes
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una función recibe n en a0 y debe devolver helper(n) + n. helper sigue la ABI y puede sobrescribir todos los registros preservados por el llamador. El ra recibido por la función también debe conservarse. ¿Qué plan funciona con un marco de 16 bytes?
  1. Guardar ra y n; llamar; cargar n en t0; sumar; restaurar ra y sp
  2. Guardar solo ra; copiar n en t0; llamar; sumar t0; restaurar ra y sp
  3. Guardar solo n; llamar; cargar n; sumar; restaurar sp; retornar mediante ra
  4. Guardar ra y n; llamar; restaurar n en a0; sumar a0 consigo mismo; restaurar ra
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una función recursiva reserva un marco de 16 bytes en cada entrada, guarda n y ra, devuelve 1 para n < 2 y, en caso contrario, devuelve n * f(n - 1). Con n = 4 y sp = 0x9000 iniciales, ¿qué resultado y valor mínimo de sp se obtienen?
  1. 24 y 0x8FD0
  2. 6 y 0x8FC0
  3. 24 y 0x8FB0
  4. 24 y 0x8FC0
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

En RV32 ILP32, la memoria en 0xA000 contiene el valor de puntero 0xB000. En 0xB000 comienza un arreglo de enteros de 4 bytes cuyo elemento 4 es igual a 27. Con a0 = 0xA000, ¿qué secuencia deja ese elemento en a0?
  1. lw a0, 16(a0); lw a0, 0(a0)
  2. lw t0, 0(a0); lw a0, 16(t0)
  3. addi t0, a0, 16; lw a0, 0(t0)
  4. lw t0, 0(a0); lw a0, 4(t0)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un procedimiento reserva 32 bytes, almacena ra en 28(sp) y s0 en 24(sp) y después llama a un auxiliar. En su salida, primero suma 16 a sp, después carga desde 24(sp) y 28(sp), luego suma otros 16 y retorna. ¿Por qué puede fallar aunque sp termine con su valor original?
  1. Las cargas usan direcciones 16 bytes más altas
  2. El auxiliar debe preservar cada registro temporal
  3. Un marco no puede contener bytes sin usar
  4. El puntero de pila final está desalineado
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 5: Construye, enlaza e inicia un programa10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

¿Qué archivo produce gcc -c module.c sin enlazar?
  1. Una imagen de proceso ejecutable
  2. Un listado fuente de ensamblador
  3. Un archivo objeto reubicable
  4. Una caché de símbolos de biblioteca compartida
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué componente mapea el código y los datos ejecutables en un nuevo proceso y prepara su pila inicial?
  1. El compilador
  2. El cargador
  3. El ensamblador
  4. El enlazador estático
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un archivo objeto define main, pero llama a mix, que está definido en un segundo objeto. ¿Qué permite al enlazador conectar esa llamada con la definición?
  1. La tabla de líneas de depuración y las posiciones del archivo fuente
  2. Los códigos de operación y una convención de registros compartida
  3. La distribución inicial de la pila y el punto de entrada del ejecutable
  4. La información de símbolos y los registros de reubicación de la llamada
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Se hace referencia a una etiqueta local antes de su definición, situada más adelante en la misma sección de ensamblador. ¿Cómo puede un ensamblador completar ese salto condicional?
  1. Registrar la referencia y resolver su posición más tarde
  2. Exigir que el cargador analice la etiqueta del código fuente
  3. Tratar el nombre de la etiqueta como un entero inmediato
  4. Generar el salto con un desplazamiento permanentemente nulo
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un programa se enlazó estáticamente con la implementación de filter de una biblioteca. Después se corrige el archivo de biblioteca, pero el ejecutable no se modifica. ¿Qué código contiene el ejecutable para filter?
  1. El código original copiado durante el enlace
  2. El código corregido del archivo en la siguiente llamada
  3. Una versión del código fuente interpretada de nuevo
  4. Una rutina de la PLT que carga código compartido de reemplazo
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un programa RISC-V destinado a Linux coloca 93 en a7, 7 en a0 y después ejecuta ecall. ¿Qué servicio solicita?
  1. Escribir 7 bytes en un flujo estándar
  2. Devolver 93 al llamador actual
  3. Terminar el proceso con estado 7
  4. Reservar un marco de pila de 7 bytes
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un enlazador coloca el texto en 0x20000 y los datos en 0x30000000. El módulo P aporta 0x180 bytes de texto y 0x30 bytes de datos; Q va inmediatamente después. Una llamada de P está en el desplazamiento de texto 0x70; su destino en Q está en el desplazamiento de texto 0x20 de Q. La variable de Q está en el desplazamiento de datos 0x14 de Q. ¿Cuáles son el desplazamiento en bytes de la llamada y la dirección de la variable?
  1. +0x1A0 y 0x30000044
  2. +0x130 y 0x30000194
  3. +0x12C y 0x30000044
  4. +0x130 y 0x30000044
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un listado RV32I en 0x1000 contiene las siguientes opciones de expansión: li t0, 5000 usa dos instrucciones, mv a0, t0 una y call Helper un par de dos instrucciones sin acortar. Helper retorna normalmente. ¿En qué dirección se reanuda la ejecución después de la llamada y cuántos bytes de código preceden a esa reanudación?
  1. 0x100C y 12 bytes
  2. 0x1014 y 20 bytes
  3. 0x1010 y 16 bytes
  4. 0x1018 y 24 bytes
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una función de biblioteca comienza en el desplazamiento 0x2C0 de una biblioteca compartida. Un llamador local está en el desplazamiento 0x90; un puntero a una función externa a la biblioteca está almacenado en una posición de la GOT. La base de la biblioteca cambia de 0x40000000 a 0x50000000. ¿Qué descripción gestiona correctamente ambas referencias?
  1. Sumar 0x10000000 al desplazamiento local y conservar cada entrada de la GOT
  2. Mantener el destino local en 0x400002C0; reubicar solo la instrucción de llamada
  3. Restar 0x10000000 a la distancia local y borrar toda la GOT
  4. Mantener la distancia local +0x230; vincular el destino externo de la GOT según sea necesario
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un ejecutable se enlazó correctamente con una biblioteca compartida. Más tarde, esa biblioteca no está disponible y la carga del ejecutable falla antes de main. ¿Qué explica el fallo y qué cambiaría para el código de esa biblioteca incluido estáticamente?
  1. La carga necesita el archivo compartido; el código incluido ya estaría en el ejecutable
  2. El enlace se repite al iniciar; el código incluido aún necesitaría el archivo original
  3. Falta el objeto de entrada; el código incluido aún necesitaría cada objeto de entrada
  4. El archivo no tiene código máquina; el código incluido evitaría el cargador de procesos
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 6: Evalúa diseños y diagnostica código ensamblador10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

¿Qué par de instrucciones RISC-V realiza la operación de suma con memoria expresada por add eax, [ebx+12] de x86, si eax corresponde a a0 y ebx a s1?
  1. addi a0, a0, 12; add a0, a0, s1
  2. lw a0, 12(s1); add a0, a0, a0
  3. sw a0, 12(s1); add a0, a0, s1
  4. lw t0, 12(s1); add a0, a0, t0
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un procesador tiene una ISA de carga y almacenamiento e indicadores de condición. ¿Qué se puede concluir sobre la afirmación de que los indicadores lo convierten en CISC?
  1. Los indicadores implican aritmética de dos operandos, así que la afirmación es válida.
  2. Los indicadores pueden aparecer en diseños RISC, así que la afirmación es falsa.
  3. Una ISA de carga y almacenamiento no puede usar indicadores, así que la descripción se contradice.
  4. Los indicadores implican aritmética combinada con memoria, así que la afirmación es válida.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

La versión P ejecuta 0,8 miles de millones de instrucciones con un CPI de 1,5 y 2 GHz. La versión Q ejecuta 1,0 miles de millones de instrucciones con un CPI de 1,0 y 2 GHz. ¿Qué tiempos de ejecución resultan para P y Q?
  1. 0,60 s y 0,50 s
  2. 0,40 s y 0,50 s
  3. 0,60 s y 1,00 s
  4. 1,20 s y 1,00 s
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un programa tiene 120 instrucciones máquina. Si 72 usan codificaciones de 16 bits y el resto usan codificaciones de 32 bits, ¿cuántos bytes de código ocupa?
  1. 240 bytes
  2. 384 bytes
  3. 336 bytes
  4. 408 bytes
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

El diseño P ejecuta 1,2 miles de millones de instrucciones con un CPI de 1,1 y 2,2 GHz. El diseño Q ejecuta 0,9 miles de millones de instrucciones con un CPI de 1,8 y 2,7 GHz. Una optimización propuesta reduce el número de instrucciones de Q un 10 %, pero aumenta su CPI un 20 %, manteniendo fija su frecuencia. ¿Cómo se compara Q optimizado con P?
  1. Q tarda 0,648 s y es más lento que P
  2. Q tarda 0,54 s y es más rápido que P
  3. Q tarda 0,60 s y empata con P
  4. Q tarda 0,72 s y es más lento que P
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una rutina de 200 instrucciones usa originalmente solo codificaciones de 32 bits. Después de que 110 codificaciones pasen a ser de 16 bits, la secuencia de instrucciones y el tiempo de ejecución medido no cambian. ¿Cuáles son el nuevo tamaño y la conclusión de rendimiento justificada?
  1. 580 bytes; ejecución un 27,5 % más rápida
  2. 620 bytes; menos bytes con la misma velocidad medida
  3. 400 bytes; la mitad de instrucciones ejecutadas
  4. 580 bytes; menos bytes con la misma velocidad medida
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un archivo binario usa RV32I más mul, espera la convención de llamada ILP32 e invoca llamadas al sistema de RISC-V Linux. Una placa anuncia RV32I, usa ILP32 y ejecuta un simulador con su propia interfaz de servicios. ¿Qué requisitos adicionales deben cumplirse para que ese archivo binario se ejecute como se pretende?
  1. Compatibilidad con mul y la interfaz de servicios actual del simulador
  2. Compatibilidad con mul y la interfaz de servicios de Linux esperada
  3. Solo RV32I y la interfaz de servicios de Linux esperada
  4. Compatibilidad con mul y solo la convención ILP32 coincidente
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un arreglo RV32 ILP32 contiene struct sample { int id; int reading; }; sin relleno adicional. Un puntero al elemento 2 está en a0. El campo reading del siguiente elemento vale 31. ¿Qué cálculo de dirección y carga recuperan ese campo sin cambiar a0?
  1. addi t0, a0, 4; lw t1, 4(t0)
  2. addi t0, a0, 8; lw t1, 0(t0)
  3. addi t0, a0, 8; lw t1, 4(t0)
  4. addi t0, a0, 12; lw t1, 4(t0)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Este procedimiento hoja debe devolver la suma de n enteros, con n no negativo. Se llama con n = 0 y una dirección que no debe leerse. ¿Qué corrección mínima del flujo de control evita el acceso inválido y preserva el comportamiento para cantidades positivas?

addi t1, zero, 0
L: lw t0, 0(a0)
add t1, t1, t0
addi a0, a0, 4
addi a1, a1, -1
bne a1, zero, L
D: addi a0, t1, 0
ret
  1. Usar una carga de byte sin signo en el bucle
  2. Insertar beq a1, zero, D antes de L
  3. Cambiar el bne final por un blt con signo
  4. Inicializar t1 con el primer elemento del arreglo
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una rutina escrita a mano reduce las instrucciones ejecutadas de 50 millones a 40 millones en el mismo procesador de 2 GHz. El CPI medio sube de 1,2 a 1,6. ¿Qué ocurre con el tiempo y qué muestra eso sobre la afirmación basada en el número de instrucciones?
  1. Baja de 30 ms a 24 ms; la reducción del número determina el tiempo
  2. Sube de 60 ms a 64 ms; el CPI contrarresta la reducción del número
  3. Sube de 30 ms a 32 ms; el CPI contrarresta la reducción del número
  4. Sube de 24 ms a 32 ms; el CPI contrarresta la reducción del número
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

82 Términos Clave, cada uno con su definición

A

Alineación natural
La alineación natural sitúa un acceso a memoria en una dirección divisible por el tamaño del acceso, como un múltiplo de 4 para una palabra.
Un compilador puede insertar relleno para que una media palabra o una palabra comience en una dirección adecuadamente alineada.
Archivo objeto reubicable
Un archivo objeto reubicable contiene código máquina, datos y metadatos cuya ubicación final y referencias externas pueden completarse más tarde.
Producir un objeto con -c detiene el proceso antes de combinar las bibliotecas y los módulos necesarios para un ejecutable.
Arquitectura de carga y almacenamiento
Una arquitectura de carga y almacenamiento usa instrucciones específicas para transferir datos de memoria, mientras que las operaciones aritméticas y lógicas actúan sobre valores de registros e inmediatos.
Para sumar un elemento de un arreglo, RISC-V lo carga en un registro antes de ejecutar una instrucción aritmética.
Arquitectura de conjunto complejo de instrucciones (CISC)
CISC es un enfoque de diseño de conjuntos de instrucciones que admite operaciones y formas de direccionamiento más elaboradas, normalmente mediante codificaciones de longitud variable.
Una instrucción x86 puede combinar la lectura de un operando en memoria con su suma a un registro.
Arquitectura de conjunto reducido de instrucciones (RISC)
RISC es un enfoque de diseño de conjuntos de instrucciones que favorece codificaciones regulares, operaciones relativamente simples e instrucciones separadas para transferir datos de memoria.
Añadir extensiones puede ampliar una ISA RISC sin eliminar su organización de carga y almacenamiento.
Arquitectura del conjunto de instrucciones
Una arquitectura del conjunto de instrucciones es el contrato que define las instrucciones, los registros y el comportamiento de la máquina visibles para el software.
Dos procesadores pueden implementar la misma ISA con circuitos internos diferentes y distintas velocidades de ejecución.
Asignación de registros
La asignación de registros es la asignación que hace el compilador de los valores del programa a los registros limitados disponibles en cada punto de la ejecución.
Un bucle optimizado puede mantener su índice y su suma acumulada en registros durante muchas iteraciones.

B

Bit implícito
Un bit implícito es un valor reconstruido a partir de una regla de codificación sin ocupar una posición de bit almacenada propia.
El bit inferior de un desplazamiento de salto de tipo B es un cero implícito porque la distancia en bytes es par.
Bloque básico
Un bloque básico es una secuencia lineal de instrucciones en la que se entra por el principio y de la que solo se sale al final durante el flujo normal de control.
Una etiqueta que es destino de un salto condicional comienza un nuevo bloque que un compilador puede analizar por separado.

C

Campo de función
Un campo de función precisa un código de operación para seleccionar una operación específica, como suma o resta dentro de la aritmética de registros.
Los mismos registros y código de operación pueden representar ADD o SUB según los bits de funct7.
Campo de registro
Un campo de registro es una parte de anchura fija de la codificación de una instrucción que identifica por su número un registro de origen o de destino.
Un campo de 5 bits representa a0 como el registro número 10, escrito 01010 en binario.
Carga de dirección
La carga de dirección es una operación del ensamblador que forma la dirección de una etiqueta como valor de registro sin leer el objeto almacenado allí.
Después de la t0, Item, se necesita una carga independiente para leer el valor contenido en Item.
Cargador
Un cargador prepara un ejecutable como proceso en ejecución mediante el mapeo de su contenido y el establecimiento del estado inicial de la memoria y los registros.
El inicio comienza solo después de que se hayan preparado el código, los datos y la pila inicial del programa.
Ciclos por instrucción (CPI)
Los ciclos por instrucción son el número medio de ciclos de reloj usados por cada instrucción ejecutada para un programa y una implementación concretos.
Un programa con más instrucciones puede terminar antes si su CPI es suficientemente menor.
Código de operación
Un código de operación es un campo de instrucción que identifica un grupo de operaciones y ayuda a determinar la distribución usada para decodificar sus demás bits.
Los 7 bits inferiores distinguen un almacenamiento de palabra RV32I de una instrucción aritmética de registros.
Código independiente de la posición
El código independiente de la posición usa referencias relativas e indirección para que pueda ejecutarse correctamente en distintas direcciones de carga.
Desplazar una biblioteca y sus destinos locales juntos preserva las distancias usadas por sus referencias locales.
Compatibilidad binaria
La compatibilidad binaria permite ejecutar software existente en código máquina sin volver a compilarlo cuando coinciden los requisitos de ISA, ABI y entorno.
Usar una extensión opcional añade un requisito de hardware además de la compatibilidad con la ISA base.
Compilación en tiempo de ejecución
La compilación en tiempo de ejecución genera código máquina durante la ejecución, a menudo centrando el esfuerzo de compilación en el código que se ejecuta repetidamente.
Una máquina virtual puede interpretar primero el código de bytes y después compilar un método que se ejecuta con frecuencia.
Compilador
Un compilador traduce un programa a otra representación, normalmente seleccionando y optimizando instrucciones máquina para una ISA de destino.
Un compilador dirigido a RISC-V puede sustituir cálculos repetidos de índices por un incremento de puntero.
Concepto de programa almacenado
El concepto de programa almacenado representa las instrucciones como valores binarios en memoria, lo que permite a un procesador buscarlas y ejecutarlas.
El contador de programa proporciona la dirección desde la que se buscará la siguiente instrucción codificada.
Contador de programa
El contador de programa es el registro que contiene la dirección de la instrucción que se busca o ejecuta en la secuencia de instrucciones.
Un salto condicional tomado sustituye el avance secuencial del pc por la dirección calculada para su destino.
Continuación secuencial
La continuación secuencial consiste en que la ejecución continúa en la siguiente instrucción o bloque de código porque ninguna transferencia de control lo omite.
Una traducción de if-else necesita un salto después de la parte then para evitar la continuación secuencial hacia la parte else.
Convención de llamada
Una convención de llamada es el acuerdo que especifica las ubicaciones de argumentos y resultados y la preservación de registros durante las llamadas a procedimientos.
Los procedimientos de enteros de RISC-V pasan sus primeros argumentos de a0 a a7.
Cuarteto de bits
Un cuarteto de bits es un grupo de 4 bits que corresponde a un dígito hexadecimal y facilita la conversión de palabras de instrucciones binarias a hexadecimal.
Una instrucción completa de 32 bits se divide en ocho cuartetos después de unir todos sus campos.

D

Densidad de código
La densidad de código describe la compacidad con que las instrucciones máquina representan un programa y suele compararse mediante los bytes que necesita su código.
Las instrucciones comprimidas pueden reducir los bytes de código y preservar el número de operaciones ejecutadas.
Derrame de registros a memoria
El derrame de registros a memoria consiste en almacenar un valor activo de un registro en memoria para reutilizar ese registro y después volver a cargar el valor cuando se necesite.
Los valores activos adicionales pueden añadir almacenamientos y cargas en la pila incluso cuando el C original no tiene ningún acceso explícito a memoria.
Desbordamiento
El desbordamiento ocurre cuando un resultado aritmético queda fuera del rango representable para la anchura y la interpretación con o sin signo elegidas.
La suma de RV32 conserva los bits inferiores del resultado incluso cuando la suma con signo no cabe en 32 bits.
Desensamblado
El desensamblado consiste en convertir los bits de instrucciones codificadas de nuevo en nombres de instrucciones y operandos mediante las reglas de decodificación de la ISA.
Un desensamblador identifica el código de operación antes de tratar otros bits como registros o fragmentos de inmediato.
Desplazamiento aritmético a la derecha
Un desplazamiento aritmético a la derecha mueve los bits hacia posiciones inferiores y rellena las posiciones superiores con el bit de signo original.
Desplazar −7 con signo 1 posición a la derecha da −4 porque el resultado se redondea hacia menos infinito.
Desplazamiento de un campo de estructura
El desplazamiento de un campo de estructura es la distancia en bytes desde el comienzo de una estructura hasta uno de sus miembros, determinada por su distribución según la ABI.
Una carga suma el desplazamiento del campo a la dirección del objeto actual para alcanzar el miembro de ese objeto.
Desplazamiento del salto
Un desplazamiento del salto es la distancia en bytes con signo desde la dirección de la propia instrucción de salto condicional hasta la dirección de destino prevista.
Un destino hacia atrás da un desplazamiento negativo incluso cuando ambas direcciones son positivas.
Desplazamiento lógico a la derecha
Un desplazamiento lógico a la derecha mueve los bits hacia posiciones inferiores y rellena con ceros las posiciones superiores que quedan libres, lo que permite escalar valores sin signo.
Un desplazamiento lógico a la derecha de un patrón negativo con signo puede producir un resultado positivo grande.
Desreferenciación
Una desreferenciación accede al objeto identificado por un valor de puntero y se implementa mediante una carga o un almacenamiento de memoria apropiados.
lw t0, 0(a0) lee un entero de 4 bytes mediante la dirección contenida actualmente en a0.
Dirección relativa al PC
Una dirección relativa al pc se forma a partir de la dirección de la propia instrucción más un desplazamiento codificado, manteniendo una ubicación relativa al código.
Una secuencia auipc puede alcanzar datos locales incluso cuando se desplaza todo el grupo de código y datos.
Direccionamiento con índice escalado
El direccionamiento con índice escalado combina una base con un índice multiplicado por un paso de acceso y suele añadir un desplazamiento para formar una dirección de memoria.
Un operando en memoria de x86 puede expresar una base más cuatro veces un índice más 8 en una instrucción.
Direccionamiento por bytes
El direccionamiento por bytes es una organización de memoria en la que cada dirección identifica un byte, de modo que los valores mayores ocupan direcciones consecutivas.
Dos elementos adyacentes de un arreglo de 32 bits comienzan en direcciones que difieren en 4, no en 1.
Direccionamiento por desplazamiento
El direccionamiento por desplazamiento forma una dirección efectiva de memoria sumando un desplazamiento constante con signo al valor de un registro base.
En lw t0, 20(s1), la palabra cargada comienza 20 bytes más allá de la dirección almacenada en s1.
Directiva del ensamblador
Una directiva del ensamblador controla la generación de datos, la selección de secciones, la alineación o los metadatos de símbolos sin ser ejecutada por el procesador.
La directiva .word 5 genera un valor de 4 bytes, mientras que .text selecciona dónde pertenece el código posterior.

E

Enlace dinámico
El enlace dinámico conecta un programa con código de biblioteca compartida independiente durante la carga o la ejecución, y requiere que haya bibliotecas compatibles disponibles.
Una biblioteca compartida ausente puede impedir que se inicie un ejecutable enlazado anteriormente.
Enlace estático
El enlace estático incluye el código objeto de biblioteca requerido en el ejecutable, de modo que ese código incluido forma parte del propio archivo del programa.
Sustituir un archivo de biblioteca estática no actualiza el código ya copiado en un ejecutable anterior.
Enlazador
Un enlazador combina módulos objeto y bibliotecas, distribuye el contenido ejecutable, resuelve símbolos y ajusta referencias que dependen de direcciones.
Un error de referencia no definida significa que el enlace final no pudo encontrar la definición de un símbolo requerido.
Ensamblador
Un ensamblador convierte texto de ensamblador en instrucciones y datos codificados, y registra símbolos y referencias para resolverlos más tarde.
Un ensamblador expande call y puede dejar entradas de reubicación para una función definida en otro módulo.
Epílogo
Un epílogo es la secuencia de salida que restaura el estado guardado, libera el marco de pila actual y devuelve el control al llamador.
Restaurar los registros antes de aumentar sp mantiene sus desplazamientos relativos al marco reservado.
Evaluación en cortocircuito
La evaluación en cortocircuito deja de comprobar una condición compuesta una vez que su valor de verdad global ya está determinado.
Un primer operando falso de && impide evaluar el segundo operando en C.
Extensión con ceros
La extensión con ceros amplía un patrón de bits rellenando las nuevas posiciones superiores con ceros, preservando el valor interpretado sin signo.
Cargar el byte 0xF0 con lbu produce el valor positivo 240 en un registro de 32 bits.
Extensión de signo
La extensión de signo amplía un patrón de bits con signo copiando su bit más significativo en las nuevas posiciones superiores, preservando su valor con signo.
Cargar el byte 0xF0 con lb produce 0xFFFFFFF0, la representación de 32 bits de −16.

F

Formato de instrucción
Un formato de instrucción es la disposición de campos dentro de una instrucción codificada que asigna posiciones de bits a operandos y controles.
El tipo S tiene un inmediato dividido donde el tipo R tendría un campo de función y un registro de destino.

I

Indicadores de condición
Los indicadores de condición son bits de estado que registran propiedades de un resultado o comparación que las instrucciones posteriores pueden comprobar.
x86 puede comparar registros y después saltar según los indicadores, mientras que los saltos condicionales de RISC-V comparan directamente.
Inmediato superior
Un inmediato superior proporciona un campo de 20 bits que lui coloca por encima de 12 ceros inferiores al construir un valor de registro RV32.
Una instrucción addi posterior puede proporcionar una parte inferior con signo para completar una constante de 32 bits.
Instrucción comprimida
Una instrucción comprimida es una codificación más corta de una operación, como una forma de 16 bits de RISC-V C usada junto a instrucciones de 32 bits.
Si la mitad de 100 instrucciones usan 2 bytes, el tamaño total del código es 300 bytes en lugar de 400.
Interfaz binaria de aplicación
Una interfaz binaria de aplicación es el acuerdo de software sobre convenciones de llamada, distribución de datos y otras reglas para la cooperación entre archivos binarios.
La coincidencia de ISA no puede corregir un desacuerdo sobre qué registro contiene el valor de retorno de una función.

L

Lenguaje ensamblador
El lenguaje ensamblador es texto legible específico de una ISA que nombra instrucciones y operandos antes de que un ensamblador los codifique en bits.
El texto add a0, a1, a2 nombra una operación que tiene una codificación definida de código máquina de RISC-V.
Límite del bucle
Un límite del bucle restringe las iteraciones permitidas por la comparación de un bucle y determina a qué índices o direcciones puede acceder su cuerpo.
Una comprobación que sale cuando el índice alcanza la longitud mantiene el índice del arreglo por debajo de esa longitud.
Llamada al sistema
Una llamada al sistema es una solicitud de un programa para obtener un servicio del sistema operativo mediante la interfaz definida por su entorno de ejecución.
En RISC-V Linux, ecall con 93 en a7 solicita la salida y usa a0 para el estado.

M

Marco de pila
Un marco de pila es la región reservada para el estado guardado, los objetos locales, los valores derramados a memoria y el espacio para argumentos en la pila de una activación de procedimiento.
Tres llamadas activas pueden contener tres copias distintas del argumento guardado de una misma función.
Máscara de bits
Una máscara de bits es un patrón usado con una operación bit a bit para seleccionar, poner a cero, poner a uno o invertir posiciones concretas de otro patrón.
Aplicar AND con 0xFF conserva el byte inferior y pone a cero todas las posiciones superiores del registro.
Microarquitectura
Una microarquitectura es el diseño interno de un procesador para implementar las operaciones y el comportamiento que exige su conjunto de instrucciones.
Cambiar el hardware de ejecución puede mejorar la velocidad de un chip y preservar su interfaz de código máquina existente.
Microoperación
Una microoperación es una operación interna en la que un procesador puede traducir una instrucción de la ISA para que la ejecute su núcleo.
Una instrucción x86 visible externamente puede requerir varias microoperaciones internas más sencillas.

N

Número dinámico de instrucciones ejecutadas
El número dinámico de instrucciones ejecutadas es la cantidad de instrucciones realmente ejecutadas en la ruta de un programa, incluidas las pasadas repetidas por bucles.
La comprobación final fallida de un bucle contribuye al número de instrucciones ejecutadas aunque su cuerpo no vuelva a ejecutarse.

O

Operando inmediato
Un operando inmediato es una constante representada mediante bits dentro de una instrucción, disponible sin tener que buscar primero la constante en memoria.
El último operando de addi t0, t0, 1 se codifica en la instrucción como inmediato.
Orden de bytes con el menos significativo primero
El orden de bytes con el menos significativo primero almacena el byte menos significativo de un valor de varios bytes en su dirección de memoria más baja.
Un almacenamiento de palabra de 0x12345678 coloca 0x78 en el primer byte y 0x12 en el cuarto.

P

Paso entre elementos
Un paso entre elementos es la distancia en bytes entre posiciones correspondientes de elementos consecutivos de un arreglo o de una distribución repetida.
Un arreglo de estructuras de dos enteros tiene un paso de 8 bytes, mientras que su segundo campo tiene un desplazamiento de 4.
Procedimiento hoja
Un procedimiento hoja es un procedimiento que no llama a otros procedimientos, lo que le permite mantener intacta la dirección de retorno recibida.
Un procedimiento hoja que solo usa registros de argumentos y temporales puede retornar sin reservar un marco de pila.
Prólogo
Un prólogo es la secuencia de entrada que establece el marco de un procedimiento y guarda el estado recibido de los registros que debe preservar.
Guardar ra antes de una llamada anidada protege la dirección necesaria para retornar al llamador exterior.
Pseudoinstrucción
Una pseudoinstrucción es una abreviatura del ensamblador que se expande en una o más instrucciones realmente definidas por la ISA del procesador.
El número de instrucciones máquina que produce li depende del valor de su operando constante.
Puesta a uno si es menor
La puesta a uno si es menor es una operación de comparación que escribe 1 cuando el primer operando de origen es menor que el segundo y escribe 0 en caso contrario.
slt y sltu pueden discrepar cuando el bit más significativo de un operando de origen le da valores diferentes con signo y sin signo.
Puntero
Un puntero es un valor que representa una dirección de objeto o código y se usa para localizar aquello a lo que accederá una carga, un almacenamiento o una transferencia indirecta.
Un registro de puntero proporciona una dirección, mientras que una carga recupera el valor almacenado en esa dirección.
Puntero de pila
El puntero de pila es el registro que marca el límite actual del almacenamiento reservado en la pila y se mueve cuando se reservan o liberan marcos.
Restar 16 de sp reserva un marco alineado en direcciones más bajas según la convención de RISC-V.

R

Recursión
La recursión ocurre cuando un procedimiento se llama a sí mismo directa o indirectamente, creando invocaciones activas separadas con su propio estado.
El factorial carga de nuevo el argumento guardado de una llamada exterior después de que la llamada interior devuelva su resultado.
Registro cero
El registro cero es el registro x0 de RISC-V, que siempre se lee como cero y descarta cualquier valor que una instrucción escriba en él.
Un salto puede escribir su enlace en zero cuando el programa no necesita guardar una dirección de retorno.
Registro de enteros
Un registro de enteros es una pequeña ubicación de almacenamiento del procesador que contiene un patrón de bits usado por operaciones con enteros o cálculos de direcciones.
En RV32I, cada uno de los 32 registros de enteros contiene 32 bits, tanto si se usa para un valor como para una dirección.
Registro preservado por el procedimiento llamado
Un registro preservado por el procedimiento llamado debe conservar su valor recibido después de una llamada, por lo que un procedimiento que lo modifica debe restaurarlo.
Una función puede usar s1 internamente si restaura el s1 original del llamador antes de retornar.
Registro preservado por el procedimiento llamador
Un registro preservado por el procedimiento llamador puede ser sobrescrito por un procedimiento llamado, por lo que su llamador debe proteger cualquier valor actual que necesite después.
Un valor activo en t2 debe guardarse antes de llamar a un auxiliar que puede usar ese registro.
Reubicación relativa
La reubicación relativa es el ajuste de una referencia basado en la distancia entre una instrucción o ubicación de datos ya situada y su destino.
Desplazar solo una sección puede cambiar el desplazamiento de una llamada aunque los bytes de la instrucción no hayan cambiado inicialmente.

S

Salto con enlace
Un salto con enlace es una transferencia de control que también escribe la dirección de la siguiente instrucción en un registro de destino para usarla más tarde.
jal ra, Helper registra dónde debe reanudar el llamador cuando Helper retorne.
Salto condicional
Un salto condicional cambia la dirección de la siguiente instrucción solo cuando se cumple su comparación; en caso contrario, continúa en secuencia.
bne t0, t1, Loop vuelve a Loop solo mientras los dos patrones de registro sean diferentes.
Salto indirecto
Un salto indirecto obtiene su destino de un cálculo basado en un registro, lo que permite que una dirección almacenada elija la siguiente instrucción.
jalr puede implementar una llamada mediante puntero a función, un retorno o un despacho mediante tabla de saltos.

T

Tabla de saltos
Una tabla de saltos es una colección indexada de direcciones de código que se usa para transferir el control de forma eficiente entre varios casos.
Un despacho de switch debe comprobar su rango de casos antes de leer una dirección de su tabla de saltos.
Tabla de símbolos
Una tabla de símbolos registra definiciones con nombre y referencias sin resolver que se usan para conectar instrucciones y datos entre módulos traducidos.
Una función puede estar sin definir en un objeto y definida en otro objeto proporcionado al enlazador.
Terminador nulo
Un terminador nulo es el byte cero que marca el final de una cadena de bytes de C y ocupa espacio después del último byte distinto de cero.
Un bucle de copia de cadenas almacena el terminador para que el destino también tenga un final reconocible.
Tiempo de vida de un objeto
El tiempo de vida de un objeto es el periodo durante el cual su almacenamiento representa un objeto válido al que el programa puede acceder.
Devolver un puntero a un objeto local no mantiene vivo ese objeto después de que retorne su función.

V

Vinculación diferida
La vinculación diferida retrasa la resolución de un procedimiento de biblioteca compartida hasta su primera llamada y registra el resultado para llamadas posteriores.
La primera llamada puede invocar al enlazador dinámico, mientras que las llamadas posteriores usan una dirección ya almacenada.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar