Rux Compiled Unit

Binary specification for the .rcu object files emitted by the Rux compiler. The same format is used on every supported operating system — FreeBSD, Linux, macOS, and Windows.

Overview

A Rux Compiled Unit (RCU) (.rcu) is the binary object file emitted by the Rux compiler for each compiled .rux source file. The Rux linker collects one or more RCU files and links them into a native executable for the target operating system — a PE (.exe) on Windows, an ELF binary on Linux and BSD, or a Mach-O binary on macOS.

ArtefactRole
.ruxRux source file (human-readable)
.rcuRux Compiled Unit — machine code, symbols, relocations, metadata
executableNative binary from the Rux linker — PE (.exe) on Windows, ELF on Linux/BSD, Mach-O on macOS

RCU is the object format for the x86-64 backend only. The AArch64 backend takes a separate route (C generated and compiled through a native Clang toolchain) and does not emit RCU.

Design Goals

GoalHow it is achieved
SimpleFixed-size header and section entries; no indirection chains; fewer tables than COFF or ELF
Fast to parseAll table offsets are stored directly in the header; the linker can seek straight to any structure
Linker-readySymbols, relocations, and string data are fully self-contained
Incremental-compilation–friendlyThe Rux Metadata block reserves a SHA-256 source-hash field and compiler version stamp so the build system can skip unchanged sources (see Incremental Compilation)

Comparison with Common Formats

FeatureCOFF (.obj)ELF (.o)RCU (.rcu)
Magic / identification2-byte machine type16-byte e_ident4-byte "RCU\0"
Section entry size40 bytes64 bytes (ELF64)40 bytes
Symbol entry size18 bytes24 bytes (ELF64)20 bytes
Relocation entry size10 bytes (no addend)24 bytes (ELF64 RELA)16 bytes
Source-language metadatanonoyes (optional block)
ChecksumnonoCRC-32C
Architecture scopemulti-architecturemulti-architecturex86-64 only

File Layout

An RCU file consists of six consecutive regions. All multi-byte integer fields are stored in little-endian byte order. Fields are placed at the exact offsets listed — there is no implicit struct padding.

┌──────────────────────────────────────────────┐  Offset 0
│                 File Header                  │  32 bytes (fixed)
├──────────────────────────────────────────────┤  Offset 32
│               Section Table                  │  section_count × 40 bytes
├──────────────────────────────────────────────┤  Offset 32 + section_count × 40
│               Symbol Table                   │  symbol_count × 20 bytes
├──────────────────────────────────────────────┤
│     Section Raw Data + Relocation Arrays     │  one block per section:
│       ┌──────────────────────────┐           │    raw data (aligned)
│       │  section[0] raw data     │           │    followed by relocation
│       │  section[0] relocations  │           │    entries (4-byte aligned)
│       │  section[1] raw data     │           │
│       │  section[1] relocations  │           │
│       │  ...                     │           │
│       └──────────────────────────┘           │
├──────────────────────────────────────────────┤
│               String Table                   │  string_table_size bytes
├──────────────────────────────────────────────┤
│          Rux Metadata  (optional)            │  64 bytes when present
└──────────────────────────────────────────────┘

Reading tip — a reader needs only the 32-byte File Header to locate every other structure. Parse the header first, then use the stored offsets and counts to seek directly to any region.

File Header

The File Header is always 32 bytes and begins at file offset 0.

OffsetSizeTypeFieldDescription
04u8[4]magicMagic bytes: 52 43 55 00 ("RCU\0")
42u16versionFormat version packed as (major << 8) | minor; v1.0 = 0x0100
61u8archTarget architecture — see Architecture
71u8flagsFile-level flags — see File Flags
82u16section_countNumber of entries in the Section Table
102u16_reservedMust be 0x0000
124u32symbol_countNumber of entries in the Symbol Table
164u32string_table_offFile offset to the first byte of the String Table
204u32string_table_sizeByte size of the String Table
244u32metadata_offsetFile offset to the Rux Metadata block; 0 = not present
284u32checksumCRC-32C of the whole file with this field treated as 0; 0 = validation disabled

Architecture

ValueConstantMeaning
0x01ARCH_X86_64x86-64 (AMD64), little-endian

The arch field encodes only the processor architecture, not the operating system or ABI. A single RCU file can therefore be linked on any supported OS; the linker chooses the executable format (PE, ELF, or Mach-O) and the calling convention (Win64 or System V AMD64) for the target. Additional architectures may be assigned in future minor versions.

