diff --git a/commands.md b/commands.md index 3af78bf..e8d710a 100644 --- a/commands.md +++ b/commands.md @@ -113,12 +113,14 @@ WEND ## BYTE -Declares an 8-bit variable or constant. +Declares an 8-bit variable, register-hinted temporary, or constant. **Syntax:** ``` BYTE BYTE = +BYTE REGISTER +BYTE REGISTER = BYTE @
BYTE CONST = ``` @@ -127,10 +129,17 @@ BYTE CONST = ``` BYTE counter BYTE speed = 5 +BYTE REGISTER temp +BYTE REGISTER scratch = 0 BYTE screen @ $D020 BYTE CONST MAX_SPEED = 10 ``` +`REGISTER` is a storage hint for the optimizer. The variable may be kept in a +CPU register (A/X/Y) and its memory allocation can be eliminated entirely. +Only valid inside `FUNC`/`FEND` blocks; incompatible with `@` and `CONST`. +See the REGISTER section in `language.md` for details. + --- ## CALL diff --git a/editor_syntaxes/kate/c65cm.xml b/editor_syntaxes/kate/c65cm.xml index 9cf6202..262d745 100644 --- a/editor_syntaxes/kate/c65cm.xml +++ b/editor_syntaxes/kate/c65cm.xml @@ -43,6 +43,7 @@ POINTER POKE POKEW + REGISTER SCRIPT STEP SUBEND diff --git a/editor_syntaxes/sublime/c65cm.sublime-syntax b/editor_syntaxes/sublime/c65cm.sublime-syntax index 5b77414..092dd1d 100644 --- a/editor_syntaxes/sublime/c65cm.sublime-syntax +++ b/editor_syntaxes/sublime/c65cm.sublime-syntax @@ -38,7 +38,7 @@ contexts: scope: meta.preprocessor.c65cm keywords: - - match: '\b(ADD|AND|AS|ASM|BREAK|BYTE|CALL|CASE|CONST|DEC|DECREMENT|DEFAULT|DO|ELSE|ENDASM|ENDIF|ENDSCRIPT|ENDSWITCH|EXIT|FEND|FOR|FUNC|GOSUB|GOTO|IF|INC|INCREMENT|LABEL|LET|LIBRARY|MACRO|NEXT|OR|ORIGIN|PASSING|PEEK|PEEKW|POINTER|POKE|POKEW|SCRIPT|STEP|SUBEND|SUBTRACT|SWITCH|THEN|TO|WHILE|WITH|WEND|WORD|XOR)\b' + - match: '\b(ADD|AND|AS|ASM|BREAK|BYTE|CALL|CASE|CONST|DEC|DECREMENT|DEFAULT|DO|ELSE|ENDASM|ENDIF|ENDSCRIPT|ENDSWITCH|EXIT|FEND|FOR|FUNC|GOSUB|GOTO|IF|INC|INCREMENT|LABEL|LET|LIBRARY|MACRO|NEXT|OR|ORIGIN|PASSING|PEEK|PEEKW|POINTER|POKE|POKEW|REGISTER|SCRIPT|STEP|SUBEND|SUBTRACT|SWITCH|THEN|TO|WHILE|WITH|WEND|WORD|XOR)\b' scope: keyword.control.c65cm - match: '\b(in|out|io)\b(?=\s*:)' scope: storage.modifier.parameter.c65cm diff --git a/internal/optimizer/optimizer_test.go b/internal/optimizer/optimizer_test.go index 01cc7c8..eb74871 100644 --- a/internal/optimizer/optimizer_test.go +++ b/internal/optimizer/optimizer_test.go @@ -732,3 +732,36 @@ func TestPassRegDead_CallSiteKeepsStore(t *testing.T) { t.Errorf("expected sta call_val to be kept (before jsr), got:\n%s", joined) } } + +func TestPassRegDead_InitValueFlowsThrough(t *testing.T) { + // BYTE REGISTER x = 42; POKE $d020, x → value flows through A, never touches RAM + input := []string{ + "\tlda #$2a", + "\tsta test_x", + "\tlda test_x", + "\tsta 53280", + "\trts", + } + + cfg := &Config{ + EnableRegisterVars: true, + EnableStoreLoad: true, + RegisterVars: map[string]bool{ + "test_x": true, + }, + } + + output, dissolved := Optimize(input, cfg) + + if !dissolved["test_x"] { + t.Error("test_x should be dissolved — init value flows through A to POKE") + } + + joined := strings.Join(output, "\n") + if strings.Contains(joined, "test_x") { + t.Errorf("expected no reference to test_x in output, got:\n%s", joined) + } + if !strings.Contains(joined, "lda #$2a") { + t.Error("init value lda #$2a should survive") + } +} diff --git a/language.md b/language.md index 500ac55..5fec5df 100644 --- a/language.md +++ b/language.md @@ -88,12 +88,40 @@ initialize() BYTE variables store 8-bit values (0-255). ```c65 -BYTE count // Uninitialized +BYTE count // Uninitialized (0) BYTE speed = 5 // Initialized to 5 +BYTE REGISTER temp // Register-hinted (default 0) +BYTE REGISTER scratch = 0 // Register-hinted with init value BYTE screen @ $D020 // Memory-mapped to specific address -BYTE CONST MAX_SPEED = 10 // Constant (recommended over #DEFINE) +BYTE CONST MAX_SPEED = 10 // Constant ``` +### REGISTER Variables + +The `REGISTER` hint tells the optimizer that a BYTE variable's value only matters as +it flows through the computation — never its stored location. The compiler can keep +the value in a CPU register (A, X, or Y) and eliminate dead stores and memory +allocations entirely. + +```c65 +FUNC fast_copy + BYTE REGISTER b + b = PEEK $d011 + b = b | 32 + POKE $d011, b // b never touches RAM — flows through A +FEND +``` + +**Rules:** + +- `BYTE REGISTER` is only valid inside `FUNC`/`FEND` blocks (function-local only) +- Cannot be combined with `@` (memory-mapped) or `CONST` +- Cannot be referenced from `ASM`, `SCRIPT`, or `MACRO` blocks via `|varname|` +- `WORD REGISTER` is not supported (the 6502 has no 16-bit ALU register) +- Without `--opt`, `REGISTER` behaves identically to a normal `BYTE` +- With `--opt`, the optimizer may dissolve the variable, eliminating its `!8` + allocation and all store/load operations + ### WORD Variables WORD variables store 16-bit values (0-65535). @@ -109,7 +137,7 @@ WORD CONST SCREEN_RAM = $0400 // Constant ### Memory-Mapped Variables -Variables can be placed at specific addresses using `@`: +Variables can be placed at specific addresses using `@`. Not compatible with `REGISTER`. ```c65 BYTE borderColor @ $D020 // VIC-II border color