Skip to content

Latest commit

 

History

History
173 lines (130 loc) · 8.57 KB

File metadata and controls

173 lines (130 loc) · 8.57 KB

DiscoC Architecture

DiscoC is a small compiler toolkit for specialized hardware targets. The current code-generation backend targets the SuperFX/GSU processor; the repository also contains the initial target model for a future SPC-700 backend.

End-to-end pipeline

The normal object-file path is:

.dc source
    |
    v
Lexer -> Parser -> AST -> Analyzer -> Optimizer
                                      |
                                      v
                               IRLowerer
                                      |
                                      v
                               IRVerifier
                                      |
                                      v
                       target backend selection
                         /              \\
                        v                v
                 IRCodeGenerator   future SPC700 backend
                                      |
                                      v
                              relocatable .o
                                      |
                                      v
                               discld linker
                                      |
                                      v
                         linked target payload

The compiler also exposes two inspection or alternate-emission paths:

  • discc --emit-ast prints the optimized abstract syntax tree.
  • discc --emit-ir prints the verified IR and its basic blocks.
  • discc --emit-asm writes textual GSU assembly. That assembly can be passed to discas to create a relocatable object file.

The linked .bin is a GSU payload. It is not a complete SNES ROM image: it does not provide a SNES header, host-side startup integration, cartridge metadata, or other ROM-level resources.

Compiler stages

Lexer and parser

The lexer converts source text into tokens. The recursive-descent parser constructs an owning AST using std::unique_ptr for child nodes. Syntax errors are reported before semantic analysis begins.

AST optimization

The analyzer runs before AST transformations. This ordering guarantees that an optimization cannot erase an invalid expression before it receives a diagnostic. The optimizer then operates on resolved SymbolId references; current transformations include recognizing suitable loops for the GSU hardware LOOP instruction and simplifying selected small arithmetic operations. Transformations are deliberately conservative when a loop value is observable outside the loop or control flow can escape it.

Semantic analysis

The analyzer resolves functions, scopes, variables, structures, ROM data, types, pointer operations, and control-flow-related semantic rules. It also calculates stack offsets and local allocation sizes used by the backend.

Function prototypes may declare a function without defining its body. The prototype participates in semantic checking of calls in that compilation unit, while the definition can be emitted by another source file and linked later. Prototypes do not generate code or duplicate object-file symbols.

IR lowering and verification

IRLowerer converts the analyzed AST into a typed, control-flow-aware IR. IRVerifier checks structural invariants before code generation. This keeps target-independent compiler structure separate from GSU or SPC-700 byte encoding.

Target backends

The default object path uses IRCodeGenerator. It consumes only verified IR plus analyzed symbol/data information and emits GSU instructions into the project object format.

The SPC700Target model records the SPC-700 address width, memory-mapped regions, register roles, and initial return-value convention. It is a foundation-only target at present: --emit-ir can inspect programs selected for SPC-700, while object and assembly emission reject that target until its lowering and assembler stages exist.

Before emission, IRCodeGenerator runs a linear-scan allocation over the verified IRValueId live intervals. Reused values may reside in R5, R7, or R8; R0 remains the expression accumulator and R1/R3 remain backend temporaries. Values that do not fit, or are used only once, are rematerialized from their defining IR instruction. Calls preserve allocated live values while the existing stack-based argument ABI remains unchanged.

The AssemblyGenerator remains available for human-readable assembly export. It is useful for inspection and for the discas workflow, but it is a separate textual backend and should not be treated as the canonical implementation of every high-level feature.

Object and link stages

Each compilation unit can produce a relocatable .o file. The object stores code, ROM data, exported symbols, and relocation records. All multi-byte object fields use explicit little-endian encoding. discld verifies that all input objects use the same target configuration, concatenates code and data sections, resolves symbols, applies relocations, and writes the final payload. Local branches that exceed the short displacement range are relaxed by discas into object-relative absolute jumps.

The linker currently lays out all code before all data. Symbol addresses are calculated from the configured code start address and the accumulated section offsets.

For switches, constant selectors are lowered to a direct branch. Dynamic switches with four or more cases use a balanced comparison tree; smaller switches retain the compact linear form. Case blocks and fall-through edges remain explicit in the IR, so this is a dispatch optimization only.

GSU ABI conventions

The compiler targets the GSU instruction set and ABI described in Nintendo's official SNES Development Manual, Book II, Super FX section. The generated code follows the project's documented GSU conventions while keeping external assembly support optional.

The relevant register conventions used by the compiler are:

Register Convention
R0 expression result and first return-value register
R9 frame pointer used by generated functions
R10 / SP stack pointer
R11 link/return address register
R12 hardware-loop counter when a LOOP is emitted
R13 hardware-loop target register when required by setup
R14 ROM buffer/address register for ROM reads
R15 / PC program counter and call target register

Generated functions save the link and frame registers, establish R9 as the frame pointer, allocate aligned local storage, and restore the frame before returning. Parameters use positive frame-pointer offsets beginning at FP + 4; locals use negative offsets. Stack arguments are word-aligned.

The ABI classifies R9 and R11 as callee-preserved. R0, R1, R3, and the allocated value registers R5, R7, and R8 are caller-preserved; the caller saves any allocated value that remains live across a call. R10 is owned by the stack frame, R12/R13 are reserved for hardware loops, R14 is the ROM address register, and R15 is the program counter. Every argument occupies one aligned two-byte stack slot, including byte arguments.

Target configuration

The object format carries the target, memory mapping, and code start address. The supported target identifiers are GSU and SPC700; the supported SNES mappings are LoROM and HiROM. The linker rejects a set of input objects when their target configurations are incompatible.

Build-level target selection is provided on the command line, while source directives configure target-specific placement details:

set memory_mapping = lorom;
set code_start_address = 0x8000;
discc --target gsu program.dc -o program.o

The SPC-700 target model and ABI proposal are documented in spc700-target.md.

Ownership and stability model

AST nodes own their child nodes. IR graphs do not store pointers into resizable instruction or block vectors; values and blocks are referenced by stable numeric IDs owned by an IRFunction. The backend builds short-lived lookup tables while processing one function and does not make the IR own target byte buffers.

This separation is intentional: the AST and IR are compiler-phase data, while ObjectFile owns the emitted code/data vectors and serialized object contents.

Current boundaries

The project is pre-release compiler infrastructure. Register allocation is still conservative and uses rematerialization rather than explicit spill slots when register pressure exceeds the current pool. The assembly-export path also has narrower feature coverage than the IR binary backend for some advanced constructs. These limitations should be considered when using --emit-asm as a source of hand-edited assembly.