File Flags

Bits 1–7 are reserved and must be 0.

BitConstantSet when…
0F_HAS_METADATAThe Rux Metadata block is present at metadata_offset

Version Compatibility

A reader must reject a file whose magic bytes do not match "RCU\0". A reader should reject a file whose version major byte is greater than the highest major version it supports. Minor version differences within the same major version are backward-compatible.

Section Table

The Section Table begins immediately at file offset 32 and contains section_count entries of 40 bytes each.

OffsetSizeTypeFieldDescription
08char[8]nameSection name, null-padded ASCII (e.g. ".text\0\0\0")
84u32typeSection type — see Section Types
124u32flagsSection flags — see Section Flags
164u32raw_offsetFile offset to section raw data; 0 for SEC_BSS
204u32raw_sizeByte size of raw data stored in the file; 0 for SEC_BSS
244u32virtual_sizeByte size the section occupies in memory; equals raw_size for non-empty sections; 1 when raw_size is 0
282u16alignmentRequired alignment in bytes; must be a power of two in the range 1–4096
302u16reloc_countNumber of relocation entries for this section
324u32reloc_offsetFile offset to this section's relocation array; 0 if reloc_count == 0
364u32_reservedMust be 0x00000000

Section Types

ValueConstantDescription
0SEC_NULLUnused placeholder entry
1SEC_TEXTExecutable machine code
2SEC_DATAInitialized read-write data
3SEC_RODATARead-only data: string literals, float constants, jump tables
4SEC_BSSZero-initialized data; no raw bytes are stored in the file
5SEC_METARux-specific metadata (at most one per file)

Section Flags

BitConstantMeaning
0SF_ALLOCSection occupies memory at run time
1SF_EXECSection is executable
2SF_READSection is readable
3SF_WRITESection is writable
4SF_MERGEThe linker may merge identical entries from multiple units
5SF_STRINGSSection contains null-terminated strings (implies SF_MERGE)

Standard Sections

The Rux compiler always emits exactly three sections in fixed order. User code never names sections explicitly; the compiler assigns data to the appropriate section automatically.

IndexNameTypeFlagsAlignmentContents
0.textSEC_TEXTSF_ALLOC | SF_EXEC | SF_READ16Function machine code
1.rodataSEC_RODATASF_ALLOC | SF_READ8String literals, float constants
2.dataSEC_DATASF_ALLOC | SF_READ | SF_WRITE8Initialized global variables

Sections with no generated content are written with raw_size = 0; their virtual_size is 1.

Symbol Table

The Symbol Table begins immediately after the last Section Table entry.

File offset = 32 + section_count × 40

Each entry is 20 bytes.

OffsetSizeTypeFieldDescription
04u32name_offByte offset into the String Table for the symbol name
44u32valueByte offset of the symbol within its section; absolute value when section_idx == 0xFFFE
84u32sizeSymbol size in bytes; 0 if unknown
122u16section_idxZero-based index of the owning section; 0xFFFF = external (undefined); 0xFFFE = absolute constant
141u8kindSymbol kind — see Symbol Kinds
151u8visibilitySymbol visibility — see Symbol Visibility
164u32type_name_offString Table offset for the Rux type string (e.g. "fn(i32, i32) -> i32"); 0 = not recorded

Symbol Kinds

ValueConstantDescription
0SYM_UNKNOWNUnclassified symbol
1SYM_FUNCFunction defined in this unit
2SYM_DATAMutable global variable defined in this unit
3SYM_CONSTRead-only constant defined in this unit
4SYM_SECTIONSection symbol (one per section; name equals the section name)
5SYM_FILESource file name (informational; section_idx = 0xFFFE, value = 0)
6SYM_EXTERN_FUNCExternal function referenced by this unit but defined elsewhere
7SYM_EXTERN_DATAExternal data referenced by this unit but defined elsewhere

External symbols (SYM_EXTERN_FUNC, SYM_EXTERN_DATA) always have section_idx = 0xFFFF and value = 0.

Symbol Visibility

ValueConstantDescription
0VIS_LOCALVisible only within this unit; not exported to the linker
1VIS_GLOBALExported; the linker requires exactly one definition across all linked units
2VIS_WEAKExported; a VIS_GLOBAL definition from another unit silently overrides this one

