No description
Find a file
2026-07-05 23:47:10 +02:00
editor_syntaxes Updated new c65gm keywords to editor syntaxes. LIBRARY and MACRO 2026-02-17 07:54:09 +01:00
examples Fixed up POKE and POKEW ,-syntax 2026-07-05 23:47:10 +02:00
internal Fixed up POKE and POKEW ,-syntax 2026-07-05 23:47:10 +02:00
lib Added decimal output lib and used it in new multdivlib demo. 2026-05-17 15:04:05 +02:00
opencode-config Fixed up POKE and POKEW ,-syntax 2026-07-05 23:47:10 +02:00
scripts Improvement on the release script 2026-05-24 13:38:45 +02:00
vendor Added Starlark scripting language for SCRIPT blocks. 2025-11-06 06:57:06 +01:00
.gitignore Added release building script 2026-05-24 13:25:26 +02:00
AGENTS.md Fix pragma auto-removal for multi-FUNC groups sharing one FEND 2026-05-17 16:23:14 +02:00
build_c65gm.sh Added c65cm stdlib as embedded into the c65gm exe so it's completely self contained, for easy distribution. 2026-04-17 11:53:20 +02:00
BUILDNUM v1.5 2026-06-11 02:17:17 +02:00
commands.md Improved commands reference 2026-04-16 07:02:59 +02:00
docker-compose.yml Fix pragma auto-removal for multi-FUNC groups sharing one FEND 2026-05-17 16:23:14 +02:00
Dockerfile Fixed up POKE and POKEW ,-syntax 2026-07-05 23:47:10 +02:00
go.mod Added Starlark scripting language for SCRIPT blocks. 2025-11-06 06:57:06 +01:00
go.sum Added Starlark scripting language for SCRIPT blocks. 2025-11-06 06:57:06 +01:00
language.md Added file loading (bin, text files) into SCRIPTS 2026-06-12 14:48:33 +02:00
LICENSE Added license. Docs updated to explain the command line options. 2026-04-17 15:08:53 +02:00
main.go Fixed up so trying to read (from script) symlinks outside project folder result in error 2026-06-12 16:06:06 +02:00
main_test.go Added license. Docs updated to explain the command line options. 2026-04-17 15:08:53 +02:00
opencode-docker.sh First commit refactoring and centralizing constant handling all over 2026-04-13 12:21:11 +02:00
README.md Add experimental peephole optimizer with 5 passes, CLI flags, pragmas, and I/O region protection. 2026-06-04 01:21:10 +02:00
syntax.md Added pragma to control number of steps in scripts and macros. Now that we support file loading some scripts might need more steps to process images or whatnot. 2026-06-12 15:12:34 +02:00

c65gm

A high-level 6502 cross-compiler targeting the ACME Cross-Assembler. c65gm provides a more expressive language for writing 6502 assembly programs, with features like functions, type-checked variables, control flow structures, and compile-time optimizations.

Download

Pre-built binaries are available from siders.techserio.com/downloads. Each release follows the naming pattern c65gm_v{MAJOR}.{BUILD}_os_arch:

File Platform
c65gm_v1.1_linux_amd64.tar.gz Linux (x86_64)
c65gm_v1.1_linux_arm64.tar.gz Linux (ARM64)
c65gm_v1.1_darwin_amd64.tar.gz macOS (Intel)
c65gm_v1.1_darwin_arm64.tar.gz macOS (Apple Silicon)
c65gm_v1.1_windows_amd64.zip Windows (x86_64)

Each release includes a checksums.txt file for verifying download integrity:

sha256sum -c checksums.txt

Verify with the annotated tag

Every release is created from an annotated git tag containing the Go compiler version and SHA256 checksums. To inspect:

git fetch --tags
git tag -l v1.1

The tag message shows the exact checksums. You can reproduce the build from the tag and verify your binary matches:

What It Does

