First naive version of optimizer
This commit is contained in:
parent
59b53fb5f0
commit
037d232c55
51 changed files with 941 additions and 10 deletions
|
|
@ -231,3 +231,6 @@ func (c *AddCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *AddCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *AddCommand) GetName() string { return "ADD" }
|
||||
|
|
|
|||
|
|
@ -229,3 +229,6 @@ func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *AndCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *AndCommand) GetName() string { return "AND" }
|
||||
|
|
|
|||
|
|
@ -49,3 +49,6 @@ func (c *BreakCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
fmt.Sprintf("\tjmp %s", c.skipLabel),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *BreakCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *BreakCommand) GetName() string { return "BREAK" }
|
||||
|
|
|
|||
|
|
@ -149,3 +149,6 @@ func (c *ByteCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
// Variables are rendered by assembleOutput, not by individual commands
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *ByteCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *ByteCommand) GetName() string { return "BYTE" }
|
||||
|
|
|
|||
|
|
@ -75,3 +75,6 @@ func (c *CallCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
|
|||
func (c *CallCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
return c.asmOutput, nil
|
||||
}
|
||||
|
||||
func (c *CallCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *CallCommand) GetName() string { return "CALL" }
|
||||
|
|
|
|||
|
|
@ -148,3 +148,6 @@ func (c *CaseCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *CaseCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *CaseCommand) GetName() string { return "CASE" }
|
||||
|
|
|
|||
|
|
@ -139,3 +139,6 @@ func (c *DecrCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *DecrCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *DecrCommand) GetName() string { return "DEC" }
|
||||
|
|
|
|||
|
|
@ -76,3 +76,6 @@ func (c *DefaultCommand) Generate(ctx *compiler.CompilerContext) ([]string, erro
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *DefaultCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *DefaultCommand) GetName() string { return "DEFAULT" }
|
||||
|
|
|
|||
|
|
@ -54,3 +54,6 @@ func (c *ElseCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
c.skipLabel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ElseCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *ElseCommand) GetName() string { return "ELSE" }
|
||||
|
|
|
|||
|
|
@ -47,3 +47,6 @@ func (c *EndIfCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex
|
|||
func (c *EndIfCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
return []string{c.endLabel}, nil
|
||||
}
|
||||
|
||||
func (c *EndIfCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *EndIfCommand) GetName() string { return "ENDIF" }
|
||||
|
|
|
|||
|
|
@ -58,3 +58,6 @@ func (c *EndSwitchCommand) Generate(ctx *compiler.CompilerContext) ([]string, er
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *EndSwitchCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *EndSwitchCommand) GetName() string { return "ENDSWITCH" }
|
||||
|
|
|
|||
|
|
@ -47,3 +47,6 @@ func (c *FendCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func (c *FendCommand) GetClass() compiler.CmdClass { return compiler.CmdBoundary }
|
||||
func (c *FendCommand) GetName() string { return "FEND" }
|
||||
|
|
|
|||
|
|
@ -243,3 +243,6 @@ func (c *ForCommand) generateAssignment() []string {
|
|||
|
||||
return asm
|
||||
}
|
||||
|
||||
func (c *ForCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *ForCommand) GetName() string { return "FOR" }
|
||||
|
|
|
|||
|
|
@ -48,3 +48,6 @@ func (c *FuncCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
marker := fmt.Sprintf("; @@FUNC_BEGIN %s", c.funcName)
|
||||
return []string{marker, c.funcName}, nil
|
||||
}
|
||||
|
||||
func (c *FuncCommand) GetClass() compiler.CmdClass { return compiler.CmdBoundary }
|
||||
func (c *FuncCommand) GetName() string { return "FUNC" }
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ func (c *GosubCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *GosubCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *GosubCommand) GetName() string { return "GOSUB" }
|
||||
|
||||
func (c *GosubCommand) generatePrePassing() []string {
|
||||
var asm []string
|
||||
|
||||
|
|
|
|||
|
|
@ -98,3 +98,6 @@ func (c *GotoCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
// Jump to computed address
|
||||
return []string{fmt.Sprintf("\tjmp $%04x", c.address)}, nil
|
||||
}
|
||||
|
||||
func (c *GotoCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *GotoCommand) GetName() string { return "GOTO" }
|
||||
|
|
|
|||
|
|
@ -119,3 +119,6 @@ func (c *IfCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return cmpAsm, nil
|
||||
}
|
||||
|
||||
func (c *IfCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *IfCommand) GetName() string { return "IF" }
|
||||
|
|
|
|||
|
|
@ -138,3 +138,6 @@ func (c *IncrCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *IncrCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *IncrCommand) GetName() string { return "INC" }
|
||||
|
|
|
|||
|
|
@ -46,3 +46,6 @@ func (c *LabelCommand) Interpret(line preproc.Line, _ *compiler.CompilerContext)
|
|||
func (c *LabelCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
return []string{c.labelName}, nil
|
||||
}
|
||||
|
||||
func (c *LabelCommand) GetClass() compiler.CmdClass { return compiler.CmdBoundary }
|
||||
func (c *LabelCommand) GetName() string { return "LABEL" }
|
||||
|
|
|
|||
|
|
@ -182,3 +182,6 @@ func (c *LetCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *LetCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *LetCommand) GetName() string { return "LET" }
|
||||
|
|
|
|||
|
|
@ -44,3 +44,6 @@ func (c *MacroCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
result = append(result, fmt.Sprintf("; end @%s", c.macroName))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *MacroCommand) GetClass() compiler.CmdClass { return compiler.CmdBoundary }
|
||||
func (c *MacroCommand) GetName() string { return "MACRO" }
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *NextCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *NextCommand) GetName() string { return "NEXT" }
|
||||
|
||||
// generateEndCheck generates code to exit if var == end.
|
||||
func (c *NextCommand) generateEndCheck(ctx *compiler.CompilerContext) []string {
|
||||
var asm []string
|
||||
|
|
|
|||
|
|
@ -229,3 +229,6 @@ func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *OrCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *OrCommand) GetName() string { return "OR" }
|
||||
|
|
|
|||
|
|
@ -54,3 +54,6 @@ func (c *OriginCommand) Interpret(line preproc.Line, ctx *compiler.CompilerConte
|
|||
func (c *OriginCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
return []string{fmt.Sprintf("*= $%04x", c.address)}, nil
|
||||
}
|
||||
|
||||
func (c *OriginCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *OriginCommand) GetName() string { return "ORIGIN" }
|
||||
|
|
|
|||
|
|
@ -280,6 +280,17 @@ func (c *PeekCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *PeekCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *PeekCommand) GetName() string {
|
||||
if c.isZPPointer {
|
||||
return "PEEK:ZP"
|
||||
}
|
||||
if c.isAddrVar {
|
||||
return "PEEK:SM"
|
||||
}
|
||||
return "PEEK"
|
||||
}
|
||||
|
||||
// parseOffset extracts offset from syntax like "var[offset]"
|
||||
// Returns (base, offset) where offset is empty if no brackets found
|
||||
func parseOffset(param string) (base string, offset string) {
|
||||
|
|
|
|||
|
|
@ -281,6 +281,17 @@ func (c *PeekWCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *PeekWCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *PeekWCommand) GetName() string {
|
||||
if c.isZPPointer {
|
||||
return "PEEKW:ZP"
|
||||
}
|
||||
if c.isAddrVar {
|
||||
return "PEEKW:SM"
|
||||
}
|
||||
return "PEEKW"
|
||||
}
|
||||
|
||||
// parseOffsetW extracts offset from syntax like "var[offset]"
|
||||
// Returns (base, offset) where offset is empty if no brackets found
|
||||
func parseOffsetW(param string) (base string, offset string) {
|
||||
|
|
|
|||
|
|
@ -132,3 +132,6 @@ func (c *PointerCommand) Generate(ctx *compiler.CompilerContext) ([]string, erro
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *PointerCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *PointerCommand) GetName() string { return "POINT" }
|
||||
|
|
|
|||
|
|
@ -258,6 +258,17 @@ func (c *PokeCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *PokeCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *PokeCommand) GetName() string {
|
||||
if c.isZPPointer {
|
||||
return "POKE:ZP"
|
||||
}
|
||||
if c.isAddrVar {
|
||||
return "POKE:SM"
|
||||
}
|
||||
return "POKE"
|
||||
}
|
||||
|
||||
// parsePOKEOffset extracts offset from syntax like "var[offset]"
|
||||
// Returns (base, offset) where offset is empty if no brackets found
|
||||
func parsePOKEOffset(param string) (base string, offset string) {
|
||||
|
|
|
|||
|
|
@ -294,6 +294,17 @@ func (c *PokeWCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *PokeWCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *PokeWCommand) GetName() string {
|
||||
if c.isZPPointer {
|
||||
return "POKEW:ZP"
|
||||
}
|
||||
if c.isAddrVar {
|
||||
return "POKEW:SM"
|
||||
}
|
||||
return "POKEW"
|
||||
}
|
||||
|
||||
// parsePOKEWOffset extracts offset from syntax like "var[offset]"
|
||||
// Returns (base, offset) where offset is empty if no brackets found
|
||||
func parsePOKEWOffset(param string) (base string, offset string) {
|
||||
|
|
|
|||
|
|
@ -510,3 +510,6 @@ func (c *ShiftLCommand) generateWordShiftVar(ctx *compiler.CompilerContext) ([]s
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *ShiftLCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *ShiftLCommand) GetName() string { return "SHL" }
|
||||
|
||||
|
|
|
|||
|
|
@ -653,3 +653,5 @@ func (c *ShiftRCommand) generateWordShiftVar(ctx *compiler.CompilerContext) ([]s
|
|||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *ShiftRCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *ShiftRCommand) GetName() string { return "SHR" }
|
||||
|
|
|
|||
|
|
@ -42,3 +42,6 @@ func (c *SubEndCommand) Interpret(line preproc.Line, _ *compiler.CompilerContext
|
|||
func (c *SubEndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
return []string{"\trts"}, nil
|
||||
}
|
||||
|
||||
func (c *SubEndCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *SubEndCommand) GetName() string { return "SUBEND" }
|
||||
|
|
|
|||
|
|
@ -251,3 +251,6 @@ func (c *SubtractCommand) Generate(_ *compiler.CompilerContext) ([]string, error
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *SubtractCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *SubtractCommand) GetName() string { return "SUB" }
|
||||
|
|
|
|||
|
|
@ -74,3 +74,6 @@ func (c *SwitchCommand) Generate(ctx *compiler.CompilerContext) ([]string, error
|
|||
// The variable will be loaded and compared by each CASE
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (c *SwitchCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *SwitchCommand) GetName() string { return "SWITCH" }
|
||||
|
|
|
|||
|
|
@ -57,3 +57,6 @@ func (c *WendCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
c.skipLabel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *WendCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *WendCommand) GetName() string { return "WEND" }
|
||||
|
|
|
|||
|
|
@ -125,3 +125,6 @@ func (c *WhileCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
|||
asm = append(asm, cmpAsm...)
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *WhileCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
|
||||
func (c *WhileCommand) GetName() string { return "WHILE" }
|
||||
|
|
|
|||
|
|
@ -204,3 +204,6 @@ func (c *WordCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
// Variables are rendered by assembleOutput, not by individual commands
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *WordCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *WordCommand) GetName() string { return "WORD" }
|
||||
|
|
|
|||
|
|
@ -267,3 +267,6 @@ func (c *XorCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *XorCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
|
||||
func (c *XorCommand) GetName() string { return "XOR" }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,15 @@ import (
|
|||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
// CmdClass categorizes commands for the peephole optimizer
|
||||
type CmdClass int
|
||||
|
||||
const (
|
||||
CmdLinear CmdClass = iota // straight-line code, no visible labels
|
||||
CmdFlowCtrl // IF/WHILE/FOR/SWITCH: internal labels, bodies still optimizable
|
||||
CmdBoundary // FUNC/FEND/LABEL/MACRO: reset all optimizer state
|
||||
)
|
||||
|
||||
// Command represents a script command that can interpret source lines and generate assembly
|
||||
type Command interface {
|
||||
// WillHandle checks if this command can handle the given line
|
||||
|
|
@ -20,6 +29,12 @@ type Command interface {
|
|||
// Generate produces assembly output based on previously interpreted line
|
||||
// Returns assembly lines and error if generation fails
|
||||
Generate(ctx *CompilerContext) ([]string, error)
|
||||
|
||||
// GetClass returns the command's optimizer class
|
||||
GetClass() CmdClass
|
||||
|
||||
// GetName returns a short command name for @@OPT annotations (e.g. "LET", "ADD", "POKE")
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// CommandRegistry manages registered commands and dispatches lines to appropriate handlers
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/optimizer"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
|
|
@ -233,6 +234,9 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
|||
}
|
||||
|
||||
codeOutput = append(codeOutput, fmt.Sprintf("; %s", line.Text))
|
||||
if len(asmLines) > 0 && c.isOptimizing() {
|
||||
codeOutput = append(codeOutput, fmt.Sprintf("; @@OPT:%s:%s", classString(cmd.GetClass()), cmd.GetName()))
|
||||
}
|
||||
codeOutput = append(codeOutput, asmLines...)
|
||||
}
|
||||
|
||||
|
|
@ -251,6 +255,11 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
|||
return nil, fmt.Errorf("Unclosed SCRIPT MACRO block.")
|
||||
}
|
||||
|
||||
// Peephole optimization pass
|
||||
if cfg := c.getOptimizerConfig(); cfg != nil {
|
||||
codeOutput = optimizer.Optimize(codeOutput, cfg)
|
||||
}
|
||||
|
||||
// Analyze for overlapping absolute addresses in function call chains
|
||||
c.checkAbsoluteOverlaps()
|
||||
|
||||
|
|
@ -276,6 +285,44 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
|||
return c.assembleOutput(codeOutput, removedFuncs), nil
|
||||
}
|
||||
|
||||
// isOptimizing returns true if any peephole optimization pragma is active
|
||||
func (c *Compiler) isOptimizing() bool {
|
||||
if c.ctx == nil || c.ctx.Pragma == nil {
|
||||
return false
|
||||
}
|
||||
idx := c.ctx.Pragma.GetCurrentPragmaSetIndex()
|
||||
ps := c.ctx.Pragma.GetPragmaSetByIndex(idx)
|
||||
cfg := optimizer.NewConfig(ps)
|
||||
return cfg.Any()
|
||||
}
|
||||
|
||||
// getOptimizerConfig returns optimizer config if optimization is enabled, nil otherwise
|
||||
func (c *Compiler) getOptimizerConfig() *optimizer.Config {
|
||||
if c.ctx == nil || c.ctx.Pragma == nil {
|
||||
return nil
|
||||
}
|
||||
idx := c.ctx.Pragma.GetCurrentPragmaSetIndex()
|
||||
ps := c.ctx.Pragma.GetPragmaSetByIndex(idx)
|
||||
cfg := optimizer.NewConfig(ps)
|
||||
if !cfg.Any() {
|
||||
return nil
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func classString(cls CmdClass) string {
|
||||
switch cls {
|
||||
case CmdLinear:
|
||||
return "LINEAR"
|
||||
case CmdFlowCtrl:
|
||||
return "FLOWCTRL"
|
||||
case CmdBoundary:
|
||||
return "BOUNDARY"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// printErrorWithContext prints an error with source code context to stderr
|
||||
func (c *Compiler) printErrorWithContext(lines []preproc.Line, lineIndex int, err error) {
|
||||
if lineIndex < 0 || lineIndex >= len(lines) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ func (c *TestBreakCommand) Generate(ctx *CompilerContext) ([]string, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *TestBreakCommand) GetClass() CmdClass { return CmdLinear }
|
||||
func (c *TestBreakCommand) GetName() string { return "BREAK" }
|
||||
|
||||
func TestCompilerArchitecture(t *testing.T) {
|
||||
// Create pragma
|
||||
pragma := preproc.NewPragma()
|
||||
|
|
|
|||
29
internal/optimizer/config.go
Normal file
29
internal/optimizer/config.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package optimizer
|
||||
|
||||
import (
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
EnableLoad bool
|
||||
EnableImm bool
|
||||
EnableJmp bool
|
||||
EnableSelf bool
|
||||
Debug bool
|
||||
}
|
||||
|
||||
func NewConfig(ps preproc.PragmaSet) *Config {
|
||||
all := ps.GetPragma("_P_OPT_ALL") != "" && ps.GetPragma("_P_OPT_ALL") != "0"
|
||||
|
||||
return &Config{
|
||||
EnableLoad: all || (ps.GetPragma("_P_OPT_LOAD") != "" && ps.GetPragma("_P_OPT_LOAD") != "0"),
|
||||
EnableImm: all || (ps.GetPragma("_P_OPT_IMM") != "" && ps.GetPragma("_P_OPT_IMM") != "0"),
|
||||
EnableJmp: all || (ps.GetPragma("_P_OPT_JMP") != "" && ps.GetPragma("_P_OPT_JMP") != "0"),
|
||||
EnableSelf: all || (ps.GetPragma("_P_OPT_SELF") != "" && ps.GetPragma("_P_OPT_SELF") != "0"),
|
||||
Debug: (ps.GetPragma("_P_OPT_DEBUG") != "" && ps.GetPragma("_P_OPT_DEBUG") != "0"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Any() bool {
|
||||
return c.EnableLoad || c.EnableImm || c.EnableJmp || c.EnableSelf
|
||||
}
|
||||
11
internal/optimizer/debug.go
Normal file
11
internal/optimizer/debug.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package optimizer
|
||||
|
||||
import "fmt"
|
||||
|
||||
// debugWithOriginals wraps the current state with a debug comment header
|
||||
func debugWithOriginals(lines []asmLine, phase string) []asmLine {
|
||||
header := fmt.Sprintf("; --- %s ---", phase)
|
||||
result := []asmLine{{text: header, isComment: true}}
|
||||
result = append(result, lines...)
|
||||
return result
|
||||
}
|
||||
36
internal/optimizer/optimizer.go
Normal file
36
internal/optimizer/optimizer.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package optimizer
|
||||
|
||||
// Optimize applies all enabled peephole passes to the generated ASM lines.
|
||||
// Each pass runs sequentially on a parsed representation of the lines.
|
||||
// @@OPT markers are stripped from output.
|
||||
func Optimize(lines []string, cfg *Config) []string {
|
||||
if cfg == nil || !cfg.Any() {
|
||||
return lines
|
||||
}
|
||||
|
||||
parsed := parseLines(lines)
|
||||
|
||||
if cfg.Debug {
|
||||
parsed = debugWithOriginals(parsed, "before peephole")
|
||||
}
|
||||
|
||||
if cfg.EnableLoad {
|
||||
parsed = passLoadElimination(parsed)
|
||||
}
|
||||
if cfg.EnableImm {
|
||||
parsed = passImmElimination(parsed)
|
||||
}
|
||||
if cfg.EnableJmp {
|
||||
parsed = passJmpNext(parsed)
|
||||
}
|
||||
if cfg.EnableSelf {
|
||||
parsed = passSelfAssignment(parsed)
|
||||
}
|
||||
|
||||
if cfg.Debug {
|
||||
parsed = debugWithOriginals(parsed, "after peephole")
|
||||
}
|
||||
|
||||
parsed = stripOptMarkers(parsed)
|
||||
return linesToString(parsed)
|
||||
}
|
||||
209
internal/optimizer/optimizer_test.go
Normal file
209
internal/optimizer/optimizer_test.go
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
package optimizer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func lines(s ...string) []string { return s }
|
||||
|
||||
func TestPassLoadElimination(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected int // expected number of lines after optimization
|
||||
}{
|
||||
{
|
||||
name: "redundant same variable",
|
||||
input: lines("\tlda x", "\tsta y", "\tlda x"),
|
||||
expected: 2, // third lda removed
|
||||
},
|
||||
{
|
||||
name: "not redundant - store to same variable",
|
||||
input: lines("\tlda x", "\tsta x", "\tlda x"),
|
||||
expected: 3, // X was stored, so A is stale
|
||||
},
|
||||
{
|
||||
name: "not redundant - arithmetic in between",
|
||||
input: lines("\tlda x", "\tadc #1", "\tlda x"),
|
||||
expected: 3, // A was modified
|
||||
},
|
||||
{
|
||||
name: "redundant - label resets state",
|
||||
input: lines("\tlda x", "label", "\tlda x"),
|
||||
expected: 3, // label resets state, second lda not redundant
|
||||
},
|
||||
{
|
||||
name: "x register redundant",
|
||||
input: lines("\tldx counter", "\tldy counter", "\tldx counter"),
|
||||
expected: 2, // third ldx is redundant (X still holds counter)
|
||||
},
|
||||
{
|
||||
name: "immediate loads not affected",
|
||||
input: lines("\tlda #5", "\tsta x", "\tlda #5"),
|
||||
expected: 3, // handled by imm pass
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parsed := parseLines(tt.input)
|
||||
result := passLoadElimination(parsed)
|
||||
cleaned := stripOptMarkers(result)
|
||||
if got := len(cleaned); got != tt.expected {
|
||||
t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassImmElimination(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "redundant same immediate",
|
||||
input: lines("\tlda #$05", "\tsta y", "\tlda #$05"),
|
||||
expected: 2, // third removed
|
||||
},
|
||||
{
|
||||
name: "different immediate values",
|
||||
input: lines("\tlda #5", "\tsta y", "\tlda #3"),
|
||||
expected: 3, // different values
|
||||
},
|
||||
{
|
||||
name: "arithmetic invalidates",
|
||||
input: lines("\tlda #5", "\tadc #1", "\tlda #5"),
|
||||
expected: 3, // adc modified A
|
||||
},
|
||||
{
|
||||
name: "x and y independent",
|
||||
input: lines("\tldx #$00", "\tldy #$00", "\tldx #$00"),
|
||||
expected: 2, // first ldx removed? No, let me check: ldx #0 then ldy #0, X still 0, so third redundant
|
||||
// Actually: ldx#0(X=0), ldy#0(Y=0,X=0), ldx#0(X=0,redundant)
|
||||
// Result: ldx#0, ldy#0 → 2 lines
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parsed := parseLines(tt.input)
|
||||
result := passImmElimination(parsed)
|
||||
cleaned := stripOptMarkers(result)
|
||||
if got := len(cleaned); got != tt.expected {
|
||||
t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassJmpNext(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "jmp to next label",
|
||||
input: lines("\tjmp _L1", "_L1"),
|
||||
expected: 1, // jmp removed
|
||||
},
|
||||
{
|
||||
name: "jmp not to next label",
|
||||
input: lines("\tjmp _L1", "\tlda x", "_L1"),
|
||||
expected: 3, // not immediately followed by label
|
||||
},
|
||||
{
|
||||
name: "label different from jmp target",
|
||||
input: lines("\tjmp _L1", "_L2"),
|
||||
expected: 2, // different labels
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parsed := parseLines(tt.input)
|
||||
result := passJmpNext(parsed)
|
||||
cleaned := stripOptMarkers(result)
|
||||
if got := len(cleaned); got != tt.expected {
|
||||
t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassSelfAssignment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "self assignment",
|
||||
input: lines("\tlda x", "\tsta x"),
|
||||
expected: 0, // both removed
|
||||
},
|
||||
{
|
||||
name: "not self - different variables",
|
||||
input: lines("\tlda x", "\tsta y"),
|
||||
expected: 2, // different
|
||||
},
|
||||
{
|
||||
name: "not self - immediate load",
|
||||
input: lines("\tlda #$05", "\tsta #$05"),
|
||||
expected: 2, // immediate + immediate doesn't match
|
||||
},
|
||||
{
|
||||
name: "not self - SM code pattern (+ offset)",
|
||||
input: lines("\tlda x", "\tsta _L1+1"),
|
||||
expected: 2, // SM code pattern
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parsed := parseLines(tt.input)
|
||||
result := passSelfAssignment(parsed)
|
||||
cleaned := stripOptMarkers(result)
|
||||
if got := len(cleaned); got != tt.expected {
|
||||
t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassLoadNoCommentReset(t *testing.T) {
|
||||
// Comments should NOT reset register state
|
||||
parsed := parseLines(lines(
|
||||
"\tlda b",
|
||||
"\tsta a",
|
||||
"; comment",
|
||||
"\tlda b", // should be redundant
|
||||
))
|
||||
result := passLoadElimination(parsed)
|
||||
if len(result) != 3 {
|
||||
t.Errorf("expected 3 lines (comment kept, lda b removed), got %d:\n%v", len(result), linesToString(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptimizeIntegration(t *testing.T) {
|
||||
input := lines(
|
||||
"; a = b",
|
||||
"; @@OPT:LINEAR:LET",
|
||||
"\tlda b",
|
||||
"\tsta a",
|
||||
"; second use of b (redundant load)",
|
||||
"; @@OPT:LINEAR:LET",
|
||||
"\tlda b",
|
||||
"\tsta c",
|
||||
)
|
||||
|
||||
cfg := &Config{EnableLoad: true}
|
||||
output := Optimize(input, cfg)
|
||||
|
||||
// 2 source comments + 3 asm lines (lda b removed) = 5
|
||||
if len(output) != 5 {
|
||||
t.Errorf("expected 5 lines, got %d:\n%v", len(output), output)
|
||||
}
|
||||
}
|
||||
67
internal/optimizer/pass_imm.go
Normal file
67
internal/optimizer/pass_imm.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package optimizer
|
||||
|
||||
// passImmElimination removes redundant immediate loads.
|
||||
// Pattern: lda #N; ... (no A modification) ...; lda #N → remove second
|
||||
// Applies to A, X, Y.
|
||||
func passImmElimination(lines []asmLine) []asmLine {
|
||||
rs := newRegState()
|
||||
var result []asmLine
|
||||
|
||||
for _, line := range lines {
|
||||
if !line.isCode {
|
||||
if line.optMarker {
|
||||
continue
|
||||
}
|
||||
if line.isLabel {
|
||||
rs.reset()
|
||||
}
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
if isChainBreaker(line.opcode) {
|
||||
rs.reset()
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
if isRedundantImm(line, rs) {
|
||||
continue // remove line
|
||||
}
|
||||
|
||||
updateRegState(line, rs)
|
||||
result = append(result, line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func isRedundantImm(line asmLine, rs *regState) bool {
|
||||
switch line.opcode {
|
||||
case "lda":
|
||||
if isImmediate(line.operand) {
|
||||
val := parseHexOrDec(line.operand[1:])
|
||||
if rs.a.val == val {
|
||||
return true
|
||||
}
|
||||
rs.loadImm("a", val)
|
||||
}
|
||||
case "ldx":
|
||||
if isImmediate(line.operand) {
|
||||
val := parseHexOrDec(line.operand[1:])
|
||||
if rs.x.val == val {
|
||||
return true
|
||||
}
|
||||
rs.loadImm("x", val)
|
||||
}
|
||||
case "ldy":
|
||||
if isImmediate(line.operand) {
|
||||
val := parseHexOrDec(line.operand[1:])
|
||||
if rs.y.val == val {
|
||||
return true
|
||||
}
|
||||
rs.loadImm("y", val)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
38
internal/optimizer/pass_jmp.go
Normal file
38
internal/optimizer/pass_jmp.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package optimizer
|
||||
|
||||
import "strings"
|
||||
|
||||
// passJmpNext removes unconditional jumps to the immediately following label.
|
||||
// Pattern: jmp L; L: → (remove jmp)
|
||||
func passJmpNext(lines []asmLine) []asmLine {
|
||||
var result []asmLine
|
||||
skipNext := false
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
|
||||
line := lines[i]
|
||||
|
||||
if skipJmpMarker(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for jmp L followed by label L:
|
||||
if line.isCode && line.opcode == "jmp" && i+1 < len(lines) {
|
||||
next := lines[i+1]
|
||||
if next.isLabel && strings.TrimSpace(next.text) == strings.TrimSpace(line.operand) {
|
||||
// Remove the jmp, keep the label
|
||||
result = append(result, next)
|
||||
skipNext = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
56
internal/optimizer/pass_load.go
Normal file
56
internal/optimizer/pass_load.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package optimizer
|
||||
|
||||
// passLoadElimination removes redundant memory-to-register loads.
|
||||
// Pattern: lda X; ... (X not modified) ...; lda X → remove second
|
||||
// Applies to A, X, Y registers identically.
|
||||
func passLoadElimination(lines []asmLine) []asmLine {
|
||||
rs := newRegState()
|
||||
var result []asmLine
|
||||
|
||||
for _, line := range lines {
|
||||
if !line.isCode {
|
||||
if line.optMarker {
|
||||
continue
|
||||
}
|
||||
if line.isLabel {
|
||||
rs.reset()
|
||||
}
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
if isChainBreaker(line.opcode) {
|
||||
rs.reset()
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for redundant memory load
|
||||
if line.opcode == "lda" && !isImmediate(line.operand) {
|
||||
if rs.a.src == line.operand {
|
||||
continue // redundant, remove
|
||||
}
|
||||
rs.a.src = line.operand
|
||||
} else if line.opcode == "ldx" && !isImmediate(line.operand) {
|
||||
if rs.x.src == line.operand {
|
||||
continue
|
||||
}
|
||||
rs.x.src = line.operand
|
||||
} else if line.opcode == "ldy" && !isImmediate(line.operand) {
|
||||
if rs.y.src == line.operand {
|
||||
continue
|
||||
}
|
||||
rs.y.src = line.operand
|
||||
} else {
|
||||
updateRegState(line, rs)
|
||||
}
|
||||
|
||||
result = append(result, line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func isImmediate(operand string) bool {
|
||||
return len(operand) > 0 && operand[0] == '#'
|
||||
}
|
||||
38
internal/optimizer/pass_self.go
Normal file
38
internal/optimizer/pass_self.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package optimizer
|
||||
|
||||
import "strings"
|
||||
|
||||
// passSelfAssignment removes lda X; sta X pairs where the store immediately
|
||||
// follows the load and neither line is a branch target.
|
||||
func passSelfAssignment(lines []asmLine) []asmLine {
|
||||
var result []asmLine
|
||||
skipNext := false
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
|
||||
line := lines[i]
|
||||
|
||||
if skipJmpMarker(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for lda X; sta X
|
||||
if line.isCode && line.opcode == "lda" && !isImmediate(line.operand) && i+1 < len(lines) {
|
||||
next := lines[i+1]
|
||||
if next.isCode && next.opcode == "sta" && next.operand == line.operand {
|
||||
if !strings.Contains(line.operand, "+") {
|
||||
skipNext = true // skip sta on next iteration
|
||||
continue // skip lda
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
140
internal/optimizer/regstate.go
Normal file
140
internal/optimizer/regstate.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
package optimizer
|
||||
|
||||
import "strings"
|
||||
|
||||
type regInfo struct {
|
||||
val int // -1 = unknown, else the immediate value known to be in reg
|
||||
src string // variable name if loaded from memory (or "" for immediates)
|
||||
}
|
||||
|
||||
type regState struct {
|
||||
a regInfo
|
||||
x regInfo
|
||||
y regInfo
|
||||
}
|
||||
|
||||
func newRegState() *regState {
|
||||
return ®State{
|
||||
a: regInfo{val: -1},
|
||||
x: regInfo{val: -1},
|
||||
y: regInfo{val: -1},
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *regState) reset() {
|
||||
rs.a = regInfo{val: -1}
|
||||
rs.x = regInfo{val: -1}
|
||||
rs.y = regInfo{val: -1}
|
||||
}
|
||||
|
||||
func (rs *regState) resetA() { rs.a = regInfo{val: -1} }
|
||||
func (rs *regState) resetX() { rs.x = regInfo{val: -1} }
|
||||
func (rs *regState) resetY() { rs.y = regInfo{val: -1} }
|
||||
|
||||
// loadImm records that a register was loaded with an immediate value
|
||||
func (rs *regState) loadImm(reg string, val int) {
|
||||
r := regInfo{val: val}
|
||||
switch reg {
|
||||
case "a":
|
||||
rs.a = r
|
||||
case "x":
|
||||
rs.x = r
|
||||
case "y":
|
||||
rs.y = r
|
||||
}
|
||||
}
|
||||
|
||||
// loadMem records that a register was loaded from a memory location
|
||||
func (rs *regState) loadMem(reg, varName string) {
|
||||
r := regInfo{val: -1, src: varName}
|
||||
switch reg {
|
||||
case "a":
|
||||
rs.a = r
|
||||
case "x":
|
||||
rs.x = r
|
||||
case "y":
|
||||
rs.y = r
|
||||
}
|
||||
}
|
||||
|
||||
// storeInvalidate marks that a memory location was written.
|
||||
// If any register's src matches, that register becomes "unknown" (value gone).
|
||||
func (rs *regState) storeInvalidate(varName string) {
|
||||
if rs.a.src == varName {
|
||||
rs.a.src = ""
|
||||
rs.a.val = -1
|
||||
}
|
||||
if rs.x.src == varName {
|
||||
rs.x.src = ""
|
||||
rs.x.val = -1
|
||||
}
|
||||
if rs.y.src == varName {
|
||||
rs.y.src = ""
|
||||
rs.y.val = -1
|
||||
}
|
||||
}
|
||||
|
||||
// isChainBreaker returns true for instructions that reset all register tracking
|
||||
func isChainBreaker(opcode string) bool {
|
||||
switch opcode {
|
||||
case "jmp", "jsr", "rts", "rti", "brk":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// updateRegState updates the register state machine for a code line
|
||||
func updateRegState(line asmLine, rs *regState) {
|
||||
if !line.isCode {
|
||||
return
|
||||
}
|
||||
|
||||
switch line.opcode {
|
||||
case "lda":
|
||||
if strings.HasPrefix(line.operand, "#") {
|
||||
rs.loadImm("a", parseHexOrDec(line.operand[1:]))
|
||||
} else {
|
||||
rs.loadMem("a", line.operand)
|
||||
}
|
||||
case "ldx":
|
||||
if strings.HasPrefix(line.operand, "#") {
|
||||
rs.loadImm("x", parseHexOrDec(line.operand[1:]))
|
||||
} else {
|
||||
rs.loadMem("x", line.operand)
|
||||
}
|
||||
case "ldy":
|
||||
if strings.HasPrefix(line.operand, "#") {
|
||||
rs.loadImm("y", parseHexOrDec(line.operand[1:]))
|
||||
} else {
|
||||
rs.loadMem("y", line.operand)
|
||||
}
|
||||
case "sta":
|
||||
rs.storeInvalidate(line.operand)
|
||||
case "stx":
|
||||
rs.storeInvalidate(line.operand)
|
||||
case "sty":
|
||||
rs.storeInvalidate(line.operand)
|
||||
case "inc", "dec":
|
||||
rs.storeInvalidate(line.operand)
|
||||
case "tax":
|
||||
rs.x = regInfo{val: -1, src: rs.a.src}
|
||||
case "tay":
|
||||
rs.y = regInfo{val: -1, src: rs.a.src}
|
||||
case "txa":
|
||||
rs.a = regInfo{val: -1, src: rs.x.src}
|
||||
case "tya":
|
||||
rs.a = regInfo{val: -1, src: rs.y.src}
|
||||
case "inx", "dex":
|
||||
rs.resetX()
|
||||
case "iny", "dey":
|
||||
rs.resetY()
|
||||
case "asl", "lsr", "rol", "ror", "adc", "sbc", "and", "ora", "eor", "pla":
|
||||
rs.resetA()
|
||||
case "plp":
|
||||
rs.reset()
|
||||
case "tsx":
|
||||
rs.resetX()
|
||||
case "txs":
|
||||
// SP doesn't affect register tracking
|
||||
}
|
||||
}
|
||||
97
internal/optimizer/scanner.go
Normal file
97
internal/optimizer/scanner.go
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package optimizer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseHexOrDec parses an ACME-style number: $ff or 255
|
||||
func parseHexOrDec(s string) int {
|
||||
if strings.HasPrefix(s, "$") {
|
||||
v, _ := strconv.ParseInt(s[1:], 16, 16)
|
||||
return int(v)
|
||||
}
|
||||
// Strip trailing characters after the number (e.g., ",y" in "5,y")
|
||||
if idx := strings.IndexAny(s, ",)"); idx >= 0 {
|
||||
s = s[:idx]
|
||||
}
|
||||
v, _ := strconv.ParseInt(s, 10, 16)
|
||||
return int(v)
|
||||
}
|
||||
|
||||
type asmLine struct {
|
||||
text string
|
||||
isLabel bool
|
||||
isCode bool
|
||||
isComment bool
|
||||
optMarker bool
|
||||
opcode string
|
||||
operand string
|
||||
}
|
||||
|
||||
func parseLines(lines []string) []asmLine {
|
||||
var result []asmLine
|
||||
for _, l := range lines {
|
||||
al := asmLine{text: l}
|
||||
|
||||
if l == "" {
|
||||
result = append(result, al)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(l, "; @@OPT:") {
|
||||
al.optMarker = true
|
||||
al.isComment = true
|
||||
result = append(result, al)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(l, ";") {
|
||||
al.isComment = true
|
||||
result = append(result, al)
|
||||
continue
|
||||
}
|
||||
|
||||
if l[0] == '\t' {
|
||||
al.isCode = true
|
||||
parts := strings.Fields(l)
|
||||
if len(parts) > 0 {
|
||||
al.opcode = strings.ToLower(parts[0])
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
al.operand = parts[1]
|
||||
}
|
||||
} else {
|
||||
al.isLabel = true
|
||||
}
|
||||
|
||||
result = append(result, al)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// stripOptMarkers removes @@OPT comment lines from the output
|
||||
func stripOptMarkers(lines []asmLine) []asmLine {
|
||||
var result []asmLine
|
||||
for _, l := range lines {
|
||||
if l.optMarker {
|
||||
continue
|
||||
}
|
||||
result = append(result, l)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func linesToString(lines []asmLine) []string {
|
||||
var result []string
|
||||
for _, l := range lines {
|
||||
result = append(result, l.text)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// skipJmpMarker returns true if the line is an @@OPT comment (to be skipped in pass logic)
|
||||
func skipJmpMarker(line asmLine) bool {
|
||||
return line.optMarker
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue