allow const expression intermediate values to be 32 bits integers to avoid needless overflow errors.

This commit is contained in:
Irmen de Jong 2023-12-03 01:48:01 +01:00
parent 2b227b43fe
commit c609e982fe
7 changed files with 90 additions and 25 deletions

View File

@ -1,12 +1,13 @@
package prog8.code.core package prog8.code.core
enum class DataType { enum class DataType {
UBYTE, // pass by value UBYTE, // pass by value 8 bits unsigned
BYTE, // pass by value BYTE, // pass by value 8 bits signed
UWORD, // pass by value UWORD, // pass by value 16 bits unsigned
WORD, // pass by value WORD, // pass by value 16 bits signed
FLOAT, // pass by value LONG, // pass by value 32 bits signed
BOOL, // pass by value FLOAT, // pass by value machine dependent
BOOL, // pass by value bit 0 of a 8 bit byte
STR, // pass by reference STR, // pass by reference
ARRAY_UB, // pass by reference ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference ARRAY_B, // pass by reference
@ -23,11 +24,12 @@ enum class DataType {
*/ */
infix fun isAssignableTo(targetType: DataType) = infix fun isAssignableTo(targetType: DataType) =
when(this) { when(this) {
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT) BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL) UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT) BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
UWORD -> targetType.oneOf(UWORD, FLOAT) UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT) WORD -> targetType.oneOf(WORD, LONG, FLOAT)
LONG -> targetType.oneOf(LONG, FLOAT)
FLOAT -> targetType.oneOf(FLOAT) FLOAT -> targetType.oneOf(FLOAT)
STR -> targetType.oneOf(STR, UWORD) STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this in ArrayDatatypes -> targetType == this
@ -41,7 +43,8 @@ enum class DataType {
this == other -> false this == other -> false
this in ByteDatatypes -> false this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes this in WordDatatypes -> other in ByteDatatypes
this== STR && other== UWORD || this== UWORD && other== STR -> false this == LONG -> other in ByteDatatypes+WordDatatypes
this == STR && other == UWORD || this == UWORD && other == STR -> false
else -> true else -> true
} }
@ -123,11 +126,11 @@ enum class BranchCondition {
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL) val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD) val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL) val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL) val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT) val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL) val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD) val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT) val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)

View File

