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
}
@ -370,4 +396,29 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
return modifications
}
}
}
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,12 +22,11 @@ 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
and pass that via a new datastructure to asmgen? So that asmgen is no longer tasked with doing the allocations.
This could perhaps make it easer for the codegen as well to deal with sections, if any, in the future.
- 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
and pass that via a new datastructure to asmgen? So that asmgen is no longer tasked with doing the allocations.
This could perhaps make it easer for the codegen as well to deal with sections, if any, in the future.
- remove support for old @"screencodes" string encoding syntax (parser+code+docs)
- allow "xxx" * constexpr (where constexpr is not a number literal), now gives expression error not same type

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