Relocation Table

Each section with fix-ups has its own relocation array. A section's array starts at section.reloc_offset and contains section.reloc_count entries of 16 bytes each.

OffsetSizeTypeFieldDescription
04u32section_offsetByte offset within the owning section where the fix-up placeholder begins
44u32symbol_indexZero-based index into the Symbol Table of the target symbol
82u16typeRelocation type — see Relocation Types
102u16_reservedMust be 0x0000
124i32addendSigned constant added to the resolved symbol value during fix-up

Relocation Types

ValueConstantPlaceholder widthLinker formulaTypical use
0REL_NONEno-opAlignment padding
1REL_ABS6464-bitsym + addend64-bit pointer in .data / .rodata
2REL_ABS3232-bit(u32)(sym + addend)32-bit absolute value in the low 4 GiB
3REL_REL3232-bit(i32)(sym + addend − (patch_va + 4))CALL rel32, JMP rel32, [rip + disp32]

Variables used in the formulas:

  • sym — virtual address assigned to the target symbol by the linker.
  • patch_va — virtual address of the first byte of the placeholder field (section_virtual_address + section_offset).
  • addend — the signed value stored in the relocation entry.

REL_REL32 — PC-Relative 32-bit Fix-up

REL_REL32 is the most common relocation type for x86-64 code. The assembler emits four zero bytes as a placeholder. The linker replaces them with a signed 32-bit integer according to:

patch = (i32)(sym + addend − (patch_va + 4))

The + 4 in the denominator accounts for the processor advancing RIP past the 4-byte placeholder before adding the displacement.

With addend = 0 (the default emitted by the Rux compiler) the formula covers all standard x86-64 position-relative encodings:

InstructionOpcode prefixPlaceholder at offsetNotes
CALL symE8 (1 byte)instr + 1Near call
JMP symE9 (1 byte)instr + 1Near unconditional jump
Jcc sym0F 8x (2 bytes)instr + 2Conditional jumps
LEA rax, [rip + sym]48 8D 05 (3 bytes)instr + 3RIP-relative address load
MOV rax, [rip + sym]48 8B 05 (3 bytes)instr + 3RIP-relative memory load

String Table

The String Table is a flat array of null-terminated UTF-8 strings packed consecutively. Byte offset 0 is always \0 — the empty string sentinel. All string-offset fields in other structures are byte offsets into this array.

Offset   Content
 0       \0                          ← always: empty sentinel
 1       "add\0"
 5       "main\0"
10       "fn(i32, i32) -> i32\0"
30       "-> i32\0"
37       "math.rux\0"
46       ...

Strings are encoded in UTF-8. The Rux compiler guarantees that symbol names and section names contain only printable ASCII characters. Source file paths and Rux type strings may contain arbitrary UTF-8.

Rux Metadata

When header.flags & F_HAS_METADATA is set, a 64-byte Rux Metadata block is present at header.metadata_offset. This block is specific to Rux and carries information used by the build system for incremental compilation and by tooling for diagnostics and introspection.

The current Rux compiler emits this block on every unit and always sets F_HAS_METADATA. The block remains optional in the format, so readers must still honour the flag rather than assuming its presence.

OffsetSizeTypeFieldDescription
04u8[4]magicBlock magic: 4D 45 54 41 ("META")
44u32block_sizeTotal size of this block in bytes (currently 64)
84u32source_path_offString Table offset for the source file path (e.g. "src/math.rux")
124u32package_name_offString Table offset for the Rux package name
168u64build_timestampUnix seconds since epoch at compile time
244u32rux_versionRux language version: (major << 16) | (minor << 8) | patch
284u32compiler_flagsCompiler option flags — see Compiler Flags
3232u8[32]source_hashSHA-256 digest of the source .rux file; all-zero bytes if unavailable

Current implementation — the compiler fills in source_path_off, package_name_off, build_timestamp, and rux_version, but does not yet populate source_hash (written as 32 zero bytes) or compiler_flags (written as 0). Both fields are reserved for the incremental-compilation and tooling features below, and the block is emitted now so they can be enabled without a format change.

Compiler Flags

Bits 3–31 are reserved and must be 0.

BitConstantSet when…
0CF_DEBUGCompiled with -g / debug information enabled
1CF_OPTIMIZEDCompiled with optimizations enabled
2CF_TESTCompiled in test mode (rux test)

