Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/) - while
the project is in `0.x`, breaking changes may land in MINOR releases; PATCH
releases are reserved for bug fixes only.

## [v.0.2.13] 2026-06-24

- **Codegen diagnostic for unhandled TAC ops** — the `default: break` in the
TAC instruction switch has been replaced with a `fprintf(stderr)` + error
return that names the unhandled op number. Programs using unimplemented
features now fail loudly at compile time instead of silently producing wrong
binaries. `emit_function_from_cfg` now returns `int` (0 on failure) so the
error propagates to `generate_rom`.
- **Bootstrap emu tests decoupled from analyzer_basic.c02** — the eight
bootstrap-verification tests (rom_size, reset_vector, etc.) now use
`emu_store_const.c02` instead of `analyzer_basic.c02`, which uses TAC ops
not yet implemented in the code generator.
- **Fix global/ZP bug class** — `TAC_INC`/`TAC_DEC` on global variables now
emit `INC abs` ($EE) / `DEC abs` ($CE) targeting the global's RAM address
instead of the stale ZP scratch slot. 16-bit global INC/DEC uses `BNE +3`
(3-byte abs instruction) instead of `+2`. `COMPARE_OP` right-hand operands
now route through a global-aware `emit_cmp_byte` helper that dispatches
`CMP abs` ($CD) for globals. New opcode emitters: `inc_abs`, `dec_abs`,
`cmp_abs`.
- Emulator tests: `inc_global` (global u8 increment), `cmp_global` (compare
local against global with branch).
- **u8 arithmetic (`TAC_ADD`, `TAC_SUB`)** — `CLC; LDA src1; ADC src2; STA dst`
for addition, `SEC; LDA src1; SBC src2; STA dst` for subtraction. Global-aware
RHS helpers (`emit_adc_byte`, `emit_sbc_byte`) dispatch ADC/SBC abs ($6D/$ED)
for globals. New opcode emitters: `clc` ($18), `sec` ($38), `adc_imm` ($69),
`adc_zpg` ($65), `adc_abs` ($6D), `sbc_imm` ($E9), `sbc_zpg` ($E5),
`sbc_abs` ($ED). CLC/SEC is emitted once outside the byte loop so u16
carry propagation works correctly.
- Emulator tests: `add_u8`, `sub_u8`, `add_const`.
- **`TAC_NEG` (unary minus)** — `SEC; LDA #0; SBC [src1]; STA [dst]`. SEC is
emitted once before the byte loop so borrow propagates correctly for u16.
- Emulator test: `neg_u8` (double negate round-trips back to original value).
- **u16 comparisons** — `COMPARE_OP` macro replaced with backpatched
comparison handlers that support both u8 and u16 operands. u16 ordering
(LT/GTE/GT/LTE) uses a high-byte-first pattern: compare high bytes first
to determine definite ordering, fall through to low bytes when equal.
u16 EQ/NEQ compare both bytes. All forward branch offsets use backpatching
instead of hardcoded values, making them robust to variable-size loads
(zpg=2 vs abs=3 vs imm=2).
- Emulator tests: `cmp_u16_lt` (255<256, high-byte decides), `cmp_u16_eq`
(500==500, both bytes match), `cmp_u16_gt` (1000>255, high-byte decides).
- **Signed comparisons (i8/i16)** — ordering comparisons (LT/GTE/GT/LTE) now
detect signed operand types and emit the N XOR V pattern: `SEC; SBC; BVC +2;
EOR #$80; BMI true` for i8, with an extended high-byte-first pattern for
i16 (signed high byte via N^V, unsigned low byte fallback via BCC). EQ/NEQ
remain sign-agnostic. GTE inverts the LT result. GT/LTE swap operands.
New opcode emitter: `bvc_rel` ($50).
- Emulator tests: `cmp_i8_lt` (-5<3), `cmp_i8_gt` (3>-5), `cmp_i8_neg`
(-10<-3), `cmp_i16_signed` (-300<300).
- **u16 arithmetic** — `TAC_ADD`/`TAC_SUB` are width-aware from the start
(CLC/SEC outside byte loop, carry propagates between bytes). Signed i8/i16
arithmetic works via two's complement — same ADC/SBC instructions.
- Emulator tests: `add_u16` (300+200=500), `sub_u16` (1000-500=500),
`add_i8` (-5+47=42), `add_i16` (-300+800=500).

## [v0.2.12] 2026-06-23

