begin of ast-codegen v2

This commit is contained in:
Irmen de Jong 2019-07-21 12:00:22 +02:00
parent 173663380b
commit 03782a37a2
12 changed files with 865 additions and 75 deletions

View File

@ -38,6 +38,7 @@ private fun compileMain(args: Array<String>) {
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<String>) {
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<String>) {
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")

View File

@ -168,9 +168,11 @@ class Program(val name: String, val modules: MutableList<Module>) {
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<Module>) {
mainBlocks[0].subScopes()["start"] as Subroutine?
}
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
}
class Module(override val name: String,

View File

@ -735,6 +735,8 @@ data class IdentifierReference(val nameInSource: List<String>, 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")

View File

@ -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")

View File

@ -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 {

View File

@ -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<String> = Stack()
private val continueStmtLabelStack : Stack<String> = Stack()

View File

@ -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<Program, String?> {
writeAssembly: Boolean, asm2: Boolean): Pair<Program, String?> {
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.")

View File

@ -34,7 +34,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
val memory = mutableMapOf<Int, List<RuntimeValue>>()
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) {

View File

@ -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) {

View File

@ -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<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
private val breakpointLabels = mutableListOf<String>()
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<Statement>) {
out("; vars allocated on zeropage")
val variables = statements.filterIsInstance<VarDecl>().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<Statement>) {
out("\n; memdefs and kernel subroutines")
val memvars = statements.filterIsInstance<VarDecl>().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<Subroutine>().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<Statement>) {
out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().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<String> {
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()
}
}
}
}

View File

@ -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

View File

@ -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
}