v5 Linker: merging module bytecode
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesBefore you start
- Derive module initializer artifacts and absolute instruction indexes.
What you will practice
- Relocate function IDs and Main jump addresses in separate coordinate systems.
- Preserve function-local jumps and input artifacts.
- Validate dependency order before deterministic concatenation.
In this lesson
Lesson
3 min readv5 Linker: merging module bytecode
Link combines ModuleBytecode objects into one Bytecode. First validate unique module names, main-last, and every import referring to an earlier module. Then relocate code. Inputs from CompileModules use dense local function IDs 0..N-1 and valid local jump coordinates; this linker is not a general verifier for arbitrary corrupt artifacts.
Worked reconstruction
Suppose util has two functions and seven Main instructions. main has local function ID 0 and a Main JUMP_IF_FALSE target 5:
| Operand in main artifact | Offset | Linked value |
|---|---|---|
| Function ID 0 | previous function count 2 | 2 |
| MAKE_CLOSURE 0 in Main | previous function count 2 | 2 |
| MAKE_CLOSURE 0 inside a function | previous function count 2 | 2 |
| Main JUMP_IF_FALSE 5 | previous Main length 7 | 12 |
| Function-local JUMP 5 | no instruction relocation | 5 |
| CALL 2 | arity, not an address | 2 |
Function IDs and instruction addresses are different number spaces. A module's Main moves inside the concatenated Main, while each function keeps its own separate code slice. Only its closure-ID operands need the function offset; its local jumps stay unchanged. Jump targets equal to the old Main length also shift: they represent that initializer's end, now the next initializer's start in the merged stream.
Relocation algorithm from the invariant
Maintain functionOffset and mainOffset. Copy each function and instruction slice before patching. Add functionOffset to Function.ID and MAKE_CLOSURE operands in both function and Main code. Add mainOffset only to JUMP and JUMP_IF_FALSE operands in Main. Append the relocated copies, then advance each offset by its own count.
Calling Link twice on the same input must produce equivalent output. A shallow copy of Bytecode still shares its slice backing arrays; modifying its Functions or Code in place can permanently rebase the input and double-shift the second link. Preserve the artifacts so they can be cached, linked again, or compared.
Dependency validation is not scheduling
Source order is the required initialization order. The linker rejects a future
import rather than sorting modules to repair it. main being last does not
mean it is the only initializer executed: all Main slices run in dependency
order. Link errors stop the capstone before encoding or runtime execution.
Debugging lab
A linker correctly shifts every closure ID but leaves Main jumps unchanged. With util length 7, main’s local back edge targets instruction 2. Where does it jump incorrectly, and what should the target be?
Compare your diagnosis after attempting the lab
It jumps into util’s initializer at global index 2 instead of main’s local index 2 now located at global 9. Add the accumulated Main length to Main jump operands; do not apply that offset to function-local jumps or call arities.
Transfer task
Work a three-module layout with function counts 2,1,3 and Main lengths 7,4,9. Derive the third module’s function and Main offsets, including a jump to its one-past-end label. Then explain how to test linking twice without relying only on output length.
Carry forward
Carry linked Bytecode with stable unique IDs and relocated Main targets into the v5 capstone. Save a multi-module program with a loop in a later initializer; straight-line examples cannot expose missing jump relocation.
Build bytecode-linker-v5. Keep a before/after trace of one failing test. Use the checkpoint to diagnose specific skills; passing it does not replace implementing and testing the artifact.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.