Optimized and, or and xor commands

This commit is contained in:
Mattias Hansson 2026-06-04 02:15:35 +02:00
parent c76c514d35
commit 205ae92d0e
6 changed files with 893 additions and 68 deletions

View file

@ -156,6 +156,26 @@ func (c *AndCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
} }
} }
// Normalize operands: leverage AND commutativity to swap if needed
// This ensures word operands are in param1 when mixed with bytes
// Makes code generation simpler and more consistent
param1IsByteSized := false
if c.param1IsVar {
param1IsByteSized = (c.param1VarKind == compiler.KindByte)
} else {
param1IsByteSized = ((c.param1Value >> 8) & 0xFF) == 0
}
param2IsWord := c.param2IsVar && c.param2VarKind == compiler.KindWord
if param1IsByteSized && param2IsWord {
// Swap param1 and param2
c.param1VarName, c.param2VarName = c.param2VarName, c.param1VarName
c.param1VarKind, c.param2VarKind = c.param2VarKind, c.param1VarKind
c.param1Value, c.param2Value = c.param2Value, c.param1Value
c.param1IsVar, c.param2IsVar = c.param2IsVar, c.param1IsVar
}
return nil return nil
} }
@ -180,6 +200,46 @@ func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
} }
// At least one param is a variable - generate AND code // At least one param is a variable - generate AND code
// Same variable on both sides: a & a = a
if c.param1IsVar && c.param2IsVar && c.param1VarName == c.param2VarName {
if c.destVarName == c.param1VarName {
return asm, nil
}
if c.param1VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if c.destVarKind == compiler.KindWord {
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
}
return asm, nil
}
// Track if A is known to be 0 after low byte (avoids redundant lda #0 for high byte)
aIsZero := false
// If either param is literal 0, low byte result is always 0
param1LoIsZero := !c.param1IsVar && uint8(c.param1Value&0xFF) == 0
param2LoIsZero := !c.param2IsVar && uint8(c.param2Value&0xFF) == 0
// If param1 is literal $FF and param2 is a var, just load param2 ($FF AND a = a)
param1IsFF := !c.param1IsVar && uint8(c.param1Value&0xFF) == 0xFF
if param1LoIsZero || param2LoIsZero {
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
aIsZero = true
} else if param1IsFF && c.param2IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param2VarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else {
// Load param1 // Load param1
if c.param1IsVar { if c.param1IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
@ -187,18 +247,36 @@ func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
} }
// AND with param2 // AND with param2 (skip if literal $FF, as and #$ff preserves accumulator)
if c.param2IsVar { if c.param2IsVar {
asm = append(asm, fmt.Sprintf("\tand %s", c.param2VarName)) asm = append(asm, fmt.Sprintf("\tand %s", c.param2VarName))
} else { } else if uint8(c.param2Value&0xFF) != 0xFF {
asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF))) asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF)))
} }
// Store low byte // Store low byte
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
}
// If destination is word, handle high byte // If destination is word, handle high byte
if c.destVarKind == compiler.KindWord { if c.destVarKind == compiler.KindWord {
// Determine if param2 high byte is effectively 0
// (AND with 0 always yields 0 regardless of param1 high byte)
param2HiEffectiveZero := false
if c.param2IsVar {
if c.param2VarKind == compiler.KindByte {
param2HiEffectiveZero = true
}
} else {
param2HiEffectiveZero = ((c.param2Value >> 8) & 0xFF) == 0
}
if param2HiEffectiveZero {
if !aIsZero {
asm = append(asm, "\tlda #0")
}
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} else {
// Load high byte of param1 // Load high byte of param1
if c.param1IsVar { if c.param1IsVar {
if c.param1VarKind == compiler.KindWord { if c.param1VarKind == compiler.KindWord {
@ -216,7 +294,7 @@ func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
if c.param2VarKind == compiler.KindWord { if c.param2VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tand %s+1", c.param2VarName)) asm = append(asm, fmt.Sprintf("\tand %s+1", c.param2VarName))
} else { } else {
asm = append(asm, "\tand #0") asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF)))
} }
} else { } else {
hi := uint8((c.param2Value >> 8) & 0xFF) hi := uint8((c.param2Value >> 8) & 0xFF)
@ -226,6 +304,7 @@ func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
// Store high byte // Store high byte
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
}
return asm, nil return asm, nil
} }