Alignment and Padding Rules

  1. Section Table — begins at file offset 32 with no preceding padding.
  2. Symbol Table — begins immediately after the last Section Table entry with no padding (32 + section_count × 40).
  3. Section raw data — must begin at a file offset that satisfies offset mod alignment == 0 where alignment is the section's declared alignment. Insert zero-fill padding between consecutive sections as necessary.
  4. Relocation arrays — must begin at a 4-byte-aligned file offset. Insert zero-fill padding after section raw data if needed before the relocation array.
  5. String Table — byte-aligned; no special alignment required.
  6. Rux Metadata block — must begin at an 8-byte-aligned file offset. Insert zero-fill padding after the String Table if needed.

Unused bytes in any padding region must be set to 0x00.

Example: Two-Function Unit

This section shows a complete RCU for the following source file.

// Math.rux
func Add(a: int32, b: int32) -> int32 {
    return a + b;
}

func Main() -> int32 {
    return Add(3, 4);
}

Machine Code — .text (31 bytes)

Calling convention — this listing uses the System V AMD64 ABI (Linux, BSD, macOS), where the first two integer arguments arrive in edi/esi. On Windows (Win64) they would arrive in ecx/edx with 32 bytes of shadow space, so the exact instruction bytes differ per target — the RCU structures around the code do not.

; Add — section offset 0, size 10 bytes
Offset  Bytes             Disassembly
  0     55                push  rbp
  1     48 89 E5          mov   rbp, rsp
  4     01 F7             add   edi, esi        ; b into a (i32)
  6     89 F8             mov   eax, edi        ; return value
  8     5D                pop   rbp
  9     C3                ret

; Main — section offset 10, size 21 bytes
Offset  Bytes             Disassembly
 10     55                push  rbp
 11     48 89 E5          mov   rbp, rsp
 14     BF 03 00 00 00    mov   edi, 3          ; first argument
 19     BE 04 00 00 00    mov   esi, 4          ; second argument
 24     E8 00 00 00 00    call  Add             ; ← REL_REL32 relocation at offset 25
 29     5D                pop   rbp
 30     C3                ret

The four zero bytes at offsets 25–28 are the REL_REL32 placeholder for the call Add instruction.

Symbol Table (2 entries)

Indexname_offvaluesizesection_idxkindvisibilitytype_name_off
01 ("Add")0100 (.text)SYM_FUNC (1)VIS_GLOBAL (1)10 ("fn(i32, i32) -> i32")
15 ("Main")10210 (.text)SYM_FUNC (1)VIS_GLOBAL (1)30 ("-> i32")

Relocation Table for .text (1 entry)

section_offsetsymbol_indextypeaddendExplanation
250 (Add)3 (REL_REL32)0Fix up 4-byte placeholder at .text[25..28] for call Add

Linker verification — assuming .text is placed at virtual address 0x1000:

  • Add_va = 0x1000 + 0 = 0x1000
  • patch_va = 0x1000 + 25 = 0x1019
  • patch = (i32)(0x1000 + 0 − (0x1019 + 4)) = (i32)(0x1000 − 0x101D) = −29 = 0xFFFFFFE3
  • CPU executes E8 E3 FF FF FF → jumps to 0x101D + (−29) = 0x1000

String Table (37 bytes)

Off   Hex                                           ASCII
 0    00                                            \0 (sentinel)
 1    41 64 64 00                                   "Add\0"
 5    4D 61 69 6E 00                                "Main\0"
10    66 6E 28 69 33 32 2C 20 69 33 32 29 20        "fn(i32, i32) -> i32\0"
      2D 3E 20 69 33 32 00
30    2D 3E 20 69 33 32 00                          "-> i32\0"

File Layout Summary

The compiler always emits three sections. .rodata and .data are empty here (no string literals or global variables), so their raw_size = 0 and virtual_size = 1.

File offset   Region                       Size
  0x0000      File Header                  32 bytes
  0x0020      Section Table  (3 entries)   120 bytes
  0x0098      Symbol Table   (2 entries)   40 bytes
  0x00C0      .text raw data               31 bytes  (alignment 16 → offset 0xC0 = 192, 192 % 16 = 0 ✓)
              [1 byte zero padding]
  0x00E0      .text relocations (1 entry)  16 bytes  (alignment 4  → offset 0xE0 = 224, 224 % 4  = 0 ✓)
  0x00F0      .rodata raw data              0 bytes  (alignment 8  → offset 0xF0 = 240, 240 % 8  = 0 ✓)
  0x00F0      .data raw data                0 bytes  (alignment 8  → offset 0xF0 = 240, no padding)
  0x00F0      String Table                 37 bytes
              [3 bytes zero padding for 8-byte alignment]
  0x0118      Rux Metadata                 64 bytes  (alignment 8  → offset 0x118 = 280, 280 % 8  = 0 ✓)
  0x0158      End of file

