To compile a file to disk, we need a format in which to write the compiled code to disk, and later load it into Guile. A good object file format has a number of characteristics:
These characteristics are not specific to Scheme. Indeed, mainstream languages like C and C++ have solved this issue many times in the past. Guile builds on their work by adopting ELF, the object file format of GNU and other Unix-like systems, as its object file format. Although Guile uses ELF on all platforms, we do not use platform support for ELF. Guile implements its own linker and loader. The advantage of using ELF is not sharing code, but sharing ideas. ELF is simply a well-designed object file format.
An ELF file has two meta-tables describing its contents. The first meta-table is for the loader, and is called the program table or sometimes the segment table. The program table divides the file into big chunks that should be treated differently by the loader. Mostly the difference between these segments is their permissions.
Typically all segments of an ELF file are marked as read-only, except that part that represents modifiable static data or static data that needs load-time initialization. Loading an ELF file is as simple as mmapping the thing into memory with read-only permissions, then using the segment table to mark a small sub-region of the file as writable. This writable section is typically added to the root set of the garbage collector as well.
One ELF segment is marked as “dynamic”, meaning that it has data of
interest to the loader. Guile uses this segment to record the Guile
version corresponding to this file. There is also an entry in the
dynamic segment that points to the address of an initialization thunk
that is run to perform any needed link-time initialization. (This is
like dynamic relocations for normal ELF shared objects, except that we
compile the relocations as a procedure instead of having the loader
interpret a table of relocations.) Finally, the dynamic segment marks
the location of the “entry thunk” of the object file. This thunk is
returned to the caller of load-thunk-from-memory
or
load-thunk-from-file
. When called, it will execute the “body”
of the compiled expression.
The other meta-table in an ELF file is the section table. Whereas the program table divides an ELF file into big chunks for the loader, the section table specifies small sections for use by introspective tools like debuggers or the like. One segment (program table entry) typically contains many sections. There may be sections outside of any segment, as well.
Typical sections in a Guile .go
file include:
.rtl-text
Bytecode.
.data
Data that needs initialization, or which may be modified at runtime.
.rodata
Statically allocated data that needs no run-time initialization, and which therefore can be shared between processes.
.dynamic
The dynamic section, discussed above.
.symtab
.strtab
A table mapping addresses in the .rtl-text
to procedure names.
.strtab
is used by .symtab
.
.guile.procprops
.guile.arities
.guile.arities.strtab
.guile.docstrs
.guile.docstrs.strtab
Side tables of procedure properties, arities, and docstrings.
.guile.docstrs.strtab
Side table of frame maps, describing the set of live slots for ever return point in the program text, and whether those slots are pointers are not. Used by the garbage collector.
.debug_info
.debug_abbrev
.debug_str
.debug_loc
.debug_line
Debugging information, in DWARF format. See the DWARF specification, for more information.
.shstrtab
Section name string table.
For more information, see the
elf(5) man page. See the DWARF
specification for more on the DWARF debugging format. Or if you are an
adventurous explorer, try running readelf
or objdump
on
compiled .go
files. It’s good times!