fix const evaluation of bitwise logical expressions (&, |, ^, <<, >>) of signed operands

This commit is contained in:
Irmen de Jong
2025-04-30 22:27:31 +02:00
parent b047731f82
commit d04164c0a6
5 changed files with 154 additions and 42 deletions
@@ -5,10 +5,7 @@ import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.BaseDataType
import prog8.code.core.Position
import prog8.code.core.isInteger
import prog8.code.core.isIntegerOrBool
import prog8.code.core.*
import kotlin.math.*
@@ -70,52 +67,45 @@ class ConstExprEvaluator {
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() xor right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
}
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() or right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() or right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() or right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
}
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() and right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() and right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() and right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
@@ -185,6 +185,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
}
}
// check if shifts have a positive integer shift type
if(expr.operator=="<<" || expr.operator==">>") {
if(rightDt.isInteger) {
val rconst = expr.right.constValue(program)
if(rconst!=null && rconst.number<0)
errors.err("can only shift by a positive amount", expr.right.position)
} else
errors.err("right operand of bit shift must be an integer", expr.right.position)
}
}
return noModifications
+62
View File
@@ -406,4 +406,66 @@ main {
errors.errors[0] shouldContain(":4:37: no cast available")
errors.errors[1] shouldContain(":5:37: no cast available")
}
test("const evaluation of signed bitwise operations") {
val src="""
%import textio
main {
sub start() {
byte @shared a = -1
byte @shared b = -15
ubyte @shared ub = 2
const byte ca = -1
const byte cb = -15
const ubyte cub = 2
txt.print_ub( a & b )
txt.spc()
txt.print_ub( a | b )
txt.spc()
txt.print_ub( a ^ b )
txt.spc()
txt.print_b( a << ub )
txt.spc()
txt.print_b( a >> ub )
txt.nl()
txt.print_ub( ca & cb )
txt.spc()
txt.print_ub( ca | cb )
txt.spc()
txt.print_ub( ca ^ cb )
txt.spc()
txt.print_b( ca << cub )
txt.spc()
txt.print_b( ca >> cub )
txt.nl()
word @shared aw = -1
word @shared bw = -15
uword @shared uw = 2
const word caw = -1
const word cbw = -15
const uword cuw = 2
txt.print_uw( aw & bw )
txt.spc()
txt.print_uw( aw | bw )
txt.spc()
txt.print_uw( aw ^ bw )
txt.nl()
txt.print_uw( caw & cbw )
txt.spc()
txt.print_uw( caw | cbw )
txt.spc()
txt.print_uw( caw ^ cbw )
txt.nl()
txt.print_w( cbw << cuw )
txt.spc()
txt.print_w( cbw >> cuw )
txt.nl()
}
}"""
compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
}
})
+2
View File
@@ -923,6 +923,8 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``%``
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
``&`` is bitwise and, ``|`` is bitwise or, ``^`` is bitwise xor, ``~`` is bitwise invert (this one is an unary operator)
``<<`` is bitwise left shift and ``>>`` is bitwise right shift (both will not change the datatype of the value)
While the operands can be signed integers (the expression will just consider the underlying bit patterns),
the result value of a bitwise expression is always unsigned.
assignment: ``=``
Sets the target on the LHS (left hand side) of the operator to the value of the expression on the RHS (right hand side).
+52 -4
View File
@@ -1,9 +1,57 @@
%zeropage basicsafe
%import textio
main {
sub start() {
cx16.r0 = foo()
}
byte @shared a = -1
byte @shared b = -15
ubyte @shared ub = 2
const byte ca = -1
const byte cb = -15
const ubyte cub = 2
extsub $f000 = foo() clobbers(X) -> uword @AY
txt.print_ub( a & b )
txt.spc()
txt.print_ub( a | b )
txt.spc()
txt.print_ub( a ^ b )
txt.spc()
txt.print_b( a << ub )
txt.spc()
txt.print_b( a >> ub )
txt.nl()
txt.print_ub( ca & cb )
txt.spc()
txt.print_ub( ca | cb )
txt.spc()
txt.print_ub( ca ^ cb )
txt.spc()
txt.print_b( ca << cub )
txt.spc()
txt.print_b( ca >> cub )
txt.nl()
word @shared aw = -1
word @shared bw = -15
uword @shared uw = 2
const word caw = -1
const word cbw = -15
const uword cuw = 2
txt.print_uw( aw & bw )
txt.spc()
txt.print_uw( aw | bw )
txt.spc()
txt.print_uw( aw ^ bw )
txt.nl()
txt.print_uw( caw & cbw )
txt.spc()
txt.print_uw( caw | cbw )
txt.spc()
txt.print_uw( caw ^ cbw )
txt.nl()
txt.print_w( cbw << cuw )
txt.spc()
txt.print_w( cbw >> cuw )
txt.nl()
}
}