diff --git a/codeCore/src/prog8/code/core/Conversions.kt b/codeCore/src/prog8/code/core/Conversions.kt index bb8890b6d..995fe2edf 100644 --- a/codeCore/src/prog8/code/core/Conversions.kt +++ b/codeCore/src/prog8/code/core/Conversions.kt @@ -1,6 +1,11 @@ package prog8.code.core import kotlin.math.abs +import kotlin.math.pow + +val powersOfTwoFloat = (1..16).map { (2.0).pow(it) }.toTypedArray() +val negativePowersOfTwoFloat = powersOfTwoFloat.map { -it }.toTypedArray() +val powersOfTwoInt = (0..16).map { 2.0.pow(it).toInt() }.toTypedArray() fun Number.toHex(): String { // 0..15 -> "0".."15" diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 13a4b0d62..cd60c7b13 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -933,6 +933,11 @@ internal class AssignmentAsmGen(private val program: PtProgram, } private fun optimizedDivideExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean { + val constDivisor = expr.right.asConstInteger() + if(constDivisor in powersOfTwoInt) { + println("TODO optimize: divide ${expr.type} by power-of-2 ${constDivisor} at ${expr.position}") // TODO + } + when(expr.type) { DataType.UBYTE -> { assignExpressionToRegister(expr.left, RegisterOrPair.A, false) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt index d3185475b..a79af94f8 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt @@ -1477,6 +1477,9 @@ $shortcutLabel:""") asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name") } "/" -> { + if(value in powersOfTwoInt) { + println("TODO optimize: (u)byte division by power-of-2 $value") // TODO + } if (dt == DataType.UBYTE) asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name") else @@ -1827,6 +1830,9 @@ $shortcutLabel:""") "/" -> { if(value==0) throw AssemblyError("division by zero") + else if (value in powersOfTwoInt) { + println("TODO optimize: (u)word division by power-of-2 $value") // TODO + } if(dt==DataType.WORD) { asmgen.out(""" lda $lsb diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index b226577f6..a8bef8875 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -5,7 +5,6 @@ import prog8.code.ast.* import prog8.code.core.* import prog8.intermediate.* import kotlin.io.path.readBytes -import kotlin.math.pow class IRCodeGen( @@ -682,13 +681,11 @@ class IRCodeGen( return code } - internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() } - internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk { val code = IRCodeChunk(null, null) if(factor==1) return code - val pow2 = powersOfTwo.indexOf(factor) + val pow2 = powersOfTwoInt.indexOf(factor) if(pow2==1) { // just shift 1 bit code += IRInstruction(Opcode.LSL, dt, reg1=reg) @@ -712,7 +709,7 @@ class IRCodeGen( val code = IRCodeChunk(null, null) if(factor==1) return code - val pow2 = powersOfTwo.indexOf(factor) + val pow2 = powersOfTwoInt.indexOf(factor) if(pow2==1) { // just shift 1 bit code += if(knownAddress!=null) @@ -785,13 +782,13 @@ class IRCodeGen( val code = IRCodeChunk(null, null) if(factor==1) return code - val pow2 = powersOfTwo.indexOf(factor) + val pow2 = powersOfTwoInt.indexOf(factor) + // TODO also try to optimize for signed division by powers of 2 if(pow2==1 && !signed) { code += IRInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift } else if(pow2>=1 &&!signed) { - // just shift multiple bits - // TODO also try to optimize for signed division by powers of 2 + // just shift multiple bits (unsigned) val pow2reg = registers.nextFree() code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2) code += if(signed) @@ -815,7 +812,8 @@ class IRCodeGen( val code = IRCodeChunk(null, null) if(factor==1) return code - val pow2 = powersOfTwo.indexOf(factor) + val pow2 = powersOfTwoInt.indexOf(factor) + // TODO also try to optimize for signed division by powers of 2 if(pow2==1 && !signed) { // just simple bit shift code += if(knownAddress!=null) @@ -824,7 +822,7 @@ class IRCodeGen( IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol) } else if(pow2>=1 && !signed) { - // just shift multiple bits + // just shift multiple bits (unsigned) val pow2reg = registers.nextFree() code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2) code += if(signed) { diff --git a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt index 502002ffe..ef345de54 100644 --- a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt @@ -14,9 +14,6 @@ import kotlin.math.log2 import kotlin.math.pow class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() { - private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet() - private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() - override fun after(typecast: TypecastExpression, parent: Node): Iterable { val mods = mutableListOf() @@ -686,7 +683,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co if(!idt.isKnown) throw FatalAstException("unknown dt") return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position) - } else if (cv in powersOfTwo) { + } else if (cv in powersOfTwoFloat) { expr.operator = "&" expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position) return null @@ -738,13 +735,15 @@ class ExpressionSimplifier(private val program: Program, private val options: Co else -> return null } } - in powersOfTwo -> { + in powersOfTwoFloat -> { if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) { // Unsigned number divided by a power of two => shift right // Signed number can't simply be bitshifted in this case (due to rounding issues for negative values), // so we leave that as is and let the code generator deal with it. val numshifts = log2(cv).toInt() return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position) + } else { + println("TODO optimize: divide by power-of-2 $cv at ${expr.position}") // TODO } } } @@ -795,14 +794,14 @@ class ExpressionSimplifier(private val program: Program, private val options: Co // left return expr2.left } - in powersOfTwo -> { + in powersOfTwoFloat -> { if (leftValue.inferType(program).isInteger) { // times a power of two => shift left val numshifts = log2(cv).toInt() return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position) } } - in negativePowersOfTwo -> { + in negativePowersOfTwoFloat -> { if (leftValue.inferType(program).isInteger) { // times a negative power of two => negate, then shift val numshifts = log2(-cv).toInt() diff --git a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt index 51cabd1c0..e4dab7815 100644 --- a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt @@ -21,7 +21,8 @@ class StatementOptimizer(private val program: Program, if(functionCallStatement.target.nameInSource.size==1) { val functionName = functionCallStatement.target.nameInSource[0] if (functionName in functions.purefunctionNames) { - errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position) + if("ignore_unused" !in parent.definingBlock.options()) + errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position) return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer)) } } diff --git a/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt b/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt index 65499482f..23fa3ff23 100644 --- a/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt +++ b/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt @@ -149,7 +149,8 @@ class UnusedCodeRemover(private val program: Program, val declIndex = (parent as IStatementContainer).statements.indexOf(decl) val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent) if(declIndex==singleUseIndex-1) { - errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position) + if("ignore_unused" !in decl.definingBlock.options()) + errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position) val fcall = assignment.value as IFunctionCall val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position) return listOf( diff --git a/compiler/res/prog8lib/cx16/sprites.p8 b/compiler/res/prog8lib/cx16/sprites.p8 index ed7a410c9..0723fbdc9 100644 --- a/compiler/res/prog8lib/cx16/sprites.p8 +++ b/compiler/res/prog8lib/cx16/sprites.p8 @@ -182,7 +182,7 @@ sprites { } sub set_mousepointer_image(uword data, bool compressed) { - get_data_ptr(0) ; the mouse cursor is sprite 0 + get_data_ptr_internal(0) ; the mouse cursor is sprite 0 if cx16.r1L==0 and cx16.r0==0 return ; mouse cursor not enabled ubyte vbank = cx16.r1L diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 22fdb839d..e6fc06821 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -5,9 +5,10 @@ See open issues on github. Re-generate the skeletons doc files. -optimize signed byte/word division by powers of 2 (and shift right?), it's now using divmod routine. (also % ?) - see inplacemodificationByteVariableWithLiteralval() and inplacemodificationSomeWordWithLiteralval() - and for IR: see divideByConst() in IRCodeGen +optimize byte/word division by powers of 2 (and shift right?), it's now often still using divmod routine. (also % ?) + see the TODOs in inplacemodificationByteVariableWithLiteralval(), inplacemodificationSomeWordWithLiteralval(), optimizedDivideExpr(), + and finally in optimizeDivision() + and for IR: see divideByConst() / divideByConstInplace() in IRCodeGen 1 shift right of AX signed word: stx P8ZP_SCRATCH_B1 diff --git a/examples/test.p8 b/examples/test.p8 index 5a7faafb3..77e2a19ee 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,68 +3,109 @@ %option no_sysinit %zeropage basicsafe + main { sub start() { - ubyte[10] array - array[10] = 0 - ; array[-11] = 0 + signed() + unsigned() + } -; txt.print_ub(ptr[index]) -; txt.nl() -; ptr[index] = 123 -; txt.print_ub(ptr[index]) -; txt.nl() + sub signed() { + txt.print("signed\n") + byte @shared bvalue = -88 + word @shared wvalue = -8888 + + txt.print_b(bvalue/2) + txt.nl() + txt.print_w(wvalue/2) + txt.nl() + + bvalue /= 2 + wvalue /= 2 + + txt.print_b(bvalue) + txt.nl() + txt.print_w(wvalue) + txt.nl() + + bvalue *= 2 + wvalue *= 2 + + txt.print_b(bvalue) + txt.nl() + txt.print_w(wvalue) + txt.nl() + txt.nl() + + txt.print_b(bvalue/4) + txt.nl() + txt.print_w(wvalue/4) + txt.nl() + + bvalue /= 4 + wvalue /= 4 + + txt.print_b(bvalue) + txt.nl() + txt.print_w(wvalue) + txt.nl() + + bvalue *= 4 + wvalue *= 4 + + txt.print_b(bvalue) + txt.nl() + txt.print_w(wvalue) + txt.nl() + txt.nl() + } + + sub unsigned() { + txt.print("unsigned\n") + ubyte @shared ubvalue = 88 + uword @shared uwvalue = 8888 + + txt.print_ub(ubvalue/2) + txt.nl() + txt.print_uw(uwvalue/2) + txt.nl() + + ubvalue /= 2 + uwvalue /= 2 + + txt.print_ub(ubvalue) + txt.nl() + txt.print_uw(uwvalue) + txt.nl() + + ubvalue *= 2 + uwvalue *= 2 + + txt.print_ub(ubvalue) + txt.nl() + txt.print_uw(uwvalue) + txt.nl() + txt.nl() + + txt.print_ub(ubvalue/4) + txt.nl() + txt.print_uw(uwvalue/4) + txt.nl() + + ubvalue /= 4 + uwvalue /= 4 + + txt.print_ub(ubvalue) + txt.nl() + txt.print_uw(uwvalue) + txt.nl() + + ubvalue *= 4 + uwvalue *= 4 + + txt.print_ub(ubvalue) + txt.nl() + txt.print_uw(uwvalue) + txt.nl() } } - - - -; -;main { -; sub start() { -; signed() -; unsigned() -; } -; -; sub signed() { -; byte @shared bvalue = -100 -; word @shared wvalue = -20000 -; -; bvalue /= 2 ; TODO should be a simple bit shift? -; wvalue /= 2 ; TODO should be a simple bit shift? -; -; txt.print_b(bvalue) -; txt.nl() -; txt.print_w(wvalue) -; txt.nl() -; -; bvalue *= 2 -; wvalue *= 2 -; -; txt.print_b(bvalue) -; txt.nl() -; txt.print_w(wvalue) -; txt.nl() -; } -; -; sub unsigned() { -; ubyte @shared ubvalue = 100 -; uword @shared uwvalue = 20000 -; -; ubvalue /= 2 -; uwvalue /= 2 -; -; txt.print_ub(ubvalue) -; txt.nl() -; txt.print_uw(uwvalue) -; txt.nl() -; -; ubvalue *= 2 -; uwvalue *= 2 -; -; txt.print_ub(ubvalue) -; txt.nl() -; txt.print_uw(uwvalue) -; txt.nl() -; } -;}