mirror of
				https://github.com/irmen/prog8.git
				synced 2025-11-03 19:16:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1182 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
			
		
		
	
	
			1182 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
package prog8tests.compiler
 | 
						|
 | 
						|
import io.kotest.assertions.withClue
 | 
						|
import io.kotest.core.spec.style.FunSpec
 | 
						|
import io.kotest.engine.spec.tempdir
 | 
						|
import io.kotest.matchers.shouldBe
 | 
						|
import io.kotest.matchers.shouldNotBe
 | 
						|
import io.kotest.matchers.string.shouldContain
 | 
						|
import io.kotest.matchers.string.shouldStartWith
 | 
						|
import io.kotest.matchers.types.instanceOf
 | 
						|
import io.kotest.matchers.types.shouldBeSameInstanceAs
 | 
						|
import prog8.ast.ParentSentinel
 | 
						|
import prog8.ast.Program
 | 
						|
import prog8.ast.expressions.*
 | 
						|
import prog8.ast.statements.*
 | 
						|
import prog8.code.ast.PtAssignTarget
 | 
						|
import prog8.code.ast.PtAssignment
 | 
						|
import prog8.code.ast.PtFunctionCall
 | 
						|
import prog8.code.core.BaseDataType
 | 
						|
import prog8.code.core.DataType
 | 
						|
import prog8.code.core.Position
 | 
						|
import prog8.code.target.C64Target
 | 
						|
import prog8.code.target.Cx16Target
 | 
						|
import prog8.code.target.VMTarget
 | 
						|
import prog8tests.helpers.*
 | 
						|
 | 
						|
 | 
						|
