From 03782a37a20924e12200ffa5b816fb6d50bc24f3 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 21 Jul 2019 12:00:22 +0200 Subject: [PATCH] begin of ast-codegen v2 --- compiler/src/prog8/CompilerMain.kt | 8 +- compiler/src/prog8/ast/AstToplevel.kt | 6 +- .../prog8/ast/expressions/AstExpressions.kt | 2 + .../src/prog8/ast/processing/AstChecker.kt | 10 +- .../src/prog8/ast/statements/AstStatements.kt | 2 + compiler/src/prog8/compiler/Compiler.kt | 2 +- compiler/src/prog8/compiler/Main.kt | 12 +- .../intermediate/IntermediateProgram.kt | 2 +- .../compiler/target/c64/codegen/AsmGen.kt | 2 +- .../compiler/target/c64/codegen2/AsmGen2.kt | 825 ++++++++++++++++++ compiler/src/prog8/optimizer/CallGraph.kt | 3 +- examples/test.p8 | 66 +- 12 files changed, 865 insertions(+), 75 deletions(-) create mode 100644 compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 63b294ff2..643560f26 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -38,6 +38,7 @@ private fun compileMain(args: Array) { var optimize = true var optimizeInlining = true var launchAstVm = false + var asm2 = false for (arg in args) { if(arg=="-emu") emulatorToStart = "x64" @@ -53,6 +54,10 @@ private fun compileMain(args: Array) { optimizeInlining = false else if(arg=="-avm") launchAstVm = true + else if(arg=="-asm2") { + writeVmCode = false + asm2 = true + } else if(!arg.startsWith("-")) moduleFile = arg else @@ -64,7 +69,7 @@ private fun compileMain(args: Array) { val filepath = Paths.get(moduleFile).normalize() val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining, - !launchAstVm, writeVmCode, writeAssembly) + !launchAstVm && !asm2, writeVmCode, writeAssembly, asm2) if(launchAstVm) { println("\nLaunching AST-based vm...") @@ -92,6 +97,7 @@ private fun usage() { System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation") System.err.println(" [-writevm] write intermediate vm code to a file as well") System.err.println(" [-noasm] don't create assembly code") + System.err.println(" [-asm2] use new Ast-Asmgen2 (WIP)") System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler") System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation") System.err.println(" [-noopt] don't perform any optimizations") diff --git a/compiler/src/prog8/ast/AstToplevel.kt b/compiler/src/prog8/ast/AstToplevel.kt index 126ab59af..692efb1ad 100644 --- a/compiler/src/prog8/ast/AstToplevel.kt +++ b/compiler/src/prog8/ast/AstToplevel.kt @@ -168,9 +168,11 @@ class Program(val name: String, val modules: MutableList) { val namespace = GlobalNamespace(modules) val heap = HeapValues() - val loadAddress: Int + val definedLoadAddress: Int get() = modules.first().loadAddress + var actualLoadAddress: Int = 0 + fun entrypoint(): Subroutine? { val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block } if(mainBlocks.size > 1) @@ -181,6 +183,8 @@ class Program(val name: String, val modules: MutableList) { mainBlocks[0].subScopes()["start"] as Subroutine? } } + + fun allBlocks(): List = modules.flatMap { it.statements.filterIsInstance() } } class Module(override val name: String, diff --git a/compiler/src/prog8/ast/expressions/AstExpressions.kt b/compiler/src/prog8/ast/expressions/AstExpressions.kt index 5d0d611fd..4d44680e3 100644 --- a/compiler/src/prog8/ast/expressions/AstExpressions.kt +++ b/compiler/src/prog8/ast/expressions/AstExpressions.kt @@ -735,6 +735,8 @@ data class IdentifierReference(val nameInSource: List, override val posi } } + fun memberOfStruct(namespace: INameScope) = this.targetVarDecl(namespace)?.struct + fun heapId(namespace: INameScope): Int { val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value") diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index b5d568610..ff8a7e432 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -631,11 +631,11 @@ internal class AstChecker(private val program: Program, if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block") val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]" if(directive.args.isEmpty()) err(errormsg) - if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg) - if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg) - if(directive.args.size==3 && directive.args[2].int==null) err(errormsg) - if(directive.args.size>3) err(errormsg) - checkFileExists(directive, directive.args[0].str!!) + else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg) + else if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg) + else if(directive.args.size==3 && directive.args[2].int==null) err(errormsg) + else if(directive.args.size>3) err(errormsg) + else checkFileExists(directive, directive.args[0].str!!) } "%option" -> { if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level") diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt index 52e994798..9e0191504 100644 --- a/compiler/src/prog8/ast/statements/AstStatements.kt +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -812,6 +812,8 @@ class StructDecl(override val name: String, override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) + + fun nameOfFirstMember() = (statements.first() as VarDecl).name } class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 0b24cb039..c1e6ed84d 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -151,7 +151,7 @@ data class CompilationOptions(val output: OutputType, internal class Compiler(private val program: Program) { - private val prog: IntermediateProgram = IntermediateProgram(program.name, program.loadAddress, program.heap, program.modules.first().source) + private val prog: IntermediateProgram = IntermediateProgram(program.name, program.definedLoadAddress, program.heap, program.modules.first().source) private var generatedLabelSequenceNumber = 0 private val breakStmtLabelStack : Stack = Stack() private val continueStmtLabelStack : Stack = Stack() diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index d0c66d695..e6bb02984 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -6,6 +6,7 @@ import prog8.ast.base.* import prog8.ast.statements.Directive import prog8.compiler.target.c64.codegen.AsmGen import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.target.c64.codegen2.AsmGen2 import prog8.optimizer.constantFold import prog8.optimizer.optimizeStatements import prog8.optimizer.simplifyExpressions @@ -22,7 +23,7 @@ import kotlin.system.measureTimeMillis fun compileProgram(filepath: Path, optimize: Boolean, optimizeInlining: Boolean, generateVmCode: Boolean, writeVmCode: Boolean, - writeAssembly: Boolean): Pair { + writeAssembly: Boolean, asm2: Boolean): Pair { lateinit var programAst: Program var programName: String? = null @@ -106,7 +107,7 @@ fun compileProgram(filepath: Path, println("StackVM program code written to '$stackVmFilename'") } - if (writeAssembly) { + if (writeAssembly && !asm2) { val zeropage = MachineDefinition.C64Zeropage(compilerOptions) intermediate.allocateZeropage(zeropage) val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize) @@ -114,6 +115,13 @@ fun compileProgram(filepath: Path, programName = assembly.name } } + + if(asm2 && writeAssembly) { + // asm generation directly from the Ast, no need for intermediate code + val assembly = AsmGen2(programAst, compilerOptions, MachineDefinition.C64Zeropage(compilerOptions)).compileToAssembly(optimize) + assembly.assemble(compilerOptions) + programName = assembly.name + } } println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index f12037cf6..a4e77576e 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -34,7 +34,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap val memory = mutableMapOf>() private lateinit var currentBlock: ProgramBlock - fun allocateZeropage(zeropage: Zeropage) { + fun allocateZeropage(zeropage: Zeropage) { // TODO not used anymore??? // allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP) var notAllocated = 0 for(block in blocks) { diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt index 726dc5645..449b7586a 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt @@ -305,7 +305,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null } structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) } - // leave outsort the other variables by type + // sort the other variables by type out("; other variables sorted by type") val sortedVars = normalVars.sortedBy { it.value.type } for (variable in sortedVars) { diff --git a/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt b/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt new file mode 100644 index 000000000..0dbe985bc --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt @@ -0,0 +1,825 @@ +package prog8.compiler.target.c64.codegen2 + +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.base.initvarsSubName +import prog8.ast.expressions.* +import prog8.ast.statements.* +import prog8.compiler.* +import prog8.compiler.target.c64.AssemblyProgram +import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.target.c64.codegen.optimizeAssembly +import java.io.File +import java.util.* + + +internal class AssemblyError(msg: String) : RuntimeException(msg) + + +internal class AsmGen2(val program: Program, + val options: CompilationOptions, + val zeropage: Zeropage) { + + private val assemblyLines = mutableListOf() + private val globalFloatConsts = mutableMapOf() // all float values in the entire program (value -> varname) + private val allocatedZeropageVariables = mutableMapOf>() + private val breakpointLabels = mutableListOf() + + internal fun compileToAssembly(optimize: Boolean): AssemblyProgram { + assemblyLines.clear() + println("Generating assembly code... ") + + header() + val allBlocks = program.allBlocks() + if(allBlocks.first().name != "main") + throw AssemblyError("first block should be 'main'") + for(b in program.allBlocks()) + block2asm(b) + footer() + + if(optimize) { + var optimizationsDone = 1 + while (optimizationsDone > 0) { + optimizationsDone = optimizeAssembly(assemblyLines) + } + } + + File("${program.name}.asm").printWriter().use { + for (line in assemblyLines) { it.println(line) } + } + + return AssemblyProgram(program.name) + } + + private fun header() { + val ourName = this.javaClass.name + out("; 6502 assembly code for '${program.name}'") + out("; generated by $ourName on ${Date()}") + out("; assembler syntax is for the 64tasm cross-assembler") + out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") + out("\n.cpu '6502'\n.enc 'none'\n") + + program.actualLoadAddress = program.definedLoadAddress + if (program.actualLoadAddress == 0) // fix load address + program.actualLoadAddress = if (options.launcher == LauncherType.BASIC) + MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS + + when { + options.launcher == LauncherType.BASIC -> { + if (program.actualLoadAddress != 0x0801) + throw AssemblyError("BASIC output must have load address $0801") + out("; ---- basic program with sys call ----") + out("* = ${program.actualLoadAddress.toHex()}") + val year = Calendar.getInstance().get(Calendar.YEAR) + out(" .word (+), $year") + out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'") + out("+\t.word 0") + out("_prog8_entrypoint\t; assembly code starts here\n") + out(" jsr prog8_lib.init_system") + } + options.output == OutputType.PRG -> { + out("; ---- program without basic sys call ----") + out("* = ${program.actualLoadAddress.toHex()}\n") + out(" jsr prog8_lib.init_system") + } + options.output == OutputType.RAW -> { + out("; ---- raw assembler program ----") + out("* = ${program.actualLoadAddress.toHex()}\n") + } + } + + if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) { + // disable shift-commodore charset switching and run/stop key + out(" lda #$80") + out(" lda #$80") + out(" sta 657\t; disable charset switching") + out(" lda #239") + out(" sta 808\t; disable run/stop key") + } + + out(" ldx #\$ff\t; init estack pointer") + + out(" ; initialize the variables in each block") + for (block in program.allBlocks()) { + val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName } + if(initVarsSub!=null) + out(" jsr ${block.name}.$initvarsSubName") + } + + out(" clc") + when (zeropage.exitProgramStrategy) { + Zeropage.ExitProgramStrategy.CLEAN_EXIT -> { + out(" jmp main.start\t; jump to program entrypoint") + } + Zeropage.ExitProgramStrategy.SYSTEM_RESET -> { + out(" jsr main.start\t; call program entrypoint") + out(" jmp (c64.RESET_VEC)\t; cold reset") + } + } + out("") + } + + private fun footer() { + // the global list of all floating point constants for the whole program + out("; global float constants") + for (flt in globalFloatConsts) { + val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key)) + out("${flt.value}\t.byte $floatFill ; float ${flt.key}") + } + } + + private fun block2asm(block: Block) { + out("\n; ---- block: '${block.name}' ----") + out("${block.name}\t.proc\n") // TODO not if force_output? + if(block.address!=null) { + out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'") + out("* = ${block.address.toHex()}") + } + + zeropagevars2asm(block.statements) + memdefs2asm(block.statements) + vardecls2asm(block.statements) + out("") + + // first translate regular statements, and then put the subroutines at the end. + val (subroutine, stmts) = block.statements.partition { it is Subroutine } + stmts.forEach { translate(it) } + subroutine.forEach { translate(it as Subroutine) } + + out("\n\t.pend\n") // TODO not if force_output? + } + + private fun out(str: String, splitlines: Boolean = true) { + if (splitlines) { + for (line in str.split('\n')) { + val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim() + // trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation + assemblyLines.add(trimmed) + } + } else assemblyLines.add(str) + } + + private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String { + val b0 = "$" + flt.b0.toString(16).padStart(2, '0') + val b1 = "$" + flt.b1.toString(16).padStart(2, '0') + val b2 = "$" + flt.b2.toString(16).padStart(2, '0') + val b3 = "$" + flt.b3.toString(16).padStart(2, '0') + val b4 = "$" + flt.b4.toString(16).padStart(2, '0') + return "$b0, $b1, $b2, $b3, $b4" + } + + private fun zeropagevars2asm(statements: List) { + out("; vars allocated on zeropage") + val variables = statements.filterIsInstance().filter { it.type==VarDeclType.VAR } + for(variable in variables) { + val fullName = variable.scopedname + val zpVar = allocatedZeropageVariables[fullName] + if(zpVar==null) { + // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) + if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE && + variable.datatype in zeropage.allowedDatatypes + && variable.datatype != DataType.FLOAT) { + try { + val address = zeropage.allocate(fullName, variable.datatype, null) + out("${variable.name} = $address\t; auto zp ${variable.datatype}") + // make sure we add the var to the set of zpvars for this block + allocatedZeropageVariables[fullName] = Pair(address, variable.datatype) + } catch (x: ZeropageDepletedError) { + // leave it as it is. + } + } + } + else { + TODO("already allocated on zp?? $zpVar") + // it was already allocated on the zp, what to do? + // out("${variable.name} = ${zpVar.first}\t; zp ${zpVar.second}") + } + } + } + + private fun vardecl2asm(decl: VarDecl) { + when (decl.datatype) { + DataType.UBYTE -> out("${decl.name}\t.byte 0") + DataType.BYTE -> out("${decl.name}\t.char 0") + DataType.UWORD -> out("${decl.name}\t.word 0") + DataType.WORD -> out("${decl.name}\t.sint 0") + DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float") + DataType.STRUCT -> {} // is flattened + DataType.STR -> TODO() + DataType.STR_S -> TODO() + DataType.ARRAY_UB -> { + // unsigned integer byte arraysize + val data = makeArrayFillDataUnsigned(decl) + if (data.size <= 16) + out("${decl.name}\t.byte ${data.joinToString()}") + else { + out(decl.name) + for (chunk in data.chunked(16)) + out(" .byte " + chunk.joinToString()) + } + } + DataType.ARRAY_B -> TODO() + DataType.ARRAY_UW -> TODO() + DataType.ARRAY_W -> TODO() + DataType.ARRAY_F -> TODO() + } + } + + private fun memdefs2asm(statements: List) { + out("\n; memdefs and kernel subroutines") + val memvars = statements.filterIsInstance().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST } + for(m in memvars) { + out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}") + } + val asmSubs = statements.filterIsInstance().filter { it.isAsmSubroutine } + for(sub in asmSubs) { + if(sub.asmAddress!=null) { + if(sub.statements.isNotEmpty()) + throw AssemblyError("kernel subroutine cannot have statements") + out(" ${sub.name} = ${sub.asmAddress.toHex()}") + } + } + } + + private fun vardecls2asm(statements: List) { + out("\n; non-zeropage variables") + val vars = statements.filterIsInstance().filter { it.type==VarDeclType.VAR } + + // first output the flattened struct member variables *in order* + // after that, the other variables sorted by their datatype + + val (structMembers, normalVars) = vars.partition { it.struct!=null } + structMembers.forEach { vardecl2asm(it) } + + normalVars.sortedBy { it.datatype }.forEach { + if(it.scopedname !in allocatedZeropageVariables) + vardecl2asm(it) + } + } + + private fun makeArrayFillDataUnsigned(decl: VarDecl): List { + val array = (decl.value as ReferenceLiteralValue).array!! + return array.map { (it as NumericLiteralValue).number.toString() } + } + + private fun getFloatConst(number: Double): String { + val name = globalFloatConsts[number] + if(name!=null) + return name + val newName = "prog8_float_const_${globalFloatConsts.size}" + globalFloatConsts[number] = newName + return newName + } + + private fun asmIdentifierName(identifier: IdentifierReference): String { + val name = if(identifier.memberOfStruct(program.namespace)!=null) { + identifier.targetVarDecl(program.namespace)!!.name + } else { + identifier.nameInSource.joinToString(".") + } + return fixNameSymbols(name) + } + + private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names + + private fun branchInstruction(condition: BranchCondition, complement: Boolean) = + if(complement) { + when (condition) { + BranchCondition.CS -> "bcc" + BranchCondition.CC -> "bcs" + BranchCondition.EQ, BranchCondition.Z -> "beq" + BranchCondition.NE, BranchCondition.NZ -> "bne" + BranchCondition.VS -> "bvc" + BranchCondition.VC -> "bvs" + BranchCondition.MI, BranchCondition.NEG -> "bmi" + BranchCondition.PL, BranchCondition.POS -> "bpl" + } + } else { + when (condition) { + BranchCondition.CS -> "bcs" + BranchCondition.CC -> "bcc" + BranchCondition.EQ, BranchCondition.Z -> "beq" + BranchCondition.NE, BranchCondition.NZ -> "bne" + BranchCondition.VS -> "bvs" + BranchCondition.VC -> "bvc" + BranchCondition.MI, BranchCondition.NEG -> "bmi" + BranchCondition.PL, BranchCondition.POS -> "bpl" + } + } + + private fun translate(stmt: Statement) { + when(stmt) { + is VarDecl, is StructDecl, is NopStatement -> {} + is Directive -> translate(stmt) + is Return -> translate(stmt) + is Subroutine -> translate(stmt) + is InlineAssembly -> translate(stmt) + is FunctionCallStatement -> translate(stmt) + is Assignment -> translate(stmt) + is Jump -> translate(stmt) + is PostIncrDecr -> translate(stmt) + is Label -> translate(stmt) + is BranchStatement -> translate(stmt) + is ForLoop -> translate(stmt) + is Continue -> TODO() + is Break -> TODO() + is IfStatement -> TODO() + is WhileLoop -> TODO() + is RepeatLoop -> TODO() + is WhenStatement -> TODO() + is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?") + is AnonymousScope -> throw AssemblyError("anonscope should have been flattened") + is Block -> throw AssemblyError("block should have been handled elsewhere") + } + } + + private fun translate(stmt: Label) { + out("${stmt.name}") + } + + private fun translate(stmt: BranchStatement) { + if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars()) + throw AssemblyError("only else part contains code, shoud have been switched already") + + val jump = stmt.truepart.statements.first() as? Jump + if(jump!=null) { + // branch with only a jump + val instruction = branchInstruction(stmt.condition, false) + out(" $instruction ${getJumpTarget(jump)}") + } else { + TODO("$stmt") + } + } + + private fun translate(stmt: Directive) { + when(stmt.directive) { + "%asminclude" -> { + val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source) + val scopeprefix = stmt.args[1].str ?: "" + if(!scopeprefix.isBlank()) + out("$scopeprefix\t.proc") + assemblyLines.add(sourcecode.trimEnd().trimStart('\n')) + if(!scopeprefix.isBlank()) + out(" .pend\n") + } + "%asmbinary" -> { + val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else "" + val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else "" + out(" .binary \"${stmt.args[0].str}\" $offset $length") + } + "%breakpoint" -> { + val label = "_prog8_breakpoint_${breakpointLabels.size+1}" + breakpointLabels.add(label) + out("$label\tnop") + } + } + } + + private fun translate(stmt: ForLoop) { + out("; for $stmt") + } + + private fun translate(stmt: PostIncrDecr) { + when { + stmt.target.register!=null -> { + when(stmt.target.register!!) { + Register.A -> out(""" + clc + adc #1 + """) + Register.X -> out(" inx") + Register.Y -> out(" iny") + } + } + stmt.target.identifier!=null -> { + val targetName = asmIdentifierName(stmt.target.identifier!!) + out(" inc $targetName") + } + else -> TODO("postincrdecr $stmt") + } + } + + private fun translate(jmp: Jump) { + out(" jmp ${getJumpTarget(jmp)}") + } + + private fun getJumpTarget(jmp: Jump): String { + return when { + jmp.identifier!=null -> asmIdentifierName(jmp.identifier) + jmp.generatedLabel!=null -> jmp.generatedLabel + jmp.address!=null -> jmp.address.toHex() + else -> "????" + } + } + + private fun translate(ret: Return) { + if(ret.value!=null) { + TODO("$ret value") + } + out(" rts") + } + + private fun translate(sub: Subroutine) { + if(sub.isAsmSubroutine) { + if(sub.asmAddress!=null) + return // already done at the memvars section + + // asmsub with most likely just an inline asm in it + out("${sub.name}\t.proc") + sub.statements.forEach{ translate(it) } + out(" .pend\n") + } else { + // regular subroutine + out("${sub.name}\t.proc") + zeropagevars2asm(sub.statements) + memdefs2asm(sub.statements) + sub.statements.forEach{ translate(it) } + vardecls2asm(sub.statements) + out(" .pend\n") + } + } + + private fun translate(asm: InlineAssembly) { + val assembly = asm.assembly.trimEnd().trimStart('\n') + assemblyLines.add(assembly) + } + + private fun translate(call: FunctionCallStatement) { + if(call.arglist.isEmpty()) { + out(" jsr ${call.target.nameInSource.joinToString(".")}") + } else { + TODO("call $call") + } + } + + private fun translate(assign: Assignment) { + if(assign.aug_op!=null) + throw AssemblyError("aug-op assignments should have been transformed to normal ones") + + when(assign.value) { + is NumericLiteralValue -> { + val numVal = assign.value as NumericLiteralValue + when(numVal.type) { + DataType.UBYTE, DataType.BYTE -> assignByteConstant(assign.target, numVal.number.toInt()) + DataType.UWORD, DataType.WORD -> assignWordConstant(assign.target, numVal.number.toInt()) + DataType.FLOAT -> assignFloatConstant(assign.target, numVal.number.toDouble()) + DataType.STR -> TODO() + DataType.STR_S -> TODO() + DataType.ARRAY_UB -> TODO() + DataType.ARRAY_B -> TODO() + DataType.ARRAY_UW -> TODO() + DataType.ARRAY_W -> TODO() + DataType.ARRAY_F -> TODO() + DataType.STRUCT -> TODO() + } + } + is RegisterExpr -> { + assignRegister(assign.target, (assign.value as RegisterExpr).register) + } + is IdentifierReference -> { + val type = assign.target.inferType(program, assign)!! + when(type) { + DataType.UBYTE, DataType.BYTE -> assignByteVariable(assign.target, assign.value as IdentifierReference) + DataType.UWORD, DataType.WORD -> assignWordVariable(assign.target, assign.value as IdentifierReference) + DataType.FLOAT -> TODO() + DataType.STR -> TODO() + DataType.STR_S -> TODO() + DataType.ARRAY_UB -> TODO() + DataType.ARRAY_B -> TODO() + DataType.ARRAY_UW -> TODO() + DataType.ARRAY_W -> TODO() + DataType.ARRAY_F -> TODO() + DataType.STRUCT -> TODO() + } + } + is AddressOf -> { + val identifier = (assign.value as AddressOf).identifier + val scopedname = (assign.value as AddressOf).scopedname!! + assignAddressOf(assign.target, identifier, scopedname) + } + is DirectMemoryRead -> { + val read = (assign.value as DirectMemoryRead) + when(read.addressExpression) { + is NumericLiteralValue -> { + val address = (read.addressExpression as NumericLiteralValue).number.toInt() + assignMemoryByte(assign.target, address, null) + } + is IdentifierReference -> { + assignMemoryByte(assign.target, null, read.addressExpression as IdentifierReference) + } + else -> { + translateExpression(read.addressExpression) + out("; read memory byte from result and put that in ${assign.target}") // TODO + } + } + } + is PrefixExpression -> { + translateExpression(assign.value as PrefixExpression) + assignEvalResult(assign.target) + } + is BinaryExpression -> { + translateExpression(assign.value as BinaryExpression) + assignEvalResult(assign.target) + } + is ArrayIndexedExpression -> { + translateExpression(assign.value as ArrayIndexedExpression) + assignEvalResult(assign.target) + } + is TypecastExpression -> { + val cast = assign.value as TypecastExpression + val sourceType = cast.expression.inferType(program) + val targetType = assign.target.inferType(program, assign) + if((sourceType in ByteDatatypes && targetType in ByteDatatypes) || + (sourceType in WordDatatypes && targetType in WordDatatypes)) { + // no need for a type cast + assign.value = cast.expression + translate(assign) + } else { + translateExpression(assign.value as TypecastExpression) + assignEvalResult(assign.target) + } + } + is FunctionCall -> { + translateExpression(assign.value as FunctionCall) + assignEvalResult(assign.target) + } + is ReferenceLiteralValue -> TODO("string/array/struct assignment?") + is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") + is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") + } + } + + private fun translateExpression(expr: ArrayIndexedExpression) { + out("; evaluate arrayindexed ${expr}") + } + + private fun translateExpression(expr: FunctionCall) { + out("; evaluate funccall ${expr}") + } + + private fun translateExpression(expr: TypecastExpression) { + translateExpression(expr.expression) + out("; typecast to ${expr.type}") + } + + private fun translateExpression(expression: Expression) { + when(expression) { + is PrefixExpression -> translateExpression(expression) + is BinaryExpression -> translateExpression(expression) + is ArrayIndexedExpression -> translateExpression(expression) + is TypecastExpression -> translateExpression(expression) + is AddressOf -> translateExpression(expression) + is DirectMemoryRead -> translateExpression(expression) + is NumericLiteralValue -> translateExpression(expression) + is RegisterExpr -> translateExpression(expression) + is IdentifierReference -> translateExpression(expression) + is FunctionCall -> translateExpression(expression) + is ReferenceLiteralValue -> TODO("string/array/struct assignment?") + is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") + is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") + } + } + + private fun translateExpression(expr: AddressOf) { + out("; take address of ${expr}") + } + + private fun translateExpression(expr: DirectMemoryRead) { + out("; memread ${expr}") + } + + private fun translateExpression(expr: NumericLiteralValue) { + out("; literalvalue ${expr}") + } + + private fun translateExpression(expr: RegisterExpr) { + out("; register value ${expr}") + } + + private fun translateExpression(expr: IdentifierReference) { + out("; identifier value ${expr}") + } + + private fun translateExpression(expr: BinaryExpression) { + translateExpression(expr.left) + translateExpression(expr.right) + out("; evaluate binary ${expr.operator}") + } + + private fun translateExpression(expr: PrefixExpression) { + translateExpression(expr.expression) + out("; evaluate prefix ${expr.operator}") + } + + private fun assignEvalResult(target: AssignTarget) { + out("; put result in $target") + } + + private fun assignAddressOf(target: AssignTarget, name: IdentifierReference, scopedname: String) { + when { + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + val struct = name.memberOfStruct(program.namespace) + if(struct!=null) { + // take the address of the first struct member instead + val decl = name.targetVarDecl(program.namespace)!! + val firstStructMember = struct.nameOfFirstMember() + // find the flattened var that belongs to this first struct member + val firstVarName = listOf(decl.name, firstStructMember) + val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl + val sourceName = firstVar.name + out(""" + lda #<$sourceName + ldy #>$sourceName + sta $targetName + sty $targetName+1 + """) + } else { + val sourceName = fixNameSymbols(scopedname) + out(""" + lda #<$sourceName + ldy #>$sourceName + sta $targetName + sty $targetName+1 + """) + } + } + else -> TODO("assign address to $target") + } + } + + private fun assignWordVariable(target: AssignTarget, variable: IdentifierReference) { + val sourceName = variable.nameInSource.joinToString(".") + when { + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + out(""" + lda $sourceName + ldy $sourceName+1 + sta $targetName + sty $targetName+1 + """) + } + else -> TODO("assign word to $target") + } + } + + private fun assignByteVariable(target: AssignTarget, variable: IdentifierReference) { + val sourceName = variable.nameInSource.joinToString(".") + when { + target.register!=null -> { + out(" ld${target.register.name.toLowerCase()} $sourceName") + } + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + out(""" + lda $sourceName + sta $targetName + """) + } + else -> TODO("assign byte to $target") + } + } + + private fun assignRegister(target: AssignTarget, register: Register) { + when { + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + out(" st${register.name.toLowerCase()} $targetName") + } + target.register!=null -> { + when(register) { + Register.A -> when(target.register) { + Register.A -> {} + Register.X -> out(" tax") + Register.Y -> out(" tay") + } + Register.X -> when(target.register) { + Register.A -> out(" txa") + Register.X -> {} + Register.Y -> out(" txy") + } + Register.Y -> when(target.register) { + Register.A -> out(" tya") + Register.X -> out(" tyx") + Register.Y -> {} + } + } + } + else -> out("; assign register $register to $target") + } + } + + private fun assignWordConstant(target: AssignTarget, word: Int) { + if(target.identifier!=null) { + val targetName = asmIdentifierName(target.identifier) + // TODO optimize case where lsb = msb + out(""" + lda #<${word.toHex()} + ldy #>${word.toHex()} + sta $targetName + sty $targetName+1 + """) + } else { + out("; assign byte $word to $target") + } + } + + private fun assignByteConstant(target: AssignTarget, byte: Int) { + when { + target.register!=null -> { + out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}") + } + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + out(""" + lda #${byte.toHex()} + sta $targetName + """) + } + else -> out("; assign byte $byte to $target") + } + } + + private fun assignFloatConstant(target: AssignTarget, float: Double) { + if(float==0.0) { + // optimized case for float zero + if (target.identifier != null) { + val targetName = asmIdentifierName(target.identifier) + out(""" + lda #0 + sta $targetName + sta $targetName+1 + sta $targetName+2 + sta $targetName+3 + sta $targetName+4 + """) + } else { + out("; assign float 0.0 to $target") + } + } else { + // non-zero value + val constFloat = getFloatConst(float) + if (target.identifier != null) { + val targetName = asmIdentifierName(target.identifier) + out(""" + lda $constFloat + sta $targetName + lda $constFloat+1 + sta $targetName+1 + lda $constFloat+2 + sta $targetName+2 + lda $constFloat+3 + sta $targetName+3 + lda $constFloat+4 + sta $targetName+4 + """) + } else { + out("; assign float $float ($constFloat) to $target") + } + } + } + + private fun assignMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) { + if(address!=null) { + when { + target.register!=null -> { + out(" ld${target.register.name.toLowerCase()} ${address.toHex()}") + } + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + out(""" + lda ${address.toHex()} + sta $targetName + """) + } + else -> TODO() + } + } + else if(identifier!=null) { + val sourceName = asmIdentifierName(identifier) + when { + target.register!=null -> { + out(""" + ldy #0 + lda ($sourceName),y + """) + when(target.register){ + Register.A -> {} + Register.X -> out(" tax") + Register.Y -> out(" tay") + } + } + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + out(""" + ldy #0 + lda ($sourceName),y + sta $targetName + """) + } + else -> TODO() + } + } + } +} diff --git a/compiler/src/prog8/optimizer/CallGraph.kt b/compiler/src/prog8/optimizer/CallGraph.kt index 4aef29a61..a17b242b9 100644 --- a/compiler/src/prog8/optimizer/CallGraph.kt +++ b/compiler/src/prog8/optimizer/CallGraph.kt @@ -111,7 +111,8 @@ class CallGraph(private val program: Program): IAstVisitor { override fun visit(subroutine: Subroutine) { val alwaysKeepSubroutines = setOf( Pair("main", "start"), - Pair("irq", "irq") + Pair("irq", "irq"), + Pair("prog8_lib", "init_system") ) if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines diff --git a/examples/test.p8 b/examples/test.p8 index 0e6df1a1b..74f08de61 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -7,68 +7,10 @@ sub start() { - word w1 - word w2 - word w3 - word w4 - word w5 - word w6 - word w7 - word w8 - word w9 - word w10 - word w11 - word w12 - word w13 - word w14 - word w15 - word w16 - word w17 - word w18 - word w19 - word @zp w20 - word @zp w21 - word w22 - word w23 - word w24 - word w25 - word w26 - word w27 - word w28 - word w29 - word w30 - - ubyte[] blaat = 10 to 20 - - for ubyte c in 'a' to 'z' { - c64.CHROUT(blaat[c]) - } - - word q - q=w26 - q+=w27 - q+=w28 - q+=w29 - q+=w30 - q+=w10 - q+=w11 - q+=w12 - q+=w13 - q+=w14 - q+=w15 - q+=w16 - q+=w17 - q+=w18 - q+=w19 - q+=w20 - q+=w21 - q+=w22 - q+=w23 - q+=w24 - q+=w25 - q+=w26 - q+=w27 - + if_z goto start + if_pos goto start + if_cc goto start + if_nz goto start }