Funciones y la pila
Una función es un bloque de instrucciones con nombre que puedes usar más de una vez. Saltas a ella, realiza su tarea y devuelve el control al lugar desde donde la llamaste. Esto te permite escribir una pieza de lógica una sola vez y usarla desde muchos lugares en lugar de repetir las mismas líneas a lo largo de tu programa.
Usa para llamar a una función: salta a la etiqueta y almacena la ubicación de retorno en ra. La función termina con , que salta de vuelta a la dirección en ra. y son pseudoinstrucciones: nombres convenientes que el ensamblador expande a las instrucciones reales, y .
Los argumentos se colocan en a0, a1 y los registros que siguen. El valor de retorno se coloca en a0. Esta disposición es la convención de llamada, el acuerdo compartido que permite que distintas piezas de código funcionen juntas.
li a0, 6 # argumentocall square # llama, y ra recuerda dónde volverli a7, 1 # square deja su resultado en a0ecallli a7, 10ecallsquare:mul a0, a0, a0 # a0 = a0 * a0ret # vuelve a ra
Sigue la llamada. coloca el argumento 6 en a0. salta a la etiqueta square y almacena la dirección de retorno en ra. Dentro de la función, calcula 6 * 6 = 36 y deja el resultado en a0, el registro usado para valores de retorno. salta de vuelta a la instrucción justo después de la llamada, donde y imprimen 36, y luego el programa termina.
El llamador está a punto de llamar a A.
caller ejecuta , lo que guarda automáticamente el punto de retorno a caller en el registro ra. Antes de que A pueda ejecutar , se topa con un problema: la instrucción está a punto de sobrescribir ra con una nueva dirección de retorno, destruyendo la que A necesita para volver a casa. Para solucionarlo, A hace espacio en la pila () y guarda allí el ra original (). Ahora es seguro ejecutar . B se ejecuta y regresa con un ordinario, aterrizando de nuevo dentro de A. Finalmente, A restaura el ra original desde la pila (), limpia el puntero de pila (), y su propio usa ese valor restaurado para saltar con éxito de vuelta a caller. Si A se saltara este paso, la instrucción habría sobrescrito ra de forma permanente, y el final de A saltaría al lugar equivocado en lugar de volver a casa.La pila
Solo hay unos pocos registros, por lo que las funciones que llaman a otras funciones necesitan espacio temporal. La pila es memoria que se toma prestada a través del puntero de pila sp: resta para hacer espacio, almacena lo que debas conservar y restá antes de .
La pila es una estructura Last-In, First-Out (LIFO): el valor almacenado más recientemente es el primero que recuperas. En RISC-V, la pila crece hacia abajo por la memoria. Restar de sp reserva nuevo espacio que es seguro usar. Cuando recuperas valores, debes hacerlo en el orden inverso al que los almacenaste, o el flujo de ejecución del programa se corromperá.
addi sp, sp, -4 # reserva espacio para una palabrasw ra, 0(sp) # guarda la dirección de retorno# ... llamar a otras funciones aquí ...lw ra, 0(sp) # la restauraaddi sp, sp, 4 # libera el espacioret
Cada / sobrescribe ra, así que si tu función llama a otra función, la llamada interna destruye tu dirección de retorno y tu propio salta al lugar equivocado. Para evitarlo, reserva 4 bytes en la pila, y guarda ra allí. Después de que terminan las llamadas internas, restaura la dirección de retorno, y devuelve el espacio antes de .
ra antes de , y siempre devuelve sp al valor que tenía al inicio. Una pila que no coincide con su estado inicial es la causa habitual de un fallo en los programas en ensamblador.