temp vars are now dynamically added to AST as needed

This commit is contained in:
Irmen de Jong 2022-02-10 02:47:46 +01:00
parent 41b1c80492
commit 08bacdd090
20 changed files with 125 additions and 76 deletions

View File

@ -4,11 +4,11 @@ import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.AugmentAssignmentOperators
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.getTempVar
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
@ -98,14 +98,7 @@ X = BinExpr X = LeftExpr
// we can see if we can unwrap the binary expression by working on a new temporary variable
// (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence.
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
else -> throw FatalAstException("invalid dt $tempDt")
}
val tempVar = program.getTempVar(origExpr.inferType(program).getOr(DataType.UNDEFINED))
val assignTempVar = Assignment(
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position

View File

@ -4,7 +4,12 @@ import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.InferredTypes
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.statements.ZeropageWish
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
@ -69,7 +74,7 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
return opti.applyModifications()
}
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt istype DataType.UBYTE -> listOf("cx16", "r9L")

View File

@ -130,7 +130,7 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
val name = getTempVarName(arg.inferType(program))
val name = getTempRegisterName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(
@ -474,14 +474,8 @@ class StatementOptimizer(private val program: Program,
val returnDt = subr.returntypes.single()
if (returnDt in IntegerDatatypes) {
// first assign to intermediary variable, then return that
val returnVarName = "retval_interm_" + when(returnDt) {
DataType.UBYTE -> "ub"
DataType.BYTE -> "b"
DataType.UWORD -> "uw"
DataType.WORD -> "w"
else -> "<undefined>"
}
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
val returnVarName = program.getTempVar(returnDt)
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, value, AssignmentOrigin.OPTIMIZER, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)

View File

@ -10,8 +10,6 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----

View File

@ -13,8 +13,6 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----

View File

@ -6,24 +6,6 @@ prog8_lib {
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
; to store intermediary expression results for return values:
; NOTE: these variables can be used in the StatementReorderer and StatementOptimizer
uword @zp retval_interm_uw
word @zp retval_interm_w
ubyte @zp retval_interm_ub
byte @zp retval_interm_b
word retval_interm_w2
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 {
%asm {{

View File

@ -345,7 +345,7 @@ private fun writeAssembly(program: Program,
// asm generation directly from the Ast
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val variables = VariableExtractor().extractVars(program)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
program.processAstBeforeAsmGeneration(compilerOptions, variables, errors)
errors.report()
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")

View File

@ -13,6 +13,7 @@ import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.IVariablesAndConsts
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@ -23,8 +24,8 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, variables: IVariablesAndConsts, errors: IErrorReporter) {
val fixer = BeforeAsmAstChanger(this, compilerOptions, variables, errors)
fixer.visit(this)
while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this)

View File

@ -8,10 +8,11 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName
import prog8.optimizer.getTempRegisterName
internal class BeforeAsmAstChanger(val program: Program,
private val options: CompilationOptions,
private val variables: IVariablesAndConsts,
private val errors: IErrorReporter
) : AstWalker() {
@ -243,7 +244,7 @@ internal class BeforeAsmAstChanger(val program: Program,
}
if(separateLeftExpr) {
val name = getTempVarName(leftDt)
val name = getTempRegisterName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
@ -252,13 +253,9 @@ internal class BeforeAsmAstChanger(val program: Program,
)
}
if(separateRightExpr) {
val name = when {
rightDt istype DataType.UBYTE -> listOf("prog8_lib","retval_interm_ub")
rightDt istype DataType.UWORD -> listOf("prog8_lib","retval_interm_uw")
rightDt istype DataType.BYTE -> listOf("prog8_lib","retval_interm_b2")
rightDt istype DataType.WORD -> listOf("prog8_lib","retval_interm_w2")
else -> throw AssemblyError("invalid dt")
}
val name = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
val tempvardecl = program.toplevelModule.lookup(name) as VarDecl
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
rightOperandReplacement = IdentifierReference(name, expr.position)
rightAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
@ -354,7 +351,9 @@ internal class BeforeAsmAstChanger(val program: Program,
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program)
val tempvar = if(dt.isBytes) listOf("prog8_lib","retval_interm_ub") else listOf("prog8_lib","retval_interm_b")
val tempvar = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
val tempvardecl = program.toplevelModule.lookup(tempvar) as VarDecl
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.BEFOREASMGEN, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))

View File

@ -55,8 +55,8 @@ internal class StatementReorderer(val program: Program,
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal returnvalue intermediates.
if(decl.name.startsWith("tempvar_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal temporary variables.
return noModifications
}
if(decl.findInitializer(program)!=null)

View File

@ -104,18 +104,17 @@ internal class VariablesAndConsts (
override val subroutineConsts: Map<Subroutine, Set<IVariablesAndConsts.ConstantNumberSymbol>>
override val subroutineMemvars: Map<Subroutine, Set<IVariablesAndConsts.MemoryMappedVariable>>
private val bv = astBlockVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }.toMutableMap()
private val bc = astBlockConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
private val bmv = astBlockMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
private val sv = astSubroutineVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }
private val sc = astSubroutineConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
private val smv = astSubroutineMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
init {
val bv = astBlockVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }
val bc = astBlockConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
val bmv = astBlockMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
val sv = astSubroutineVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }
val sc = astSubroutineConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
val smv = astSubroutineMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
astBlockVars.forEach { (block, decls) ->
val vars = bv.getValue(block)
vars.addAll(decls.map {
IVariablesAndConsts.StaticVariable(it.datatype, it.scopedName, it.definingScope, it.value, it.arraysize?.constIndex(), it.zeropage, it.position)
})
vars.addAll(decls.map { toStatic(it) })
}
astBlockConsts.forEach { (block, decls) ->
bc.getValue(block).addAll(
@ -146,9 +145,7 @@ internal class VariablesAndConsts (
}
astSubroutineVars.forEach { (sub, decls) ->
val vars = sv.getValue(sub)
vars.addAll(decls.map {
IVariablesAndConsts.StaticVariable(it.datatype, it.scopedName, it.definingScope, it.value, it.arraysize?.constIndex(), it.zeropage, it.position)
})
vars.addAll(decls.map { toStatic(it) })
}
astSubroutineConsts.forEach { (sub, decls) ->
sc.getValue(sub).addAll(
@ -179,4 +176,16 @@ internal class VariablesAndConsts (
subroutineConsts = sc
subroutineMemvars = smv
}
private fun toStatic(decl: VarDecl) =
IVariablesAndConsts.StaticVariable(decl.datatype, decl.scopedName, decl.definingScope, decl.value, decl.arraysize?.constIndex(), decl.zeropage, decl.position)
override fun addIfUnknown(definingBlock: Block, variable: VarDecl) {
var blockvars = bv[definingBlock]
if(blockvars==null) {
blockvars = mutableSetOf()
bv[definingBlock] = blockvars
}
blockvars.add(toStatic(variable))
}
}

View File

@ -303,7 +303,7 @@ class TestOptimization: FunSpec({
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target(), outputDir= outputDir)
result.program.processAstBeforeAsmGeneration(options, ErrorReporterForTests())
result.program.processAstBeforeAsmGeneration(options, DummyVarsAndConsts, ErrorReporterForTests())
// assignment is now split into:
// bb = not bb
@ -357,11 +357,11 @@ class TestOptimization: FunSpec({
st.size shouldBe 8
st.last() shouldBe instanceOf<Return>()
var assign = st[3] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[4] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[5] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[6] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("bb")
}

View File

@ -76,6 +76,11 @@ class TestAsmGenSymbols: StringSpec({
override val subroutineVars: Map<Subroutine, Set<IVariablesAndConsts.StaticVariable>>
override val subroutineConsts: Map<Subroutine, Set<IVariablesAndConsts.ConstantNumberSymbol>>
override val subroutineMemvars: Map<Subroutine, Set<IVariablesAndConsts.MemoryMappedVariable>>
override fun addIfUnknown(definingBlock: Block, variable: VarDecl) {
throw NotImplementedError("dummy")
}
init {
blockVars = mutableMapOf()
blockVars[block] = mutableSetOf(IVariablesAndConsts.StaticVariable(varInBlock.datatype, varInBlock.scopedName, varInBlock.definingScope, varInBlock.value, varInBlock.arraysize?.constIndex(), varInBlock.zeropage, varInBlock.position))

View File

@ -6,6 +6,7 @@ import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Block
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
@ -76,3 +77,23 @@ internal object DummyCompilationTarget : ICompilationTarget {
throw NotImplementedError("dummy")
}
}
internal object DummyVarsAndConsts : IVariablesAndConsts {
override val blockVars: Map<Block, Set<IVariablesAndConsts.StaticVariable>>
get() = throw NotImplementedError("dummy")
override val blockConsts: Map<Block, Set<IVariablesAndConsts.ConstantNumberSymbol>>
get() = throw NotImplementedError("dummy")
override val blockMemvars: Map<Block, Set<IVariablesAndConsts.MemoryMappedVariable>>
get() = throw NotImplementedError("dummy")
override val subroutineVars: Map<Subroutine, Set<IVariablesAndConsts.StaticVariable>>
get() = throw NotImplementedError("dummy")
override val subroutineConsts: Map<Subroutine, Set<IVariablesAndConsts.ConstantNumberSymbol>>
get() = throw NotImplementedError("dummy")
override val subroutineMemvars: Map<Subroutine, Set<IVariablesAndConsts.MemoryMappedVariable>>
get() = throw NotImplementedError("dummy")
override fun addIfUnknown(definingBlock: Block, variable: VarDecl) {
throw NotImplementedError("dummy")
}
}

View File

@ -1,5 +1,12 @@
package prog8.ast
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.statements.ZeropageWish
import kotlin.math.abs
fun Number.toHex(): String {
@ -29,3 +36,37 @@ fun UInt.toHex(): String {
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}
fun Program.getTempVar(dt: DataType, altNames: Boolean=false): List<String> {
val tmpvarName = if(altNames) {
when (dt) {
DataType.UBYTE -> listOf("prog8_lib", "tempvar_ub2")
DataType.BYTE -> listOf("prog8_lib", "tempvar_b2")
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw2")
DataType.WORD -> listOf("prog8_lib", "tempvar_w2")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float2")
else -> throw FatalAstException("invalid dt")
}
} else {
when (dt) {
DataType.UBYTE -> listOf("prog8_lib", "tempvar_ub")
DataType.BYTE -> listOf("prog8_lib", "tempvar_b")
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw")
DataType.WORD -> listOf("prog8_lib", "tempvar_w")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
else -> throw FatalAstException("invalid dt")
}
}
val block = this.allBlocks.first { it.name==tmpvarName[0] }
if(block.statements.filterIsInstance<VarDecl>().any { it.name == tmpvarName[1] })
return tmpvarName
// add new temp variable to the ast directly (we can do this here because we're not iterating inside those container blocks)
val decl = VarDecl(
VarDeclType.VAR, VarDeclOrigin.AUTOGENERATED, dt, ZeropageWish.DONTCARE,
null, tmpvarName[1], null, false, false, null, Position.DUMMY)
block.statements.add(decl)
decl.linkParents(block)
return tmpvarName
}

View File

@ -56,7 +56,7 @@ class Program(val name: String,
}
val allBlocks: List<Block>
get() = modules.flatMap { it.statements.filterIsInstance<Block>() }
get() = modules.flatMap { it.statements.asSequence().filterIsInstance<Block>() }
val entrypoint: Subroutine
get() {

View File

@ -175,7 +175,8 @@ enum class VarDeclOrigin {
USERCODE,
SUBROUTINEPARAM,
STRINGLITERAL,
ARRAYLITERAL
ARRAYLITERAL,
AUTOGENERATED
}

View File

@ -38,7 +38,8 @@ fun AssignTarget.isIOAddress(machine: IMachineDefinition): Boolean {
} else false
}
ident != null -> {
val decl = ident.targetVarDecl(definingModule.program) ?: throw FatalAstException("invalid identifier ${ident.nameInSource}")
val decl = ident.targetVarDecl(definingModule.program) ?:
throw FatalAstException("invalid identifier ${ident.nameInSource}")
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isIOAddress((decl.value as NumericLiteralValue).number.toUInt())
else

View File

@ -27,4 +27,6 @@ interface IVariablesAndConsts {
val subroutineVars: Map<Subroutine, Set<StaticVariable>>
val subroutineConsts: Map<Subroutine, Set<ConstantNumberSymbol>>
val subroutineMemvars: Map<Subroutine, Set<MemoryMappedVariable>>
fun addIfUnknown(definingBlock: Block, variable: VarDecl)
}

View File

@ -4,7 +4,6 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- (newvaralloc) UnusedCodeRemover after(decl: VarDecl): fix that vars defined in a library can also safely be removed if unused. Currently this breaks programs such as textelite (due to diskio.save().end_address ?)
- check that retval_interm_* are not in the varallocation if they're not used
- make it so that subroutine parameters as variables can again be allocated in ZP, if there's still space