Seems like you're confusing machine code and assembly language.
From the machine code perspective, the whole instruction, and thus all its the fields are simply numbers:
- some are fields encoding things like opcodes,
- some are fields encoding things like register numbers, and,
- some fields are numbers encoding things like signed or unsigned integers.
These encodings are defined by the instruction set architecture. The hardware is specifically designed to interpret these numeric bit fields according to the its ISA specification.
The assembler, linker, and operating system loader conspire to allow you to use symbolic values to form instructions instead of numbers for various fields (or even one number for the whole instruction):
- opcode mnemonics for opcode numbers in opcode fields,
- register names for register numbers in register fields, and,
- labels — even complex expressions — for various immediate values in numeric operand fields.
I wouldn't take it too seriously that one text would refer to ra
vs. rd
. It could indicate a discrepancy, or, just be a different way of documenting the machine code fields of an instruction.
The JAL instruction encodes two operands: a register, and an immediate.
The identified register number is updated with the location of a return address, which is the location of the jal instruction — plus the length of the jal instruction, so that the register gets the value of the next sequential (by
address) instruction after the jal, which is a proper return address from a call.
Like all the bit fields in an instruction, the immediate is an encoding — the decoded value ultimately yields the branch target address. It is computed by converting the immediate bit fields into a signed offset, and added to the pc of the jal instruction. The encoding allows for 18 bits (spread across several bit fields) plus a sign bit, and does not encode the last bit of the offset (branch targets are always 16-bit aligned, meaning the last bit would always be zero anyway so it is not stored). Ultimately the jal can reach -0.5MB to +0.5MB from the jal instruction itself.
As previously mentioned, the executing hardware converts the immediate (sub)field(s) into an offset that it adds to the pc to identify the final branch/call target. That we can provide labels and other complex expressions in assembly language is a feature of those languages, whose aim it to condense labels and other expressions into the immediate bit field constants needed by the processor to go where intended. There are complex interactions of relocation in the object code and/or fixups in the loaded code that ensure these immediate bit fields hold a useful bit pattern that the hardware can use based on otherwise relatively simple field extraction and addition at runtime to get where intended.
For functions to call each other without stepping on each others toes, the asm code for callers and callees must agree upon all of:
- passed parameter(s)
- passed return address
- returned return value(s)
- preserved registers
- scratch registers
This is referred to broadly as the calling convention. It dictates how a caller knowing nothing else about the callee and vice versa, may interact. It imposes software convention requirements on which register or stack location holds the first parameter, second, etc.. on which register or stack location holds the return address, on how the return value is conveyed, and, what registers of the caller's environment are preserved by the call vs. potentially erased by the call (scratch).
When the conventions are properly observed, a caller (not knowing the implementation of the callee, only the types of parameters and return value, aka the function signature) can
- safely store local variables in machine registers across a call,
- pass parameters
- invoke the callee,
- return to the caller,
- and then, receive return values and continue
rd
is the link register, wherejal
stores the return address. Function and subroutine are basically interchangeable terms. You could argue that a subroutines are the subset of functions that don't have a return value, likevoid foo(int x)
– Orangeman