Optimizations of shift operators

This commit is contained in:
Mattias Hansson 2026-04-15 03:03:35 +02:00
parent 0357df9873
commit 822326f993
4 changed files with 843 additions and 233 deletions

View file

@ -175,84 +175,177 @@ func (c *ShiftLCommand) Interpret(line preproc.Line, ctx *compiler.CompilerConte
} }
func (c *ShiftLCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { func (c *ShiftLCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
// Case 1: BYTE destination
if c.destVarKind == compiler.KindByte {
return c.generateByteShift(ctx)
}
// Case 2: WORD destination
return c.generateWordShift(ctx)
}
// generateByteShift handles BYTE << amount
func (c *ShiftLCommand) generateByteShift(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string var asm []string
// Check if shift amount >= bit width (result will be zero) // Constant shift amount
bitWidth := 8
if c.destVarKind == compiler.KindWord {
bitWidth = 16
}
amountZero := false
if !c.amountIsVar { if !c.amountIsVar {
// Constant amount amount := c.amountValue
if c.amountValue >= uint16(bitWidth) {
amountZero = true // Shift >= 8 bits -> zero
} if amount >= 8 {
if amount > 0 {
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 8 bits, value will be zero\n",
c.line.Filename, c.line.LineNo, amount)
} }
if amountZero { // Store zero
// Result is zero, just store zero if c.sourceIsVar && c.sourceVarName == c.destVarName {
if c.destVarKind == compiler.KindByte { // Same variable: just zero it
asm = append(asm, "\tlda #0") asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else { } else {
// Different: load zero and store
asm = append(asm, "\tlda #0") asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
return asm, nil return asm, nil
} }
// Step 1: Copy source to destination if needed // Shift 0 -> copy source
if c.sourceIsVar && c.sourceVarName == c.destVarName { if amount == 0 {
// Same variable, no copy needed return c.generateByteCopy(), nil
} else {
copyAsm := c.generateCopy()
asm = append(asm, copyAsm...)
} }
// Step 2: Apply shift // Constant shift 1-7
shiftAsm, err := c.generateShift(ctx) return c.generateByteShiftConst(amount), nil
if err != nil {
return nil, err
} }
asm = append(asm, shiftAsm...)
// Variable shift amount
return c.generateByteShiftVar(ctx)
}
// generateByteCopy copies source to destination (BYTE)
func (c *ShiftLCommand) generateByteCopy() []string {
var asm []string
if c.sourceIsVar {
if c.sourceVarName == c.destVarName {
// Same variable, no copy needed
return asm
}
// Different variable
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else {
// Literal
val := uint8(c.sourceValue & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", val))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
}
return asm
}
// generateByteShiftConst generates BYTE << constant (1-7)
func (c *ShiftLCommand) generateByteShiftConst(amount uint16) []string {
var asm []string
// Copy source to destination if needed
copyAsm := c.generateByteCopy()
asm = append(asm, copyAsm...)
// Apply shift
for i := uint16(0); i < amount; i++ {
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
}
return asm
}
// generateByteShiftVar generates BYTE << variable
func (c *ShiftLCommand) generateByteShiftVar(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
// Copy source to destination if needed
copyAsm := c.generateByteCopy()
asm = append(asm, copyAsm...)
// Generate labels
loopLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
doneLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
// Variable shift loop
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
asm = append(asm, loopLabel)
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
asm = append(asm, "\tdex")
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
asm = append(asm, doneLabel)
return asm, nil return asm, nil
} }
// generateCopy generates assembly to copy source to destination // generateWordShift handles WORD << amount
func (c *ShiftLCommand) generateCopy() []string { func (c *ShiftLCommand) generateWordShift(ctx *compiler.CompilerContext) ([]string, error) {
// Constant shift amount
if !c.amountIsVar {
amount := c.amountValue
// Shift >= 16 bits -> zero
if amount >= 16 {
if amount > 0 {
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 16 bits, value will be zero\n",
c.line.Filename, c.line.LineNo, amount)
}
// Store zero
if c.sourceIsVar && c.sourceVarName == c.destVarName {
// Same variable: zero both bytes
asm := []string{
"\tlda #0",
fmt.Sprintf("\tsta %s", c.destVarName),
fmt.Sprintf("\tsta %s+1", c.destVarName),
}
return asm, nil
} else {
// Different: load zero and store to both bytes
asm := []string{
"\tlda #0",
fmt.Sprintf("\tsta %s", c.destVarName),
fmt.Sprintf("\tsta %s+1", c.destVarName),
}
return asm, nil
}
}
// Shift 0 -> copy source
if amount == 0 {
return c.generateWordCopy(), nil
}
// Constant shift 1-15
return c.generateWordShiftConst(amount), nil
}
// Variable shift amount
return c.generateWordShiftVar(ctx)
}
// generateWordCopy copies source to destination (WORD)
func (c *ShiftLCommand) generateWordCopy() []string {
var asm []string var asm []string
// If source is literal, just load it if c.sourceIsVar && c.sourceVarName == c.destVarName {
if !c.sourceIsVar { // Same variable, no copy needed
lo := uint8(c.sourceValue & 0xFF)
hi := uint8((c.sourceValue >> 8) & 0xFF)
if c.destVarKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// Optimization: don't reload if lo == hi
if lo != hi {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
return asm return asm
} }
// Source is variable if c.sourceIsVar {
if c.destVarKind == compiler.KindByte { // Variable source
// Destination is byte
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else {
// Destination is word
if c.sourceVarKind == compiler.KindByte { if c.sourceVarKind == compiler.KindByte {
// Byte -> Word (zero-extend) // Byte -> Word (zero-extend)
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
@ -266,83 +359,96 @@ func (c *ShiftLCommand) generateCopy() []string {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName)) asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
} else {
// Literal source
lo := uint8(c.sourceValue & 0xFF)
hi := uint8((c.sourceValue >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if lo != hi {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
return asm return asm
} }
// generateShift generates assembly to shift destination left by amount // generateWordShiftConst generates WORD << constant (1-15)
func (c *ShiftLCommand) generateShift(ctx *compiler.CompilerContext) ([]string, error) { func (c *ShiftLCommand) generateWordShiftConst(amount uint16) []string {
var asm []string var asm []string
// Constant amount // Special case: shift >= 8
if !c.amountIsVar { if amount >= 8 && amount < 16 {
amount := c.amountValue remaining := amount - 8
if amount == 0 {
return asm, nil // No shift needed
}
// Determine bit width // Get source low byte
bitWidth := 8 if c.sourceIsVar {
if c.destVarKind == compiler.KindWord { if c.sourceVarName == c.destVarName {
bitWidth = 16 // Same variable: read from destination (already has value)
} asm = append(asm, fmt.Sprintf("\tlda %s", c.destVarName))
// Warn if shift amount >= bit width (but not for 0)
if amount >= uint16(bitWidth) {
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= %d bits, value will be zero\n",
c.line.Filename, c.line.LineNo, amount, bitWidth)
}
if amount >= uint16(bitWidth) {
// Shift all bits out -> zero
if c.destVarKind == compiler.KindByte {
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else { } else {
asm = append(asm, "\tlda #0") // Different: read from source
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
}
} else {
// Literal
lo := uint8(c.sourceValue & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
}
// Store to destination 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 // Zero low byte
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// Shift high byte remaining bits
for i := uint16(0); i < remaining; i++ {
asm = append(asm, fmt.Sprintf("\tasl %s+1", c.destVarName))
} }
// Unroll shift loop return asm
}
// Shift 1-7: normal copy and shift
copyAsm := c.generateWordCopy()
asm = append(asm, copyAsm...)
// Apply shift
for i := uint16(0); i < amount; i++ { for i := uint16(0); i < amount; i++ {
if c.destVarKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName))
} }
}
return asm, nil
}
// Variable amount return asm
}
// generateWordShiftVar generates WORD << variable
func (c *ShiftLCommand) generateWordShiftVar(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
// Copy source to destination if needed
copyAsm := c.generateWordCopy()
asm = append(asm, copyAsm...)
// Generate labels // Generate labels
loopLabel := ctx.GeneralStack.Push() loopLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop() ctx.GeneralStack.Pop()
doneLabel := ctx.GeneralStack.Push() doneLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop() ctx.GeneralStack.Pop()
// Load amount into X // Variable shift loop
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName)) asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
// Check for zero amount
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel)) asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
// Shift loop
asm = append(asm, loopLabel) asm = append(asm, loopLabel)
if c.destVarKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName))
}
asm = append(asm, "\tdex") asm = append(asm, "\tdex")
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel)) asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
asm = append(asm, doneLabel) asm = append(asm, doneLabel)
return asm, nil return asm, nil
} }

