cc45 is a C compiler designed for the MEGA65 (45GS02). It translates a subset of the C language into optimized assembly code.
- Function Declarations: Full support for function definitions with parameters.
- Local Variables: Support for local variables allocated on the stack.
- Structures & Unions: Full support for
structanduniondefinitions, members, and dot/arrow operators. - Anonymous Aggregates: Support for nested structures and unions without names, allowing direct member access from the parent scope.
- Control Flow:
if/elsestatements,whileloops, andforloops. - Increment/Decrement: Optimized
++and--(prefix and postfix) using simulated opcodes. - Nested Expressions: Robust handling of complex arithmetic expressions and nested function calls.
- String Pooling: Automatic identification and pooling of string literals into a data section using
.text(PETSCII). - Procedure System: Translates C functions into
PROCblocks forca45, leveraging high-level procedure syntax. - Volatile Support: Full support for the
volatilekeyword to prevent optimization of hardware-mapped or interrupt-modified variables. - Static Assertions: Support for
_Static_assertto perform compile-time validation of constant expressions. - Function Specifiers: Support for
_Noreturnto indicate that a function does not return to its caller, enabling optimizations to skip return instructions. - Alignment: Support for C11
_Alignasand_Alignofto manage data alignment for global variables and struct members. - Generic Selections: Support for C11
_Genericexpressions for compile-time type-based dispatch. - Arrays: Native support for one-dimensional arrays (
type name[size]) with subscript indexing and automatic pointer decay. - Ternary Operator: Support for conditional expressions (
cond ? then : else). - Bitwise Operators: Verified support for
&,|,^,<<, and>>with constant folding. - Modulo Operator: Support for
%operator for integer remainder. - Compound Assignment: Support for
+=,-=,*=,/=,%=,&=,|=,^=,<<=, and>>=. forLoop Declarations: Support for C99-style variable declarations in theforloop initializer.- Type Definitions: Support for
typedefto create type aliases. - Enumerations: Support for
enumtypes and constants. - Explicit Casts: Support for C-style cast expressions
(type)exprfor explicit type conversions, including narrowing, widening, and pointer casts. - Implicit Narrowing Warnings: Compile-time warnings when implicit conversions lose data (e.g.,
inttochar). Explicit casts suppress the warning.
The compiler supports a variety of C statements for program flow:
<statement> ::= <compound-statement>
| <expression-statement>
| <selection-statement>
| <iteration-statement>
| <jump-statement>
| <declaration-statement>
| <static-assert-statement>
| <typedef-definition>
| <enum-definition>
<compound-statement> ::= "{" <block-item-list>? "}"
<block-item-list> ::= <block-item> <block-item-list>?
<block-item> ::= <statement> | <declaration-statement> | <typedef-definition> | <enum-definition>
<expression-statement> ::= <expression>? ";"
<selection-statement> ::= "if" "(" <expression> ")" <statement> ( "else" <statement> )?
| "switch" "(" <expression> ")" <statement>
| "case" <expression> ":" <statement>?
| "default" ":" <statement>?
<iteration-statement> ::= "while" "(" <expression> ")" <statement>
| "do" <statement> "while" "(" <expression> ")" ";"
| "for" "(" ( <expression-statement> | <declaration-statement> ) <expression>? ";" <expression>? ")" <statement>
<jump-statement> ::= "return" <expression>? ";"
| "break" ";"
| "continue" ";"
| "continue" <expression> ";"
| "continue" "default" ";"
| "asm" "(" <string-literal> ")" ";"
| "__asm__" "(" <string-literal> ")" ";"
<declaration-statement> ::= ( "_Alignas" "(" ( <expression> | <type-specifier> "*"* ) ")" )? ( "volatile" )? ( "auto" )? <type-specifier> "*"* <identifier> ( "[" <integer-literal> "]" )? ( "=" <expression> )? ";"
<static-assert-statement> ::= "_Static_assert" "(" <expression> "," <string-literal> ")" ";"
<typedef-definition> ::= "typedef" <type-specifier> "*"* <identifier> ";"
<enum-definition> ::= "enum" <identifier>? "{" <enumerator-list> "}" ";"
<enumerator-list> ::= <enumerator> ( "," <enumerator> )*
<enumerator> ::= <identifier> ( "=" <integer-literal> )?
<struct-definition> ::= ( "struct" | "union" ) <identifier>? "{" <member-list> "}" ";"
<member-list> ::= <member-declaration> <member-list>?
<member-declaration> ::= ( "_Alignas" "(" ( <expression> | <type-specifier> "*"* ) ")" )? <type-specifier> "*"* <identifier>? ( "[" <integer-literal> "]" )? ";"
| <struct-definition>
<type-specifier> ::= "int" | "char" | "void" | ( "struct" | "union" | "enum" ) <identifier> | <identifier>
Expressions are evaluated using standard C precedence rules:
<expression> ::= <assignment>
<assignment> ::= <unary> <assignment-op> <assignment> | <conditional>
<assignment-op> ::= "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>="
<conditional> ::= <logical-or> ( "?" <expression> ":" <conditional> )?
<logical-or> ::= <logical-and> ( "||" <logical-and> )*
<logical-and> ::= <bitwise-or> ( "&&" <bitwise-or> )*
<bitwise-or> ::= <bitwise-xor> ( "|" <bitwise-xor> )*
<bitwise-xor> ::= <bitwise-and> ( "^" <bitwise-and> )*
<bitwise-and> ::= <equality> ( "&" <equality> )*
<equality> ::= <relational> ( ("==" | "!=") <relational> )*
<relational> ::= <shift> ( ("<" | ">" | "<=" | ">=") <shift> )*
<shift> ::= <additive> ( ("<<" | ">>") <additive> )*
<additive> ::= <multiplicative> ( ("+" | "-") <multiplicative> )*
<multiplicative> ::= <unary> ( ("*" | "/") <unary> )*
<unary> ::= ( "!" | "~" | "-" | "*" | "&" | "++" | "--" ) <unary>
| "(" <type-specifier> "*"* ")" <unary>
| <primary> ( "++" | "--" )
| <primary> "[" <expression> "]"
| <primary>
<primary> ::= <identifier>
| <integer-literal>
| <string-literal>
| <function-call>
| <generic-selection>
| "_Alignof" "(" <type-specifier> "*"* ")"
| <primary> "." <identifier>
| <primary> "->" <identifier>
| "(" <expression> ")"
<generic-selection> ::= "_Generic" "(" <expression> "," <generic-assoc-list> ")"
<generic-assoc-list> ::= <generic-association> ( "," <generic-association> )*
<generic-association> ::= ( <type-specifier> "*"* | "default" ) ":" <expression>
```
<function-call> ::= <identifier> "(" <argument-list>? ")"
<argument-list> ::= <expression> ( "," <expression> )*
Used for conditional branching.
if/else: If the condition evaluates to a non-zero value, the first statement is executed. If anelseclause is present and the condition is zero, the second statement is executed.switch: Evaluates an expression and transfers control to thecaselabel with a matching constant value.case <const>: Defines a jump target for a specific value.default: (Optional) Defines the jump target if no cases match.- Fall-through: Control flows from one case into the next unless interrupted by a
breakstatement. continue <value> / default: (Extension) Jumps to the specifiedcaseordefaultlabel within the currentswitchblock. This can be used to re-execute logic or share code between cases without usinggoto.
while: Repeatedly executes the body as long as the condition is non-zero. The condition is checked before each iteration.do: Repeatedly executes the body as long as the condition is non-zero. The condition is checked after each iteration, ensuring the body executes at least once.for: A flexible loop. The initializer is executed once. The condition is checked before each iteration. The increment expression is executed after each iteration of the body.
cc45 supports standard C loop control statements break and continue within while, do-while, and for loops.
break Statement
The break statement terminates the execution of the innermost enclosing loop. Control passes to the statement immediately following the loop body.
Example: Searching
int find_first_even(int* arr, int size) {
int i;
int result = -1;
for (i = 0; i < size; i = i + 1) {
if (arr[i] % 2 == 0) {
result = arr[i];
break; // Exit the for loop immediately
}
}
return result;
}continue Statement
The continue statement skips the remainder of the current iteration of the innermost enclosing loop and proceeds to the next iteration.
- In a
whileordo-whileloop, it jumps to the condition evaluation. - In a
forloop, it jumps to the increment expression.
Example: Filtering
int sum_odds(int limit) {
int i;
int sum = 0;
for (i = 0; i < limit; i = i + 1) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
sum = sum + i;
}
return sum;
}Nested Loops
break and continue only affect the innermost loop in which they appear.
for (i = 0; i < 10; i = i + 1) {
while (j < 10) {
if (condition) break; // Exits the while loop, but stays in the for loop
j = j + 1;
}
}return: Exits the current function and returns the value of the optional expression to the caller. On Mega65, the return value is passed via theAandXregisters.break/continue: Controls the flow of loops (see Loop Control for details).goto <label>: Transfers control unconditionally to the specified label within the current function.<label>:: Defines a jump target forgotostatements. Labels are function-scoped.asm/__asm__: Inserts a string of raw assembly directly into the compiler's output. Each__asm__("...")call emits one line of assembly. See Inline Assembly and Variable Access for detailed usage.
The __asm__("...") statement inserts a single line of assembly into the compiler's output. Each call emits exactly one assembler instruction or directive. Inline assembly can reference C variables using the compiler's naming conventions, giving direct access to parameters, locals, and globals from hand-written assembly.
The compiler mangles C variable names to avoid collisions with CPU registers and assembler keywords:
| Scope | Prefix | Storage | Addressing | Example |
|---|---|---|---|---|
| Parameter | _p_ |
Hardware stack | Stack-relative (, s) |
_p_val |
| Local | _l_ |
Hardware stack | Stack-relative (, s) |
_l_count |
| Global | _ |
Data segment | Absolute | _total |
Global variables are stored at fixed addresses in the .data segment. Access them with absolute addressing — no , s suffix needed:
volatile int g_value = 0;
void store_to_global(int val) {
// Load 16-bit parameter from stack, store to global
__asm__("ldax _p_val, s");
__asm__("stax _g_value");
}The compiler prepends _ to the C variable name: g_value becomes _g_value in assembly. For a variable named total, the assembly name would be _total. This follows the traditional C linkage convention (single leading underscore).
8-bit globals use lda/sta instead of ldax/stax:
volatile char g_flag = 0;
void set_flag(char f) {
__asm__("lda.sp _p_f"); // 8-bit stack-relative load
__asm__("sta _g_flag"); // 8-bit absolute store
}Function parameters are accessed via a saved frame pointer using the 45GS02's native ($nn,SP),Y indirect addressing mode. At function entry, the proc directive saves the stack pointer as a 16-bit pointer on the stack. Parameters are then accessed through this frame pointer with fixed Y offsets — their offsets never change regardless of how many local variables are declared.
The assembler handles this transparently. For 16-bit (int, pointers) parameters, use ldax/stax:
void process(int value) {
__asm__("ldax _p_value, s"); // Load 16-bit param into A (low) / X (high)
// ... use AX ...
}For 8-bit (char) parameters, use lda.sp/sta.sp:
void process_byte(char b) {
__asm__("lda.sp _p_b"); // Load 8-bit param into A
// ... use A ...
}The assembler detects that _p_ symbols are frame-relative and generates LDY #<offset>; LDA ($fp,SP),Y instead of the TSX; LDA $0101+offset,X sequence used for locals.
Local variables live on the stack and are accessed via SP-relative addressing (synthesized as TSX; LDA/STA $0101+offset,X). The compiler tracks their offsets via .var directives. Volatile locals are always read from and written to the stack (non-volatile locals may be optimized away):
int copy_value(int val) {
volatile int result = 0;
__asm__("ldax _p_val, s"); // Load parameter (frame-relative)
__asm__("stax _l_result, s"); // Store to local (SP-relative)
return result; // Compiler reads _l_result back
}When a function with parameters and locals is executing, the stack looks like this (growing downward):
┌──────────────────┐ ← Higher addresses
│ Caller's frame │
├──────────────────┤
│ Parameter N │ ← _p_paramN (fixed Y offset from FP)
│ ... │
│ Parameter 1 │ ← _p_param1
├──────────────────┤
│ Return address │ ← 2 bytes (pushed by JSR)
├──────────────────┤
│ Saved FP (SP) │ ← 2 bytes (pushed by proc prologue)
├──────────────────┤
│ Local var 1 │ ← _l_var1 (SP-relative, offset 0)
│ ... │
│ Local var M │ ← _l_varM
└──────────────────┘ ← SP points here
The _fp assembler variable tracks the frame pointer's position relative to SP. It starts at 1 and is automatically adjusted by .var directives as locals are pushed. Parameter offsets (Y values) are fixed at proc declaration time and never change. Local offsets remain SP-relative and shift as new locals are declared. Inline assembly uses the symbolic names with , s and the assembler resolves the correct addressing mode automatically.
-
Do not modify SP between reads. Inline
push/popinstructions change SP, which invalidates all symbolic stack offsets. Load all needed parameters into registers before pushing anything:// WRONG: push changes SP, making _p_b offset incorrect __asm__("ldax _p_a, s"); __asm__("push .ax"); // SP shifts! __asm__("ldax _p_b, s"); // reads wrong location // CORRECT: load both params first, then operate __asm__("ldax _p_a, s"); __asm__("stax $02"); // save to ZP temp (no SP change) __asm__("ldax _p_b, s"); // SP unchanged, correct offset
-
Register clobbering. After
__asm__, the compiler assumes all registers are unknown (invalidateRegs()). The compiler will reload any values it needs from the stack. -
ZP temporaries. The compiler uses ZP addresses starting at
$02(configurable via-Dcc45.zeroPageStart) for intermediate results. Simulated opcodes also use ZP$00as a scratch byte. Avoid relying on ZP values persisting across inline assembly boundaries. -
Struct members. Struct members are not emitted as individual labels. Access a member by adding its byte offset to the base variable name:
// struct { char id; int value; } s; — id at offset 0, value at offset 1 __asm__("lda.sp _l_s+0"); // s.id (8-bit) __asm__("ldax _l_s+1, s"); // s.value (16-bit)
-
@labels. Labels within inline assembly can use the@prefix (e.g.,@loop:) to signal internal branch targets. Unlike standard labels,@labels do not reset the assembler's register tracking, allowing better optimization.
Allows grouping multiple statements into a single block, which is essential for loop bodies and conditional branches.
cc45 supports the C11 alignment keywords to manage how data is positioned in memory.
The _Alignas specifier can be used to request a specific alignment (in bytes) for global variables and struct members. The alignment must be a power of two.
- Global Variables:
_Alignas(16) int buffer[256];ensures the buffer starts at an address divisible by 16. The compiler emits a.aligndirective to the assembler. - Struct Members:
_Alignason a member forces padding to be inserted before that member to satisfy the alignment requirement. The overall size of the struct is also padded to the maximum alignment of any of its members.
The _Alignof operator returns the alignment requirement of its operand type as a constant of type int.
int a = _Alignof(int); // a = 2
int b = _Alignof(char); // b = 1int: 16-bit unsigned word. Arithmetic and comparisons are performed using 16-bit logic.char: 8-bit unsigned byte. Promoted to 16-bit during most expression evaluations to maintain consistency.struct&union: Custom aggregate types.struct: Members are allocated sequentially in memory with appropriate padding for alignment.union: All members share the same base address, allowing different interpretations of the same memory. The total size is the size of the largest member, padded for max alignment.
- Anonymous Aggregates: Nested
structoruniondefinitions without a member name. Their members are "flattened" into the parent scope, allowing direct access (e.g.,s.inner_member). - Pointers: All pointers are 16-bit addresses. The compiler supports multiple levels of indirection (e.g.,
char **pp).- Dereferencing: Standard
*operator for accessing memory. - Address-of: Standard
&operator for obtaining the stack-relative address of a variable. - Arithmetic: Supports pointer addition and subtraction, scaled by the size of the pointed-to type.
- Dereferencing: Standard
volatile: Prevents the compiler from optimizing away reads or writes to the variable. Essential for I/O registers or variables shared with interrupt handlers.auto: (Classic C) Declares a variable with automatic storage duration. Inccomp, all local variables are automatic by default, so this keyword is functionally a no-op provided for compatibility.unsigned: Explicitly declares an unsigned integer type. Supported asunsigned int(16-bit),unsigned char(8-bit), or a bareunsigned(equivalent tounsigned int).signed: Explicitly declares a signed integer type. Supported assigned int(16-bit) andsigned char(8-bit). Signed types use two's complement representation. Relational operators (<,>,<=,>=) are signedness-aware when operating on these types.
The sizeof operator returns the size, in bytes, of its operand. The operand can be a type name (e.g., sizeof(int)) or an expression (e.g., sizeof x). The result is a constant of type int.
int a = sizeof(int); // a = 2
int b = sizeof(char); // b = 1
struct Point { int x, y; };
int c = sizeof(struct Point); // c = 4Note on Padding:
cc45may insert padding in structs to satisfy alignment requirements, which will be reflected in thesizeofresult.
The cast operator (type)expr performs an explicit type conversion. The compiler supports casts between all scalar types:
int big = 0x1234;
char low = (char)big; // Narrowing: keeps low byte (0x34)
char c = 200;
int wide = (int)c; // Widening: zero-extends to 16-bit (200)
char *p = &c;
int addr = (int)p; // Pointer to intNarrowing casts (int/pointer to char) discard the high byte, keeping only the low 8 bits. Widening casts (char to int/pointer) zero-extend the value to 16 bits.
Cast expressions are folded at compile time when the operand is a constant (e.g., (char)0x1FF becomes 0xFF).
When an implicit conversion loses data, the compiler emits a warning to stderr:
file.c:8:5: warning: implicit conversion loses data from 'int' to 'char'
This occurs when assigning an int or pointer value to a char variable without an explicit cast. Using a cast expression suppresses the warning, signalling that the truncation is intentional.
Note on Declaration Order: Currently, type qualifiers (
volatile,auto) must appear before the signedness specifiers (signed,unsigned). For example,volatile signed int x;is valid, butsigned volatile int x;is not yet supported.
- Register Tracking: The compiler tracks the state of
A,X,Y, andZregisters to eliminate redundant loads. - Zero Page Cache: Intermediate expression results are cached in a pool of Zero Page registers, reducing stack traffic.
- Direct Stack Access: Access to local variables and struct members on the stack is synthesized using
TSX+ absolute,X indexed addressing (e.g.,TSX; LDA $0103,X), since the 45GS02 has no native direct stack-relative load/store instructions. - Push/Pop Abstractions: Utilizes the assembler's high-level
pushandpopinstructions for 8-bit and 16-bit register saves/restores, improving code readability. - Strength Reduction: Converts expensive operations into faster equivalents (e.g.,
x * 2becomesx << 1,x % 8becomesx & 7). - Constant Propagation: Substitutes variables with known constant values into expressions at compile time.
- Constant Folding: Simple arithmetic expressions like
1 + 2are evaluated at compile time. - Dead Variable Elimination: Removes stack allocation and initialization for local variables that are initialized with constants and not subsequently used (skips
volatilevariables). - Dead Code Removal: Detects and removes code following a
returnstatement. - Segment-based Separation: Automatically organizes output into
code,data, andbsssegments. Initialized globals are placed indata, uninitialized globals inbss, and function bodies incode, allowing for optimized memory layout by the assembler. - Logical Short-circuiting: Optimizes logical
&&and||operators to jump directly to target labels, bypassing intermediate boolean conversions. - Increment Optimization: Uses
INC A,INX,INW, etc., for+ 1operations. - Tiered Branching: Automatically selects between short (8-bit) and long (16-bit) relative branches.
cc45 supports a subset of the ISO C standard (C11). It is designed to be lean and efficient for the MEGA65 architecture.
- Integer Sizes:
intis 16-bit.charis 8-bit. - Signedness: All types default to unsigned. Signedness is supported via the
signedkeyword, which enables signed comparison logic (cmp.s16). - Promotion:
chartypes are promoted to 16-bitintduring expression evaluation. - Preprocessor:
cc45includes a fully-featured integrated C preprocessor (cp45) supporting macros, file inclusion, and conditional compilation.
The following standard C keywords are not supported by cc45:
- Storage Classes:
extern,register,static. - Type Qualifiers:
const,restrict,inline. - Data Types:
float,double,long,short.
- Function Pointers: Not supported.
- Stack Alignment:
_Alignasis currently not supported for local (stack) variables. - Standard Library: Minimal. No standard C library is provided by default.
cc45 [options] <input_file.c>-E: Run only the preprocessor. Output is sent tostdoutby default, or to a file if-ois specified.-o <file>: Specify the output assembly file name (default isout.s).-c: Compile and then invokeca45to generate a binary object file (.bin).-v: Verbose mode. Prints phase status messages (preprocessing, lexing, parsing, code generation).-vv: Extra verbose mode. Includes everything from-vplus lexical token dumps and AST printing.-I<path>: Add an include search path for the preprocessor.-Dname=val: Define a symbol for the preprocessor/assembler or configure compiler parameters:-Dcc45.zeroPageStart=$addr: Set the start of the ZP register pool (default:$02).-Dcc45.zeroPageAvail=n: Set the number of ZP registers available for caching (default:9).
-O0: Disable all optimizations (constant folding, constant propagation, dead variable elimination). Useful for debugging code generation issues.-?: Display help and exit.
- Preprocessing: Invokes
cp45to handle file inclusion, macro definition, and conditional compilation. - Lexical Analysis: Strips comments and tokenizes the preprocessed source.
- Parsing: Builds an AST using recursive descent.
- Constant Folding: Simplifies the AST by evaluating constant expressions.
- Code Generation: Traverses the AST and emits high-level
ca45assembly.- Function arguments and local variables are managed via the stack using stack-relative addressing (
offset, s). - The compiler uses the
.varand.cleanupassembler directives to maintain correct stack offsets.
- Function arguments and local variables are managed via the stack using stack-relative addressing (
To prevent naming collisions between C variables and 45GS02 processor registers (A, X, Y, Z), status flags (P.C, P.Z, etc.), or internal assembler labels, cc45 uses a prefixing scheme in the generated assembly:
- Parameters: Prefixed with
_p_(e.g.,valbecomes_p_val). Declared on theprocinstruction line with size qualifiers (W#for 16-bit,B#for 8-bit). - Local Variables: Prefixed with
_l_(e.g.,ibecomes_l_i). Offsets are tracked via.vardirectives as variables are pushed onto the stack. - Global Variables: Prefixed with
_(e.g.,totalbecomes_total). Stored in the.datasegment at absolute addresses. This follows the traditional C linkage convention (single leading underscore), ensuring compatibility with other 6502 toolchains (cc65, etc.). - Functions: Prefixed with
_(e.g.,mainbecomes_main). Emitted asproc _mainand called viajsr _main.
These prefixes are applied to both variable declarations and all subsequent instructions referencing those variables. The same names are accessible from __asm__() inline assembly (see Inline Assembly and Variable Access).
To ensure labels are unique across different functions and scopes, cc45 utilizes a hierarchical scoping system in the generated assembly:
- Temporary Labels: Internal loop and branch targets are generated as
L<n>. - Fully Qualified Names: When processed by the internal assembler or viewed in an expanded listing (Level 2), these labels are prefixed with their containing procedure or scope name, separated by colons (e.g.,
main:L0:). - Scoping: This prevents collisions between common label names used in different C functions by flattening the hierarchy into globally unique identifiers.
ASTNode/ASTVisitor: The core of the compiler's architecture. Every language construct is anASTNode, andCodeGeneratoris anASTVisitorthat traverses the tree to emit code.RegState: A structure used to track the contents of the CPU registers (A,X,Y,Z). It stores whether a register's value is currentlyknown, if it holds a specificvariableandoffset, or a literalvalue.ZPReg: Manages the allocation of virtual zero-page registers used for intermediate expression results.
The compiler tracks the state of all four primary 45GS02 registers to eliminate redundant LDA and LDX instructions.
- Scope: Tracking is local to basic blocks. It is automatically invalidated (
invalidateRegs()) at control flow boundaries such as labels, branches, and function calls. - Optimization: If a variable or constant is already present in a register, the compiler will skip the corresponding load instruction.
The compiler tracks the state of the processor status register (Carry, Zero, Negative, and Overflow flags) to eliminate redundant instructions.
- TriState Logic: Flags are tracked using a TriState (
SET,CLEAR,UNKNOWN). - ZN Source Tracking: The compiler tracks which register (
A,X,Y, orZ) was the last to affect the Zero (Z) and Negative (N) flags. - Optimizations:
- Carry Flag: Redundant
CLCandSECinstructions are omitted if the carry flag is already known to be in the required state (e.g., before anADCorSBC). - CMP Elimination:
CMP #$00instructions are skipped in control flow statements (if,while, etc.) if the flags already reflect the state of the register holding the condition. - 8-bit Control Flow: Conditions using
char(8-bit) types are optimized to use a singleBEQ/BNEinstruction, bypassing the 16-bit truthiness check used forintand pointers.
- Carry Flag: Redundant
- Invalidation: Flags are invalidated at control flow boundaries (labels, branches) and after instructions that affect flags in complex ways (like
ADC,SBC, or function calls).
To add support for a new opcode or addressing mode in the compiler's output:
M65Emitter: Add a new method toinclude/M65Emitter.hppand implement it insrc/main/M65Emitter.cpp.CodeGenerator: Call the new emitter method from the appropriatevisit()function insrc/main/CodeGenerator.cpp.