diff --git a/internal/commands/and.go b/internal/commands/and.go index 27867f0..fa61fbc 100644 --- a/internal/commands/and.go +++ b/internal/commands/and.go @@ -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 } @@ -180,51 +200,110 @@ func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) { } // At least one param is a variable - generate AND code - // Load param1 - if c.param1IsVar { - asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) - } else { - asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) + + // 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 } - // AND with param2 - if c.param2IsVar { - asm = append(asm, fmt.Sprintf("\tand %s", c.param2VarName)) - } else { - asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF))) - } + // Track if A is known to be 0 after low byte (avoids redundant lda #0 for high byte) + aIsZero := false - // Store low byte - asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) + // 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 + if c.param1IsVar { + asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) + } else { + asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) + } + + // AND with param2 (skip if literal $FF, as and #$ff preserves accumulator) + if c.param2IsVar { + asm = append(asm, fmt.Sprintf("\tand %s", c.param2VarName)) + } else if uint8(c.param2Value&0xFF) != 0xFF { + asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF))) + } + + // Store low byte + asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) + } // If destination is word, handle high byte if c.destVarKind == compiler.KindWord { - // Load high byte of param1 - if c.param1IsVar { - if c.param1VarKind == compiler.KindWord { - asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) - } else { + // 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 { - hi := uint8((c.param1Value >> 8) & 0xFF) - asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi)) - } - - // AND with high byte of param2 - if c.param2IsVar { - if c.param2VarKind == compiler.KindWord { - asm = append(asm, fmt.Sprintf("\tand %s+1", c.param2VarName)) + // Load high byte of param1 + if c.param1IsVar { + if c.param1VarKind == compiler.KindWord { + asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) + } else { + asm = append(asm, "\tlda #0") + } } else { - asm = append(asm, "\tand #0") + hi := uint8((c.param1Value >> 8) & 0xFF) + asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi)) } - } else { - hi := uint8((c.param2Value >> 8) & 0xFF) - asm = append(asm, fmt.Sprintf("\tand #$%02x", hi)) - } - // Store high byte - asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) + // AND with high byte of param2 + if c.param2IsVar { + if c.param2VarKind == compiler.KindWord { + asm = append(asm, fmt.Sprintf("\tand %s+1", c.param2VarName)) + } else { + asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF))) + } + } else { + hi := uint8((c.param2Value >> 8) & 0xFF) + asm = append(asm, fmt.Sprintf("\tand #$%02x", hi)) + } + + // Store high byte + asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) + } } return asm, nil diff --git a/internal/commands/and_test.go b/internal/commands/and_test.go index 3b5d3b9..86112b3 100644 --- a/internal/commands/and_test.go +++ b/internal/commands/and_test.go @@ -71,7 +71,6 @@ func TestAndCommand_OldSyntax(t *testing.T) { "\tand b", "\tsta result", "\tlda #0", - "\tand #0", "\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}) }, wantAsm: []string{ - "\tlda #$ff", - "\tand b", + "\tlda b", "\tsta result", }, }, @@ -182,8 +180,7 @@ func TestAndCommand_OldSyntax(t *testing.T) { "\tlda wval", "\tand bval", "\tsta result", - "\tlda wval+1", - "\tand #0", + "\tlda #0", "\tsta result+1", }, }, @@ -286,7 +283,6 @@ func TestAndCommand_NewSyntax(t *testing.T) { "\tand b", "\tsta result", "\tlda #0", - "\tand #0", "\tsta result+1", }, }, @@ -320,6 +316,96 @@ func TestAndCommand_NewSyntax(t *testing.T) { "\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", line: "result = 255 & 15", @@ -358,6 +444,206 @@ func TestAndCommand_NewSyntax(t *testing.T) { "\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", line: "unknown = a & b", diff --git a/internal/commands/or.go b/internal/commands/or.go index a03a406..0dc782d 100644 --- a/internal/commands/or.go +++ b/internal/commands/or.go @@ -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 } @@ -180,25 +200,73 @@ func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) { } // At least one param is a variable - generate OR code - // Load param1 - if c.param1IsVar { - asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) - } else { - asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) + + // 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 } - // OR with param2 - if c.param2IsVar { - asm = append(asm, fmt.Sprintf("\tora %s", c.param2VarName)) - } else { - asm = append(asm, fmt.Sprintf("\tora #$%02x", uint8(c.param2Value&0xFF))) - } + // 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 - // Store low byte - asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) + 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 + if c.param1IsVar { + asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) + } else { + asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) + } + + // OR with param2 (skip if literal 0, as ora #0 is a no-op) + if c.param2IsVar { + asm = append(asm, fmt.Sprintf("\tora %s", c.param2VarName)) + } else if uint8(c.param2Value&0xFF) != 0 { + asm = append(asm, fmt.Sprintf("\tora #$%02x", uint8(c.param2Value&0xFF))) + } + + // Store low byte + asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) + } // If destination is word, handle high byte 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 if c.param1IsVar { if c.param1VarKind == compiler.KindWord { @@ -211,16 +279,18 @@ func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) { 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.param2VarKind == compiler.KindWord { 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 { hi := uint8((c.param2Value >> 8) & 0xFF) - asm = append(asm, fmt.Sprintf("\tora #$%02x", hi)) + if hi != 0 { + asm = append(asm, fmt.Sprintf("\tora #$%02x", hi)) + } + // Skip ora when param2 const high byte is 0 } // Store high byte diff --git a/internal/commands/or_test.go b/internal/commands/or_test.go index ddc7143..9fcdc19 100644 --- a/internal/commands/or_test.go +++ b/internal/commands/or_test.go @@ -71,7 +71,6 @@ func TestOrCommand_OldSyntax(t *testing.T) { "\tora b", "\tsta result", "\tlda #0", - "\tora #0", "\tsta result+1", }, }, @@ -183,7 +182,6 @@ func TestOrCommand_OldSyntax(t *testing.T) { "\tora bval", "\tsta result", "\tlda wval+1", - "\tora #0", "\tsta result+1", }, }, @@ -286,7 +284,6 @@ func TestOrCommand_NewSyntax(t *testing.T) { "\tora b", "\tsta result", "\tlda #0", - "\tora #0", "\tsta result+1", }, }, @@ -320,6 +317,32 @@ func TestOrCommand_NewSyntax(t *testing.T) { "\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", line: "result = 15 | 240", @@ -358,6 +381,294 @@ func TestOrCommand_NewSyntax(t *testing.T) { "\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", line: "unknown = a | b", diff --git a/internal/commands/xor.go b/internal/commands/xor.go index f925a1d..1124888 100644 --- a/internal/commands/xor.go +++ b/internal/commands/xor.go @@ -200,18 +200,34 @@ func (c *XorCommand) Generate(_ *compiler.CompilerContext) ([]string, error) { } // At least one param is a variable - generate XOR code - // Load param1 - if c.param1IsVar { - asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) - } else { - asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) + + // 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 } - // XOR with param2 - if c.param2IsVar { - asm = append(asm, fmt.Sprintf("\teor %s", c.param2VarName)) + // 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 { - asm = append(asm, fmt.Sprintf("\teor #$%02x", uint8(c.param2Value&0xFF))) + // Load param1 + if c.param1IsVar { + asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) + } else { + asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) + } + + // XOR with param2 (skip if literal 0, as eor #0 is a no-op) + if c.param2IsVar { + asm = append(asm, fmt.Sprintf("\teor %s", c.param2VarName)) + } else if uint8(c.param2Value&0xFF) != 0 { + asm = append(asm, fmt.Sprintf("\teor #$%02x", uint8(c.param2Value&0xFF))) + } } // Store low byte diff --git a/internal/commands/xor_test.go b/internal/commands/xor_test.go index 6cc73be..6105952 100644 --- a/internal/commands/xor_test.go +++ b/internal/commands/xor_test.go @@ -463,6 +463,44 @@ func TestXorCommand_NewSyntax(t *testing.T) { "\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", line: "result = 255 ^ 170", @@ -501,6 +539,31 @@ func TestXorCommand_NewSyntax(t *testing.T) { "\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", line: "unknown = a ^ b",