View file

@ -518,6 +518,131 @@ func TestShiftLCommand_Generate(t *testing.T) {
"_L2", "_L2",
}, },
}, },
{
name: "WORD optimization - shift by 8",
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftLCommand{
sourceIsVar: false,
sourceValue: 0x1234,
amountIsVar: false,
amountValue: 8,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$34",
"\tsta result+1",
"\tlda #0",
"\tsta result",
},
},
{
name: "WORD optimization - shift by 12 (8 + 4)",
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftLCommand{
sourceIsVar: false,
sourceValue: 0x00AB,
amountIsVar: false,
amountValue: 12,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$ab",
"\tsta result+1",
"\tlda #0",
"\tsta result",
"\tasl result+1",
"\tasl result+1",
"\tasl result+1",
"\tasl result+1",
},
},
{
name: "BYTE to WORD conversion with shift",
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
ctx.SymbolTable.AddVar("byteval", "", compiler.KindByte, 0x55, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftLCommand{
sourceIsVar: true,
sourceVarName: "byteval",
sourceVarKind: compiler.KindByte,
amountIsVar: false,
amountValue: 4,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda byteval",
"\tsta result",
"\tlda #0",
"\tsta result+1",
"\tasl result",
"\trol result+1",
"\tasl result",
"\trol result+1",
"\tasl result",
"\trol result+1",
"\tasl result",
"\trol result+1",
},
},
{
name: "WORD to BYTE conversion with shift (truncation)",
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
ctx.SymbolTable.AddVar("wordval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftLCommand{
sourceIsVar: true,
sourceVarName: "wordval",
sourceVarKind: compiler.KindWord,
amountIsVar: false,
amountValue: 4,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda wordval",
"\tsta result",
"\tasl result",
"\tasl result",
"\tasl result",
"\tasl result",
},
},
{
name: "WORD same source and destination",
setup: func(ctx *compiler.CompilerContext) *ShiftLCommand {
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftLCommand{
sourceIsVar: true,
sourceVarName: "value",
sourceVarKind: compiler.KindWord,
amountIsVar: true,
amountVarName: "shift",
amountVarKind: compiler.KindByte,
destVarName: "value",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tldx shift",
"\tbeq _L2",
"_L1",
"\tasl value",
"\trol value+1",
"\tdex",
"\tbne _L1",
"_L2",
},
},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -175,83 +175,320 @@ func (c *ShiftRCommand) Interpret(line preproc.Line, ctx *compiler.CompilerConte
} }
func (c *ShiftRCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { func (c *ShiftRCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
// Case 1: BYTE destination
if c.destVarKind == compiler.KindByte {
return c.generateByteShift(ctx)
}
// Case 2: WORD destination
return c.generateWordShift(ctx)
}
// generateByteShift handles BYTE >> amount
func (c *ShiftRCommand) generateByteShift(ctx *compiler.CompilerContext) ([]string, error) {
// Special case: WORD source, BYTE destination
if c.sourceVarKind == compiler.KindWord {
return c.generateWordToByteShift(ctx)
}
var asm []string var asm []string
// Check if shift amount >= bit width (result will be zero) // Constant shift amount
bitWidth := 8
if c.destVarKind == compiler.KindWord {
bitWidth = 16
}
amountZero := false
if !c.amountIsVar { if !c.amountIsVar {
// Constant amount amount := c.amountValue
if c.amountValue >= uint16(bitWidth) {
amountZero = true // Shift >= 8 bits -> zero
} if amount >= 8 {
if amount > 0 {
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 8 bits, value will be zero\n",
c.line.Filename, c.line.LineNo, amount)
} }
if amountZero { // Store zero
// Result is zero, just store zero if c.sourceIsVar && c.sourceVarName == c.destVarName {
if c.destVarKind == compiler.KindByte { // Same variable: just zero it
asm = append(asm, "\tlda #0") asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else { } else {
// Different: load zero and store
asm = append(asm, "\tlda #0") asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
return asm, nil return asm, nil
} }
// Step 1: Copy source to destination if needed // Shift 0 -> copy source
if c.sourceIsVar && c.sourceVarName == c.destVarName { if amount == 0 {
// Same variable, no copy needed return c.generateByteCopy(), nil
} else {
copyAsm := c.generateCopy()
asm = append(asm, copyAsm...)
} }
// Step 2: Apply shift // Constant shift 1-7
shiftAsm, err := c.generateShift(ctx) return c.generateByteShiftConst(amount), nil
if err != nil {
return nil, err
} }
asm = append(asm, shiftAsm...)
// Variable shift amount
return c.generateByteShiftVar(ctx)
}
// generateByteCopy copies source to destination (BYTE)
func (c *ShiftRCommand) generateByteCopy() []string {
var asm []string
if c.sourceIsVar {
if c.sourceVarName == c.destVarName {
// Same variable, no copy needed
return asm
}
// Different variable
if c.sourceVarKind == compiler.KindWord {
// WORD source, BYTE destination - just take low byte
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else {
// BYTE source
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
}
} else {
// Literal
val := uint8(c.sourceValue & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", val))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
}
return asm
}
// generateByteShiftConst generates BYTE >> constant (1-7)
func (c *ShiftRCommand) generateByteShiftConst(amount uint16) []string {
var asm []string
// Copy source to destination if needed
copyAsm := c.generateByteCopy()
asm = append(asm, copyAsm...)
// Apply shift (right shift uses LSR)
for i := uint16(0); i < amount; i++ {
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
}
return asm
}
// generateByteShiftVar generates BYTE >> variable
func (c *ShiftRCommand) generateByteShiftVar(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
// Copy source to destination if needed
copyAsm := c.generateByteCopy()
asm = append(asm, copyAsm...)
// Generate labels
loopLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
doneLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
// Variable shift loop (right shift uses LSR)
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
asm = append(asm, loopLabel)
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
asm = append(asm, "\tdex")
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
asm = append(asm, doneLabel)
return asm, nil return asm, nil
} }
// generateCopy generates assembly to copy source to destination // generateWordToByteShift handles WORD >> amount -> BYTE
func (c *ShiftRCommand) generateCopy() []string { func (c *ShiftRCommand) generateWordToByteShift(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string var asm []string
// If source is literal, just load it // Constant shift amount
if !c.sourceIsVar { if !c.amountIsVar {
lo := uint8(c.sourceValue & 0xFF) amount := c.amountValue
hi := uint8((c.sourceValue >> 8) & 0xFF)
if c.destVarKind == compiler.KindByte { // Shift >= 16 bits -> zero
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo)) if amount >= 16 {
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) if amount > 0 {
} else { _, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 16 bits, value will be zero\n",
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo)) c.line.Filename, c.line.LineNo, amount)
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if lo != hi {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
return asm
} }
// Source is variable // Store zero
if c.destVarKind == compiler.KindByte { asm = append(asm, "\tlda #0")
// Destination is byte asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
return asm, nil
}
// Shift 0 -> just take low byte
if amount == 0 {
if c.sourceIsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else { } else {
// Destination is word val := uint8(c.sourceValue & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", val))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
}
return asm, nil
}
// For WORD -> BYTE right shift, we need to handle the full WORD shift
// then take the low byte. This is more complex than BYTE shift.
// Shift 8-15: result depends only on high byte
if amount >= 8 && amount < 16 {
remaining := amount - 8
// Get high byte
if c.sourceIsVar {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
} else {
hi := uint8((c.sourceValue >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
// Store to destination
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// Shift remaining bits
for i := uint16(0); i < remaining; i++ {
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
}
return asm, nil
}
// Shift 1-7: need full WORD shift, then take low byte
// We'll use the destination as a temporary for the low byte
// and handle high byte in A register
if c.sourceIsVar {
// Load low byte to destination
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// Load high byte to A
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
} else {
// Literal source
lo := uint8(c.sourceValue & 0xFF)
hi := uint8((c.sourceValue >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
// Apply shifts: lsr A (high byte), ror destination (low byte)
for i := uint16(0); i < amount; i++ {
asm = append(asm, "\tlsr")
asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName))
}
return asm, nil
}
// Variable shift amount - use general approach with loop
// We need X for shift count, A for high byte, destination for low byte
// Generate labels
loopLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
doneLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
if c.sourceIsVar {
// Load low byte to destination
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// Load high byte to A
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
} else {
// Literal source
lo := uint8(c.sourceValue & 0xFF)
hi := uint8((c.sourceValue >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
// Load shift amount to X
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
// Shift loop
asm = append(asm, loopLabel)
asm = append(asm, "\tlsr")
asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName))
asm = append(asm, "\tdex")
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
asm = append(asm, doneLabel)
return asm, nil
}
// generateWordShift handles WORD >> amount
func (c *ShiftRCommand) generateWordShift(ctx *compiler.CompilerContext) ([]string, error) {
// Constant shift amount
if !c.amountIsVar {
amount := c.amountValue
// Shift >= 16 bits -> zero
if amount >= 16 {
if amount > 0 {
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 16 bits, value will be zero\n",
c.line.Filename, c.line.LineNo, amount)
}
// Store zero
if c.sourceIsVar && c.sourceVarName == c.destVarName {
// Same variable: zero both bytes
asm := []string{
"\tlda #0",
fmt.Sprintf("\tsta %s", c.destVarName),
fmt.Sprintf("\tsta %s+1", c.destVarName),
}
return asm, nil
} else {
// Different: load zero and store to both bytes
asm := []string{
"\tlda #0",
fmt.Sprintf("\tsta %s", c.destVarName),
fmt.Sprintf("\tsta %s+1", c.destVarName),
}
return asm, nil
}
}
// Shift 0 -> copy source
if amount == 0 {
return c.generateWordCopy(), nil
}
// Constant shift 1-15
return c.generateWordShiftConst(amount), nil
}
// Variable shift amount
return c.generateWordShiftVar(ctx)
}
// generateWordCopy copies source to destination (WORD)
func (c *ShiftRCommand) generateWordCopy() []string {
var asm []string
if c.sourceIsVar && c.sourceVarName == c.destVarName {
// Same variable, no copy needed
return asm
}
if c.sourceIsVar {
// Variable source
if c.sourceVarKind == compiler.KindByte { if c.sourceVarKind == compiler.KindByte {
// Byte -> Word (zero-extend) // Byte -> Word (zero-extend)
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
@ -265,84 +502,96 @@ func (c *ShiftRCommand) generateCopy() []string {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName)) asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
} else {
// Literal source
lo := uint8(c.sourceValue & 0xFF)
hi := uint8((c.sourceValue >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if lo != hi {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
} }
return asm return asm
} }
// generateShift generates assembly to shift destination right by amount // generateWordShiftConst generates WORD >> constant (1-15)
func (c *ShiftRCommand) generateShift(ctx *compiler.CompilerContext) ([]string, error) { func (c *ShiftRCommand) generateWordShiftConst(amount uint16) []string {
var asm []string var asm []string
// Constant amount // Special case: shift >= 8
if !c.amountIsVar { if amount >= 8 && amount < 16 {
amount := c.amountValue remaining := amount - 8
if amount == 0 {
return asm, nil // No shift needed
}
// Determine bit width // Get source high byte
bitWidth := 8 if c.sourceIsVar {
if c.destVarKind == compiler.KindWord { if c.sourceVarName == c.destVarName {
bitWidth = 16 // Same variable: read from destination (already has value)
} asm = append(asm, fmt.Sprintf("\tlda %s+1", c.destVarName))
// Warn if shift amount >= bit width (but not for 0)
if amount >= uint16(bitWidth) {
_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= %d bits, value will be zero\n",
c.line.Filename, c.line.LineNo, amount, bitWidth)
}
if amount >= uint16(bitWidth) {
// Shift all bits out -> zero
if c.destVarKind == compiler.KindByte {
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
} else { } else {
asm = append(asm, "\tlda #0") // Different: read from source
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
}
} else {
// Literal
hi := uint8((c.sourceValue >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
// Store to destination low byte
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// Zero high byte
asm = append(asm, "\tlda #0")
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
return asm, nil // Shift low byte remaining bits (right shift)
for i := uint16(0); i < remaining; i++ {
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
} }
// Unroll shift loop return asm
}
// Shift 1-7: normal copy and shift
copyAsm := c.generateWordCopy()
asm = append(asm, copyAsm...)
// Apply shift (right shift uses LSR high byte, ROR low byte)
for i := uint16(0); i < amount; i++ { for i := uint16(0); i < amount; i++ {
if c.destVarKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlsr %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tlsr %s+1", c.destVarName))
asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName))
} }
}
return asm, nil
}
// Variable amount return asm
}
// generateWordShiftVar generates WORD >> variable
func (c *ShiftRCommand) generateWordShiftVar(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
// Copy source to destination if needed
copyAsm := c.generateWordCopy()
asm = append(asm, copyAsm...)
// Generate labels // Generate labels
loopLabel := ctx.GeneralStack.Push() loopLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop() ctx.GeneralStack.Pop()
doneLabel := ctx.GeneralStack.Push() doneLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop() ctx.GeneralStack.Pop()
// Variable shift loop (right shift uses LSR high byte, ROR low byte)
// Load amount into X
asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName)) asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
// Check for zero amount
asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel)) asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
// Shift loop
asm = append(asm, loopLabel) asm = append(asm, loopLabel)
if c.destVarKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tlsr %s", c.destVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlsr %s+1", c.destVarName)) asm = append(asm, fmt.Sprintf("\tlsr %s+1", c.destVarName))
asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tror %s", c.destVarName))
}
asm = append(asm, "\tdex") asm = append(asm, "\tdex")
asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel)) asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
asm = append(asm, doneLabel) asm = append(asm, doneLabel)
return asm, nil return asm, nil
} }

