proper initialization of block-level global variables

This commit is contained in:
Irmen de Jong 2020-03-22 22:47:05 +01:00
parent f0d4c3aba9
commit 8bfa2c4c02
13 changed files with 160 additions and 169 deletions

View File

@ -6,7 +6,6 @@ import prog8.ast.processing.*
import prog8.ast.statements.Block
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilationOptions
import prog8.compiler.target.AsmInitialValuesGatherer
import prog8.compiler.target.AsmVariablePreparer
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
@ -22,12 +21,6 @@ internal fun Program.prepareAsmVariables(errors: ErrorReporter) {
mover.applyModifications()
}
internal fun Program.gatherInitialValues(): Map<Block, Map<String, VarDecl>> {
val gather = AsmInitialValuesGatherer(this)
gather.visit(this)
return gather.initialValues
}
internal fun Program.reorderStatements() {
val initvalueCreator = AddressOfInserter(this)
initvalueCreator.visit(this)

View File

@ -376,6 +376,9 @@ internal class AstChecker(private val program: Program,
}
}
if(assignment.value.inferType(program) != assignment.target.inferType(program, assignment))
errors.err("assignment value is of different type as the target", assignment.value.position)
super.visit(assignment)
}
@ -502,37 +505,12 @@ internal class AstChecker(private val program: Program,
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
err("struct can not be in zeropage")
}
if (decl.value == null) {
when {
decl.datatype in NumericDatatypes -> {
// initialize numeric var with value zero by default.
val litVal =
when (decl.datatype) {
in ByteDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position)
in WordDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position)
else -> NumericLiteralValue(decl.datatype, 0.0, decl.position)
}
litVal.parent = decl
decl.value = litVal
}
decl.datatype == DataType.STRUCT -> {
// structs may be initialized with an array, but it's okay to not initialize them as well.
}
decl.type == VarDeclType.VAR -> {
if(decl.datatype in ArrayDatatypes) {
// array declaration can have an optional initialization value
// if it is absent, the size must be given, which should have been checked earlier
if(decl.value==null && decl.arraysize==null)
throw FatalAstException("array init check failed")
}
}
else -> err("var/const declaration needs a compile-time constant initializer value for type ${decl.datatype}")
// const fold should have provided it!
}
super.visit(decl)
return
}
when(decl.value) {
null -> {
// a vardecl without an initial value, don't bother with the rest
return super.visit(decl)
}
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
is StringLiteralValue -> {
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
@ -565,6 +543,8 @@ internal class AstChecker(private val program: Program,
return
}
}
} else {
errors.err("struct literal is wrong type to initialize this variable", decl.value!!.position)
}
}
else -> {
@ -602,6 +582,10 @@ internal class AstChecker(private val program: Program,
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
throw FatalAstException("initialisation value $declValue is of different type (${declValue.inferType(program)} as the variable (${decl.datatype}) at ${decl.position}")
super.visit(decl)
}

View File

@ -233,6 +233,15 @@ class VarDecl(val type: VarDeclType,
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only determine default zero value for a numeric type")
}
}
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
@ -274,20 +283,7 @@ class VarDecl(val type: VarDeclType,
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
fun zeroElementValue() = defaultZero(declaredDatatype, position)
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {

View File

@ -120,11 +120,9 @@ fun compileProgram(filepath: Path,
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.prepareAsmVariables(errors)
errors.handle()
val initialValues = programAst.gatherInitialValues()
val assembly = CompilationTarget.asmGenerator(
programAst,
zeropage,
initialValues,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)

View File

@ -1,31 +0,0 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.Block
import prog8.ast.statements.VarDecl
internal class AsmInitialValuesGatherer(val program: Program): IAstVisitor {
val initialValues = mutableMapOf<Block, MutableMap<String, VarDecl>>()
override fun visit(decl: VarDecl) {
// collect all variables that have an initialisation value
super.visit(decl)
val declValue = decl.value
if(declValue!=null
&& decl.type== VarDeclType.VAR
&& decl.datatype in NumericDatatypes
&& declValue.constValue(program)!=null) {
val block = decl.definingBlock()
var blockInits = initialValues[block]
if(blockInits==null) {
blockInits = mutableMapOf()
initialValues[block] = blockInits
}
blockInits[decl.makeScopedName(decl.name)] = decl
}
}
}

View File

@ -3,6 +3,8 @@ package prog8.compiler.target
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AnonymousScope
@ -12,6 +14,14 @@ import prog8.ast.statements.VarDecl
class AsmVariablePreparer(val program: Program, val errors: ErrorReporter): AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.value==null && decl.type==VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return emptyList()
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()

View File

@ -8,15 +8,12 @@ import prog8.compiler.Zeropage
import java.nio.file.Path
typealias InitialValues = Map<Block, Map<String, VarDecl>>
internal interface CompilationTarget {
companion object {
lateinit var name: String
lateinit var machine: IMachineDefinition
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
lateinit var asmGenerator: (Program, Zeropage, InitialValues, CompilationOptions, Path) -> IAssemblyGenerator
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
}
}

View File

@ -9,7 +9,6 @@ import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.InitialValues
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
@ -28,7 +27,6 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
private val zeropage: Zeropage,
private val initialValues: InitialValues,
private val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator {
@ -44,6 +42,7 @@ internal class AsmGen(private val program: Program,
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>()
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
@ -127,8 +126,11 @@ internal class AsmGen(private val program: Program,
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
program.allBlocks().filter { it in initialValues }.forEach { out(" jsr ${it.name}.prog8_init_vars") }
out(" ; initialize the variables in each block that has globals")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out(" clc")
when (zeropage.exitProgramStrategy) {
@ -175,10 +177,10 @@ internal class AsmGen(private val program: Program,
// if any global vars need to be initialized, generate a subroutine that does this
// it will be called from program init.
if(block in initialValues) {
if(block in blockLevelVarInits) {
out("prog8_init_vars\t.proc\n")
initialValues.getValue(block).forEach { (scopedName, decl) ->
val scopedFullName = scopedName.split('.')
blockLevelVarInits.getValue(block).forEach { decl ->
val scopedFullName = decl.makeScopedName(decl.name).split('.')
require(scopedFullName.first()==block.name)
val target = AssignTarget(null, IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
val assign = Assignment(target, null, decl.value!!, decl.position)
@ -314,7 +316,7 @@ internal class AsmGen(private val program: Program,
(decl.value as ArrayLiteralValue).value
else {
// no init value, use zeros
val zero = decl.asDefaultValueDecl(decl.parent).value!!
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
val floatFills = array.map {
@ -389,7 +391,7 @@ internal class AsmGen(private val program: Program,
(decl.value as ArrayLiteralValue).value
else {
// no array init value specified, use a list of zeros
val zero = decl.asDefaultValueDecl(decl.parent).value!!
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
return when (decl.datatype) {
@ -416,7 +418,7 @@ internal class AsmGen(private val program: Program,
(decl.value as ArrayLiteralValue).value
else {
// no array init value specified, use a list of zeros
val zero = decl.asDefaultValueDecl(decl.parent).value!!
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
return when (decl.datatype) {
@ -602,7 +604,8 @@ internal class AsmGen(private val program: Program,
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
is VarDecl, is StructDecl, is NopStatement -> {}
is VarDecl -> translate(stmt)
is StructDecl, is NopStatement -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
@ -820,6 +823,27 @@ internal class AsmGen(private val program: Program,
}
}
private fun translate(stmt: VarDecl) {
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
// generate an assignment statement to (re)initialize the variable's value.
// if the vardecl is not in a subroutine however, we have to initialize it globally.
if(stmt.definingSubroutine()==null) {
val block = stmt.definingBlock()
var inits = blockLevelVarInits[block]
if(inits==null) {
inits = mutableSetOf()
blockLevelVarInits[block] = inits
}
inits.add(stmt)
} else {
val target = AssignTarget(null, IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
val assign = Assignment(target, null, stmt.value!!, stmt.position)
assign.linkParents(stmt.parent)
translate(assign)
}
}
}
private fun translate(stmt: Directive) {
when(stmt.directive) {
"%asminclude" -> {

View File

@ -140,6 +140,13 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
decl.value = declValue.cast(decl.datatype)
}
return super.visit(decl)
}

View File

@ -2,44 +2,23 @@
%zeropage basicsafe
; This example computes the first 20 values of the Fibonacci sequence.
; It uses the feature that variables that don't have an initialization value,
; will retain their previous value over multiple invocations of the program or subroutine.
; This is extremely handy for the Fibonacci sequence because it is defined
; in terms of 'the next value is the sum of the previous two values'
main {
sub start() {
c64scr.print("fibonacci sequence\n")
fib_setup()
for A in 0 to 20 {
c64scr.print_uw(fib_next())
c64.CHROUT('\n')
}
check_eval_stack()
}
sub fib_setup() {
; (re)start the sequence
main.fib_next.prev=0
main.fib_next.current=1
}
uword fib_prev = 0 ; TODO fix initialization of block-global vars (outside of a subroutine)
uword fib_current = 1 ; TODO fix initialization of block-global vars (outside of a subroutine)
sub fib_next() -> uword {
uword prev ; no init value so will retain previous value
uword current ; no init value so will retain previous value
uword new = current + prev
prev = current
current = new
return prev
uword new = fib_current + fib_prev
fib_prev = fib_current
fib_current = new
return fib_prev
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("stack x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -8,10 +8,14 @@ main {
sub start() {
c64scr.print("mid-point\ncircle\n and\nbresenham\nline\nalgorithms.\n")
ubyte r
for r in 3 to 12 step 3 {
circle(20, 12, r)
}
circle(20, 12, 6)
circle(20, 12, 6) ; TODO FIX ERROR IN LOCALS
circle(20, 12, 6) ; TODO FIX ERROR IN LOCALS
; ubyte r
; for r in 3 to 12 step 3 {
; circle(20, 12, r)
; }
c64scr.print("enter for disc:")
void c64.CHRIN()
@ -108,6 +112,13 @@ main {
byte y = 0
byte decisionOver2 = 1-x
c64scr.print_b(x)
c64.CHROUT(',')
c64scr.print_b(y)
c64.CHROUT(',')
c64scr.print_b(decisionOver2)
c64.CHROUT('\n')
while x>=y {
c64scr.setcc(xcenter + x as ubyte, ycenter + y as ubyte, 81, 1)
c64scr.setcc(xcenter - x as ubyte, ycenter + y as ubyte, 81, 2)

View File

@ -5,45 +5,67 @@
main {
sub subje() {
ubyte yy=33 ; TODO reinitialize var here
ubyte zz ; TODO reinitialize var here
ubyte[5] array1 = [1,2,3,4,5]
c64scr.print_ub(yy)
c64.CHROUT(',')
c64scr.print_ub(zz)
c64.CHROUT('\n')
yy++
A=zz
Y=array1[2]
}
sub start() {
ubyte ub1
ubyte ub2 = 99
uword uw1
uword uw2 = 9999
ubyte[5] array1
ubyte[5] array2 = [22,33,44,55,66]
ubyte zz2
A=zz2
subje()
subje()
subje()
subje()
return
c64scr.print_ub(ub1)
c64.CHROUT(',')
c64scr.print_ub(ub2)
c64.CHROUT(',')
c64scr.print_uw(uw1)
c64.CHROUT(',')
c64scr.print_uw(uw2)
c64.CHROUT(',')
c64scr.print_ub(array1[0])
c64.CHROUT(',')
c64scr.print_ub(array2[0])
c64.CHROUT('\n')
ub1++
ub2++
uw1++
uw2++
array1[0]++
array2[0]++
c64scr.print_ub(ub1)
c64.CHROUT(',')
c64scr.print_ub(ub2)
c64.CHROUT(',')
c64scr.print_uw(uw1)
c64.CHROUT(',')
c64scr.print_uw(uw2)
c64.CHROUT(',')
c64scr.print_ub(array1[0])
c64.CHROUT(',')
c64scr.print_ub(array2[0])
c64.CHROUT('\n')
; ubyte ub1
; ubyte ub2 = 99
; uword uw1
; uword uw2 = 9999
; ubyte[5] array1
; ubyte[5] array2 = [22,33,44,55,66]
;
; c64scr.print_ub(ub1)
; c64.CHROUT(',')
; c64scr.print_ub(ub2)
; c64.CHROUT(',')
; c64scr.print_uw(uw1)
; c64.CHROUT(',')
; c64scr.print_uw(uw2)
; c64.CHROUT(',')
; c64scr.print_ub(array1[0])
; c64.CHROUT(',')
; c64scr.print_ub(array2[0])
; c64.CHROUT('\n')
;
; ub1++
; ub2++
; uw1++
; uw2++
; array1[0]++
; array2[0]++
;
; c64scr.print_ub(ub1)
; c64.CHROUT(',')
; c64scr.print_ub(ub2)
; c64.CHROUT(',')
; c64scr.print_uw(uw1)
; c64.CHROUT(',')
; c64scr.print_uw(uw2)
; c64.CHROUT(',')
; c64scr.print_ub(array1[0])
; c64.CHROUT(',')
; c64scr.print_ub(array2[0])
; c64.CHROUT('\n')
}
}

View File

@ -46,8 +46,9 @@ main {
irq {
ubyte angle
sub irq() {
ubyte angle ; no initialization value so it keeps the previous one.
ubyte @zp spri
c64.EXTCOL--