omit more redundant 0-initializations ("stz's")

This commit is contained in:
Irmen de Jong 2024-10-17 01:43:33 +02:00
parent 38ef394e15
commit a0cf1889a3
10 changed files with 123 additions and 110 deletions

View File

@ -172,7 +172,7 @@ class AstPreprocessor(val program: Program,
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val nextAssignment = decl.nextSibling() as? Assignment val nextAssignment = decl.nextSibling() as? Assignment
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) { if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
// check if it's a proper initializer assignment for the variable // check if the following assignment initializes the variable
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) { if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) {
if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource)) if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource))
nextAssignment.origin = AssignmentOrigin.VARINIT nextAssignment.origin = AssignmentOrigin.VARINIT

View File

@ -52,11 +52,8 @@ internal class StatementReorderer(
// This allows you to restart the program and have the same starting values of the variables // 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' // So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null decl.value = null
if(decl.findInitializer(program)!=null) val canskip = canSkipInitializationWith0(decl)
return noModifications // an initializer assignment for a vardecl is already here if (!canskip) {
val nextFor = decl.nextSibling() as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
if (!hasNextForWithThisLoopvar) {
// Add assignment to initialize with zero // 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. // 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 identifier = IdentifierReference(listOf(decl.name), decl.position)
@ -106,6 +103,62 @@ internal class StatementReorderer(
return noModifications return noModifications
} }
private fun canSkipInitializationWith0(decl: VarDecl): Boolean {
// 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
val following = statements.asSequence().dropWhile { it!==decl }.drop(1)
for(stmt in following) {
when(stmt) {
is Assignment -> {
if (!stmt.isAugmentable) {
val assignTgt = stmt.target.identifier?.targetVarDecl(program)
if (assignTgt == decl)
return true
}
return false
}
is ChainedAssignment -> {
var chained: ChainedAssignment? = stmt
while(chained!=null) {
val assignTgt = chained.target.identifier?.targetVarDecl(program)
if (assignTgt == decl)
return true
if(chained.nested is Assignment) {
if ((chained.nested as Assignment).target.identifier?.targetVarDecl(program) == decl)
return true
}
chained = chained.nested as? ChainedAssignment
}
}
is ForLoop -> return stmt.loopVar.nameInSource == listOf(decl.name)
is IFunctionCall,
is Jump,
is Break,
is BuiltinFunctionPlaceholder,
is ConditionalBranch,
is Continue,
is Return,
is Subroutine,
is InlineAssembly,
is Block,
is AnonymousScope,
is IfElse,
is RepeatLoop,
is UnrollLoop,
is UntilLoop,
is When,
is WhileLoop -> return false
else -> {}
}
}
return false
}
private fun directivesToTheTop(statements: MutableList<Statement>) { private fun directivesToTheTop(statements: MutableList<Statement>) {
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove} val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives.toSet()) statements.removeAll(directives.toSet())

View File

@ -6,8 +6,13 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Program
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclType import prog8.ast.statements.VarDeclType
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Encoding import prog8.code.core.Encoding
@ -22,6 +27,13 @@ import prog8tests.helpers.compileText
*/ */
class TestCompilerOnCharLit: FunSpec({ class TestCompilerOnCharLit: FunSpec({
fun findInitializer(vardecl: VarDecl, program: Program): Assignment? =
(vardecl.parent as IStatementContainer).statements
.asSequence()
.filterIsInstance<Assignment>()
.singleOrNull { it.origin== AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl(program) === vardecl }
test("testCharLitAsRomsubArg") { test("testCharLitAsRomsubArg") {
val platform = Cx16Target() val platform = Cx16Target()
val result = compileText(platform, false, """ val result = compileText(platform, false, """
@ -70,7 +82,7 @@ class TestCompilerOnCharLit: FunSpec({
withClue("initializer value should have been moved to separate assignment"){ withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null decl.value shouldBe null
} }
val assignInitialValue = decl.findInitializer(program)!! val assignInitialValue = findInitializer(decl, program)!!
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch") assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
withClue("char literal should have been replaced by ubyte literal") { withClue("char literal should have been replaced by ubyte literal") {
assignInitialValue.value shouldBe instanceOf<NumericLiteral>() assignInitialValue.value shouldBe instanceOf<NumericLiteral>()

View File

@ -504,9 +504,9 @@ main {
xx += 6 xx += 6
*/ */
val stmts = result.compilerAst.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7 stmts.size shouldBe 6
stmts.filterIsInstance<VarDecl>().size shouldBe 3 stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 4 stmts.filterIsInstance<Assignment>().size shouldBe 3
} }
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") { test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {

View File

@ -15,61 +15,6 @@ import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({ class TestIntermediateAst: FunSpec({
test("creation") {
val text="""
%import textio
%import graphics
main {
sub start() {
bool cc
ubyte dd
ubyte[] array = [1,2,3]
cc = 11 in array
dd = sqrt(lsb(dd))
}
}
"""
val target = C64Target()
val errors = ErrorReporterForTests()
val result = compileText(target, false, text, writeAssembly = false)!!
val ast = IntermediateAstMaker(result.compilerAst, errors).transform()
ast.name shouldBe result.compilerAst.name
ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found")
entry.children.size shouldBe 7
entry.name shouldBe "start"
entry.scopedName shouldBe "main.start"
val blocks = ast.allBlocks().toList()
blocks.size shouldBeGreaterThan 1
blocks[0].name shouldBe "main"
blocks[0].scopedName shouldBe "main"
val ccInit = entry.children[3] as PtAssignment
ccInit.target.identifier?.name shouldBe "main.start.cc"
(ccInit.value as PtBool).value shouldBe false
val ddInit = entry.children[4] as PtAssignment
ddInit.target.identifier?.name shouldBe "main.start.dd"
(ddInit.value as PtNumber).number shouldBe 0.0
val ccdecl = entry.children[0] as PtVariable
ccdecl.name shouldBe "cc"
ccdecl.scopedName shouldBe "main.start.cc"
ccdecl.type shouldBe DataType.BOOL
val dddecl = entry.children[1] as PtVariable
dddecl.name shouldBe "dd"
dddecl.scopedName shouldBe "main.start.dd"
dddecl.type shouldBe DataType.UBYTE
val arraydecl = entry.children[2] as IPtVariable
arraydecl.name shouldBe "array"
arraydecl.type shouldBe DataType.ARRAY_UB
val ccAssignV = (entry.children[5] as PtAssignment).value
ccAssignV shouldBe instanceOf<PtContainmentCheck>()
val ddAssignV = (entry.children[6] as PtAssignment).value
ddAssignV shouldBe instanceOf<PtFunctionCall>()
}
test("isSame on binaryExpressions") { test("isSame on binaryExpressions") {
val expr1 = PtBinaryExpression("/", DataType.UBYTE, Position.DUMMY) val expr1 = PtBinaryExpression("/", DataType.UBYTE, Position.DUMMY)
expr1.add(PtNumber(DataType.UBYTE, 1.0, Position.DUMMY)) expr1.add(PtNumber(DataType.UBYTE, 1.0, Position.DUMMY))

View File

@ -194,11 +194,11 @@ main {
val result = compileText(Cx16Target(), false, src, errors, true)!! val result = compileText(Cx16Target(), false, src, errors, true)!!
errors.errors.size shouldBe 0 errors.errors.size shouldBe 0
val start = result.codegenAst!!.entrypoint()!! val start = result.codegenAst!!.entrypoint()!!
start.children.size shouldBe 10 start.children.size shouldBe 9
val a1_1 = start.children[5] as PtAssignment val a1_1 = start.children[4] as PtAssignment
val a1_2 = start.children[6] as PtAssignment val a1_2 = start.children[5] as PtAssignment
val a1_3 = start.children[7] as PtAssignment val a1_3 = start.children[6] as PtAssignment
val a1_4 = start.children[8] as PtAssignment val a1_4 = start.children[7] as PtAssignment
a1_1.multiTarget shouldBe true a1_1.multiTarget shouldBe true
a1_2.multiTarget shouldBe true a1_2.multiTarget shouldBe true
a1_3.multiTarget shouldBe true a1_3.multiTarget shouldBe true

View File

@ -4,9 +4,11 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin import prog8.ast.statements.AssignmentOrigin
import prog8.code.ast.PtAssignment import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
@ -189,4 +191,35 @@ main {
assigns[5].value.constValue(result) shouldBe null assigns[5].value.constValue(result) shouldBe null
assigns[6].value.constValue(result) shouldBe null assigns[6].value.constValue(result) shouldBe null
} }
test("not inserting redundant 0-initializations") {
val src="""
main {
sub start() {
ubyte v0
ubyte v1
ubyte v2
ubyte v3
v0 = v1 = v2 = 99
for v3 in 10 to 20 {
cx16.r0L++
}
}
}"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!.compilerAst
val st = result.entrypoint.statements
st.size shouldBe 8
st[0] shouldBe instanceOf<VarDecl>()
st[1] shouldBe instanceOf<VarDecl>()
st[2] shouldBe instanceOf<VarDecl>()
st[3] shouldBe instanceOf<VarDecl>()
st[4] shouldBe instanceOf<Assignment>()
st[5] shouldBe instanceOf<Assignment>()
st[6] shouldBe instanceOf<Assignment>()
st[7] shouldBe instanceOf<ForLoop>()
(st[4] as Assignment).target.identifier?.nameInSource shouldBe listOf("v2")
(st[5] as Assignment).target.identifier?.nameInSource shouldBe listOf("v1")
(st[6] as Assignment).target.identifier?.nameInSource shouldBe listOf("v0")
}
}) })

View File

@ -290,12 +290,6 @@ class VarDecl(val type: VarDeclType,
return copy return copy
} }
fun findInitializer(program: Program): Assignment? =
(parent as IStatementContainer).statements
.asSequence()
.filterIsInstance<Assignment>()
.singleOrNull { it.origin==AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl(program) === this }
override fun referencesIdentifier(nameInSource: List<String>): Boolean = override fun referencesIdentifier(nameInSource: List<String>): Boolean =
value?.referencesIdentifier(nameInSource)==true || value?.referencesIdentifier(nameInSource)==true ||
this.arraysize?.referencesIdentifier(nameInSource)==true this.arraysize?.referencesIdentifier(nameInSource)==true

View File

@ -9,15 +9,13 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
Future Things and Ideas Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
- improve detection that a variable is not read before being written so that initializing it to zero can be omitted
(only happens now if a vardecl is immediately followed by a for loop for instance) BUT this may break stuff if the variable is read from a function call for instance in between?
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions - Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- Can we support signed % (remainder) somehow? - Can we support signed % (remainder) somehow?
- Don't add "random" rts to %asm blocks but instead give a warning about it? (but this breaks existing behavior that others already depend on... command line switch? block directive?) - Don't add "random" rts to %asm blocks but instead give a warning about it? (but this breaks existing behavior that others already depend on... command line switch? block directive?)
- IR: implement missing operators in AssignmentGen (array shifts etc) - IR: implement missing operators in AssignmentGen (array shifts etc)
- instead of copy-pasting inline asmsubs, make them into a 64tass macro and use that instead. - instead of copy-pasting inline asmsubs, make them into a 64tass macro and use that instead.
that will allow them to be reused from custom user written assembly code as well. that will allow them to be reused from custom user written assembly code as well.
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. - Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers.
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type - make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
(this is already done hardcoded for several of the builtin functions) (this is already done hardcoded for several of the builtin functions)
@ -48,6 +46,7 @@ Future Things and Ideas
But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?) But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?)
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level. Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions - Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
- Zig-like defer to clean up stuff when leaving the scope. But as we don't have closures, it's more limited. Still useful?
Libraries: Libraries:

View File

@ -3,37 +3,14 @@
%zeropage basicsafe %zeropage basicsafe
main { main {
bool[] barray = [true, false, true, false]
uword[] warray = [&value1, &barray, &value5, 4242]
ubyte @shared integer = 99
bool @shared value1
bool @shared value2 = barray[2] ; should be const!
bool @shared value3 = true
bool @shared value4 = false
bool @shared value5 = barray[cx16.r0L] ; cannot be const
uword @shared value6 = warray[3] ; should be const!
uword @shared value7 = warray[2] ; cannot be const
sub start() { sub start() {
txt.print_ub(integer) ubyte v0
integer++ ubyte v1
txt.spc() ubyte v2
txt.print_ub(integer) ubyte v3
txt.nl() v0 = v1 = v2 = 99
for v3 in 10 to 20 {
txt.print_bool(value1) cx16.r0L++
txt.spc() }
txt.print_bool(value2)
txt.spc()
txt.print_bool(value3)
txt.spc()
txt.print_bool(value4)
txt.spc()
txt.print_bool(value5)
txt.spc()
txt.print_uw(value6)
txt.nl()
} }
} }