View file

@ -518,6 +518,136 @@ func TestShiftRCommand_Generate(t *testing.T) {
"_L2", "_L2",
}, },
}, },
{
name: "WORD optimization - shift by 8",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0x1234,
amountIsVar: false,
amountValue: 8,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$12",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "WORD optimization - shift by 12 (8 + 4)",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0xAB00,
amountIsVar: false,
amountValue: 12,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$ab",
"\tsta result",
"\tlda #0",
"\tsta result+1",
"\tlsr result",
"\tlsr result",
"\tlsr result",
"\tlsr result",
},
},
{
name: "BYTE to WORD conversion with shift",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("byteval", "", compiler.KindByte, 0x55, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "byteval",
sourceVarKind: compiler.KindByte,
amountIsVar: false,
amountValue: 4,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda byteval",
"\tsta result",
"\tlda #0",
"\tsta result+1",
"\tlsr result+1",
"\tror result",
"\tlsr result+1",
"\tror result",
"\tlsr result+1",
"\tror result",
"\tlsr result+1",
"\tror result",
},
},
{
name: "WORD to BYTE conversion with shift (truncation)",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("wordval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "wordval",
sourceVarKind: compiler.KindWord,
amountIsVar: false,
amountValue: 4,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda wordval",
"\tsta result",
"\tlda wordval+1",
"\tlsr",
"\tror result",
"\tlsr",
"\tror result",
"\tlsr",
"\tror result",
"\tlsr",
"\tror result",
},
},
{
name: "WORD same source and destination",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "value",
sourceVarKind: compiler.KindWord,
amountIsVar: true,
amountVarName: "shift",
amountVarKind: compiler.KindByte,
destVarName: "value",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tldx shift",
"\tbeq _L2",
"_L1",
"\tlsr value+1",
"\tror value",
"\tdex",
"\tbne _L1",
"_L2",
},
},
} }
for _, tt := range tests { for _, tt := range tests {