optimize pointer.field += 1 into pointer.field INC/DEC

This commit is contained in:
Irmen de Jong
2025-09-16 21:57:44 +02:00
parent a8bede17b2
commit a71895cbe8
4 changed files with 246 additions and 126 deletions
@@ -257,16 +257,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
when (operator) {
"+" -> {
if(target.dt.isByte) inplaceByteAdd(target, value)
else if(target.dt.isWord) inplaceWordAdd(target, value)
else if(target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
val amount = value.number.number.toInt()
inplaceByteInc(target, amount)
} else {
if (target.dt.isByte) inplaceByteAdd(target, value)
else if (target.dt.isWord) inplaceWordAdd(target, value)
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
}
"-" -> {
if(target.dt.isByte) inplaceByteSub(target, value)
else if(target.dt.isWord) inplaceWordSub(target, value)
else if(target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
val amount = value.number.number.toInt()
inplaceByteDec(target, amount)
} else {
if (target.dt.isByte) inplaceByteSub(target, value)
else if (target.dt.isWord) inplaceWordSub(target, value)
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
}
}
"*" -> {
if(target.dt.isByte) TODO("inplaceByteMul(target, value) ${target.position}")
@@ -1184,6 +1194,46 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
private fun inplaceByteInc(target: PtrTarget, amount: Int) {
require(amount==1 || amount==2)
val (zpPtrVar, offset) = deref(target.pointer)
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar)")
repeat(amount) {
asmgen.out(" inc a")
}
asmgen.out(" sta ($zpPtrVar)")
}
else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
clc
adc #$amount
sta ($zpPtrVar),y""")
}
}
private fun inplaceByteDec(target: PtrTarget, amount: Int) {
require(amount==1 || amount==2)
val (zpPtrVar, offset) = deref(target.pointer)
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar)")
repeat(amount) {
asmgen.out(" dec a")
}
asmgen.out(" sta ($zpPtrVar)")
}
else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
sec
sbc #$amount
sta ($zpPtrVar),y""")
}
}
private fun inplaceByteAdd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
@@ -1544,44 +1594,44 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
when {
target.datatype.isByteOrBool -> {
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_PTR),y""")
asmgen.assignRegister(RegisterOrPair.A, target)
}
target.datatype.isWord || target.datatype.isPointer -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
ldy #1
lda (P8ZP_SCRATCH_PTR),y
tax
lda (P8ZP_SCRATCH_PTR)""")
else
asmgen.out("""
ldy #1
lda (P8ZP_SCRATCH_PTR),y
tax
dey
lda (P8ZP_SCRATCH_PTR),y""")
asmgen.assignRegister(RegisterOrPair.AX, target)
}
target.datatype.isLong -> {
TODO("assign long from pointer to $target ${target.position}")
}
target.datatype.isFloat -> {
// TODO optimize the float copying to avoid having to go through FAC1
asmgen.out("""
lda P8ZP_SCRATCH_PTR
ldy P8ZP_SCRATCH_PTR+1
jsr floats.MOVFM""")
asmgen.assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt ${target.datatype}")
}
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
// when {
// target.datatype.isByteOrBool -> {
// asmgen.out("""
// ldy #0
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.A, target)
// }
// target.datatype.isWord || target.datatype.isPointer -> {
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// lda (P8ZP_SCRATCH_PTR)""")
// else
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// dey
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.AX, target)
// }
// target.datatype.isLong -> {
// TODO("assign long from pointer to $target ${target.position}")
// }
// target.datatype.isFloat -> {
// // TODO optimize the float copying to avoid having to go through FAC1
// asmgen.out("""
// lda P8ZP_SCRATCH_PTR
// ldy P8ZP_SCRATCH_PTR+1
// jsr floats.MOVFM""")
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
// }
// else -> throw AssemblyError("weird dt ${target.datatype}")
// }
}
private fun saveOnStack(regs: RegisterOrPair) {
@@ -137,63 +137,75 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val inplaceInstrs = mutableListOf<IRCodeChunkBase>()
val (addressReg, fieldOffset) = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
val oldvalueReg = codeGen.registers.next(targetDt)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
inplaceInstrs += operandTr.chunks
}
if(targetDt== IRDataType.FLOAT) {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
if((augAssign.operator=="+=" || augAssign.operator=="-=") && value.asConstInteger()==1 || value.asConstInteger()==2) {
// INC/DEC optimization instead of ADD/SUB
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
val instr = if(augAssign.operator=="+=") Opcode.INC else Opcode.DEC
repeat(value.asConstInteger()!!) {
addInstr(inplaceInstrs, IRInstruction(instr, targetDt, reg1 = oldvalueReg), null)
}
} else {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
// note: the instructions to load the value will be placed after the LOADFIELD instruction so that later optimizations about what modification is actually done, are easier
}
if(targetDt== IRDataType.FLOAT) {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
} else {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
}
@@ -222,6 +234,42 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return chunks
}
private fun loadfield(
inplaceInstrs: MutableList<IRCodeChunkBase>,
addressReg: Int,
fieldOffset: UByte,
targetDt: IRDataType,
oldvalueReg: Int
) {
if (targetDt == IRDataType.FLOAT) {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg),
null
)
} else {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg),
null
)
}
}
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
val value: PtExpression
if(origAssign.operator in PrefixOperators) {
+1 -3
View File
@@ -58,10 +58,8 @@ and for example the below code omits line 5::
STRUCTS and TYPED POINTERS
--------------------------
- fix code size regressions (if any left)
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
- update structpointers.rst docs with 6502 specific things?
- implement the remaining TODO's in PointerAssignmentsGen.
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
- optimize the float copying in assignIndexedPointer() (also word?)
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
- try to optimize pointer arithmetic used in peek/poke a bit more so the routines in sorting module can use typed pointers without increasing code size, see test.p8 in commit d394dc1e
+48 -24
View File
@@ -2,38 +2,62 @@
%zeropage basicsafe
main {
word bignum
struct Node {
ubyte id
str name
bool flag
word counter
}
sub start() {
txt.print_uw(allocator.alloc(10))
^^Node test = []
bignum = 11111
test.counter = 22222
txt.print_w(bignum)
txt.spc()
txt.print_uw(allocator.alloc(20))
bignum++
txt.print_w(bignum)
txt.spc()
txt.print_uw(allocator.alloc(30))
bignum--
txt.print_w(bignum)
txt.nl()
allocator.freeall()
txt.print_w(test.counter)
txt.spc()
test.counter ++
txt.print_uw(allocator.alloc(10))
txt.print_w(test.counter)
txt.spc()
txt.print_uw(allocator.alloc(20))
test.counter --
txt.print_w(test.counter)
txt.nl()
txt.print_w(bignum)
txt.spc()
txt.print_uw(allocator.alloc(30))
bignum+=5555
txt.print_w(bignum)
txt.spc()
bignum-=5555
txt.print_w(bignum)
txt.nl()
txt.print_w(test.counter)
txt.spc()
test.counter += 5555
txt.print_w(test.counter)
txt.spc()
test.counter -= 5555
txt.print_w(test.counter)
txt.nl()
}
}
allocator {
; extremely trivial allocator allocator
uword buffer = memory("allocator", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
; cannot free individual allocations only the whole allocator at once
next = buffer
}
}