class TestOptimization: FunSpec({
 | 
						|
    val outputDir = tempdir().toPath()
 | 
						|
    
 | 
						|
    test("remove empty subroutine except start") {
 | 
						|
        val sourcecode = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                }
 | 
						|
                sub empty() {
 | 
						|
                    ; going to be removed
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), true, sourcecode, outputDir)!!
 | 
						|
        val mainBlock = result.compilerAst.entrypoint.definingBlock
 | 
						|
        val startSub = mainBlock.statements.single() as Subroutine
 | 
						|
        result.compilerAst.entrypoint shouldBeSameInstanceAs startSub
 | 
						|
        withClue("only start sub should remain") {
 | 
						|
            startSub.name shouldBe "start"
 | 
						|
        }
 | 
						|
        withClue("compiler has inserted return in empty subroutines") {
 | 
						|
            startSub.statements.single() shouldBe instanceOf<Return>()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    test("scan all asmsubs to see if another subroutine is referenced") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub foo() {
 | 
						|
        cx16.r0++
 | 
						|
    }
 | 
						|
    asmsub baz() {
 | 
						|
        %asm{{
 | 
						|
            jsr p8s_foo
 | 
						|
            jmp blah
 | 
						|
        }}
 | 
						|
    }
 | 
						|
    asmsub bar() {
 | 
						|
        %asm{{
 | 
						|
            inx
 | 
						|
            jmp p8s_foo
 | 
						|
        }}
 | 
						|
    }
 | 
						|
    sub start() {
 | 
						|
        bar()
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(C64Target(), true, src, outputDir) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("don't remove empty subroutine if it's referenced") {
 | 
						|
        val sourcecode = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword xx = &empty
 | 
						|
                    xx++
 | 
						|
                }
 | 
						|
                sub empty() {
 | 
						|
                    ; should not be removed
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), true, sourcecode, outputDir)!!
 | 
						|
        val mainBlock = result.compilerAst.entrypoint.definingBlock
 | 
						|
        val startSub = mainBlock.statements[0] as Subroutine
 | 
						|
        val emptySub = mainBlock.statements[1] as Subroutine
 | 
						|
        result.compilerAst.entrypoint shouldBeSameInstanceAs startSub
 | 
						|
        startSub.name shouldBe "start"
 | 
						|
        emptySub.name shouldBe "empty"
 | 
						|
        withClue("compiler has inserted return in empty subroutines") {
 | 
						|
            emptySub.statements.single() shouldBe instanceOf<Return>()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    test("don't remove empty subroutine if it's referenced in vardecl") {
 | 
						|
        val sourcecode = """
 | 
						|
main {
 | 
						|
    ubyte tw = other.width()
 | 
						|
    sub start() {
 | 
						|
        tw++
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
other {
 | 
						|
    sub width() -> ubyte {
 | 
						|
        cx16.r0++
 | 
						|
        return 80
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(C64Target(), true, sourcecode, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
        compileText(VMTarget(), true, sourcecode, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("generated constvalue from typecast inherits proper parent linkage") {
 | 
						|
        val number = NumericLiteral(BaseDataType.UBYTE, 11.0, Position.DUMMY)
 | 
						|
        val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
 | 
						|
        val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
 | 
						|
        tc.linkParents(ParentSentinel)
 | 
						|
        tc.parent shouldNotBe null
 | 
						|
        number.parent shouldNotBe null
 | 
						|
        tc shouldBeSameInstanceAs number.parent
 | 
						|
        val constvalue = tc.constValue(program)!!
 | 
						|
        constvalue shouldBe instanceOf<NumericLiteral>()
 | 
						|
        constvalue.number shouldBe 11.0
 | 
						|
        constvalue.type shouldBe BaseDataType.BYTE
 | 
						|
        constvalue.parent shouldBeSameInstanceAs tc.parent
 | 
						|
    }
 | 
						|
 | 
						|
    test("generated constvalue from prefixexpr inherits proper parent linkage") {
 | 
						|
        val number = NumericLiteral(BaseDataType.UBYTE, 11.0, Position.DUMMY)
 | 
						|
        val pfx = PrefixExpression("-", number, Position.DUMMY)
 | 
						|
        val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
 | 
						|
        pfx.linkParents(ParentSentinel)
 | 
						|
        pfx.parent shouldNotBe null
 | 
						|
        number.parent shouldNotBe null
 | 
						|
        pfx shouldBeSameInstanceAs number.parent
 | 
						|
        val constvalue = pfx.constValue(program)!!
 | 
						|
        constvalue shouldBe instanceOf<NumericLiteral>()
 | 
						|
        constvalue.number shouldBe -11.0
 | 
						|
        constvalue.type shouldBe BaseDataType.BYTE
 | 
						|
        constvalue.parent shouldBeSameInstanceAs pfx.parent
 | 
						|
    }
 | 
						|
 | 
						|
    test("various 'not' operator rewrites even without optimizations") {
 | 
						|
        val src = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    bool @shared a1
 | 
						|
                    bool @shared a2
 | 
						|
                    a1 = not a1                 ; a1 = not a1
 | 
						|
                    a1 = not not a1             ; a1 = a1,  so removed totally
 | 
						|
                    a1 = not not not a1         ; a1 = not a1
 | 
						|
                    a1 = not a1 or not a2       ; a1 = not (a1 and a2)
 | 
						|
                    a1 = not a1 and not a2      ; a1 = not (a1 or a2)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), false, src, outputDir, writeAssembly = true)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 9
 | 
						|
 | 
						|
        val value1 = (stmts[4] as Assignment).value as PrefixExpression
 | 
						|
        val value2 = (stmts[5] as Assignment).value as PrefixExpression
 | 
						|
        val value3 = (stmts[6] as Assignment).value as PrefixExpression
 | 
						|
        val value4 = (stmts[7] as Assignment).value as PrefixExpression
 | 
						|
        value1.operator shouldBe "not"
 | 
						|
        value2.operator shouldBe "not"
 | 
						|
        value3.operator shouldBe "not"
 | 
						|
        value4.operator shouldBe "not"
 | 
						|
        value1.expression shouldBe instanceOf<IdentifierReference>()
 | 
						|
        value2.expression shouldBe instanceOf<IdentifierReference>()
 | 
						|
        (value3.expression as BinaryExpression).operator shouldBe "and"
 | 
						|
        (value4.expression as BinaryExpression).operator shouldBe "or"
 | 
						|
    }
 | 
						|
 | 
						|
    test("various 'not' operator rewrites with optimizations") {
 | 
						|
        val src = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    bool @shared a1
 | 
						|
                    bool @shared a2
 | 
						|
                    a1 = not a1                 ; a1 = not a1
 | 
						|
                    a1 = not not a1             ; a1 = a1,  so removed totally
 | 
						|
                    a1 = not not not a1         ; a1 = not a1
 | 
						|
                    a1 = not a1 or not a2       ; a1 = not (a1 and a2)
 | 
						|
                    a1 = not a1 and not a2      ; a1 = not (a1 or a2)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), true, src, outputDir, writeAssembly = true)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 9
 | 
						|
 | 
						|
        val value1 = (stmts[4] as Assignment).value as PrefixExpression
 | 
						|
        val value2 = (stmts[5] as Assignment).value as PrefixExpression
 | 
						|
        val value3 = (stmts[6] as Assignment).value as PrefixExpression
 | 
						|
        val value4 = (stmts[7] as Assignment).value as PrefixExpression
 | 
						|
        value1.operator shouldBe "not"
 | 
						|
        value2.operator shouldBe "not"
 | 
						|
        value3.operator shouldBe "not"
 | 
						|
        value4.operator shouldBe "not"
 | 
						|
        value1.expression shouldBe instanceOf<IdentifierReference>()
 | 
						|
        value2.expression shouldBe instanceOf<IdentifierReference>()
 | 
						|
        (value3.expression as BinaryExpression).operator shouldBe "and"
 | 
						|
        (value4.expression as BinaryExpression).operator shouldBe "or"
 | 
						|
    }
 | 
						|
 | 
						|
    test("asmgen correctly deals with float typecasting in augmented assignment") {
 | 
						|
        val src="""
 | 
						|
            %import floats
 | 
						|
            
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    ubyte ub
 | 
						|
                    float ff = 1.0
 | 
						|
                    ff += (ub as float)         ; operator doesn't matter
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly = false)!!
 | 
						|
        val assignFF = result.compilerAst.entrypoint.statements.dropLast(1).last() as Assignment
 | 
						|
        assignFF.isAugmentable shouldBe true
 | 
						|
        assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
 | 
						|
        val value = assignFF.value as BinaryExpression
 | 
						|
        value.operator shouldBe "+"
 | 
						|
        (value.left as? IdentifierReference)?.nameInSource shouldBe listOf("ff")
 | 
						|
        value.right shouldBe instanceOf<TypecastExpression>()
 | 
						|
 | 
						|
        compileText(C64Target(), optimize=false, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("unused variable removal") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    ubyte unused                        ; removed
 | 
						|
                    ubyte @shared unused_but_shared     ; this one should remain
 | 
						|
                    ubyte usedvar_only_written          ; not removed because has multiple assignments
 | 
						|
                    usedvar_only_written=2
 | 
						|
                    usedvar_only_written++
 | 
						|
                    ubyte usedvar                       ; and this one remains too
 | 
						|
                    usedvar = msb(usedvar)
 | 
						|
                    unused_but_shared = usedvar
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        result.compilerAst.entrypoint.statements.size shouldBe 8
 | 
						|
        val alldecls = result.compilerAst.entrypoint.allDefinedSymbols.toList()
 | 
						|
        alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
 | 
						|
    }
 | 
						|
 | 
						|
    test("unused variable removal from subscope") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start()  {
 | 
						|
                    if cx16.r0!=0 {
 | 
						|
                        uword xx            ; to be removed
 | 
						|
                        cx16.r0 = 0
 | 
						|
                    }
 | 
						|
                    func2()
 | 
						|
            
 | 
						|
                    sub func2() {
 | 
						|
                         uword yy           ; to be removed
 | 
						|
                         yy=99              ; to be removed
 | 
						|
                         cx16.r0 = 0
 | 
						|
                         rol(cx16.r0)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        result.compilerAst.entrypoint.statements.size shouldBe 4
 | 
						|
        val ifstmt = result.compilerAst.entrypoint.statements[0] as IfElse
 | 
						|
        ifstmt.truepart.statements.size shouldBe 1
 | 
						|
        (ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
 | 
						|
        val func2 = result.compilerAst.entrypoint.statements.last() as Subroutine
 | 
						|
        func2.statements.size shouldBe 3
 | 
						|
        (func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
 | 
						|
    }
 | 
						|
 | 
						|
    test("unused subroutine removal") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub strip() {
 | 
						|
        lstrip()
 | 
						|
    }
 | 
						|
 | 
						|
    sub lstrip() {
 | 
						|
        lstripped()
 | 
						|
    }
 | 
						|
 | 
						|
    sub lstripped() {
 | 
						|
        cx16.r0++
 | 
						|
        evenmore()
 | 
						|
        cx16.r1++
 | 
						|
    }
 | 
						|
 | 
						|
    sub evenmore() {
 | 
						|
        cx16.r1++
 | 
						|
        cx16.r2++
 | 
						|
    }
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        ; nothing
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        result.compilerAst.entrypoint.statements.size shouldBe 1
 | 
						|
        result.compilerAst.entrypoint.definingScope.statements.size shouldBe 1
 | 
						|
    }
 | 
						|
 | 
						|
    test("test simple augmented assignment optimization correctly initializes all variables") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start()  {
 | 
						|
                    ubyte @shared z1
 | 
						|
                    z1 = 10
 | 
						|
                    ubyte @shared z2
 | 
						|
                    z2 = ~z2
 | 
						|
                    bool @shared z3
 | 
						|
                    z3 = not z3
 | 
						|
                    uword @shared z4
 | 
						|
                    z4 = (z4 as ubyte)
 | 
						|
                    ubyte @shared z5
 | 
						|
                    z5 = z1+z5+5
 | 
						|
                    ubyte @shared z6
 | 
						|
                    z6 = z1+z6-5
 | 
						|
                }
 | 
						|
            }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        /* expected:
 | 
						|
        ubyte z1
 | 
						|
        z1 = 10
 | 
						|
        ubyte z2
 | 
						|
        z2 = 255
 | 
						|
        bool z3
 | 
						|
        z3 = true
 | 
						|
        uword z4
 | 
						|
        z4 = 0
 | 
						|
        ubyte z5
 | 
						|
        z5 = z1 + 5
 | 
						|
        ubyte z6
 | 
						|
        z6 = z1 - 5
 | 
						|
        */
 | 
						|
        val statements = result.compilerAst.entrypoint.statements
 | 
						|
        statements.size shouldBe 13
 | 
						|
        val z1decl = statements[0] as VarDecl
 | 
						|
        val z1init = statements[1] as Assignment
 | 
						|
        val z2decl = statements[2] as VarDecl
 | 
						|
        val z2init = statements[3] as Assignment
 | 
						|
        val z3decl = statements[4] as VarDecl
 | 
						|
        val z3init = statements[5] as Assignment
 | 
						|
        val z4decl = statements[6] as VarDecl
 | 
						|
        val z4init = statements[7] as Assignment
 | 
						|
        val z5decl = statements[8] as VarDecl
 | 
						|
        val z5init = statements[9] as Assignment
 | 
						|
        val z6decl = statements[10] as VarDecl
 | 
						|
        val z6init = statements[11] as Assignment
 | 
						|
 | 
						|
        z1decl.name shouldBe "z1"
 | 
						|
        z1init.value shouldBe NumericLiteral(BaseDataType.UBYTE, 10.0, Position.DUMMY)
 | 
						|
        z2decl.name shouldBe "z2"
 | 
						|
        z2init.value shouldBe NumericLiteral(BaseDataType.UBYTE, 255.0, Position.DUMMY)
 | 
						|
        z3decl.name shouldBe "z3"
 | 
						|
        z3init.value shouldBe NumericLiteral(BaseDataType.BOOL, 1.0, Position.DUMMY)
 | 
						|
        z4decl.name shouldBe "z4"
 | 
						|
        z4init.value shouldBe NumericLiteral(BaseDataType.UWORD, 0.0, Position.DUMMY)
 | 
						|
        z5decl.name shouldBe "z5"
 | 
						|
        (z5init.value as BinaryExpression).operator shouldBe "+"
 | 
						|
        (z5init.value as BinaryExpression).right shouldBe NumericLiteral(BaseDataType.UBYTE, 5.0, Position.DUMMY)
 | 
						|
        z6decl.name shouldBe "z6"
 | 
						|
        (z6init.value as BinaryExpression).operator shouldBe "-"
 | 
						|
        (z6init.value as BinaryExpression).right shouldBe NumericLiteral(BaseDataType.UBYTE, 5.0, Position.DUMMY)
 | 
						|
    }
 | 
						|
 | 
						|
    test("force_output option should work with optimizing memwrite assignment") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                %option force_output
 | 
						|
                
 | 
						|
                sub start() {
 | 
						|
                    uword @shared aa
 | 
						|
                    ubyte @shared zz
 | 
						|
                    @(aa) = zz + 32    
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 6
 | 
						|
        val assign=stmts[4] as Assignment
 | 
						|
        (assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
 | 
						|
    }
 | 
						|
 | 
						|
    test("don't optimize memory writes away") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword @shared aa
 | 
						|
                    ubyte @shared zz
 | 
						|
                    @(aa) = zz + 32   ; do not optimize this away!
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 6
 | 
						|
        val assign=stmts[4] as Assignment
 | 
						|
        (assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
 | 
						|
    }
 | 
						|
 | 
						|
    test("correctly process constant prefix numbers") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start()  {
 | 
						|
                    ubyte @shared z1 = 1
 | 
						|
                    ubyte @shared z2 = + 1
 | 
						|
                    ubyte @shared z3 = ~ 1
 | 
						|
                    bool @shared z4 = not 1
 | 
						|
                    byte @shared z5 = - 1
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 11
 | 
						|
        stmts.filterIsInstance<VarDecl>().size shouldBe 5
 | 
						|
        stmts.filterIsInstance<Assignment>().size shouldBe 5
 | 
						|
    }
 | 
						|
 | 
						|
    test("correctly process constant prefix numbers with type mismatch and give error") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start()  {
 | 
						|
                    ubyte @shared z1 = - 200
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 2
 | 
						|
        errors.errors[0] shouldContain  "out of range"
 | 
						|
        errors.errors[1] shouldContain  "cannot assign word to byte"
 | 
						|
    }
 | 
						|
 | 
						|
    test("out of range cast should give error") {
 | 
						|
        val src="""
 | 
						|
            main {
 | 
						|
                sub start()  {
 | 
						|
                    ubyte @shared z1 = - 200 as ubyte
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain  "no cast"
 | 
						|
    }
 | 
						|
 | 
						|
    test("test augmented expression asmgen") {
 | 
						|
        val src = """
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                ubyte c
 | 
						|
                ubyte r
 | 
						|
                ubyte q
 | 
						|
                r = (q+r)-c
 | 
						|
                q=r
 | 
						|
                r = q+(r-c)
 | 
						|
                q=r
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
        val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!!
 | 
						|
        result.compilerAst.entrypoint.statements.size shouldBe 11
 | 
						|
        result.compilerAst.entrypoint.statements.last() shouldBe instanceOf<Return>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("keep the value initializer assignment if the next one depends on it") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                uword @shared yy
 | 
						|
                yy = 20             ; ok to remove =0 initializer before this
 | 
						|
                uword @shared zz
 | 
						|
                zz += 60            ; NOT ok to remove initializer, should evaluate to 60
 | 
						|
                ubyte @shared xx
 | 
						|
                xx = 6+lsb(mkword(xx,22))   ; is not an initializer because it references xx
 | 
						|
            }
 | 
						|
        }
 | 
						|
        """
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        /* expected result:
 | 
						|
        uword yy
 | 
						|
        yy = 20
 | 
						|
        uword zz
 | 
						|
        zz = 60
 | 
						|
        ubyte xx
 | 
						|
        xx = 0
 | 
						|
        xx = abs(xx)
 | 
						|
        xx += 6
 | 
						|
         */
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 7
 | 
						|
        stmts.filterIsInstance<VarDecl>().size shouldBe 3
 | 
						|
        stmts.filterIsInstance<Assignment>().size shouldBe 3
 | 
						|
    }
 | 
						|
 | 
						|
    test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                uword @shared xx
 | 
						|
                xx = xx + 20    ; is same var so can be changed just fine into xx=20
 | 
						|
                uword @shared yy
 | 
						|
                xx = 20
 | 
						|
                yy = 0          ; is other var..
 | 
						|
                xx = xx+10      ; so this should not be changed into xx=10
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        /*
 | 
						|
        expected result:
 | 
						|
        uword xx
 | 
						|
        xx = 20
 | 
						|
        uword yy
 | 
						|
        yy = 0
 | 
						|
        xx = 20
 | 
						|
        yy = 0
 | 
						|
        xx += 10
 | 
						|
         */
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 8
 | 
						|
        stmts.filterIsInstance<VarDecl>().size shouldBe 2
 | 
						|
        stmts.filterIsInstance<Assignment>().size shouldBe 5
 | 
						|
        val assignXX1 = stmts[1] as Assignment
 | 
						|
        assignXX1.target.identifier!!.nameInSource shouldBe listOf("xx")
 | 
						|
        assignXX1.value shouldBe NumericLiteral(BaseDataType.UWORD, 20.0, Position.DUMMY)
 | 
						|
        val assignXX2 = stmts[6] as Assignment
 | 
						|
        assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
 | 
						|
        val xxValue = assignXX2.value as BinaryExpression
 | 
						|
        xxValue.operator shouldBe "+"
 | 
						|
        (xxValue.left as? IdentifierReference)?.nameInSource shouldBe listOf("xx")
 | 
						|
        xxValue.right shouldBe NumericLiteral(BaseDataType.UWORD, 10.0, Position.DUMMY)
 | 
						|
    }
 | 
						|
 | 
						|
    test("multi-comparison with many values replaced by containment check on heap variable") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                ubyte @shared source=99
 | 
						|
                ubyte @shared thingy=42
 | 
						|
        
 | 
						|
                if source==3 or source==4 or source==99 or source==1 or source==2 or source==42
 | 
						|
                    thingy++
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
 | 
						|
        compileText(VMTarget(), optimize=true, src, outputDir, writeAssembly=true) shouldNotBe null
 | 
						|
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        /*
 | 
						|
        expected result:
 | 
						|
        ubyte[] auto_heap_var = [1,2,3,4,42,99]
 | 
						|
        ubyte source
 | 
						|
        source = 99
 | 
						|
        ubyte thingy
 | 
						|
        thingy = 42
 | 
						|
        if source in auto_heap_var
 | 
						|
            thingy++
 | 
						|
         */
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 7
 | 
						|
        val ifStmt = stmts[5] as IfElse
 | 
						|
        val containment = ifStmt.condition as ContainmentCheck
 | 
						|
        (containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
 | 
						|
        (containment.iterable as IdentifierReference).nameInSource.single() shouldStartWith "auto_heap_value"
 | 
						|
        val arrayDecl = stmts[0] as VarDecl
 | 
						|
        arrayDecl.isArray shouldBe true
 | 
						|
        arrayDecl.arraysize?.constIndex() shouldBe 6
 | 
						|
        val arrayValue = arrayDecl.value as ArrayLiteral
 | 
						|
        arrayValue.type shouldBe InferredTypes.InferredType.known(DataType.arrayFor(BaseDataType.UBYTE))
 | 
						|
        arrayValue.value shouldBe listOf(
 | 
						|
            NumericLiteral.optimalInteger(1, Position.DUMMY),
 | 
						|
            NumericLiteral.optimalInteger(2, Position.DUMMY),
 | 
						|
            NumericLiteral.optimalInteger(3, Position.DUMMY),
 | 
						|
            NumericLiteral.optimalInteger(4, Position.DUMMY),
 | 
						|
            NumericLiteral.optimalInteger(42, Position.DUMMY),
 | 
						|
            NumericLiteral.optimalInteger(99, Position.DUMMY))
 | 
						|
    }
 | 
						|
 | 
						|
    test("multi-comparison with few values replaced by inline containment check") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                ubyte @shared source=99
 | 
						|
                ubyte @shared thingy=42
 | 
						|
        
 | 
						|
                if source==3 or source==4 or source==99
 | 
						|
                    thingy++
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
 | 
						|
        compileText(VMTarget(), optimize=true, src, outputDir, writeAssembly=true) shouldNotBe null
 | 
						|
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 6
 | 
						|
        val ifStmt = stmts[4] as IfElse
 | 
						|
        val containment = ifStmt.condition as ContainmentCheck
 | 
						|
        (containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
 | 
						|
        val array = (containment.iterable as ArrayLiteral)
 | 
						|
        array.value.size shouldBe 3
 | 
						|
        array.value.map { (it as NumericLiteral).number } shouldBe listOf(3.0, 4.0, 99.0)
 | 
						|
    }
 | 
						|
 | 
						|
    test("invalid multi-comparison (not all equals) not replaced") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                ubyte @shared source=99
 | 
						|
                ubyte @shared thingy=42
 | 
						|
        
 | 
						|
                if source==3 or source==4 or source!=99 or source==1
 | 
						|
                    thingy++
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 6
 | 
						|
        val ifStmt = stmts[4] as IfElse
 | 
						|
        ifStmt.condition shouldBe instanceOf<BinaryExpression>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("invalid multi-comparison (not all same needle) not replaced") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                ubyte @shared source=99
 | 
						|
                ubyte @shared thingy=42
 | 
						|
        
 | 
						|
                if source==3 or source==4 or thingy==99 or source==1
 | 
						|
                    thingy++
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 6
 | 
						|
        val ifStmt = stmts[4] as IfElse
 | 
						|
        ifStmt.condition shouldBe instanceOf<BinaryExpression>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("invalid multi-comparison (not all or) not replaced") {
 | 
						|
        val src="""
 | 
						|
        main {
 | 
						|
            sub start() {
 | 
						|
                ubyte @shared source=99
 | 
						|
                ubyte @shared thingy=42
 | 
						|
        
 | 
						|
                if source==3 or source==4 and source==99 or source==1
 | 
						|
                    thingy++
 | 
						|
            }
 | 
						|
        }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 6
 | 
						|
        val ifStmt = stmts[4] as IfElse
 | 
						|
        ifStmt.condition shouldBe instanceOf<BinaryExpression>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("pointer indexing inside other expression ok") {
 | 
						|
        val src="""
 | 
						|
            main{
 | 
						|
                sub start () {
 | 
						|
                    uword @shared eRef
 | 
						|
                    if eRef[3] & 10 ==0 {
 | 
						|
                      return
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }"""
 | 
						|
        val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
 | 
						|
        val stmts = result.compilerAst.entrypoint.statements
 | 
						|
        stmts.size shouldBe 4
 | 
						|
    }
 | 
						|
 | 
						|
    test("repeated assignments to IO register should remain") {
 | 
						|
        val srcX16="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        ubyte @shared xx
 | 
						|
        xx = 42
 | 
						|
        xx = 42  ; removed
 | 
						|
        xx = 42  ; removed
 | 
						|
        cx16.VERA_DATA0 = 0
 | 
						|
        cx16.VERA_DATA0 = 0
 | 
						|
        cx16.VERA_DATA0 = 0
 | 
						|
        @($9fff) = 0
 | 
						|
        @($9fff) = 0
 | 
						|
        @($9fff) = 0
 | 
						|
        return
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        var result = compileText(Cx16Target(), true, srcX16, outputDir, writeAssembly = true)!!
 | 
						|
        var statements = result.compilerAst.entrypoint.statements
 | 
						|
        statements.size shouldBe 9
 | 
						|
        (statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
 | 
						|
        (statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
 | 
						|
        (statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
 | 
						|
        (statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
 | 
						|
        (statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
 | 
						|
        (statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
 | 
						|
        (statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
 | 
						|
 | 
						|
        val srcC64="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        ubyte @shared xx
 | 
						|
        xx = 42
 | 
						|
        xx = 42  ;removed
 | 
						|
        xx = 42  ;removed
 | 
						|
        c64.EXTCOL = 0
 | 
						|
        c64.EXTCOL = 0
 | 
						|
        c64.EXTCOL = 0
 | 
						|
        @(53281) = 0
 | 
						|
        @(53281) = 0
 | 
						|
        @(53281) = 0
 | 
						|
        return
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        result = compileText(C64Target(), true, srcC64, outputDir, writeAssembly = true)!!
 | 
						|
        statements = result.compilerAst.entrypoint.statements
 | 
						|
        statements.size shouldBe 9
 | 
						|
        (statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
 | 
						|
        (statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
 | 
						|
        (statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
 | 
						|
        (statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
 | 
						|
        (statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
 | 
						|
        (statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
 | 
						|
        (statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
 | 
						|
    }
 | 
						|
 | 
						|
    test("no string error when inlining") {
 | 
						|
        val text="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        test()
 | 
						|
    }
 | 
						|
 | 
						|
    sub test() {
 | 
						|
        cx16.r0 = "abc"
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(C64Target(), true, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("replacing string print by chrout with referenced string elsewhere shouldn't give string symbol error") {
 | 
						|
        val text="""
 | 
						|
%import textio
 | 
						|
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        str key = "test"
 | 
						|
        txt.print(":")
 | 
						|
        if key != ":" {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
"""
 | 
						|
        compileText(VMTarget(), true, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("sub only called by asm should not be optimized away") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        %asm{{
 | 
						|
            jsr p8s_test
 | 
						|
        }}
 | 
						|
    }
 | 
						|
 | 
						|
    sub test() {
 | 
						|
        cx16.r0++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(Cx16Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("no crash for making var in removed/inlined subroutine fully scoped") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        test()
 | 
						|
    }
 | 
						|
 | 
						|
    sub test() {
 | 
						|
        sub nested() {
 | 
						|
            ubyte counter
 | 
						|
            counter++
 | 
						|
        }
 | 
						|
        test2(main.test.nested.counter)
 | 
						|
    }
 | 
						|
 | 
						|
    sub test2(ubyte value) {
 | 
						|
        value++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(Cx16Target(), true, src, outputDir, writeAssembly = false, errors = errors) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("var to const") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        ubyte xx=10         ; to const
 | 
						|
        ubyte @shared yy=20 ; remain var
 | 
						|
        cx16.r0L = xx+yy
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false, errors = errors)!!
 | 
						|
        val st = result.compilerAst.entrypoint.statements
 | 
						|
        st.size shouldBe 5
 | 
						|
        val xxConst = st[0] as VarDecl
 | 
						|
        xxConst.type shouldBe VarDeclType.CONST
 | 
						|
        xxConst.name shouldBe "xx"
 | 
						|
        (xxConst.value as? NumericLiteral)?.number shouldBe 10.0
 | 
						|
        (st[1] as VarDecl).type shouldBe VarDeclType.VAR
 | 
						|
        val expr = (st[3] as Assignment).value as BinaryExpression
 | 
						|
        (expr.left as? IdentifierReference)?.nameInSource shouldBe listOf("yy")
 | 
						|
        (expr.right as? NumericLiteral)?.number shouldBe 10.0
 | 
						|
    }
 | 
						|
 | 
						|
    test("var to const inside typecast") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    uword variable  ; will be made a const
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        cx16.r0 = msb(variable) as uword
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(VMTarget(), true, src, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("De Morgan's laws") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        bool @shared a1
 | 
						|
        bool @shared a2
 | 
						|
 | 
						|
        if not a1 or not a2
 | 
						|
            cx16.r0++
 | 
						|
        if not a1 and not a2
 | 
						|
            cx16.r0++
 | 
						|
            
 | 
						|
        if cx16.r0L==42 or not a2
 | 
						|
            cx16.r0L++
 | 
						|
        if not a2 or cx16.r0L==42
 | 
						|
            cx16.r0L++
 | 
						|
            
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
 | 
						|
        val st = result.compilerAst.entrypoint.statements
 | 
						|
        st.size shouldBe 9
 | 
						|
        val if1c = (st[4] as IfElse).condition as PrefixExpression
 | 
						|
        val if2c = (st[5] as IfElse).condition as PrefixExpression
 | 
						|
        val if3c = (st[6] as IfElse).condition as PrefixExpression
 | 
						|
        val if4c = (st[7] as IfElse).condition as PrefixExpression
 | 
						|
        if1c.operator shouldBe "not"
 | 
						|
        if2c.operator shouldBe "not"
 | 
						|
        if3c.operator shouldBe "not"
 | 
						|
        if4c.operator shouldBe "not"
 | 
						|
        (if1c.expression as BinaryExpression).operator shouldBe "and"
 | 
						|
        (if2c.expression as BinaryExpression).operator shouldBe "or"
 | 
						|
        (if3c.expression as BinaryExpression).operator shouldBe "and"
 | 
						|
        (if4c.expression as BinaryExpression).operator shouldBe "and"
 | 
						|
    }
 | 
						|
 | 
						|
    test("absorption laws") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        bool @shared a
 | 
						|
        bool @shared b
 | 
						|
 | 
						|
        if a or (a and b)
 | 
						|
            cx16.r0 ++
 | 
						|
        if a or (b and a)
 | 
						|
            cx16.r0 ++
 | 
						|
        if a and (a or b)
 | 
						|
            cx16.r0 ++
 | 
						|
        if a and (b or a)
 | 
						|
            cx16.r0 ++
 | 
						|
 | 
						|
        if a and (b and a)
 | 
						|
            cx16.r0 ++
 | 
						|
        if a or (b or a)
 | 
						|
            cx16.r0 ++
 | 
						|
        if (b and a) and b
 | 
						|
            cx16.r0 ++
 | 
						|
        if (b or a) or b
 | 
						|
            cx16.r0 ++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
 | 
						|
        val st = result.compilerAst.entrypoint.statements
 | 
						|
        st.size shouldBe 13
 | 
						|
        val if1 = st[4] as IfElse
 | 
						|
        val if2 = st[5] as IfElse
 | 
						|
        val if3 = st[6] as IfElse
 | 
						|
        val if4 = st[7] as IfElse
 | 
						|
        (if1.condition as IdentifierReference).nameInSource shouldBe listOf("a")
 | 
						|
        (if2.condition as IdentifierReference).nameInSource shouldBe listOf("a")
 | 
						|
        (if3.condition as IdentifierReference).nameInSource shouldBe listOf("a")
 | 
						|
        (if4.condition as IdentifierReference).nameInSource shouldBe listOf("a")
 | 
						|
        val if5 = st[8] as IfElse
 | 
						|
        val if6 = st[9] as IfElse
 | 
						|
        val if7 = st[10] as IfElse
 | 
						|
        val if8 = st[11] as IfElse
 | 
						|
        val if5bc = if5.condition as BinaryExpression
 | 
						|
        val if6bc = if6.condition as BinaryExpression
 | 
						|
        val if7bc = if7.condition as BinaryExpression
 | 
						|
        val if8bc = if8.condition as BinaryExpression
 | 
						|
        if5bc.left shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if5bc.right shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if6bc.left shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if6bc.right shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if7bc.left shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if7bc.right shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if8bc.left shouldBe instanceOf<IdentifierReference>()
 | 
						|
        if8bc.right shouldBe instanceOf<IdentifierReference>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("funky bitshifts") {
 | 
						|
        val src="""
 | 
						|
main {   
 | 
						|
    sub start() {
 | 
						|
        const uword one = 1
 | 
						|
        const uword two = 2
 | 
						|
        uword @shared answer = one * two >> 8
 | 
						|
        funcw(one * two >> 8)
 | 
						|
 | 
						|
        const uword uw1 = 99
 | 
						|
        const uword uw2 = 22
 | 
						|
        uword @shared answer2 = uw1 * uw2 >> 8      ; optimized into  msb(uw1*uw2) as uword
 | 
						|
        funcw(uw1 * uw2 >> 8)
 | 
						|
 | 
						|
        uword @shared uw3 = 99
 | 
						|
        uword @shared uw4 = 22
 | 
						|
        uword @shared answer3 = uw3 * uw4 >> 8      ; optimized into  msb(uw1*uw2) as uword
 | 
						|
        funcw(uw3 * uw4 >> 8)
 | 
						|
    }
 | 
						|
 | 
						|
    sub funcw(uword ww) {
 | 
						|
        cx16.r0++
 | 
						|
    }
 | 
						|
    
 | 
						|
}"""
 | 
						|
 | 
						|
        val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
 | 
						|
        val st = result.compilerAst.entrypoint.statements
 | 
						|
        st.size shouldBe 18
 | 
						|
 | 
						|
        val answerValue = (st[3] as Assignment).value
 | 
						|
        answerValue shouldBe NumericLiteral(BaseDataType.UWORD, 0.0, Position.DUMMY)
 | 
						|
 | 
						|
        val funcarg1 = (st[4] as FunctionCallStatement).args.single()
 | 
						|
        funcarg1 shouldBe NumericLiteral(BaseDataType.UWORD, 0.0, Position.DUMMY)
 | 
						|
 | 
						|
        val answer2Value = (st[8] as Assignment).value
 | 
						|
        answer2Value shouldBe NumericLiteral(BaseDataType.UWORD, 8.0, Position.DUMMY)
 | 
						|
 | 
						|
        val funcarg2 = (st[9] as FunctionCallStatement).args.single()
 | 
						|
        funcarg2 shouldBe NumericLiteral(BaseDataType.UWORD, 8.0, Position.DUMMY)
 | 
						|
 | 
						|
        val answer3ValueTc = (st[15] as Assignment).value as TypecastExpression
 | 
						|
        answer3ValueTc.type shouldBe DataType.UWORD
 | 
						|
        val answer3Value = answer3ValueTc.expression as FunctionCallExpression
 | 
						|
        answer3Value.target.nameInSource shouldBe listOf("msb")
 | 
						|
        answer3Value.args.single() shouldBe instanceOf<BinaryExpression>()
 | 
						|
 | 
						|
        val funcarg3tc = (st[16] as FunctionCallStatement).args.single() as TypecastExpression
 | 
						|
        funcarg3tc.type shouldBe DataType.UWORD
 | 
						|
        val funcarg3 = funcarg3tc.expression as FunctionCallExpression
 | 
						|
        funcarg3.target.nameInSource shouldBe listOf("msb")
 | 
						|
        funcarg3.args.single() shouldBe instanceOf<BinaryExpression>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("no operand swap on logical expressions with shortcircuit evaluation") {
 | 
						|
        val src="""
 | 
						|
%import diskio
 | 
						|
%zeropage basicsafe
 | 
						|
%option no_sysinit
 | 
						|
 | 
						|
main {
 | 
						|
    str scanline_buf = "?"* 20
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        if diskio.f_open("test.prg") and diskio.f_read(scanline_buf, 2)==2
 | 
						|
            cx16.r0++
 | 
						|
 | 
						|
        if diskio.f_open("test.prg") or diskio.f_read(scanline_buf, 2)==2
 | 
						|
            cx16.r0++
 | 
						|
 | 
						|
        if diskio.f_open("test.prg") xor diskio.f_read(scanline_buf, 2)==2
 | 
						|
            cx16.r0++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
 | 
						|
        val st = result.compilerAst.entrypoint.statements
 | 
						|
        st.size shouldBe 4
 | 
						|
        val ifCond1 = (st[0] as IfElse).condition as BinaryExpression
 | 
						|
        val ifCond2 = (st[1] as IfElse).condition as BinaryExpression
 | 
						|
        val ifCond3 = (st[2] as IfElse).condition as BinaryExpression
 | 
						|
        (ifCond1.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_open")
 | 
						|
        (ifCond2.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_open")
 | 
						|
        (ifCond3.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_open")
 | 
						|
        val right1 = ifCond1.right as BinaryExpression
 | 
						|
        val right2 = ifCond2.right as BinaryExpression
 | 
						|
        val right3 = ifCond3.right as BinaryExpression
 | 
						|
        (right1.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_read")
 | 
						|
        (right2.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_read")
 | 
						|
        (right3.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_read")
 | 
						|
    }
 | 
						|
 | 
						|
    test("eliminate same target register assignments") {
 | 
						|
        val src="""
 | 
						|
%zeropage basicsafe
 | 
						|
%option no_sysinit
 | 
						|
 | 
						|
main {
 | 
						|
    extsub ${'$'}2000 = func1() clobbers(X) -> ubyte @A, word @R0, byte @R1
 | 
						|
    extsub ${'$'}3000 = func2() clobbers(X) -> ubyte @A, uword @R0, uword @R1
 | 
						|
    extsub ${'$'}4000 = func3() clobbers(X) -> ubyte @R0
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        bool flag
 | 
						|
        void cbm.GETIN()
 | 
						|
        flag, cx16.r1L = cbm.GETIN()
 | 
						|
        void, cx16.r0s, cx16.r1sL = func1()
 | 
						|
        void, cx16.r2, cx16.r1 = func2()
 | 
						|
        cx16.r0L = func3()
 | 
						|
        cx16.r0H = func3()
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val result = compileText(C64Target(), true, src, outputDir, writeAssembly = true)!!
 | 
						|
        val st = result.codegenAst!!.entrypoint()!!.children
 | 
						|
        st.size shouldBe 10
 | 
						|
        (st[3] as PtFunctionCall).name shouldBe "cbm.GETIN"
 | 
						|
        (st[3] as PtFunctionCall).void shouldBe true
 | 
						|
        val a1 = st[4] as PtAssignment
 | 
						|
        (a1.value as PtFunctionCall).name shouldBe "cbm.GETIN"
 | 
						|
        a1.multiTarget shouldBe true
 | 
						|
        a1.children.size shouldBe 3
 | 
						|
        (a1.children[0] as PtAssignTarget).void shouldBe false
 | 
						|
        (a1.children[0] as PtAssignTarget).identifier!!.name shouldBe "p8b_main.p8s_start.p8v_flag"
 | 
						|
        (a1.children[1] as PtAssignTarget).void shouldBe false
 | 
						|
        (a1.children[1] as PtAssignTarget).identifier!!.name shouldBe "cx16.r1L"
 | 
						|
 | 
						|
        (st[5] as PtFunctionCall).name shouldBe "p8b_main.p8s_func1"
 | 
						|
        (st[5] as PtFunctionCall).void shouldBe true
 | 
						|
        val a2 = st[6] as PtAssignment
 | 
						|
        (a2.value as PtFunctionCall).name shouldBe "p8b_main.p8s_func2"
 | 
						|
        a2.multiTarget shouldBe true
 | 
						|
        a2.children.size shouldBe 4
 | 
						|
        (a2.children[0] as PtAssignTarget).void shouldBe true
 | 
						|
        (a2.children[1] as PtAssignTarget).void shouldBe false
 | 
						|
        (a2.children[1] as PtAssignTarget).identifier!!.name shouldBe "cx16.r2"
 | 
						|
        (a2.children[2] as PtAssignTarget).void shouldBe true
 | 
						|
 | 
						|
        (st[7] as PtFunctionCall).name shouldBe "p8b_main.p8s_func3"
 | 
						|
        (st[7] as PtFunctionCall).void shouldBe true
 | 
						|
        val a3 = st[8] as PtAssignment
 | 
						|
        (a3.value as PtFunctionCall).name shouldBe "p8b_main.p8s_func3"
 | 
						|
        a3.multiTarget shouldBe false
 | 
						|
        a3.children.size shouldBe 2
 | 
						|
        (a3.children[0] as PtAssignTarget).void shouldBe false
 | 
						|
        (a3.children[0] as PtAssignTarget).identifier!!.name shouldBe "cx16.r0H"
 | 
						|
    }
 | 
						|
 | 
						|
    test("symbol table correct in asmgen after earlier optimization steps") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        uword @shared bulletRef, enemyRef
 | 
						|
        const ubyte BD_Y = 10
 | 
						|
        const ubyte EN_Y = 11
 | 
						|
 | 
						|
        if bulletRef[BD_Y] == enemyRef[EN_Y] or bulletRef[BD_Y] == enemyRef[EN_Y] + 1 {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(VMTarget(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
        compileText(C64Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("optimizing inlined functions must reference proper scopes") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        void other.sub1()
 | 
						|
        cx16.r0L = other.sub1()+other.sub1()
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
other {
 | 
						|
    sub  sub2() -> ubyte{
 | 
						|
        cx16.r0++
 | 
						|
        cx16.r1++
 | 
						|
        return cx16.r0L
 | 
						|
    }
 | 
						|
 | 
						|
    sub sub1() -> ubyte {
 | 
						|
        return sub2()
 | 
						|
    }
 | 
						|
}"""
 | 
						|
 | 
						|
        compileText(VMTarget(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
        compileText(C64Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("if-else should not have 'not' in the condition even after optimization steps") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        if (cx16.r0 & 1) as bool == false
 | 
						|
            cx16.r1++
 | 
						|
        else
 | 
						|
            cx16.r2++
 | 
						|
 | 
						|
        if (cx16.r0 & 1) as bool == true
 | 
						|
            cx16.r1++
 | 
						|
        else
 | 
						|
            cx16.r2++
 | 
						|
 | 
						|
        if not((cx16.r0 & 1) as bool)
 | 
						|
            cx16.r1++
 | 
						|
        else
 | 
						|
            cx16.r2++
 | 
						|
 | 
						|
        if (cx16.r0 & 1) as bool
 | 
						|
            cx16.r1++
 | 
						|
        else
 | 
						|
            cx16.r2++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
        compileText(VMTarget(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
        compileText(C64Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("boolean comparisons without optimization can be assembled") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        bool @shared pre_start, xxx
 | 
						|
 | 
						|
        if (pre_start != false and xxx) {
 | 
						|
            return
 | 
						|
        } else if (pre_start != false and xxx) {
 | 
						|
            return
 | 
						|
        }
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
})
 |