Compare commits

..
27 Commits
Author SHA1 Message Date
Irmen de Jong 68a7f9c665 version 2.1 2020-06-04 23:03:18 +02:00
Irmen de Jong ffd8d9c7c1 more assignment expression optimizations 2020-06-04 22:57:32 +02:00
Irmen de Jong c66fc8630c fixed missing repeated constant folding in expression optimization 2020-06-04 20:22:37 +02:00
Irmen de Jong 9ca1c66f2b added some optimizations for >= 0 and <0 comparisons for integers 2020-06-04 01:43:37 +02:00
Irmen de Jong 33647a29d0 be smarter about certain implicit type casts 2020-06-03 23:55:41 +02:00
Irmen de Jong 02b12cc762 optimized swap() for byte and word vars, optimized graphics line routine 2020-06-03 23:27:50 +02:00
Irmen de Jong 3280993e2a stricter type checking in assignments (less implicit typecasts) 2020-06-02 22:36:57 +02:00
Irmen de Jong 3723c22054 fix string param type 2020-06-02 02:09:52 +02:00
Irmen de Jong 0a2c4ea0c4 improved ast printing 2020-06-02 01:51:27 +02:00
Irmen de Jong 58a83c0439 improved code gen for passing string and array types. 2020-06-02 01:44:42 +02:00
Irmen de Jong d665489054 implemented asm for addressof-assignment 2020-06-02 00:31:56 +02:00
Irmen de Jong 9200992024 slightly improved asm gen error messages 2020-06-02 00:31:20 +02:00
Irmen de Jong 6408cc46a8 cmdrx16 github ref 2020-05-15 00:32:45 +02:00
Irmen de Jong 961bcdb7ae some more todo's noted down 2020-05-15 00:24:25 +02:00
Irmen de Jong edee70cf31 use new api for ast mods in unused code remover 2020-05-15 00:16:53 +02:00
Irmen de Jong 1978a9815a version 2.0 2020-05-14 23:59:18 +02:00
Irmen de Jong f5e6db9d66 big compiler speedup due to optimized scope lookups 2020-05-14 23:59:02 +02:00
Irmen de Jong a94bc40ab0 performance todo's 2020-05-08 20:41:10 +02:00
Irmen de Jong 534b5ced8f updated the compiled examples 2020-04-10 23:36:29 +02:00
Irmen de Jong 5ebd9b54e4 added some more optimized array assignments 2020-04-10 23:30:19 +02:00
Irmen de Jong cc4e272526 the new assignment code (once complete) really is a big enough change to bump the version to 2.0 2020-04-09 00:24:37 +02:00
Irmen de Jong 295e199bfa optimized asm output for unneeded typecasts, fixed parent node linking issues with replaceChildNode, Assignment aug_op field is now mutable to avoid having to recreate many Assignment nodes 2020-04-09 00:12:50 +02:00
Irmen de Jong df3371b0f0 slight gfx optimizations 2020-04-08 22:53:23 +02:00
Irmen de Jong e4fe1d2b8d attempts to optimize in-place assignments 2020-04-08 03:11:38 +02:00
Irmen de Jong b8b9244ffa merged AddressOfInserter into StatementReorderer 2020-04-06 15:23:54 +02:00
Irmen de Jong 3be3989e1c version 2020-04-06 14:31:23 +02:00
Irmen de Jong ed54cf680a fixed ast parent link bug in AstWalker, rewrote StatementReorderer using new API, when labels are sorted. 2020-04-06 14:31:02 +02:00
64 changed files with 2064 additions and 630 deletions

No files matched your search