View file

@ -71,7 +71,6 @@ func TestAndCommand_OldSyntax(t *testing.T) {
"\tand b", "\tand b",
"\tsta result", "\tsta result",
"\tlda #0", "\tlda #0",
"\tand #0",
"\tsta result+1", "\tsta result+1",
}, },
}, },
@ -113,8 +112,7 @@ func TestAndCommand_OldSyntax(t *testing.T) {
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
}, },
wantAsm: []string{ wantAsm: []string{
"\tlda #$ff", "\tlda b",
"\tand b",
"\tsta result", "\tsta result",
}, },
}, },
@ -182,8 +180,7 @@ func TestAndCommand_OldSyntax(t *testing.T) {
"\tlda wval", "\tlda wval",
"\tand bval", "\tand bval",
"\tsta result", "\tsta result",
"\tlda wval+1", "\tlda #0",
"\tand #0",
"\tsta result+1", "\tsta result+1",
}, },
}, },
@ -286,7 +283,6 @@ func TestAndCommand_NewSyntax(t *testing.T) {
"\tand b", "\tand b",
"\tsta result", "\tsta result",
"\tlda #0", "\tlda #0",
"\tand #0",
"\tsta result+1", "\tsta result+1",
}, },
}, },
@ -320,6 +316,96 @@ func TestAndCommand_NewSyntax(t *testing.T) {
"\tsta result", "\tsta result",
}, },
}, },
{
name: "byte & $FF -> byte (optimization: skip and #$ff)",
line: "result = a & $FF",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
},
},
{
name: "byte & $FF -> word (optimization: skip and #$ff)",
line: "result = a & $FF",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "byte & 0 -> byte (optimization: lda #0)",
line: "result = a & 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
},
},
{
name: "0 & byte -> byte (optimization: lda #0)",
line: "result = 0 & a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
},
},
{
name: "byte & 0 -> word (optimization: lda #0)",
line: "result = a & 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
"\tsta result+1",
},
},
{
name: "byte & $100 -> byte (optimization: lda #0, low byte of $100 is 0)",
line: "result = a & $100",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
},
},
{
name: "byte & $100 -> word (optimization: lda #0)",
line: "result = a & $100",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
"\tlda #0",
"\tand #$01",
"\tsta result+1",
},
},
{ {
name: "constant folding", name: "constant folding",
line: "result = 255 & 15", line: "result = 255 & 15",
@ -358,6 +444,206 @@ func TestAndCommand_NewSyntax(t *testing.T) {
"\tsta result", "\tsta result",
}, },
}, },
{
name: "byte & byte -> byte (same variable: a & a = a)",
line: "result = a & a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
},
},
{
name: "word & word -> word (same variable: x & x = x)",
line: "result = x & x",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda x",
"\tsta result",
"\tlda x+1",
"\tsta result+1",
},
},
{
name: "$FF & byte -> word (optimization: lda param2)",
line: "result = $FF & a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "byte & word -> byte (swap case)",
line: "result = bval & wval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tand bval",
"\tsta result",
},
},
{
name: "byte & word -> word (swap case)",
line: "result = bval & wval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tand bval",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "word_const & byte -> byte",
line: "result = 300 & b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$2c",
"\tand b",
"\tsta result",
},
},
{
name: "word_const & byte -> word",
line: "result = 300 & b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$2c",
"\tand b",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "byte & word_const -> byte",
line: "result = b & 300",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda b",
"\tand #$2c",
"\tsta result",
},
},
{
name: "byte & word_const -> word",
line: "result = b & 300",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda b",
"\tand #$2c",
"\tsta result",
"\tlda #0",
"\tand #$01",
"\tsta result+1",
},
},
{
name: "0 & byte -> word (optimization: lda #0, skip high byte)",
line: "result = 0 & a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
"\tsta result+1",
},
},
{
name: "self-assignment: word &= byte",
line: "wval = wval & bval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tand bval",
"\tsta wval",
"\tlda #0",
"\tsta wval+1",
},
},
{
name: "self-assignment: word &= byte_const",
line: "wval = wval & 42",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tand #$2a",
"\tsta wval",
"\tlda #0",
"\tsta wval+1",
},
},
{
name: "self-assignment: word &= word_const",
line: "wval = wval & 300",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tand #$2c",
"\tsta wval",
"\tlda wval+1",
"\tand #$01",
"\tsta wval+1",
},
},
{
name: "self-assignment: word &= word",
line: "wval = wval & wval2",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("wval2", "", compiler.KindWord, 0x5678, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tand wval2",
"\tsta wval",
"\tlda wval+1",
"\tand wval2+1",
"\tsta wval+1",
},
},
{ {
name: "error: unknown destination", name: "error: unknown destination",
line: "unknown = a & b", line: "unknown = a & b",

View file

@ -156,6 +156,26 @@ func (c *OrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
} }
} }
// Normalize operands: leverage OR commutativity to swap if needed
// This ensures word operands are in param1 when mixed with bytes
// Makes code generation simpler and more consistent
param1IsByteSized := false
if c.param1IsVar {
param1IsByteSized = (c.param1VarKind == compiler.KindByte)
} else {
param1IsByteSized = ((c.param1Value >> 8) & 0xFF) == 0
}
param2IsWord := c.param2IsVar && c.param2VarKind == compiler.KindWord
if param1IsByteSized && param2IsWord {
// Swap param1 and param2
c.param1VarName, c.param2VarName = c.param2VarName, c.param1VarName
c.param1VarKind, c.param2VarKind = c.param2VarKind, c.param1VarKind
c.param1Value, c.param2Value = c.param2Value, c.param1Value
c.param1IsVar, c.param2IsVar = c.param2IsVar, c.param1IsVar
}
return nil return nil
} }
@ -180,6 +200,39 @@ func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
} }
// At least one param is a variable - generate OR code // At least one param is a variable - generate OR code
// Same variable on both sides: a | a = a
if c.param1IsVar && c.param2IsVar && c.param1VarName == c.param2VarName {
if c.destVarName == c.param1VarName {
return asm, nil
}
if c.param1VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if c.destVarKind == compiler.KindWord {
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
}
return asm, nil
}
// If either param is literal $FF, low byte result is always $FF
param1IsFF := !c.param1IsVar && uint8(c.param1Value&0xFF) == 0xFF
param2IsFF := !c.param2IsVar && uint8(c.param2Value&0xFF) == 0xFF
if param1IsFF || param2IsFF {
asm = append(asm, "\tlda #$ff")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else if !c.param1IsVar && uint8(c.param1Value&0xFF) == 0 && c.param2IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param2VarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else {
// Load param1 // Load param1
if c.param1IsVar { if c.param1IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
@ -187,18 +240,33 @@ func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
} }
// OR with param2 // OR with param2 (skip if literal 0, as ora #0 is a no-op)
if c.param2IsVar { if c.param2IsVar {
asm = append(asm, fmt.Sprintf("\tora %s", c.param2VarName)) asm = append(asm, fmt.Sprintf("\tora %s", c.param2VarName))
} else { } else if uint8(c.param2Value&0xFF) != 0 {
asm = append(asm, fmt.Sprintf("\tora #$%02x", uint8(c.param2Value&0xFF))) asm = append(asm, fmt.Sprintf("\tora #$%02x", uint8(c.param2Value&0xFF)))
} }
// Store low byte // Store low byte
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
}
// If destination is word, handle high byte // If destination is word, handle high byte
if c.destVarKind == compiler.KindWord { if c.destVarKind == compiler.KindWord {
// Optimization: skip high byte for self-assignment when param2 is byte-sized
// e.g., word_var = word_var | byte_var would just copy high byte to itself
if c.destVarName == c.param1VarName {
param2IsByteSized := false
if c.param2IsVar {
param2IsByteSized = (c.param2VarKind == compiler.KindByte)
} else {
param2IsByteSized = ((c.param2Value >> 8) & 0xFF) == 0
}
if param2IsByteSized {
return asm, nil
}
}
// Load high byte of param1 // Load high byte of param1
if c.param1IsVar { if c.param1IsVar {
if c.param1VarKind == compiler.KindWord { if c.param1VarKind == compiler.KindWord {
@ -211,17 +279,19 @@ func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi)) asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
} }
// OR with high byte of param2 // OR with high byte of param2 (skip if literal 0, as ora #0 is a no-op)
if c.param2IsVar { if c.param2IsVar {
if c.param2VarKind == compiler.KindWord { if c.param2VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tora %s+1", c.param2VarName)) asm = append(asm, fmt.Sprintf("\tora %s+1", c.param2VarName))
} else {
asm = append(asm, "\tora #0")
} }
// Skip ora when param2 is byte var (high byte is 0)
} else { } else {
hi := uint8((c.param2Value >> 8) & 0xFF) hi := uint8((c.param2Value >> 8) & 0xFF)
if hi != 0 {
asm = append(asm, fmt.Sprintf("\tora #$%02x", hi)) asm = append(asm, fmt.Sprintf("\tora #$%02x", hi))
} }
// Skip ora when param2 const high byte is 0
}
// Store high byte // Store high byte
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))

