Compare commits

...
24 Commits
Author SHA1 Message Date
Irmen de Jong 9cbb8e1a64 version 3.1 2020-08-18 16:26:23 +02:00
Irmen de Jong 53e9ad5088 better asm code for repeat loops 2020-08-18 16:02:40 +02:00
Irmen de Jong cf6ea63fa6 forloop asm done 2020-08-18 15:29:39 +02:00
Irmen de Jong 1de0ebb7bc more forloop asm 2020-08-18 15:16:56 +02:00
Irmen de Jong 77c1376d6d proper error message for arrays that are declared too big 2020-08-18 14:47:52 +02:00
Irmen de Jong 353f1954a5 for loop codegen 2020-08-18 14:03:31 +02:00
Irmen de Jong 8bf3406cf8 gradle version 2020-08-18 00:53:14 +02:00
Irmen de Jong 936bf9a05c gradle version 2020-08-18 00:47:23 +02:00
Irmen de Jong 4487499663 more forloop codegen 2020-08-17 23:42:43 +02:00
Irmen de Jong 3976cc26a2 more forloop codegen 2020-08-17 23:19:23 +02:00
Irmen de Jong e6ff87ecd0 upgraded to Kotlin 1.4, fixed several compilation warnings 2020-08-17 19:36:07 +02:00
Irmen de Jong c0887b5f08 removed 'continue' statement to be able to generate more optimized loop assembly code. started with for loop optimizations 2020-08-17 19:22:29 +02:00
Irmen de Jong f14dda4eca fix certain corruption of A register argument on asm sub call 2020-08-16 19:15:44 +02:00
Irmen de Jong bd7f75c130 loop todos 2020-07-30 02:54:37 +02:00
Irmen de Jong fbe3ce008b slight expression rewrite in case of certain in-place assignments, to try to get the in-place variable operand to the leftmost position 2020-07-30 01:30:21 +02:00
Irmen de Jong 7ac6c8f2d1 todo related to in-place assignment 2020-07-27 00:32:59 +02:00
Irmen de Jong fdfbb7bdf0 improved call arguments type check 2020-07-27 00:28:48 +02:00
Irmen de Jong 1c16bbb742 tweaks for string handling as arguments 2020-07-27 00:12:27 +02:00
Irmen de Jong 9735527062 cleanup double code 2020-07-26 23:46:06 +02:00
Irmen de Jong 402827497e fix float array assignment 2020-07-26 23:32:20 +02:00
Irmen de Jong f81aa0d867 Merge branch 'remove_aug_assign' 2020-07-26 19:23:34 +02:00
Irmen de Jong d32a970101 partly optimize assignments so that simple increments and decrements can be done via separate statements (postincrdecr) 2020-07-26 19:22:12 +02:00
Irmen de Jong cd651aa416 use repeat 2020-07-26 13:50:14 +02:00
Irmen de Jong 8a3189123a to reduce complexity, augmented assignment has been removed again from internal Ast and codegen for now. 2020-07-26 13:48:31 +02:00
49 changed files with 1483 additions and 1463 deletions

No files matched your search