@ -62,6 +62,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(identifier: IdentifierReference) { override fun visit(identifier: IdentifierReference) {
checkLongType(identifier)
val stmt = identifier.targetStatement(program) val stmt = identifier.targetStatement(program)
if(stmt==null) if(stmt==null)
errors.undefined(identifier.nameInSource, identifier.position) errors.undefined(identifier.nameInSource, identifier.position)
@ -279,6 +280,10 @@ internal class AstChecker(private val program: Program,
super.visit(label) super.visit(label)
} }
override fun visit(numLiteral: NumericLiteral) {
checkLongType(numLiteral)
}
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean { private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor class Searcher: IAstVisitor
{ {
@ -528,6 +533,9 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(assignTarget: AssignTarget) { override fun visit(assignTarget: AssignTarget) {
if(assignTarget.inferType(program).istype(DataType.LONG))
errors.err("integer overflow", assignTarget.position)
super.visit(assignTarget) super.visit(assignTarget)
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt() val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
@ -577,6 +585,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(addressOf: AddressOf) { override fun visit(addressOf: AddressOf) {
checkLongType(addressOf)
val variable=addressOf.identifier.targetVarDecl(program) val variable=addressOf.identifier.targetVarDecl(program)
if(variable!=null && variable.type==VarDeclType.CONST) if(variable!=null && variable.type==VarDeclType.CONST)
errors.err("invalid pointer-of operand type", addressOf.position) errors.err("invalid pointer-of operand type", addressOf.position)
@ -584,6 +593,9 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
if(decl.datatype==DataType.LONG)
errors.err("integer overflow", decl.position)
fun err(msg: String) = errors.err(msg, decl.position) fun err(msg: String) = errors.err(msg, decl.position)
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
@ -915,6 +927,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: PrefixExpression) { override fun visit(expr: PrefixExpression) {
checkLongType(expr)
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED) val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
if(dt==DataType.UNDEFINED) if(dt==DataType.UNDEFINED)
return // any error should be reported elsewhere return // any error should be reported elsewhere
@ -935,6 +948,7 @@ internal class AstChecker(private val program: Program,
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
super.visit(expr) super.visit(expr)
checkLongType(expr)
val leftIDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
@ -1036,6 +1050,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(typecast: TypecastExpression) { override fun visit(typecast: TypecastExpression) {
checkLongType(typecast)
if(typecast.type in IterableDatatypes) if(typecast.type in IterableDatatypes)
errors.err("cannot type cast to string or array type", typecast.position) errors.err("cannot type cast to string or array type", typecast.position)
@ -1082,6 +1097,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(functionCallExpr: FunctionCallExpression) { override fun visit(functionCallExpr: FunctionCallExpression) {
checkLongType(functionCallExpr)
// this function call is (part of) an expression, which should be in a statement somewhere. // this function call is (part of) an expression, which should be in a statement somewhere.
val stmtOfExpression = findParentNode<Statement>(functionCallExpr) val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}") ?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
@ -1137,6 +1153,11 @@ internal class AstChecker(private val program: Program,
super.visit(functionCallExpr) super.visit(functionCallExpr)
} }
override fun visit(bfc: BuiltinFunctionCall) {
checkLongType(bfc)
super.visit(bfc)
}
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors) val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)
if(targetStatement!=null) { if(targetStatement!=null) {
@ -1273,6 +1294,10 @@ internal class AstChecker(private val program: Program,
} }
} }
} }
args.forEach{
checkLongType(it)
}
} }
override fun visit(postIncrDecr: PostIncrDecr) { override fun visit(postIncrDecr: PostIncrDecr) {
@ -1310,6 +1335,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
checkLongType(arrayIndexedExpression)
val target = arrayIndexedExpression.arrayvar.targetStatement(program) val target = arrayIndexedExpression.arrayvar.targetStatement(program)
if(target is VarDecl) { if(target is VarDecl) {
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD) if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
@ -1454,6 +1480,12 @@ internal class AstChecker(private val program: Program,
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position) errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
} }
private fun checkLongType(expression: Expression) {
if(expression.inferType(program).istype(DataType.LONG)) {
errors.err("integer overflow", expression.position)
}
}
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean { private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
return if (targetDt == DataType.STR) { return if (targetDt == DataType.STR) {
when { when {

View File

@ -1043,4 +1043,27 @@ main {
errors.errors.single() shouldContain "doesn't match" errors.errors.single() shouldContain "doesn't match"
} }
test("long type okay in const expr but otherwise overflow") {
val src="""
main {
sub start() {
const ubyte HEIGHT=240
uword large = 320*240/8/8
thing(large)
thing(320*240/8/8)
thing(320*HEIGHT/8/8)
thing(320*HEIGHT) ; overflow
}
sub thing(uword value) {
value++
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "can't cast"
errors.errors[1] shouldContain "overflow"
}
}) })

View File

@ -484,6 +484,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position) in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position) in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position) in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position)
else -> throw FatalAstException("integer overflow: $dvalue") else -> throw FatalAstException("integer overflow: $dvalue")
} }
} }
@ -492,6 +493,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return when (value) { return when (value) {
in 0u..255u -> NumericLiteral(DataType.UBYTE, value.toDouble(), position) in 0u..255u -> NumericLiteral(DataType.UBYTE, value.toDouble(), position)
in 0u..65535u -> NumericLiteral(DataType.UWORD, value.toDouble(), position) in 0u..65535u -> NumericLiteral(DataType.UWORD, value.toDouble(), position)
in 0u..2147483647u -> NumericLiteral(DataType.LONG, value.toDouble(), position)
else -> throw FatalAstException("unsigned integer overflow: $value") else -> throw FatalAstException("unsigned integer overflow: $value")
} }
} }
@ -635,6 +637,9 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
DataType.BOOL -> { DataType.BOOL -> {
return CastValue(true, NumericLiteral(targettype, number, position)) return CastValue(true, NumericLiteral(targettype, number, position))
} }
DataType.LONG -> {
/* ignore this cast, LONG can't be used. Error will be given elsewhere */
}
else -> { else -> {
throw FatalAstException("type cast of weird type $type") throw FatalAstException("type cast of weird type $type")
} }

View File

@ -67,6 +67,7 @@ object InferredTypes {
DataType.BYTE to InferredType.known(DataType.BYTE), DataType.BYTE to InferredType.known(DataType.BYTE),
DataType.UWORD to InferredType.known(DataType.UWORD), DataType.UWORD to InferredType.known(DataType.UWORD),
DataType.WORD to InferredType.known(DataType.WORD), DataType.WORD to InferredType.known(DataType.WORD),
DataType.LONG to InferredType.known(DataType.LONG),
DataType.FLOAT to InferredType.known(DataType.FLOAT), DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.BOOL to InferredType.known(DataType.BOOL), DataType.BOOL to InferredType.known(DataType.BOOL),
DataType.STR to InferredType.known(DataType.STR), DataType.STR to InferredType.known(DataType.STR),

View File

@ -12,7 +12,6 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Compiler: Compiler:
- Currently "320*240/8/8" gives integer overflow, so: allow constant integer subexpressions to contain out of range integers (>65535 etc) as long as the final constant value is within byte/word range.
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. - Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays.
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type - make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
(this is already done hardcoded for several of the builtin functions) (this is already done hardcoded for several of the builtin functions)

View File

@ -1,12 +1,14 @@
%zeropage basicsafe
%import textio
%option no_sysinit
main { main {
sub start() { sub start() {
txt.print(iso:"This is ISO text.\n") const ubyte HEIGHT=240
uword large = 320*240/8/8
thing(large)
thing(320*240/8/8)
thing(320*HEIGHT/8/8)
thing(320*HEIGHT) ; overflow
}
repeat { sub thing(uword value) {
} value++
} }
} }