// linker.v — links one or more VROBJ object files into a VRBIN executable.
//
// Code sections are concatenated, symbol entries are rebased, and call-site
// relocations are patched with the final code offsets. Duplicate or unresolved
// symbols are link errors.
module linker

import obj

pub fn link(paths []string, out string) ! {
	if paths.len == 0 {
		return error('no object files to link')
	}
	mut code := []u8{}
	mut strings := []string{}
	mut symbols := map[string]int{}
	mut relocs := []obj.Reloc{}
	mut lines := []obj.LineInfo{}
	for p in paths {
		o := obj.read(p)!
		base := code.len
		for s in o.symbols {
			if s.name in symbols {
				return error('duplicate symbol "${s.name}" (in ${p})')
			}
			symbols[s.name] = base + s.entry
		}
		code << o.code
		for r in o.relocs {
			relocs << obj.Reloc{ offset: r.offset + u32(base), name: r.name, kind: r.kind }
		}
		for l in o.lines {
			lines << obj.LineInfo{ off: l.off + u32(base), line: l.line }
		}
	}
	// resolve relocations
	for r in relocs {
		if r.kind == 1 {
			// string constant: rebase to its index in the merged table
			idx := intern_str(mut strings, r.name)
			obj.patch_i64(mut code, r.offset, i64(idx))
			continue
		}
		entry := symbols[r.name] or {
			return error('unresolved symbol "${r.name}" (no such function)')
		}
		obj.patch_i64(mut code, r.offset, i64(entry))
	}
	// build the executable
	mut fns := []obj.BinFn{}
	for name, entry in symbols {
		fns << obj.BinFn{ name: name, entry: entry }
	}
	obj.write_bin(out, obj.Bin{ fns: fns, strings: strings, code: code, lines: lines })!
}

fn intern_str(mut table []string, s string) int {
	for i, t in table {
		if t == s {
			return i
		}
	}
	table << s
	return table.len - 1
}