+2 -2
View File
@@ -4,8 +4,8 @@ sudo: false
# dist: xenial
before_install:
- chmod +x gradlew
- chmod +x ./gradlew
script:
- gradle test
- ./gradlew test
+1 -1
View File
@@ -77,7 +77,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
repeat {
ubyte prime = find_next_prime()
if prime==0
break
+6 -2
View File
@@ -1,11 +1,11 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
// id "org.jetbrains.kotlin.jvm" version "1.4.0"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0'
@@ -110,3 +110,7 @@ dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}
task wrapper(type: Wrapper) {
gradleVersion = '6.1.1'
}
+7 -18
View File
@@ -215,23 +215,13 @@ pop_float_to_indexed_var .proc
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
sta _target+1
sty _target+2
ldy #4
_loop lda (c64.SCRATCH_ZPWORD1),y
_target sta $ffff,y ; modified
dey
bpl _loop
rts
.pend
@@ -772,7 +762,6 @@ set_array_float .proc
asl a
clc
adc c64.ESTACK_LO,x
clc
adc c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
bcc +
+1 -1
View File
@@ -1 +1 @@
3.0
3.1
+10 -9
View File
@@ -287,12 +287,17 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(assignment: Assignment) {
assignment.target.accept(this)
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
output(" ${assignment.aug_op} ")
else
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.left isSameAs assignment.target) {
// we only support the inplace assignments of the form A = A <operator> <value>
assignment.target.accept(this)
output(" ${binExpr.operator}= ")
binExpr.right.accept(this)
} else {
assignment.target.accept(this)
output(" = ")
assignment.value.accept(this)
assignment.value.accept(this)
}
}
override fun visit(postIncrDecr: PostIncrDecr) {
@@ -300,10 +305,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(postIncrDecr.operator)
}
override fun visit(contStmt: Continue) {
output("continue")
}
override fun visit(breakStmt: Break) {
output("break")
}
+5 -10
View File
@@ -161,14 +161,14 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
}
augassignment()?.let {
return Assignment(it.assign_target().toAst(),
it.operator.text,
it.expression().toAst(),
it.toPosition())
// replace A += X with A = A + X
val target = it.assign_target().toAst()
val expression = BinaryExpression(target.toExpression(), it.operator.text.substring(0, 1), it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
}
postincrdecr()?.let {
@@ -217,9 +217,6 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
@@ -593,8 +590,6 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
return ForLoop(loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
-12
View File
@@ -5,7 +5,6 @@ import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
@@ -36,17 +35,6 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
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)
@@ -25,6 +25,8 @@ sealed class Expression: Node {
abstract fun referencesIdentifiers(vararg name: String): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
infix fun isSameAs(other: Expression): Boolean {
if(this===other)
return true
@@ -304,9 +304,9 @@ internal class AstChecker(private val program: Program,
} else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
// The language has no typed pointers at this time.
// TODO The language has no typed pointers at this time! This should be handled better.
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
}
}
@@ -492,8 +492,7 @@ internal class AstChecker(private val program: Program,
when(decl.value) {
null -> {
// a vardecl without an initial value, don't bother with the rest
return super.visit(decl)
// a vardecl without an initial value, don't bother with it
}
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
is StringLiteralValue -> {
@@ -577,6 +576,26 @@ internal class AstChecker(private val program: Program,
}
}
// array length limits
if(decl.isArray) {
val length = decl.arraysize!!.size() ?: 1
when (decl.datatype) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
if(length==0 || length>256)
err("string and byte array length must be 1-256")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(length==0 || length>128)
err("word array length must be 1-128")
}
DataType.ARRAY_F -> {
if(length==0 || length>51)
err("float array length must be 1-51")
}
else -> {}
}
}
super.visit(decl)
}
@@ -810,6 +829,10 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
}
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
if(error!=null)
errors.err(error, functionCall.args.first().position)
super.visit(functionCall)
}
@@ -838,6 +861,11 @@ internal class AstChecker(private val program: Program,
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
}
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program)
if(error!=null)
errors.err(error, functionCallStatement.args.first().position)
super.visit(functionCallStatement)
}
@@ -26,19 +26,16 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val tempvar = IdentifierReference(listOf(tempname), first.position)
val assignTemp = Assignment(
AssignTarget(tempvar, null, null, first.position),
null,
first,
first.position
)
val assignFirst = Assignment(
AssignTarget.fromExpr(first),
null,
second,
first.position
)
val assignSecond = Assignment(
AssignTarget.fromExpr(second),
null,
tempvar,
first.position
)
@@ -88,7 +88,6 @@ abstract class AstWalker {
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
@@ -130,7 +129,6 @@ abstract class AstWalker {
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
@@ -309,11 +307,6 @@ abstract class AstWalker {
track(after(postIncrDecr, parent), postIncrDecr, parent)
}
fun visit(contStmt: Continue, parent: Node) {
track(before(contStmt, parent), contStmt, parent)
track(after(contStmt, parent), contStmt, parent)
}
fun visit(breakStmt: Break, parent: Node) {
track(before(breakStmt, parent), breakStmt, parent)
track(after(breakStmt, parent), breakStmt, parent)
@@ -95,9 +95,6 @@ interface IAstVisitor {
postIncrDecr.target.accept(this)
}
fun visit(contStmt: Continue) {
}
fun visit(breakStmt: Break) {
}
@@ -14,8 +14,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
// - 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.
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
@@ -79,7 +79,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
// move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
@@ -99,10 +99,6 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null) {
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
}
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
@@ -122,6 +118,55 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs assignment.target) {
// A = A <operator> 5, unchanged
return noModifications
}
if(binExpr.operator in associativeOperators) {
if (binExpr.right isSameAs assignment.target) {
// A = v <associative-operator> A ==> A = A <associative-operator> v
return listOf(IAstModification.SwapOperands(binExpr))
}
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
return if(leftBinExpr.left isSameAs assignment.target) {
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
return if(rightBinExpr.left isSameAs assignment.target) {
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
}
}
return noModifications
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
@@ -137,7 +182,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
null, sourceValue, sourceValue.position)
sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
@@ -168,8 +213,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
@@ -13,30 +13,41 @@ import prog8.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall)
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope())
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
if(error!=null)
throw CompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement)
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope())
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
if (error!=null)
throw CompilerException(error)
}
private fun checkTypes(call: IFunctionCall, scope: INameScope) {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
when(target) {
is Subroutine -> {
val paramtypes = target.parameters.map { it.type }
if(argtypes!=paramtypes)
throw CompilerException("parameter type mismatch $call")
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
val paramtypes = func.parameters.map { it.possibleDatatypes }
for(x in argtypes.zip(paramtypes)) {
if(x.first !in x.second)
throw CompilerException("parameter type mismatch $call")
companion object {
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
when (target) {
is Subroutine -> {
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
if(mismatch>=0)
return "argument ${mismatch+1} type mismatch"
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) {
if (x.value.first !in x.value.second)
return "argument ${x.index+1} type mismatch"
}
}
else -> {
}
}
else -> {}
return null
}
}
}
@@ -148,18 +148,6 @@ class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class Break(override val position: Position) : Statement() {
override lateinit var parent: Node
@@ -324,7 +312,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
}
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -346,31 +334,37 @@ open class Assignment(var target: AssignTarget, var aug_op : String?, var value:
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
return("Assignment(target: $target, value: $value, pos=$position)")
}
fun asDesugaredNonaugmented(): Assignment {
val augmented = aug_op ?: return this
val isInplace: Boolean
get() {
val binExpr = value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs target)
return true // A = A <operator> 5
val leftOperand: Expression =
when {
target.identifier != null -> target.identifier!!
target.arrayindexed != null -> target.arrayindexed!!
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
else -> throw FatalAstException("strange this")
if(binExpr.operator in associativeOperators) {
if (binExpr.right isSameAs target)
return true // A = v <associative-operator> A
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
// A = (A <associative-operator> x) <same-operator> y
// A = (x <associative-operator> A) <same-operator> y
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
// A = y <associative-operator> (A <same-operator> x)
// A = y <associative-operator> (x <same-operator> y)
return rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target
}
}
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
}
return false
}
}
data class AssignTarget(var identifier: IdentifierReference?,
@@ -425,6 +419,15 @@ data class AssignTarget(var identifier: IdentifierReference?,
return InferredTypes.unknown()
}
fun toExpression(): Expression {
return when {
identifier!=null -> identifier!!
arrayindexed!=null -> arrayindexed!!
memoryAddress!=null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
else -> throw FatalAstException("invalid assignmenttarget $this")
}
}
infix fun isSameAs(value: Expression): Boolean {
return when {
this.memoryAddress!=null -> {
@@ -40,7 +40,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
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(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.InsertFirst(assign, scope)
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
+1 -3
View File
@@ -79,7 +79,7 @@ fun compileProgram(filepath: Path,
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Parsing...")
val importer = ModuleImporter(errors)
val importer = ModuleImporter()
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
@@ -175,8 +175,6 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.transformAssignments(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
+3 -3
View File
@@ -16,7 +16,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
throw CompilerException("zero page usage has been disabled")
@@ -39,13 +39,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(free.size > 0) {
if(size==1) {
for(candidate in free.min()!! .. free.max()!!+1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
if(loneByte(candidate))
return makeAllocation(candidate, 1, datatype, scopedname)
}
return makeAllocation(free[0], 1, datatype, scopedname)
}
for(candidate in free.min()!! .. free.max()!!+1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
if (sequentialFree(candidate, size))
return makeAllocation(candidate, size, datatype, scopedname)
}
@@ -27,9 +27,9 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
private val errors: ErrorReporter,
private val zeropage: Zeropage,
private val options: CompilationOptions,
val errors: ErrorReporter,
val zeropage: Zeropage,
val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator {
private val assemblyLines = mutableListOf<String>()
@@ -40,16 +40,14 @@ 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, errors, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>()
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
loopContinueLabels.clear()
println("Generating assembly code... ")
@@ -185,7 +183,7 @@ internal class AsmGen(private val program: Program,
val scopedFullName = decl.makeScopedName(decl.name).split('.')
require(scopedFullName.first()==block.name)
val target = AssignTarget(IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
val assign = Assignment(target, null, decl.value!!, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
assign.linkParents(decl.parent)
assignmentAsmGen.translate(assign)
}
@@ -638,7 +636,6 @@ internal class AsmGen(private val program: Program,
is BranchStatement -> translate(stmt)
is IfStatement -> translate(stmt)
is ForLoop -> forloopsAsmGen.translate(stmt)
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
is Break -> out(" jmp ${loopEndLabels.peek()}")
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
@@ -676,9 +673,7 @@ internal class AsmGen(private val program: Program,
private fun translate(stmt: RepeatLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
val counterLabel = makeLabel("repeatcounter")
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
when (stmt.iterations) {
null -> {
@@ -694,13 +689,13 @@ internal class AsmGen(private val program: Program,
throw AssemblyError("invalid number of iterations")
when {
iterations == 0 -> {}
iterations <= 255 -> {
out(" lda #${iterations}")
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
iterations <= 256 -> {
out(" lda #${iterations and 255}")
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt.body)
}
else -> {
out(" lda #<${iterations} | ldy #>${iterations}")
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt.body)
}
}
}
@@ -710,11 +705,11 @@ internal class AsmGen(private val program: Program,
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> {
out(" lda $name")
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
}
DataType.UWORD, DataType.WORD -> {
out(" lda $name | ldy $name+1")
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
}
@@ -725,11 +720,11 @@ internal class AsmGen(private val program: Program,
when (dt) {
in ByteDatatypes -> {
out(" inx | lda ${ESTACK_LO_HEX},x")
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
}
in WordDatatypes -> {
out(" inx | lda ${ESTACK_LO_HEX},x | ldy ${ESTACK_HI_HEX},x")
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
}
else -> throw AssemblyError("invalid loop expression datatype $dt")
}
@@ -737,49 +732,63 @@ internal class AsmGen(private val program: Program,
}
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun repeatWordCountInAY(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: A/Y must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
out("""
sta $counterLabel
sty $counterLabel+1
$repeatLabel lda $counterLabel
sta $counterVar
sty $counterVar+1
$repeatLabel lda $counterVar
bne +
lda $counterLabel+1
lda $counterVar+1
beq $endLabel
+ lda $counterLabel
+ lda $counterVar
bne +
dec $counterLabel+1
+ dec $counterLabel
dec $counterVar+1
+ dec $counterVar
""")
translate(body)
out("""
jmp $repeatLabel
$counterLabel .word 0
$endLabel""")
out(" jmp $repeatLabel")
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
out("""$counterVar = $zpAddr ; auto zp UWORD""")
} else {
out("""
$counterVar .word 0""")
}
out(endLabel)
}
private fun repeatByteCountInA(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: A must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
out("""
sta $counterLabel
$repeatLabel lda $counterLabel
beq $endLabel
dec $counterLabel""")
sta $counterVar
$repeatLabel""")
translate(body)
out("""
jmp $repeatLabel
$counterLabel .byte 0
$endLabel""")
dec $counterVar
bne $repeatLabel
beq $endLabel""")
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
out("""$counterVar = $zpAddr ; auto zp UBYTE""")
} else {
out("""
$counterVar .byte 0""")
}
out(endLabel)
}
private fun translate(stmt: WhileLoop) {
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(whileLabel)
out(whileLabel)
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
@@ -800,14 +809,12 @@ $endLabel""")
out(" jmp $whileLabel")
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: UntilLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
out(repeatLabel)
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
@@ -827,7 +834,6 @@ $endLabel""")
}
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: WhenStatement) {
@@ -929,7 +935,7 @@ $endLabel""")
val next = (stmt.parent as INameScope).nextSibling(stmt)
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
val target = AssignTarget(IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
val assign = Assignment(target, null, stmt.value!!, stmt.position)
val assign = Assignment(target, stmt.value!!, stmt.position)
assign.linkParents(stmt.parent)
translate(assign)
}
@@ -1006,21 +1012,6 @@ $endLabel""")
}
}
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
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)
@@ -1048,6 +1039,9 @@ $endLabel""")
fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromWordVariable(target, variable)
fun assignFromAddressOf(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromAddressOf(target, variable)
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromFloatVariable(target, variable)
@@ -15,731 +15,18 @@ import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
internal class AssignmentAsmGen(private val program: Program, private val errors: ErrorReporter, private val asmgen: AsmGen) {
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(assign: Assignment) {
if (assign.aug_op == null)
translateNormalAssignment(assign)
if(assign.isInplace)
translateNormalAssignment(assign) // TODO generate better code here for in-place assignments
else
translateInplaceAssignment(assign)
translateNormalAssignment(assign)
}
private fun translateInplaceAssignment(assign: Assignment) {
require(assign.aug_op != null)
when {
assign.target.identifier != null -> {
if (inplaceAssignToIdentifier(assign))
return
}
assign.target.memoryAddress != null -> {
if (inplaceAssignToMemoryByte(assign))
return
}
assign.target.arrayindexed != null -> {
if (inplaceAssignToArrayOrString(assign))
return
}
}
// TODO this is the slow FALLBACK, eventually we don't want to have to use it anymore:
errors.warn("using suboptimal in-place assignment code (this should still be optimized)", assign.position)
val normalAssignment = assign.asDesugaredNonaugmented()
return translateNormalAssignment(normalAssignment)
}
private fun inplaceAssignToArrayOrString(assign: Assignment): Boolean {
val targetArray = assign.target.arrayindexed!!
val arrayName = targetArray.identifier
val arrayIndex = targetArray.arrayspec.index
val targetName = asmgen.asmIdentifierName(arrayName)
val arrayDt = arrayName.targetVarDecl(program.namespace)!!.datatype
val constValue = assign.value.constValue(program)?.number
if (constValue != null) {
// constant value to set in array
val hexValue = constValue.toHex()
if (assign.aug_op == "setvalue") {
when (arrayDt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" lda #$hexValue | sta $targetName,y")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
asl a
tay
lda #<$hexValue
sta $targetName,y
lda #>$hexValue
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> {
assignFromFloatConstant(assign.target, constValue.toDouble())
}
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
} else {
TODO("aug assignment to element in array/string")
}
return true
}
// non-const value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when(arrayDt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" lda $sourceName")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" sta $targetName,y")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
asl a
tay
lda $sourceName
sta $targetName,y
lda $sourceName+1
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> return false // TODO optimize instead of fallback?
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
return true
}
is AddressOf -> {
TODO("assign address into array $assign")
}
is DirectMemoryRead -> {
TODO("assign memory read into array $assign")
}
is ArrayIndexedExpression -> {
if(assign.aug_op != "setvalue")
return false // we don't put effort into optimizing anything beside simple assignment
val valueArrayExpr = assign.value as ArrayIndexedExpression
val valueArrayIndex = valueArrayExpr.arrayspec.index
val valueVariablename = asmgen.asmIdentifierName(valueArrayExpr.identifier)
// val valueDt = valueArrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
when(arrayDt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${valueArrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(valueArrayExpr)
asmgen.out(" lda $valueVariablename,y")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" sta $targetName,y")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #2*${valueArrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(valueArrayExpr)
asmgen.out(" asl a | tay")
}
asmgen.out("""
lda $valueVariablename,y
pha
lda $valueVariablename+1,y
pha
""")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #2*${arrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out(" asl a | tay")
}
asmgen.out("""
pla
sta $targetName+1,y
pla
sta $targetName,y
""")
return true
}
DataType.ARRAY_F -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #5*${valueArrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(valueArrayExpr)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
""")
}
asmgen.out("""
lda $valueVariablename,y
pha
lda $valueVariablename+1,y
pha
lda $valueVariablename+2,y
pha
lda $valueVariablename+3,y
pha
lda $valueVariablename+4,y
pha
""")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #5*${arrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
""")
}
asmgen.out("""
pla
sta $targetName+4,y
pla
sta $targetName+3,y
pla
sta $targetName+2,y
pla
sta $targetName+1,y
pla
sta $targetName,y
""")
return true
}
else -> throw AssemblyError("assignment to array: invalid array dt")
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
return false
}
private fun inplaceAssignToMemoryByte(assign: Assignment): Boolean {
val address = assign.target.memoryAddress?.addressExpression?.constValue(program)?.number
?: return inplaceAssignToNonConstMemoryByte(assign)
val hexAddr = address.toHex()
val constValue = assign.value.constValue(program)
if (constValue != null) {
val hexValue = constValue.number.toHex()
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta $hexAddr")
"+=" -> asmgen.out(" lda $hexAddr | clc | adc #$hexValue | sta $hexAddr")
"-=" -> asmgen.out(" lda $hexAddr | sec | sbc #$hexValue | sta $hexAddr")
"/=" -> TODO("membyte /= const $hexValue")
"*=" -> TODO("membyte *= const $hexValue")
"&=" -> asmgen.out(" lda $hexAddr | and #$hexValue | sta $hexAddr")
"|=" -> asmgen.out(" lda $hexAddr | ora #$hexValue | sta $hexAddr")
"^=" -> asmgen.out(" lda $hexAddr | eor #$hexValue | sta $hexAddr")
"%=" -> TODO("membyte %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const value.
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $sourceName | sta $hexAddr")
else -> TODO("membyte aug.assign variable $assign")
}
return true
}
is DirectMemoryRead -> {
val memory = (assign.value as DirectMemoryRead).addressExpression.constValue(program)!!.number.toHex()
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $memory | sta $hexAddr")
else -> TODO("membyte aug.assign memread $assign")
}
return true
}
is ArrayIndexedExpression -> {
TODO("membyte = array value $assign")
}
is AddressOf -> throw AssemblyError("can't assign address to byte")
else -> {
fallbackAssignment(assign)
return true
}
}
return false
}
private fun inplaceAssignToNonConstMemoryByte(assign: Assignment): Boolean {
// target address is not constant, so evaluate it from the stack
asmgen.translateExpression(assign.target.memoryAddress!!.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta ${C64Zeropage.SCRATCH_W1}
lda $ESTACK_HI_HEX,x
sta ${C64Zeropage.SCRATCH_W1}+1
""")
val constValue = assign.value.constValue(program)
if (constValue != null) {
val hexValue = constValue.number.toHex()
asmgen.out(" ldy #0")
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"+=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | clc | adc #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"-=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | sec | sbc #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"/=" -> TODO("membyte /= const $hexValue")
"*=" -> TODO("membyte *= const $hexValue")
"&=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | and #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"|=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | ora #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"^=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | eor #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"%=" -> TODO("membyte %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
TODO("membyte = variable $assign")
}
is DirectMemoryRead -> {
TODO("membyte = memread $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
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("assign to memory byte: expected byte array or string source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(arrayExpr)
asmgen.out("""
lda $variablename,y
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
is AddressOf -> throw AssemblyError("can't assign memory address to memory byte")
else -> {
fallbackAssignment(assign)
return true
}
}
return false // TODO optimized
}
private fun inplaceAssignToIdentifier(assign: Assignment): Boolean {
val targetType = assign.target.inferType(program, assign)
val constNumber = assign.value.constValue(program)?.number
val targetName = asmgen.asmIdentifierName(assign.target.identifier!!)
when (targetType.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
// (u)byte assignment
if (constNumber != null) {
val hexValue = constNumber.toHex()
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta $targetName")
"+=" -> asmgen.out(" lda $targetName | clc | adc #$hexValue | sta $targetName")
"-=" -> asmgen.out(" lda $targetName | sec | sbc #$hexValue | sta $targetName")
"/=" -> TODO("variable /= const $hexValue")
"*=" -> TODO("variable *= const $hexValue")
"&=" -> asmgen.out(" lda $targetName | and #$hexValue | sta $targetName")
"|=" -> asmgen.out(" lda $targetName | ora #$hexValue | sta $targetName")
"^=" -> asmgen.out(" lda $targetName | eor #$hexValue | sta $targetName")
"%=" -> TODO("variable %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const (u)byte value
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda $sourceName | sta $targetName")
"+=" -> asmgen.out(" lda $targetName | clc | adc $sourceName | sta $targetName")
"-=" -> asmgen.out(" lda $targetName | sec | sbc $sourceName | sta $targetName")
"/=" -> TODO("variable /= variable")
"*=" -> TODO("variable *= variable")
"&=" -> asmgen.out(" lda $targetName | and $sourceName | sta $targetName")
"|=" -> asmgen.out(" lda $targetName | ora $sourceName | sta $targetName")
"^=" -> asmgen.out(" lda $targetName | eor $sourceName | sta $targetName")
"%=" -> TODO("variable %= variable")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
is DirectMemoryRead -> {
TODO("variable = memory read $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
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("assign to identifier: expected byte array or string source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(arrayExpr)
asmgen.out(" lda $variablename,y | sta $targetName")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.UWORD, DataType.WORD -> {
if (constNumber != null) {
val hexNumber = constNumber.toHex()
when (assign.aug_op) {
"setvalue" -> {
asmgen.out("""
lda #<$hexNumber
sta $targetName
lda #>$hexNumber
sta $targetName+1
""")
}
"+=" -> {
asmgen.out("""
lda $targetName
clc
adc #<$hexNumber
sta $targetName
lda $targetName+1
adc #>$hexNumber
sta $targetName+1
""")
}
"-=" -> {
asmgen.out("""
lda $targetName
sec
sbc #<$hexNumber
sta $targetName
lda $targetName+1
sbc #>$hexNumber
sta $targetName+1
""")
}
else -> TODO("variable aug.assign ${assign.aug_op} const $hexNumber")
}
return true
}
// non-const value
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when (assign.aug_op) {
"setvalue" -> {
asmgen.out("""
lda $sourceName
sta $targetName
lda $sourceName+1
sta $targetName+1
""")
}
"+=" -> {
asmgen.out("""
lda $targetName
clc
adc $sourceName
sta $targetName
lda $targetName+1
adc $sourceName+1
sta $targetName+1
""")
return true
}
"-=" -> {
asmgen.out("""
lda $targetName
sec
sbc $sourceName
sta $targetName
lda $targetName+1
sbc $sourceName+1
sta $targetName+1
""")
return true
}
else -> {
TODO("variable aug.assign variable")
}
}
return true
}
is DirectMemoryRead -> throw AssemblyError("expected a typecast for assigning memory read byte to word")
is AddressOf -> {
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") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
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("assign to identifier: expected word array source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.out("""
asl a
tay
lda $variablename,y
sta $targetName
lda $variablename+1,y
sta $targetName+1
""")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.FLOAT -> {
if (constNumber != null) {
// assign a constant
val floatConst = asmgen.getFloatConst(constNumber.toDouble())
when (assign.aug_op) {
"setvalue" -> assignFromFloatConstant(assign.target, constNumber.toDouble())
"+=" -> {
if (constNumber == 0.5) {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.MOVFM
jsr c64flt.FADDH
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
} else {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.MOVFM
lda #<$floatConst
ldy #>$floatConst
jsr c64flt.FADD
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
}
return true
}
"-=" -> {
asmgen.out("""
lda #<$floatConst
ldy #>$floatConst
jsr c64flt.MOVFM
lda #<$targetName
ldy #>$targetName
jsr c64flt.FSUB
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
return true
}
else -> TODO("float const value aug.assign $assign")
}
return true
}
// non-const float value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
when (assign.aug_op) {
"setvalue" -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
"+=" -> return false // TODO optimized float += variable
"-=" -> return false // TODO optimized float -= variable
else -> TODO("float non-const value aug.assign $assign")
}
return true
}
is ArrayIndexedExpression -> {
when(assign.aug_op) {
"setvalue" -> {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
if (arrayDt != DataType.ARRAY_F)
throw AssemblyError("assign to identifier: expected float array source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.out("""
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
tay
lda $variablename,y
sta $targetName
lda $variablename+1,y
sta $targetName+1
lda $variablename+2,y
sta $targetName+2
lda $variablename+3,y
sta $targetName+3
lda $variablename+4,y
sta $targetName+4
""")
}
else -> TODO("float $assign")
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.STR -> {
val identifier = assign.value as? IdentifierReference
?: throw AssemblyError("string value assignment expects identifier value")
val sourceName = asmgen.asmIdentifierName(identifier)
asmgen.out("""
lda #<$targetName
sta ${C64Zeropage.SCRATCH_W1}
lda #>$targetName
sta ${C64Zeropage.SCRATCH_W1+1}
lda #<$sourceName
ldy #>$sourceName
jsr prog8_lib.strcpy
""")
return true
}
else -> throw AssemblyError("assignment to identifier: invalid target datatype: $targetType")
}
return false
}
private fun fallbackAssignment(assign: Assignment) {
if (assign.aug_op != "setvalue") {
/* stack-based evaluation of the expression is required */
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
} else {
when (assign.value) {
is FunctionCall -> {
// TODO is there a way to avoid function return value being passed via the stack?
// for instance, 1 byte return value always in A, etc
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
else -> {
/* stack-based evaluation of the expression is required */
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
}
}
}
// old code-generation below:
// eventually, all of this should have been replaced by newer more optimized code above.
// eventually, all of this should have been replaced by newer more optimized code.
private fun translateNormalAssignment(assign: Assignment) {
require(assign.aug_op == null)
when (assign.value) {
is NumericLiteralValue -> {
val numVal = assign.value as NumericLiteralValue
@@ -1288,8 +575,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
sta $arrayVarName+$indexValue+4
""")
} else {
// TODO the index in A below seems to be clobbered?
asmgen.translateArrayIndexIntoA(targetArrayIdx)
asmgen.translateExpression(index)
asmgen.out("""
lda #<${constFloat}
sta ${C64Zeropage.SCRATCH_W1}
@@ -194,7 +194,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)
private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40)
private val powersOfTwo = setOf(0,1,2,4,8,16,32,64,128,256)
private fun translateExpression(expr: BinaryExpression) {
val leftIDt = expr.left.inferType(program)
@@ -14,10 +14,6 @@ import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex
import kotlin.math.absoluteValue
// todo choose more efficient comparisons to avoid needless lda's
// todo optimize common case when step == 2 or -2
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: ForLoop) {
@@ -43,9 +39,9 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val stepsize=range.step.constValue(program)!!.number.toInt()
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
@@ -60,19 +56,21 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
lda $ESTACK_LO_HEX,x
sta $varname
lda $ESTACK_LO_PLUS1_HEX,x
sta $modifiedLabel+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname
jmp $loopLabel
$endLabel inx""")
}
else {
} else {
// bytes, step >= 2 or <= -2
@@ -82,18 +80,20 @@ $endLabel inx""")
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
lda $ESTACK_LO_HEX,x
sta $varname
lda $ESTACK_LO_PLUS1_HEX,x
sta $modifiedLabel+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname""")
lda $varname""")
if(stepsize>0) {
asmgen.out("""
clc
adc #$stepsize
sta $varname
cmp $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel cmp #0 ; modified
bcc $loopLabel
beq $loopLabel""")
} else {
@@ -101,7 +101,7 @@ $continueLabel lda $varname""")
sec
sbc #${stepsize.absoluteValue}
sta $varname
cmp $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel cmp #0 ; modified
bcs $loopLabel""")
}
asmgen.out("""
@@ -116,44 +116,53 @@ $endLabel inx""")
stepsize == 1 || stepsize == -1 -> {
asmgen.translateExpression(range.to)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
null, range.from, range.position)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position), range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.out("""
lda $ESTACK_HI_PLUS1_HEX,x
sta $modifiedLabel+1
lda $ESTACK_LO_PLUS1_HEX,x
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname+1
cmp $ESTACK_HI_PLUS1_HEX,x
$modifiedLabel cmp #0 ; modified
bne +
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne +
inc $varname+1
bne $loopLabel
inc $varname+1
jmp $loopLabel
""")
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
+ dec $varname
jmp $loopLabel""")
}
asmgen.out("""
+ jmp $loopLabel
$endLabel inx""")
asmgen.out(endLabel)
asmgen.out(" inx")
}
stepsize > 0 -> {
// (u)words, step >= 2
asmgen.translateExpression(range.to)
asmgen.out("""
lda $ESTACK_HI_PLUS1_HEX,x
sta $modifiedLabel+1
lda $ESTACK_LO_PLUS1_HEX,x
sta $modifiedLabel2+1
""")
val varname = asmgen.asmIdentifierName(stmt.loopVar)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
null, range.from, range.position)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position), range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
@@ -168,12 +177,11 @@ $endLabel inx""")
lda $varname+1
adc #>$stepsize
sta $varname+1
lda $ESTACK_HI_PLUS1_HEX,x
cmp $varname+1
bcc $endLabel
bne $loopLabel
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel cmp #0 ; modified
bcc $loopLabel
bne $endLabel
$modifiedLabel2 lda #0 ; modified
cmp $varname
bcc $endLabel
bcs $loopLabel
$endLabel inx""")
@@ -186,9 +194,9 @@ $endLabel inx""")
lda $varname+1
adc #>$stepsize
sta $varname+1
lda $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel2 lda #0 ; modified
cmp $varname
lda $ESTACK_HI_PLUS1_HEX,x
$modifiedLabel lda #0 ; modified
sbc $varname+1
bvc +
eor #$80
@@ -200,9 +208,14 @@ $endLabel inx""")
// (u)words, step <= -2
asmgen.translateExpression(range.to)
asmgen.out("""
lda $ESTACK_HI_PLUS1_HEX,x
sta $modifiedLabel+1
lda $ESTACK_LO_PLUS1_HEX,x
sta $modifiedLabel2+1
""")
val varname = asmgen.asmIdentifierName(stmt.loopVar)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
null, range.from, range.position)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position), range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
@@ -217,11 +230,11 @@ $endLabel inx""")
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
cmp $ESTACK_HI_PLUS1_HEX,x
$modifiedLabel cmp #0 ; modified
bcc $endLabel
bne $loopLabel
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel2 cmp #0 ; modified
bcs $loopLabel
$endLabel inx""")
} else {
@@ -235,9 +248,9 @@ $endLabel inx""")
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
cmp $ESTACK_LO_PLUS1_HEX,x
$modifiedLabel2 cmp #0 ; modified
lda $varname+1
sbc $ESTACK_HI_PLUS1_HEX,x
$modifiedLabel sbc #0 ; modified
bvc +
eor #$80
+ bpl $loopLabel
@@ -250,15 +263,12 @@ $endLabel inx""")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val iterableName = asmgen.asmIdentifierName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
when(iterableDt) {
@@ -269,72 +279,88 @@ $endLabel inx""")
sta $loopLabel+1
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel""")
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
beq $endLabel
sta ${asmgen.asmIdentifierName(stmt.loopVar)}""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $loopLabel+1
inc $loopLabel+1
bne $loopLabel
inc $loopLabel+2
bne $loopLabel
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
val length = decl.arraysize!!.size()!!
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $modifiedLabel+1
sty $modifiedLabel+2
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified""")
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmIdentifierName(stmt.loopVar)}""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
iny
cpy #${length and 255}
beq $endLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
if(length<=255) {
asmgen.out("""
ldy $indexVar
iny
cpy #$length
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
} else {
asmgen.out("""
$indexVar .byte 0""")
}
asmgen.out(endLabel)
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
val length = decl.arraysize!!.size()!! * 2
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
val indexVar = asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $modifiedLabel+1
sty $modifiedLabel+2
lda #<$iterableName+1
ldy #>$iterableName+1
sta $modifiedLabel2+1
sty $modifiedLabel2+2
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified
$loopLabel sty $indexVar
lda $iterableName,y
sta $loopvarName
$modifiedLabel2 lda ${65535.toHex()},y ; modified
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
iny
iny
cpy #${length and 255}
beq $endLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
if(length<=127) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
} else {
asmgen.out("""
$indexVar .byte 0""")
}
asmgen.out(endLabel)
}
DataType.ARRAY_F -> {
throw AssemblyError("for loop with floating point variables is not supported")
@@ -342,205 +368,123 @@ $endLabel""")
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter var in such cases
if (range.isEmpty())
throw AssemblyError("empty range")
if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
}
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
val counterLabel = asmgen.makeLabel("for_counter")
// loop over byte range via loopvar
// loop over byte range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmIdentifierName(stmt.loopVar)
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.last-range.first+1 and 255}
sta $counterLabel
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.body)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
2 -> {
if(range.last==255) {
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
inc $varname
beq $endLabel
inc $varname
bne $loopLabel""")
} else {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname
inc $varname
jmp $loopLabel""")
}
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
clc
adc #${range.step}
sta $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
-2 -> {
when (range.last) {
0 -> asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
1 -> asmgen.out("""
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
else -> asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
}
}
else -> {
// step <= -2
// step <= -3 or >= 3
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
sec
sbc #${range.step.absoluteValue}
sta $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
lda $varname
cmp #${range.last}
beq $endLabel
clc
adc #${range.step}
sta $varname
jmp $loopLabel""")
}
}
asmgen.out(endLabel)
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar
// loop over word range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmIdentifierName(stmt.loopVar)
when {
range.step == 1 -> {
// word, step = 1
val lastValue = range.last+1
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $varname
bne +
inc $varname+1
+ lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
range.step == -1 -> {
// word, step = 1
val lastValue = range.last-1
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname
bne +
dec $varname+1
+ dec $varname
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
range.step >= 2 -> {
// word, step >= 2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val lastValue = range.last+range.step
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel clc
lda $varname
adc #<${range.step}
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
else -> {
// step <= -2
// word, step >= 2 or <= -2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val lastValue = range.last+range.step
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel sec
lda $varname
sbc #<${range.step.absoluteValue}
sta $varname
lda $varname+1
sbc #>${range.step.absoluteValue}
sta $varname+1
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
clc
adc #<${range.step}
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
jmp $loopLabel
$endLabel""")
}
}
@@ -548,7 +492,127 @@ $endLabel""")
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
if (range.last == 255) {
asmgen.out("""
inc $varname
bne $loopLabel
$endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname
jmp $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
when (range.last) {
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
jmp $loopLabel
$endLabel""")
}
1 -> {
asmgen.out("""
dec $varname
jmp $loopLabel
$endLabel""")
}
else -> {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname
jmp $loopLabel
$endLabel""")
}
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
$endLabel""")
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel
$endLabel""")
asmgen.loopEndLabels.pop()
}
}
@@ -115,7 +115,6 @@ 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 argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype")
}
}
@@ -125,7 +124,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 argument via a variable?") // TODO huh
in PassByReferenceDatatypes -> asmgen.assignFromAddressOf(target, value)
else -> throw AssemblyError("weird parameter datatype")
}
}
@@ -183,12 +182,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+
+ pla
""")
}
else -> {
@@ -0,0 +1,744 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
// OLD inplace-assignment code.
// should really come up with a more compact way to generate this kind of code...
/***
internal class InplaceAssignmentAsmGen(private val program: Program, private val errors: ErrorReporter, private val asmgen: AsmGen) {
internal fun translate(assign: Assignment) {
if (assign.aug_op == null)
translateNormalAssignment(assign)
else
translateInplaceAssignment(assign)
}
private fun translateInplaceAssignment(assign: Assignment) {
require(assign.aug_op != null)
when {
assign.target.identifier != null -> {
if (inplaceAssignToIdentifier(assign))
return
}
assign.target.memoryAddress != null -> {
if (inplaceAssignToMemoryByte(assign))
return
}
assign.target.arrayindexed != null -> {
if (inplaceAssignToArrayOrString(assign))
return
}
}
// TODO this is the slow FALLBACK, eventually we don't want to have to use it anymore:
errors.warn("using suboptimal in-place assignment code (this should still be optimized)", assign.position)
val normalAssignment = assign.asDesugaredNonaugmented()
return translateNormalAssignment(normalAssignment)
}
private fun inplaceAssignToArrayOrString(assign: Assignment): Boolean {
val targetArray = assign.target.arrayindexed!!
val arrayName = targetArray.identifier
val arrayIndex = targetArray.arrayspec.index
val targetName = asmgen.asmIdentifierName(arrayName)
val arrayDt = arrayName.targetVarDecl(program.namespace)!!.datatype
val constValue = assign.value.constValue(program)?.number
if (constValue != null) {
// constant value to set in array
val hexValue = constValue.toHex()
if (assign.aug_op == "setvalue") {
when (arrayDt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" lda #$hexValue | sta $targetName,y")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
asl a
tay
lda #<$hexValue
sta $targetName,y
lda #>$hexValue
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> {
assignFromFloatConstant(assign.target, constValue.toDouble())
}
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
} else {
TODO("aug assignment to element in array/string")
}
return true
}
// non-const value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when(arrayDt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" lda $sourceName")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" sta $targetName,y")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
asl a
tay
lda $sourceName
sta $targetName,y
lda $sourceName+1
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> return false // TODO optimize instead of fallback?
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
return true
}
is AddressOf -> {
TODO("assign address into array $assign")
}
is DirectMemoryRead -> {
TODO("assign memory read into array $assign")
}
is ArrayIndexedExpression -> {
if(assign.aug_op != "setvalue")
return false // we don't put effort into optimizing anything beside simple assignment
val valueArrayExpr = assign.value as ArrayIndexedExpression
val valueArrayIndex = valueArrayExpr.arrayspec.index
val valueVariablename = asmgen.asmIdentifierName(valueArrayExpr.identifier)
// val valueDt = valueArrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
when(arrayDt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${valueArrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(valueArrayExpr)
asmgen.out(" lda $valueVariablename,y")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" sta $targetName,y")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #2*${valueArrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(valueArrayExpr)
asmgen.out(" asl a | tay")
}
asmgen.out("""
lda $valueVariablename,y
pha
lda $valueVariablename+1,y
pha
""")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #2*${arrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out(" asl a | tay")
}
asmgen.out("""
pla
sta $targetName+1,y
pla
sta $targetName,y
""")
return true
}
DataType.ARRAY_F -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #5*${valueArrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(valueArrayExpr)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
""")
}
asmgen.out("""
lda $valueVariablename,y
pha
lda $valueVariablename+1,y
pha
lda $valueVariablename+2,y
pha
lda $valueVariablename+3,y
pha
lda $valueVariablename+4,y
pha
""")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #5*${arrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
""")
}
asmgen.out("""
pla
sta $targetName+4,y
pla
sta $targetName+3,y
pla
sta $targetName+2,y
pla
sta $targetName+1,y
pla
sta $targetName,y
""")
return true
}
else -> throw AssemblyError("assignment to array: invalid array dt")
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
return false
}
private fun inplaceAssignToMemoryByte(assign: Assignment): Boolean {
val address = assign.target.memoryAddress?.addressExpression?.constValue(program)?.number
?: return inplaceAssignToNonConstMemoryByte(assign)
val hexAddr = address.toHex()
val constValue = assign.value.constValue(program)
if (constValue != null) {
val hexValue = constValue.number.toHex()
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta $hexAddr")
"+=" -> asmgen.out(" lda $hexAddr | clc | adc #$hexValue | sta $hexAddr")
"-=" -> asmgen.out(" lda $hexAddr | sec | sbc #$hexValue | sta $hexAddr")
"/=" -> TODO("membyte /= const $hexValue")
"*=" -> TODO("membyte *= const $hexValue")
"&=" -> asmgen.out(" lda $hexAddr | and #$hexValue | sta $hexAddr")
"|=" -> asmgen.out(" lda $hexAddr | ora #$hexValue | sta $hexAddr")
"^=" -> asmgen.out(" lda $hexAddr | eor #$hexValue | sta $hexAddr")
"%=" -> TODO("membyte %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const value.
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $sourceName | sta $hexAddr")
else -> TODO("membyte aug.assign variable $assign")
}
return true
}
is DirectMemoryRead -> {
val memory = (assign.value as DirectMemoryRead).addressExpression.constValue(program)!!.number.toHex()
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $memory | sta $hexAddr")
else -> TODO("membyte aug.assign memread $assign")
}
return true
}
is ArrayIndexedExpression -> {
TODO("membyte = array value $assign")
}
is AddressOf -> throw AssemblyError("can't assign address to byte")
else -> {
fallbackAssignment(assign)
return true
}
}
return false
}
private fun inplaceAssignToNonConstMemoryByte(assign: Assignment): Boolean {
// target address is not constant, so evaluate it from the stack
asmgen.translateExpression(assign.target.memoryAddress!!.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta ${C64Zeropage.SCRATCH_W1}
lda $ESTACK_HI_HEX,x
sta ${C64Zeropage.SCRATCH_W1}+1
""")
val constValue = assign.value.constValue(program)
if (constValue != null) {
val hexValue = constValue.number.toHex()
asmgen.out(" ldy #0")
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"+=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | clc | adc #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"-=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | sec | sbc #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"/=" -> TODO("membyte /= const $hexValue")
"*=" -> TODO("membyte *= const $hexValue")
"&=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | and #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"|=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | ora #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"^=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | eor #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"%=" -> TODO("membyte %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
TODO("membyte = variable $assign")
}
is DirectMemoryRead -> {
TODO("membyte = memread $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
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("assign to memory byte: expected byte array or string source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(arrayExpr)
asmgen.out("""
lda $variablename,y
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
is AddressOf -> throw AssemblyError("can't assign memory address to memory byte")
else -> {
fallbackAssignment(assign)
return true
}
}
return false // TODO optimized
}
private fun inplaceAssignToIdentifier(assign: Assignment): Boolean {
val targetType = assign.target.inferType(program, assign)
val constNumber = assign.value.constValue(program)?.number
val targetName = asmgen.asmIdentifierName(assign.target.identifier!!)
when (targetType.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
// (u)byte assignment
if (constNumber != null) {
val hexValue = constNumber.toHex()
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta $targetName")
"+=" -> asmgen.out(" lda $targetName | clc | adc #$hexValue | sta $targetName")
"-=" -> asmgen.out(" lda $targetName | sec | sbc #$hexValue | sta $targetName")
"/=" -> TODO("variable /= const $hexValue")
"*=" -> TODO("variable *= const $hexValue")
"&=" -> asmgen.out(" lda $targetName | and #$hexValue | sta $targetName")
"|=" -> asmgen.out(" lda $targetName | ora #$hexValue | sta $targetName")
"^=" -> asmgen.out(" lda $targetName | eor #$hexValue | sta $targetName")
"%=" -> TODO("variable %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const (u)byte value
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda $sourceName | sta $targetName")
"+=" -> asmgen.out(" lda $targetName | clc | adc $sourceName | sta $targetName")
"-=" -> asmgen.out(" lda $targetName | sec | sbc $sourceName | sta $targetName")
"/=" -> TODO("variable /= variable")
"*=" -> TODO("variable *= variable")
"&=" -> asmgen.out(" lda $targetName | and $sourceName | sta $targetName")
"|=" -> asmgen.out(" lda $targetName | ora $sourceName | sta $targetName")
"^=" -> asmgen.out(" lda $targetName | eor $sourceName | sta $targetName")
"%=" -> TODO("variable %= variable")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
is DirectMemoryRead -> {
TODO("variable = memory read $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
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("assign to identifier: expected byte array or string source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(arrayExpr)
asmgen.out(" lda $variablename,y | sta $targetName")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.UWORD, DataType.WORD -> {
if (constNumber != null) {
val hexNumber = constNumber.toHex()
when (assign.aug_op) {
"setvalue" -> {
asmgen.out("""
lda #<$hexNumber
sta $targetName
lda #>$hexNumber
sta $targetName+1
""")
}
"+=" -> {
asmgen.out("""
lda $targetName
clc
adc #<$hexNumber
sta $targetName
lda $targetName+1
adc #>$hexNumber
sta $targetName+1
""")
}
"-=" -> {
asmgen.out("""
lda $targetName
sec
sbc #<$hexNumber
sta $targetName
lda $targetName+1
sbc #>$hexNumber
sta $targetName+1
""")
}
else -> TODO("variable aug.assign ${assign.aug_op} const $hexNumber")
}
return true
}
// non-const value
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when (assign.aug_op) {
"setvalue" -> {
asmgen.out("""
lda $sourceName
sta $targetName
lda $sourceName+1
sta $targetName+1
""")
}
"+=" -> {
asmgen.out("""
lda $targetName
clc
adc $sourceName
sta $targetName
lda $targetName+1
adc $sourceName+1
sta $targetName+1
""")
return true
}
"-=" -> {
asmgen.out("""
lda $targetName
sec
sbc $sourceName
sta $targetName
lda $targetName+1
sbc $sourceName+1
sta $targetName+1
""")
return true
}
else -> {
TODO("variable aug.assign variable")
}
}
return true
}
is DirectMemoryRead -> throw AssemblyError("expected a typecast for assigning memory read byte to word")
is AddressOf -> {
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") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
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("assign to identifier: expected word array source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.out("""
asl a
tay
lda $variablename,y
sta $targetName
lda $variablename+1,y
sta $targetName+1
""")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.FLOAT -> {
if (constNumber != null) {
// assign a constant
val floatConst = asmgen.getFloatConst(constNumber.toDouble())
when (assign.aug_op) {
"setvalue" -> assignFromFloatConstant(assign.target, constNumber.toDouble())
"+=" -> {
if (constNumber == 0.5) {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.MOVFM
jsr c64flt.FADDH
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
} else {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.MOVFM
lda #<$floatConst
ldy #>$floatConst
jsr c64flt.FADD
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
}
return true
}
"-=" -> {
asmgen.out("""
lda #<$floatConst
ldy #>$floatConst
jsr c64flt.MOVFM
lda #<$targetName
ldy #>$targetName
jsr c64flt.FSUB
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
return true
}
else -> TODO("float const value aug.assign $assign")
}
return true
}
// non-const float value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
when (assign.aug_op) {
"setvalue" -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
"+=" -> return false // TODO optimized float += variable
"-=" -> return false // TODO optimized float -= variable
else -> TODO("float non-const value aug.assign $assign")
}
return true
}
is ArrayIndexedExpression -> {
when(assign.aug_op) {
"setvalue" -> {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
if (arrayDt != DataType.ARRAY_F)
throw AssemblyError("assign to identifier: expected float array source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.out("""
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
tay
lda $variablename,y
sta $targetName
lda $variablename+1,y
sta $targetName+1
lda $variablename+2,y
sta $targetName+2
lda $variablename+3,y
sta $targetName+3
lda $variablename+4,y
sta $targetName+4
""")
}
else -> TODO("float $assign")
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.STR -> {
val identifier = assign.value as? IdentifierReference
?: throw AssemblyError("string value assignment expects identifier value")
val sourceName = asmgen.asmIdentifierName(identifier)
asmgen.out("""
lda #<$targetName
sta ${C64Zeropage.SCRATCH_W1}
lda #>$targetName
sta ${C64Zeropage.SCRATCH_W1+1}
lda #<$sourceName
ldy #>$sourceName
jsr prog8_lib.strcpy
""")
return true
}
else -> throw AssemblyError("assignment to identifier: invalid target datatype: $targetType")
}
return false
}
private fun fallbackAssignment(assign: Assignment) {
if (assign.aug_op != "setvalue") {
/* stack-based evaluation of the expression is required */
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
} else {
when (assign.value) {
is FunctionCall -> {
// TODO is there a way to avoid function return value being passed via the stack?
// for instance, 1 byte return value always in A, etc
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
else -> {
/* stack-based evaluation of the expression is required */
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
}
}
}
}
***/
@@ -103,9 +103,9 @@ val BuiltinFunctions = mapOf(
FParam("length", setOf(DataType.UBYTE))), null)
)
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
@@ -274,22 +274,16 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.STR -> {
val refLv = target.value as StringLiteralValue
if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
@@ -312,7 +306,7 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position)
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
}
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@@ -320,7 +314,7 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position)
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@@ -328,7 +322,7 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position)
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
}
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@@ -336,7 +330,7 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position)
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@@ -375,7 +369,7 @@ private fun builtinSgn(args: List<Expression>, position: Position, program: Prog
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position)
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
}
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
@@ -1,157 +0,0 @@
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
private val noModifications = emptyList<IAstModification>()
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 noModifications
}
}
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 noModifications
}
}
@@ -179,7 +179,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@@ -6,7 +6,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
@@ -24,12 +23,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private val noModifications = emptyList<IAstModification>()
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if (assignment.aug_op != null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
+1 -1
View File
@@ -10,7 +10,7 @@ internal fun Program.constantFold(errors: ErrorReporter) {
if(errors.isEmpty()) {
replacer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this, errors)
val optimizer = ConstantFoldingOptimizer(this)
optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
@@ -14,11 +14,6 @@ import prog8.functions.BuiltinFunctions
import kotlin.math.floor
/*
TODO: remove unreachable code after return and exit()
*/
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() {
@@ -105,32 +100,34 @@ internal class StatementOptimizer(private val program: Program,
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
val pos = functionCallStatement.position
if(string.value.length==1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if(string.value.length==2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout1)
anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
val string = vardecl.value as? StringLiteralValue
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout1)
anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
}
}
}
}
@@ -211,7 +208,7 @@ internal class StatementOptimizer(private val program: Program,
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position))
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -226,7 +223,7 @@ internal class StatementOptimizer(private val program: Program,
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, byte, forLoop.position))
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -239,7 +236,7 @@ internal class StatementOptimizer(private val program: Program,
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@@ -255,9 +252,9 @@ internal class StatementOptimizer(private val program: Program,
val constvalue = untilLoop.untilCondition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no continue and break statements)
// always true -> keep only the statement block (if there are no break statements)
errors.warn("condition is always true", untilLoop.untilCondition.position)
if(!hasContinueOrBreak(untilLoop.body))
if(!hasBreak(untilLoop.body))
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
} else {
// always false
@@ -296,8 +293,10 @@ internal class StatementOptimizer(private val program: Program,
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
}
if (iterations == 1)
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent))
}
}
return noModifications
}
@@ -324,21 +323,73 @@ internal class StatementOptimizer(private val program: Program,
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// remove assignments to self
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs assignment.target) {
val rExpr = binExpr.right as? BinaryExpression
if(rExpr!=null) {
val op1 = binExpr.operator
val op2 = rExpr.operator
if(rExpr.left is NumericLiteralValue && op2 in setOf("+", "*", "&", "|")) {
// associative operator, make sure the constant numeric value is second (right)
return listOf(IAstModification.SwapOperands(rExpr))
}
val rNum = (rExpr.right as? NumericLiteralValue)?.number
if(rNum!=null) {
if (op1 == "+" || op1 == "-") {
if (op2 == "+") {
// A = A +/- B + N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val addConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val subConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, parent))
}
}
}
}
}
if(binExpr.right isSameAs assignment.target) {
if(binExpr.operator in setOf("+", "*", "&", "|")) {
// associative operator, swap the operands so that the assignment target is first (left)
return listOf(IAstModification.SwapOperands(binExpr))
}
}
}
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace))
return listOf(IAstModification.Remove(assignment, parent))
// remove assignment to self
return listOf(IAstModification.Remove(assignment, parent))
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
@@ -441,7 +492,7 @@ internal class StatementOptimizer(private val program: Program,
return linesToRemove
}
private fun hasContinueOrBreak(scope: INameScope): Boolean {
private fun hasBreak(scope: INameScope): Boolean {
class Searcher: IAstVisitor
{
@@ -450,10 +501,6 @@ internal class StatementOptimizer(private val program: Program,
override fun visit(breakStmt: Break) {
count++
}
override fun visit(contStmt: Continue) {
count++
}
}
val s=Searcher()
@@ -6,6 +6,9 @@ import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block
/*
TODO: remove unreachable code after return and exit()
*/
internal class UnusedCodeRemover: AstWalker() {
+1 -1
View File
@@ -34,7 +34,7 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal class ModuleImporter(private val errors: ErrorReporter) {
internal class ModuleImporter() {
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
+1 -1
View File
@@ -55,7 +55,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
repeat {
ubyte prime = find_next_prime()
if prime==0
break
+12
View File
@@ -257,6 +257,16 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
**Arrays at a specific memory location:**
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
&ubyte[5*40] top5screenrows = $0400
This way you can set the second character on the second row from the top like this::
top5screenrows[41] = '!'
Strings
^^^^^^^
@@ -397,6 +407,8 @@ The *repeat* loop is used as a short notation of a for loop where the loop varia
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
Breaking out of a loop prematurely is possible with the ``break`` statement.
.. attention::
The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
after the loop without first assigning a new value to it!
+2 -4
View File
@@ -306,6 +306,7 @@ should be allocated by the compiler. Instead, the (mandatory) value assigned to
should be the *memory address* where the value is located::
&byte BORDERCOLOR = $d020
&ubyte[5*40] top5screenrows = $0400 ; works for array as well
Direct access to memory locations
@@ -404,7 +405,7 @@ assignment: ``=``
Note that an assignment sometimes is not possible or supported.
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
Syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
This is syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
postfix increment and decrement: ``++`` ``--``
Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
@@ -559,7 +560,6 @@ You can use a single statement, or a statement block like in the example below::
for <loopvar> in <expression> [ step <amount> ] {
; do something...
break ; break out of the loop
continue ; immediately enter next iteration
}
For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers::
@@ -592,7 +592,6 @@ You can use a single statement, or a statement block like in the example below::
while <condition> {
; do something...
break ; break out of the loop
continue ; immediately enter next iteration
}
@@ -605,7 +604,6 @@ You can use a single statement, or a statement block like in the example below::
do {
; do something...
break ; break out of the loop
continue ; immediately enter next iteration
} until <condition>
+4 -3
View File
@@ -2,9 +2,10 @@
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)
- optimize assignment codegeneration
- get rid of all TODO's ;-)
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' ?
- investigate support for 8bitguy's Commander X16 platform https://www.commanderx16.com and https://github.com/commanderx16/x16-docs
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
+3 -2
View File
@@ -37,8 +37,9 @@ sub start() {
}
sub delay() {
repeat 32 {
while c64.RASTER {
repeat 8 {
ubyte jiffy = c64.TIME_LO
while c64.TIME_LO==jiffy {
}
}
}
-3
View File
@@ -1,9 +1,6 @@
%import c64lib
%import c64graphics
; TODO fix compiler errors when compiling without optimizations
main {
sub start() {
-3
View File
@@ -7,9 +7,6 @@
; NOTE: this will take an eternity to draw on a real c64.
; even in Vice in warp mode (700% speed on my machine) it's slow, but you can see progress
; TODO fix compiler errors when compiling without optimizations
main {
const ubyte width = 255
const ubyte height = 200
-3
View File
@@ -1,9 +1,6 @@
%import c64utils
%zeropage basicsafe
; TODO fix compiler errors when compiling without optimization ( /= )
main {
struct Color {
+17 -22
View File
@@ -2,45 +2,40 @@
%import c64utils
%import c64flt
%zeropage basicsafe
%option enable_floats
main {
sub start() {
uword count=0
count=0
repeat 10 {
c64.CHROUT('*')
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
ubyte ub = 9
repeat ub {
c64.CHROUT('*')
count=0
repeat 255 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
repeat 320 {
c64.CHROUT('+')
count=0
repeat 256 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
uword uw = 320
repeat uw {
c64.CHROUT('-')
count=0
repeat 40000 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
ub = 7
repeat ub+2 {
c64.CHROUT('*')
}
c64.CHROUT('\n')
uw = 318
repeat uw+2 {
c64.CHROUT('*')
}
c64.CHROUT('\n')
}
}
-2
View File
@@ -3,8 +3,6 @@
%import c64graphics
%option enable_floats
; TODO fix compiler errors when compiling without optimizations
main {
sub start() {
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+2
View File
@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
Vendored
+4
View File
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-3
View File
@@ -98,7 +98,6 @@ statement :
| repeatloop
| whenstmt
| breakstmt
| continuestmt
| labeldef
;
@@ -214,8 +213,6 @@ returnstmt : 'return' expression? ;
breakstmt : 'break';
continuestmt: 'continue';
identifier : NAME ;
scoped_identifier : NAME ('.' NAME)* ;
+4
View File
@@ -47,3 +47,7 @@ sourceSets {
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.1.1'
}