RPN: better handling of bit shifts

This commit is contained in:
Irmen de Jong 2023-03-21 02:54:26 +01:00
parent 2afd283582
commit 134fd62da8
10 changed files with 133 additions and 71 deletions

View File

@ -270,10 +270,16 @@ class PtRpn(type: DataType, position: Position): PtExpression(type, position) {
fun finalLeftOperand() = children[children.size-3] fun finalLeftOperand() = children[children.size-3]
fun finalRightOperand() = children[children.size-2] fun finalRightOperand() = children[children.size-2]
fun finalOperation() = Triple(finalLeftOperand(), finalOperator(), finalRightOperand()) fun finalOperation() = Triple(finalLeftOperand(), finalOperator(), finalRightOperand())
fun truncateLastOperator(): PtRpn { fun truncateLastOperator(): PtExpression {
// NOTE: this is a destructive operation! // NOTE: this is a destructive operation!
children.removeLast() children.removeLast()
children.removeLast() children.removeLast()
if(children.last() !is PtRpnOperator) {
if(children.size==1 && children[0] is PtExpression)
return children[0] as PtExpression
else
throw IllegalArgumentException("don't know what to do with children: $children")
}
val finalOper = finalOperator() val finalOper = finalOperator()
if(finalOper.type==type) return this if(finalOper.type==type) return this
val typeAdjusted = PtRpn(finalOper.type, this.position) val typeAdjusted = PtRpn(finalOper.type, this.position)
@ -286,7 +292,7 @@ class PtRpn(type: DataType, position: Position): PtExpression(type, position) {
class PtRpnOperator(val operator: String, val type: DataType, val leftType: DataType, val rightType: DataType, position: Position): PtNode(position) { class PtRpnOperator(val operator: String, val type: DataType, val leftType: DataType, val rightType: DataType, position: Position): PtNode(position) {
init { init {
// NOTE: For now, we require that the types of the operands are the same size as the output type of the operator node. // NOTE: For now, we require that the types of the operands are the same size as the output type of the operator node.
if(operator !in ComparisonOperators) { if(operator !in ComparisonOperators && operator != "<<" && operator != ">>") {
require(type equalsSize leftType && type equalsSize rightType) { require(type equalsSize leftType && type equalsSize rightType) {
"operand type size(s) differ from operator result type $type: $leftType $rightType oper: $operator" "operand type size(s) differ from operator result type $type: $leftType $rightType oper: $operator"
} }

View File

@ -374,16 +374,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
} }
if(value.children.size==3 && left is PtExpression && right is PtExpression) { if(value.children.size==3 && left is PtExpression && right is PtExpression) {
// TODO RPN make this work also when left is not an Expression (in which case size >3)
if (simpleEqualityExprRPN(left, oper.operator, right, assign.target)) if (simpleEqualityExprRPN(left, oper.operator, right, assign.target))
return true return true
if (simpleLogicalExprRPN(left, oper.operator, right, assign.target)) if (simpleLogicalExprRPN(left, oper.operator, right, assign.target))
return true return true
if (simplePlusOrMinusExprRPN(left, oper.operator, right, assign.target)) if (simplePlusOrMinusExprRPN(left, oper.operator, right, assign.target))
return true return true
if (simpleBitshiftExprRPN(left, oper.operator, right, assign.target))
return true
} }
if (simpleBitshiftExprRPN(value, oper, right, assign.target))
return true
val asmExtra = asmgen.subroutineExtra(scope) val asmExtra = asmgen.subroutineExtra(scope)
val evalVars = mutableMapOf ( val evalVars = mutableMapOf (
DataType.UBYTE to Stack<String>(), DataType.UBYTE to Stack<String>(),
@ -564,8 +566,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return false return false
// expression datatype is BOOL (ubyte) but operands can be anything // expression datatype is BOOL (ubyte) but operands can be anything
if(left.type in ByteDatatypes && right.type in ByteDatatypes && if(left.type in ByteDatatypes && right.type in ByteDatatypes && left.isSimple() && right.isSimple()) {
left.isSimple() && right.isSimple()) {
assignExpressionToRegister(left, RegisterOrPair.A, false) assignExpressionToRegister(left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false) asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
@ -589,8 +590,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
} }
assignRegisterByte(target, CpuRegister.A) assignRegisterByte(target, CpuRegister.A)
return true return true
} else if(left.type in WordDatatypes && right.type in WordDatatypes && } else if(left.type in WordDatatypes && right.type in WordDatatypes && left.isSimple() && right.isSimple()) {
left.isSimple() && right.isSimple()) {
assignExpressionToRegister(left, RegisterOrPair.AY, false) assignExpressionToRegister(left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false) asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false) asmgen.saveRegisterStack(CpuRegister.Y, false)
@ -759,59 +759,61 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return false return false
} }
private fun simpleBitshiftExprRPN(left: PtExpression, operator: String, right: PtExpression, target: AsmAssignTarget): Boolean { private fun simpleBitshiftExprRPN(expr: PtRpn, operator: PtRpnOperator, rightNode: PtNode, target: AsmAssignTarget): Boolean {
if(operator!="<<" && operator!=">>") if(operator.operator!="<<" && operator.operator!=">>")
return false
val shifts = (rightNode as? PtExpression)?.asConstInteger()
if(shifts==null || shifts !in 0..7 || operator.leftType !in IntegerDatatypes)
return false return false
val shifts = right.asConstInteger() if(operator.leftType in ByteDatatypes) {
if(shifts!=null) { val signed = operator.leftType == DataType.BYTE
val dt = left.type val left = expr.truncateLastOperator()
if(dt in ByteDatatypes && shifts in 0..7) { assignExpressionToRegister(left, RegisterOrPair.A, signed)
val signed = dt == DataType.BYTE if(operator.operator=="<<") {
assignExpressionToRegister(left, RegisterOrPair.A, signed) repeat(shifts) {
if(operator=="<<") { asmgen.out(" asl a")
repeat(shifts) { }
asmgen.out(" asl a") } else {
} if(signed && shifts>0) {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
} else { } else {
if(signed && shifts>0) { repeat(shifts) {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A") asmgen.out(" lsr a")
} else {
repeat(shifts) {
asmgen.out(" lsr a")
}
} }
} }
assignRegisterByte(target, CpuRegister.A) }
return true assignRegisterByte(target, CpuRegister.A)
} else if(dt in WordDatatypes && shifts in 0..7) { return true
val signed = dt == DataType.WORD } else if(operator.leftType in WordDatatypes) {
val signed = operator.leftType == DataType.WORD
if(operator.operator=="<<") {
val left = expr.truncateLastOperator()
assignExpressionToRegister(left, RegisterOrPair.AY, signed) assignExpressionToRegister(left, RegisterOrPair.AY, signed)
if(operator=="<<") { if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) {
asmgen.out(" asl a | rol P8ZP_SCRATCH_B1")
}
asmgen.out(" ldy P8ZP_SCRATCH_B1")
}
} else {
if(signed) {
return false // TODO("shift AY >> $shifts signed")
} else {
val left = expr.truncateLastOperator()
assignExpressionToRegister(left, RegisterOrPair.AY, false)
if(shifts>0) { if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1") asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) { repeat(shifts) {
asmgen.out(" asl a | rol P8ZP_SCRATCH_B1") asmgen.out(" lsr P8ZP_SCRATCH_B1 | ror a")
} }
asmgen.out(" ldy P8ZP_SCRATCH_B1") asmgen.out(" ldy P8ZP_SCRATCH_B1")
} }
} else {
if(signed) {
// TODO("shift AY >> $shifts signed")
return false
} else {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) {
asmgen.out(" lsr P8ZP_SCRATCH_B1 | ror a")
}
asmgen.out(" ldy P8ZP_SCRATCH_B1")
}
}
} }
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
} }
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
} }
return false return false

View File

@ -1288,7 +1288,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sta $name+1 sta $name+1
""") """)
} }
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte") "<<", ">>" -> {
throw AssemblyError("shift by a word variable not supported, max is a byte")
}
"&" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1") "&" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
"|" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1") "|" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
"^" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1") "^" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
@ -1504,7 +1506,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY) asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
remainderVarByWordInAY() remainderVarByWordInAY()
} }
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte") "<<", ">>" -> {
if(value is PtNumber && value.number<=255) {
when (dt) {
in WordDatatypes -> TODO("shift a word var by ${value.number}")
in ByteDatatypes -> TODO("shift a byte var by ${value.number}")
else -> throw AssemblyError("weird dt for shift")
}
} else {
throw AssemblyError("shift by a word value not supported, max is a byte")
}
}
"&" -> { "&" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY) asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
asmgen.out(" and $name | sta $name | tya | and $name+1 | sta $name+1") asmgen.out(" and $name | sta $name | tya | and $name+1 | sta $name+1")

