The first experience I had with relocation of the output of an assembler was with IBM's first Fortran for the 704. Standard subroutines were to be selectively included in the compiler’s binary card output at the end of the compiled program.
No format had been established to indicate which fields within instructions of a routine were to be relocated. A hack was employed whereby the address field of each instruction was examined to see if it was an address within the range of the routine. If so the relocation amount was added. This was an imperfect heuristic for it might be misapplied to data words that were not instructions, such as floating point constants. This technique applied originally only to the small fixed set of routines supplied by IBM with their first 704 Fortran. It was hazardous for compiler users to add their own code to the subroutine library for this reason.
The FAP assembler included an option to produce relocatable output whereby the assembler used its knowledge of the instruction format to include relocation information in a new card format. At the same time a four card loader was provided to do the relocation as the decks were loaded, thru the card reader into core memory just prior to execution. This greatly facilitated home grown subroutines, written in assembler.
Not long thereafter the Fortran II language allowed subroutines to be defined in Fortran, which the compiler would compile independently to produce relocatable output. Then it became easy to combine smaller Fortran and assembler programs into larger programs as their concatenated binary decks were loaded into the machine. A 7 card loader was produced that made symbolic references possible at load time, matching the definition of 6 character symbols in one deck with uses of those symbols in other decks.