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
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)

View File

@ -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<Statement>(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 {

View File

@ -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"
}
})

View File

@ -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")
}

View File

@ -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),

View File

@ -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)

View File

@ -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++
}
}