View File

@ -929,6 +929,14 @@ internal class AstChecker(private val program: Program,
errors.err("bitwise operator can only be used on integer operands", expr.right.position) errors.err("bitwise operator can only be used on integer operands", expr.right.position)
} }
"in" -> throw FatalAstException("in expression should have been replaced by containmentcheck") "in" -> throw FatalAstException("in expression should have been replaced by containmentcheck")
"<<", ">>" -> {
if(rightDt in WordDatatypes) {
val shift = expr.right.constValue(program)?.number?.toInt()
if(shift==null || shift > 255) {
errors.err("shift by a word value not supported, max is a byte", expr.position)
}
}
}
} }
if(leftDt !in NumericDatatypes && leftDt != DataType.STR && leftDt != DataType.BOOL) if(leftDt !in NumericDatatypes && leftDt != DataType.STR && leftDt != DataType.BOOL)
@ -937,11 +945,13 @@ internal class AstChecker(private val program: Program,
errors.err("right operand is not numeric or str", expr.right.position) errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt) { if(leftDt!=rightDt) {
if(leftDt==DataType.STR && rightDt in IntegerDatatypes && expr.operator=="*") { if(leftDt==DataType.STR && rightDt in IntegerDatatypes && expr.operator=="*") {
// only exception allowed: str * constvalue // exception allowed: str * constvalue
if(expr.right.constValue(program)==null) if(expr.right.constValue(program)==null)
errors.err("can only use string repeat with a constant number value", expr.left.position) errors.err("can only use string repeat with a constant number value", expr.left.position)
} else if(leftDt==DataType.BOOL && rightDt in ByteDatatypes || leftDt in ByteDatatypes && rightDt==DataType.BOOL) { } else if(leftDt==DataType.BOOL && rightDt in ByteDatatypes || leftDt in ByteDatatypes && rightDt==DataType.BOOL) {
// expression with one side BOOL other side (U)BYTE is allowed; bool==byte // expression with one side BOOL other side (U)BYTE is allowed; bool==byte
} else if((expr.operator == "<<" || expr.operator == ">>") && (leftDt in WordDatatypes && rightDt in ByteDatatypes)) {
// exception allowed: shifting a word by a byte
} else { } else {
errors.err("left and right operands aren't the same type", expr.left.position) errors.err("left and right operands aren't the same type", expr.left.position)
} }

View File

@ -139,6 +139,10 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
errors.warn("shift always results in 0", expr.position) errors.warn("shift always results in 0", expr.position)
if(dt.istype(DataType.UWORD) && shifts.number>=16.0) if(dt.istype(DataType.UWORD) && shifts.number>=16.0)
errors.warn("shift always results in 0", expr.position) errors.warn("shift always results in 0", expr.position)
if(shifts.number<=255.0 && shifts.type in WordDatatypes) {
val byteVal = NumericLiteral(DataType.UBYTE, shifts.number, shifts.position)
return listOf(IAstModification.ReplaceNode(expr.right, byteVal, expr))
}
} }
} }
return noModifications return noModifications

