prog8/compiler/test/codegeneration/TestVariousCodeGen.kt

567 lines
14 KiB
Kotlin
Raw Normal View History

package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
2023-02-09 21:49:34 +00:00
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
2023-02-09 21:49:34 +00:00
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.instanceOf
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtBinaryExpression
2024-11-18 21:33:47 +00:00
import prog8.code.ast.PtFunctionCall
import prog8.code.ast.PtIfElse
import prog8.code.ast.PtPrefix
2023-02-09 21:49:34 +00:00
import prog8.code.ast.PtVariable
import prog8.code.core.BaseDataType
2023-02-09 21:49:34 +00:00
import prog8.code.core.DataType
import prog8.code.target.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
2023-02-19 02:07:55 +00:00
class TestVariousCodeGen: FunSpec({
2023-02-03 23:02:50 +00:00
test("nested scoping") {
val text="""
main {
sub start() {
testscope.duplicate()
cx16.r0L = testscope.duplicate2()
}
}
testscope {
sub sub1() {
ubyte @shared duplicate
ubyte @shared duplicate2
}
sub duplicate() {
; do nothing
}
sub duplicate2() -> ubyte {
return cx16.r0L
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("word array indexing") {
val text="""
main {
sub start() {
uword[3] seed
cx16.r0 = seed[0] + seed[1] + seed[2]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
2023-02-09 21:49:34 +00:00
test("ast result from compileText") {
val text="""
main {
sub start() {
uword[3] seed
cx16.r0 = seed[0] + seed[1] + seed[2]
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.compilerAst.name shouldStartWith "on_the_fly"
val ast = result.codegenAst!!
ast.name shouldBe result.compilerAst.name
ast.children.size shouldBeGreaterThan 2
val start = ast.entrypoint()!!
start.name shouldBe "p8s_start"
2023-02-09 21:49:34 +00:00
start.children.size shouldBeGreaterThan 2
val seed = start.children[0] as PtVariable
seed.name shouldBe "p8v_seed"
2023-02-09 21:49:34 +00:00
seed.value shouldBe null
seed.type shouldBe DataType.arrayFor(BaseDataType.UWORD)
2023-02-09 21:49:34 +00:00
val assign = start.children[1] as PtAssignment
assign.target.identifier!!.name shouldBe "cx16.r0"
assign.value shouldBe instanceOf<PtBinaryExpression>()
2023-02-09 21:49:34 +00:00
}
test("peek and poke argument types") {
val text="""
main {
sub start() {
uword[3] arr
ubyte i = 42
uword ww = peekw(arr[i])
ubyte xx = peek(arr[i])
xx = @(arr[i])
@(arr[i]) = 42
poke(arr[i], 42)
pokew(arr[i], 4242)
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("assigning memory byte into arrays works") {
val text="""
main {
sub start() {
uword factor1
ubyte[3] bytearray
uword[3] wordarray
@(factor1) = bytearray[0]
bytearray[0] = @(factor1)
@(factor1) = lsb(wordarray[0])
wordarray[0] = @(factor1)
@(5000) = bytearray[0]
@(5000) = lsb(wordarray[0])
bytearray[0] = @(5000)
wordarray[0] = @(5000)
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("reading memory from unknown var gives proper error") {
val text="""
main {
sub start() {
cx16.r0L = @(doesnotexist)
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = true, errors = errors)
2023-07-08 20:34:47 +00:00
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "isn't uword"
errors.errors[1] shouldContain "undefined symbol: doesnotexist"
}
2023-03-21 01:54:26 +00:00
test("shifting by word value is ok") {
val text="""
main {
sub start() {
ubyte @shared c = 1
2023-03-21 01:54:26 +00:00
@(15000 + c<<${'$'}0003) = 42
@(15000 + (c<<${'$'}0003)) = 42
@(15000 + c*${'$'}0008) = 42 ; *8 becomes a shift after opt
uword @shared qq = 15000 + c<<${'$'}0003
qq = 15000 + (c<<${'$'}0003)
qq = 16000 + c*${'$'}0008
}
}"""
2023-07-02 13:26:04 +00:00
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), true, text, writeAssembly = true) shouldNotBe null
2023-03-21 01:54:26 +00:00
}
test("builtin func in float expression") {
val src="""
%import floats
main {
sub start() {
float @shared fl =25.1
fl = abs(fl)+0.5
fl = sqrt(fl)+0.5
}
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("string vars in inlined subroutines are ok") {
val src="""
main {
sub start() {
void block2.curdir()
void block2.other()
}
}
block2 {
str result="zzzz"
sub curdir() -> str {
return result
}
sub other() -> str {
return "other"
}
}"""
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
}
2023-06-30 23:44:19 +00:00
test("array with pointers") {
val src = """
main {
sub start() {
str localstr = "hello"
ubyte[] otherarray = [1,2,3]
uword[] words = [1111,2222,"three",&localstr,&otherarray]
uword @shared zz = &words
bool @shared result = 2222 in words
2023-06-30 23:44:19 +00:00
zz = words[2]
zz++
zz = words[3]
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
}
test("case sensitive symbols") {
val src = """
main {
sub start() {
ubyte bytevar = 11 ; var at 0
ubyte byteVAR = 22 ; var at 1
ubyte ByteVar = 33 ; var at 2
ubyte @shared total = bytevar+byteVAR+ByteVar ; var at 3
goto skipLABEL
SkipLabel:
return
skipLABEL:
bytevar = 42
}
}"""
val target = Cx16Target()
compileText(target, true, src, writeAssembly = true) shouldNotBe null
}
test("addresses from labels/subroutines") {
val src = """
main {
sub start() {
mylabel:
ubyte @shared variable
2023-06-30 23:44:19 +00:00
uword @shared pointer1 = &main.start
uword @shared pointer2 = &start
uword @shared pointer3 = &main.start.mylabel
uword @shared pointer4 = &mylabel
uword[] @shared ptrs = [&variable, &start, &main.start, &mylabel, &main.start.mylabel]
}
}
"""
compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null
}
test("duplicate symbols okay other block and variable") {
val src = """
main {
ubyte derp
sub start() {
derp++
foo.bar()
}
}
foo {
sub bar() {
derp.print()
}
}
derp {
sub print() {
cx16.r0++
cx16.r1++
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("ambiguous symbol name variable vs block") {
val src = """
main {
sub start() {
uword module
module++
module.test++
}
}
module {
ubyte @shared test
}
"""
val errors=ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "ambiguous symbol"
}
test("prefix expressions with typecasting") {
val src="""
main
{
sub start()
{
uword uw = 54321
ubyte ub = 123
word sw = -12345
byte sb = -123
func_uw(~ub as uword)
func_ub(~uw as ubyte)
func_uw(~sb as uword)
func_ub(~sw as ubyte)
func_w(-sb as word)
func_b(-sw as byte)
}
sub func_uw(uword arg) {
}
sub func_w(word arg) {
}
sub func_ub(ubyte arg) {
}
sub func_b(byte arg) {
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("inlining sub with 2 statements") {
val src="""
main {
sub start() {
init()
}
sub init() {
init_handler()
return
}
sub init_handler() {
cx16.r0++
}
}"""
compileText(VMTarget(), true, src, writeAssembly = false) shouldNotBe null
compileText(Cx16Target(), true, src, writeAssembly = false) shouldNotBe null
}
test("push pop are inlined also with noopt") {
val text = """
main {
sub start() {
sys.push(11)
sys.pushw(2222)
cx16.r2++
cx16.r1 = sys.popw()
cx16.r0L = sys.pop()
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val assemblyFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
val assembly = assemblyFile.readText()
assembly shouldContain "inlined routine follows: push"
assembly shouldContain "inlined routine follows: pushw"
assembly shouldContain "inlined routine follows: pop"
assembly shouldContain "inlined routine follows: popw"
}
test("syslib correctly available for raw outputs") {
val text = """
%output raw
%launcher none
%address ${'$'}2000
main {
sub start() {
cx16.r0++
sys.clear_carry()
}
}
"""
compileText(Cx16Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(C128Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(PETTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(AtariTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("block start address must be greater than program load address") {
val src = """
%output raw
%launcher none
%address ${'$'}2000
main $2000 {
sub start() {
sys.clear_carry()
}
}
otherblock ${'$'}2013 {
%option force_output
}
thirdblock ${'$'}2014 {
%option force_output
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "6:1: block address must be at least program load address + 20"
errors.errors[1] shouldContain "12:1: block address must be at least program load address + 20"
}
test("for loops with just 1 iteration") {
val src="""
main {
sub start() {
for cx16.r0L in 100 downto 100 {
cx16.r1++
}
for cx16.r0L in 100 to 100 {
cx16.r1++
}
for cx16.r0 in 2222 downto 2222 {
cx16.r1++
}
for cx16.r0 in 2222 to 2222 {
cx16.r1++
}
for cx16.r0L in 100 downto 100 step -5 {
cx16.r1++
}
for cx16.r0L in 100 to 100 step 5 {
cx16.r1++
}
for cx16.r0 in 2222 downto 2222 step -5 {
cx16.r1++
}
for cx16.r0 in 2222 to 2222 step 5 {
cx16.r1++
}
}
}"""
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("multiple status flags return values from asmsub") {
val src="""
main {
2024-11-06 21:14:53 +00:00
extsub 5000 = carryAndNegativeAndByteAndWord() -> bool @Pc, bool @Pn, ubyte @X, uword @AY
sub start() {
ubyte @shared x
uword @shared w
bool @shared flag1
bool @shared flag2
flag1, flag2, x, w = carryAndNegativeAndByteAndWord()
flag1, void, void, w = carryAndNegativeAndByteAndWord()
void, void, void, void = carryAndNegativeAndByteAndWord()
void carryAndNegativeAndByteAndWord()
}
}"""
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("missing rts in asmsub") {
val src="""
main {
sub start() {
test()
test2()
}
asmsub test() {
%asm {{
nop
nop
}}
}
inline asmsub test2() {
%asm {{
nop
nop
}}
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "asmsub seems to never return"
}
test("missing rts in asmsub suppressed") {
val src="""
main {
sub start() {
test()
}
asmsub test() {
%asm {{
nop
nop
; !notreached!
}}
}
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
2024-11-18 21:33:47 +00:00
test("if not without else is not swapped") {
val src="""
main {
sub start() {
if not thing()
cx16.r0++
}
sub thing() -> bool {
cx16.r0++
return false
}
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
val result = compileText(VMTarget(), false, src, writeAssembly = true)!!
val st = result.codegenAst!!.entrypoint()!!.children
st.size shouldBe 2
val ifelse = st[0] as PtIfElse
ifelse.hasElse() shouldBe false
(ifelse.condition as PtPrefix).operator shouldBe "not"
}
test("if not with else is swapped") {
val src="""
main {
sub start() {
if not thing()
cx16.r0++
else
cx16.r1++
}
sub thing() -> bool {
cx16.r0++
return false
}
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
val result = compileText(VMTarget(), false, src, writeAssembly = true)!!
val st = result.codegenAst!!.entrypoint()!!.children
st.size shouldBe 2
val ifelse = st[0] as PtIfElse
ifelse.hasElse() shouldBe true
ifelse.condition shouldBe instanceOf<PtFunctionCall>()
}
})