diff --git a/compiler/src/prog8/ast/base/Extensions.kt b/compiler/src/prog8/ast/base/Extensions.kt index 1109e0b37..2bbe3c5ef 100644 --- a/compiler/src/prog8/ast/base/Extensions.kt +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -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> { - val gather = AsmInitialValuesGatherer(this) - gather.visit(this) - return gather.initialValues -} - internal fun Program.reorderStatements() { val initvalueCreator = AddressOfInserter(this) initvalueCreator.visit(this) diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index 25c58c2ed..97cdaf901 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -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) } diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt index f3ac56cdc..7fb3fbcf1 100644 --- a/compiler/src/prog8/ast/statements/AstStatements.kt +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -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() // 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 { val result = struct!!.statements.withIndex().map { diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index 4c3abd976..2a9dcf08e 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -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) diff --git a/compiler/src/prog8/compiler/target/AsmInitialValuesGatherer.kt b/compiler/src/prog8/compiler/target/AsmInitialValuesGatherer.kt deleted file mode 100644 index d36c5de22..000000000 --- a/compiler/src/prog8/compiler/target/AsmInitialValuesGatherer.kt +++ /dev/null @@ -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>() - - 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 - } - } -} diff --git a/compiler/src/prog8/compiler/target/AsmVariablePreparer.kt b/compiler/src/prog8/compiler/target/AsmVariablePreparer.kt index 075bfded3..d6ece789b 100644 --- a/compiler/src/prog8/compiler/target/AsmVariablePreparer.kt +++ b/compiler/src/prog8/compiler/target/AsmVariablePreparer.kt @@ -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 { + 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 { val decls = scope.statements.filterIsInstance() val sub = scope.definingSubroutine() diff --git a/compiler/src/prog8/compiler/target/CompilationTarget.kt b/compiler/src/prog8/compiler/target/CompilationTarget.kt index 703ae5f3b..dc86d3a2d 100644 --- a/compiler/src/prog8/compiler/target/CompilationTarget.kt +++ b/compiler/src/prog8/compiler/target/CompilationTarget.kt @@ -8,15 +8,12 @@ import prog8.compiler.Zeropage import java.nio.file.Path -typealias InitialValues = Map> - - internal interface CompilationTarget { companion object { lateinit var name: String lateinit var machine: IMachineDefinition lateinit var encodeString: (str: String, altEncoding: Boolean) -> List lateinit var decodeString: (bytes: List, altEncoding: Boolean) -> String - lateinit var asmGenerator: (Program, Zeropage, InitialValues, CompilationOptions, Path) -> IAssemblyGenerator + lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator } } diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt index 21826b390..0f74a136f 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt @@ -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() internal val loopContinueLabels = ArrayDeque() + internal val blockLevelVarInits = mutableMapOf>() 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().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" -> { diff --git a/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt index ded01ba7c..863058c79 100644 --- a/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -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) } diff --git a/examples/fibonacci.p8 b/examples/fibonacci.p8 index be86c3a8a..93b6667d2 100644 --- a/examples/fibonacci.p8 +++ b/examples/fibonacci.p8 @@ -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") - } - } - } diff --git a/examples/lines-circles.p8 b/examples/lines-circles.p8 index 5dc0f3999..67c8963b9 100644 --- a/examples/lines-circles.p8 +++ b/examples/lines-circles.p8 @@ -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) diff --git a/examples/test.p8 b/examples/test.p8 index 032dcbce1..679408eaa 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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') } } diff --git a/examples/wizzine.p8 b/examples/wizzine.p8 index 61f85907b..182814f24 100644 --- a/examples/wizzine.p8 +++ b/examples/wizzine.p8 @@ -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--