View file

@ -71,7 +71,6 @@ func TestOrCommand_OldSyntax(t *testing.T) {
"\tora b", "\tora b",
"\tsta result", "\tsta result",
"\tlda #0", "\tlda #0",
"\tora #0",
"\tsta result+1", "\tsta result+1",
}, },
}, },
@ -183,7 +182,6 @@ func TestOrCommand_OldSyntax(t *testing.T) {
"\tora bval", "\tora bval",
"\tsta result", "\tsta result",
"\tlda wval+1", "\tlda wval+1",
"\tora #0",
"\tsta result+1", "\tsta result+1",
}, },
}, },
@ -286,7 +284,6 @@ func TestOrCommand_NewSyntax(t *testing.T) {
"\tora b", "\tora b",
"\tsta result", "\tsta result",
"\tlda #0", "\tlda #0",
"\tora #0",
"\tsta result+1", "\tsta result+1",
}, },
}, },
@ -320,6 +317,32 @@ func TestOrCommand_NewSyntax(t *testing.T) {
"\tsta result", "\tsta result",
}, },
}, },
{
name: "byte | 0 -> byte (optimization: skip ora #0)",
line: "result = a | 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
},
},
{
name: "byte | 0 -> word (optimization: skip ora #0)",
line: "result = a | 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{ {
name: "constant folding", name: "constant folding",
line: "result = 15 | 240", line: "result = 15 | 240",
@ -358,6 +381,294 @@ func TestOrCommand_NewSyntax(t *testing.T) {
"\tsta result", "\tsta result",
}, },
}, },
{
name: "byte | $FF -> byte (optimization: lda #$ff)",
line: "result = a | $FF",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$ff",
"\tsta result",
},
},
{
name: "$FF | byte -> byte (optimization: lda #$ff)",
line: "result = $FF | a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$ff",
"\tsta result",
},
},
{
name: "byte | $FF -> word (optimization: lda #$ff)",
line: "result = a | $FF",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$ff",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "byte | byte -> byte (same variable: a | a = a)",
line: "result = a | a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
},
},
{
name: "0 | byte -> word (optimization: skip ora #0)",
line: "result = 0 | a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xF0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
"\tlda #$00",
"\tsta result+1",
},
},
{
name: "byte | word -> byte (swap case)",
line: "result = bval | wval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora bval",
"\tsta result",
},
},
{
name: "byte | word -> word (swap case)",
line: "result = bval | wval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora bval",
"\tsta result",
"\tlda wval+1",
"\tsta result+1",
},
},
{
name: "$FF | word -> word",
line: "result = $FF | wval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$ff",
"\tsta result",
"\tlda wval+1",
"\tsta result+1",
},
},
{
name: "word | $FF -> word",
line: "result = wval | $FF",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$ff",
"\tsta result",
"\tlda wval+1",
"\tsta result+1",
},
},
{
name: "$FF00 | byte -> byte",
line: "result = $FF00 | b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda b",
"\tsta result",
},
},
{
name: "$FF00 | byte -> word",
line: "result = $FF00 | b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xAB, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda b",
"\tsta result",
"\tlda #$ff",
"\tsta result+1",
},
},
{
name: "byte | word_const -> byte",
line: "result = b | 300",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda b",
"\tora #$2c",
"\tsta result",
},
},
{
name: "byte | word_const -> word",
line: "result = b | 300",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda b",
"\tora #$2c",
"\tsta result",
"\tlda #0",
"\tora #$01",
"\tsta result+1",
},
},
{
name: "word_const | byte -> byte",
line: "result = 300 | b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$2c",
"\tora b",
"\tsta result",
},
},
{
name: "word_const | byte -> word",
line: "result = 300 | b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #$2c",
"\tora b",
"\tsta result",
"\tlda #$01",
"\tsta result+1",
},
},
{
name: "self-assignment: word |= byte (optimization: skip high byte entirely)",
line: "wval = wval | bval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora bval",
"\tsta wval",
},
},
{
name: "self-assignment reversed: word |= byte (optimization via swap)",
line: "wval = bval | wval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("bval", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora bval",
"\tsta wval",
},
},
{
name: "self-assignment: word |= byte_const (optimization: skip high byte entirely)",
line: "wval = wval | 42",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora #$2a",
"\tsta wval",
},
},
{
name: "self-assignment: word |= word_const (no optimization: high byte needed)",
line: "wval = wval | 300",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora #$2c",
"\tsta wval",
"\tlda wval+1",
"\tora #$01",
"\tsta wval+1",
},
},
{
name: "self-assignment: word |= word (no optimization: both high bytes needed)",
line: "wval = wval | wval2",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("wval2", "", compiler.KindWord, 0x5678, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda wval",
"\tora wval2",
"\tsta wval",
"\tlda wval+1",
"\tora wval2+1",
"\tsta wval+1",
},
},
{
name: "word | word -> word (same variable: x | x = x)",
line: "result = x | x",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda x",
"\tsta result",
"\tlda x+1",
"\tsta result+1",
},
},
{ {
name: "error: unknown destination", name: "error: unknown destination",
line: "unknown = a | b", line: "unknown = a | b",

View file

@ -200,6 +200,21 @@ func (c *XorCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
} }
// At least one param is a variable - generate XOR code // At least one param is a variable - generate XOR code
// Same variable on both sides: a ^ a = 0
if c.param1IsVar && c.param2IsVar && c.param1VarName == c.param2VarName {
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if c.destVarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
return asm, nil
}
// If param1 is literal 0, just load param2 directly (0 XOR a = a)
if !c.param1IsVar && uint8(c.param1Value&0xFF) == 0 && c.param2IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param2VarName))
} else {
// Load param1 // Load param1
if c.param1IsVar { if c.param1IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
@ -207,12 +222,13 @@ func (c *XorCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
} }
// XOR with param2 // XOR with param2 (skip if literal 0, as eor #0 is a no-op)
if c.param2IsVar { if c.param2IsVar {
asm = append(asm, fmt.Sprintf("\teor %s", c.param2VarName)) asm = append(asm, fmt.Sprintf("\teor %s", c.param2VarName))
} else { } else if uint8(c.param2Value&0xFF) != 0 {
asm = append(asm, fmt.Sprintf("\teor #$%02x", uint8(c.param2Value&0xFF))) asm = append(asm, fmt.Sprintf("\teor #$%02x", uint8(c.param2Value&0xFF)))
} }
}
// Store low byte // Store low byte
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))

View file

@ -463,6 +463,44 @@ func TestXorCommand_NewSyntax(t *testing.T) {
"\tsta result", "\tsta result",
}, },
}, },
{
name: "byte ^ 0 -> byte (optimization: skip eor #0)",
line: "result = a ^ 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
},
},
{
name: "0 ^ byte -> byte (optimization: skip eor #0)",
line: "result = 0 ^ a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
},
},
{
name: "byte ^ 0 -> word (optimization: skip eor #0)",
line: "result = a ^ 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda a",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{ {
name: "constant folding", name: "constant folding",
line: "result = 255 ^ 170", line: "result = 255 ^ 170",
@ -501,6 +539,31 @@ func TestXorCommand_NewSyntax(t *testing.T) {
"\tsta result", "\tsta result",
}, },
}, },
{
name: "byte ^ byte -> byte (same variable: a ^ a = 0)",
line: "result = a ^ a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0xFF, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
},
},
{
name: "word ^ word -> word (same variable: x ^ x = 0)",
line: "result = x ^ x",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
wantAsm: []string{
"\tlda #0",
"\tsta result",
"\tsta result+1",
},
},
{ {
name: "error: unknown destination", name: "error: unknown destination",
line: "unknown = a ^ b", line: "unknown = a ^ b",