# 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](https://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:

```bash
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:

```bash
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), shifts (SHL, SHR)
- **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](https://github.com/meonwax/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:

```bash
go build -o c65gm
```

Or install to your GOPATH:

```bash
go install
```

## Usage

### Quick Start (Recommended)
Compile and assemble directly to a .prg file:

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

### Command Reference

#### Build (compile + assemble to .prg)
```bash
./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)
```bash
./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
```bash
./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:
```bash
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:**
```bash
# 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:
```bash
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:
```bash
c65gm program.c65 -o output.asm  # → output.asm
c65gm compile -i program.c65     # → program.asm
```

### Subcommands
For explicit control, use subcommands:

#### `build` - Compile + Assemble
```bash
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
```bash
c65gm compile -i program.c65 [-o output.asm]
```
Produces assembly file only, doesn't run ACME.

#### `help` - Show Usage
```bash
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 X` → `sta X` when A wasn't modified between |
| 2 | `_P_OPT_LOAD` | Redundant load | `lda X; ...; lda X` → `lda X` when A already holds X's value |
| 3 | `_P_OPT_IMM` | Redundant immediate | `lda #N; ...; lda #N` → `lda #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:

```c65
#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

```bash
# 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:

```bash
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
  ```bash
  export C65LIBPATH=/path/to/c65gm/lib
  c65gm program.c65
  ```

### Examples Summary
```bash
# 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:

```bash
go test ./...
```

Run tests with verbose output:

```bash
go test -v ./...
```

Run tests for a specific package:

```bash
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
```bash
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
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 BUILDNUM** — `git commit` with checksums in the commit body
8. **Create annotated tag** — `git tag -a v1.42` pointing at the pre-bump commit, with checksums and Go version in the tag message

### Publish

```bash
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:

```bash
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

```bash
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:

```bash
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](LICENSE) file for the full GPL v2 license text.
