mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
1130 lines
40 KiB
Kotlin
1130 lines
40 KiB
Kotlin
package prog8tests.compiler
|
|
|
|
import io.kotest.assertions.withClue
|
|
import io.kotest.core.spec.style.FunSpec
|
|
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.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({
|
|
test("remove empty subroutine except start") {
|
|
val sourcecode = """
|
|
main {
|
|
sub start() {
|
|
}
|
|
sub empty() {
|
|
; going to be removed
|
|
}
|
|
}
|
|
"""
|
|
val result = compileText(C64Target(), true, sourcecode)!!
|
|
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) 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)!!
|
|
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, writeAssembly = true) shouldNotBe null
|
|
compileText(VMTarget(), true, sourcecode, writeAssembly = true) shouldNotBe null
|
|
}
|
|
|
|
test("generated constvalue from typecast inherits proper parent linkage") {
|
|
val number = NumericLiteral(DataType.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 DataType.BYTE
|
|
constvalue.parent shouldBeSameInstanceAs tc.parent
|
|
}
|
|
|
|
test("generated constvalue from prefixexpr inherits proper parent linkage") {
|
|
val number = NumericLiteral(DataType.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 DataType.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, 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, 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, writeAssembly = false)!!
|
|
val assignFF = result.compilerAst.entrypoint.statements.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, 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)
|
|
}
|
|
}
|
|
"""
|
|
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
|
result.compilerAst.entrypoint.statements.size shouldBe 7
|
|
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, writeAssembly=false)!!
|
|
result.compilerAst.entrypoint.statements.size shouldBe 3
|
|
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[2] as Subroutine
|
|
func2.statements.size shouldBe 2
|
|
(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, writeAssembly=false)!!
|
|
result.compilerAst.entrypoint.statements.size shouldBe 0
|
|
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, 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 12
|
|
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(DataType.UBYTE, 10.0, Position.DUMMY)
|
|
z2decl.name shouldBe "z2"
|
|
z2init.value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY)
|
|
z3decl.name shouldBe "z3"
|
|
z3init.value shouldBe NumericLiteral(DataType.BOOL, 1.0, Position.DUMMY)
|
|
z4decl.name shouldBe "z4"
|
|
z4init.value shouldBe NumericLiteral(DataType.UWORD, 0.0, Position.DUMMY)
|
|
z5decl.name shouldBe "z5"
|
|
(z5init.value as BinaryExpression).operator shouldBe "+"
|
|
(z5init.value as BinaryExpression).right shouldBe NumericLiteral(DataType.UBYTE, 5.0, Position.DUMMY)
|
|
z6decl.name shouldBe "z6"
|
|
(z6init.value as BinaryExpression).operator shouldBe "-"
|
|
(z6init.value as BinaryExpression).right shouldBe NumericLiteral(DataType.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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 5
|
|
val assign=stmts.last() 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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 5
|
|
val assign=stmts.last() 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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 10
|
|
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, 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, 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, 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, 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 6
|
|
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, 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 7
|
|
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(DataType.UWORD, 20.0, Position.DUMMY)
|
|
val assignXX2 = stmts.last() 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(DataType.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, writeAssembly=true) shouldNotBe null
|
|
|
|
val result = compileText(C64Target(), optimize=true, src, 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 6
|
|
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.ARRAY_UB)
|
|
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, writeAssembly=true) shouldNotBe null
|
|
|
|
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 5
|
|
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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 5
|
|
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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 5
|
|
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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 5
|
|
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, writeAssembly=false)!!
|
|
val stmts = result.compilerAst.entrypoint.statements
|
|
stmts.size shouldBe 3
|
|
}
|
|
|
|
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, 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, 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, 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, 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, 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, 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, writeAssembly = false, errors = errors)!!
|
|
val st = result.compilerAst.entrypoint.statements
|
|
st.size shouldBe 4
|
|
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, 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, writeAssembly = false)!!
|
|
val st = result.compilerAst.entrypoint.statements
|
|
st.size shouldBe 8
|
|
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, writeAssembly = false)!!
|
|
val st = result.compilerAst.entrypoint.statements
|
|
st.size shouldBe 12
|
|
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, writeAssembly = false)!!
|
|
val st = result.compilerAst.entrypoint.statements
|
|
st.size shouldBe 17
|
|
|
|
val answerValue = (st[3] as Assignment).value
|
|
answerValue shouldBe NumericLiteral(DataType.UWORD, 0.0, Position.DUMMY)
|
|
|
|
val funcarg1 = (st[4] as FunctionCallStatement).args.single()
|
|
funcarg1 shouldBe NumericLiteral(DataType.UWORD, 0.0, Position.DUMMY)
|
|
|
|
val answer2Value = (st[8] as Assignment).value
|
|
answer2Value shouldBe NumericLiteral(DataType.UWORD, 8.0, Position.DUMMY)
|
|
|
|
val funcarg2 = (st[9] as FunctionCallStatement).args.single()
|
|
funcarg2 shouldBe NumericLiteral(DataType.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, writeAssembly = false)!!
|
|
val st = result.compilerAst.entrypoint.statements
|
|
st.size shouldBe 3
|
|
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 {
|
|
romsub ${'$'}2000 = func1() clobbers(X) -> ubyte @A, word @R0, byte @R1
|
|
romsub ${'$'}3000 = func2() clobbers(X) -> ubyte @A, uword @R0, uword @R1
|
|
romsub ${'$'}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, writeAssembly = true)!!
|
|
val st = result.codegenAst!!.entrypoint()!!.children
|
|
st.size shouldBe 9
|
|
(st[2] as PtFunctionCall).name shouldBe "cbm.GETIN"
|
|
(st[2] as PtFunctionCall).void shouldBe true
|
|
val a1 = st[3] 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[4] as PtFunctionCall).name shouldBe "p8b_main.p8s_func1"
|
|
(st[4] as PtFunctionCall).void shouldBe true
|
|
val a2 = st[5] 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[6] as PtFunctionCall).name shouldBe "p8b_main.p8s_func3"
|
|
(st[6] as PtFunctionCall).void shouldBe true
|
|
val a3 = st[7] 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, writeAssembly = true) shouldNotBe null
|
|
compileText(C64Target(), true, src, 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, writeAssembly = true) shouldNotBe null
|
|
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
|
|
}
|
|
})
|