From c609e982fec3679851cf33455c1704ab160a81a2 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 3 Dec 2023 01:48:01 +0100 Subject: [PATCH] allow const expression intermediate values to be 32 bits integers to avoid needless overflow errors. --- codeCore/src/prog8/code/core/Enumerations.kt | 37 ++++++++++--------- .../compiler/astprocessing/AstChecker.kt | 32 ++++++++++++++++ compiler/test/TestTypecasts.kt | 23 ++++++++++++ .../prog8/ast/expressions/AstExpressions.kt | 5 +++ .../prog8/ast/expressions/InferredTypes.kt | 1 + docs/source/todo.rst | 1 - examples/test.p8 | 16 ++++---- 7 files changed, 90 insertions(+), 25 deletions(-) diff --git a/codeCore/src/prog8/code/core/Enumerations.kt b/codeCore/src/prog8/code/core/Enumerations.kt index 9545b6118..32905f3d1 100644 --- a/codeCore/src/prog8/code/core/Enumerations.kt +++ b/codeCore/src/prog8/code/core/Enumerations.kt @@ -1,12 +1,13 @@ package prog8.code.core enum class DataType { - UBYTE, // pass by value - BYTE, // pass by value - UWORD, // pass by value - WORD, // pass by value - FLOAT, // pass by value - BOOL, // pass by value + UBYTE, // pass by value 8 bits unsigned + BYTE, // pass by value 8 bits signed + UWORD, // pass by value 16 bits unsigned + WORD, // pass by value 16 bits signed + LONG, // pass by value 32 bits signed + FLOAT, // pass by value machine dependent + BOOL, // pass by value bit 0 of a 8 bit byte STR, // pass by reference ARRAY_UB, // pass by reference ARRAY_B, // pass by reference @@ -23,11 +24,12 @@ enum class DataType { */ infix fun isAssignableTo(targetType: DataType) = when(this) { - BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT) - UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL) - BYTE -> targetType.oneOf(BYTE, WORD, FLOAT) - UWORD -> targetType.oneOf(UWORD, FLOAT) - WORD -> targetType.oneOf(WORD, FLOAT) + BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT) + UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL) + BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT) + UWORD -> targetType.oneOf(UWORD, LONG, FLOAT) + WORD -> targetType.oneOf(WORD, LONG, FLOAT) + LONG -> targetType.oneOf(LONG, FLOAT) FLOAT -> targetType.oneOf(FLOAT) STR -> targetType.oneOf(STR, UWORD) in ArrayDatatypes -> targetType == this @@ -41,7 +43,8 @@ enum class DataType { this == other -> false this in ByteDatatypes -> false 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 } @@ -123,11 +126,11 @@ enum class BranchCondition { val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL) 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) -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.FLOAT) -val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT) +val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG) +val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL +val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT) +val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL +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 StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD) val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 17ea474b3..0f4abc776 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -62,6 +62,7 @@ internal class AstChecker(private val program: Program, } override fun visit(identifier: IdentifierReference) { + checkLongType(identifier) val stmt = identifier.targetStatement(program) if(stmt==null) errors.undefined(identifier.nameInSource, identifier.position) @@ -279,6 +280,10 @@ internal class AstChecker(private val program: Program, super.visit(label) } + override fun visit(numLiteral: NumericLiteral) { + checkLongType(numLiteral) + } + private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean { class Searcher: IAstVisitor { @@ -528,6 +533,9 @@ internal class AstChecker(private val program: Program, } override fun visit(assignTarget: AssignTarget) { + if(assignTarget.inferType(program).istype(DataType.LONG)) + errors.err("integer overflow", assignTarget.position) + super.visit(assignTarget) 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) { + checkLongType(addressOf) val variable=addressOf.identifier.targetVarDecl(program) if(variable!=null && variable.type==VarDeclType.CONST) 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) { + if(decl.datatype==DataType.LONG) + errors.err("integer overflow", decl.position) + fun err(msg: String) = errors.err(msg, decl.position) // 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) { + checkLongType(expr) val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED) if(dt==DataType.UNDEFINED) return // any error should be reported elsewhere @@ -935,6 +948,7 @@ internal class AstChecker(private val program: Program, override fun visit(expr: BinaryExpression) { super.visit(expr) + checkLongType(expr) val leftIDt = expr.left.inferType(program) val rightIDt = expr.right.inferType(program) @@ -1036,6 +1050,7 @@ internal class AstChecker(private val program: Program, } override fun visit(typecast: TypecastExpression) { + checkLongType(typecast) if(typecast.type in IterableDatatypes) 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) { + checkLongType(functionCallExpr) // this function call is (part of) an expression, which should be in a statement somewhere. val stmtOfExpression = findParentNode(functionCallExpr) ?: 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) } + override fun visit(bfc: BuiltinFunctionCall) { + checkLongType(bfc) + super.visit(bfc) + } + override fun visit(functionCallStatement: FunctionCallStatement) { val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors) if(targetStatement!=null) { @@ -1273,6 +1294,10 @@ internal class AstChecker(private val program: Program, } } } + + args.forEach{ + checkLongType(it) + } } override fun visit(postIncrDecr: PostIncrDecr) { @@ -1310,6 +1335,7 @@ internal class AstChecker(private val program: Program, } override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { + checkLongType(arrayIndexedExpression) val target = arrayIndexedExpression.arrayvar.targetStatement(program) if(target is VarDecl) { 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) } + 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 { return if (targetDt == DataType.STR) { when { diff --git a/compiler/test/TestTypecasts.kt b/compiler/test/TestTypecasts.kt index 348916537..b15a9bd8a 100644 --- a/compiler/test/TestTypecasts.kt +++ b/compiler/test/TestTypecasts.kt @@ -1043,4 +1043,27 @@ main { 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" + } + }) diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index b3f79a3c8..36d386de2 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -484,6 +484,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position) in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position) in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position) + in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position) else -> throw FatalAstException("integer overflow: $dvalue") } } @@ -492,6 +493,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed return when (value) { in 0u..255u -> NumericLiteral(DataType.UBYTE, 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") } } @@ -635,6 +637,9 @@ class NumericLiteral(val type: DataType, // only numerical types allowed DataType.BOOL -> { return CastValue(true, NumericLiteral(targettype, number, position)) } + DataType.LONG -> { + /* ignore this cast, LONG can't be used. Error will be given elsewhere */ + } else -> { throw FatalAstException("type cast of weird type $type") } diff --git a/compilerAst/src/prog8/ast/expressions/InferredTypes.kt b/compilerAst/src/prog8/ast/expressions/InferredTypes.kt index 2451a74c4..8d0ddbd81 100644 --- a/compilerAst/src/prog8/ast/expressions/InferredTypes.kt +++ b/compilerAst/src/prog8/ast/expressions/InferredTypes.kt @@ -67,6 +67,7 @@ object InferredTypes { DataType.BYTE to InferredType.known(DataType.BYTE), DataType.UWORD to InferredType.known(DataType.UWORD), DataType.WORD to InferredType.known(DataType.WORD), + DataType.LONG to InferredType.known(DataType.LONG), DataType.FLOAT to InferredType.known(DataType.FLOAT), DataType.BOOL to InferredType.known(DataType.BOOL), DataType.STR to InferredType.known(DataType.STR), diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e1661ef9e..1b6148fee 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -12,7 +12,6 @@ Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ 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. - 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) diff --git a/examples/test.p8 b/examples/test.p8 index ae379a977..9d5c5cd44 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,12 +1,14 @@ -%zeropage basicsafe -%import textio -%option no_sysinit - main { 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++ } }