mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:17:18 +00:00
328 lines
7.3 KiB
V
328 lines
7.3 KiB
V
// obj.v — the VuurRaaf object file format (VROBJ) and executable format (VRBIN).
|
|
//
|
|
// VROBJ is the linker input: code, exported symbols, string constants, and
|
|
// relocations for call sites that the linker must resolve.
|
|
// VRBIN is the final executable consumed by the runtime VM.
|
|
//
|
|
// Both formats are little-endian and self-describing:
|
|
//
|
|
// VROBJ: "VROBJ" ver u32 nsym { u32 name_len name i64 entry }*
|
|
// u32 nstr { u32 len bytes }* u32 ncode code u32 nreloc { u32 off u32 name_len name u8 kind }*
|
|
// VRBIN: "VRBIN" ver u32 nfn { u32 name_len name i64 entry }*
|
|
// u32 nstr { u32 len bytes }* u32 ncode code
|
|
module obj
|
|
|
|
import os
|
|
import math
|
|
|
|
pub const magic = 'VROBJ'
|
|
pub const bin_magic = 'VRBIN'
|
|
|
|
pub struct Symbol {
|
|
pub mut:
|
|
name string
|
|
entry int // code offset of the function entry
|
|
}
|
|
|
|
pub struct Reloc {
|
|
pub mut:
|
|
offset u32 // byte offset of the 8-byte operand inside `code`
|
|
name string // kind 0: symbol name; kind 1: the string constant itself
|
|
kind u8 // 0 = call site, 1 = string constant reference
|
|
}
|
|
|
|
pub struct Obj {
|
|
pub mut:
|
|
symbols []Symbol
|
|
strings []string
|
|
code []u8
|
|
relocs []Reloc
|
|
lines []LineInfo
|
|
locals []DbgLocal
|
|
}
|
|
|
|
pub struct BinFn {
|
|
pub mut:
|
|
name string
|
|
entry int
|
|
}
|
|
|
|
// LineInfo maps a code offset to the source line it was generated from,
|
|
// enabling source-level locations in runtime errors.
|
|
pub struct LineInfo {
|
|
pub mut:
|
|
off u32
|
|
line int
|
|
}
|
|
|
|
// DbgLocal is one named local variable of a function (debug info for the
|
|
// interactive debugger): the VM can resolve `print <name>` to the value at
|
|
// stack slot bp+slot while stopped inside `fn`.
|
|
pub struct DbgLocal {
|
|
pub mut:
|
|
fn string
|
|
name string
|
|
slot int
|
|
}
|
|
|
|
pub struct Bin {
|
|
pub mut:
|
|
fns []BinFn
|
|
strings []string
|
|
code []u8
|
|
lines []LineInfo
|
|
locals []DbgLocal
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// encoding helpers
|
|
|
|
pub fn encode_u32(v u32) []u8 {
|
|
return [u8(v & 0xff), u8((v >> 8) & 0xff), u8((v >> 16) & 0xff), u8((v >> 24) & 0xff)]
|
|
}
|
|
|
|
pub fn encode_i64(v i64) []u8 {
|
|
mut b := []u8{}
|
|
for i in 0..8 {
|
|
b << u8((v >> (8 * i)) & 0xff)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// encode_f64 writes a little-endian f64 (its IEEE-754 bit pattern).
|
|
pub fn encode_f64(v f64) []u8 {
|
|
bits := math.f64_bits(v)
|
|
mut b := []u8{}
|
|
for i in 0..8 {
|
|
b << u8((bits >> (8 * i)) & 0xff)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// patch_i64 writes a little-endian i64 over `code[off..off+8]`.
|
|
pub fn patch_i64(mut code []u8, off u32, v i64) {
|
|
for i in 0..8 {
|
|
code[off + u32(i)] = u8((v >> (8 * i)) & 0xff)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// reading
|
|
|
|
struct Reader {
|
|
mut:
|
|
b []u8
|
|
pos int
|
|
}
|
|
|
|
fn (mut r Reader) u8_() !u8 {
|
|
if r.pos >= r.b.len {
|
|
return error('object file truncated')
|
|
}
|
|
b := r.b[r.pos]
|
|
r.pos++
|
|
return b
|
|
}
|
|
|
|
fn (mut r Reader) u32_() !u32 {
|
|
mut v := u32(0)
|
|
for i in 0..4 {
|
|
b := r.u8_()!
|
|
v |= u32(b) << u32(8 * i)
|
|
}
|
|
return v
|
|
}
|
|
|
|
fn (mut r Reader) i64_() !i64 {
|
|
mut v := i64(0)
|
|
for i in 0..8 {
|
|
b := r.u8_()!
|
|
v |= i64(u64(b) << u32(8 * i))
|
|
}
|
|
return v
|
|
}
|
|
|
|
fn (mut r Reader) read_str() !string {
|
|
n := int(r.u32_()!)
|
|
if r.pos + n > r.b.len {
|
|
return error('object file truncated')
|
|
}
|
|
s := r.b[r.pos..r.pos + n].bytestr()
|
|
r.pos += n
|
|
return s
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// VROBJ
|
|
|
|
// format_version is 2 since v0.2: a debug-locals section was appended after
|
|
// the line table. Version 1 files (no locals) still read fine.
|
|
const format_version = u8(2)
|
|
|
|
pub fn write(path string, o Obj) ! {
|
|
mut b := []u8{}
|
|
b << magic.bytes()
|
|
b << format_version
|
|
b << encode_u32(u32(o.symbols.len))
|
|
for s in o.symbols {
|
|
b << encode_u32(u32(s.name.len))
|
|
b << s.name.bytes()
|
|
b << encode_i64(i64(s.entry))
|
|
}
|
|
b << encode_u32(u32(o.strings.len))
|
|
for s in o.strings {
|
|
b << encode_u32(u32(s.len))
|
|
b << s.bytes()
|
|
}
|
|
b << encode_u32(u32(o.code.len))
|
|
b << o.code
|
|
b << encode_u32(u32(o.relocs.len))
|
|
for r in o.relocs {
|
|
b << encode_u32(r.offset)
|
|
b << encode_u32(u32(r.name.len))
|
|
b << r.name.bytes()
|
|
b << r.kind
|
|
}
|
|
b << encode_u32(u32(o.lines.len))
|
|
for l in o.lines {
|
|
b << encode_u32(l.off)
|
|
b << encode_i64(i64(l.line))
|
|
}
|
|
b << encode_u32(u32(o.locals.len))
|
|
for l in o.locals {
|
|
b << encode_u32(u32(l.fn.len))
|
|
b << l.fn.bytes()
|
|
b << encode_u32(u32(l.name.len))
|
|
b << l.name.bytes()
|
|
b << encode_i64(i64(l.slot))
|
|
}
|
|
os.write_bytes(path, b)!
|
|
}
|
|
|
|
pub fn read(path string) !Obj {
|
|
b := os.read_bytes(path)!
|
|
if b.len < magic.len || b[0..magic.len].bytestr() != magic {
|
|
return error('not a VROBJ file: ${path}')
|
|
}
|
|
mut r := Reader{ b: b, pos: magic.len }
|
|
ver := r.u8_()! // format version
|
|
mut o := Obj{}
|
|
nsym := int(r.u32_()!)
|
|
for _ in 0..nsym {
|
|
name := r.read_str()!
|
|
entry := int(r.i64_()!)
|
|
o.symbols << Symbol{ name: name, entry: entry }
|
|
}
|
|
nstr := int(r.u32_()!)
|
|
for _ in 0..nstr {
|
|
o.strings << r.read_str()!
|
|
}
|
|
ncode := int(r.u32_()!)
|
|
if r.pos + ncode > b.len {
|
|
return error('object file truncated')
|
|
}
|
|
o.code = b[r.pos..r.pos + ncode]
|
|
r.pos += ncode
|
|
nrel := int(r.u32_()!)
|
|
for _ in 0..nrel {
|
|
off := r.u32_()!
|
|
name := r.read_str()!
|
|
kind := r.u8_()!
|
|
o.relocs << Reloc{ offset: off, name: name, kind: kind }
|
|
}
|
|
nlines := int(r.u32_()!)
|
|
for _ in 0..nlines {
|
|
off := r.u32_()!
|
|
line := int(r.i64_()!)
|
|
o.lines << LineInfo{ off: off, line: line }
|
|
}
|
|
if ver >= 2 {
|
|
nlocals := int(r.u32_()!)
|
|
for _ in 0..nlocals {
|
|
fn_name := r.read_str()!
|
|
name := r.read_str()!
|
|
slot := int(r.i64_()!)
|
|
o.locals << DbgLocal{ fn: fn_name, name: name, slot: slot }
|
|
}
|
|
}
|
|
return o
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// VRBIN
|
|
|
|
pub fn write_bin(path string, bin Bin) ! {
|
|
mut b := []u8{}
|
|
b << bin_magic.bytes()
|
|
b << format_version
|
|
b << encode_u32(u32(bin.fns.len))
|
|
for f in bin.fns {
|
|
b << encode_u32(u32(f.name.len))
|
|
b << f.name.bytes()
|
|
b << encode_i64(i64(f.entry))
|
|
}
|
|
b << encode_u32(u32(bin.strings.len))
|
|
for s in bin.strings {
|
|
b << encode_u32(u32(s.len))
|
|
b << s.bytes()
|
|
}
|
|
b << encode_u32(u32(bin.code.len))
|
|
b << bin.code
|
|
b << encode_u32(u32(bin.lines.len))
|
|
for l in bin.lines {
|
|
b << encode_u32(l.off)
|
|
b << encode_i64(i64(l.line))
|
|
}
|
|
b << encode_u32(u32(bin.locals.len))
|
|
for l in bin.locals {
|
|
b << encode_u32(u32(l.fn.len))
|
|
b << l.fn.bytes()
|
|
b << encode_u32(u32(l.name.len))
|
|
b << l.name.bytes()
|
|
b << encode_i64(i64(l.slot))
|
|
}
|
|
os.write_bytes(path, b)!
|
|
}
|
|
|
|
pub fn read_bin(path string) !Bin {
|
|
b := os.read_bytes(path)!
|
|
if b.len < bin_magic.len || b[0..bin_magic.len].bytestr() != bin_magic {
|
|
return error('not a VRBIN file: ${path}')
|
|
}
|
|
mut r := Reader{ b: b, pos: bin_magic.len }
|
|
ver := r.u8_()! // format version
|
|
mut bin := Bin{}
|
|
nfn := int(r.u32_()!)
|
|
for _ in 0..nfn {
|
|
name := r.read_str()!
|
|
entry := int(r.i64_()!)
|
|
bin.fns << BinFn{ name: name, entry: entry }
|
|
}
|
|
nstr := int(r.u32_()!)
|
|
for _ in 0..nstr {
|
|
bin.strings << r.read_str()!
|
|
}
|
|
ncode := int(r.u32_()!)
|
|
if r.pos + ncode > b.len {
|
|
return error('executable file truncated')
|
|
}
|
|
bin.code = b[r.pos..r.pos + ncode]
|
|
r.pos += ncode
|
|
nlines := int(r.u32_()!)
|
|
for _ in 0..nlines {
|
|
off := r.u32_()!
|
|
line := int(r.i64_()!)
|
|
bin.lines << LineInfo{ off: off, line: line }
|
|
}
|
|
if ver >= 2 {
|
|
nlocals := int(r.u32_()!)
|
|
for _ in 0..nlocals {
|
|
fn_name := r.read_str()!
|
|
name := r.read_str()!
|
|
slot := int(r.i64_()!)
|
|
bin.locals << DbgLocal{ fn: fn_name, name: name, slot: slot }
|
|
}
|
|
}
|
|
return bin
|
|
}
|