This document analyzes assembler features to add to llvm-mos, split into two categories:
- General MOS features - Useful for all llvm-mos users
- ca65 compatibility mode - For cc65 interoperability
- Primary: Assemble cc65 C compiler output (
cl65 -S) - Stretch: Assemble cc65 runtime library source files
- Bonus: Add generally useful features to the MOS assembler
# Step 1: cc65 compiles C to ca65 assembly (stops before assembler)
~/git/cc65/bin/cl65 -S -t c64 -o output.s input.c
# Step 2: llvm-mc assembles ca65 assembly to ELF object
llvm-mc -triple=mos -x ca65 output.s -filetype=obj -o output.o
# Step 3: lld links ELF objects
ld.lld output.o runtime.o -o outputUsage counts from libsrc/runtime/*.s, libsrc/common/*.s, asminc/*.inc:
| Directive | Count | Category |
|---|---|---|
.charmap |
1280 | Character encoding |
.proc/.endproc |
130 each | Procedures |
.if/.else/.endif |
84/77/93 | Conditionals |
.struct/.endstruct |
42 each | Data structures |
.code |
39 | Segments |
.elseif |
35 | Conditionals |
.macro/.endmacro |
25 each | Macros |
.bss |
20 | Segments |
.global |
18 | Symbols |
.include |
15 | File inclusion |
.data |
15 | Segments |
.define |
10 | Text substitution |
.enum/.endenum |
10 each | Enumerations |
.rodata |
8 | Segments |
.export |
7 | Symbols |
.segment |
6 | Segments |
.import |
6 | Symbols |
.globalzp |
5 | Symbols |
.repeat/.endrepeat |
4 each | Loops |
.importzp |
4 | Symbols |
These features benefit all llvm-mos users, not just cc65 compatibility. They should be added to the main MOS assembler parser.
- Commodore systems use PETSCII, not ASCII
- Atari systems use ATASCII
- Apple II has its own character set
- Any llvm-mos user targeting these platforms needs correct string encoding
- This isn't ca65-specific - it's a fundamental 6502 development need
Maps source character codes to target character codes for string literals:
; Map ASCII lowercase to PETSCII
.charmap $61, $01 ; 'a' -> PETSCII $01
.charmap $62, $02 ; 'b' -> PETSCII $02
; ...
.byte "hello" ; Assembles to PETSCII, not ASCII- Difficulty: Medium
- Approach:
- Add
uint8_t CharMap[256]to MOS assembler state, initialized to identity - Add
.charmapdirective handler to update the table - Hook string literal parsing to apply translation
- Affects
.byte "...",.asciiz "...", and similar directives
- Add
- LLVM's
AsmParserhasparseEscapedString()that handles string literals - We need to intercept after escape processing but before byte emission
- May need to override string directive handlers in
MOSAsmParser - Alternative: Handle
.byte,.asciizentirely in MOS parser when strings present
Could ship predefined maps as include files:
petscii.inc- Commodore PETSCIIatascii.inc- Atari ATASCIIapple2.inc- Apple II character set
Or provide a .charmap_petscii / .charmap_atascii shortcut directive.
- Useful for defining hardware register layouts (VIC-II, SID, CIA, POKEY, etc.)
- Standard feature in many assemblers (MASM, NASM, ca65, etc.)
- Not ca65-specific at all - just good assembler functionality
- Makes assembly code more readable and maintainable
.struct VIC
sprite0_x .byte ; offset 0
sprite0_y .byte ; offset 1
sprite1_x .byte ; offset 2
sprite1_y .byte ; offset 3
; ... etc
border .byte ; offset 32 ($20)
background .byte ; offset 33 ($21)
.endstruct
; Usage:
lda VIC_BASE + VIC::border ; Load border color
sta VIC_BASE + VIC::background
; Size:
lda #.sizeof(VIC) ; Get structure sizestructname::fieldname= byte offset of field.sizeof(structname)= total size of struct
| Type | Size | Notes |
|---|---|---|
.byte |
1 | Single byte |
.word |
2 | 16-bit little-endian |
.addr |
2 | Same as .word (address) |
.dword |
4 | 32-bit |
.res N |
N | Reserved bytes |
- Difficulty: Hard
- Approach:
- Track struct definitions in a map:
structname→{fields, total_size} - Each field:
{name, offset, size} - When parsing
structname::fieldname, look up and return offset value - Implement
.sizeof()as expression function
- Track struct definitions in a map:
struct StructField {
StringRef Name;
int64_t Offset;
int64_t Size;
};
struct StructDef {
StringRef Name;
std::vector<StructField> Fields;
int64_t TotalSize;
};
// In MOSAsmParser:
StringMap<StructDef> StructDefs;- Need to handle
foo::barin expression parsing - When encountering
identifier::identifier, look up in struct definitions - If found, substitute the offset value
ca65 supports nested structs and .union/.endunion:
.struct CIA
PRA .byte
PRB .byte
.union
.struct
TALO .byte
TAHI .byte
.endstruct
TA .word
.endunion
.endstructRecommendation: Start without union support, add later if needed.
- Standard feature for defining related constants
- More readable than manual
.setsequences - Not ca65-specific
.enum JoyDirection
NONE ; = 0
UP ; = 1
DOWN ; = 2
LEFT ; = 3
RIGHT ; = 4
.endenum
lda joy_state
cmp #JoyDirection::UP
beq handle_up- Difficulty: Medium
- Approach:
- Parse enum name
- For each identifier, assign next sequential value (or explicit value if given)
- Create symbols as
enumname::membername= value - Track enum for potential
.sizeof()support
- No offset calculation needed
- Just sequential value assignment
- Can reuse
::scoped symbol infrastructure from structs
- Useful for generating tables, unrolled loops
- Standard assembler feature
.repeat 8, i
lda table + i
sta dest + i
.endrepeat
; Expands to 8 load/store pairs- Difficulty: Easy
- LLVM equivalent:
.rept/.endr - Just need to alias the directive names
- LLVM's
.reptalready exists
- Useful for catching assembly-time errors
- Validates assumptions about addresses, sizes, etc.
.assert * < $C000, error, "Code too large for RAM"
.assert .sizeof(buffer) = 256, error, "Buffer must be 256 bytes"- Difficulty: Easy
- LLVM equivalent: Conditional
.error/.warning - Parse expression, evaluate, emit error/warning if false
- Prevents label collisions in larger projects
- More readable than numeric labels (
1:,1b,1f) - Common need in assembly programming
ca65 uses @label for local labels scoped to .proc:
.proc foo
@loop:
dex
bne @loop
.endprocInstead of ca65's @ syntax, could use a more general scoping mechanism:
.scope foo
.local loop
loop:
dex
bne loop
.endscopeOption A: Support @label in general parser
- Requires lexer changes to recognize
@as label prefix - Transform to
.Lscope_labelinternally - Simpler for users
Option B: Use LLVM's existing local label mechanism
- LLVM has
.Lprefix for local symbols - Less convenient but already works
Option C: Add general .scope/.endscope with .local
- More verbose but more explicit
- Works with existing lexer
Recommendation: Add @label support to general parser - it's widely understood syntax.
- Difficulty: Medium
- Approach:
- Track current scope name (from
.proc,.scope, or function label) - When lexer sees
@identifier, transform to.L{scope}_{identifier} - Requires lexer modification in MOS target
- Track current scope name (from
These features are ca65-specific and should only be active in ca65 compatibility mode (-x ca65).
llvm-mc -triple=mos -x ca65 input.s -o output.o- Add
CA65Modeflag toMOSAsmParser - Set based on
-x ca65command line option - CA65-specific directives only recognized when flag is set
- ca65 meaning: Make symbol visible to linker
- LLVM equivalent:
.globl sym - Implementation: Parse symbol list, emit
.globlfor each
- ca65 meaning: Declare external symbol
- LLVM equivalent: No-op (LLVM auto-resolves undefined symbols)
- Implementation: Parse and ignore
- ca65 meaning: Export + Import (make visible, resolve if external)
- LLVM equivalent:
.globl sym
- ca65 meaning: Import as zero-page symbol
- LLVM equivalent:
.zeropage sym(already in MOS)
- ca65 meaning: Export as zero-page symbol
- LLVM equivalent:
.globl sym+.zeropage sym
- ca65 meaning: Global + zero-page
- LLVM equivalent:
.globl sym+.zeropage sym
- ca65 meaning: Switch to named segment
- LLVM equivalent:
.section NAME - Mapping:
ca65 Segment ELF Section "CODE".text"DATA".data"BSS".bss"RODATA".rodata"ZEROPAGE".zeropageOther Pass through as-is
- LLVM equivalent:
.section .text,"ax",@progbits
- LLVM equivalent:
.section .data,"aw",@progbits
- LLVM equivalent:
.section .bss,"aw",@nobits
This document analyzes assembler features to add to llvm-mos, split into two categories:
- General MOS features - Useful for all llvm-mos users
- ca65 compatibility mode - For cc65 interoperability
- Primary: Assemble cc65 C compiler output (
cl65 -S) - Stretch: Assemble cc65 runtime library source files
- Bonus: Add generally useful features to the MOS assembler
# Step 1: cc65 compiles C to ca65 assembly (stops before assembler)
~/git/cc65/bin/cl65 -S -t c64 -o output.s input.c
# Step 2: llvm-mc assembles ca65 assembly to ELF object
llvm-mc -triple=mos -x ca65 output.s -filetype=obj -o output.o
# Step 3: lld links ELF objects
ld.lld output.o runtime.o -o outputUsage counts from libsrc/runtime/*.s, libsrc/common/*.s, asminc/*.inc:
| Directive | Count | Category |
|---|---|---|
.charmap |
1280 | Character encoding |
.proc/.endproc |
130 each | Procedures |
.if/.else/.endif |
84/77/93 | Conditionals |
.struct/.endstruct |
42 each | Data structures |
.code |
39 | Segments |
.elseif |
35 | Conditionals |
.macro/.endmacro |
25 each | Macros |
.bss |
20 | Segments |
.global |
18 | Symbols |
.include |
15 | File inclusion |
.data |
15 | Segments |
.define |
10 | Text substitution |
.enum/.endenum |
10 each | Enumerations |
.rodata |
8 | Segments |
.export |
7 | Symbols |
.segment |
6 | Segments |
.import |
6 | Symbols |
.globalzp |
5 | Symbols |
.repeat/.endrepeat |
4 each | Loops |
.importzp |
4 | Symbols |
These features benefit all llvm-mos users, not just cc65 compatibility. They should be added to the main MOS assembler parser.
- Commodore systems use PETSCII, not ASCII
- Atari systems use ATASCII
- Apple II has its own character set
- Any llvm-mos user targeting these platforms needs correct string encoding
- This isn't ca65-specific - it's a fundamental 6502 development need
Maps source character codes to target character codes for string literals:
; Map ASCII lowercase to PETSCII
.charmap $61, $01 ; 'a' -> PETSCII $01
.charmap $62, $02 ; 'b' -> PETSCII $02
; ...
.byte "hello" ; Assembles to PETSCII, not ASCII- Difficulty: Medium
- Approach:
- Add
uint8_t CharMap[256]to MOS assembler state, initialized to identity - Add
.charmapdirective handler to update the table - Hook string literal parsing to apply translation
- Affects
.byte "...",.asciiz "...", and similar directives
- Add
- LLVM's
AsmParserhasparseEscapedString()that handles string literals - We need to intercept after escape processing but before byte emission
- May need to override string directive handlers in
MOSAsmParser - Alternative: Handle
.byte,.asciizentirely in MOS parser when strings present
Could ship predefined maps as include files:
petscii.inc- Commodore PETSCIIatascii.inc- Atari ATASCIIapple2.inc- Apple II character set
Or provide a .charmap_petscii / .charmap_atascii shortcut directive.
- Useful for defining hardware register layouts (VIC-II, SID, CIA, POKEY, etc.)
- Standard feature in many assemblers (MASM, NASM, ca65, etc.)
- Not ca65-specific at all - just good assembler functionality
- Makes assembly code more readable and maintainable
.struct VIC
sprite0_x .byte ; offset 0
sprite0_y .byte ; offset 1
sprite1_x .byte ; offset 2
sprite1_y .byte ; offset 3
; ... etc
border .byte ; offset 32 ($20)
background .byte ; offset 33 ($21)
.endstruct
; Usage:
lda VIC_BASE + VIC::border ; Load border color
sta VIC_BASE + VIC::background
; Size:
lda #.sizeof(VIC) ; Get structure sizestructname::fieldname= byte offset of field.sizeof(structname)= total size of struct
| Type | Size | Notes |
|---|---|---|
.byte |
1 | Single byte |
.word |
2 | 16-bit little-endian |
.addr |
2 | Same as .word (address) |
.dword |
4 | 32-bit |
.res N |
N | Reserved bytes |
- Difficulty: Hard
- Approach:
- Track struct definitions in a map:
structname→{fields, total_size} - Each field:
{name, offset, size} - When parsing
structname::fieldname, look up and return offset value - Implement
.sizeof()as expression function
- Track struct definitions in a map:
struct StructField {
StringRef Name;
int64_t Offset;
int64_t Size;
};
struct StructDef {
StringRef Name;
std::vector<StructField> Fields;
int64_t TotalSize;
};
// In MOSAsmParser:
StringMap<StructDef> StructDefs;- Need to handle
foo::barin expression parsing - When encountering
identifier::identifier, look up in struct definitions - If found, substitute the offset value
ca65 supports nested structs and .union/.endunion:
.struct CIA
PRA .byte
PRB .byte
.union
.struct
TALO .byte
TAHI .byte
.endstruct
TA .word
.endunion
.endstructRecommendation: Start without union support, add later if needed.
- Standard feature for defining related constants
- More readable than manual
.setsequences - Not ca65-specific
.enum JoyDirection
NONE ; = 0
UP ; = 1
DOWN ; = 2
LEFT ; = 3
RIGHT ; = 4
.endenum
lda joy_state
cmp #JoyDirection::UP
beq handle_up- Difficulty: Medium
- Approach:
- Parse enum name
- For each identifier, assign next sequential value (or explicit value if given)
- Create symbols as
enumname::membername= value - Track enum for potential
.sizeof()support
- No offset calculation needed
- Just sequential value assignment
- Can reuse
::scoped symbol infrastructure from structs
- Useful for generating tables, unrolled loops
- Standard assembler feature
.repeat 8, i
lda table + i
sta dest + i
.endrepeat
; Expands to 8 load/store pairs- Difficulty: Easy
- LLVM equivalent:
.rept/.endr - Just need to alias the directive names
- LLVM's
.reptalready exists
- Useful for catching assembly-time errors
- Validates assumptions about addresses, sizes, etc.
.assert * < $C000, error, "Code too large for RAM"
.assert .sizeof(buffer) = 256, error, "Buffer must be 256 bytes"- Difficulty: Easy
- LLVM equivalent: Conditional
.error/.warning - Parse expression, evaluate, emit error/warning if false
- Prevents label collisions in larger projects
- More readable than numeric labels (
1:,1b,1f) - Common need in assembly programming
ca65 uses @label for local labels scoped to .proc:
.proc foo
@loop:
dex
bne @loop
.endprocInstead of ca65's @ syntax, could use a more general scoping mechanism:
.scope foo
.local loop
loop:
dex
bne loop
.endscopeOption A: Support @label in general parser
- Requires lexer changes to recognize
@as label prefix - Transform to
.Lscope_labelinternally - Simpler for users
Option B: Use LLVM's existing local label mechanism
- LLVM has
.Lprefix for local symbols - Less convenient but already works
Option C: Add general .scope/.endscope with .local
- More verbose but more explicit
- Works with existing lexer
Recommendation: Add @label support to general parser - it's widely understood syntax.
- Difficulty: Medium
- Approach:
- Track current scope name (from
.proc,.scope, or function label) - When lexer sees
@identifier, transform to.L{scope}_{identifier} - Requires lexer modification in MOS target
- Track current scope name (from
These features are ca65-specific and should only be active in ca65 compatibility mode (-x ca65).
llvm-mc -triple=mos -x ca65 input.s -o output.o- Add
CA65Modeflag toMOSAsmParser - Set based on
-x ca65command line option - CA65-specific directives only recognized when flag is set
- ca65 meaning: Make symbol visible to linker
- LLVM equivalent:
.globl sym - Implementation: Parse symbol list, emit
.globlfor each
- ca65 meaning: Declare external symbol
- LLVM equivalent: No-op (LLVM auto-resolves undefined symbols)
- Implementation: Parse and ignore
- ca65 meaning: Export + Import (make visible, resolve if external)
- LLVM equivalent:
.globl sym
- ca65 meaning: Import as zero-page symbol
- LLVM equivalent:
.zeropage sym(already in MOS)
- ca65 meaning: Export as zero-page symbol
- LLVM equivalent:
.globl sym+.zeropage sym
- ca65 meaning: Global + zero-page
- LLVM equivalent:
.globl sym+.zeropage sym
- ca65 meaning: Switch to named segment
- LLVM equivalent:
.section NAME - Mapping:
ca65 Segment ELF Section "CODE".text"DATA".data"BSS".bss"RODATA".rodata"ZEROPAGE".zeropageOther Pass through as-is
- LLVM equivalent:
.section .text,"ax",@progbits
- LLVM equivalent:
.section .data,"aw",@progbits
- LLVM equivalent:
.section .bss,"aw",@nobits
- LLVM equivalent:
.section .rodata,"a",@progbits
- ca65 meaning: Start procedure (creates label, starts local scope)
- LLVM translation:
- Emit label
name: - Set current scope to
namefor@labelhandling
- Emit label
- Notes:
near/farmodifier is for ld65, can ignore
- ca65 meaning: End procedure
- LLVM translation: Clear current scope name
These ca65 directives control assembler behavior that doesn't apply to LLVM:
| Directive | ca65 Meaning | LLVM Handling |
|---|---|---|
.fopt compiler,"..." |
File metadata | No-op |
.smart on|off |
Smart mode | No-op |
.autoimport on|off |
Auto-import | No-op (always on) |
.case on|off |
Case sensitivity | No-op (always on) |
- ca65 meaning: Set target CPU
- Options:
- No-op (use command-line
-mcpu) - Switch subtarget features mid-file (complex)
- No-op (use command-line
- Recommendation: No-op with warning if different from command-line
- ca65 meaning: Enable/disable debug info
- LLVM: Could toggle
.file/.locemission - Recommendation: No-op initially
- ca65 meaning: Conditional based on CPU type
- LLVM translation: Define predefined symbols at init:
__CPU_6502__ = 1 ; for 6502 __CPU_65C02__ = 1 ; for 65C02 __CPU_4510__ = 1 ; for 4510 __CPU_45GS02__ = 1 ; for 45GS02 - Then translate
.ifp02to.if __CPU_6502__
- ca65 meaning: Test CPU capability
- LLVM translation: Define capability symbols:
CPU_HAS_ZPIND = 1 ; 65C02+ indirect without Y CPU_HAS_INA = 1 ; 65C02+ INC A CPU_HAS_STZ = 1 ; 65C02+ store zero CPU_HAS_BRA8 = 1 ; 65C02+ unconditional branch - Then
.cap()is just symbol lookup
.macpack longbranch ; Include long branch macros
.macpack generic ; Include generic macrosDefines pseudo-instructions for branch-over-jump patterns:
jeq target ; Expands to: beq *+5 / jmp target (if target far)
jne target ; bne *+5 / jmp target
jmi target ; etc.Option A: Ship macro definition files
- Create
macpack-longbranch.incwith LLVM.macrodefinitions .macpack longbranch→.include "macpack-longbranch.inc"- Pros: Simple, maintainable
- Cons: Extra files to ship
Option B: Implement as pseudo-instructions
- Add
JEQ,JNE, etc. to instruction table - Expand during assembly
- Pros: More efficient
- Cons: More complex
Recommendation: Option A (ship macro files)
Defines convenience macros:
add- Add to accumulatorsub- Subtract from accumulatorbge- Branch if greater or equal (unsigned)blt- Branch if less than (unsigned)- etc.
.define VERSION "1.0"
.define DOUBLE(x) ((x) * 2)
.byte VERSION ; Expands to .byte "1.0"
lda #DOUBLE(5) ; Expands to lda #((5) * 2)- Conflicts with LLVM's
.setsemantics - Requires lexer-level text substitution
- Complex to implement correctly
- Difficulty: Medium-Hard
- Approach:
- Track defined text macros
- During tokenization, check if identifier is defined macro
- If so, substitute text and re-tokenize
- Alternative: Only support simple (non-parameterized) defines
- LLVM equivalent:
.2byte valfor each - Notes: LLVM
.wordis target-dependent, use.2byteexplicitly
- LLVM equivalent:
.2byte valfor each - Notes: Same as
.word(16-bit address)
- LLVM equivalent:
.skip nor.fill n, 1, fill
Add to main MOS parser:
.charmapdirective + string translation infrastructure.repeat/.endrepeatas aliases for.rept/.endr.assertdirective
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp
Add ca65 compatibility layer:
-x ca65command-line flag and mode tracking- Symbol directives:
.export,.import,.importzp,.exportzp,.global,.globalzp - Segment directives:
.segment,.code,.data,.bss,.rodata - Procedure directives:
.proc,.endproc - Control directives:
.fopt,.smart,.autoimport,.case,.setcpu,.debuginfo(all no-op)
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp(mode flag, delegation)llvm/lib/Target/MOS/AsmParser/MOSCA65AsmParser.cpp(new file, ca65 handlers)
- Track current scope (procedure name)
- Lexer modification to recognize
@label - Transform
@labelto.L{scope}_{label}
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp
Add to main MOS parser:
.struct/.endstructdirective handlers- Struct definition tracking
structname::fieldnameexpression parsing.sizeof()expression function
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp
Add to main MOS parser:
.enum/.endenumdirective handlers- Reuse
::scoping from structs
Add to ca65 mode:
- Define
__CPU_*andCPU_HAS_*symbols at init .ifp02/etc. directive handlers.cap()expression function
- Create
macpack-longbranch.inc,macpack-generic.inc .macpackdirective handler →.include
.definesupport (if needed)- Error messages
- Test suite
- Documentation
| Feature | Risk | Mitigation |
|---|---|---|
.charmap |
Medium - needs string interception | May need to handle string directives entirely in MOS |
.struct |
High - significant new feature | Start simple, no nested structs/unions initially |
:: scoping |
Medium - affects expression parsing | Careful integration with existing parser |
.sizeof() |
Medium - tied to struct | Required for structs to be useful |
@label |
Low - straightforward transform | Lexer modification needed |
.define |
High - lexer-level changes | Defer or limit to simple cases |
llvm/test/MC/MOS/
├── charmap.s # Character encoding tests
├── struct.s # Structure definition tests
├── enum.s # Enumeration tests
├── ca65-directives.s # ca65 mode directive tests
├── ca65-segments.s # Segment translation tests
├── ca65-local-labels.s # @label handling tests
└── ca65-macpack.s # Macro package tests
- Assemble actual cc65 runtime files
- Compare output with ca65-assembled versions
# Phase 1-2 complete when this works:
echo 'int add(int a, int b) { return a+b; }' > test.c
cl65 -S -t none -o test.s test.c
llvm-mc -triple=mos -x ca65 test.s -filetype=obj -o test.o
# Phase 3-7 complete when this works:
llvm-mc -triple=mos -x ca65 ~/git/cc65/libsrc/runtime/add.s -filetype=obj -o add.o-
ca65 Users Guide - Official ca65 documentation
-
cc65 Users Guide - C compiler documentation
-
cc65 source:
~/git/cc65/ -
LLVM MC documentation:
llvm/docs/MCInternals.rst -
LLVM equivalent:
.section .rodata,"a",@progbits
- ca65 meaning: Start procedure (creates label, starts local scope)
- LLVM translation:
- Emit label
name: - Set current scope to
namefor@labelhandling
- Emit label
- Notes:
near/farmodifier is for ld65, can ignore
- ca65 meaning: End procedure
- LLVM translation: Clear current scope name
These ca65 directives control assembler behavior that doesn't apply to LLVM:
| Directive | ca65 Meaning | LLVM Handling |
|---|---|---|
.fopt compiler,"..." |
File metadata | No-op |
.smart on|off |
Smart mode | No-op |
.autoimport on|off |
Auto-import | No-op (always on) |
.case on|off |
Case sensitivity | No-op (always on) |
- ca65 meaning: Set target CPU
- Options:
- No-op (use command-line
-mcpu) - Switch subtarget features mid-file (complex)
- No-op (use command-line
- Recommendation: No-op with warning if different from command-line
- ca65 meaning: Enable/disable debug info
- LLVM: Could toggle
.file/.locemission - Recommendation: No-op initially
- ca65 meaning: Conditional based on CPU type
- LLVM translation: Define predefined symbols at init:
__CPU_6502__ = 1 ; for 6502 __CPU_65C02__ = 1 ; for 65C02 __CPU_4510__ = 1 ; for 4510 __CPU_45GS02__ = 1 ; for 45GS02 - Then translate
.ifp02to.if __CPU_6502__
- ca65 meaning: Test CPU capability
- LLVM translation: Define capability symbols:
CPU_HAS_ZPIND = 1 ; 65C02+ indirect without Y CPU_HAS_INA = 1 ; 65C02+ INC A CPU_HAS_STZ = 1 ; 65C02+ store zero CPU_HAS_BRA8 = 1 ; 65C02+ unconditional branch - Then
.cap()is just symbol lookup
.macpack longbranch ; Include long branch macros
.macpack generic ; Include generic macrosDefines pseudo-instructions for branch-over-jump patterns:
jeq target ; Expands to: beq *+5 / jmp target (if target far)
jne target ; bne *+5 / jmp target
jmi target ; etc.Option A: Ship macro definition files
- Create
macpack-longbranch.incwith LLVM.macrodefinitions .macpack longbranch→.include "macpack-longbranch.inc"- Pros: Simple, maintainable
- Cons: Extra files to ship
Option B: Implement as pseudo-instructions
- Add
JEQ,JNE, etc. to instruction table - Expand during assembly
- Pros: More efficient
- Cons: More complex
Recommendation: Option A (ship macro files)
Defines convenience macros:
add- Add to accumulatorsub- Subtract from accumulatorbge- Branch if greater or equal (unsigned)blt- Branch if less than (unsigned)- etc.
.define VERSION "1.0"
.define DOUBLE(x) ((x) * 2)
.byte VERSION ; Expands to .byte "1.0"
lda #DOUBLE(5) ; Expands to lda #((5) * 2)- Conflicts with LLVM's
.setsemantics - Requires lexer-level text substitution
- Complex to implement correctly
- Difficulty: Medium-Hard
- Approach:
- Track defined text macros
- During tokenization, check if identifier is defined macro
- If so, substitute text and re-tokenize
- Alternative: Only support simple (non-parameterized) defines
- LLVM equivalent:
.2byte valfor each - Notes: LLVM
.wordis target-dependent, use.2byteexplicitly
- LLVM equivalent:
.2byte valfor each - Notes: Same as
.word(16-bit address)
- LLVM equivalent:
.skip nor.fill n, 1, fill
Add to main MOS parser:
.charmapdirective + string translation infrastructure.repeat/.endrepeatas aliases for.rept/.endr.assertdirective
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp
Add ca65 compatibility layer:
-x ca65command-line flag and mode tracking- Symbol directives:
.export,.import,.importzp,.exportzp,.global,.globalzp - Segment directives:
.segment,.code,.data,.bss,.rodata - Procedure directives:
.proc,.endproc - Control directives:
.fopt,.smart,.autoimport,.case,.setcpu,.debuginfo(all no-op)
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp(mode flag, delegation)llvm/lib/Target/MOS/AsmParser/MOSCA65AsmParser.cpp(new file, ca65 handlers)
- Track current scope (procedure name)
- Lexer modification to recognize
@label - Transform
@labelto.L{scope}_{label}
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp
Add to main MOS parser:
.struct/.endstructdirective handlers- Struct definition tracking
structname::fieldnameexpression parsing.sizeof()expression function
Files:
llvm/lib/Target/MOS/AsmParser/MOSAsmParser.cpp
Add to main MOS parser:
.enum/.endenumdirective handlers- Reuse
::scoping from structs
Add to ca65 mode:
- Define
__CPU_*andCPU_HAS_*symbols at init .ifp02/etc. directive handlers.cap()expression function
- Create
macpack-longbranch.inc,macpack-generic.inc .macpackdirective handler →.include
.definesupport (if needed)- Error messages
- Test suite
- Documentation
| Feature | Risk | Mitigation |
|---|---|---|
.charmap |
Medium - needs string interception | May need to handle string directives entirely in MOS |
.struct |
High - significant new feature | Start simple, no nested structs/unions initially |
:: scoping |
Medium - affects expression parsing | Careful integration with existing parser |
.sizeof() |
Medium - tied to struct | Required for structs to be useful |
@label |
Low - straightforward transform | Lexer modification needed |
.define |
High - lexer-level changes | Defer or limit to simple cases |
llvm/test/MC/MOS/
├── charmap.s # Character encoding tests
├── struct.s # Structure definition tests
├── enum.s # Enumeration tests
├── ca65-directives.s # ca65 mode directive tests
├── ca65-segments.s # Segment translation tests
├── ca65-local-labels.s # @label handling tests
└── ca65-macpack.s # Macro package tests
- Assemble actual cc65 runtime files
- Compare output with ca65-assembled versions
# Phase 1-2 complete when this works:
echo 'int add(int a, int b) { return a+b; }' > test.c
cl65 -S -t none -o test.s test.c
llvm-mc -triple=mos -x ca65 test.s -filetype=obj -o test.o
# Phase 3-7 complete when this works:
llvm-mc -triple=mos -x ca65 ~/git/cc65/libsrc/runtime/add.s -filetype=obj -o add.o- ca65 Users Guide - Official ca65 documentation
- cc65 Users Guide - C compiler documentation
- cc65 source:
~/git/cc65/ - LLVM MC documentation:
llvm/docs/MCInternals.rst