fp / s0)
stack[] y heap[]).sp.goto (branches).| Abreviación | Nombre Completo | Significado en Español | Uso en Compiladores |
|---|---|---|---|
zero (x0) |
Zero | Cero cableado | Siempre vale 0 por hardware. |
ra (x1) |
Return Address | Dirección de Retorno | Guarda a dónde volver tras un subprograma. |
sp (x2) |
Stack Pointer | Puntero de Pila | Apunta al fondo actual móvil de la pila. |
fp / s0 (x8) |
Frame Pointer | Puntero de Marco | Base fija e inamovible de la función activa. |
gp (x3) / tp (x4) |
Global / Thread Ptr | Puntero Global / Hilo | Acceso a globales y contexto de hilos. |
t0 - t6 |
Temporaries | Temporales | Registros rápidos y volátiles para evaluar expresiones. |
s1 - s11 |
Saved Registers | Registros Guardados | Variables de larga vida (Callee-saved). |
a0 - a7 |
Arguments | Argumentos / Retorno | Primeros 8 parámetros. a0-a1 reciben retornos. |
add / addi: Add / Add Immediate (suma)sub: Subtract (resta)mul / div / rem: Multiply / Divide / Remainder (%)neg: Negate (0 - rs)and / or / xor: Operaciones bitwisesll / slli: Shift Left (multiplicar x 2^n)srl / sra: Shift Right Lógico / Aritmético
lw / sw: Load / Store Word (4 bytes)lb / sb: Load / Store Byte (1 byte)slt / slti: Set on Less Than (comparación <)beq / bne: Branch Equal / Not Equalblt / bge: Branch Less Than / Greater Equalj: Jump incondicional (goto)jal / ret: Jump and Link / Return
i = Inmediato (constante). Si empieza con b = Branch (salto condicional). Si empieza con l = Load (de memoria a CPU). Si empieza con s = Store (de CPU a memoria).
-12(fp)?Los primeros bytes de la cabecera del marco están estrictamente reservados por hardware y convención:
Nota: No siempre empiezan en -12(fp); pueden iniciar en -8(fp) si no hay parámetros ni this (solo s0 y ra), o después (-16(fp), -20(fp)...) si se respaldan más parámetros.
ra y el s0 previo al entrar, la función destruirá el camino de retorno y el programa colapsará en bucle infinito.
2. -8(fp): Reservado para el puntero this o el primer parámetro que deba preservarse.
3. -12(fp): Es el primer byte seguro y disponible para variables del usuario.
fpwhile (contador < limite) { total = total + contador; contador++; }
for (i = 0; i < 10; i = i * 2) { a++; }
sp directamente?sp es VOLÁTIL y MÓVIL: Cada vez que preparas argumentos para llamar a otra función, evalúas expresiones complejas en stack o usas memoria dinámica local, sp cambia su valor (addi sp, sp, -16).x estaba en 4(sp), y luego empujas 3 argumentos a la pila para llamar a otra función, ¡ahora la variable x está en 16(sp)! El compilador tendría que recalcular los offsets de todas las variables locales instrucción por instrucción.fp (Frame Pointer / s0):
fp es un ancla estática. Se fija al entrar a la función y NUNCA se mueve.
La variable x siempre será -12(fp) desde la primera línea hasta el return, sin importar cuánto se mueva sp.
jal vs j: Dos Formas de Saltarra.
• Uso: Exclusivo para estructuras de control dentro del mismo contexto (if, else, while, for, switch).
• Equivalente: goto etiqueta
PC + 4) en ra.
• ¿Modifica la pila? Por convención, la función invocada ejecutará su prólogo y creará un nuevo marco.
• Uso: Exclusivo para llamadas a subprogramas y funciones.
• Retorno: La función hija saldrá con ret (jr ra).
a0, a1 ... a7 (súper rápido).jal:alloca o int arr[n])?
Si el tamaño del arreglo depende de n en tiempo de ejecución, el compilador debe hacer sub sp, sp, t0. En ese escenario, sin fp es imposible compilar, porque el desplazamiento respecto a sp es desconocido en compilación.
addi sp, sp, -FRAMESIZE.fp (FPO - Frame Pointer Omission)?
En niveles de optimización altos (`-O2`), el compilador calcula offsets variables respecto a sp con matemáticas complejas para liberar el registro `s0` y ganar velocidad. Para los alumnos, usar fp simplifica el proyecto y evita semanas de depuración.
main ➔ A ➔ B ➔ C
Cada función reserva su propio tamaño exacto con tipos mezclados (int, double, char, boolean) y resguarda su ra:
int id (4B, offset -8(fp))
- double saldo (8B, offsets -12(fp) y -16(fp))
- char grado (1B en slot -20(fp))
- boolean activo (1B en slot -24(fp))
- Tamaño marco: 32 Bytes (múltiplo de 16)
• Función B: 24 Bytes (int x, int y, resC, ra, fp)
• Función C: Función hoja (16 Bytes)
main llama a A: A guarda ra_main en -4(s0).
2. A llama a B: B guarda ra_A en -4(s0).
3. B llama a C: C coloca 99 en a0 y retorna a B con ret.
4. B suma x + 99 = 109, recupera ra_A con lw ra, -4(s0) y retorna a A.
5. A recupera ra_main con lw ra, -4(s0) y retorna 109 a main.
fp, los 80 bytes de atributos residen una sola vez en memoria.
this:
Entra en a0, se respalda en el marco local (ej. -8(fp)) y se vuelve a reenviar en a0 en cada llamada hija:
id (4 bytes int)saldo (8 bytes double)estado (1 byte bool)| Cuádrupla C3D | Instrucciones RISC-V | Estrategia en el Compilador |
|---|---|---|
(+ , a , b , t) |
lw t0, offA(fp); lw t1, offB(fp); add t2, t0, t1; sw t2, offT(fp) |
Cargar operandos, sumar y guardar resultado en ranura |
(if> , a , b , L1) |
lw t0, offA(fp); lw t1, offB(fp); bgt t0, t1, L1 |
Cargar valores y emitir branch condicional |
(goto , _ , _ , L1) |
j L1 |
Salto incondicional directo (sin tocar ra) |
([]= , heap , H , val) |
sw t0, offset(a0) |
Almacenamiento de atributo en dirección Heap |
(call , func , 2 , t0) |
jal ra, func; sw a0, offT(fp) |
Llamada con enlace y captura de retorno de a0 |
public int factorial(int n)
▼
// Código Java
| PTR | DIR | OFFSET | MARCO & ROL | VALOR |
|---|
fact(4); el hardware fija ra = 0x0018 (PC + 4).
• fact(4) [fp = 0x013C]:
- Guarda fp_prev = 0x0154 (enlace a main).
- Guarda ra = 0x0018 en -4(fp).
- Guarda n = 4 en -8(fp). Llama a fact(3).
• fact(3) [fp = 0x0124]:
- Guarda fp_prev = 0x013C, ra = 0x0060, n = 3. Llama a fact(2).
• fact(2) [fp = 0x010C]:
- Guarda fp_prev = 0x0124, ra = 0x0060, n = 2. Llama a fact(1).
• fact(1) [fp = 0x00F4] (CASO BASE):
- 1 <= 1 es VERDADERO ➔ Coloca 1 en a0.
- Restaura ra y fp, libera 24B y ejecuta ret.
a0 = 1.
2. Inmediatamente lo guarda en -12(fp): sw a0, -12(s0).
3. Carga n=2 de -8(fp) y ret=1 de -12(fp).
4. mul a0, t0, t1 (2 * 1 = 2) ➔ Deja 2 en a0 y retorna.
• Al volver a fact(3) [PC = 0x0060]:
1. Recibe a0 = 2 y lo guarda en -12(fp) de su marco.
2. Multiplica n=3 * ret=2 = 6 ➔ Deja 6 en a0 y retorna.
• Al volver a fact(4) [PC = 0x0060]:
1. Recibe a0 = 6 y lo guarda en -12(fp) de su marco.
2. Multiplica n=4 * ret=6 = 24 ➔ Deja 24 en a0 y retorna.
• main [PC = 0x0018] recibe 24 en a0 y lo guarda en su variable.
a0 es un canal de paso. Si el invocador (caller) no respaldara a0 inmediatamente en su pila local (-12(fp)), cualquier llamada subsiguiente destruiría el valor devuelto.
A ➔ B ➔ C: Tipos Mixtos y Alineación0(fp): s0_viejo (4 bytes) ➔ Enlace dinámico a main
• -4(fp): ra (4 bytes) ➔ Dirección de retorno a main
• -8(fp): int id = 10 (4 bytes, alineado a 4B)
• -12(fp) y -16(fp): double saldo = 3.14 (8 bytes):
- Ocupa dos palabras contiguas de 32 bits en RV32
- Almacenado con dos sw (alta y baja) o fsd
• -20(fp): char grado = 'Z' (1 byte con sb en ranura de 4B)
• -24(fp): bool activo = true (1 byte con sb en ranura de 4B)
• -28(fp): int resB = 109 (retorno de B, cierra múltiplo de 16B).
main salta a A con jal ra, func_A (en 0x0014):
ra adquiere la dirección 0x0018 (PC + 4 en main).
• A DEBE salvar ra en -4(fp) inmediatamente.
• Cuando A llama a B con jal ra, func_B (en 0x004C):
ra es sobreescrito por el hardware con 0x0050 (en A).
• B salva ra (0x0050) en su propio marco (-4(fp)).
• Cuando B llama a C con jal ra, func_C (en 0x0080):
ra adquiere 0x0084 (en B).
• Desenrollado: C retorna 99 a B (0x0084), B suma 10 + 99 = 109 y retorna a A (0x0050), y A retorna 109 a main (0x0018).
ra SIEMPRE debe guardarse en el prólogo y restaurarse en el epílogo.
33 | 0x1004: 12 | 0x1008: 55 | 0x100C: 891 | 0x1014: 2 | 0x1018: 44 | 0x101C: 1970 | 0x1024: 5sw y lw sobre 0x1000 + i*4.
qsort(arr, 0, 9): low=0, high=9, piv=4, ra, fpqsort(arr, 0, 3): low=0, high=3, piv=1, ra, fpqsort(arr, 0, 0): Caso base (0 >= 0) ➔ ret inmediatoqsort(arr, 2, 3): sub-rango derecho...heap[H]. La llamada emite param 0x1000, param low, param high. El código RISC-V carga a0 = 0x1000 y usa direccionamiento base + desplazamiento para mutar la memoria global.
Con el uso del Frame Pointer (fp), el respaldo sistemático de ra y la separación entre Heap y Stack, tus alumnos tienen la base sólida para construir el compilador completo.