From d2cc7ccdfa5d596ba93c3ce2eb449a54d58cfa99 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 23 Apr 2025 14:28:43 +0200 Subject: [PATCH] remove redundant variable=0 initializations (BSS clear takes care of them) --- .../codegen/cpu6502/ProgramAndVarsGen.kt | 4 +- .../prog8/codegen/intermediate/IRCodeGen.kt | 4 +- .../astprocessing/SimplifiedAstMaker.kt | 3 ++ .../astprocessing/StatementReorderer.kt | 14 ++++--- compiler/test/codegeneration/TestVariables.kt | 27 +++++-------- docs/source/todo.rst | 1 + examples/test.p8 | 40 ++++++++++++------- simpleAst/src/prog8/code/SymbolTable.kt | 2 + .../src/prog8/vm/VmProgramLoader.kt | 13 +++++- 9 files changed, 69 insertions(+), 39 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt index d127b5bc6..74804506a 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt @@ -353,7 +353,9 @@ internal class ProgramAndVarsGen( initializers.forEach { assign -> if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name)) asmgen.translate(assign) - // the other variables that should be set to zero are done so as part of the BSS section. + else + throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear") + // the other variables that should be set to zero are done so as part of the BSS section clear. } asmgen.out(" rts\n .bend") } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 8f1121c4a..db64abb43 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -64,15 +64,17 @@ class IRCodeGen( if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) { val block = variable.parent.astNode as PtBlock val initialization = (block.children.firstOrNull { - it is PtAssignment && it.target.identifier?.name==variable.scopedName + it is PtAssignment && it.isVarInitializer && it.target.identifier?.name==variable.scopedName } as PtAssignment?) val initValue = initialization?.value when(initValue){ is PtBool -> { + require(initValue.asInt()!=0) { "boolean var should not be initialized with false, it wil be set to false as part of BSS clear, initializer=$initialization" } variable.setOnetimeInitNumeric(initValue.asInt().toDouble()) initsToRemove += block to initialization } is PtNumber -> { + require(initValue.number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" } variable.setOnetimeInitNumeric(initValue.number) initsToRemove += block to initialization } diff --git a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt index ce67abb61..10e1ec082 100644 --- a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt @@ -164,6 +164,9 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro } } + if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0) + throw FatalAstException("should not have a redundant block-level variable=0 assignment; it will be zeroed as part of BSS clear") + val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT) val multi = srcAssign.target.multi if(multi==null) { diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index 92c5d24c1..c3002c009 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -52,7 +52,8 @@ internal class StatementReorderer( if (decl.datatype.isNumericOrBool) { if(decl !in declsProcessedWithInitAssignment) { declsProcessedWithInitAssignment.add(decl) - if (decl.value == null) { + if (decl.value == null || decl.value?.constValue(program)?.number==0.0) { + decl.value = null if (decl.origin==VarDeclOrigin.USERCODE && decl.allowInitializeWithZero) { if(decl.dirty) { // no initialization at all! @@ -62,11 +63,9 @@ internal class StatementReorderer( // unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar). // 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 - val canskip = canSkipInitializationWith0(decl) - if (!canskip) { + // (this explicit initialization assignment to 0 is not required for global variables in the block scope, these are zeroed as part of the BSS section clear) + if (!canSkipInitializationWith0(decl)) { // Add assignment to initialize with zero - // Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later. val identifier = IdentifierReference(listOf(decl.name), decl.position) val assignzero = Assignment(AssignTarget(identifier, null, null, null, false, decl.position), decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position) @@ -115,6 +114,11 @@ internal class StatementReorderer( } private fun canSkipInitializationWith0(decl: VarDecl): Boolean { + // if the variable is declared in a block, we can omit the init with 0 because + // the variable will be initialized to zero when the BSS section is cleared as a whole. + if(decl.parent is Block) + return true + // if there is an assignment to the variable below it (regular assign, or For loop), // and there is nothing important in between, we can skip the initialization. val statements = (decl.parent as? IStatementContainer)?.statements ?: return false diff --git a/compiler/test/codegeneration/TestVariables.kt b/compiler/test/codegeneration/TestVariables.kt index d303a0319..203c192b9 100644 --- a/compiler/test/codegeneration/TestVariables.kt +++ b/compiler/test/codegeneration/TestVariables.kt @@ -125,29 +125,24 @@ main { val result = compileText(C64Target(), true, src, outputDir, writeAssembly = true)!!.compilerAst val main = result.allBlocks.first { it.name=="main" } - main.statements.size shouldBe 17 + main.statements.size shouldBe 15 val assigns = main.statements.filterIsInstance() - assigns[0].target.identifier?.nameInSource shouldBe listOf("value1") - assigns[1].target.identifier?.nameInSource shouldBe listOf("value2") - assigns[2].target.identifier?.nameInSource shouldBe listOf("value3") - assigns[3].target.identifier?.nameInSource shouldBe listOf("value4") - assigns[4].target.identifier?.nameInSource shouldBe listOf("value5") - assigns[5].target.identifier?.nameInSource shouldBe listOf("value6") - assigns[6].target.identifier?.nameInSource shouldBe listOf("value7") + assigns.size shouldBe 5 + assigns[0].target.identifier?.nameInSource shouldBe listOf("value2") + assigns[1].target.identifier?.nameInSource shouldBe listOf("value3") + assigns[2].target.identifier?.nameInSource shouldBe listOf("value5") + assigns[3].target.identifier?.nameInSource shouldBe listOf("value6") + assigns[4].target.identifier?.nameInSource shouldBe listOf("value7") assigns[0].origin shouldBe AssignmentOrigin.VARINIT assigns[1].origin shouldBe AssignmentOrigin.VARINIT assigns[2].origin shouldBe AssignmentOrigin.VARINIT assigns[3].origin shouldBe AssignmentOrigin.VARINIT assigns[4].origin shouldBe AssignmentOrigin.VARINIT - assigns[5].origin shouldBe AssignmentOrigin.VARINIT - assigns[6].origin shouldBe AssignmentOrigin.VARINIT - assigns[0].value.constValue(result)?.number shouldBe 0 - assigns[1].value.constValue(result)?.number shouldBe 1 - assigns[2].value.constValue(result)?.number shouldBe 1 - assigns[3].value.constValue(result)?.number shouldBe 0 + assigns[0].value.constValue(result)?.asBooleanValue shouldBe true + assigns[1].value.constValue(result)?.asBooleanValue shouldBe true + assigns[2].value.constValue(result) shouldBe null + assigns[3].value.constValue(result)?.number shouldBe 4242 assigns[4].value.constValue(result) shouldBe null - assigns[5].value.constValue(result)?.number shouldBe 4242 - assigns[6].value.constValue(result) shouldBe null } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 2f4876652..7a5252986 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -7,6 +7,7 @@ TODO Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ +- randrange() function can be optimized by just multiplying by the range and bit shifting down? - romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?) - Kotlin: can we use inline value classes in certain spots? - add float support to the configurable compiler targets diff --git a/examples/test.p8 b/examples/test.p8 index f8e4d952a..17fa4f8eb 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,25 +1,37 @@ %import textio -%zeropage basicsafe +%zeropage dontuse %option no_sysinit main { + ubyte @shared ub_global + uword @shared uw_global = 0 + bool[4] @shared bool_array_global sub start() { - uword[] @nosplit ptrs = [one, two, three] + ubyte @shared ub_scoped + uword @shared uw_scoped = 0 + bool[4] @shared bool_array_scoped - ubyte @shared x =1 + dump() - goto ptrs[x] - } + ub_scoped++ + uw_scoped++ + bool_array_scoped[2]=true + ub_global++ + uw_global++ + bool_array_global[2]=true - sub one() { - txt.print("one\n") - } - sub two() { - txt.print("two\n") - } - sub three() { - txt.print("three\n") - } + dump() + sub dump() { + txt.print_ub(ub_global) + txt.print_uw(uw_global) + txt.print_bool(bool_array_global[2]) + txt.nl() + txt.print_ub(ub_scoped) + txt.print_uw(uw_scoped) + txt.print_bool(bool_array_scoped[2]) + txt.nl() + } + } } diff --git a/simpleAst/src/prog8/code/SymbolTable.kt b/simpleAst/src/prog8/code/SymbolTable.kt index ca5fffaad..144ea1bc2 100644 --- a/simpleAst/src/prog8/code/SymbolTable.kt +++ b/simpleAst/src/prog8/code/SymbolTable.kt @@ -198,6 +198,8 @@ class StStaticVariable(name: String, // This has to do with the way Prog8 does the (re)initialization of such variables: via code assignment statements. // Certain codegens might want to put them back into the variable directly. // For strings and arrays this doesn't occur - these are always already specced at creation time. + + require(number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear" } initializationNumericValue = number } diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index 25c808102..a1b34e65b 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -1,10 +1,10 @@ package prog8.vm import prog8.Either +import prog8.code.core.DataType +import prog8.intermediate.* import prog8.left import prog8.right -import prog8.intermediate.* -import prog8.code.core.DataType class VmProgramLoader { private val placeholders = mutableMapOf, String>() // program chunk+index to symbolname @@ -223,6 +223,15 @@ class VmProgramLoader { else -> throw IRParseException("invalid dt") } } + } else { + when { + variable.dt.isUnsignedByte || variable.dt.isBool -> memory.setUB(addr, 0u) + variable.dt.isSignedByte -> memory.setSB(addr, 0) + variable.dt.isUnsignedWord -> memory.setUW(addr, 0u) + variable.dt.isSignedWord -> memory.setSW(addr, 0) + variable.dt.isFloat -> memory.setFloat(addr, 0.0) + else -> throw IRParseException("invalid dt") + } } }