a procedure call
Programs are built from reusable pieces: a function to compute a square root, a function to draw a button. Calling one is like sending a coworker off to do a task and expecting them back at your desk afterwards. A procedure call is the machine-level act of jumping to a function's code, running it, and then returning to exactly the instruction right after the call so the original work can continue where it left off.
The core trick is remembering where to come back to. A plain jump goes somewhere and forgets where it came from; a call must leave a trail. On RISC-V the call instruction jal (jump and link) does two things at once: it jumps to the function and it saves the address of the next instruction (the return address) into a register. When the function finishes, a return instruction (ret, really jalr back to that saved address) jumps right back. Arguments are usually passed in agreed-upon registers, and the function leaves its result in an agreed-upon register too, so caller and callee can hand work back and forth without stepping on each other.
Procedure calls matter because they are how big programs stay organized: each function is a little contract you can reason about in isolation. But the machinery is delicate. If a function makes its own calls, it must first save the return address (otherwise the inner call overwrites it and it can never get home). The rules for who saves what, where arguments go, and how the stack is managed are spelled out by the calling convention, which is what lets code written by different people, even different compilers, call each other reliably.
Caller side: li a0, 9 # put the argument 9 in a0 jal ra, isqrt # call isqrt; ra <- address of next line # ... on return, the result is now in a0 isqrt: # callee # ... compute, leave answer in a0 ... ret # jump back to the saved return address
jal jumps and saves the return address; ret jumps back. Argument and result both travel in a0 here.
jal saves the return address into a register, not onto the stack. A function that itself calls others must save that register to the stack first, or it will lose its way home.