fix rounding errors in signed divide by power-of-two

The optimized bit-shifting division is removed (for now)
This commit is contained in:
Irmen de Jong 2022-07-24 12:21:10 +02:00
parent 05f935b598
commit dcc1f00048
5 changed files with 35 additions and 49 deletions

View File

@ -519,14 +519,14 @@ internal class ExpressionsAsmGen(private val program: Program,
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) {
translateExpressionInternal(expr.left)
when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
else -> throw AssemblyError("wrong dt")
// shifting only yields the correct rounded result on unsinged numbers
if(leftDt==DataType.UBYTE) {
asmgen.out(" lsr P8ESTACK_LO+1,x")
return
} else if(leftDt==DataType.UWORD) {
asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
return
}
return
}
}
}

View File

@ -511,14 +511,14 @@ class CodeGen(internal val program: PtProgram,
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1) {
if(pow2==1 && !signed) {
// just shift 1 bit
code += if(signed)
VmCodeInstruction(Opcode.ASR, dt, reg1=reg)
else
VmCodeInstruction(Opcode.LSR, dt, reg1=reg)
}
else if(pow2>=1) {
else if(pow2>=1 &&!signed) {
// just shift multiple bits
val pow2reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
@ -544,14 +544,14 @@ class CodeGen(internal val program: PtProgram,
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1) {
if(pow2==1 && !signed) {
// just shift 1 bit
code += if(signed)
VmCodeInstruction(Opcode.ASRM, dt, value=address)
else
VmCodeInstruction(Opcode.LSRM, dt, value=address)
}
else if(pow2>=1) {
else if(pow2>=1 && !signed) {
// just shift multiple bits
val pow2reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)

View File

@ -21,7 +21,6 @@ import kotlin.math.pow
class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@ -469,19 +468,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
}
in powersOfTwo -> {
if (leftDt in IntegerDatatypes) {
// divided by a power of two => shift right
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
// unsigned number divided by a power of two => shift right
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
if (leftDt == DataType.UBYTE) {
@ -537,13 +529,6 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftValue.inferType(program).isInteger) {
// times a negative power of two => negate, then shift left
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
}
// no need to check for left val constant (because of associativity)

View File

@ -3,9 +3,11 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- what to do with the rouding difference in signed divide by 2 / 4 (double ror)? it rounds towards minus infinity (so -5 / 2 = -3)
while the NON-optimized routine produces -2 . Virtual machine also produces -3?
What rounding do we want?
- Add optimized signed word division for factors of 2 (bit shifting but this time with correct rounding)
CodeGen divideByConst() and divideByConstInplace()
ExpressionsAsmGen translateExpression()
ExpressionSimplifier optimizeDivision() ?
- add item to XyzZeropage that enables an option that if zeropage=FULL or KERNALSAFE, moves the cx16 virtual registers to ZP, same location as on x16
(can be done on C64 only for now) Remove those addresses from the ZP free pool = allocate them in ZP like Cx16Zeropage does
Adapt the code in AstPreprocessor that relocates the registers as well.

View File

@ -3,28 +3,27 @@
%zeropage basicsafe
main {
sub derp(word num, ubyte a1, ubyte a2, ubyte a3, ubyte a4) {
txt.print_w(num)
txt.nl()
}
; TODO test with new optimized division routines.
sub start() {
word bb = -15
bb /= 4
txt.print_w(bb)
byte qq = 1
byte bb = -51
derp((bb*qq)/-4, 1,2,3,4)
bb /= -4
txt.print_b(bb)
txt.nl()
bb = 15
bb /= 4
txt.print_w(bb)
bb = 51
bb /= -4
txt.print_b(bb)
txt.nl()
uword ubb = 15
ubyte ubb = 51
ubb /= 4
txt.print_uw(ubb)
txt.print_ub(ubb)
txt.nl()
recurse1()
}
sub recurse1() {
recurse2()
}
sub recurse2() {
start()
}
}