diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index 5d303a04c..7ab3be0d8 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -76,7 +76,7 @@ class VarConstantValueTypeAdjuster( if (declValue != null) { // variable is never written to, so it can be replaced with a constant, IF the value is a constant errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position) - val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.initOnce, decl.position) + val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.dirty, decl.position) decl.value = null return listOf( IAstModification.ReplaceNode(decl, const, parent) @@ -96,7 +96,7 @@ class VarConstantValueTypeAdjuster( } // variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position) - val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.initOnce, decl.position) + val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.dirty, decl.position) return listOf( IAstModification.ReplaceNode(decl, const, parent), IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer) @@ -419,21 +419,6 @@ internal class ConstantIdentifierReplacer( return null } - if (decl.isArray && decl.initOnce) { - if (decl.value == null) { - // initonce array without initialization value, make an array of just zeros - val size = decl.arraysize?.constIndex() - if(size!=null) { - val zeros = Array(size) { decl.zeroElementValue() } - val initvalue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), zeros, decl.position) - decl.value = initvalue - initvalue.linkParents(decl) - } - } else { - errors.err("arrays with an initialization value already are initialized only once by default", decl.position) - } - } - val rangeExpr = decl.value as? RangeExpression ?: return null // convert the initializer range expression from a range, to an actual array literal. diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 72dc6b9ff..237eab252 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -920,14 +920,14 @@ internal class AstChecker(private val program: Program, } - if(decl.datatype==DataType.STR) { - if(!decl.initOnce) - throw FatalAstException("string vars must be initonce") - } - - if (decl.initOnce) { - if (decl.datatype != DataType.STR) { - errors.warn("non-string initonce variable: value will not be reset in subsequent subroutine invocations", decl.position) + if (decl.dirty) { + if(decl.datatype==DataType.STR) + errors.err("string variables cannot be @dirty", decl.position) + else { + if(decl.value==null) + errors.info("dirty variable: initial value will be undefined", decl.position) + else + errors.err("dirty variable can't have initialization value", decl.position) } } diff --git a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt index a2a9a2932..e12c964c7 100644 --- a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt @@ -27,8 +27,7 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co override fun after(decl: VarDecl, parent: Node): Iterable { if (decl.type == VarDeclType.VAR && decl.value != null && (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL)) { - if(!decl.initOnce) - throw InternalCompilerException("vardecls for non-initonce variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl") + throw InternalCompilerException("vardecls with initial numerical value, should have been rewritten as plain vardecl + assignment $decl") } return noModifications diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index 94cb42ae4..94bd1f7df 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -528,8 +528,10 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr when(srcVar.type) { VarDeclType.VAR -> { val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null - if(srcVar.initOnce && value==null) - throw FatalAstException("initonce without value $srcVar") + if(srcVar.dirty && value!=null) + throw FatalAstException("dirty with initializer value $srcVar") +// if(value==null && !srcVar.dirty) +// throw FatalAstException("no init value but not marked dirty $srcVar") return PtVariable( srcVar.name, srcVar.datatype, diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 4ff256676..a5e60f16c 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -180,7 +180,7 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro } return VarDecl( variable.type, variable.origin, normalDt, variable.zeropage, variable.arraysize, variable.name, emptyList(), - variable.value?.copy(), variable.sharedWithAsm, false, variable.alignment, variable.initOnce, variable.position + variable.value?.copy(), variable.sharedWithAsm, false, variable.alignment, variable.dirty, variable.position ) } diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index a6a265102..e459a78ce 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -43,15 +43,16 @@ internal class StatementReorderer( override fun after(decl: VarDecl, parent: Node): Iterable { if (decl.type == VarDeclType.VAR) { + if(decl.dirty && decl.value!=null) + errors.err("dirty variable can't have initialization value", decl.position) + if (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL) { if(decl !in declsProcessedWithInitAssignment) { declsProcessedWithInitAssignment.add(decl) if (decl.value == null) { if (decl.origin==VarDeclOrigin.USERCODE && decl.allowInitializeWithZero) { - if(decl.initOnce) { - val zerovalue = decl.zeroElementValue() - decl.value = zerovalue - zerovalue.linkParents(decl) + if(decl.dirty) { + // no initialization at all! return noModifications } // A numeric vardecl without an initial value is initialized with zero, @@ -72,9 +73,6 @@ internal class StatementReorderer( } } } else { - if(decl.initOnce) { - return noModifications - } // Transform the vardecl with initvalue to a plain vardecl + assignment // this allows for other optimizations to kick in. // So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99' @@ -231,7 +229,7 @@ internal class StatementReorderer( it.sharedWithAsm, it.splitArray, it.alignment, - it.initOnce, + it.dirty, it.position ) IAstModification.ReplaceNode(it, newvar, subroutine) diff --git a/compilerAst/src/prog8/ast/Program.kt b/compilerAst/src/prog8/ast/Program.kt index 861d17929..fd88dc470 100644 --- a/compilerAst/src/prog8/ast/Program.kt +++ b/compilerAst/src/prog8/ast/Program.kt @@ -87,7 +87,7 @@ class Program(val name: String, val varName = "string_${internedStringsBlock.statements.size}" val decl = VarDecl( VarDeclType.VAR, VarDeclOrigin.STRINGLITERAL, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, emptyList(), string, - sharedWithAsm = false, splitArray = false, alignment = 0u, initOnce = true, position = string.position + sharedWithAsm = false, splitArray = false, alignment = 0u, dirty = false, position = string.position ) internedStringsBlock.statements.add(decl) decl.linkParents(internedStringsBlock) diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 47da2b900..2f6e17dea 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -328,8 +328,8 @@ private fun Sub_paramsContext.toAst(): List = val options = it.decloptions() if(options.ALIGNPAGE().isNotEmpty() || options.ALIGNWORD().isNotEmpty()) throw SyntaxError("cannot use alignments on parameters", it.toPosition()) - if(options.INITONCE().isNotEmpty()) - throw SyntaxError("cannot use @initonce on parameters", it.toPosition()) + if(options.DIRTY().isNotEmpty()) + throw SyntaxError("cannot use @dirty on parameters", it.toPosition()) val zp = getZpOption(options) var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED if(it.ARRAYSIG()!=null || it.arrayindex()!=null) @@ -771,7 +771,7 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl options.SHARED().isNotEmpty(), split, if(alignword) 2u else if(align64) 64u else if(alignpage) 256u else 0u, - dt==DataType.STR || options.INITONCE().isNotEmpty(), + options.DIRTY().isNotEmpty(), toPosition() ) } diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 5081743a8..936390fb1 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -249,7 +249,7 @@ class VarDecl(val type: VarDeclType, val sharedWithAsm: Boolean, val splitArray: Boolean, val alignment: UInt, - val initOnce: Boolean, + val dirty: Boolean, override val position: Position) : Statement(), INamedStatement { override lateinit var parent: Node var allowInitializeWithZero = true @@ -263,7 +263,7 @@ class VarDecl(val type: VarDeclType, sharedWithAsm = false, splitArray = false, alignment = 0u, - initOnce = false, + dirty = false, position = param.position ) } @@ -273,15 +273,10 @@ class VarDecl(val type: VarDeclType, val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") } val arraysize = ArrayIndex.forArray(array) return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, arrayDt, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, emptyList(), array, - sharedWithAsm = false, splitArray = splitArray, alignment = 0u, initOnce = false, position = array.position) + sharedWithAsm = false, splitArray = splitArray, alignment = 0u, dirty = false, position = array.position) } } - init { - if(datatype==DataType.STR && origin!=VarDeclOrigin.SUBROUTINEPARAM) - require(initOnce) { "string variable must be initonce" } - } - init { if(datatype in SplitWordArrayTypes) require(splitArray) @@ -320,7 +315,7 @@ class VarDecl(val type: VarDeclType, if(names.size>1) throw FatalAstException("should not copy a vardecl that still has multiple names") val copy = VarDecl(type, origin, datatype, zeropage, arraysize?.copy(), name, names, value?.copy(), - sharedWithAsm, splitArray, alignment, initOnce, position) + sharedWithAsm, splitArray, alignment, dirty, position) copy.allowInitializeWithZero = this.allowInitializeWithZero return copy } @@ -335,19 +330,19 @@ class VarDecl(val type: VarDeclType, // just copy the initialization value to a separate vardecl for each component return names.map { val copy = VarDecl(type, origin, datatype, zeropage, arraysize?.copy(), it, emptyList(), value?.copy(), - sharedWithAsm, splitArray, alignment, initOnce, position) + sharedWithAsm, splitArray, alignment, dirty, position) copy.allowInitializeWithZero = this.allowInitializeWithZero copy } } else { // evaluate the value once in the vardecl for the first component, and set the other components to the first val first = VarDecl(type, origin, datatype, zeropage, arraysize?.copy(), names[0], emptyList(), value?.copy(), - sharedWithAsm, splitArray, alignment, initOnce, position) + sharedWithAsm, splitArray, alignment, dirty, position) first.allowInitializeWithZero = this.allowInitializeWithZero val firstVar = firstVarAsValue(first) return listOf(first) + names.drop(1 ).map { val copy = VarDecl(type, origin, datatype, zeropage, arraysize?.copy(), it, emptyList(), firstVar.copy(), - sharedWithAsm, splitArray, alignment, initOnce, position) + sharedWithAsm, splitArray, alignment, dirty, position) copy.allowInitializeWithZero = this.allowInitializeWithZero copy } diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 20f722bb5..d7db1f7e2 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -236,6 +236,15 @@ when assembling the rest of the code). Example:: byte @shared assemblyVariable = 42 +**uninitialized variables:** +All variables will be initialized by prog8 at startup, they'll get their assigned initialization value, or be cleared to zero. +This (re)initialization is also done on each subroutine entry for the variables declared in the subroutine. +There may be certain scenarios where this initialization is redundant and/or where you want to avoid the overhead of it +You can do so by using the ``@dirty`` tag on the variable declaration. +This means that the variable will *not* be (re)initialized by Prog8 and that its value is undefined. +You have to assign it a value yourself first, before using the variable. If you don't do that, the value can be anything, so beware. + + **memory alignment:** A string or array variable can be aligned to a couple of possible interval sizes in memory. The use for this is very situational, but two examples are: sprite data for the C64 that needs diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 4f288e1f9..451b08911 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -385,6 +385,7 @@ Tag Effect @alignword aligns string or array variable on an even memory address @align64 aligns string or array variable on a 64 byte address interval (example: for C64 sprite data) @alignpage aligns string or array variable on a 256 byte address interval (example: to avoid page boundaries) +@dirty the variable won't be initialized by Prog8 which means that its value is undefined. You'll have to set it yourself before using the variable. Used to reduce overhead in certain scenarios. 🦶🔫 Footgun warning. ========== ====== diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e0fff641b..07f676035 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,9 +1,7 @@ TODO ==== -- BUG: fix @initonce for variables that end up in zeropage. They remain uninitialized altogether now. Fix it or don't allow it? -- add unit tests for @initonce variables -- add docs about variables with @initonce initialization +- add unit tests for @dirty variables for releasenotes: gfx2.width and gfx2.height got renamed as gfx_lores.WIDTH/HEIGHT or gfx_hires4.WIDTH/HEIGTH constants. Screen mode routines also renamed. diff --git a/examples/test.p8 b/examples/test.p8 index c7e34f172..ae8fb2c5e 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,65 +1,71 @@ +%import floats %import textio %option no_sysinit %zeropage basicsafe -; INIT ONCE tests +; DIRTY tests main { + uword @shared @dirty globw + uword @shared globwi = 4444 + float @shared @dirty globf + float @shared globfi = 4 + ubyte[5] @shared @dirty globarr1 + ubyte[] @shared globarr2 = [11,22,33,44,55] + sub start() { - uword w0 - uword @initonce w1 - uword @initonce w2 - uword @initonce w3 - uword @initonce w4 = 12345 - uword[4] wa - uword[] @shared wb = [1111,2222,3333,4444] - - dump() + testdirty() txt.nl() - w0++ - w1++ - w2++ - w3++ - w4++ - wa[1]++ - wa[2]++ - wa[3]++ - wb[0]++ - dump() + testdirty() txt.nl() - - repeat 10 { - footgun() - } - txt.nl() - - sub dump() { - txt.print_uw(w0) - txt.spc() - txt.print_uw(w1) - txt.spc() - txt.print_uw(w2) - txt.spc() - txt.print_uw(w3) - txt.spc() - txt.print_uw(w4) - txt.spc() - txt.print_uw(wa[1]) - txt.spc() - txt.print_uw(wa[2]) - txt.spc() - txt.print_uw(wa[3]) - txt.spc() - txt.print_uw(wb[0]) - txt.nl() - } } - sub footgun() { - ; TODO should just be a nonlocal variable outside of the subroutine...? - ubyte @shared @initonce @requirezp variable = 42 ; BUG: is never initialized now - txt.print_ub(variable) + sub testdirty() { + uword @shared @dirty locw + uword @shared locwi = 4444 + float @shared @dirty locf + float @shared locfi = 4.0 + ubyte[5] @shared @dirty locarr1 + ubyte[] @shared locarr2 = [11,22,33,44,55] + + txt.print("globals: ") + txt.print_uw(globw) txt.spc() - variable++ + floats.print(globf) + txt.print(" with init: ") + txt.print_uw(globwi) + txt.spc() + floats.print(globfi) + txt.print(" arrays: ") + txt.print_ub(globarr1[2]) + txt.spc() + txt.print_ub(globarr2[2]) + txt.print("\nlocals: ") + txt.print_uw(locw) + txt.spc() + floats.print(locf) + txt.print(" with init: ") + txt.print_uw(locwi) + txt.spc() + floats.print(locfi) + txt.print(" arrays: ") + txt.print_ub(locarr1[2]) + txt.spc() + txt.print_ub(locarr2[2]) + txt.nl() + + + globw++ + globwi++ + globf++ + globfi++ + globarr1[2]++ + globarr2[2]++ + locw++ + locwi++ + locf++ + locfi++ + locarr1[2]++ + locarr2[2]++ } } diff --git a/parser/antlr/Prog8ANTLR.g4 b/parser/antlr/Prog8ANTLR.g4 index c75d1b532..01b0cab2a 100644 --- a/parser/antlr/Prog8ANTLR.g4 +++ b/parser/antlr/Prog8ANTLR.g4 @@ -69,7 +69,7 @@ ALIGN64: '@align64' ; ALIGNPAGE: '@alignpage' ; -INITONCE: '@initonce' ; +DIRTY: '@dirty' ; ARRAYSIG : '[' [ \t]* ']' ; @@ -159,7 +159,7 @@ directivearg : stringliteral | identifier | integerliteral ; vardecl: datatype (arrayindex | ARRAYSIG)? decloptions identifier (',' identifier)* ; -decloptions: (SHARED | ZEROPAGE | ZEROPAGEREQUIRE | ZEROPAGENOT | SPLIT | ALIGNWORD | ALIGN64 | ALIGNPAGE | INITONCE)* ; +decloptions: (SHARED | ZEROPAGE | ZEROPAGEREQUIRE | ZEROPAGENOT | SPLIT | ALIGNWORD | ALIGN64 | ALIGNPAGE | DIRTY)* ; varinitializer : vardecl '=' expression ; diff --git a/syntax-files/IDEA/Prog8.xml b/syntax-files/IDEA/Prog8.xml index 7da3e5a60..350333a07 100644 --- a/syntax-files/IDEA/Prog8.xml +++ b/syntax-files/IDEA/Prog8.xml @@ -12,7 +12,7 @@