retain type of consts better to avoid precision loss

this also fixed a difference in const calculation where the result could differ if you were using optimzations or not.
This commit is contained in:
Irmen de Jong 2024-09-14 20:10:59 +02:00
parent aba1a73e28
commit 0d3ad80659
11 changed files with 129 additions and 90 deletions

View File

@ -66,7 +66,7 @@ class ConstExprEvaluator {
// DataType.WORD -> result = result.toShort().toInt() // DataType.WORD -> result = result.toShort().toInt()
// else -> { /* keep as it is */ } // else -> { /* keep as it is */ }
// } // }
return NumericLiteral.optimalNumeric(result.toDouble(), left.position) return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
} }
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral { private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
@ -133,7 +133,7 @@ class ConstExprEvaluator {
val error = "cannot add $left and $right" val error = "cannot add $left and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -150,7 +150,7 @@ class ConstExprEvaluator {
val error = "cannot subtract $left and $right" val error = "cannot subtract $left and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -167,7 +167,7 @@ class ConstExprEvaluator {
val error = "cannot multiply ${left.type} and ${right.type}" val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -190,7 +190,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt() val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteral.optimalInteger(result, left.position) NumericLiteral.optimalInteger(left.type, right.type, result, left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number==0.0) divideByZeroError(right.position) if(right.number==0.0) divideByZeroError(right.position)
@ -219,7 +219,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position) NumericLiteral.optimalNumeric(left.type, right.type, left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position) if(right.number ==0.0) divideByZeroError(right.position)

View File

@ -33,6 +33,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
} }
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> { override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(numLiteral.type==DataType.LONG) {
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=DataType.LONG)
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
if(parent is Assignment) { if(parent is Assignment) {
val iDt = parent.target.inferType(program) val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) { if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
@ -164,13 +172,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(leftconst==null && rightconst!=null && rightconst.number<0.0) { if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
if (expr.operator == "-") { if (expr.operator == "-") {
// X - -1 ---> X + 1 // X - -1 ---> X + 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position) val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position) val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
} }
else if (expr.operator == "+") { else if (expr.operator == "+") {
// X + -1 ---> X - 1 // X + -1 ---> X - 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position) val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position) val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
} }
@ -384,12 +392,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) { if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program) val valueDt = numval.inferType(program)
if(valueDt istype DataType.LONG) {
return noModifications // this is handled in the numericalvalue case
}
if(valueDt isnot decl.datatype) { if(valueDt isnot decl.datatype) {
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) { val cast = numval.cast(decl.datatype, true)
val cast = numval.cast(decl.datatype, true) if (cast.isValid)
if (cast.isValid) return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
} }
} }
return noModifications return noModifications

View File

@ -15,6 +15,9 @@
; ;
; NOTE: the bitmap screen data is positioned in vram at $0:0000 ; NOTE: the bitmap screen data is positioned in vram at $0:0000
; ;
; NOTE: In the future, these routines might be split out to separate modules, 1 for each screen mode,
; so they can be optimized a lot better. There's already a "gfx_lores" module with a few routines for lores 256C mode.
;
; SCREEN MODE LIST: ; SCREEN MODE LIST:
; mode 0 = reset back to default text mode ; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 x 256c (8 bpp) ; mode 1 = bitmap 320 x 240 x 256c (8 bpp)

View File

@ -233,7 +233,7 @@ math {
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte { sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A. ; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary ; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
ubyte angle = atan2(x1, y1, x2, y2) - 256/48 ubyte angle = atan2(x1, y1, x2, y2) - (256/48 as ubyte)
return 23-lsb(mkword(angle,0) / 2730) return 23-lsb(mkword(angle,0) / 2730)
} }

View File

@ -190,21 +190,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
} }
val cvalue = assignment.value.constValue(program) val cvalue = assignment.value.constValue(program)
if(cvalue!=null) { if(cvalue!=null) {
val number = cvalue.number return castLiteral(cvalue)
// more complex comparisons if the type is different, but the constant value is compatible
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
if(number<0x80)
return castLiteral(cvalue)
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
if(number<0x8000)
return castLiteral(cvalue)
}
} }
} }
} }
@ -357,15 +343,72 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
} }
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> { override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
val fromConst = range.from.constValue(program)
val toConst = range.to.constValue(program)
if(fromConst!=null) {
val smaller = NumericLiteral.optimalInteger(fromConst.number.toInt(), fromConst.position)
if(fromConst.type.largerThan(smaller.type)) {
val toType = range.to.inferType(program)
if(toType isnot smaller.type) {
if(toConst!=null) {
// can we make the to value into the same smaller type?
val smallerTo = NumericLiteral.optimalInteger(toConst.number.toInt(), toConst.position)
if(smaller.type==smallerTo.type) {
val newRange = RangeExpression(smaller, smallerTo, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
} else {
val newRange = RangeExpression(smaller, range.to, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
}
if(toConst!=null) {
val smaller = NumericLiteral.optimalInteger(toConst.number.toInt(), toConst.position)
if(toConst.type.largerThan(smaller.type)) {
val fromType = range.from.inferType(program)
if(fromType isnot smaller.type) {
if(fromConst!=null) {
// can we make the from value into the same smaller type?
val smallerFrom = NumericLiteral.optimalInteger(fromConst.number.toInt(), fromConst.position)
if(smaller.type==smallerFrom.type) {
val newRange = RangeExpression(smallerFrom, smaller, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
} else {
val newRange = RangeExpression(range.from, smaller, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
}
val modifications = mutableListOf<IAstModification>()
val fromDt = range.from.inferType(program).getOr(DataType.UNDEFINED) val fromDt = range.from.inferType(program).getOr(DataType.UNDEFINED)
val toDt = range.to.inferType(program).getOr(DataType.UNDEFINED) val toDt = range.to.inferType(program).getOr(DataType.UNDEFINED)
val modifications = mutableListOf<IAstModification>()
val (commonDt, toChange) = BinaryExpression.commonDatatype(fromDt, toDt, range.from, range.to) val (commonDt, toChange) = BinaryExpression.commonDatatype(fromDt, toDt, range.from, range.to)
if(toChange!=null) if(toChange!=null)
addTypecastOrCastedValueModification(modifications, toChange, commonDt, range) addTypecastOrCastedValueModification(modifications, toChange, commonDt, range)
return modifications return modifications
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val constIdx = arrayIndexedExpression.indexer.constIndex()
if(constIdx!=null) {
val smaller = NumericLiteral.optimalInteger(constIdx, arrayIndexedExpression.indexer.position)
val idxDt = arrayIndexedExpression.indexer.indexExpr.inferType(program).getOr(DataType.UNDEFINED)
if(idxDt.largerThan(smaller.type)) {
val newIdx = ArrayIndex(smaller, smaller.position)
val newIndexer = ArrayIndexedExpression(arrayIndexedExpression.arrayvar, newIdx, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, newIndexer, parent))
}
}
return noModifications
}
private fun addTypecastOrCastedValueModification( private fun addTypecastOrCastedValueModification(
modifications: MutableList<IAstModification>, modifications: MutableList<IAstModification>,
expressionToCast: Expression, expressionToCast: Expression,

View File

@ -498,6 +498,24 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
fun fromBoolean(bool: Boolean, position: Position) = fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteral(DataType.BOOL, if(bool) 1.0 else 0.0, position) NumericLiteral(DataType.BOOL, if(bool) 1.0 else 0.0, position)
fun optimalNumeric(origType1: DataType, origType2: DataType?, value: Number, position: Position) : NumericLiteral {
val optimal = optimalNumeric(value, position)
val largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2
if(largestOrig.largerThan(optimal.type))
return NumericLiteral(largestOrig, optimal.number, position)
else
return optimal
}
fun optimalInteger(origType1: DataType, origType2: DataType?, value: Int, position: Position): NumericLiteral {
val optimal = optimalInteger(value, position)
val largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2
if(largestOrig.largerThan(optimal.type))
return NumericLiteral(largestOrig, optimal.number, position)
else
return optimal
}
fun optimalNumeric(value: Number, position: Position): NumericLiteral { fun optimalNumeric(value: Number, position: Position): NumericLiteral {
val digits = floor(value.toDouble()) - value.toDouble() val digits = floor(value.toDouble()) - value.toDouble()
return if(value is Double && digits!=0.0) { return if(value is Double && digits!=0.0) {
@ -1090,7 +1108,16 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
} else if(vardecl.type!= VarDeclType.CONST) { } else if(vardecl.type!= VarDeclType.CONST) {
return null return null
} }
return vardecl.value?.constValue(program)
// the value of a variable can (temporarily) be a different type as the vardecl itself.
// don't return the value if the types don't match yet!
val value = vardecl.value?.constValue(program)
if(value==null || value.type==vardecl.datatype)
return value
val optimal = NumericLiteral.optimalNumeric(value.number, value.position)
if(optimal.type==vardecl.datatype)
return optimal
return null
} }
override fun toString(): String { override fun toString(): String {

View File

@ -1,8 +1,10 @@
TODO TODO
==== ====
wrong answer if cast as uword is not done in: Should give type error, but seems to make num a word !?:
const uword W=320; uword x1 = ((WIDTH-256)/2 as uword) + math.sin8u(i) const uword screenwidth = txt.DEFAULT_WIDTH
const ubyte num = (screenwidth-1) / 2
callgraph issue? : if a sub contains another sub and it calls that, the outer sub is never removed even if it doesn't get called? callgraph issue? : if a sub contains another sub and it calls that, the outer sub is never removed even if it doesn't get called?

View File

@ -33,9 +33,9 @@ main {
} }
if fastserial if fastserial
diskio.fastmode(3) void diskio.fastmode(3)
else else
diskio.fastmode(0) void diskio.fastmode(0)
test_save() test_save()
test_save_blocks() test_save_blocks()

View File

@ -3,8 +3,8 @@
%zeropage basicsafe %zeropage basicsafe
main { main {
const uword width = 60 const ubyte width = 60
const uword height = 50 const ubyte height = 50
const ubyte max_iter = 16 const ubyte max_iter = 16
sub start() { sub start() {

View File

@ -6,8 +6,8 @@
; Note: this program can be compiled for multiple target systems. ; Note: this program can be compiled for multiple target systems.
main { main {
const uword width = 30 const ubyte width = 30
const uword height = 20 const ubyte height = 20
const ubyte max_iter = 16 const ubyte max_iter = 16
sub start() { sub start() {

View File

@ -1,61 +1,16 @@
%import textio %import textio
%import gfx_lores
%import emudbg
%zeropage basicsafe %zeropage basicsafe
%option no_sysinit %option no_sysinit
main { main {
const uword WIDTH = 320
const ubyte HEIGHT = 240
sub start() { sub start() {
uword clo, chi uword @shared large = (320*240/8/8)
const uword WIDTH=320
void cx16.set_screen_mode(128) uword x1 = ((WIDTH-256)/2 as uword) + 200
txt.print_uw(x1)
word x1, y1, x2, y2
ubyte i
ubyte color = 2
sys.set_irqd()
emudbg.reset_cpu_cycles()
for i in 0 to 254 step 4 {
x1 = ((WIDTH-256)/2 as word) + math.sin8u(i) as word
y1 = (HEIGHT-128)/2 + math.cos8u(i)/2
x2 = ((WIDTH-64)/2 as word) + math.sin8u(i)/4 as word
y2 = (HEIGHT-64)/2 + math.cos8u(i)/4
cx16.GRAPH_set_colors(color, 0, 1)
cx16.GRAPH_draw_line(x1 as uword, y1 as uword, x2 as uword, y2 as uword)
}
clo, chi = emudbg.cpu_cycles()
sys.clear_irqd()
txt.print_uwhex(chi, true)
txt.print_uwhex(clo, false)
txt.nl() txt.nl()
x1 = ((WIDTH-256)/2) + 200
sys.wait(50) txt.print_uw(x1)
cx16.GRAPH_clear()
sys.wait(50)
sys.set_irqd()
emudbg.reset_cpu_cycles()
color = 5
for i in 0 to 254 step 4 {
x1 = ((WIDTH-256)/2 as word) + math.sin8u(i) as word
y1 = (HEIGHT-128)/2 + math.cos8u(i)/2
x2 = ((WIDTH-64)/2 as word) + math.sin8u(i)/4 as word
y2 = (HEIGHT-64)/2 + math.cos8u(i)/4
gfx_lores.line(x1 as uword, y1 as ubyte, x2 as uword, y2 as ubyte, color)
}
clo, chi = emudbg.cpu_cycles()
sys.clear_irqd()
txt.print_uwhex(chi, true)
txt.print_uwhex(clo, false)
txt.nl()
} }
} }