File Header Bytes

Offset  Bytes                           Field
  0     52 43 55 00                     magic         "RCU\0"
  4     01 00                           version       0x0100 → v1.0
  6     01                              arch          0x01 → x86-64
  7     01                              flags         F_HAS_METADATA
  8     03 00                           section_count 3
 10     00 00                           _reserved
 12     02 00 00 00                     symbol_count  2
 16     F0 00 00 00                     string_table_off  0x00F0
 20     25 00 00 00                     string_table_size 37
 24     18 01 00 00                     metadata_offset   0x0118
 28     xx xx xx xx                     checksum      (CRC-32C computed last)

Incremental Compilation

The Rux Metadata block is designed to let the build system avoid recompiling unchanged source files:

  1. Source hash check — compute the SHA-256 of the .rux file and compare it to source_hash in the metadata. If they match, the source has not changed since the last compilation.
  2. Compiler version check — if the installed compiler's rux_version differs from the value stored in the metadata, the file must be recompiled regardless of the hash, because language semantics or code-generation may have changed.

If both checks pass the existing .rcu is used directly; the source file is not read again.

Status — this describes the intended design. The current compiler writes an all-zero source_hash, so the hash-based skip is not yet active. The metadata block is already emitted on every unit, so the mechanism can be turned on later without changing the file format.

Checksum — CRC-32C

The checksum field holds a CRC-32C (Castagnoli polynomial 0x1EDC6F41) computed over the entire file content with the four bytes at offset 28 treated as 0x00000000.

Writers must compute and store the checksum after writing all other fields. Readers may skip validation by checking for checksum == 0; a stored checksum of 0 disables checking.

Implementation Notes

Reading an RCU File

  1. Read 32 bytes from offset 0 → File Header.
  2. Verify magic == 0x52, 0x43, 0x55, 0x00.
  3. Verify version major byte <= supported major version.
  4. Read section_count × 40 bytes at offset 32 → Section Table.
  5. Read symbol_count × 20 bytes at offset (32 + section_count × 40) → Symbol Table.
  6. Read string_table_size bytes at string_table_off → String Table.
  7. For each section entry: a. If raw_size > 0, read raw_size bytes at raw_offset → section data. b. If reloc_count > 0, read reloc_count × 16 bytes at reloc_offset → relocations.
  8. If flags & F_HAS_METADATA, read 64 bytes at metadata_offset → Rux Metadata. Verify metadata magic == 0x4D, 0x45, 0x54, 0x41.
  9. If header.checksum != 0, validate CRC-32C.

Writing an RCU File

  1. Assemble sections, symbols, relocations, and strings from the code-generation stage.
  2. Intern all strings (names, type strings, paths) into the String Table.
  3. Compute section_count, symbol_count, and all file offsets (write 0 into header.checksum as a placeholder).
  4. Write regions in order: File Header → Section Table → Symbol Table → for each section: raw data (aligned) + relocation array (4-byte aligned) → String Table (no alignment) → Rux Metadata (8-byte aligned, optional).
  5. Compute CRC-32C over the complete file content (treating offset 28 as zero).
  6. Seek to offset 28 and write the 4-byte checksum.

Linker Responsibilities

The Rux linker processes a set of RCU files to produce a native executable for the target operating system:

  1. Collect and merge all same-named sections (.text, .rodata, .data, .bss).
  2. Resolve all symbols: pair each SYM_EXTERN_* reference with its VIS_GLOBAL or VIS_WEAK definition; report an error for any unresolved symbol.
  3. Assign virtual addresses to all merged sections.
  4. Apply all relocations using the formulas in Relocation Types.
  5. Emit an executable in the target's native format — PE/COFF on Windows, ELF on Linux and BSD, or Mach-O on macOS — with the appropriate headers, an import table for external symbols (for example a PE .idata section for Windows API calls, or dynamic-linking entries on ELF and Mach-O), and the merged section data.