View File

@ -60,15 +60,15 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (parent is Assignment) { if (parent is Assignment) {
if (parent.target.inferType(program).isWords) { if (parent.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr) modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
if(rightDt.isBytes) // if(rightDt.isBytes)
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr) // modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
} }
} else if (parent is TypecastExpression && parent.type == DataType.UWORD && parent.parent is Assignment) { } else if (parent is TypecastExpression && parent.type == DataType.UWORD && parent.parent is Assignment) {
val assign = parent.parent as Assignment val assign = parent.parent as Assignment
if (assign.target.inferType(program).isWords) { if (assign.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr) modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
if(rightDt.isBytes) // if(rightDt.isBytes)
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr) // modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
} }
} }
if(modifications.isNotEmpty()) if(modifications.isNotEmpty())
@ -140,16 +140,18 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
} }
// determine common datatype and add typecast as required to make left and right equal types if((expr.operator!="<<" && expr.operator!=">>") || !leftDt.isWords || !rightDt.isBytes) {
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right) // determine common datatype and add typecast as required to make left and right equal types
if(toFix!=null) { val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
val modifications = mutableListOf<IAstModification>() if(toFix!=null) {
when { val modifications = mutableListOf<IAstModification>()
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr) when {
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr) toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
else -> throw FatalAstException("confused binary expression side") toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
else -> throw FatalAstException("confused binary expression side")
}
return modifications
} }
return modifications
} }
} }
} }