+4 -4
View File
@@ -31,13 +31,13 @@ which aims to provide many conveniences over raw assembly code (even when using
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (seconds)
- option to automatically run the program in the Vice emulator
- use a modern PC to do the work on
- very quick compilation times
- can automatically run the program in the Vice emulator after succesful compilation
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
It is mainly targeted at the Commodore-64 machine at this time.
Prog8 is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation/manual
+2 -2
View File
@@ -1,11 +1,11 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0'
+1 -1
View File
@@ -1 +1 @@
1.91
2.1
+1 -3
View File
@@ -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(" = ")
+19 -18
View File
@@ -53,32 +53,31 @@ interface INameScope {
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is ForLoop -> if(stmt.body.name==name) return stmt.body
is RepeatLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> subscopes[stmt.name] = stmt
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return subscopes
return null
}
fun getLabelOrVariable(name: String): Statement? {
@@ -126,7 +125,7 @@ interface INameScope {
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
scope = scope?.subScope(name)
if(scope==null)
break
}
@@ -134,7 +133,7 @@ interface INameScope {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as Statement?
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
@@ -146,7 +145,7 @@ interface INameScope {
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
@@ -212,7 +211,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
mainBlocks[0].subScope("start") as Subroutine?
}
}
@@ -233,6 +232,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
require(node is Module && replacement is Module)
val idx = modules.indexOf(node)
modules[idx] = replacement
replacement.parent = this
}
}
@@ -259,6 +259,7 @@ class Module(override val name: String,
require(node is Statement && replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
+4 -2
View File
@@ -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)
@@ -150,7 +150,9 @@ object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
override fun linkParents(parent: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
}
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
+18 -9
View File
@@ -4,7 +4,8 @@ import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.target.AsmVariableAndReturnsPreparer
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
@@ -13,19 +14,16 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: Err
checker.visit(this)
}
internal fun Program.prepareAsmVariablesAndReturns(errors: ErrorReporter) {
val fixer = AsmVariableAndReturnsPreparer(this, errors)
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements() {
val initvalueCreator = AddressOfInserter(this)
initvalueCreator.visit(this)
initvalueCreator.applyModifications()
val checker = StatementReorderer(this)
checker.visit(this)
val reorder = StatementReorderer(this)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts(errors: ErrorReporter) {
@@ -34,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)
@@ -61,6 +61,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun replaceChildNode(node: Node, replacement: Node) {
require(node === expression && replacement is Expression)
expression = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
@@ -112,6 +113,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node")
}
replacement.parent = this
}
override fun toString(): String {
@@ -231,6 +233,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
node===arrayspec.index -> arrayspec.index = replacement as Expression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
@@ -268,6 +271,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression)
expression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -299,6 +303,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier)
identifier = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
@@ -320,6 +325,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -535,6 +541,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
require(replacement is Expression)
val idx = value.indexOf(node)
value[idx] = replacement
replacement.parent = this
}
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
@@ -629,6 +636,7 @@ class RangeExpr(var from: Expression,
step===node -> step=replacement
else -> throw FatalAstException("invalid replacement")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
@@ -807,6 +815,7 @@ class FunctionCall(override var target: IdentifierReference,
val idx = args.indexOf(node)
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun constValue(program: Program) = constValue(program, true)
@@ -1,93 +0,0 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Statement
import prog8.ast.statements.Subroutine
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
internal class AddressOfInserter(val program: Program): AstWalker() {
// Insert AddressOf (&) expression where required (string params to a UWORD function param etc).
// TODO join this into the StatementReorderer?
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
}
}
+10 -10
View File
@@ -22,10 +22,10 @@ internal class AstChecker(private val program: Program,
if(mainBlocks.size>1)
errors.err("more than one 'main' block", mainBlocks[0].position)
if(mainBlocks.isEmpty())
errors.err("there is no 'main' block", program.position)
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScopes()["start"] as? Subroutine
val startSub = mainBlock.subScope("start") as? Subroutine
if (startSub == null) {
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
} else {
@@ -58,7 +58,7 @@ internal class AstChecker(private val program: Program,
if(irqBlocks.size>1)
errors.err("more than one 'irq' block", irqBlocks[0].position)
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
val irqSub = irqBlock.subScope("irq") as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
@@ -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>()
+12 -1
View File
@@ -41,6 +41,17 @@ interface IAstModification {
}
}
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
@@ -56,7 +67,7 @@ interface IAstModification {
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.parent = parent
replacement.linkParents(parent)
}
}
@@ -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) }
@@ -1,195 +1,125 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
internal class StatementReorderer(val program: Program) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end.
// - in every scope:
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
// -- all vardecls then follow.
// -- the remaining statements then follow in their original order.
//
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every scope, most directives and vardecls are moved to the top.
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) augmented assignment is turned into regular assignment.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - sorts the choices in when statement.
// - a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val addVardecls = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
addVardecls.clear()
super.visit(module)
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
val nonLibraryBlocks = module.statements.withIndex()
.filter { it.value is Block && !(it.value as Block).isInLibrary }
.map { it.index to it.value }
.reversed()
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if(mainBlock!=null && (mainBlock as Block).address==null) {
module.remove(mainBlock)
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
val varDecls = module.statements.filterIsInstance<VarDecl>()
module.statements.removeAll(varDecls)
module.statements.addAll(0, varDecls)
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
for((where, decls) in addVardecls) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
reorderVardeclsAndDirectives(module.statements)
return emptyList()
}
override fun visit(block: Block): Statement {
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
val subroutines = block.statements.filterIsInstance<Subroutine>()
var numSubroutinesAtEnd = 0
// move all subroutines to the end of the block
for (subroutine in subroutines) {
if(subroutine.name!="start" || block.name!="main") {
block.remove(subroutine)
block.statements.add(subroutine)
}
numSubroutinesAtEnd++
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
parent as Module
if(block.isInLibrary) {
return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
}
// move the "start" subroutine to the top
if(block.name=="main") {
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
block.remove(it)
block.statements.add(0, it)
numSubroutinesAtEnd--
reorderVardeclsAndDirectives(block.statements)
return emptyList()
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls)
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives)
block.statements.addAll(0, directives)
block.linkParents(block.parent)
return super.visit(block)
return emptyList()
}
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
subroutine.statements.removeAll(varDecls)
subroutine.statements.addAll(0, varDecls)
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
subroutine.statements.removeAll(directives)
subroutine.statements.addAll(0, directives)
return subroutine
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in addVardecls)
addVardecls[scope] = mutableListOf()
val declList = addVardecls.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
override fun visit(decl: VarDecl): Statement {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position)
assign.linkParents(decl.parent)
decl.value = null
addVarDecl(decl.definingScope(), decl)
return assign
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
)
}
}
return super.visit(decl)
return emptyList()
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return emptyList()
}
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null) {
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
}
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
// struct assignments will be flattened (if it's not a struct literal)
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
val assignments = if (assg.value is StructLiteralValue) {
flattenStructAssignmentFromStructLiteral(assg, program) // 'structvar = { ..... } '
} else {
flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
}
return if (assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
}
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
val assignments = if (assignment.value is StructLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = { ..... } '
} else {
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
return modifications
}
}
if(assg.aug_op!=null) {
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: Expression =
when {
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
}
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
}
return assg
return emptyList()
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
@@ -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))
}
}
}
@@ -48,7 +48,9 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override fun replaceChildNode(node: Node, replacement: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override val expensiveToInline = false
}
@@ -72,6 +74,7 @@ class Block(override val name: String,
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -139,6 +142,7 @@ open class Return(var value: Expression?, override val position: Position) : Sta
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
value = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -274,6 +278,7 @@ open class VarDecl(val type: VarDeclType,
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
value = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -324,6 +329,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index)
index = replacement
replacement.parent = this
}
companion object {
@@ -345,7 +351,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
}
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is NumericLiteralValue
@@ -362,6 +368,7 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
node===value -> value = replacement as Expression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -371,6 +378,30 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
}
fun asDesugaredNonaugmented(): Assignment {
val augmented = aug_op ?: return this
val leftOperand: Expression =
when {
target.register != null -> RegisterExpr(target.register!!, target.position)
target.identifier != null -> target.identifier!!
target.arrayindexed != null -> target.arrayindexed!!
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
else -> throw FatalAstException("strange this")
}
val assignment =
if(augmented=="setvalue") {
Assignment(target, null, value, position)
} else {
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
Assignment(target, null, expression, position)
}
assignment.linkParents(parent)
return assignment
}
}
data class AssignTarget(val register: Register?,
@@ -393,6 +424,7 @@ data class AssignTarget(val register: Register?,
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -503,6 +535,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AssignTarget && node===target)
target = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -557,6 +590,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
val idx = args.indexOf(node)
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -607,6 +641,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -670,6 +705,7 @@ class Subroutine(override val name: String,
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -680,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 }
@@ -724,6 +762,7 @@ class IfStatement(var condition: Expression,
node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -752,6 +791,7 @@ class BranchStatement(var condition: BranchCondition,
node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -782,6 +822,7 @@ class ForLoop(val loopRegister: Register?,
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -817,6 +858,7 @@ class WhileLoop(var condition: Expression,
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -836,6 +878,7 @@ class ForeverLoop(var body: AnonymousScope, override val position: Position) : S
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===body)
body = replacement
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -861,6 +904,7 @@ class RepeatLoop(var body: AnonymousScope,
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -887,6 +931,7 @@ class WhenStatement(var condition: Expression,
val idx = choices.indexOf(node)
choices[idx] = replacement as WhenChoice
}
replacement.parent = this
}
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
@@ -925,6 +970,7 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements)
statements = replacement
replacement.parent = this
}
override fun toString(): String {
@@ -953,6 +999,7 @@ class StructDecl(override val name: String,
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
val numberOfElements: Int
@@ -976,6 +1023,7 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun toString(): String {
@@ -0,0 +1,104 @@
package prog8.compiler
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
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) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return emptyList()
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()
if (sub != null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing != null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if (!conflicts) {
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
initValue.parent = assign
IAstModification.InsertFirst(assign, scope)
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
}
}
return emptyList()
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
&& outerStatements[subroutineStmtIdx - 1] !is Jump
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
}
return mods
}
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) {
if(typecast.parent !is Expression) {
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()
}
}
+17 -5
View File
@@ -5,6 +5,7 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.CompilationTarget
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
@@ -41,11 +42,13 @@ fun compileProgram(filepath: Path,
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
// printAst(programAst) // TODO
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
@@ -161,16 +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()
@@ -184,14 +191,19 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
optimize: Boolean, compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.prepareAsmVariablesAndReturns(errors)
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst) // TODO
val assembly = CompilationTarget.asmGenerator(
programAst,
errors,
zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
@@ -1,80 +0,0 @@
package prog8.compiler.target
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
class AsmVariableAndReturnsPreparer(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) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return emptyList()
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()
if(sub!=null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if(!conflicts) {
val numericVarsWithValue = decls.filter { it.value!=null && it.datatype in NumericDatatypes }
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
IAstModification.InsertFirst(assign, scope)
} +
decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
}
}
return emptyList()
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if(subroutine.asmAddress==null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm()==0
&& subroutine.statements.lastOrNull {it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertAfter(subroutine.statements.last(), returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if(subroutineStmtIdx>0
&& outerStatements[subroutineStmtIdx-1] !is Jump
&& outerStatements[subroutineStmtIdx-1] !is Subroutine
&& outerStatements[subroutineStmtIdx-1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx-1], returnStmt, outerScope as Node)
}
return mods
}
}
@@ -1,6 +1,7 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import java.nio.file.Path
@@ -12,6 +13,6 @@ internal interface CompilationTarget {
lateinit var machine: IMachineDefinition
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
}
}
@@ -26,6 +26,7 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
private val errors: ErrorReporter,
private val zeropage: Zeropage,
private val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator {
@@ -38,7 +39,7 @@ internal class AsmGen(private val program: Program,
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, errors, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>()
@@ -913,6 +914,7 @@ internal class AsmGen(private val program: Program,
val indexName = asmIdentifierName(index)
out(" lda $indexName")
}
// TODO optimize more cases
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x")
@@ -920,6 +922,28 @@ internal class AsmGen(private val program: Program,
}
}
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is RegisterExpr -> {
when (index.register) {
Register.A -> out(" tay")
Register.X -> out(" txa | tay")
Register.Y -> {}
}
}
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" ldy $indexName")
}
// TODO optimize more cases, see translateArrayIndexIntoA
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | ldy $ESTACK_LO_HEX,x")
}
}
}
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
File diff suppressed because it is too large. Load diff
@@ -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")
}
}
@@ -320,8 +331,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// the general, non-optimized cases
translateExpression(expr.left)
translateExpression(expr.right)
if(leftDt!=rightDt)
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
@@ -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 -> {
@@ -225,6 +235,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
@@ -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()
}
}
+2 -2
View File
@@ -29,7 +29,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>()
init {
@@ -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)
@@ -1,11 +1,11 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
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
@@ -18,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
@@ -28,7 +29,6 @@ internal class StatementOptimizer(private val program: Program,
private val vardeclsToRemove = mutableListOf<VarDecl>()
override fun visit(program: Program) {
removeUnusedCode(callgraph)
super.visit(program)
for(decl in vardeclsToRemove) {
@@ -36,45 +36,6 @@ internal class StatementOptimizer(private val program: Program,
}
}
private fun removeUnusedCode(callgraph: CallGraph) {
// 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)
}
}
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) }
}
// 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)
}
if (removeModules.isNotEmpty()) {
program.modules.removeAll(removeModules)
}
}
override fun visit(block: Block): Statement {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
@@ -358,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)
@@ -0,0 +1,38 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block
internal class UnusedCodeRemover: AstWalker() {
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 entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent)) // TODO does removing modules work like this?
}
return removals
}
}
+7 -22
View File
@@ -2,26 +2,11 @@
TODO
====
- finalize (most) of the still missing "new" assignment asm code generation
- 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/
Memory Block Operations integrated in language?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
array/string memory block operations?
- array operations
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
clear (set whole array to the given value, default 0)
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
- strings: identical operations as on array.
For now, we have the ``memcopy`` and ``memset`` builtin functions.
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
More optimizations
@@ -29,13 +14,14 @@ More optimizations
Add more compiler optimizations to the existing ones.
- more targeted optimizations for assigment asm code, such as the following:
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- remove unreachable code after an exit(), return or goto
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
the program will then rely solely on the values as they are in memory at the time of program startup.
- Also some library routines and code patterns could perhaps be optimized further
- can the parameter passing to subroutines be optimized to avoid copying?
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- more optimizations on the language AST level
- more optimizations on the final assembly source level
@@ -45,13 +31,13 @@ Eval stack redesign? (lot of work)
The eval stack is now a split lsb/msb stack using X as the stackpointer.
Is it easier/faster to just use a single page unsplit stack?
It could then even be moved into the zeropage to greatly reduce code size and slowness.
It could then even be moved into the zeropage to reduce code size and slowness.
Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
without having to index with X into the eval stack all the time?
This could GREATLY improvde code size and speed for operatios that work on just a single value.
This could GREATLY improve code size and speed for operations that work on just a single value.
Bug Fixing
@@ -64,4 +50,3 @@ Misc
Several ideas were discussed on my reddit post
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
+1 -1
View File
@@ -106,7 +106,7 @@ main {
check_eval_stack()
c64scr.print("\nyou should see no errors above.")
c64scr.print("\nyou should see no errors printed above (only at first run).")
}
sub check_eval_stack() {
+1 -1
View File
@@ -13,7 +13,7 @@ main {
c64.SPXY[0] = 80
c64.SPXY[1] = 100
c64.SCROLX = c64.SCROLX & %11110111 ; 38 column mode
c64.SCROLX &= %11110111 ; 38 column mode
c64utils.set_rasterirq(1) ; enable animation
+53 -51
View File
@@ -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,24 +135,29 @@ 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
ubyte cy_min_yy = cy - yy
ubyte cy_plus_xx = cy + xx
ubyte cy_min_xx = cy - xx
for plotx in cx to cx+xx {
plot(cy + yy)
plot(cy - yy)
plot(cy_plus_yy)
plot(cy_min_yy)
}
for plotx in cx-xx to cx-1 {
plot(cy + yy)
plot(cy - yy)
plot(cy_plus_yy)
plot(cy_min_yy)
}
for plotx in cx to cx+yy {
plot(cy + xx)
plot(cy - xx)
plot(cy_plus_xx)
plot(cy_min_xx)
}
for plotx in cx-yy to cx {
plot(cy + xx)
plot(cy - xx)
plot(cy_plus_xx)
plot(cy_min_xx)
}
yy++
if decisionOver2<=0
@@ -175,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 {{
@@ -212,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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-2
View File
@@ -1,7 +1,5 @@
%import c64lib
%import c64graphics
%zeropage basicsafe
main {
+4 -4
View File
@@ -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 {
-1
View File
@@ -3,7 +3,6 @@
%import c64flt
%zeropage basicsafe
main {
const uword width = 30
const uword height = 20
+1 -2
View File
@@ -1,7 +1,6 @@
%import c64utils
%import c64lib
main {
sub start() {
@@ -18,7 +17,7 @@ main {
irq {
const ubyte barheight = 4
const ubyte barheight = 4 ; should be big enough to re-trigger the Raster irq properly.
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte color = 0
ubyte yanim = 0
+36
View 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"
}
}
-2
View File
@@ -6,8 +6,6 @@
; shows next piece
; staged speed increase
; some simple sound effects
;
; @todo show ghost?
main {
+32 -8
View File
@@ -6,15 +6,39 @@
main {
sub start() {
float[] floats = [1.1, 2.2]
ubyte index=1
c64flt.print_f(floats[0])
c64flt.print_f(floats[1])
floats[0] = 9.99
floats[index] = 8.88
c64flt.print_f(floats[0])
c64flt.print_f(floats[1])
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
}
}
-1
View File
@@ -2,7 +2,6 @@
%import c64flt
%import c64graphics
%option enable_floats
%zeropage basicsafe
main {