DiscoC is a freestanding systems programming language and compiler, assembler, and linker for specialized hardware. Its C-like syntax is paired with explicit rules for small integers, banked memory, hardware access, and target capabilities. It is not an ISO C implementation.
The toolchain is written in C++23, with a C++14-compatible experimental DOS cross-build. The executable backend targets the SNES SuperFX (GSU); SPC700 currently provides a frontend and IR foundation.
Status: v0.1.0 baseline / Experimental Correctness and explicit language contracts take priority over speed. GSU compilation, assembly, linking, and runtime regressions are implemented, but this is not a production-ready compiler. The current language is frozen as DiscoC Language Baseline 0.1. Syntax, ABI and object format may change before 1.0; optimization is immature and generated code is not yet expected to be optimal. A linked
.binis a GSU payload, not a complete SNES ROM.
The 0.1 stabilization contract and release-readiness record define baseline acceptance and compatibility limits. The first baseline release does not depend on further optimization or executable SPC700 support.
DiscoC 0.1.0 establishes the first documented and tested language/toolchain baseline, with an end-to-end functional SuperFX target.
See the release notes for compatibility limits and the v0.1.0 release for packaged tools, documentation and examples. The release remains experimental; SPC700 is a frontend/IR foundation, not a supported executable target.
| Target | Current support |
|---|---|
gsu / superfx |
Source compilation, assembly, relocatable objects, linking, and ROM/RAM execution payloads. |
spc700 |
Language analysis, target-capability diagnostics, and verified IR inspection; no machine-code emission yet. |
Select the target through the CLI or discoc.toml. Hardware-specific features
are capability-checked rather than treated as portable language operations.
The Baseline 0.1 language specification is the source of truth for parser, analyzer, IR, backend and examples:
- Small, predictable values: signed-by-default
byteandword,unsignedvariants, byte-sizedbool, and transparenttypealiases includingi8,u8,i16, andu16. - Deliberate numeric rules: modular overflow for signed and unsigned integers,
explicit narrowing and signedness conversions, boolean comparisons,
short-circuit logic, bitwise operators, checked shifts, and software
/and%on GSU. - Independent access properties:
rom/ramspecify address space,constrestricts modification, andvolatilepreserves observable memory accesses through the IR and backend. - Bank-aware data pointers: 16-bit near pointers and four-byte far
descriptors, pointer arithmetic, indexing,
->,null, and checked casts. Far accesses restore the configured near data bank. Typed word accesses require two-byte alignment. - Memory-resident aggregates: structs, arrays of structs/pointers, initializer lists, and character/string literals. Unsupported aggregate-by-value operations are diagnosed instead of silently using an incompatible ABI.
- Static storage and visibility: mutable RAM globals, ROM data, explicit
initialization, and
internal/export/externlinkage. - Compile-time tools:
constexpr, enums, constant-expression array sizes and case labels,sizeof,alignof,offsetof, andstatic_assert. - Structured control flow: compound assignments,
++/--, realforloops with correctcontinuebehavior, and explicitfallthrough;annotations. - Stateful SuperFX graphics: lexical
plot { ... }blocks,cursor.x/cursor.y,color,pixel,read_pixel, and cache-flushingflush. PLOT advances X in hardware; direct ROM byte colors can use GETC, while RAM and computed colors use COLOR. Symbolic options and bitmap declarations keep plot state separate from SNES host screen configuration. - Attributes and modules:
@cache,@packed,@align(N),@target(...), and top-level@cfg(gsu)/@cfg(spc700)selection.import "code.dc";shares public declarations without copying definitions; project builds compile discovered dependencies once in separate objects. Declaration-only.dciinterfaces remain supported.@...is for attributes, not imports or a C textual preprocessor.
Warnings cover shadowing, unused declarations, uninitialized reads, unreachable
code, and implicit fallthrough. -Wall also enables expensive-helper warnings;
-Werror turns enabled warnings into errors.
discccompiles one source file or builds a multi-file project fromdiscoc.toml.--checkchecks the frontend/IR without emitting objects;--emit-irprints the verified IR.discasassembles DiscoC's ca65-like assembly dialect into relocatable objects.discldresolves symbols, validates placement and relocations, and produces a fixed-origin payload with optional runtime initialization and final assembly export.
The canonical backend uses typed IR and a control-flow graph, stable resolved
symbol IDs, a dedicated IR verifier, and linear-scan register allocation.
GSU lowering includes hardware-loop selection, switch dispatch optimization,
and branch relaxation. The ABI uses R9 as frame pointer and R10 as stack
pointer, with regression coverage for nested calls and stack restoration.
Objects use a versioned little-endian format with CODE/DATA/RAM images, visibility, relocations, and alignment metadata. The reader and linker reject malformed records, invalid relocation spans, and unsupported bank crossings. Rebuild the tools together when the object format changes.
Install a C++23-capable compiler. CMake is the primary build system; the helpers also support direct GCC/Clang builds. With CMake 3.21+ and Ninja installed, the same happy path works on Linux, macOS and a Windows MSVC Developer shell:
cmake --preset release
cmake --build --preset release
ctest --preset releaseThe tools are build/release/bin/discc, discas, and discld (with .exe on
Windows). Windows MinGW users can select the standalone, statically linked
runtime preset instead:
cmake --preset release-mingw
cmake --build --preset release-mingw
ctest --preset release-mingwIts tools are in build/release-mingw/bin. debug and debug-mingw presets
use separate build directories. Add the chosen bin directory to PATH for
your terminal session, then discc build discovers the current directory's
discoc.toml. Compiler and project output directories are separate.
Build helpers remain available:
# Windows: build the tools and run the tests
.\build.cmd -Test
# Direct GCC build, without configuring a CMake project
.\build.cmd -Backend Direct -Compiler g++# Linux/macOS: build the tools and run the tests
bash build.sh --test
# Alternatives
make test
bash build.sh --backend direct --compiler clang++The default helper build directory is build/<backend>-<configuration>, such as
build/cmake-Release; its executables are in bin/. CMake, Visual Studio and
direct GCC/Clang builds use the same <build-dir>/bin convention.
Manual CMake builds remain supported:
git clone https://github.com/DiscoLabOfficial/DiscoC.git
cd DiscoC
cmake -S . -B build/native -DCMAKE_BUILD_TYPE=Release
cmake --build build/native --config Release --parallel
ctest --test-dir build/native -C Release --output-on-failureSee Building DiscoC for prerequisites, MinGW/MSVC selection,
static linking, direct-build tests, and the retained experimental DOS
cross-build. Use a fresh build directory when changing compilers or generators.
An optional local cmake --install build/release --prefix <directory> installs
only the three tools into <directory>/bin; it does not publish a release.
Create these two source files. This example writes 42 to a host-agreed RAM
address rather than treating registers after STOP as a result API.
// main.dc
import "math.dc";
void main() {
volatile word* output = (volatile word*)0x0100;
*output = add(30, 12);
}// math.dc
word add(word a, word b) {
return a + b;
}Place discoc.toml alongside them:
[project]
name = "multifile"
target = "gsu"
sources = ["main.dc"]
[target.gsu]
memory_mapping = "lorom"
execution_memory = "ram"
origin = 0x700900
ram_bank = 0x00
stack_pointer = 0x2000
[runtime]
initialize = true
[output]
directory = "build"
binary = "multifile.bin"
assembly = "final.s"Then run:
discc build
# Equivalent explicit selection:
discc --project discoc.tomlThe import discovers math.dc; it does not paste its function body into
main.dc. Each source is parsed/compiled once per build. Symbols are public by
default; export makes that explicit, and internal hides helpers and data.
This produces build/0-main.o, build/1-math.o,
build/multifile.bin, and build/final.s. The result is stored at
$70:0100; load the payload at $70:0900 and enter there with the host setup
described below.
To build the repository's original multi-file example from the repository root:
discc build --config examples/multifile/discoc.tomlConfiguration precedence is built-in defaults → manifest → explicit CLI
options. Manifest paths resolve relative to the manifest, not the terminal's
working directory. Root sources and outputs are explicit, while .dc imports
discover source dependencies. This is not a package manager or an incremental
build system.
Imports search the current source directory first, then configured directories:
[compiler]
import_paths = ["src", "lib"]These paths are manifest-relative. Repeat --import-path DIR (or -I DIR) to
replace the configured list with CLI-relative directories. See the
source-import example for shared dependencies,
imported types/constants, and RAM/ROM data.
See Project manifests for the supported TOML subset, schema, output-path rules, CLI overrides, and frontend-only project checks.
The separate compile/link workflow remains available for the same source files:
discc main.dc -o main.o
discc math.dc -o math.o
discld main.o math.o --origin 0x700900 --init-runtime \
--ram-bank 0 --stack-pointer 0x2000 --emit-asm final.s -o multifile.binOrdinary discc main.dc checks its import graph but emits only main.o; compile
and link each dependency separately, or use discc build to do that automatically.
There are two distinct assembly exports:
# Relocatable assembly for one compilation unit
discc main.dc --emit-asm -o main.s
discas main.s -o main.o
# Inspect target-independent IR, or check language rules only
discc main.dc --emit-ir
discc --check -Wall -Werror main.dc
# SPC700 currently supports frontend/IR workflows only
discc --target spc700 --check main.dcCompiler assembly preserves symbols and relocations. Without edits, assembling and linking it with the same options produces the same bytes as direct object compilation.
discld --emit-asm final.s, or discc build --emit-asm final.s, exports the
final linked payload, including startup and resolved addresses. Reassemble
it without adding startup or changing its origin:
discas final.s -o reconstructed.o
discld reconstructed.o -o reconstructed.binreconstructed.bin matches the linked payload byte for byte. This is DiscoC
assembly, not native WLA-DX syntax; .byte directives preserve encodings when
needed.
LoROM with ROM execution at $00:8000 is the default. HiROM remains an explicit
--memory-mapping hirom choice, independent of RAM/ROM execution.
Placement belongs in the CLI or manifest, not source-level set directives.
For the quick-start RAM payload, the host must copy it to $70:0900, set
PBR=$70 and R15=$0900, grant the required bus access, and configure the
other SNES/GSU registers. The bootstrap selects near RAM/stack bank $70,
initializes R10, and enters main. After normal completion, the host can
read the word at $70:0100.
The binary is fixed-origin, not position-independent. Storing it with
WLA-DX .incbin does not relocate it. Relink to change its execution address;
the complete payload must fit one accessible program bank.
Mutable globals begin at near RAM offset $0400 by default; --ram-origin
changes that placement. Use --init-runtime for static initialization, or
explicitly accept the host-owned contract with --host-initialized-globals.
Without runtime initialization, the host also owns the initial stack/data-bank
setup.
Far data accesses do not implement interbank code calls. RAM execution does not yet provide separate ROM placement for ROM-qualified constants. The host remains responsible for real cartridge capacity, memory reservations, stack space, and valid ROM integration. See GSU loading and startup.
DiscoC can calculate and draw a bitmap using the GSU's real plotting state. This triangle was compiled from the official example, copied to cartridge RAM, and displayed by a complete SNES test ROM in Mesen:
The triangle has 97 scanlines and 9,409 pixels. Its edges and color bands are calculated in DiscoC, not supplied as a pre-rendered image. The accompanying SNES host and Mesen checks verify the framebuffer, VRAM transfer, tilemap, and CPU-read result after STOP.
plot { ... } exposes a stateful cursor and color rather than independent
draw calls:
plot {
options transparent;
color 5;
at (10, 20); // cursor.x = 10; cursor.y = 20;
for (word i = 0; i < 16; i++) {
pixel; // Draw at the cursor; hardware advances X by one.
}
byte c = read_pixel at (10, 20);
flush;
}cursor.xandcursor.ymap to GSU R1 and R2. Assign them directly or useat (x, y);to set both; values persist across pixels and loop iterations.color expr;updates COLR, the current color index. Immediate, computed, and RAM-backed values use COLOR. An eligible direct ROM byte read can use GETC with the required ROM bank, R14, and ROM-buffer setup.pixel;emits PLOT. It draws using the current cursor and COLR, then increments X in hardware; no extra R1 increment is emitted. Y is unchanged.read_pixeluses RPIX to flush pixel caches and read a logical color index. It does not advance X.flush;uses the same instruction with its result discarded and is preserved even when no value is needed.optionsacceptstransparent,dither,high_nibble,freeze_high, andobject;options;clears them.draw at (x, y) with color c;is convenient sugar for setting color and cursor, then drawing one pixel.
Bitmap declarations describe the framebuffer separately from the plotting state:
bitmap main_screen {
mode bitmap;
size 256x192;
depth 4bpp;
base 0x0000;
}
// In a function, before drawing:
// use bitmap main_screen;Bitmap modes support 256x128, 256x160, or 256x192 at 2/4/8bpp. OBJ profiles use
mode obj; without a bitmap size. The compiler checks combinations and base
alignment; the SNES host still owns SCMR/SCBR setup, bus access, CGRAM, and PPU
display. A bitmap declaration does not initialize those registers automatically.
To compile the pictured RAM triangle, with the DiscoC tools on PATH:
discc build --config examples/snes/triangle/discoc.toml
# With WLA-DX installed, build the complete SNES ROM:
cmake -P examples/snes/triangle/build-snes.cmake
# Additionally verify all pixels, VRAM/tilemap and the negative ROM in Mesen:
cmake -DVERIFY_MESEN=ON -P examples/snes/triangle/build-snes.cmakeThis .bin is a fixed-origin payload, not a standalone SNES ROM. The example's
portable build script assembles the complete host ROM with WLA-DX and optionally
verifies it in Mesen. Outputs go to build/snes-triangle; the host consumes
origin/SCBR/SCMR from the final linked export. See
the triangle build instructions
for that workflow and SuperFX graphics for the full contract.
From DiscoC source to a running SuperFX animation: this demo calculates both the rotating triangle and its black-and-white checkerboard using integer fixed-point math. There are no pre-rendered frames, and the SNES CPU only selects the angle and transfers the completed GSU framebuffer during VBlank.
Real Mesen captures, replayed at 8 fps. Playback is accelerated; the ROM is a correctness demonstration, not yet a smooth-animation benchmark.
Here is the actual drawing loop from the demo's plot block. Scanline clipping
provides left and right; sine and cosine are Q6 rotation coefficients.
Sampling local coordinates u and v makes the checker pattern rotate with
the triangle:
word u = left * cosine + dy * sine;
word v = -left * sine + dy * cosine;
at (128 + left, y);
@cache
for (word x = left; x <= right; x++) {
// Bit 9 of Q6 is an eight-pixel checker cell. Sampling
// local u/v rotates the pattern with the geometry.
word shade = 1 + (((u ^ v) & 512) >> 9);
color shade;
white += shade - 1;
black += 2 - shade;
pixel; // PLOT alone advances the hardware X cursor.
pixels++;
u += cosine;
v -= sine;
}The cursor is positioned once per scanline; each pixel; advances X in
hardware. @cache requests instruction caching, and the frame ends with
flush; before the host reads RAM. The pixel loop is only partially cached;
the generated clearing loop fits entirely in the instruction-cache window.
The complete DiscoC program
includes the sine table, rotation, clipping, previous-frame clearing, and
host-visible diagnostics. With the DiscoC and WLA-DX tools on PATH, build
the complete SNES ROM from the repository root:
cmake -P tests/graphics/rotating_triangle/build-snes.cmakeOpen build/plot-rotating-triangle/rotating-triangle.sfc in a SuperFX-capable
SNES emulator. The optional Mesen checks compare all 49,152 pixels at all
64 angles, including phase wrap, RAM/VRAM transfers, and the red-screen
failure path. See build and verification instructions.
lib/core provides near-RAM byte copy/move/fill routines and signed Q8.8/Q12.4
fixed-point helpers. Fixed-point aliases are transparent integer types with
explicit scaling helpers, not new primitive types or floating-point support.
lib/targets/gsu contains target-specific graphics wrappers.
Import a library's .dci interface and explicitly compile/link its matching
.dc implementation, or list it in project.sources. Importing an interface
does not automatically add an implementation to the build.
Alternatively, import the .dc implementation directly and let a project build
discover and compile its source dependencies.
Useful examples:
- Language core: qualifiers, globals, numeric operators, and lexical plotting.
- Module interfaces: shared types, declarations, and multi-file linking.
- Source imports: automatic project discovery, shared dependencies, and configurable import paths.
- SNES triangle: canonical source, TOML, WLA-DX host, complete ROM and independent Mesen checks for Baseline 0.1.
- Fixed-point and memory helpers: portable core modules and explicit library linking.
- RAM result: host-visible output and startup.
- Stateful plotting: a ROM-colored scanline triangle. See SuperFX graphics for its host setup and build commands.
- SNES triangle test: RAM-executed plotting, a complete WLA-DX host ROM, and optional Mesen framebuffer/display checks.
- Rotating checkerboard: fixed-point rotation, persistent animation, VBlank uploads, and checks at all 64 angles.
See the freestanding library contract for API, rounding, overflow, and memory-access limits.
CTest covers language conformance, negative diagnostics, parser/object hardening, IR verification, multi-file linking, placement boundaries, project manifests, assembly/binary equivalence, and GSU execution regressions. Optional sanitizer and fuzzing configurations are documented in Testing.
The default execution tests use an instruction-level GSU model. A separate, optional static and rotating triangle integration tests have also been verified in Mesen using complete SNES ROMs, including the host and PPU display. This does not constitute physical-hardware validation; test your ROM in its deployment environment.
SPC700 machine emission and DSP libraries, interbank calls, aggregate-by-value ABI support, function pointers, and unions remain future work. Inline assembly and interrupt/naked/section/calling-convention attributes have design contracts, not implemented source semantics. BF16/FP16 and wider fixed-point arithmetic are also deferred.
See ROADMAP.MD for completed milestones and future work.
- Language specification: supported semantics and compatibility limits.
- Baseline 0.1 and language conformance: stabilization acceptance, rule-to-test index and release boundaries.
- Project manifests: TOML configuration and builds.
- Architecture: compiler pipeline, ABI, and ownership.
- IR and CFG: intermediate representation and verification.
- Object format: serialization and relocation contracts.
- Assembler: supported assembly syntax and workflows.
- GSU loading: execution origins and host initialization.
- SuperFX graphics: cursor state, color selection, pixel-cache effects, bitmap metadata, and migration from the old API.
- SPC700 foundation: target model and planned backend.
- Building and Testing: build paths and regression tooling.
DiscoC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be a useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

