got rid of GoSub ast node and codegen complexity related to that.

sometimes programs get smaller, sometimes bigger.
This commit is contained in:
Irmen de Jong 2022-08-07 02:52:45 +02:00
parent 197081f10d
commit 4644c9b621
23 changed files with 45 additions and 383 deletions

View File

@ -317,7 +317,6 @@ class AsmGen(internal val program: Program,
val (asmLabel, indirect) = getJumpTarget(stmt)
jmp(asmLabel, indirect)
}
is GoSub -> translate(stmt)
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
is Label -> translate(stmt)
is ConditionalBranch -> translate(stmt)
@ -441,15 +440,9 @@ class AsmGen(internal val program: Program,
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
functioncallAsmGen.saveXbeforeCall(functionCall)
internal fun saveXbeforeCall(gosub: GoSub) =
functioncallAsmGen.saveXbeforeCall(gosub)
internal fun restoreXafterCall(functionCall: IFunctionCall) =
functioncallAsmGen.restoreXafterCall(functionCall)
internal fun restoreXafterCall(gosub: GoSub) =
functioncallAsmGen.restoreXafterCall(gosub)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
@ -872,18 +865,6 @@ $repeatLabel lda $counterVar
}
}
private fun translate(gosub: GoSub) {
val tgt = gosub.identifier.targetSubroutine(program)
if(tgt!=null && tgt.isAsmSubroutine) {
// no need to rescue X , this has been taken care of already
out(" jsr ${getJumpTarget(gosub)}")
} else {
saveXbeforeCall(gosub)
out(" jsr ${getJumpTarget(gosub)}")
restoreXafterCall(gosub)
}
}
private fun getJumpTarget(jump: Jump): Pair<String, Boolean> {
val ident = jump.identifier
val label = jump.generatedLabel
@ -903,8 +884,6 @@ $repeatLabel lda $counterVar
}
}
private fun getJumpTarget(gosub: GoSub): String = asmSymbolName(gosub.identifier)
private fun translate(ret: Return, withRts: Boolean=true) {
ret.value?.let { returnvalue ->
val sub = ret.definingSubroutine!!

View File

@ -7,7 +7,10 @@ import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignSource
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
@ -35,17 +38,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun saveXbeforeCall(gosub: GoSub) {
val sub = gosub.identifier.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
}
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
@ -57,17 +49,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun restoreXafterCall(gosub: GoSub) {
val sub = gosub.identifier.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
@ -81,11 +62,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(!isExpression && !sub.isAsmSubroutine) {
if(!optimizeIntArgsViaRegisters(sub))
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
}
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
if (sub.inline && asmgen.options.optimize) {

View File

@ -1,6 +1,9 @@
package prog8tests.vm.helpers
import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
internal object DummyMemsizer : IMemSizer {

View File

@ -1,6 +1,5 @@
package prog8tests.vm
import io.kotest.assertions.fail
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTable

View File

@ -1,15 +1,18 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.UndefinedSymbolError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclType
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.DataType
import prog8.compiler.BuiltinFunctions
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {

View File

@ -106,7 +106,7 @@ class Inliner(val program: Program): AstWalker() {
} else
false
}
is Jump, is GoSub -> true
is Jump -> true
else -> false
}
}
@ -170,15 +170,6 @@ class Inliner(val program: Program): AstWalker() {
return super.before(program)
}
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
val sub = gosub.identifier.targetStatement(program) as? Subroutine
return if(sub==null)
noModifications
else
possibleInlineFcallStmt(sub, gosub, parent)
}
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
if(sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))

View File

@ -87,13 +87,16 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is IFunctionCall) {
val name = getTempRegisterName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
)
val dt = arg.inferType(program)
if(dt.isInteger) {
val name = getTempRegisterName(dt)
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
)
}
}
}
}
@ -244,25 +247,6 @@ class StatementOptimizer(private val program: Program,
return noModifications
}
// NOTE: do NOT remove a jump to the next statement, because this will lead to wrong code when this occurs at the end of a subroutine
// if we want to optimize this away, it can be done later at code generation time.
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
val subroutineParams = gosub.identifier.targetSubroutine(program)?.parameters
if(subroutineParams!=null && subroutineParams.isEmpty()) {
val returnstmt = gosub.nextSibling() as? Return
if(returnstmt!=null && returnstmt.value==null) {
return listOf(
IAstModification.Remove(returnstmt, parent as IStatementContainer),
IAstModification.ReplaceNode(gosub, Jump(null, gosub.identifier, null, gosub.position), parent)
)
}
}
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression

View File

