optimize codegen for x += array[index] (and others)

This commit is contained in:
Irmen de Jong 2025-01-01 23:49:05 +01:00
parent 68d5983a14
commit 3b4b37f16b
4 changed files with 183 additions and 270 deletions

View File

@ -1115,6 +1115,21 @@ $repeatLabel""")
}
}
internal fun signExtendAXlsb(valueDt: BaseDataType) {
// sign extend signed byte in A to full word in AX
when(valueDt) {
BaseDataType.UBYTE -> out(" ldx #0")
BaseDataType.BYTE -> out("""
ldx #0
cmp #$80
bcc +
dex
+
""")
else -> throw AssemblyError("need byte type")
}
}
internal fun signExtendVariableLsb(asmvar: String, valueDt: BaseDataType) {
// sign extend signed byte in a var to a full word in that variable
when(valueDt) {

View File

@ -68,18 +68,18 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
when {
target.datatype.isByteOrBool -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationByteVariableWithLiteralval(target.asmVarname, target.datatype, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(target.asmVarname, target.datatype.isSigned, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(target.asmVarname, target.datatype.isSigned, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(target.asmVarname, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(target.asmVarname, target.datatype, operator, value.array!!)
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationByteWithLiteralval(target.asmVarname, target.datatype, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteWithVariable(target.asmVarname, target.datatype.isSigned, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteWithVariable(target.asmVarname, target.datatype.isSigned, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteWithValue(target.asmVarname, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteWithValue(target.asmVarname, target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationByteVariableWithValue(target.asmVarname, target.datatype, operator, value.expression)
inplacemodificationByteWithValue(target.asmVarname, target.datatype, operator, value.expression)
} else {
inplacemodificationByteVariableWithValue(target.asmVarname, target.datatype, operator, value.expression!!)
inplacemodificationByteWithValue(target.asmVarname, target.datatype, operator, value.expression!!)
}
}
}
@ -131,18 +131,18 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
is PtNumber -> {
val addr = (memory.address as PtNumber).number.toInt()
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationByteVariableWithLiteralval(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(addr.toHex(), false, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(addr.toHex(), false, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.array!!)
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationByteWithLiteralval(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteWithLiteralval(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteWithVariable(addr.toHex(), false, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteWithVariable(addr.toHex(), false, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationByteVariableWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.expression)
inplacemodificationByteWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.expression)
} else {
inplacemodificationByteVariableWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.expression!!)
inplacemodificationByteWithValue(addr.toHex(), DataType.forDt(BaseDataType.UBYTE), operator, value.expression!!)
}
}
}
@ -192,21 +192,21 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.MEMORY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", DataType.forDt(BaseDataType.UBYTE), operator, value.memory!!)
inplacemodificationByteWithValue("P8ZP_SCRATCH_B1", DataType.forDt(BaseDataType.UBYTE), operator, value.memory!!)
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", DataType.forDt(BaseDataType.UBYTE), operator, value.array!!)
inplacemodificationByteWithValue("P8ZP_SCRATCH_B1", DataType.forDt(BaseDataType.UBYTE), operator, value.array!!)
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteVariableWithValue(tempVar, DataType.forDt(BaseDataType.UBYTE), operator, value.expression)
inplacemodificationByteWithValue(tempVar, DataType.forDt(BaseDataType.UBYTE), operator, value.expression)
else
inplacemodificationByteVariableWithValue(tempVar, DataType.forDt(BaseDataType.UBYTE), operator, value.expression!!)
inplacemodificationByteWithValue(tempVar, DataType.forDt(BaseDataType.UBYTE), operator, value.expression!!)
asmgen.out(" ldx $tempVar")
}
}
@ -239,18 +239,18 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
when {
target.datatype.isByteOrBool -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype.isSigned, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype.isSigned, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationByteWithLiteralval(targetVarName, target.datatype, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteWithVariable(targetVarName, target.datatype.isSigned, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteWithVariable(targetVarName, target.datatype.isSigned, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteWithValue(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteWithValue(targetVarName, target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
inplacemodificationByteWithValue(targetVarName, target.datatype, operator, value.expression)
} else {
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
inplacemodificationByteWithValue(targetVarName, target.datatype, operator, value.expression!!)
}
}
}
@ -336,14 +336,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.MEMORY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
inplacemodificationByteWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
inplacemodificationByteWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
@ -352,9 +352,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteVariableWithValue(tempVar, target.datatype, operator, value.expression)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
else
inplacemodificationByteVariableWithValue(tempVar, target.datatype, operator, value.expression!!)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda $tempVar")
}
@ -1057,7 +1057,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationByteVariableWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
private fun inplacemodificationByteWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
require(dt.isByteOrBool)
if(!value.isSimple()) {
// attempt short-circuit (McCarthy) evaluation
@ -1102,13 +1102,27 @@ $shortcutLabel:""")
}
}
if(value is PtArrayIndexer && value.isSimple()) {
// use the already existing optimized codegen for regular assignments x += array[index]
val binexpr = PtBinaryExpression(operator, dt, value.position)
binexpr.add(PtIdentifier(name, dt, value.position))
val arrayValue = PtArrayIndexer(value.type, value.position)
arrayValue.add(value.variable)
arrayValue.add(value.index)
binexpr.add(arrayValue)
binexpr.parent = value
assignmentAsmGen.assignExpressionToRegister(binexpr, RegisterOrPair.A, dt.isSigned)
asmgen.out(" sta $name")
return
}
// normal evaluation
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt.isSigned)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt.isSigned)
asmgen.out(" sta $name")
}
private fun inplacemodificationByteVariableWithVariable(name: String, signed: Boolean, operator: String, otherName: String) {
private fun inplacemodificationByteWithVariable(name: String, signed: Boolean, operator: String, otherName: String) {
// note: no logical and/or shortcut here, not worth it due to simple right operand
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
@ -1314,6 +1328,7 @@ $shortcutLabel:""")
when (operator) {
"-" -> {
// A = variable - A
// TODO optimize codegen to avoid temporary
val tmpVar = if(variable!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out(" sta $tmpVar | lda $variable | sec | sbc $tmpVar")
}
@ -1465,7 +1480,7 @@ $shortcutLabel:""")
}
}
private fun inplacemodificationByteVariableWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
private fun inplacemodificationByteWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
// note: no logical and/or shortcut here, not worth it due to simple right operand
require(dt.isByteOrBool)
@ -2634,16 +2649,16 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
require(dt.isWord)
fun multiplyVarByWordInAY() {
fun multiplyVarByWordInAX() {
if(block?.options?.veraFxMuls==true)
// cx16 verafx hardware muls
asmgen.out("""
sta cx16.r1
sty cx16.r1+1
stx cx16.r1+1
lda $name
ldy $name+1
ldx $name+1
sta cx16.r0
sty cx16.r0+1
stx cx16.r0+1
jsr verafx.muls
sta $name
sty $name+1
@ -2651,7 +2666,7 @@ $shortcutLabel:""")
else
asmgen.out("""
sta prog8_math.multiply_words.multiplier
sty prog8_math.multiply_words.multiplier+1
stx prog8_math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr prog8_math.multiply_words
@ -2753,8 +2768,8 @@ $shortcutLabel:""")
// value is (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word * byte multiplication routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt.base)
multiplyVarByWordInAY()
asmgen.signExtendAXlsb(valueDt.base)
multiplyVarByWordInAX()
}
"/" -> {
// value is (u) byte value, sign extend that and proceed with regular 16 bit operation
@ -2852,6 +2867,66 @@ $shortcutLabel:""")
}
valueDt.isWord -> {
// the value is a proper 16-bit word, so use both bytes of it.
if(value is PtArrayIndexer && value.isSimple()) {
// note: use AX as much as possible, to free Y for array indexing
when (operator) {
// note: use AX as much as possible, to free Y for array indexing
"+" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AX)
asmgen.out(" clc | adc $name | sta $name | txa | adc $name+1 | sta $name+1")
return
}
"-" -> {
if(value.index.type.isByte) {
// it's an array indexed by a byte so we can use sbc array,y
val arrayname = value.variable.name
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
asmgen.out("""
lda $name
sec
sbc ${arrayname}_lsb,y
sta $name
lda $name+1
sbc ${arrayname}_msb,y
sta $name+1""")
} else {
asmgen.out("""
lda $name
sec
sbc $arrayname,y
sta $name
lda $name+1
sbc $arrayname+1,y
sta $name+1""")
}
return
}
}
"*" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AX)
multiplyVarByWordInAX()
return
}
"&" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AX)
asmgen.out(" and $name | sta $name | txa | and $name+1 | sta $name+1")
return
}
"|" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AX)
asmgen.out(" ora $name | sta $name | txa | ora $name+1 | sta $name+1")
return
}
"^" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AX)
asmgen.out(" eor $name | sta $name | txa | eor $name+1 | sta $name+1")
return
}
}
}
when (operator) {
"+" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
@ -2860,11 +2935,11 @@ $shortcutLabel:""")
"-" -> {
val tmpWord = if(name!="P8ZP_SCRATCH_W1") "P8ZP_SCRATCH_W1" else "P8ZP_SCRATCH_W2"
asmgen.assignExpressionToVariable(value, tmpWord, valueDt)
asmgen.out(" lda $name | sec | sbc $tmpWord | sta $name | lda $name+1 | sbc $tmpWord+1 | sta $name+1")
asmgen.out(" lda $name | sec | sbc $tmpWord | sta $name | lda $name+1 | sbc $tmpWord+1 | sta $name+1")
}
"*" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
multiplyVarByWordInAY()
asmgen.assignExpressionToRegister(value, RegisterOrPair.AX)
multiplyVarByWordInAX()
}
"/" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
@ -2893,37 +2968,7 @@ $shortcutLabel:""")
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
asmgen.out(" eor $name | sta $name | tya | eor $name+1 | sta $name+1")
}
"==" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
asmgen.out("""
cmp $name
bne +
cpy $name+1
bne +
lda #1
bne ++
+ lda #0
+ sta $name
lda #0
sta $name+1""")
}
"!=" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
asmgen.out("""
cmp $name
bne +
cpy $name+1
bne +
lda #0
beq ++
+ lda #1
+ sta $name
lda #0
sta $name+1""")
}
// pretty uncommon, who's going to assign a comparison boolean expression to a word var?:
"<", "<=", ">", ">=" -> TODO("word-value-to-var comparisons")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
else -> throw AssemblyError("invalid operator for in-place word modification $operator")
}
}
else -> throw AssemblyError("can only use integer datatypes here")

View File

@ -1,10 +1,6 @@
TODO
====
- optimize word addition word += mul40[indexbyte] to use adc with register indexed instructions
- same with other operators (sbc, and, or, eor)
- how is tye codegen for byte values here?
- add paypal donation button as well?
- announce prog8 on the 6502.org site?
@ -56,7 +52,7 @@ IR/VM
- fix TODO("IR rol/ror on split words array")
- fix "<< in array" / ">> in array"
- implement fast code paths for TODO("inplace split....
- sometimes source lines get missing in the output p8ir, for example the first assignment is gone in:
- sometimes source lines end up missing in the output p8ir, for example the first assignment is gone in:
sub start() {
cx16.r0L = cx16.r1 as ubyte
cx16.r0sL = cx16.r1s as byte

View File

@ -1,201 +1,58 @@
%import textio
%import math
%import monogfx
%import gfx_lores
%option no_sysinit
%zeropage basicsafe
main {
sub start() {
ubyte bb, index
uword ww
index=2
math.rndseed(1234,8877)
cbm.SETTIM(0,0,0)
kernal()
txt.print_uw(cbm.RDTIM16())
ww = 1234
cx16.r0=10000
sys.wait(200)
cx16.r0 -= ww
txt.print_uw(cx16.r0) ; 7532
txt.nl()
cx16.r0 += ww
txt.print_uw(cx16.r0) ; 10000
txt.nl()
cx16.r0 &= ww
txt.print_uw(cx16.r0) ; 10000
txt.nl()
math.rndseed(1234,8877)
cbm.SETTIM(0,0,0)
custom_256()
txt.print_uw(cbm.RDTIM16())
; bb += tabb[index]
; bb -= index
; bb -= tabb[index]
; bb &= tabb[index]
; bb |= tabb[index]
; bb ^= tabb[index]
; bb *= tabb[index]
;
; cx16.r0L = bb + tabb[index]
; cx16.r1L = bb - index
; cx16.r2L = bb - tabb[index]
; cx16.r3L = bb & tabb[index]
; cx16.r4L = bb | tabb[index]
; cx16.r5L = bb ^ tabb[index]
; cx16.r6L = bb * tabb[index]
sys.wait(200)
; ww += tabw[index]
; ww -= tabw[index]
; ww &= tabw[index]
; ww |= tabw[index]
; ww ^= tabw[index]
; ww *= tabw[index]
;
; cx16.r0 = ww + tabw[index]
; cx16.r1 = ww - cx16.r0
; cx16.r2 = ww - tabw[index]
; cx16.r3 = ww & tabw[index]
; cx16.r4 = ww | tabw[index]
; cx16.r5 = ww ^ tabw[index]
; cx16.r6 = ww * tabw[index]
math.rndseed(1234,8877)
cbm.SETTIM(0,0,0)
custom_mono()
txt.print_uw(cbm.RDTIM16())
repeat {
}
ubyte[] tabb = [11,22,33,44,55,66]
uword[] tabw = [111,222,333,444,555,666]
}
sub kernal() {
cx16.set_screen_mode(128)
cx16.GRAPH_set_colors(2,1,0)
cx16.GRAPH_clear()
repeat 1000 {
cx16.r0 = math.rndw() % 320
cx16.r1 = math.rnd() % 240
cx16.r2 = math.rndw() % 320
cx16.r3 = math.rnd() % 240
cx16.GRAPH_draw_line(cx16.r0, cx16.r1, cx16.r2, cx16.r3)
}
cx16.set_screen_mode(0)
}
sub custom_mono() {
monogfx.lores()
repeat 1000 {
cx16.r0 = math.rndw() % 320
cx16.r1L = math.rnd() % 240
cx16.r2 = math.rndw() % 320
cx16.r3L = math.rnd() % 240
line(cx16.r0, cx16.r1L, cx16.r2, cx16.r3L)
}
monogfx.textmode()
}
sub custom_256() {
gfx_lores.graphics_mode()
repeat 1000 {
cx16.r0 = math.rndw() % 320
cx16.r1L = math.rnd() % 240
cx16.r2 = math.rndw() % 320
cx16.r3L = math.rnd() % 240
gfx_lores.line(cx16.r0, cx16.r1L, cx16.r2, cx16.r3L, 2)
}
gfx_lores.text_mode()
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
x1 = x2
x2 = cx16.r0
cx16.r0L = y1
y1 = y2
y2 = cx16.r0L
}
word @zp dx = (x2 as word)-x1
ubyte @zp dy = y2-y1
if dx==0 {
monogfx.vertical_line(x1, y1, abs(dy) as uword +1, true)
return
}
if dy==0 {
if x1>x2
x1=x2
monogfx.horizontal_line(x1, y1, abs(dx) as uword +1, true)
return
}
cx16.r1L = 1 ;; true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r1L = 0 ;; false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
word @zp d = 0
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = 0
if dx >= dy {
if cx16.r1L!=0 {
repeat {
plot()
if x1==x2
return
x1++
d += dy2
if d > dx {
y1++
d -= dx2
}
}
} else {
repeat {
plot()
if x1==x2
return
x1--
d += dy2
if d > dx {
y1++
d -= dx2
}
}
}
}
else {
if cx16.r1L!=0 {
repeat {
plot()
if y1==y2
return
y1++
d += dx2
if d > dy {
x1++
d -= dy2
}
}
} else {
repeat {
plot()
if y1==y2
return
y1++
d += dx2
if d > dy {
x1--
d -= dy2
}
}
}
}
asmsub plot() {
%asm {{
lda p8v_x1+1
lsr a
lda p8v_x1
ror a
lsr a
lsr a
clc
ldy p8v_y1
adc times40_lo,y
sta cx16.VERA_ADDR_L
lda times40_mid,y
adc #0
sta cx16.VERA_ADDR_M
lda p8v_x1
and #7
tax
lda maskbits,x
tsb cx16.VERA_DATA0
rts
maskbits .byte 128,64,32,16,8,4,2,1
; multiplication by 40 lookup table
times40 := 40*range(240)
times40_lo .byte <times40
times40_mid .byte >times40
; !notreached!
}}
}
}
}