Compare commits

...
22 Commits
Author SHA1 Message Date
Irmen de Jong 237511f2d6 v2.4 2020-07-04 18:56:47 +02:00
Irmen de Jong cdcb652033 optimized arg passing if all args are registers 2020-07-04 18:56:30 +02:00
Irmen de Jong 71e678b382 fixed possible register subroutine arg clobbering 2020-07-04 17:05:36 +02:00
Irmen de Jong 3050156325 reverted subroutine inlining, it was a mistake 2020-07-04 01:02:36 +02:00
Irmen de Jong 4bfdbad2e4 added mandel gfx to examples 2020-07-03 23:56:36 +02:00
Irmen de Jong 06137ecdc4 v2.3 2020-07-03 23:51:27 +02:00
Irmen de Jong d89f5b0df8 todo about fixing argclobbering 2020-07-03 23:49:17 +02:00
Irmen de Jong b6e2b36692 refactor 2020-07-03 23:37:38 +02:00
Irmen de Jong a6d789cfbc fixed function argument type cast bug 2020-07-03 17:24:43 +02:00
Irmen de Jong c07907e7bd fixed missing shifts codegen 2020-07-02 21:28:48 +02:00
Irmen de Jong 7d8496c874 fixed missing shifts codegen 2020-07-02 19:18:47 +02:00
Irmen de Jong 164ac56db1 compiler error todos 2020-07-01 22:31:38 +02:00
Irmen de Jong fdddb8ca64 slight optimization 2020-07-01 22:23:46 +02:00
Irmen de Jong a9d4b8b0fa fixed ast modifications on node arrays, in particular function call parameter lists 2020-07-01 22:03:54 +02:00
Irmen de Jong ec7b9f54c2 subroutine inlining is an optimizer step 2020-07-01 12:41:10 +02:00
Irmen de Jong 307558a7e7 removed some double code related to call tree 2020-06-30 20:42:55 +02:00
Irmen de Jong febf423eab tehtriz compilation issues 2020-06-30 20:42:13 +02:00
Irmen de Jong a999c23014 simple subroutine inlining added 2020-06-27 17:03:03 +02:00
Irmen de Jong 69f1ade595 gfx mandelbrot example added 2020-06-18 01:35:24 +02:00
Irmen de Jong b166576e54 comments 2020-06-17 23:27:54 +02:00
Irmen de Jong ee2ba5f398 some more optimizations for swap() function call asm code generation 2020-06-17 22:40:57 +02:00
Irmen de Jong cb9825484d some more optimized in-array assignments codegeneration 2020-06-17 21:41:38 +02:00
37 changed files with 810 additions and 511 deletions

No files matched your search

