@@ -7,6 +7,185 @@ and this project adheres to [Semantic Versioning](https://semver.org/) - while
77the project is in ` 0.x ` , breaking changes may land in MINOR releases; PATCH
88releases are reserved for bug fixes only.
99
10+ ## [ v1.0.0] 2026-07-01
11+
12+ - ** v1.0 milestone: Complete Single-File Language** — every must-have and
13+ should-have feature from ` docs/roadmap.md ` 's v1.0 checklist is now
14+ implemented: function calls, pointer store, address-of, implicit widening,
15+ string literal locals, pointer arithmetic (` *(ptr + i) ` ), break/continue,
16+ struct field access, multiply/divide/modulo, and bitwise/shift operators.
17+ The only items left unchecked are explicitly-optional "nice-to-have"s
18+ (short-circuit ` && ` /` || ` codegen outside boolean-context use, and the
19+ ` &= ` /` |= ` /` ^= ` /` <<= ` /` >>= ` compound assignment forms) — the roadmap's own
20+ goal, "someone can sit down and write a non-trivial 65C02 program without
21+ hitting an unimplemented wall," is met;
22+
23+ - ** ` break ` and ` continue ` ** — both now supported inside ` while ` and ` for `
24+ loops. The analyzer tracks a ` loop_depth ` counter (incremented for the
25+ duration of a loop body) and rejects either statement outside a loop
26+ (` ERR_BREAK_OUTSIDE_LOOP ` / ` ERR_CONTINUE_OUTSIDE_LOOP ` ). The IR generator
27+ maintains a ` loop_ctx_t { continue_label, break_label } ` stack; both
28+ statements desugar to a plain ` TAC_JUMP ` to the appropriate label, so
29+ codegen needed no changes. For ` for ` loops, ` continue ` jumps to a new
30+ label sitting between the body and the incrementer, so the incrementer
31+ still runs before the next condition check. See
32+ ` docs/break-continue-implementation.md ` for the full design writeup,
33+ including a bonus fix along the way: the ` for ` -loop incrementer was being
34+ lowered with ` lower_expr ` instead of ` lower_stmt ` , so ` i = i + 1 ` -style
35+ incrementers (as opposed to ` ++i ` ) were silently generating no code;
36+
37+ - ** Local string literal initializers** — ` u8 *p = "some string"; ` now works
38+ inside function bodies, not just at global scope. The string data is placed in
39+ the ROM data section via the existing ` data_fixup_t ` backpatch mechanism (same
40+ as global strings); the pointer value (ROM address of the string) is written
41+ into the local's ZP slot during function entry via ` TAC_COPY ` with an
42+ ` OPERAND_CONST_STR ` source. Callee-saves push and pop the pointer ZP slot
43+ across nested calls to preserve it across function boundaries. The pointer
44+ occupies 2 bytes of ZP for the lifetime of the function;
45+
46+ - ** Variable shadowing is now a semantic error** — a variable declaration
47+ (or function parameter) that reuses a name still visible from an enclosing,
48+ still-live scope now raises ` ERR_SHADOWED_DECLARATION ` instead of silently
49+ compiling. Root cause: the IR/codegen identify a variable purely by its bare
50+ name (` OPERAND_VAR.name ` , matched via ` strcmp ` in ` zp_map_build ` ), with no
51+ per-scope qualifier, so a shadowed inner declaration (e.g. a `for (u8 i = 0;
52+ ...)` nested inside a function that already has an outer ` u8 i`) aliased the
53+ * same* zero-page storage as its outer namesake — the inner loop's own
54+ init/exit value silently clobbered the outer variable. This surfaced as
55+ ` break ` firing on the wrong iteration in ` arithmetic_demo.c02 ` . Reusing a
56+ name across scopes that never overlap on the scope stack (e.g. two sibling
57+ ` for ` loops each declaring their own ` i ` ) is unaffected and remains legal;
58+
59+ ## [ v0.2.17] 2026-06-27
60+
61+ - ** ROM overflow detection** — the ` EMIT() ` macro now bounds-checks ` code_pos `
62+ against ` ROM_SIZE ` before writing, setting an ` overflow ` flag on ` emitter_t `
63+ instead of silently writing past the end of the 32 KB buffer. A new
64+ ` PATCH_BYTE(pos, val) ` macro applies the same guard to all branch-offset and
65+ address backpatches. ` resolve_func_fixups ` and ` resolve_local_fixups `
66+ early-return when overflow is already set. ` generate_rom ` checks ` e.overflow `
67+ independently after the code section, data section, and symbol-table phases,
68+ printing a targeted diagnostic and returning ` NULL ` on failure. Previously,
69+ programs that grew past 32 KB would corrupt the ROM buffer without any error.
70+ ` emit_symbol_table ` now also writes through ` EMIT() ` instead of raw pointer
71+ writes, inheriting the overflow guard.
72+
73+ - ** ` __heap_start ` implicit compiler global** — programs may declare
74+ ` decl u16 __heap_start; ` to read the address of the first free RAM byte after
75+ all user globals are allocated. The driver injects the declaration
76+ automatically so no user ` decl ` is required in practice. The value is
77+ initialized during the bootstrap sequence. Intended as a base pointer for
78+ simple bump allocators.
79+
80+ - ** ` __memory_top ` implicit compiler global** — ` decl u16 __memory_top; `
81+ evaluates to ` $3FFF ` , the top of the general-purpose RAM region (see
82+ ` docs/memmap.md ` ). Injected alongside ` __heap_start ` . Both constants are
83+ defined as ` RAM_TOP ` in the codegen memory map.
84+
85+ - ** Compiler extern two-pass allocation** — ` emit_compiler_extern_inits ` is
86+ refactored into an explicit two-pass design: pass 1 allocates all RAM slots
87+ (settling ` e->ram_pos ` ), pass 2 emits initializers. This ensures
88+ ` __heap_start ` captures the correct first-free-RAM address regardless of
89+ declaration order. Unknown non-function externs now print a diagnostic and
90+ cause codegen to fail instead of being silently skipped.
91+ - ` ALLOC_COMPILER_SLOT ` / ` EMIT_COMPILER_VALUE ` — pair of local macros that
92+ collapse the slot-allocation and value-emission boilerplate to one line each;
93+ both are ` #undef ` 'd immediately after the function.
94+
95+ ## [ v0.2.16] 2026-06-27
96+
97+ - ** 16-bit multiply / divide / modulo (` __mul16 ` , ` __div16 ` , ` __sdiv16 ` )** —
98+ ` TAC_MUL ` , ` TAC_DIV ` , and ` TAC_MOD ` on ` u16 ` /` i16 ` operands now compile to
99+ subroutine calls rather than erroring. Three new helpers:
100+ - ` __mul16 ` — 16-iteration shift-and-add. Correct for both ` u16 ` and ` i16 `
101+ because the low 16 bits of a two's-complement product are sign-agnostic.
102+ Overflow silently wraps to the low 16 bits (same as C).
103+ - ` __div16 ` — 16-iteration shift-subtract with CMP-based comparison. Uses a
104+ ` BCS dosub ` guard before the 16-bit subtract so divisors with bit 15 set
105+ (≥ ` $8000 ` ) are handled correctly; the naive SEC-before-compare approach
106+ clobbers the overflow carry and produces wrong quotients for those values.
107+ - ` __sdiv16 ` — sign wrapper around ` __div16 ` , mirroring ` __sdiv8 ` : encodes
108+ signs in ` HELPER_SIGN ` (` $EC ` , bit 7 = negate quotient, bit 6 = negate
109+ remainder), negates both operands, calls ` __div16 ` , then restores signs.
110+ Follows C truncation-toward-zero convention. ` needs_sdiv16 = 1 ` implies
111+ ` needs_div16 = 1 ` .
112+ - New 16-bit helper ZP zone: ` $E0–$E7 ` (` HELPER16_ARG1 ` /` ARG2 ` /` RES ` /` REM ` ,
113+ 2 bytes each), below the existing 8-bit zone at ` $E8–$EC ` .
114+ - ZP operand map upper bound tightened from ` $EE ` to ` $DF ` to reflect the
115+ new reserved zone; ` zp_map_add ` now enforces this with an address guard
116+ (previously only a count guard existed).
117+ - ` EMIT_ARITH8(ROUTINE, RES_SLOT, NEEDS_FLAG) ` /
118+ ` EMIT_ARITH16(ROUTINE, RES_SLOT, NEEDS_FLAG) ` — pair of local macros
119+ replacing the three verbose switch arms; bit width, arg-loading, and
120+ result-storing all collapse to one line per dispatch branch.
121+ - Emulator tests: ` mul_u16 ` (300 × 13 = 3900), ` mul_u16_wrap ` (256 × 256 = 0,
122+ overflow), ` div_u16 ` (50000 / ` $C001 ` = 1, remainder = 847 — exercises the
123+ high-bit-divisor path).
124+
125+ - ** Symbol table embedded in ROM** — compiled binaries now carry a ` "C02S" `
126+ symbol table in the NOP fill area between the data section and ` $FFF6 ` ,
127+ letting ` c02-objdump ` show real function names instead of auto-generated
128+ labels. The table is always emitted by default; ` --strip-debug ` omits it.
129+ Binary size stays exactly 32 KB so EEPROM flashing is unaffected.
130+ - Footer layout: ` $FFF6–$FFF7 ` = little-endian pointer to the table (or
131+ ` $EAEA ` NOP fill if absent); ` $FFF8–$FFF9 ` = code/data boundary
132+ (unchanged); ` $FFFA–$FFFF ` = NMI/Reset/IRQ vectors.
133+ - Format: magic ` C02S ` (4 bytes) + u16 entry count (LE) + entries of
134+ u16 address (LE) + null-terminated name. All user-defined functions and
135+ emitted helpers (` __mul8 ` , ` __div16 ` , etc.) are included.
136+ - Old binaries degrade gracefully: ` $EAEA ` at ` $FFF6 ` passes the range
137+ check but fails the magic-byte check, so the disassembler falls back to
138+ ` L0 ` /` L1 ` /… auto-labels without error.
139+ - ` c02-objdump ` : ` parse_symbols ` reads the table and merges it into the
140+ jump-target label map; ` scan_end ` for the data-section boundary scan
141+ stops at the symbol table start rather than ` $FFF8 ` to avoid
142+ misidentifying table bytes as data.
143+ - ` driver.h ` : ` params_t ` gains ` int strip_debug ` ; ` main.c ` adds
144+ ` --strip-debug ` to ` long_options ` .
145+
146+ - ** ZP map overflow error propagation** — previously, ` zp_map_add ` printed a
147+ diagnostic to stderr and silently continued, potentially generating corrupt
148+ code. ` zp_map_add ` , ` zp_map_add_operand ` , and ` zp_map_build ` now all return
149+ ` int ` (0 = failure); ` emit_function_from_cfg ` checks ` zp_map_build ` and
150+ returns 0, propagating to ` generate_rom ` which returns ` NULL ` — the same
151+ path as all other codegen failures, ultimately exiting with
152+ ` CODE_GEN_ERROR_RET_CODE ` (7).
153+
154+ - ** Bug fix: signed 8-bit division and modulo (` __sdiv8 ` )** — ` TAC_DIV ` and
155+ ` TAC_MOD ` on ` i8 ` operands previously routed through the unsigned ` __div8 `
156+ helper, so ` i8 -6 / 2 ` computed ` 250 / 2 = 125 ` instead of ` -3 ` . A new
157+ ` __sdiv8 ` helper wraps ` __div8 ` : it saves operand signs into a scratch byte at
158+ ` $EC ` (HELPER_SIGN), negates both operands to their absolute values, calls
159+ ` __div8 ` , then restores the correct sign on the quotient (bit 7 of SIGN) and
160+ remainder (bit 6 of SIGN) per C's truncation-toward-zero convention. Codegen
161+ routes ` TAC_DIV ` /` TAC_MOD ` through ` __sdiv8 ` when ` is_signed_type(dst.type) ` ;
162+ ` __sdiv8 ` always calls ` __div8 ` , so ` needs_sdiv8 = 1 ` implies ` needs_div8 = 1 ` .
163+ New opcode emitters: ` bpl_rel ` . ` $EC ` added to the helper ZP zone.
164+ - Emulator tests: ` div_i8 ` (−6 / 2 = −3, PORTB = $FD), ` mod_i8 ` (−7 % 2 = −1,
165+ PORTB = $FF).
166+
167+ - ** Bug fix: binary op operand widening and sign normalisation** — binary ops
168+ derived both the result type and comparison signedness from the LEFT operand
169+ only (` ir.c ` ), ignoring the right operand entirely. Consequences: ` u8 + u16 `
170+ computed at 8 bits (the u16 high byte was silently dropped), while ` u16 + u8 `
171+ was accidentally correct; ` i8 < u8 ` used a signed compare while ` u8 < i8 ` used
172+ an unsigned compare — a trichotomy violation where both ` a < b ` and ` b < a `
173+ could be simultaneously true. Fixed in the IR generator with four new helpers:
174+ - ` ir_type_width ` / ` ir_is_signed ` — predicates for 8-vs-16-bit and
175+ signedness without an ` ir_gen_t * ` context.
176+ - ` binop_common_type(left, right) ` — returns the wider type; for equal widths,
177+ unsigned wins (C's usual arithmetic conversions), eliminating operand-order
178+ dependence in mixed-sign comparisons.
179+ - ` emit_widen_if_needed(gen, cfg, op, target) ` — emits ` TAC_CAST ` when the
180+ operand type differs from the target; ` OPERAND_CONST_INT ` values are
181+ re-typed in place (no instruction emitted).
182+ - Binop lowering now normalises both operands to the common type before
183+ arithmetic and comparison ops. Shifts are guarded separately (result type =
184+ left, shift count is never widened). Pointer arithmetic skips widening.
185+ - Emulator tests: ` binop_widen ` (` u8(1) + u16(500) = 501 ` , high byte = $01),
186+ ` cmp_mixed_sign ` (` i8(−1) < u8(100) ` with unsigned-wins → $FF reinterpreted as
187+ 255, 255 < 100 = false, branch not taken, PORTB = $01).
188+
10189## [ v0.2.15] 2026-06-26
11190
12191- ** Function call codegen (` TAC_CALL ` )** — full caller/callee ABI using a fixed
0 commit comments