View File

@ -3,7 +3,7 @@ package prog8tests
import io.kotest.core.config.AbstractProjectConfig import io.kotest.core.config.AbstractProjectConfig
object ProjectConfig : AbstractProjectConfig() { object ProjectConfig : AbstractProjectConfig() {
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2) override val parallelism = kotlin.math.max(1, Runtime.getRuntime().availableProcessors() / 2)
// override fun listeners() = listOf(SystemOutToNullListener) // override fun listeners() = listOf(SystemOutToNullListener)
} }

View File

@ -134,7 +134,7 @@ main {
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
} }
test("bitshift left of const byte converted to word") { test("bitshift left of const byte not converted to word") {
val src=""" val src="""
main { main {
sub start() { sub start() {

View File

@ -12,6 +12,7 @@ import prog8.code.ast.PtAssignment
import prog8.code.ast.PtVariable import prog8.code.ast.PtVariable
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
@ -144,4 +145,24 @@ main {
errors.errors.size shouldBe 1 errors.errors.size shouldBe 1
errors.errors[0] shouldContain "undefined symbol: doesnotexist" errors.errors[0] shouldContain "undefined symbol: doesnotexist"
} }
test("shifting by word value is ok") {
val text="""
main {
sub start() {
ubyte c = 1
@(15000 + c<<${'$'}0003) = 42
@(15000 + (c<<${'$'}0003)) = 42
@(15000 + c*${'$'}0008) = 42 ; *8 becomes a shift after opt
uword @shared qq = 15000 + c<<${'$'}0003
qq = 15000 + (c<<${'$'}0003)
qq = 16000 + c*${'$'}0008
}
}"""
compileText(C64Target(), true, text, writeAssembly = true, useRPN = false) shouldNotBe null
compileText(C64Target(), true, text, writeAssembly = true, useRPN = true) shouldNotBe null
compileText(VMTarget(), true, text, writeAssembly = true, useRPN = false) shouldNotBe null
// TODO RPN once IR RPN codegen is done: compileText(VMTarget(), true, text, writeAssembly = true, useRPN = true) shouldNotBe null
}
}) })

View File

@ -1,14 +1,19 @@
TODO TODO
==== ====
RPN: cube3d-sprites compiler crash (bit shift too large) RPN: assem once again is broken with selftest
RPN: swirl is bigger and MUCH slower RPN: optimize RPN in AssignmentAsmGen TODO's
RPN: wizzine is slower but about equal size RPN: swirl is MUCH slower
RPN: wizzine is slower
- Move asmExtra vars into BSS as well, now are .byte 0 allocated
then:
RPN: swirl is bigger
RPN: petaxian is 900 bytes larger, chess is a lot bigger
RPN: charset is larger RPN: charset is larger
RPN: cube3d is much larger, but a bit faster RPN: cube3d is much larger, but a bit faster
RPN: cube3d-float is massive and slow RPN: cube3d-float is massive and slow
RPN: mandelbrot is bigger, but seems faster RPN: mandelbrot is bigger, but seems faster
then:
RPN: Implement RPN codegen for IR. RPN: Implement RPN codegen for IR.
@ -16,7 +21,7 @@ For next minor release
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
- ubyte fits = cx<numCellsHoriz-1 much larger code than when declared as bool. (RPN only?) - ubyte fits = cx<numCellsHoriz-1 much larger code than when declared as bool. (RPN only?)
- if fits and @(celladdr(cx+1)) much larger code than if fits and not @(celladdr(cx+1)) (RPN only?) - if fits and @(celladdr(cx+1)) much larger code than if fits and not @(celladdr(cx+1)) (RPN only?)
- Move asmExtra vars into BSS as well, now are .byte 0 allocated - @($5000 + c<<$0003) = 22 why is 22 pushed on the stack first and then popped after the address is calcualted
... ...