changed @initonce to @dirty and meaning is now: not initialized at all.

This commit is contained in:
Irmen de Jong 2024-11-08 22:05:31 +01:00
parent 3ee6058524
commit 64164c1c72
16 changed files with 105 additions and 112 deletions

View File

@ -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<Expression>(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.

View File

@ -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)
}
}

View File

@ -27,8 +27,7 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
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

View File

@ -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,

View File

@ -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
)
}

View File

@ -43,15 +43,16 @@ internal class StatementReorderer(
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
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)

View File

@ -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)

View File

@ -328,8 +328,8 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
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()
)
}

View File

@ -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
}

View File

@ -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

View File

@ -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.
========== ======

View File

@ -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.

View File

@ -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()
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])
testdirty()
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]++
}
}

View File

@ -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 ;

View File

@ -12,7 +12,7 @@
<option name="HAS_STRING_ESCAPES" value="true" />
</options>
<keywords keywords="&amp;;-&gt;;@;and;as;asmsub;break;clobbers;continue;do;downto;else;extsub;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@initonce;@nozp;@requirezp;@shared;@split;@zp;atascii:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nozp;@requirezp;@shared;@split;@zp;atascii:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords3 keywords="bool;byte;const;float;str;ubyte;uword;void;word" />
<keywords4 keywords="abs;call;callfar;callfar2;clamp;cmp;defer;divmod;len;lsb;max;memory;min;mkword;msb;peek;peekf;peekw;poke;pokef;pokew;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt" />
</highlighting>

View File

@ -43,7 +43,7 @@ syn region prog8ArrayType matchgroup=prog8Type
\ start="\<\%(u\?byte\|u\?word\|float\|str\|bool\)\[" end="\]"
\ transparent
syn keyword prog8StorageClass const
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@bank\|@shared\|@split\|@nozp\|@requirezp\|@align64\|@alignword\|@alignpage\|@initonce\)\>"
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@bank\|@shared\|@split\|@nozp\|@requirezp\|@align64\|@alignword\|@alignpage\|@dirty\)\>"
syn region prog8Block start="{" end="}" transparent
syn region prog8Expression start="(" end=")" transparent