@ -6,11 +6,12 @@ import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.mapError
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.determineGosubArguments
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
@ -43,7 +44,6 @@ class IntermediateAstMaker(val program: Program) {
is Directive -> transform(statement)
is ForLoop -> transform(statement)
is FunctionCallStatement -> transform(statement)
is GoSub -> transform(statement)
is IfElse -> transform(statement)
is InlineAssembly -> transform(statement)
is Jump -> transform(statement)
@ -85,12 +85,6 @@ class IntermediateAstMaker(val program: Program) {
}
private fun transform(srcAssign: Assignment): PtNode {
if(srcAssign.origin==AssignmentOrigin.PARAMETERASSIGN) {
// assignments that are setting the parameters for a function call,
// will be gathered at the GoSub itself later.
return PtNop(srcAssign.position)
}
val assign = PtAssignment(srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(transformExpression(srcAssign.value))
@ -229,28 +223,6 @@ class IntermediateAstMaker(val program: Program) {
return call
}
private fun transform(gosub: GoSub): PtFunctionCall {
// Gather the Goto and any preceding parameter assignments back into a single Function call node.
// (the reason it was split up in the first place, is because the Compiler Ast optimizers
// can then work on any complex expressions that are used as arguments.)
val arguments = determineGosubArguments(gosub)
val parameters = gosub.identifier.targetSubroutine(program)!!.parameters
if(arguments.size != parameters.size)
throw FatalAstException("mismatched number of parameter assignments for function call")
val target = transform(gosub.identifier)
val call = PtFunctionCall(target.targetName, true, DataType.UNDEFINED, gosub.position)
// put arguments in correct order for the parameters
parameters.forEach {
val argument = arguments.getValue(it.name)
call.add(transformExpression(argument))
}
return call
}
private fun transform(srcIf: IfElse): PtIfElse {
val ifelse = PtIfElse(srcIf.position)
ifelse.add(transformExpression(srcIf.condition))

View File

@ -202,16 +202,6 @@ internal class AstChecker(private val program: Program,
super.visit(jump)
}
override fun visit(gosub: GoSub) {
val targetStatement = checkFunctionOrLabelExists(gosub.identifier, gosub)
if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionPlaceholder)
errors.err("can't gosub to a builtin function", gosub.position)
}
super.visit(gosub)
}
override fun visit(block: Block) {
val addr = block.address
if(addr!=null && addr>65535u) {

View File

@ -10,7 +10,6 @@ import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.compiler.printProgram
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {

View File

@ -7,7 +7,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.core.CompilationOptions
import prog8.code.core.Encoding
import prog8.code.core.IErrorReporter
import prog8.code.core.NumericDatatypes
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target

View File

@ -119,12 +119,6 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications
}
override fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> {
if(gosub.parent!==parent)
throw FatalAstException("parent node mismatch at $gosub")
return noModifications
}
override fun before(label: Label, parent: Node): Iterable<IAstModification> {
if(label.parent!==parent)
throw FatalAstException("parent node mismatch at $label")

View File

@ -7,9 +7,6 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.codegen.cpu6502.asmsub6502ArgsEvalOrder
import prog8.codegen.cpu6502.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.BuiltinFunctions
internal class StatementReorderer(val program: Program,
@ -26,7 +23,6 @@ internal class StatementReorderer(val program: Program,
// - 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.).
// - replace subroutine calls (statement) by just assigning the arguments to the parameters and then a GoSub to the routine.
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
@ -387,155 +383,4 @@ internal class StatementReorderer(val program: Program,
)
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)
?: throw FatalAstException("no target for $functionCallStatement")
checkUnusedReturnValues(functionCallStatement, function, errors)
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}
internal fun tryReplaceCallWithGosub(
functionCallStatement: FunctionCallStatement,
parent: Node,
program: Program,
options: CompilationOptions
): Iterable<IAstModification> {
val callee = functionCallStatement.target.targetStatement(program)!!
if(callee is Subroutine) {
if(callee.inline)
return emptyList()
return if(callee.isAsmSubroutine) {
if(options.compTarget.name==VMTarget.NAME)
emptyList()
else
tryReplaceCallAsmSubWithGosub(functionCallStatement, parent, callee)
}
else
tryReplaceCallNormalSubWithGosub(functionCallStatement, parent, callee, program)
}
return emptyList()
}
private fun tryReplaceCallNormalSubWithGosub(call: FunctionCallStatement, parent: Node, callee: Subroutine, program: Program): Iterable<IAstModification> {
val noModifications = emptyList<IAstModification>()
if(callee.parameters.isEmpty()) {
// 0 params -> just GoSub
return listOf(IAstModification.ReplaceNode(call, GoSub(call.target, call.position), parent))
}
if(callee.parameters.size==1) {
if(callee.parameters[0].type in IntegerDatatypes) {
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
return noModifications
}
}
else if(callee.parameters.size==2) {
if(callee.parameters[0].type in ByteDatatypes && callee.parameters[1].type in ByteDatatypes) {
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
return noModifications
}
}
val assignParams =
callee.parameters.zip(call.args).map {
var argumentValue = it.second
val paramIdentifier = IdentifierReference(callee.scopedName + it.first.name, argumentValue.position)
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(argDt in ArrayDatatypes) {
// pass the address of the array instead
if(argumentValue is IdentifierReference)
argumentValue = AddressOf(argumentValue, argumentValue.position)
}
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, AssignmentOrigin.PARAMETERASSIGN, argumentValue.position)
}
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
scope.statements += GoSub(call.target, call.position)
return listOf(IAstModification.ReplaceNode(call, scope, parent))
}
private fun tryReplaceCallAsmSubWithGosub(
call: FunctionCallStatement,
parent: Node,
callee: Subroutine
): Iterable<IAstModification> {
val noModifications = emptyList<IAstModification>()
if(callee.parameters.isEmpty()) {
// 0 params -> just GoSub
val scope = AnonymousScope(mutableListOf(), call.position)
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
scope.statements += GoSub(call.target, call.position)
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
} else if(!asmsub6502ArgsHaveRegisterClobberRisk(call.args, callee.asmParameterRegisters)) {
// No register clobber risk, let the asmgen assign values to the registers directly.
// this is more efficient than first evaluating them to the stack.
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
return noModifications
} else {
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
return makeGosubWithArgsViaCpuStack(call, call.position, parent, callee)
}
}
private fun makeGosubWithArgsViaCpuStack(
call: IFunctionCall,
position: Position,
parent: Node,
callee: Subroutine
): Iterable<IAstModification> {
fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
mutableListOf(IdentifierReference(targetName, position)),
true, position
)
}
fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
val pushvalue = when(dt) {
DataType.UBYTE, DataType.UWORD -> value
in PassByReferenceDatatypes -> value
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
else -> throw FatalAstException("invalid dt $dt $value")
}
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
mutableListOf(pushvalue),
true, position
)
}
val argOrder = asmsub6502ArgsEvalOrder(callee)
val scope = AnonymousScope(mutableListOf(), position)
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), position), mutableListOf(), true, position)
}
argOrder.reversed().forEach {
val arg = call.args[it]
val param = callee.parameters[it]
scope.statements += pushCall(arg, param.type, arg.position)
}
// ... and pop them off again into the registers.
argOrder.forEach {
val param = callee.parameters[it]
val targetName = callee.scopedName + param.name
scope.statements += popCall(targetName, param.type, position)
}
scope.statements += GoSub(call.target, position)
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), position), mutableListOf(), true, position)
}
return listOf(IAstModification.ReplaceNode(call as Node, scope, parent))
}

