introduce option to use internal scratch variables via prog8_lib definitions (ony for compiler, not for user code!)

This commit is contained in:
Irmen de Jong 2021-11-14 16:01:54 +01:00
parent ab2d1122a9
commit 53e1729e2f
6 changed files with 108 additions and 57 deletions

View File

@ -214,9 +214,9 @@ class AsmGen(private val program: Program,
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n")) out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
outputSourceLine(block) outputSourceLine(block)
zeropagevars2asm(block.statements) zeropagevars2asm(block.statements, block)
memdefs2asm(block.statements) memdefs2asm(block.statements, block)
vardecls2asm(block.statements) vardecls2asm(block.statements, block)
out("\n; subroutines in this block") out("\n; subroutines in this block")
// first translate regular statements, and then put the subroutines at the end. // first translate regular statements, and then put the subroutines at the end.
@ -258,10 +258,13 @@ class AsmGen(private val program: Program,
} else assemblyLines.add(fragment) } else assemblyLines.add(fragment)
} }
private fun zeropagevars2asm(statements: List<Statement>) { private fun zeropagevars2asm(statements: List<Statement>, inBlock: Block?) {
out("; vars allocated on zeropage") out("; vars allocated on zeropage")
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR } val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
val blockname = inBlock?.name
for(variable in variables) { for(variable in variables) {
if(blockname=="prog8_lib" && variable.name.startsWith("P8ZP_SCRATCH_"))
continue // the "hooks" to the temp vars are not generated as new variables
val fullName = variable.makeScopedName(variable.name) val fullName = variable.makeScopedName(variable.name)
val zpVar = allocatedZeropageVariables[fullName] val zpVar = allocatedZeropageVariables[fullName]
if(zpVar==null) { if(zpVar==null) {
@ -359,14 +362,17 @@ class AsmGen(private val program: Program,
} }
} }
private fun memdefs2asm(statements: List<Statement>) { private fun memdefs2asm(statements: List<Statement>, inBlock: Block?) {
val blockname = inBlock?.name
out("\n; memdefs and kernal subroutines") out("\n; memdefs and kernal subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST } val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) { for(m in memvars) {
if(m.value is NumericLiteralValue) if(blockname!="prog8_lib" || !m.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}") if(m.value is NumericLiteralValue)
else out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}") else
out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}")
} }
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine } val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) { for(sub in asmSubs) {
@ -379,7 +385,7 @@ class AsmGen(private val program: Program,
} }
} }
private fun vardecls2asm(statements: List<Statement>) { private fun vardecls2asm(statements: List<Statement>, inBlock: Block?) {
out("\n; non-zeropage variables") out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR } val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
@ -394,9 +400,13 @@ class AsmGen(private val program: Program,
} }
// non-string variables // non-string variables
val blockname = inBlock?.name
vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach { vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
if(it.makeScopedName(it.name) !in allocatedZeropageVariables) if(it.makeScopedName(it.name) !in allocatedZeropageVariables) {
vardecl2asm(it) if(blockname!="prog8_lib" || !it.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables
vardecl2asm(it)
}
} }
} }
@ -496,34 +506,40 @@ class AsmGen(private val program: Program,
} }
fun asmSymbolName(identifier: IdentifierReference): String { fun asmSymbolName(identifier: IdentifierReference): String {
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs") fun internalName(): String {
return identifier.nameInSource.joinToString(".") if (identifier.nameInSource.size == 2 && identifier.nameInSource[0] == "prog8_slabs")
return identifier.nameInSource.joinToString(".")
val tgt2 = identifier.targetStatement(program) val tgt2 = identifier.targetStatement(program)
if(tgt2==null && (identifier.nameInSource[0].startsWith("_prog8") || identifier.nameInSource[0].startsWith("prog8"))) if (tgt2 == null && (identifier.nameInSource[0].startsWith("_prog8") || identifier.nameInSource[0].startsWith(
return identifier.nameInSource.joinToString(".") "prog8"
))
)
return identifier.nameInSource.joinToString(".")
val target = identifier.targetStatement(program)!! val target = identifier.targetStatement(program)!!
val targetScope = target.definingSubroutine val targetScope = target.definingSubroutine
val identScope = identifier.definingSubroutine val identScope = identifier.definingSubroutine
return if(targetScope !== identScope) { return if (targetScope !== identScope) {
val scopedName = getScopedSymbolNameForTarget(identifier.nameInSource.last(), target) val scopedName = getScopedSymbolNameForTarget(identifier.nameInSource.last(), target)
if(target is Label) { if (target is Label) {
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62 // make labels locally scoped in the asm. Is slightly problematic, see github issue #62
val last = scopedName.removeLast() val last = scopedName.removeLast()
scopedName.add("_$last") scopedName.add("_$last")
} }
fixNameSymbols(scopedName.joinToString("."))
} else {
if(target is Label) {
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62
val scopedName = identifier.nameInSource.toMutableList()
val last = scopedName.removeLast()
scopedName.add("_$last")
fixNameSymbols(scopedName.joinToString(".")) fixNameSymbols(scopedName.joinToString("."))
} else {
if (target is Label) {
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62
val scopedName = identifier.nameInSource.toMutableList()
val last = scopedName.removeLast()
scopedName.add("_$last")
fixNameSymbols(scopedName.joinToString("."))
} else fixNameSymbols(identifier.nameInSource.joinToString("."))
} }
else fixNameSymbols(identifier.nameInSource.joinToString("."))
} }
return fixNameSymbols(internalName())
} }
fun asmVariableName(identifier: IdentifierReference) = fun asmVariableName(identifier: IdentifierReference) =
@ -600,7 +616,10 @@ class AsmGen(private val program: Program,
} }
} }
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names private fun fixNameSymbols(name: String): String {
val name2 = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
return name2.replace("prog8_lib.P8ZP_SCRATCH_", "P8ZP_SCRATCH_") // take care of the 'hooks' to the temp vars
}
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) { internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
if (isTargetCpu(CpuType.CPU65c02)) { if (isTargetCpu(CpuType.CPU65c02)) {
@ -934,8 +953,8 @@ class AsmGen(private val program: Program,
} else { } else {
// regular subroutine // regular subroutine
out("${sub.name}\t.proc") out("${sub.name}\t.proc")
zeropagevars2asm(sub.statements) zeropagevars2asm(sub.statements, null)
memdefs2asm(sub.statements) memdefs2asm(sub.statements, null)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic // the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock.name=="main") { if(sub.name=="start" && sub.definingBlock.name=="main") {
@ -985,7 +1004,7 @@ class AsmGen(private val program: Program,
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0") out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(sub.asmGenInfo.usedFloatEvalResultVar2) if(sub.asmGenInfo.usedFloatEvalResultVar2)
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0") out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
vardecls2asm(sub.statements) vardecls2asm(sub.statements, null)
out(" .pend\n") out(" .pend\n")
} }
} }

View File

@ -5,10 +5,7 @@ import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.*
import prog8.ast.base.Position
import prog8.ast.base.RegisterOrPair
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.AddressOf import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
@ -84,7 +81,7 @@ class AsmGenSymbolsTests: StringSpec({
return asmgen return asmgen
} }
"symbol names from strings" { "symbol and variable names from strings" {
val program = createTestProgram() val program = createTestProgram()
val asmgen = createTestAsmGen(program) val asmgen = createTestAsmGen(program)
asmgen.asmSymbolName("name") shouldBe "name" asmgen.asmSymbolName("name") shouldBe "name"
@ -97,7 +94,7 @@ class AsmGenSymbolsTests: StringSpec({
asmgen.asmVariableName(listOf("a", "b", "name")) shouldBe "a.b.name" asmgen.asmVariableName(listOf("a", "b", "name")) shouldBe "a.b.name"
} }
"symbol names from variable identifiers" { "symbol and variable names from variable identifiers" {
val program = createTestProgram() val program = createTestProgram()
val asmgen = createTestAsmGen(program) val asmgen = createTestAsmGen(program)
val sub = program.entrypoint val sub = program.entrypoint
@ -120,7 +117,7 @@ class AsmGenSymbolsTests: StringSpec({
asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside" asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside"
} }
"symbol names from label identifiers" { "symbol and variable names from label identifiers" {
val program = createTestProgram() val program = createTestProgram()
val asmgen = createTestAsmGen(program) val asmgen = createTestAsmGen(program)
val sub = program.entrypoint val sub = program.entrypoint
@ -149,4 +146,28 @@ class AsmGenSymbolsTests: StringSpec({
asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside" asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside"
} }
} }
"asm names for hooks to zp temp vars" {
/*
main {
sub start() {
prog8_lib.P8ZP_SCRATCH_REG = 1
prog8_lib.P8ZP_SCRATCH_B1 = 1
prog8_lib.P8ZP_SCRATCH_W1 = 1
prog8_lib.P8ZP_SCRATCH_W2 = 1
*/
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_REG") shouldBe "P8ZP_SCRATCH_REG"
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_W2") shouldBe "P8ZP_SCRATCH_W2"
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_REG")) shouldBe "P8ZP_SCRATCH_REG"
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_W2")) shouldBe "P8ZP_SCRATCH_W2"
val id1 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_REG"), Position.DUMMY)
id1.linkParents(program.toplevelModule)
val id2 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_W2"), Position.DUMMY)
id2.linkParents(program.toplevelModule)
asmgen.asmSymbolName(id1) shouldBe "P8ZP_SCRATCH_REG"
asmgen.asmSymbolName(id2) shouldBe "P8ZP_SCRATCH_W2"
}
}) })

View File

@ -15,6 +15,15 @@ prog8_lib {
word retval_interm_w2 word retval_interm_w2
byte retval_interm_b2 byte retval_interm_b2
; prog8 "hooks" to be able to access the temporary scratch variables
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
; NOTE: the assembly code generator will match these names and not generate
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
&ubyte P8ZP_SCRATCH_REG = $ff
&byte P8ZP_SCRATCH_B1 = $ff
&uword P8ZP_SCRATCH_W1 = $ff
&word P8ZP_SCRATCH_W2 = $ff
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A { asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{ %asm {{

View File

@ -240,13 +240,17 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
var rightAssignment: Assignment? = null var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null var rightOperandReplacement: Expression? = null
if(!expr.left.isSimple && expr.left !is IFunctionCall) { val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
if(separateLeftExpr) {
val dt = expr.left.inferType(program) val dt = expr.left.inferType(program)
val name = when { val name = when {
dt.istype(DataType.UBYTE) -> listOf("cx16","r9L") // assume (hope) cx16.r9 isn't used for anything else... // TODO assume (hope) cx16.r9 isn't used for anything else...
dt.istype(DataType.UWORD) -> listOf("cx16","r9") // assume (hope) cx16.r9 isn't used for anything else... dt.istype(DataType.UBYTE) -> listOf("cx16","r9L")
dt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b") dt.istype(DataType.BYTE) -> listOf("cx16","r9sL")
dt.istype(DataType.WORD) -> listOf("prog8_lib","retval_interm_w") dt.istype(DataType.UWORD) -> listOf("cx16","r9")
dt.istype(DataType.WORD) -> listOf("cx16","r9s")
else -> throw AssemblyError("invalid dt") else -> throw AssemblyError("invalid dt")
} }
leftOperandReplacement = IdentifierReference(name, expr.position) leftOperandReplacement = IdentifierReference(name, expr.position)
@ -256,7 +260,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
expr.position expr.position
) )
} }
if(!expr.right.isSimple && expr.right !is IFunctionCall) { if(separateRightExpr) {
val dt = expr.right.inferType(program) val dt = expr.right.inferType(program)
val name = when { val name = when {
dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub") dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub")

View File

@ -1,5 +1,6 @@
package prog8.compilerinterface package prog8.compilerinterface
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
@ -41,7 +42,7 @@ fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
} }
ident != null -> { ident != null -> {
val program = definingModule.program val program = definingModule.program
val decl = ident.targetVarDecl(program)!! val decl = ident.targetVarDecl(program) ?: throw FatalAstException("invalid identifier ${ident.nameInSource}")
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue) return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt()) machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else else

View File

@ -1,13 +1,10 @@
%import textio
%import floats
%zeropage dontuse
main { main {
sub start() { sub start() {
byte xx=1 byte xx=1
if -xx { if xx+2 {
xx++ xx++
} }