+15
View File
@@ -781,3 +781,18 @@ set_array_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.
.pend
swap_floats .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (c64.SCRATCH_ZPWORD1),y
pha
lda (c64.SCRATCH_ZPWORD2),y
sta (c64.SCRATCH_ZPWORD1),y
pla
sta (c64.SCRATCH_ZPWORD2),y
dey
bpl -
rts
.pend
+1 -1
View File
@@ -1 +1 @@
2.2
2.4
+3 -2
View File
@@ -155,6 +155,7 @@ interface INameScope {
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoVars() = statements.all { it !is VarDecl }
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: Statement) {
@@ -229,7 +230,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = modules.indexOf(node)
val idx = modules.withIndex().find { it.value===node }!!.index
modules[idx] = replacement
replacement.parent = this
}
@@ -256,7 +257,7 @@ class Module(override val name: String,
override fun definingScope(): INameScope = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Statement && replacement is Statement)
val idx = statements.indexOf(node)
val idx = statements.withIndex().find { it.value===node }!!.index
statements[idx] = replacement
replacement.parent = this
}
+9 -4
View File
@@ -6,7 +6,6 @@ import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
@@ -32,6 +31,11 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
caster.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
fixer.visit(this)
}
internal fun Program.transformAssignments(errors: ErrorReporter) {
val transform = AssignmentTransformer(this, errors)
transform.visit(this)
@@ -71,7 +75,8 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) {
}
}
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndNopRemover()
flattener.visit(this)
internal fun Program.variousCleanups() {
val process = VariousCleanups()
process.visit(this)
process.applyModifications()
}
@@ -22,7 +22,7 @@ sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun referencesIdentifiers(vararg name: String): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(other: Expression): Boolean {
@@ -529,7 +529,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
val idx = value.indexOf(node)
val idx = value.withIndex().find { it.value===node }!!.index
value[idx] = replacement
replacement.parent = this
}
@@ -731,6 +731,8 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
override fun linkParents(parent: Node) {
this.parent = parent
}
@@ -798,7 +800,7 @@ class FunctionCall(override var target: IdentifierReference,
if(node===target)
target=replacement as IdentifierReference
else {
val idx = args.indexOf(node)
val idx = args.withIndex().find { it.value===node }!!.index
args[idx] = replacement as Expression
}
replacement.parent = this
@@ -326,7 +326,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(repeatLoop: RepeatLoop) {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
@@ -334,7 +334,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", whileLoop.condition.position)
@@ -463,7 +463,6 @@ internal class AstChecker(private val program: Program,
}
// the initializer value can't refer to the variable itself (recursive definition)
// TODO use callgraph for check?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
err("recursive var declaration")
}
@@ -733,7 +732,7 @@ internal class AstChecker(private val program: Program,
}
"**" -> {
if(leftDt in IntegerDatatypes)
errors.err("power operator requires floating point", expr.position)
errors.err("power operator requires floating point operands", expr.position)
}
"and", "or", "xor" -> {
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
@@ -10,6 +10,46 @@ import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource == listOf("swap")) {
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
// otherwise:
// rewrite swap(x,y) as follows:
// - declare a temp variable of the same datatype
// - temp = x, x = y, y= temp
val first = functionCallStatement.args[0]
val second = functionCallStatement.args[1]
if(first !is IdentifierReference && second !is IdentifierReference) {
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
val tempvar = IdentifierReference(listOf(tempname), first.position)
val assignTemp = Assignment(
AssignTarget(null, 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
)
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
}
}
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
@@ -43,13 +83,16 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
IAstModification.InsertFirst(vardecl, subroutine)
}
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
IAstModification.InsertFirst(vardecl, subroutine)
}
}
}
}
@@ -104,11 +147,11 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null && litval2!=array) {
val vardecl = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl, array.definingScope() as Node)
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
)
}
}
@@ -52,7 +52,7 @@ interface IAstModification {
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOf(after)+1
val idx = parent.statements.withIndex().find { it.value===after }!!.index + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
@@ -87,7 +87,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
return when(val sub = call.target.targetStatement(scope)) {
val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
@@ -96,48 +98,55 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
val requiredType = arg.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
return listOf(IAstModification.ReplaceNode(
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
call as Node))
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
return listOf(IAstModification.ReplaceNode(
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node))
}
}
}
}
emptyList()
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
for (arg in func.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
return listOf(IAstModification.ReplaceNode(
call as Node)
} else if(arg.second.value is NumericLiteralValue) {
try {
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType)
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node))
castedValue,
call as Node)
} catch (x: ExpressionError) {
// no cast possible
}
}
}
}
}
emptyList()
}
null -> emptyList()
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
for (arg in func.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node)
}
}
}
}
}
null -> { }
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
return modifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
@@ -0,0 +1,43 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, value, parent))
}
return noModifications
}
}
@@ -0,0 +1,42 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall)
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope())
override fun visit(functionCallStatement: FunctionCallStatement)
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope())
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")
}
}
else -> {}
}
}
}
@@ -29,8 +29,6 @@ sealed class Statement : Node {
return scope.joinToString(".")
}
abstract val expensiveToInline: Boolean
fun definingBlock(): Block {
if(this is Block)
return this
@@ -48,7 +46,6 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override val expensiveToInline = false
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
@@ -59,8 +56,6 @@ class Block(override val name: String,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override fun linkParents(parent: Node) {
this.parent = parent
@@ -69,7 +64,7 @@ class Block(override val name: String,
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
val idx = statements.withIndex().find { it.value===node }!!.index
statements[idx] = replacement
replacement.parent = this
}
@@ -86,7 +81,6 @@ class Block(override val name: String,
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
@@ -109,7 +103,6 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
data class Label(val name: String, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
@@ -126,7 +119,6 @@ data class Label(val name: String, override val position: Position) : Statement(
open class Return(var value: Expression?, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = value!=null && value !is NumericLiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
@@ -158,7 +150,6 @@ class ReturnFromIrq(override val position: Position) : Return(null, position) {
class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
@@ -171,7 +162,6 @@ class Continue(override val position: Position) : Statement() {
class Break(override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
@@ -207,9 +197,6 @@ open class VarDecl(val type: VarDeclType,
var structHasBeenFlattened = false // set later
private set
override val expensiveToInline
get() = value!=null && value !is NumericLiteralValue
// prefix for literal values that are turned into a variable on the heap
companion object {
@@ -339,8 +326,6 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is NumericLiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
@@ -509,7 +494,6 @@ data class AssignTarget(val register: Register?,
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
@@ -535,7 +519,6 @@ class Jump(val address: Int?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
@@ -556,8 +539,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
val void: Boolean,
override val position: Position) : Statement(), IFunctionCall {
override lateinit var parent: Node
override val expensiveToInline
get() = args.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
@@ -569,7 +550,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
if(node===target)
target = replacement as IdentifierReference
else {
val idx = args.indexOf(node)
val idx = args.withIndex().find { it.value===node }!!.index
args[idx] = replacement as Expression
}
replacement.parent = this
@@ -585,7 +566,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
@@ -600,8 +580,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() {
override val name: String
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
companion object {
private var sequenceNumber = 1
@@ -619,7 +597,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
val idx = statements.withIndex().find { it.value===node }!!.index
statements[idx] = replacement
replacement.parent = this
}
@@ -630,7 +608,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
class NopStatement(override val position: Position): Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
@@ -664,13 +641,7 @@ class Subroutine(override val name: String,
override val position: Position) : Statement(), INameScope {
var keepAlways: Boolean = false
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override lateinit var parent: Node
val calledBy = mutableListOf<Node>()
val calls = mutableSetOf<Subroutine>()
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
@@ -681,7 +652,7 @@ class Subroutine(override val name: String,
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
val idx = statements.withIndex().find { it.value===node }!!.index
statements[idx] = replacement
replacement.parent = this
}
@@ -700,6 +671,32 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
fun countStatements(): Int {
class StatementCounter: IAstVisitor {
var count = 0
override fun visit(block: Block) {
count += block.statements.size
super.visit(block)
}
override fun visit(subroutine: Subroutine) {
count += subroutine.statements.size
super.visit(subroutine)
}
override fun visit(scope: AnonymousScope) {
count += scope.statements.size
super.visit(scope)
}
}
// the (recursive) number of statements
val counter = StatementCounter()
counter.visit(this)
return counter.count
}
}
@@ -722,8 +719,6 @@ class IfStatement(var condition: Expression,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
@@ -752,8 +747,6 @@ class BranchStatement(var condition: BranchCondition,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
@@ -781,7 +774,6 @@ class ForLoop(val loopRegister: Register?,
var body: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent=parent
@@ -818,7 +810,6 @@ class WhileLoop(var condition: Expression,
var body: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
@@ -841,7 +832,6 @@ class WhileLoop(var condition: Expression,
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
@@ -862,7 +852,6 @@ class RepeatLoop(var body: AnonymousScope,
var untilCondition: Expression,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
@@ -887,7 +876,6 @@ class WhenStatement(var condition: Expression,
var choices: MutableList<WhenChoice>,
override val position: Position): Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) {
this.parent = parent
@@ -899,7 +887,7 @@ class WhenStatement(var condition: Expression,
if(node===condition)
condition = replacement as Expression
else {
val idx = choices.indexOf(node)
val idx = choices.withIndex().find { it.value===node }!!.index
choices[idx] = replacement as WhenChoice
}
replacement.parent = this
@@ -957,7 +945,6 @@ class StructDecl(override val name: String,
override val position: Position): Statement(), INameScope {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) {
this.parent = parent
@@ -966,7 +953,7 @@ class StructDecl(override val name: String,
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
val idx = statements.withIndex().find { it.value===node }!!.index
statements[idx] = replacement
replacement.parent = this
}
+4 -3
View File
@@ -42,7 +42,7 @@ fun compileProgram(filepath: Path,
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst) // TODO
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
@@ -146,10 +146,10 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
@@ -179,11 +179,12 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.removeNopsFlattenAnonScopes()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
errors.handle()
programAst.verifyFunctionArgTypes()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
@@ -691,7 +691,6 @@ internal class AsmGen(private val program: Program,
loopEndLabels.push(endLabel)
loopContinueLabels.push(whileLabel)
out(whileLabel)
// TODO optimize for the simple cases, can we avoid stack use?
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
@@ -720,7 +719,6 @@ internal class AsmGen(private val program: Program,
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
out(repeatLabel)
// TODO optimize this for the simple cases, can we avoid stack use?
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
@@ -46,7 +46,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
}
// TODO this is the FALLBACK:
// 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)
@@ -91,7 +91,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
} else {
TODO()
TODO("aug assignment to element in array/string")
}
return true
}
@@ -155,45 +155,120 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> return false // TODO optimize?
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")
TODO("assign address into array $assign")
}
is DirectMemoryRead -> {
TODO("$assign")
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
if(valueArrayIndex is RegisterExpr || arrayIndex is RegisterExpr) {
throw AssemblyError("cannot generate code for array operations with registers as index")
}
val valueVariablename = asmgen.asmIdentifierName(valueArrayExpr.identifier)
val valueDt = valueArrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
when(arrayDt) {
DataType.ARRAY_UB -> {
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
throw AssemblyError("assignment to array: expected byte array or string")
if (assign.aug_op == "setvalue") {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${valueArrayIndex.number.toHex()}")
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")
} else {
return false // TODO optimize
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
}
DataType.ARRAY_B -> TODO()
DataType.ARRAY_UW -> TODO()
DataType.ARRAY_W -> TODO()
DataType.ARRAY_F -> TODO()
else -> throw AssemblyError("assignment to array: invalid array dt")
}
return true
@@ -219,13 +294,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"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("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> 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("%=")
"%=" -> 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}")
@@ -258,9 +332,8 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1} | lda $hexAddr | sec | sbc ${C64Zeropage.SCRATCH_B1} | sta $hexAddr")
}
}
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("membyte /= register")
"*=" -> TODO("membyte *= register")
"&=" -> {
when ((assign.value as RegisterExpr).register) {
Register.A -> asmgen.out(" and $hexAddr | sta $hexAddr")
@@ -282,7 +355,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
Register.Y -> asmgen.out(" tya | eor $hexAddr | sta $hexAddr")
}
}
"%=" -> TODO("%=")
"%=" -> TODO("membyte %= register")
"<<=" -> 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}")
@@ -291,15 +364,22 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
TODO("$assign")
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()
asmgen.out(" lda $memory | sta $hexAddr")
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $memory | sta $hexAddr")
else -> TODO("membyte aug.assign memread $assign")
}
return true
}
is ArrayIndexedExpression -> {
TODO("$assign")
TODO("membyte = array value $assign")
}
is AddressOf -> throw AssemblyError("can't assign address to byte")
else -> {
@@ -312,7 +392,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
private fun inplaceAssignToNonConstMemoryByte(assign: Assignment): Boolean {
// target address is not constant, so evaluate it on the stack
// target address is not constant, so evaluate it from the stack
asmgen.translateExpression(assign.target.memoryAddress!!.addressExpression)
asmgen.out("""
inx
@@ -330,13 +410,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"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("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> 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("%=")
"%=" -> 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}")
@@ -370,9 +449,8 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
Register.Y -> asmgen.out(" tya | ldy #0 | sta ${C64Zeropage.SCRATCH_B1} | lda (${C64Zeropage.SCRATCH_W1}),y | sec | sbc ${C64Zeropage.SCRATCH_B1} | sta (${C64Zeropage.SCRATCH_W1}),y")
}
}
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("membyte /= register")
"*=" -> TODO("membyte *= register")
"&=" -> {
when ((assign.value as RegisterExpr).register) {
Register.A -> asmgen.out(" ldy #0 | and (${C64Zeropage.SCRATCH_W1}),y| sta (${C64Zeropage.SCRATCH_W1}),y")
@@ -394,7 +472,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
Register.Y -> asmgen.out(" tya | ldy #0 | eor (${C64Zeropage.SCRATCH_W1}),y | sta (${C64Zeropage.SCRATCH_W1}),y")
}
}
"%=" -> TODO("%=")
"%=" -> TODO("membyte %= register")
"<<=" -> 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}")
@@ -403,13 +481,10 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
TODO("$assign")
}
is AddressOf -> {
TODO("$assign")
TODO("membyte = variable $assign")
}
is DirectMemoryRead -> {
TODO("$assign")
TODO("membyte = memread $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
@@ -436,6 +511,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
return true
}
is AddressOf -> throw AssemblyError("can't assign memory address to memory byte")
else -> {
fallbackAssignment(assign)
return true
@@ -459,13 +535,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"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("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> 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("%=")
"%=" -> 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}")
@@ -485,7 +560,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
Register.Y -> asmgen.out(" sty $targetName")
}
}
else -> TODO("$assign")
else -> TODO("aug.assign variable = register $assign")
}
return true
}
@@ -495,13 +570,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"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("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> 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("%=")
"%=" -> 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}")
@@ -509,7 +583,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
return true
}
is DirectMemoryRead -> {
TODO("$assign")
TODO("variable = memory read $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
@@ -572,7 +646,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
sta $targetName+1
""")
}
else -> TODO("$assign")
else -> TODO("variable aug.assign ${assign.aug_op} const $hexNumber")
}
return true
}
@@ -617,7 +691,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
return true
}
else -> {
TODO("$assign")
TODO("variable aug.assign variable")
}
}
return true
@@ -714,7 +788,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
""")
return true
}
else -> TODO("$assign")
else -> TODO("float const value aug.assign $assign")
}
return true
}
@@ -727,7 +801,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
"+=" -> return false // TODO optimized float += variable
"-=" -> return false // TODO optimized float -= variable
else -> TODO("$assign")
else -> TODO("float non-const value aug.assign $assign")
}
return true
}
@@ -763,7 +837,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
sta $targetName+4
""")
}
else -> TODO("$assign")
else -> TODO("float $assign")
}
return true
}
@@ -803,13 +877,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" lda #$hexValue")
"+=" -> asmgen.out(" clc | adc #$hexValue")
"-=" -> asmgen.out(" sec | sbc #$hexValue")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("A /= const $hexValue")
"*=" -> TODO("A *= const $hexValue")
"&=" -> asmgen.out(" and #$hexValue")
"|=" -> asmgen.out(" ora #$hexValue")
"^=" -> asmgen.out(" eor #$hexValue")
"%=" -> TODO("%=")
"%=" -> TODO("A %= 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}")
@@ -820,13 +893,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" ldx #$hexValue")
"+=" -> asmgen.out(" txa | clc | adc #$hexValue | tax")
"-=" -> asmgen.out(" txa | sec | sbc #$hexValue | tax")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("X /= const $hexValue")
"*=" -> TODO("X *= const $hexValue")
"&=" -> asmgen.out(" txa | and #$hexValue | tax")
"|=" -> asmgen.out(" txa | ora #$hexValue | tax")
"^=" -> asmgen.out(" txa | eor #$hexValue | tax")
"%=" -> TODO("%=")
"%=" -> TODO("X %= 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}")
@@ -837,13 +909,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" ldy #$hexValue")
"+=" -> asmgen.out(" tya | clc | adc #$hexValue | tay")
"-=" -> asmgen.out(" tya | sec | sbc #$hexValue | tay")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("Y /= const $hexValue")
"*=" -> TODO("Y *= const $hexValue")
"&=" -> asmgen.out(" tya | and #$hexValue | tay")
"|=" -> asmgen.out(" tya | ora #$hexValue | tay")
"^=" -> asmgen.out(" tya | eor #$hexValue | tay")
"%=" -> TODO("%=")
"%=" -> TODO("Y %= 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}")
@@ -863,13 +934,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" lda $sourceName")
"+=" -> asmgen.out(" clc | adc $sourceName")
"-=" -> asmgen.out(" sec | sbc $sourceName")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("A /= variable")
"*=" -> TODO("A *= variable")
"&=" -> asmgen.out(" and $sourceName")
"|=" -> asmgen.out(" ora $sourceName")
"^=" -> asmgen.out(" eor $sourceName")
"%=" -> TODO("%=")
"%=" -> TODO("A %= 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}")
@@ -880,13 +950,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" ldx $sourceName")
"+=" -> asmgen.out(" txa | clc | adc $sourceName | tax")
"-=" -> asmgen.out(" txa | sec | sbc $sourceName | tax")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("X /= variable")
"*=" -> TODO("X *= variable")
"&=" -> asmgen.out(" txa | and $sourceName | tax")
"|=" -> asmgen.out(" txa | ora $sourceName | tax")
"^=" -> asmgen.out(" txa | eor $sourceName | tax")
"%=" -> TODO("%=")
"%=" -> TODO("X %= 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}")
@@ -897,13 +966,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" ldy $sourceName")
"+=" -> asmgen.out(" tya | clc | adc $sourceName | tay")
"-=" -> asmgen.out(" tya | sec | sbc $sourceName | tay")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("Y /= variable")
"*=" -> TODO("Y *= variable")
"&=" -> asmgen.out(" tya | and $sourceName | tay")
"|=" -> asmgen.out(" tya | ora $sourceName | tay")
"^=" -> asmgen.out(" tya | eor $sourceName | tay")
"%=" -> TODO("%=")
"%=" -> TODO("Y %= 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}")
@@ -918,8 +986,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
when ((assign.value as RegisterExpr).register) {
Register.A -> {
when (assign.target.register!!) {
Register.A -> {
}
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
@@ -927,8 +994,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
Register.X -> {
when (assign.target.register!!) {
Register.A -> asmgen.out(" txa")
Register.X -> {
}
Register.X -> {}
Register.Y -> asmgen.out(" txa | tay")
}
}
@@ -936,8 +1002,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
when (assign.target.register!!) {
Register.A -> asmgen.out(" tya")
Register.X -> asmgen.out(" tya | tax")
Register.Y -> {
}
Register.Y -> {}
}
}
}
@@ -992,9 +1057,8 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
}
}
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("register /= register")
"*=" -> TODO("register *= register")
"&=" -> {
when ((assign.value as RegisterExpr).register) {
Register.A -> {
@@ -1020,9 +1084,9 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
}
}
"|=" -> TODO()
"^=" -> TODO()
"%=" -> TODO("%=")
"|=" -> TODO("register |= register")
"^=" -> TODO("register ^= register")
"%=" -> TODO("register %= register")
"<<=" -> 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}")
@@ -1039,13 +1103,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" lda $hexAddr")
"+=" -> asmgen.out(" clc | adc $hexAddr")
"-=" -> asmgen.out(" sec | sbc $hexAddr")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("A /= memory $hexAddr")
"*=" -> TODO("A *= memory $hexAddr")
"&=" -> asmgen.out(" and $hexAddr")
"|=" -> asmgen.out(" ora $hexAddr")
"^=" -> asmgen.out(" eor $hexAddr")
"%=" -> TODO("%=")
"%=" -> TODO("A %= memory $hexAddr")
"<<=" -> 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}")
@@ -1056,13 +1119,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" ldx $hexAddr")
"+=" -> asmgen.out(" txa | clc | adc $hexAddr | tax")
"-=" -> asmgen.out(" txa | sec | sbc $hexAddr | tax")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("X /= memory $hexAddr")
"*=" -> TODO("X *= memory $hexAddr")
"&=" -> asmgen.out(" txa | and $hexAddr | tax")
"|=" -> asmgen.out(" txa | ora $hexAddr | tax")
"^=" -> asmgen.out(" txa | eor $hexAddr | tax")
"%=" -> TODO("%=")
"%=" -> TODO("X %= memory $hexAddr")
"<<=" -> 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}")
@@ -1073,13 +1135,12 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
"setvalue" -> asmgen.out(" ldy $hexAddr")
"+=" -> asmgen.out(" tya | clc | adc $hexAddr | tay")
"-=" -> asmgen.out(" tya | sec | sbc $hexAddr | tay")
"/=" -> TODO("/=")
"*=" -> TODO("*=")
"**=" -> TODO("**=")
"/=" -> TODO("Y /= memory $hexAddr")
"*=" -> TODO("Y *= memory $hexAddr")
"&=" -> asmgen.out(" tya | and $hexAddr | tay")
"|=" -> asmgen.out(" tya | ora $hexAddr | tay")
"^=" -> asmgen.out(" tya | eor $hexAddr | tay")
"%=" -> TODO("%=")
"%=" -> TODO("Y %= memory $hexAddr")
"<<=" -> 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}")
@@ -1089,9 +1150,6 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
return true
}
}
is AddressOf -> {
TODO("$assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
@@ -1117,6 +1175,7 @@ internal class AssignmentAsmGen(private val program: Program, private val errors
}
return true
}
is AddressOf -> throw AssemblyError("can't load a memory address into a register")
else -> {
fallbackAssignment(assign)
return true
@@ -2,12 +2,8 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.base.WordDatatypes
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
@@ -581,32 +577,32 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
ldy $firstName
lda $secondName
sta $firstName
tya
sta $secondName
sty $secondName
ldy $firstName+1
lda $secondName+1
sta $firstName+1
tya
sta $secondName+1
sty $secondName+1
""")
return
}
if(dt.istype(DataType.FLOAT)) {
TODO("optimized case for swapping 2 float vars-- asm subroutine")
asmgen.out("""
lda #<$firstName
sta ${C64Zeropage.SCRATCH_W1}
lda #>$firstName
sta ${C64Zeropage.SCRATCH_W1+1}
lda #<$secondName
sta ${C64Zeropage.SCRATCH_W2}
lda #>$secondName
sta ${C64Zeropage.SCRATCH_W2+1}
jsr c64flt.swap_floats
""")
return
}
}
// TODO more optimized cases? for instance swapping elements of array vars?
// suboptimal code via the evaluation stack...
asmgen.translateExpression(first)
asmgen.translateExpression(second)
// pop in reverse order
val firstTarget = AssignTarget.fromExpr(first)
val secondTarget = AssignTarget.fromExpr(second)
asmgen.assignFromEvalResult(firstTarget)
asmgen.assignFromEvalResult(secondTarget)
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
throw AssemblyError("no asm generation for swap funccall $fcall")
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
@@ -178,8 +178,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
Register.X -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
Register.X -> asmgen.out(" pha | txa | sta $ESTACK_LO_HEX,x | dex | pla")
Register.Y -> asmgen.out(" pha | tya | sta $ESTACK_LO_HEX,x | dex | pla")
}
}
@@ -240,16 +240,26 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
DataType.UWORD -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
var left = amount
while(left>=7) {
asmgen.out(" jsr math.shift_right_uw_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_right_uw_$amount") // 3-7 (8+ is done via other optimizations)
asmgen.out(" jsr math.shift_right_uw_$left")
}
DataType.WORD -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
var left = amount
while(left>=7) {
asmgen.out(" jsr math.shift_right_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_right_w_$amount") // 3-7 (8+ is done via other optimizations)
asmgen.out(" jsr math.shift_right_w_$left")
}
else -> throw AssemblyError("weird type")
}
@@ -269,11 +279,15 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
else {
if(amount<=2) {
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
} else {
asmgen.out(" jsr math.shift_left_w_$amount") // 3-7 (8+ is done via other optimizations)
var left=amount
while(left>=7) {
asmgen.out(" jsr math.shift_left_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_left_w_$left")
}
return
}
@@ -16,7 +16,7 @@ import prog8.compiler.toHex
import kotlin.math.absoluteValue
// todo choose more efficient comparisons to avoid needless lda's
// todo optimize common case step == 2 / -2
// todo optimize common case when step == 2 or -2
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@@ -339,7 +339,7 @@ $continueLabel inc $loopLabel+1
$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 in such cases
// 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()!!
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
@@ -366,7 +366,7 @@ $counterLabel .byte 0
$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 in such cases
// 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
if(stmt.loopRegister!=null)
throw AssemblyError("can't use register to loop over words")
@@ -410,7 +410,7 @@ $endLabel""")
}
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 in such cases
// 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")
val loopLabel = asmgen.makeLabel("for_loop")
@@ -25,8 +25,45 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.args.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
translateFuncArguments(arg.first, arg.second, sub)
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StructLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
stmt.args.all {it is RegisterExpr} -> {
val argRegisters = stmt.args.map {(it as RegisterExpr).register.toString()}
val paramRegisters = sub.asmParameterRegisters.map { it.registerOrPair?.toString() }
if(argRegisters != paramRegisters) {
// all args are registers but differ from the function params. Can't pass directly, work via stack.
argsViaStackEvaluation(stmt, sub)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
argsViaStackEvaluation(stmt, sub)
}
}
}
}
}
asmgen.out(" jsr $subName")
@@ -35,88 +72,129 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceIDt = value.inferType(program)
if(!sourceIDt.isKnown)
throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a regular variable (not via registers)
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
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")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
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
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
for (regparam in sub.asmParameterRegisters) {
when (regparam.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
null -> {
}
}
} else {
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
when (regparam.statusflag) {
Statusflag.Pc -> asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+ pla
""")
null -> {
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
else -> throw AssemblyError("can only use Carry as status flag parameter")
}
}
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
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")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
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
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
lda $sourceName
beq +
sec
@@ -124,108 +202,108 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
+ clc
+
""")
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
asmgen.out("""
beq +
sec
bcs ++
+ clc
+
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+
+ pla
""")
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
else -> throw AssemblyError("cannot assign to register pair")
}
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
else -> throw AssemblyError("cannot assign to register pair")
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
if(valueDt in PassByReferenceDatatypes) {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
if(sourceDt in PassByReferenceDatatypes) {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
} else {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
} else {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
}
}
}
+20 -29
View File
@@ -24,10 +24,10 @@ private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexO
class CallGraph(private val program: Program) : IAstVisitor {
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>()
@@ -55,17 +55,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
it.importedBy.clear()
it.imports.clear()
it.importedBy.addAll(modulesImportedBy.getValue(it))
it.imports.addAll(modulesImporting.getValue(it))
forAllSubroutines(it) { sub ->
sub.calledBy.clear()
sub.calls.clear()
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
sub.calls.addAll(subroutinesCalling.getValue(sub))
}
it.importedBy.addAll(importedBy.getValue(it))
it.imports.addAll(imports.getValue(it))
}
val rootmodule = program.modules.first()
@@ -85,8 +76,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
val thisModule = directive.definingModule()
if (directive.directive == "%import") {
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
@@ -141,8 +132,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
val otherSub = functionCall.target.targetSubroutine(program.namespace)
if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
}
}
super.visit(functionCall)
@@ -152,8 +143,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
}
}
super.visit(functionCallStatement)
@@ -163,8 +154,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
}
}
super.visit(jump)
@@ -190,14 +181,14 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
}
@@ -209,8 +200,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
}
@@ -1,46 +0,0 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
import prog8.ast.statements.Statement
internal class FlattenAnonymousScopesAndNopRemover: IAstVisitor {
private var scopesToFlatten = mutableListOf<INameScope>()
private val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as Statement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
namescope.statements[idx] = nop
namescope.statements.addAll(idx, scope.statements)
scope.statements.forEach { it.parent = namescope }
visit(nop)
}
}
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(scope: AnonymousScope) {
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}
@@ -16,7 +16,6 @@ import kotlin.math.floor
/*
TODO: remove unreachable code after return and exit()
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
*/
@@ -17,7 +17,7 @@ internal class UnusedCodeRemover: AstWalker() {
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
if (sub !== entrypoint && !sub.keepAlways && (callgraph.calledBy[sub].isNullOrEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
}
}
+2 -2
View File
@@ -5,7 +5,7 @@ TODO
- finalize (most) of the still missing "new" assignment asm code generation
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
- 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
@@ -17,13 +17,13 @@ Add more compiler optimizations to the existing ones.
- more targeted optimizations for assigment asm code, such as the following:
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- remove unreachable code after an exit(), return or goto
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
the program will then rely solely on the values as they are in memory at the time of program startup.
- Also some library routines and code patterns could perhaps be optimized further
- can the parameter passing to subroutines be optimized to avoid copying?
- more optimizations on the language AST level
- more optimizations on the final assembly source level
- note: abandoned subroutine inlining because of problems referencing non-local stuff. Can't move everything around.
Eval stack redesign? (lot of work)
+1 -2
View File
@@ -1,8 +1,7 @@
%import c64utils
;%import c64flt
;%option enable_floats
%zeropage dontuse
main {
sub start() {
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -1,6 +1,7 @@
%import c64lib
%import c64utils
spritedata $2000 {
; this memory block contains the sprite data
; it must start on an address aligned to 64 bytes.
+3
View File
@@ -1,6 +1,9 @@
%import c64lib
%import c64graphics
; TODO fix compiler errors when compiling without optimizations
main {
sub start() {
+54
View File
@@ -0,0 +1,54 @@
%import c64lib
%import c64flt
%import c64graphics
%zeropage floatsafe
; Draw a mandelbrot in graphics mode (the image will be 256 x 200 pixels).
; 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
const ubyte max_iter = 16
sub start() {
graphics.enable_bitmap_mode()
ubyte pixelx
ubyte pixely
for pixely in 0 to height-1 {
float yy = (pixely as float)/0.4/height - 1.0
for pixelx in 0 to width-1 {
float xx = (pixelx as float)/0.3/width - 2.2
float xsquared = 0.0
float ysquared = 0.0
float x = 0.0
float y = 0.0
ubyte iter = 0
while iter<max_iter and xsquared+ysquared<4.0 {
y = x*y*2.0 + yy
x = xsquared - ysquared + xx
xsquared = x*x
ysquared = y*y
iter++
}
if iter & 1 {
graphics.plotx = pixelx
graphics.plot(pixely)
}
}
}
forever {
}
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ main {
float y = 0.0
ubyte iter = 0
while (iter<max_iter and xsquared+ysquared<4.0) {
while iter<max_iter and xsquared+ysquared<4.0 {
y = x*y*2.0 + yy
x = xsquared - ysquared + xx
xsquared = x*x
+3
View File
@@ -1,6 +1,9 @@
%import c64utils
%zeropage basicsafe
; TODO fix compiler errors when compiling ( /= )
main {
struct Color {
+2 -4
View File
@@ -12,10 +12,8 @@ main {
ubyte color
forever {
float x = sin(t)
float y = cos(t*1.1356)
ubyte xx=(x * width/2.2) + width/2.0 as ubyte
ubyte yy=(y * height/2.2) + height/2.0 as ubyte
ubyte xx=(sin(t) * width/2.2) + width/2.0 as ubyte
ubyte yy=(cos(t*1.1356) * height/2.2) + height/2.0 as ubyte
c64scr.setcc(xx, yy, 81, color)
t += 0.08
color++
+2
View File
@@ -7,6 +7,7 @@
; staged speed increase
; some simple sound effects
main {
const ubyte boardOffsetX = 14
@@ -107,6 +108,7 @@ waitkey:
break
}
}
if dropypos>ypos {
ypos = dropypos
sound.blockdrop()
+1 -5
View File
@@ -2,15 +2,11 @@
%import c64utils
%import c64flt
%zeropage basicsafe
%option enable_floats
main {
sub start() {
A=42
}
}
+14 -7
View File
@@ -3,6 +3,7 @@
%import c64graphics
%option enable_floats
; TODO fix compiler errors when compiling without optimizations
main {
@@ -42,26 +43,32 @@ turtle {
c64.SPENA = 1
c64.SP0COL = 5
turtlepos()
update_turtle_sprite()
}
sub turtlepos() {
sub update_turtle_sprite() {
uword xx = xpos as uword
c64.SPXY[0] = lsb(xx) + 12
if msb(xx)
c64.MSIGX = 1
else
c64.MSIGX = 0
c64.MSIGX = msb(xx) > 0
c64.SPXY[1] = lsb(ypos) + 40
}
sub pos(float x, float y) {
if pendown {
graphics.line(xpos as uword, ypos as ubyte, x as uword, y as ubyte)
}
xpos = x
ypos = y
update_turtle_sprite()
}
sub fd(uword length) {
float flen = length as float
float sx = xpos
float sy = ypos
xpos += flen * sin(angle)
ypos -= flen * cos(angle)
turtlepos()
update_turtle_sprite()
if pendown {
graphics.line(sx as uword, lsb(sy), xpos as uword, lsb(ypos))
}