View File

@ -6,7 +6,8 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
@ -218,9 +219,5 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}

View File

@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.code.core.DataType
@ -286,6 +287,6 @@ class TestSubroutines: FunSpec({
stmts.last() shouldBe instanceOf<Subroutine>()
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough
stmts.dropLast(2).last() shouldBe instanceOf<GoSub>()
stmts.dropLast(2).last() shouldBe instanceOf<IFunctionCall>()
}
})

View File

@ -240,11 +240,6 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
override fun visit(gosub: GoSub) {
output("gosub ")
gosub.identifier.accept(this)
}
override fun visit(ifElse: IfElse) {
output("if ")
ifElse.condition.accept(this)

View File

@ -3,9 +3,10 @@ package prog8.ast
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.InferredTypes
import prog8.ast.statements.*
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.statements.VarDeclType
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.ZeropageWish
@ -60,35 +61,6 @@ fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
}
}
fun determineGosubArguments(gosub: GoSub): Map<String, Expression> {
val parent = gosub.parent as IStatementContainer
val gosubIdx = parent.statements.indexOf(gosub)
val previousNodes = parent.statements.subList(0, gosubIdx).reversed()
val arguments = mutableMapOf<String, Expression>()
for (node in previousNodes) {
if(node !is Assignment || node.origin!=AssignmentOrigin.PARAMETERASSIGN)
break
arguments[node.target.identifier!!.nameInSource.last()] = node.value
}
// instead of just assigning to the parameters, another way is to use push()/pop()
if(previousNodes.isNotEmpty()) {
val first = previousNodes[0] as? IFunctionCall
if(first!=null && (first.target.nameInSource.singleOrNull() in arrayOf("pop", "popw"))) {
val numPops = previousNodes.indexOfFirst { (it as? IFunctionCall)?.target?.nameInSource?.singleOrNull() !in arrayOf("pop", "popw") }
val pops = previousNodes.subList(0, numPops)
val pushes = previousNodes.subList(numPops, numPops+numPops).reversed()
for ((push, pop) in pushes.zip(pops)) {
val name = ((pop as IFunctionCall).args.single() as IdentifierReference).nameInSource.last()
val arg = (push as IFunctionCall).args.single()
arguments[name] = arg
}
}
}
return arguments
}
fun maySwapOperandOrder(binexpr: BinaryExpression): Boolean {
fun ok(expr: Expression): Boolean {
return when(expr) {

View File

@ -584,24 +584,6 @@ class Jump(var address: UInt?,
"Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
// a GoSub is ONLY created internally for calling subroutines, there's no syntax for it in the language
class GoSub(val identifier: IdentifierReference, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = GoSub(identifier.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() =
"GoSub($identifier; pos=$position)"
}
class FunctionCallStatement(override var target: IdentifierReference,
override var args: MutableList<Expression>,
val void: Boolean,
@ -1065,7 +1047,6 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
// Calls to builtin functions will be replaced with this node just before handing the Ast to the codegen.
// this is meant to eventually (?) be able to not have any FunctionCallStatement nodes to worry about anymore
// in the codegen, because they have been converted into GoSub (for instance) or this node.
// However, if/when the codegen is moved over to use the new CodeAst (PtProgram etc. etc.) this is all moot.
class BuiltinFunctionCallStatement(override var target: IdentifierReference,
override var args: MutableList<Expression>,

View File

@ -114,7 +114,6 @@ abstract class AstWalker {
open fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> = noModifications
open fun before(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
@ -157,7 +156,6 @@ abstract class AstWalker {
open fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> = noModifications
open fun after(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
@ -307,12 +305,6 @@ abstract class AstWalker {
track(after(jump, parent), jump, parent)
}
fun visit(gosub: GoSub, parent: Node) {
track(before(gosub, parent), gosub, parent)
gosub.identifier.accept(this, gosub)
track(after(gosub, parent), gosub, parent)
}
fun visit(ifElse: IfElse, parent: Node) {
track(before(ifElse, parent), ifElse, parent)
ifElse.condition.accept(this, ifElse)

View File

@ -70,10 +70,6 @@ interface IAstVisitor {
jump.identifier?.accept(this)
}
fun visit(gosub: GoSub) {
gosub.identifier.accept(this)
}
fun visit(ifElse: IfElse) {
ifElse.condition.accept(this)
ifElse.truepart.accept(this)

View File

@ -100,17 +100,6 @@ class CallGraph(private val program: Program, private val allowMissingIdentifier
super.visit(jump)
}
override fun visit(gosub: GoSub) {
val otherSub = gosub.identifier.targetSubroutine(program)
if (otherSub != null) {
gosub.definingSubroutine?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + gosub
}
}
super.visit(gosub)
}
override fun visit(identifier: IdentifierReference) {
val target = identifier.targetStatement(program)
if(allowMissingIdentifierTargetVarDecls) {

View File

@ -931,7 +931,7 @@ callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
syscall(callnr), syscall1(callnr, arg), syscall2(callnr, arg1, arg2), syscall3(callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a gosub to asmsub kernal routines at
The regular 6502 based compiler targets just use a subroutine call to asmsub kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.

View File

@ -17,7 +17,7 @@ Need help with
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is. This info is needed for more advanced optimizations and later code generation steps.
- vm Instructions needs to know what the read-registers/memory are, and what the write-register/memory is. This info is needed for more advanced optimizations and later code generation steps.
- vm: implement remaining sin/cos functions in math.p8
- vm: find a solution for the cx16.r0..r15 that "overlap" (r0, r0L, r0H etc) but in the vm each get their own separate variable location now
- vm: somehow deal with asmsubs otherwise the vm IR can't fully encode all of prog8
@ -44,9 +44,10 @@ Compiler:
- add special (u)word array type (or modifier?) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing
- ast: don't rewrite by-reference parameter type to uword, but keep the original type (str, array)
BUT that makes the handling of these types different between the scope they are defined in, and the
scope they get passed in by reference... unless we make str and array types by-reference ALWAYS? BUT that
makes simple code accessing them in the declared scope very slow because that then has to always go through
scope they get passed in by reference... unless we make str and array types by-reference ALWAYS?
BUT that makes simple code accessing them in the declared scope very slow because that then has to always go through
the pointer rather than directly referencing the variable symbol in the generated asm....
Or maybe make codegen smart to check if it's a subroutine parameter or local declared variable?
Libraries:
@ -67,7 +68,7 @@ Expressions:
- rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables'?
"Three address code" was mentioned. https://en.wikipedia.org/wiki/Three-address_code
these variables have to be unique for each subroutine because they could otherwise be interfered with from irq routines etc.
The VM IL solves this already (by using unlimited registers) but still lacks a translation to 6502.
The VM IL solves this already (by using unlimited registers) but that still lacks a translation to 6502.
- this removes the need for the BinExprSplitter? (which is problematic and very limited now)
and perhaps the assignment splitting in BeforeAsmAstChanger too