- **c02-objdump: label markers** — jump target labels (`L0:`, `L1:`, ...) now
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,35 @@
2. **Recursive Descent Parser:** Transforms the token stream into a structured AST, treating hardware registers and standard controls as first-class grammatical constructs.
3. **Lexically Scoped Semantic Analyzer:** Two-pass validation engine over the AST. Pass 1 registers all top-level declarations (functions, structs, registers, globals) into the global symbol table. Pass 2 walks function bodies with a scoped symbol table, checking undeclared identifiers, type mismatches, argument counts/types, struct field access, lvalue validity, and return-type consistency. Invalid declarations are poisoned to prevent cascading diagnostics.
4. **IR Generator:** Lowers the analysed AST into a self-contained three-address code (TAC) intermediate representation. The IR module contains struct layouts with computed field offsets, global/register definitions with hardware addresses baked in, and one flat instruction stream per function - codegen can emit target code from the IR alone, without consulting the AST or symbol table. Supports incremental compilation: `-c` serializes the IR to a `.o` file that can be loaded back to skip the frontend entirely.
5. **Code Generator:** Emits valid 65C02 ROM binaries (32K) with a bootstrap runtime, interrupt vectors, and flat zero-page register allocation. Avoids slow stack-based execution by mapping local variables, temporaries, and parameters directly onto zero-page slots. Globals are allocated in RAM ($0200+) and initialized in the bootstrap before `JSR main`. String literals are placed in a ROM data section with backpatching fixups. Simple programs compile and run on real hardware.
5. **Code Generator:** Emits valid 65C02 ROM binaries (32K) with a bootstrap runtime, interrupt vectors, and flat zero-page register allocation. Avoids slow stack-based execution by mapping local variables, temporaries, and parameters directly onto zero-page slots. Globals are allocated in RAM ($0200+) and initialized in the bootstrap before `JSR main`. String literals are placed in a ROM data section with backpatching fixups. Supports arithmetic (`+`, `-`, unary `-`) for all integer types (u8/i8/u16/i16), comparisons across all widths and signedness (unsigned via carry-flag, signed via N⊕V), and pointer dereference. Programs compile and run on real hardware.

#### c02-objdump Disassembler

- **Disassembler:** Decodes compiled `.bin` files back into annotated 65C02 assembly, resolving jump targets to named labels for readability. See [c02-objdump](c02-objdump/) for more information.
- **Disassembler:** Decodes compiled `.bin` files back into annotated 65C02 assembly, resolving jump targets to named labels for readability. Supports section-aware output (`.text` / `.data` split), hex dumps with ASCII, and ROM usage summaries. See [c02-objdump](c02-objdump/) for more information.

## Current Status & Limitations

C02 is under active, early development. The **complete frontend** (tokenizer, parser, semantic analyzer), **IR generation**, and **code generator** are functional and tested — simple programs compile to valid 65C02 ROMs and run on real hardware.

#### What works today

- **Data movement:** variable copies, constant stores, hardware register writes (`TAC_COPY`, `TAC_STORE`, `TAC_RETURN`).
- **Data movement:** variable copies, constant stores, hardware register writes.
- **Control flow:** `if`/`else`, `while`, `for` loops via label/jump/conditional-jump.
- **Comparisons:** all six relational operators (`<`, `<=`, `==`, `!=`, `>=`, `>`), u8 only.
- **Increment/decrement:** `++`/`--` for both u8 and 16-bit values (pointers, u16).
- **Arithmetic:** `+`, `-`, and unary `-` for all integer types (u8, i8, u16, i16). Width-aware multi-byte emission for 16-bit operations with carry/borrow propagation.
- **Comparisons:** all six relational operators (`<`, `<=`, `==`, `!=`, `>=`, `>`) for all widths (u8, u16) and signedness (unsigned via carry-flag, signed via N⊕V). 16-bit comparisons use a high-byte-first pattern.
- **Increment/decrement:** `++`/`--` for both u8 and 16-bit values (pointers, u16), including globals.
- **Pointer dereference:** `*p` via indirect indexed addressing (`LDA ($nn),Y`).
- **Global variables:** RAM-allocated globals with bootstrap initialization. String literals placed in a ROM data section with backpatching fixups.
- **Global variables:** RAM-allocated globals with bootstrap initialization, correctly accessed via absolute addressing throughout all codegen paths. String literals placed in a ROM data section with backpatching fixups.

#### Not yet implemented

- **Function calls** (`TAC_CALL`) — the ABI zone is reserved but `JSR`/parameter passing is not wired up yet.
- **Arithmetic** (`+`, `-`, `*`, `/`, `%`) — no `TAC_ADD`/`TAC_SUB`/`TAC_MUL`/`TAC_DIV` codegen.
- **Struct field access** (`TAC_FIELD_LOAD`/`TAC_FIELD_STORE`).
- **Type casts** (`TAC_CAST`) — implicit widening (u8→u16) reads a garbage high byte.
- **16-bit comparisons** — only the low byte is compared; values differing in the high byte give wrong results.
- **Signed comparisons** — the `CMP`/carry-flag sequence implements unsigned ordering only.
- **Function calls** — the ABI zone ($EF–$FF) is reserved but `JSR`/parameter passing is not wired up yet.
- **Multiplication, division, modulo** (`*`, `/`, `%`) — no native 6502 instructions; needs runtime helper routines.
- **Struct field access** (`s.field` codegen).
- **Type casts** — implicit widening (u8→u16) reads a garbage high byte; needs explicit zero-extension.
- **Address-of** (`&x`) — parsed and analysed but no codegen.
- **Pointer store** (`*p = val`) — parsed and analysed but no codegen for variable-destination stores.
- **Arrays** — no array type or subscript syntax (`a[i]`).
- **Struct field access through a pointer is auto-dereferenced** — there's no `->` operator; `.` is used uniformly and the analyzer resolves single-level pointer indirection automatically (e.g. `ptr.field` where `ptr` is a `Struct*`).
- **Missing-return detection is shallow.** A non-void function with no `return` at the end is flagged, but the analyzer does not perform full path-coverage analysis.

If you're exploring the codebase: the parser ([parser.c](cc02/src/parser/parser.c)), the analyzer ([analyzer.c](cc02/src/analysis/analyzer.c)), the IR generator ([ir.c](cc02/src/ir-gen/ir.c)), and the code generator ([generator.c](cc02/src/code-gen/generator.c)) are the main files. Issues and PRs are welcome.
Expand Down
Loading
Loading