mirror of
https://github.com/irmen/prog8.git
synced 2025-06-18 08:23:37 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
68a7f9c665 | |||
ffd8d9c7c1 | |||
c66fc8630c | |||
9ca1c66f2b | |||
33647a29d0 | |||
02b12cc762 | |||
3280993e2a | |||
3723c22054 | |||
0a2c4ea0c4 | |||
58a83c0439 | |||
d665489054 | |||
9200992024 | |||
6408cc46a8 | |||
961bcdb7ae | |||
edee70cf31 |
@ -1 +1 @@
|
||||
2.0
|
||||
2.1
|
||||
|
@ -177,8 +177,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
private fun outputStatements(statements: List<Statement>) {
|
||||
for(stmt in statements) {
|
||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||
continue // skip autogenerated decls (to avoid generating a newline)
|
||||
outputi("")
|
||||
stmt.accept(this)
|
||||
output("\n")
|
||||
@ -284,7 +282,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
assignment.target.accept(this)
|
||||
if (assignment.aug_op != null)
|
||||
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
|
||||
output(" ${assignment.aug_op} ")
|
||||
else
|
||||
output(" = ")
|
||||
|
@ -24,7 +24,7 @@ enum class DataType {
|
||||
* is the type assignable to the given other type?
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
// what types are assignable to others without loss of precision?
|
||||
// what types are assignable to others, perhaps via a typecast, without loss of precision?
|
||||
when(this) {
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
|
@ -4,7 +4,8 @@ import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.target.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.optimizer.AssignmentTransformer
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
|
||||
|
||||
|
||||
@ -31,6 +32,17 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.transformAssignments(errors: ErrorReporter) {
|
||||
val transform = AssignmentTransformer(this, errors)
|
||||
transform.visit(this)
|
||||
while(transform.optimizationsDone>0 && errors.isEmpty()) {
|
||||
transform.applyModifications()
|
||||
transform.optimizationsDone = 0
|
||||
transform.visit(this)
|
||||
}
|
||||
transform.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid() {
|
||||
val imr = ImportedModuleDirectiveRemover()
|
||||
imr.visit(this, this.parent)
|
||||
|
@ -326,7 +326,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
|
||||
@ -334,7 +334,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
@ -423,9 +423,6 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignment should have been converted into normal assignment")
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program, assignment)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
@ -466,6 +463,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO use callgraph for check?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
err("recursive var declaration")
|
||||
}
|
||||
@ -848,7 +846,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@ -896,6 +894,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
if(target.regXasResult())
|
||||
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
|
||||
if(args.size!=target.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
@ -914,7 +914,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(target.isAsmSubroutine) {
|
||||
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
|
||||
}
|
||||
|
||||
// check if the argument types match the register(pairs)
|
||||
|
@ -10,7 +10,7 @@ import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class AstIdentifiersChecker(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
|
@ -6,7 +6,7 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
// TODO replace all occurrences of this with AstWalker
|
||||
interface IAstModifyingVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { it.accept(this) }
|
||||
|
@ -4,9 +4,6 @@ import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FSignature
|
||||
|
||||
|
||||
internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
@ -125,75 +122,6 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
var parentStatement: Node = functionCall
|
||||
while(parentStatement !is Statement)
|
||||
parentStatement = parentStatement.parent
|
||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(argparam in subroutine.parameters.withIndex().zip(args)) {
|
||||
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
|
||||
if(argparam.second is AddressOf)
|
||||
continue
|
||||
val idref = argparam.second as? IdentifierReference
|
||||
if(idref!=null) {
|
||||
val variable = idref.targetVarDecl(program.namespace)
|
||||
if(variable!=null && variable.datatype in IterableDatatypes) {
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[argparam.first.index],
|
||||
AddressOf(idref, idref.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
||||
val argvalue = arg.first.value
|
||||
val argDt = argvalue.inferType(program)
|
||||
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
|
||||
if(argvalue !is IdentifierReference)
|
||||
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[arg.first.index],
|
||||
AddressOf(argvalue, argvalue.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
|
@ -4,9 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -45,10 +43,33 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
if (valuetype != targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
} else {
|
||||
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
|
||||
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
|
||||
val cvalue = assignment.value.constValue(program)
|
||||
if(cvalue!=null) {
|
||||
val number = cvalue.number.toDouble()
|
||||
// more complex comparisons if the type is different, but the constant value is compatible
|
||||
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||
if(number>0)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
|
||||
if(number>0)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
|
||||
if(number<0x80)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
|
||||
if(number<0x8000)
|
||||
return castLiteral(cvalue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
@ -77,6 +98,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -716,6 +716,8 @@ class Subroutine(override val name: String,
|
||||
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||
}
|
||||
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
|
||||
fun amountOfRtsInAsm(): Int = statements
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
|
@ -1,18 +1,15 @@
|
||||
package prog8.compiler.target
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
@ -79,28 +76,10 @@ class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorRepor
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
|
||||
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
|
||||
// augmented form as wel (with the operator "setvalue")
|
||||
if (assignment.aug_op == null) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
if (assignment.target.isSameAs(binExpr.left)) {
|
||||
assignment.value = binExpr.right
|
||||
assignment.aug_op = binExpr.operator + "="
|
||||
assignment.value.parent = assignment
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
assignment.aug_op = "setvalue"
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
@ -108,6 +87,18 @@ class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorRepor
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
else if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -164,19 +164,20 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
// because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
|
||||
val remover = UnusedCodeRemover()
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.transformAssignments(errors)
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
|
@ -88,7 +88,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
DataType.ARRAY_F -> {
|
||||
assignFromFloatConstant(assign.target, constValue.toDouble())
|
||||
}
|
||||
else -> throw AssemblyError("invalid array dt $arrayDt")
|
||||
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
|
||||
}
|
||||
} else {
|
||||
TODO()
|
||||
@ -156,7 +156,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
""")
|
||||
}
|
||||
DataType.ARRAY_F -> return false // TODO optimize?
|
||||
else -> throw AssemblyError("invalid array dt $arrayDt")
|
||||
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -174,7 +174,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
when(arrayDt) {
|
||||
DataType.ARRAY_UB -> {
|
||||
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
|
||||
throw AssemblyError("expected byte array or string")
|
||||
throw AssemblyError("assignment to array: expected byte array or string")
|
||||
if (assign.aug_op == "setvalue") {
|
||||
if (valueArrayIndex is NumericLiteralValue)
|
||||
asmgen.out(" ldy #${valueArrayIndex.number.toHex()}")
|
||||
@ -194,7 +194,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
DataType.ARRAY_UW -> TODO()
|
||||
DataType.ARRAY_W -> TODO()
|
||||
DataType.ARRAY_F -> TODO()
|
||||
else -> throw AssemblyError("invalid array dt")
|
||||
else -> throw AssemblyError("assignment to array: invalid array dt")
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -418,7 +418,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
|
||||
throw AssemblyError("expected byte array or string")
|
||||
throw AssemblyError("assign to memory byte: expected byte array or string source")
|
||||
if (arrayIndex is NumericLiteralValue)
|
||||
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
|
||||
else
|
||||
@ -518,7 +518,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
|
||||
throw AssemblyError("expected byte array or string")
|
||||
throw AssemblyError("assign to identifier: expected byte array or string source")
|
||||
if (arrayIndex is NumericLiteralValue)
|
||||
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
|
||||
else
|
||||
@ -580,9 +580,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
// non-const value
|
||||
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
|
||||
when (assign.value) {
|
||||
is RegisterExpr -> {
|
||||
TODO("$assign")
|
||||
}
|
||||
is RegisterExpr -> throw AssemblyError("expected a typecast for assigning register to word")
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
|
||||
when (assign.aug_op) {
|
||||
@ -624,11 +622,11 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
}
|
||||
return true
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
TODO("$assign")
|
||||
}
|
||||
is DirectMemoryRead -> throw AssemblyError("expected a typecast for assigning memory read byte to word")
|
||||
is AddressOf -> {
|
||||
TODO("$assign")
|
||||
val name = asmgen.asmIdentifierName((assign.value as AddressOf).identifier)
|
||||
asmgen.out(" lda #<$name | sta $targetName | lda #>$name | sta $targetName+1")
|
||||
return true
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
if (assign.aug_op == "setvalue") {
|
||||
@ -637,7 +635,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (arrayDt != DataType.ARRAY_W && arrayDt != DataType.ARRAY_UW)
|
||||
throw AssemblyError("expected word array")
|
||||
throw AssemblyError("assign to identifier: expected word array source")
|
||||
if (arrayIndex is NumericLiteralValue)
|
||||
asmgen.out(" lda #${arrayIndex.number.toHex()}")
|
||||
else
|
||||
@ -741,7 +739,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (arrayDt != DataType.ARRAY_F)
|
||||
throw AssemblyError("expected float array")
|
||||
throw AssemblyError("assign to identifier: expected float array source")
|
||||
if (arrayIndex is NumericLiteralValue)
|
||||
asmgen.out(" lda #${arrayIndex.number.toHex()}")
|
||||
else
|
||||
@ -775,7 +773,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
else -> throw AssemblyError("assignment to identifier: invalid target datatype: $targetType")
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -1086,7 +1084,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
|
||||
throw AssemblyError("expected byte array or string")
|
||||
throw AssemblyError("assign to register: expected byte array or string source")
|
||||
if (arrayIndex is NumericLiteralValue)
|
||||
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
|
||||
else
|
||||
@ -1141,7 +1139,6 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
|
||||
|
||||
// old code-generation below:
|
||||
// eventually, all of this should have been replaced by newer more optimized code above.
|
||||
|
||||
private fun translateNormalAssignment(assign: Assignment) {
|
||||
require(assign.aug_op == null)
|
||||
|
||||
|
@ -568,6 +568,38 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcSwap(fcall: IFunctionCall) {
|
||||
val first = fcall.args[0]
|
||||
val second = fcall.args[1]
|
||||
if(first is IdentifierReference && second is IdentifierReference) {
|
||||
val firstName = asmgen.asmIdentifierName(first)
|
||||
val secondName = asmgen.asmIdentifierName(second)
|
||||
val dt = first.inferType(program)
|
||||
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
|
||||
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||
asmgen.out("""
|
||||
ldy $firstName
|
||||
lda $secondName
|
||||
sta $firstName
|
||||
tya
|
||||
sta $secondName
|
||||
ldy $firstName+1
|
||||
lda $secondName+1
|
||||
sta $firstName+1
|
||||
tya
|
||||
sta $secondName+1
|
||||
""")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.FLOAT)) {
|
||||
TODO("optimized case for swapping 2 float vars-- asm subroutine")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO more optimized cases? for instance swapping elements of array vars?
|
||||
|
||||
// suboptimal code via the evaluation stack...
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
// pop in reverse order
|
||||
|
@ -40,8 +40,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if (builtinFunc != null) {
|
||||
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||
} else {
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
if (!reg.stack) {
|
||||
@ -51,7 +51,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
|
||||
RegisterOrPair.X -> {
|
||||
// return value in X register has been discarded, just push a zero
|
||||
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
// return value in X register has been discarded, just push a zero in this place
|
||||
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
// return value in X register has been discarded, just push a zero in this place
|
||||
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
||||
@ -110,7 +121,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(Register.X in sub.asmClobbers)
|
||||
val saveX = Register.X in sub.asmClobbers || sub.regXasResult()
|
||||
if(saveX)
|
||||
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||
|
||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||
@ -30,7 +31,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(Register.X in sub.asmClobbers)
|
||||
if(saveX)
|
||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// pass parameter via a variable
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val paramVar = parameter.value
|
||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
@ -54,7 +55,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
@ -64,7 +65,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
@ -202,11 +203,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
156
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
156
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
@ -0,0 +1,156 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
|
||||
|
||||
|
||||
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
|
||||
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
|
||||
// augmented form as wel (with the operator "setvalue")
|
||||
if (assignment.aug_op == null) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
if (assignment.target.isSameAs(binExpr.left)) {
|
||||
assignment.value = binExpr.right
|
||||
assignment.aug_op = binExpr.operator + "="
|
||||
assignment.value.parent = assignment
|
||||
optimizationsDone++
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
assignment.aug_op = "setvalue"
|
||||
optimizationsDone++
|
||||
} else if(assignment.aug_op == "+=") {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
||||
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
||||
if(binExpr.operator == "+") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x += y + 1 -> x += y , x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x += y + 2 -> x += y , x++, x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if(binExpr.operator == "-") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x += y - 1 -> x += y , x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x += y - 2 -> x += y , x--, x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(assignment.aug_op == "-=") {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
||||
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
||||
if(binExpr.operator == "+") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x -= y + 1 -> x -= y , x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x -= y + 2 -> x -= y , x--, x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if(binExpr.operator == "-") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x -= y - 1 -> x -= y , x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x -= y - 2 -> x -= y , x++, x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -127,7 +127,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||
// make sure autogenerated vardecls are in the used symbols
|
||||
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,13 @@ import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call tree for this?
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return decl
|
||||
|
@ -14,12 +14,6 @@ import kotlin.math.pow
|
||||
/*
|
||||
todo add more expression optimizations
|
||||
|
||||
x + x -> x << 1 (for words... for bytes too?)
|
||||
x + x + x + x -> x << 2 (for words... for bytes too?)
|
||||
x + x + x -> ???? x*3 ??? words/bytes?
|
||||
x - x -> 0
|
||||
|
||||
|
||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*/
|
||||
@ -103,7 +97,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
@ -180,6 +173,64 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed >=0 --> signed ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw >=0 --> msb(signedw) ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed < 0 --> signed & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -17,6 +18,7 @@ import kotlin.math.floor
|
||||
*/
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
@ -317,20 +319,19 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstModifyingVisitor
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break): Statement {
|
||||
override fun visit(breakStmt: Break) {
|
||||
count++
|
||||
return super.visit(breakStmt)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue): Statement {
|
||||
override fun visit(contStmt: Continue) {
|
||||
count++
|
||||
return super.visit(contStmt)
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
for(stmt in scope.statements) {
|
||||
stmt.accept(s)
|
||||
|
@ -1,55 +1,38 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal class UnusedCodeRemover: IAstModifyingVisitor {
|
||||
internal class UnusedCodeRemover: AstWalker() {
|
||||
|
||||
|
||||
override fun visit(program: Program) {
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
val removals = mutableListOf<IAstModification>()
|
||||
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val removeSubroutines = mutableSetOf<Subroutine>()
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
removeSubroutines.add(sub)
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||
}
|
||||
}
|
||||
|
||||
if (removeSubroutines.isNotEmpty()) {
|
||||
removeSubroutines.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
val removeBlocks = mutableSetOf<Block>()
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removeBlocks.add(block)
|
||||
}
|
||||
|
||||
if (removeBlocks.isNotEmpty()) {
|
||||
removeBlocks.forEach { it.definingScope().remove(it) }
|
||||
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
val removeModules = mutableSetOf<Module>()
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removeModules.add(it)
|
||||
removals.add(IAstModification.Remove(it, it.parent)) // TODO does removing modules work like this?
|
||||
}
|
||||
|
||||
if (removeModules.isNotEmpty()) {
|
||||
program.modules.removeAll(removeModules)
|
||||
}
|
||||
|
||||
super.visit(program)
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ TODO
|
||||
|
||||
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
|
||||
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
|
||||
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/
|
||||
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
|
||||
|
||||
|
||||
More optimizations
|
||||
|
@ -17,67 +17,64 @@ graphics {
|
||||
c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
|
||||
}
|
||||
|
||||
|
||||
sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
|
||||
; Bresenham algorithm
|
||||
word dx
|
||||
word dy
|
||||
byte ix = 1
|
||||
byte iy = 1
|
||||
if x2>x1 {
|
||||
dx = x2-x1
|
||||
} else {
|
||||
ix = -1
|
||||
dx = x1-x2
|
||||
; Bresenham algorithm.
|
||||
; This code special cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
if y2>y1 {
|
||||
dy = y2-y1
|
||||
} else {
|
||||
iy = -1
|
||||
dy = y1-y2
|
||||
}
|
||||
word dx2 = 2 * dx
|
||||
word dy2 = 2 * dy
|
||||
word d = 0
|
||||
ubyte positive_ix = true
|
||||
word dx = x2 - x1 as word
|
||||
word dy = y2 as word - y1 as word
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
if ix<0 {
|
||||
if positive_ix {
|
||||
forever {
|
||||
graphics.plot(y1)
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx--
|
||||
d += dy2
|
||||
plotx++
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1 += iy
|
||||
d -= dx2
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
graphics.plot(y1)
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx++
|
||||
d += dy2
|
||||
plotx--
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1 += iy
|
||||
d -= dx2
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if iy<0 {
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
forever {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1--
|
||||
d += dx2
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
plotx += ix as word
|
||||
d -= dy2
|
||||
plotx++
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -86,10 +83,10 @@ graphics {
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
d += dx
|
||||
if d > dy {
|
||||
plotx += ix as word
|
||||
d -= dy2
|
||||
plotx--
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,7 +98,7 @@ graphics {
|
||||
ubyte ploty
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
plotx = xcenter + xx
|
||||
@ -138,7 +135,7 @@ graphics {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte cy_plus_yy = cy + yy
|
||||
@ -180,7 +177,7 @@ graphics {
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
uword plotx ; 0..319
|
||||
uword plotx ; 0..319 ; separate 'parameter' for plot()
|
||||
|
||||
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
%asm {{
|
||||
@ -217,7 +214,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
|
||||
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||
; the y lookup tables encode this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
_y_lookup_hi
|
||||
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
|
||||
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -71,8 +71,8 @@ main {
|
||||
ubyte dy = abs(y2 - y1)
|
||||
ubyte dx2 = 2 * dx
|
||||
ubyte dy2 = 2 * dy
|
||||
byte ix = sgn(x2 as byte - x1 as byte)
|
||||
byte iy = sgn(y2 as byte - y1 as byte)
|
||||
ubyte ix = sgn(x2 as byte - x1 as byte) as ubyte
|
||||
ubyte iy = sgn(y2 as byte - y1 as byte) as ubyte
|
||||
ubyte x = x1
|
||||
ubyte y = y1
|
||||
|
||||
@ -107,7 +107,7 @@ main {
|
||||
; Midpoint algorithm
|
||||
ubyte x = radius
|
||||
ubyte y = 0
|
||||
byte decisionOver2 = 1-x
|
||||
byte decisionOver2 = 1-x as byte
|
||||
|
||||
while x>=y {
|
||||
c64scr.setcc(xcenter + x, ycenter + y as ubyte, 81, 1)
|
||||
@ -132,7 +132,7 @@ main {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte x = radius
|
||||
ubyte y = 0
|
||||
byte decisionOver2 = 1-x
|
||||
byte decisionOver2 = 1-x as byte
|
||||
ubyte xx
|
||||
|
||||
while x>=y {
|
||||
|
36
examples/strings.p8
Normal file
36
examples/strings.p8
Normal file
@ -0,0 +1,36 @@
|
||||
%import c64lib
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
str s1 = "apple"
|
||||
str s2 = "banana"
|
||||
byte[] a1 = [66,77,88,0]
|
||||
ubyte i1 = 101
|
||||
uword w1 = 000
|
||||
|
||||
c64.STROUT(s1)
|
||||
c64.CHROUT('\n')
|
||||
c64.STROUT(a1)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
c64scr.print("bla\n")
|
||||
|
||||
; c64scr.print_uwhex(s1, true)
|
||||
; w1 = &s1
|
||||
; c64scr.print_uwhex(w1, true)
|
||||
;
|
||||
; c64scr.print_uwhex(a1, true)
|
||||
; w1 = &a1
|
||||
; c64scr.print_uwhex(w1, true)
|
||||
;
|
||||
; s1 = s1
|
||||
; s1 = s2
|
||||
; s2 = "zzz"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,18 +6,40 @@
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte ubb = 44
|
||||
byte bbb=44
|
||||
uword uww = 4444
|
||||
word www = 4444
|
||||
float flt = 4.4
|
||||
|
||||
A = ubb
|
||||
A = ubb as byte
|
||||
A = bbb
|
||||
A = bbb as ubyte
|
||||
A += 50
|
||||
|
||||
A += Y + 1
|
||||
A -= Y + 1
|
||||
A += Y - 1
|
||||
A -= Y - 1
|
||||
|
||||
A += Y + 2
|
||||
A -= Y + 2
|
||||
A += Y - 2
|
||||
A -= Y - 2
|
||||
|
||||
; ubyte ubb
|
||||
; byte bb
|
||||
; uword uww
|
||||
; word ww
|
||||
; word ww2
|
||||
;
|
||||
; A = ubb*0
|
||||
; Y = ubb*1
|
||||
; A = ubb*2
|
||||
; Y = ubb*4
|
||||
; A = ubb*8
|
||||
; Y = ubb*16
|
||||
; A = ubb*32
|
||||
; Y = ubb*64
|
||||
; A = ubb*128
|
||||
; Y = ubb+ubb+ubb
|
||||
; A = ubb+ubb+ubb+ubb
|
||||
; ww = ww2+ww2
|
||||
; ww = ww2+ww2+ww2
|
||||
; ww = ww2+ww2+ww2+ww2
|
||||
|
||||
str foo = "foo"
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user