cleaning up AsmGen interface

This commit is contained in:
Irmen de Jong 2022-02-06 17:07:03 +01:00
parent f538c9f0c3
commit 6bdd81623f
18 changed files with 154 additions and 81 deletions

View File

@ -26,8 +26,7 @@ const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
class AsmGen6502(internal val program: Program,
internal val errors: IErrorReporter,
internal val options: CompilationOptions,
internal val outputDir: Path): IAssemblyGenerator {
internal val options: CompilationOptions): IAssemblyGenerator {
// for expressions and augmented assignments:
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
@ -76,7 +75,7 @@ class AsmGen6502(internal val program: Program,
slaballocations()
footer()
val output = outputDir.resolve("${program.name}.asm")
val output = options.outputDir.resolve("${program.name}.asm")
if(options.optimize) {
val separateLines = assemblyLines.flatMapTo(mutableListOf()) { it.split('\n') }
assemblyLines.clear()
@ -90,7 +89,7 @@ class AsmGen6502(internal val program: Program,
}
return if(errors.noErrors())
AssemblyProgram(program.name, outputDir, compTarget.name)
AssemblyProgram(program.name, options.outputDir, compTarget.name)
else {
errors.report()
return null
@ -1591,7 +1590,7 @@ $repeatLabel lda $counterVar
TODO("%asmbinary inside non-library, non-filesystem module")
val sourcePath = Path(stmt.definingModule.source.origin)
val includedPath = sourcePath.resolveSibling(includedName)
val pathForAssembler = outputDir // #54: 64tass needs the path *relative to the .asm file*
val pathForAssembler = options.outputDir // #54: 64tass needs the path *relative to the .asm file*
.toAbsolutePath()
.relativize(includedPath.toAbsolutePath())
.normalize() // avoid assembler warnings (-Wportable; only some, not all)

View File

@ -5,18 +5,16 @@ import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyGenerator
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.IErrorReporter
import java.nio.file.Path
class ExperimentalAsmGen6502(internal val program: Program,
internal val errors: IErrorReporter,
internal val options: CompilationOptions,
internal val outputDir: Path
internal val options: CompilationOptions
): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
println("\n** experimental 65(c)02 code generator **\n")
println("..todo: create assembly code..")
println("..todo: create assembly code into ${options.outputDir.toAbsolutePath()}..")
return AssemblyProgram("dummy")
}
}

View File

@ -1,20 +1,18 @@
package prog8.codegen.target
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c128.C128MachineDefinition
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
object C128Target: ICompilationTarget, IStringEncoding by Encoder {
object C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = "c128"
override val machine = C128MachineDefinition()
@ -22,13 +20,4 @@ object C128Target: ICompilationTarget, IStringEncoding by Encoder {
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,20 +1,18 @@
package prog8.codegen.target
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c64.C64MachineDefinition
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
object C64Target: ICompilationTarget, IStringEncoding by Encoder {
object C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = "c64"
override val machine = C64MachineDefinition()
@ -22,13 +20,4 @@ object C64Target: ICompilationTarget, IStringEncoding by Encoder {
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,20 +1,18 @@
package prog8.codegen.target
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.codegen.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
object Cx16Target: ICompilationTarget, IStringEncoding by Encoder {
object Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = "cx16"
override val machine = CX16MachineDefinition()
@ -22,14 +20,4 @@ object Cx16Target: ICompilationTarget, IStringEncoding by Encoder {
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -0,0 +1,31 @@
package prog8.codegen.target.cbm
import prog8.ast.base.*
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMemSizer
internal object CbmMemorySizer: IMemSizer {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
override fun memorySize(decl: VarDecl): Int {
return when(decl.type) {
VarDeclType.CONST -> 0
VarDeclType.VAR, VarDeclType.MEMORY -> {
when(val dt = decl.datatype) {
in NumericDatatypes -> return memorySize(dt)
in ArrayDatatypes -> decl.arraysize!!.constIndex()!! * memorySize(ArrayToElementTypes.getValue(dt))
DataType.STR -> (decl.value as StringLiteralValue).value.length + 1
else -> 0
}
}
}
}
}

View File

@ -75,6 +75,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
outputDir = args.outputDir.normalize()
}
program = programresult
importedFiles = imported
@ -97,7 +98,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
// printProgram(program)
if (args.writeAssembly) {
when (val result = writeAssembly(program, args.errors, args.outputDir, compilationOptions)) {
when (val result = writeAssembly(program, args.errors, compilationOptions)) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
System.err.println(result.error)
@ -339,7 +340,6 @@ private sealed class WriteAssemblyResult {
private fun writeAssembly(program: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions
): WriteAssemblyResult {
// asm generation directly from the Ast
@ -350,11 +350,7 @@ private fun writeAssembly(program: Program,
// printProgram(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(
program,
errors,
compilerOptions,
outputDir).compileToAssembly()
val assembly = asmGeneratorFor(program, errors, compilerOptions).compileToAssembly()
errors.report()
return if(assembly!=null && errors.noErrors()) {
@ -378,15 +374,14 @@ internal fun asmGeneratorFor(
program: Program,
errors: IErrorReporter,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
if(options.experimentalCodegen) {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
return ExperimentalAsmGen6502(program, errors, options, outputDir)
return ExperimentalAsmGen6502(program, errors, options)
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
return AsmGen6502(program, errors, options, outputDir)
return AsmGen6502(program, errors, options)
}
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")

View File

@ -10,16 +10,23 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isIOAddress
import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName
internal class BeforeAsmAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter
) : AstWalker() {
private val allBlockVars = mutableMapOf<Block, MutableSet<VarDecl>>()
private val allSubroutineVars = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
// internal lateinit var allocation: IVariableAllocation
//
// override fun after(program: Program): Iterable<IAstModification> {
// allocation = VariableAllocation(allBlockVars, allSubroutineVars)
// allocation.dump(program.memsizer)
// return super.after(program)
// }
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
throw FatalAstException("break should have been replaced by goto $breakStmt")
}
@ -58,6 +65,25 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
}
if(decl.type==VarDeclType.VAR) {
when(val scope=decl.definingScope) {
is Block -> {
val blockVars = allBlockVars[scope] ?: mutableSetOf()
blockVars.add(decl)
allBlockVars[scope] = blockVars
}
is Subroutine -> {
val subroutineVars = allSubroutineVars[scope] ?: mutableSetOf()
subroutineVars.add(decl)
allSubroutineVars[scope] = subroutineVars
}
else -> {
throw FatalAstException("var can only occur in subroutine or block scope")
}
}
}
return noModifications
}
@ -371,3 +397,28 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
}
}
internal class VariableAllocation(
override val blockVars: Map<Block, Set<VarDecl>>,
override val subroutineVars: Map<Subroutine, Set<VarDecl>>) : IVariableAllocation
{
override fun dump(memsizer: IMemSizer) {
println("ALL BLOCK VARS:")
blockVars.forEach { (block, vars) ->
val totalsize = vars.sumOf { memsizer.memorySize(it) }
println("BLOCK: ${block.name} total size: $totalsize")
vars.forEach {
println(" ${it.datatype} ${it.name} ${it.position}")
}
}
println("ALL SUBROUTINE VARS:")
subroutineVars.forEach { (sub, vars) ->
val totalsize = vars.sumOf { memsizer.memorySize(it) }
println("SUBROUTINE: ${sub.name} total size: $totalsize")
vars.forEach {
println(" ${it.datatype} ${it.name} ${it.position}")
}
}
}
}

View File

@ -300,7 +300,7 @@ class TestOptimization: FunSpec({
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target)
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target, outputDir= outputDir)
result.program.processAstBeforeAsmGeneration(options, ErrorReporterForTests())
// assignment is now split into:

View File

@ -79,7 +79,7 @@ class TestAsmGenSymbols: StringSpec({
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
options.compTarget.machine.zeropage = C64Zeropage(options)
val asmgen = AsmGen6502(program, errors, options, Path.of(""))
val asmgen = AsmGen6502(program, errors, options)
return asmgen
}

View File

@ -8,6 +8,7 @@ import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.*
internal val DummyFunctions = object : IBuiltinFunctions {
@ -24,6 +25,7 @@ internal val DummyFunctions = object : IBuiltinFunctions {
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType) = 0
override fun memorySize(decl: VarDecl) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
@ -69,4 +71,8 @@ internal val DummyCompilationTarget = object : ICompilationTarget {
override fun memorySize(dt: DataType): Int {
throw NotImplementedError("dummy")
}
override fun memorySize(decl: VarDecl): Int {
throw NotImplementedError("dummy")
}
}

View File

@ -85,8 +85,8 @@ internal fun generateAssembly(
program: Program,
options: CompilationOptions? = null
): IAssemblyProgram? {
val coptions = options ?: CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, true, C64Target)
val coptions = options ?: CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, true, C64Target, outputDir = outputDir)
coptions.compTarget.machine.zeropage = C64Zeropage(coptions)
val asmgen = AsmGen6502(program, ErrorReporterForTests(), coptions, outputDir)
val asmgen = AsmGen6502(program, ErrorReporterForTests(), coptions)
return asmgen.compileToAssembly()
}

View File

@ -1,6 +1,7 @@
package prog8.compilerinterface
import prog8.ast.base.DataType
import prog8.ast.statements.VarDecl
// note: this is a separate interface in the compilerAst module because
@ -8,4 +9,5 @@ import prog8.ast.base.DataType
interface IMemSizer {
fun memorySize(dt: DataType): Int
fun memorySize(decl: VarDecl): Int
}

View File

@ -1,5 +1,8 @@
package prog8.compilerinterface
import java.nio.file.Path
import kotlin.io.path.Path
enum class OutputType {
RAW,
PRG
@ -32,5 +35,7 @@ class CompilationOptions(val output: OutputType,
var dontReinitGlobals: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false
)
var experimentalCodegen: Boolean = false,
var outputDir: Path = Path("")
) {
}

View File

@ -0,0 +1,12 @@
package prog8.compilerinterface
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
interface IVariableAllocation {
fun dump(memsizer: IMemSizer)
val blockVars: Map<Block, Set<VarDecl>>
val subroutineVars: Map<Subroutine, Set<VarDecl>>
}

View File

@ -3,7 +3,7 @@ TODO
For next release
^^^^^^^^^^^^^^^^
...
fix the concurrent modification issue on zeropage when running unit tests in parallel -> don't use static objects anymore
Need help with
@ -22,7 +22,6 @@ Blocked by an official Commander-x16 r39 release
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Ast modifications done in AsmGen, that should be done BEFORE calling asmgen (so that it doesn't have to modify the Ast any longer):
- block2asm: removes init-assignments to no longer output the initialization assignments as regular statements (is done separately in block initialization routine)
- block2asm: after vardecls2asm it clears the vardecl.value of all variables
- Maybe don't rely on vardecls at all any longer but figure out the variable allocations (including ZP allocations) beforehand

View File

@ -22,13 +22,22 @@ main {
foobar()
startval1++
mainglobal1++
start2()
sub start2() {
uword @shared startval1 = 2002
ubyte[2] @shared barr
uword[2] @shared warr
uword[] @shared warr2 = [1,2]
}
}
asmsub derp() {
}
sub foobar() {
uword @shared startval1 = 2002
uword @shared mainglobal1 = 2002
txt.print("foobar\n")
}
}