c65gm compiles high-level source code into ACME assembler syntax for the 6502 processor (Commodore 64 and similar platforms). It provides:

  • Type system: BYTE and WORD variables with scope resolution
  • Functions: Named functions with parameters and call graph analysis
  • Control flow: IF/ENDIF, WHILE/WEND, FOR loops, SWITCH/CASE
  • Memory operations: PEEK/POKE/PEEKW/POKEW with zero-page optimization. Access registers as variables.
  • Operators: Arithmetic (ADD, SUB), bitwise (AND, OR, XOR)
  • Preprocessor: File inclusion, macros, conditional compilation, Starlark scripting
  • Standard library: C64 screen/kernal routines, memory management, string handling, graphics (Koala), FAT16 filesystem, and more (accessed via #include <file>, path set by C65LIBPATH environment variable)
  • Optimizations: Constant folding, self-assignment detection
  • Safety features: Compile-time detection of overlapping absolute addresses in function call chains

Prerequisites

ACME Assembler (Required for creating .prg executables)

c65gm requires the ACME Cross-Assembler to create executable .prg files. If you only need to generate assembly (.asm) files, you can use compile mode without ACME.

Installation options by platform:

  • Ubuntu/Debian: sudo apt install acme
  • Fedora/RHEL: sudo dnf install acme
  • Arch Linux: sudo pacman -S acme
  • macOS: brew install acme
  • Windows: Download binary from ACME releases

If ACME is not found when running the build command, c65gm will display platform-specific installation instructions.

Go (Required for building c65gm from source)

  • Go: Version 1.25.1 or higher (tested with 1.25.5)

The project uses Go modules with these dependencies:

  • github.com/armon/go-radix - Prefix tree for command lookup
  • go.starlark.net - Embedded Starlark scripting support

Building

Build the compiler binary:

go build -o c65gm

Or install to your GOPATH:

go install

Usage

Compile and assemble directly to a .prg file:

./c65gm myprogram.c65                    # Creates myprogram.prg
./c65gm -i myprogram.c65 -o game.prg     # Creates game.prg

Command Reference

Build (compile + assemble to .prg)

./c65gm build -i myprogram.c65 [-o output.prg] [--keep-asm] [--no-cbm]
./c65gm myprogram.c65                    # Default build to myprogram.prg
./c65gm -i myprogram.c65                 # Same as above
./c65gm -in myprogram.c65                # Legacy syntax, still works

Compile (to .asm only)

./c65gm compile -i myprogram.c65 [-o output.asm]
./c65gm myprogram.c65 -o output.asm      # .asm extension triggers compile mode
./c65gm -i myprogram.c65 -out output.asm # Legacy syntax

Help

./c65gm help
./c65gm -h
./c65gm --help

Key Features

  • Self-contained: Embedded standard library and Starlark interpreter (ACME assembler required for .prg creation)
  • Flexible syntax: -i/-in and -o/-out are equivalent
  • Smart defaults: Output extension determines mode (.prg = build, .asm = compile)
  • ACME integration: Automatically finds and runs ACME assembler with -f cbm by default (provides platform-specific installation instructions if missing)
  • Backward compatible: Legacy -in/-out flags still work
  • Customizable: Use --no-cbm to disable CBM format, --keep-asm to keep intermediate files

Compiler Options Reference

Default Behavior (Simplest Case)

The simplest way to use c65gm is to provide just the input file:

c65gm myprogram.c65

This will:

  1. Compile myprogram.c65 to assembly
  2. Run ACME assembler with -f cbm format
  3. Produce myprogram.prg (C64 executable)

Input and Output Files

  • -i, --input (-in): Specify input .c65 file (required)
  • -o, --output (-out): Specify output file (optional)

Examples:

# Explicit input/output
c65gm -i program.c65 -o game.prg

# Input only (default output: program.prg)
c65gm program.c65

# Legacy syntax (still works)
c65gm -in program.c65 -out output.asm

Operation Modes

c65gm automatically determines the operation mode based on the output file extension:

Build Mode (.prg extension)

Compiles and assembles to create a C64 executable:

c65gm program.c65              # → program.prg (default)
c65gm program.c65 -o game.prg  # → game.prg

Compile Mode (.asm extension)

Compiles only, producing ACME assembly for manual assembly:

c65gm program.c65 -o output.asm  # → output.asm
c65gm compile -i program.c65     # → program.asm

Subcommands

For explicit control, use subcommands:

build - Compile + Assemble

c65gm build -i program.c65 [-o output.prg] [--keep-asm] [--no-cbm]
  • --keep-asm: Keep intermediate .asm file
  • --no-cbm: Don't add -f cbm flag to ACME (for non-CBM targets)

compile - Compile Only

c65gm compile -i program.c65 [-o output.asm]

Produces assembly file only, doesn't run ACME.

help - Show Usage

c65gm help
c65gm -h
c65gm --help

ACME Assembler Integration

c65gm automatically:

  1. Searches PATH for acme executable
  2. Runs: acme -o output.prg -f cbm temp.asm
  3. Shows ACME output and errors
  4. Cleans up temporary files (unless --keep-asm)

Error Handling: If ACME is not found, c65gm shows installation instructions and suggests using compile mode instead.

Peephole Optimizer (Experimental)

⚠️ Experimental. This feature is new and actively being developed. All optimizations are disabled by default and must be explicitly enabled. While thoroughly tested on real C64 projects, edge cases may exist — always verify your program's behavior when optimization is active.

c65gm includes a peephole optimizer that removes redundant 6502 instructions from the generated assembly. It operates as a post-pass on the compiler output, scanning instruction windows and applying pattern-matching rules where safe.

The optimizer only modifies generated code — hand-written ASM/ENDASM blocks, SCRIPT output, and inline assembly are left untouched. It can also be configured to skip memory regions used for I/O (e.g., $D000-$DFFF on the C64).

Optimization Passes

Five passes run in sequence. All are enabled by default when --opt is used.

Pass Pragma Name What it removes
1 _P_OPT_STLD Store-then-load sta X; ...; lda Xsta X when A wasn't modified between
2 _P_OPT_LOAD Redundant load lda X; ...; lda Xlda X when A already holds X's value
3 _P_OPT_IMM Redundant immediate lda #N; ...; lda #Nlda #N when A already holds N
4 _P_OPT_JMP Jump-to-next jmp L; L:L: when no code between jmp and its target
5 _P_OPT_SELF Self-assignment lda X; sta X → (removed) when same variable stored back to itself

CLI Flags

Available on the build and compile subcommands:

c65gm build   -i program.c65 --opt [--opt-debug] [--opt-exclude ...] [--opt-exclude-c64-io]
c65gm compile -i program.c65 --opt [--opt-debug] [--opt-exclude ...] [--opt-exclude-c64-io]
Flag Effect
--opt Enable all 5 optimization passes (equivalent to #PRAGMA _P_OPT_ALL 1)
--opt-debug Annotate the assembly output with [removed] markers and a summary line showing how many instructions were saved
--opt-exclude Mark an address range as I/O to exclude from store-load optimization. Repeatable. Format: START:END with hex (0x prefix) or decimal values.
--opt-exclude-c64-io Shorthand for --opt-exclude 0xD000:0xDFFF. Convenience for C64 targets.

Pragma Equivalents

All CLI flags have equivalent #PRAGMA directives in the source code:

#PRAGMA _P_OPT_ALL 1           ; enable all passes
#PRAGMA _P_OPT_LOAD 1          ; enable redundant load pass only
#PRAGMA _P_OPT_IMM 1           ; enable redundant immediate pass only
#PRAGMA _P_OPT_JMP 1           ; enable jump-to-next pass only
#PRAGMA _P_OPT_SELF 1          ; enable self-assignment pass only
#PRAGMA _P_OPT_STLD 1          ; enable store-then-load pass only
#PRAGMA _P_OPT_DEBUG 1         ; annotate output with [removed] markers
#PRAGMA _P_OPT_IO $D000 $DFFF  ; mark C64 I/O range as excluded

Pragmas and CLI flags are additive — both can be used together.

Examples

# Enable all passes
c65gm compile -i game.c65 --opt -o game.asm

# Enable passes with debug annotations (see what was removed)
c65gm compile -i game.c65 --opt --opt-debug -o game.asm

# Enable passes, protect C64 I/O region (recommended for C64 targets)
c65gm build -i game.c65 --opt --opt-exclude-c64-io

# Protect custom I/O ranges (e.g., multiple VIC-II register blocks)
c65gm compile -i game.c65 --opt --opt-exclude 0xD000:0xDFFF --opt-exclude 0xDC00:0xDC0F

# Combined: all passes + debug + I/O protection
c65gm build -i game.c65 --opt --opt-debug --opt-exclude-c64-io

# Command-line equivalent to #PRAGMA _P_OPT_ALL + #PRAGMA _P_OPT_IO $D000 $DFFF
c65gm build -i game.c65 --opt --opt-exclude 0xD000:0xDFFF

Verifying Optimizations

With --opt-debug, the assembly output contains:

; --- peephole: 4311 asm lines → 4072 (239 saved) ---
...
;     POKE target_addr , value
; [removed] 	ldy #0
        lda mem_copy_value
        sta (mem_copy_target_addr),y
...

The header shows the instruction count before and after (X asm lines → Y), plus the total saved. Each [removed] annotation marks an instruction that was eliminated. The annotation is a comment and does not affect assembly.

For a full difference view, compile with and without --opt and diff the two .asm files:

c65gm compile -i game.c65 -o baseline.asm
c65gm compile -i game.c65 --opt --opt-debug -o optimized.asm
diff -u baseline.asm optimized.asm | less

I/O Safety

The optimizer never removes reads or stores to addresses marked in the I/O exclusion map. Reading an I/O register (like $D012 raster line or $DC01 keyboard matrix) may return different values each time, so redundant load elimination is disabled for those addresses. These can be specified via:

  1. CLI flag: --opt-exclude 0xD000:0xDFFF (repeatable for multiple regions)
  2. CLI shorthand: --opt-exclude-c64-io for the full C64 I/O page
  3. Pragma: #PRAGMA _P_OPT_IO $D000 $DFFF

Variable names (like vic2, BORDER_COLOR) are NOT checked against the I/O map — only literal hex addresses are. For @-mapped variables that point to I/O registers, use the _P_OPT_IO pragma with their address range.

Environment Variables

  • C65LIBPATH: Search path for #INCLUDE <file> directives
    export C65LIBPATH=/path/to/c65gm/lib
    c65gm program.c65
    

Examples Summary

# Quick build (recommended)
c65gm program.c65

# Custom output name
c65gm -i program.c65 -o game.prg

# Keep intermediate assembly
c65gm build -i program.c65 --keep-asm

# Compile only (no ACME)
c65gm compile -i program.c65

# Legacy two-step process (still works)
c65gm -in program.c65 -out program.asm
acme -f cbm -o program.prg program.asm

Running Tests

Run all tests:

go test ./...

Run tests with verbose output:

go test -v ./...

Run tests for a specific package:

go test ./internal/compiler
go test ./internal/commands

Examples

See the examples/ directory for sample programs:

  • hires/ - High-resolution graphics demo
  • multicolorbm/ - Multicolor bitmap demo
  • memlib_demo/ - Memory library usage
  • switch_demo/ - SWITCH/CASE statement examples

Building Examples

cd examples/hires
c65gm hires.c65                     # Creates hires.prg
c65gm -i hires.c65 -o demo.prg      # Creates demo.prg
c65gm compile -i hires.c65          # Creates hires.asm only

The example directories also contain cm.sh scripts showing the old build method.

Documentation

  • language.md - Complete language reference
  • syntax.md - Syntax guide
  • commands.md - Command reference

Editor Syntaxes

  • Kate: copy XML to ~/.local/share/org.kde.syntax-highlighting/syntax/
  • Sublime: copy .sublime-syntax to Packages/User/

Release Process

This section documents how to create a new release for maintainers.

Prerequisites

  • Go 1.25.1+ installed
  • sha256sum (Linux) or shasum (macOS)
  • Git with write access to the repository

Steps

The release script does everything automatically. Just run it:

bash scripts/release.sh

This performs the following sequence:

  1. Verify git clean — fails if uncommitted changes exist
  2. Read BUILDNUM — e.g., 42 → version 1.42
  3. Verify tag available — fails if tag v1.42 already exists
  4. Bump BUILDNUM — writes 43 to BUILDNUM
  5. Build all targets — 5 platform binaries with version injected via ldflags
  6. Generate SHA256 checksums — written to dist/checksums.txt
  7. Commit BUILDNUMgit commit with checksums in the commit body
  8. Create annotated taggit tag -a v1.42 pointing at the pre-bump commit, with checksums and Go version in the tag message

Publish

git push --follow-tags origin

--follow-tags is critical. Without it, only the commit is pushed — the annotated tag (with checksums) stays local. The release is incomplete until the tag is on the remote.

Forgot to use --follow-tags?

If you already ran git push without it, push just the tag:

git push origin v1.1

The tag is the source of truth for verification. Without it on the remote, nobody can verify.

Verify a release

git checkout v1.42
# You now have the exact source (BUILDNUM=42)
go build -trimpath -ldflags="-s -w -X main.version=1.42"
sha256sum c65gm
# Compare with the checksum in: git tag -l v1.42

Reproducible builds

The release script uses these flags for deterministic builds:

go build -trimpath -ldflags="-s -w -X main.version=${VERSION}"
  • -trimpath strips local filesystem paths
  • -s -w strips symbol table and DWARF debug info
  • -X main.version injects the version
  • CGO_ENABLED=0 ensures fully static binaries

Binaries built from the same source with the same Go version produce identical hashes. The tag message captures the Go version and expected checksums for verification.

License

Copyright (C) 1999, 2025 Mattias Hansson

This program 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 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

See LICENSE file for the full GPL v2 license text.