- Aprender
- Informática
- Ciencias de la computación
- Organización y arquitectura de computadores
- Conjunto de instrucciones
Conjuntos de instrucciones y lenguaje ensamblador
IntermedioUnidad 2 de 6 del curso Curso de Organización y arquitectura de computadores
Lo que aprenderás
- 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 instruccionesDescribe 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 expresionesIdentifica 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 memoriaUsa 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 inmediatosUsa 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.
- Explica qué es una arquitectura del conjunto de instrucciones
- 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 desplazamientoCalcula 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 incondicionalesUsa 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 ensambladorConvierte sentencias if-else, condiciones compuestas, bucles while y for y sentencias switch en saltos, y sigue el código resultante con valores reales.
- Usa instrucciones aritméticas, lógicas y de desplazamiento
- 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 almacenamientoOrganiza 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áquinaCodifica 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.
- Codifica instrucciones de registro, de inmediato y de almacenamiento
- 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 llamadaPasa 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 recursivosReserva 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 ensambladorConvierte 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.
- Llama a procedimientos y retorna de ellos siguiendo la convención de llamada
- 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 objetoDescribe 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 ejecutableDescribe 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.
- Explica cómo los compiladores y ensambladores generan archivos objeto
- 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 CISCDescribe 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 compatibilidadUsa 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.
- Contrasta las características de diseño de RISC y CISC
Guía de estudio de Conjunto de instrucciones
1Instrucciones, 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.
| Registros | Nombres ABI | Función convencional |
|---|---|---|
| x0 | zero | Siempre se lee como 0 |
| x1, x2 | ra, sp | Dirección de retorno y puntero de pila |
| x3, x4 | gp, tp | Punteros global y de hilo |
| x5–x7, x28–x31 | t0–t6 | Valores temporales |
| x8–x9, x18–x27 | s0–s11 | Valores preservados entre llamadas |
| x10–x17 | a0–a7 | Argumentos; 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:
| Texto | Instrucción base o expansión |
|---|---|
mv rd, rs | addi rd, rs, 0 |
nop | addi x0, x0, 0 |
not rd, rs | xori rd, rs, -1 |
neg rd, rs | sub rd, x0, rs |
li rd, c, para un c pequeño | addi 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.
2Aritmé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ón | Condición de salto |
|---|---|
beq, bne | Igual, distinto |
blt, bge | Menor que con signo, mayor o igual que con signo |
bltu, bgeu | Menor 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.
3Campos 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 desplazamiento | Bits 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.
4Llamadas 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.
5Del 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.
6Compromisos 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
1Sigue los operandos desde una expresión hasta la memoria
15 pasos
- 1Introducció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.
- 2Explicació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. - 3DesafíoCuenta las instrucciones después del ensamblado
Un ensamblador expande una instrucción
lien dos instrucciones máquina, seguidas de una instrucciónadd. 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.
- 4EjemploConserva la suma intermedia
Calcula
r = (u + v) - wconu = 18,v = 7,w = 6ena1,a2,a3:add t0, a1, a2 sub a0, t0, a3Después de la primera instrucción,
t0contiene 25. Después de la segunda,a0contiene 19. Las tres entradas siguen disponibles porque ninguna de las instrucciones escribe en sus registros.t0tiene una función: preservar el primer resultado hasta que la resta lo lea. - 5DesafíoCambia el destino y conserva los operandos de origen
Con las mismas entradas, ahora se ejecuta
sub a3, t0, a3después de la suma. ¿Qué cambia respecto a escribir el resultado ena0?---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.a0no cambia, por lo que un llamador que espere allí el resultado no recibirá este nuevo resultado. - 6ExplicaciónConvierte un índice en una dirección
Un arreglo de enteros comienza en la dirección contenida en
s1. Cadaintde 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 cargalw t0, 8(s1)lee el valor del elemento 2; no coloca la propia dirección ent0. El registro base permanece sin cambios. - 7EjemploCarga, calcula y almacena
Implementa
A[5] = A[2] + rconr = 19ena0: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 enA[5]. Leer y escribir memoria requiere instrucciones separadas alrededor de la operación aritmética. El valor deA[2]sigue siendo 11. - 8DesafíoDetecta la unidad incorrecta de un desplazamiento
Alguien cambia la primera línea a
lw t0, 2(s1)porque la expresión nombraA[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.
- 9EjemploLee un byte de una palabra almacenada
Almacena
0x7193D2A6en una dirección alineada de una máquina con el byte menos significativo primero. Sus bytes en orden de direcciones crecientes son0xA6,0xD2,0x93,0x71. Una carga de byte en el desplazamiento 1 selecciona0xD2.lbuda 210, mientras quelbda −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. - 10ExplicaciónEncuentra la parte inferior con signo
Ahora construye
0x00003D20en un registro. Sus 12 bits inferiores son0xD20, 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 el0x3visible. Elegir0x4da0x4000, desde donde restar0x2E0alcanza el valor deseado. - 11EjemploConstruye y comprueba la constante
Usa:
lui t1, 0x4 addi t1, t1, -736La 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, 0x3D20permite al ensamblador elegir la construcción, pero su expansión sigue ocupando instrucciones reales. - 12DesafíoCorrige la división de una constante
Una división usa
lui t1, 0x3yaddi t1, t1, -736. ¿Cuánto se aleja su resultado de0x3D20y qué campo necesita corregirse?---reveal---
Produce
0x2D20, exactamente 4096 menos. Aumenta el campo superior de0x3a0x4y conserva el sumando inferior con signo. Cambiar el sumando al0xD20positivo superaría el rango positivo con signo deaddi. - 13ConsejoSigue 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.
- 14DesafíoCombina los pasos de dirección y valor
Con la base del arreglo en
s1, el elementoA[6]es 17.t1contiene la constante0x3D20construida correctamente. ¿Qué cambialw t0, 24(s1); add t0, t0, t1; sw t0, 4(s1)?---reveal---
Lee
A[6], suma 17 a 15 648 y almacena 15 665 enA[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 ens1permanecen sin cambios. - 15RepasoSigue 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.
2Sigue la aritmética hasta las decisiones y los bucles
14 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHaz visible la ruta de ejecución
- 2EjemploDesplaza el campo a su posición
- 3EjemploConserva solo la anchura del campo
- 4DesafíoPredice un desplazamiento con signo
- 5ExplicaciónCompara usando la interpretación correcta
- 6EjemploDistingue un salto de una llamada
- 7DesafíoComprueba un patrón de dos maneras
- 8EjemploDibuja las dos rutas de un if
- 9ExplicaciónColoca la comprobación del límite antes de la carga
- 10EjemploSigue tres ejecuciones del cuerpo
- 11DesafíoCuenta toda la ruta ejecutada
- 12ConexiónSustituye el escalado repetido por una dirección que avanza
- 13DesafíoProtege una carga con control en cortocircuito
- 14RepasoComprueba valores y rutas conjuntamente
3Codifica una instrucción y reconstruye un salto condicional
12 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónTraduce en ambos sentidos
- 2ExplicaciónIdentifica el formato y los operandos
- 3EjemploEscribe los campos con su anchura completa
- 4EjemploConvierte y decodifica la palabra
- 5DesafíoLocaliza el origen del valor de un almacenamiento
- 6ExplicaciónMide la distancia hacia atrás
- 7EjemploDivide el inmediato del salto condicional
- 8EjemploConstruye el salto condicional completo
- 9DesafíoRecupera el bit cero que falta
- 10ConexiónConserva la distancia relativa cuando se desplaza el código
- 11DesafíoUsa el código de operación para descartar una decodificación incorrecta
- 12RepasoComprueba una palabra máquina en sentido inverso
4Conserva valores durante llamadas anidadas
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónProtege lo que el llamador aún necesita
- 2EjemploEspecifica el resultado del procedimiento envolvente
- 3ExplicaciónAplica los compromisos de preservación
- 4DesafíoElige el estado que debe conservarse durante la llamada
- 5EjemploReserva y llena el marco
- 6EjemploCombina los resultados antes de liberar el almacenamiento
- 7ExplicaciónRepite el patrón de forma recursiva
- 8DesafíoRecupera los argumentos guardados al retornar
- 9ExplicaciónDistingue una dirección de su objeto
- 10EjemploElige operaciones de byte para una cadena
- 11DesafíoTermina en las direcciones correctas
- 12ConsejoRestaura el valor guardado, no el nuevo resultado
- 13ConexiónEl tiempo de vida del objeto sigue al del marco
- 14DesafíoConserva un puntero de destino durante una llamada a un auxiliar
- 15RepasoRevisa una llamada completa
5Sigue una llamada desde el código fuente hasta el inicio
11 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónConsidera qué sabe cada herramienta
- 2ExplicaciónCompila para una ISA de destino
- 3EjemploDeja sin completar la llamada externa
- 4DesafíoDistingue la generación de datos de la ejecución
- 5EjemploColoca el módulo de destino
- 6EjemploAjusta la referencia relativa
- 7ExplicaciónElige cuándo proporcionar el código de biblioteca
- 8DesafíoLocaliza la etapa del fallo
- 9ConexiónCrea un proceso antes de llamar a main
- 10DesafíoExplica por qué un objeto no es el programa terminado
- 11RepasoSigue un nuevo error hasta su etapa
6Compara diseños con la medida adecuada
10 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónMantén separadas tres mediciones
- 2EjemploCompara la operación visible de memoria
- 3ExplicaciónRelaciona la codificación con la decisión de diseño
- 4DesafíoClasifica una característica con cuidado
- 5EjemploCalcula el primer tiempo de ejecución
- 6EjemploCompara un número mayor de instrucciones
- 7DesafíoSepara el ahorro de bytes del factor de aceleración
- 8ConexiónLa compatibilidad va más allá de la ISA
- 9DesafíoEvalúa una afirmación sobre optimización en ensamblador
- 10RepasoIndica las pruebas que respaldan la comparación
Ejercicios de Conjunto de instrucciones
Set 1: Registros, memoria y constantes10 Preguntas de Práctica
Pregunta 1 de 10
0xB2 se carga una vez con lb y otra con lbu. ¿Qué resultados decimales producen las dos cargas, en ese orden?Mostrar respuestaOcultar respuesta
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
Mostrar respuestaOcultar respuesta
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
sub a2, a0, a1, a0 contiene 28 y a1 contiene 9. ¿Qué valor se escribe en a2?Mostrar respuestaOcultar respuesta
a2. Invertir los operandos de origen daría −19, pero ese no es su orden aquí.Pregunta 4 de 10
A[7]?Mostrar respuestaOcultar respuesta
Pregunta 5 de 10
sw almacena 0x86C4217D en 0x3000. ¿Qué valor produce lbu t0, 2(s0) cuando s0 = 0x3000?Mostrar respuestaOcultar respuesta
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
lui e inmediato con signo de addi construyen 0x00002ABC en RV32?Mostrar respuestaOcultar respuesta
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
t3, llama a un auxiliar que cumple la convención y después necesita el valor original. ¿Qué debe hacer el llamador?Mostrar respuestaOcultar respuesta
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
auipc t0, 0x2 en 0x4000, seguido de addi t0, t0, -24. ¿Qué valor final queda en t0?Mostrar respuestaOcultar respuesta
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
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?Mostrar respuestaOcultar respuesta
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)
Mostrar respuestaOcultar respuesta
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
add suma 1 a 0xFFFFFFFF?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
bgt a2, a3, Done?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
srli t0, a0, 8 y andi t0, t0, 0x1F, ¿qué valor hay en t0 si a0 = 0x0000D6B9?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
a0 = -11 con signo, ¿qué resultados producen srai t0, a0, 2 y la división entera de C -11 / 4, respectivamente?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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
while (A[i] == 6) i++; comienza con i = 1 y A = {6, 6, 6, 4, 6}. ¿Cuánto vale i cuando sale del bucle?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)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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?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
sw a2, 20(sp), ¿qué registro proporciona el campo rs1?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
addi t2, s1, -36?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
0x308C tiene como destino 0x3068. ¿Qué desplazamiento en bytes con signo se codifica?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
jal en 0x1200 tiene un desplazamiento con signo +84 y destino ra. ¿Qué dirección de destino y dirección de retorno resultan?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
addi t2, s1, -36? Aquí t2 = x7, s1 = x9, el código de operación de ADDI es 0x13 y funct3 = 000.Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
0xFEC12623. El código de operación 0x23 selecciona un almacenamiento y funct3 = 010 selecciona una palabra. ¿Qué instrucción de ensamblador representa?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
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.Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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?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
s4. ¿Qué valor debe contener s4 cuando retorne?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
a0 al siguiente elemento cuando contiene un int *?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
sp = 0x8000, reserva 32 bytes y después ejecuta sw ra, 28(sp). ¿En qué dirección comienza la palabra guardada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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?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
gcc -c module.c sin enlazar?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
main, pero llama a mix, que está definido en un segundo objeto. ¿Qué permite al enlazador conectar esa llamada con la definición?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
a7, 7 en a0 y después ejecuta ecall. ¿Qué servicio solicita?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
main. ¿Qué explica el fallo y qué cambiaría para el código de esa biblioteca incluido estáticamente?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
add eax, [ebx+12] de x86, si eax corresponde a a0 y ebx a s1?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
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?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
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Alineación natural
Archivo objeto reubicable
-c detiene el proceso antes de combinar las bibliotecas y los módulos necesarios para un ejecutable.Arquitectura de carga y almacenamiento
Arquitectura de conjunto complejo de instrucciones (CISC)
Arquitectura de conjunto reducido de instrucciones (RISC)
Arquitectura del conjunto de instrucciones
Asignación de registros
B
Bit implícito
Bloque básico
C
Campo de función
funct7.Campo de registro
a0 como el registro número 10, escrito 01010 en binario.Carga de dirección
la t0, Item, se necesita una carga independiente para leer el valor contenido en Item.Cargador
Ciclos por instrucción (CPI)
Código de operación
Código independiente de la posición
Compatibilidad binaria
Compilación en tiempo de ejecución
Compilador
Concepto de programa almacenado
Contador de programa
Continuación secuencial
Convención de llamada
a0 a a7.Cuarteto de bits
D
Densidad de código
Derrame de registros a memoria
Desbordamiento
Desensamblado
Desplazamiento aritmético a la derecha
Desplazamiento de un campo de estructura
Desplazamiento del salto
Desplazamiento lógico a la derecha
Desreferenciación
lw t0, 0(a0) lee un entero de 4 bytes mediante la dirección contenida actualmente en a0.Dirección relativa al PC
auipc puede alcanzar datos locales incluso cuando se desplaza todo el grupo de código y datos.Direccionamiento con índice escalado
Direccionamiento por bytes
Direccionamiento por desplazamiento
lw t0, 20(s1), la palabra cargada comienza 20 bytes más allá de la dirección almacenada en s1.Directiva del ensamblador
.word 5 genera un valor de 4 bytes, mientras que .text selecciona dónde pertenece el código posterior.E
Enlace dinámico
Enlace estático
Enlazador
Ensamblador
call y puede dejar entradas de reubicación para una función definida en otro módulo.Epílogo
sp mantiene sus desplazamientos relativos al marco reservado.Evaluación en cortocircuito
&& impide evaluar el segundo operando en C.Extensión con ceros
0xF0 con lbu produce el valor positivo 240 en un registro de 32 bits.Extensión de signo
0xF0 con lb produce 0xFFFFFFF0, la representación de 32 bits de −16.F
Formato de instrucción
I
Indicadores de condición
Inmediato superior
lui coloca por encima de 12 ceros inferiores al construir un valor de registro RV32.addi posterior puede proporcionar una parte inferior con signo para completar una constante de 32 bits.Instrucción comprimida
Interfaz binaria de aplicación
L
Lenguaje ensamblador
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
Llamada al sistema
ecall con 93 en a7 solicita la salida y usa a0 para el estado.M
Marco de pila
Máscara de bits
0xFF conserva el byte inferior y pone a cero todas las posiciones superiores del registro.Microarquitectura
Microoperación
N
Número dinámico de instrucciones ejecutadas
O
Operando inmediato
addi t0, t0, 1 se codifica en la instrucción como inmediato.Orden de bytes con el menos significativo primero
0x12345678 coloca 0x78 en el primer byte y 0x12 en el cuarto.P
Paso entre elementos
Procedimiento hoja
Prólogo
ra antes de una llamada anidada protege la dirección necesaria para retornar al llamador exterior.Pseudoinstrucción
li depende del valor de su operando constante.Puesta a uno si es menor
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
Puntero de pila
sp reserva un marco alineado en direcciones más bajas según la convención de RISC-V.R
Recursión
Registro cero
zero cuando el programa no necesita guardar una dirección de retorno.Registro de enteros
Registro preservado por el procedimiento llamado
s1 internamente si restaura el s1 original del llamador antes de retornar.Registro preservado por el procedimiento llamador
t2 debe guardarse antes de llamar a un auxiliar que puede usar ese registro.Reubicación relativa
S
Salto con enlace
jal ra, Helper registra dónde debe reanudar el llamador cuando Helper retorne.Salto condicional
bne t0, t1, Loop vuelve a Loop solo mientras los dos patrones de registro sean diferentes.Salto indirecto
jalr puede implementar una llamada mediante puntero a función, un retorno o un despacho mediante tabla de saltos.T
Tabla de saltos
Tabla de símbolos
Terminador nulo
Tiempo de vida de un objeto
V
Vinculación diferida
Sobre esta unidad
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.