Files
prog8/intermediate/test/TestIRFileInOut.kt
T

601 lines
21 KiB
Kotlin

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
import prog8.code.target.Amiga500Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.intermediate.*
import kotlin.io.path.Path
import kotlin.io.path.deleteExisting
import kotlin.io.path.readLines
class TestIRFileInOut: FunSpec({
test("IR reader requires format 4") {
shouldThrow<IRParseException> {
IRFileReader().read("""<?xml version="1.0"?><PROGRAM NAME="test" COMPILERVERSION="99.99"/>""")
}.message shouldBe "missing IRFORMAT"
shouldThrow<IRParseException> {
IRFileReader().read("""<?xml version="1.0"?><PROGRAM NAME="test" COMPILERVERSION="99.99" IRFORMAT="3"/>""")
}.message shouldBe "unsupported IR format: 3"
}
test("test IR writer") {
val target = Cx16Target()
val options = CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(0xffffu)
.outputDir(Path(""))
.build()
val program = IRProgram("unittest-irwriter", IRSymbolTable(), options, target)
val writer = IRFileWriter(program, Path("intermediate-irwriter-test-output.p8ir"))
val generatedFile = writer.write()
val lines = generatedFile.readLines()
lines[0] shouldBe "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
lines[1] shouldBe "<PROGRAM NAME=\"unittest-irwriter\" COMPILERVERSION=\"99.99\" IRFORMAT=\"5\">"
lines.last() shouldBe "</PROGRAM>"
generatedFile.deleteExisting()
lines.size shouldBeGreaterThan 20
}
test("IR pointer arrays round-trip") {
val target = VMTarget()
val options = CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(0xffffu)
.outputDir(Path(""))
.build()
val program = IRProgram("unittest-pointer-arrays", IRSymbolTable(), options, target)
program.st.add(IRStStaticVariable(
"main.primitive",
DataType.arrayOfPointersTo(BaseDataType.UBYTE),
null,
3u,
ZeropageWish.DONTCARE,
0u,
false
))
program.st.add(IRStStaticVariable(
"main.struct",
DataType.arrayOfPointersTo(IRSubtypePlaceholder("test.Entry")),
null,
2u,
ZeropageWish.DONTCARE,
0u,
false
))
val generatedFile = IRFileWriter(program, Path("intermediate-pointer-arrays-test-output.p8ir")).write()
try {
val lines = generatedFile.readLines()
lines.any { it.startsWith("^^ubyte[3] main.primitive") } shouldBe true
lines.any { it.startsWith("^^test.Entry[2] main.struct") } shouldBe true
val readProgram = IRFileReader().read(generatedFile)
val primitive = readProgram.st.lookup("main.primitive") as IRStStaticVariable
primitive.dt.base shouldBe BaseDataType.ARRAY
primitive.dt.sub shouldBe BaseDataType.POINTER
primitive.dt.pointeeSub shouldBe BaseDataType.UBYTE
val struct = readProgram.st.lookup("main.struct") as IRStStaticVariable
struct.dt.base shouldBe BaseDataType.ARRAY
struct.dt.sub shouldBe BaseDataType.POINTER
struct.dt.subType?.scopedNameString shouldBe "test.Entry"
} finally {
generatedFile.deleteExisting()
}
}
test("test IR reader") {
val source="""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test-ir-reader" COMPILERVERSION="99.99" IRFORMAT="4">
<OPTIONS>
compTarget=virtual
output=PRG
launcher=BASIC
zeropage=KERNALSAFE
loadAddress=$0000
</OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<VARS>
<NOINITCLEAN>
uword sys.bssvar zp=DONTCARE align=0
</NOINITCLEAN>
<NOINITDIRTY>
</NOINITDIRTY>
<INIT>
uword sys.wait.jiffies=10 zp=DONTCARE align=0
ubyte[3] sys.emptystring=0,0,0 zp=DONTCARE align=0
</INIT>
<STRUCTINSTANCESNOINIT>
</STRUCTINSTANCESNOINIT>
<STRUCTINSTANCES>
</STRUCTINSTANCES>
<CONSTANTS>
ubyte main.thing=42
</CONSTANTS>
<MEMORYMAPPED>
@uword cx16.r0=65282
</MEMORYMAPPED>
<MEMORYSLABS>
</MEMORYSLABS>
</VARS>
<INITGLOBALS>
<CHUNK><REGS>dummy</REGS><CODE>
load.b r1.b,#42.b
</CODE></CHUNK>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" NOPREFIXING="false" VERAFXMULS="false" ALIGN="NONE" POS="[examples/test.p8: line 2 col 2-5]">
<SUB NAME="main.start" RETURNS="" POS="[examples/test.p8: line 4 col 6-8]">
<PARAMS>
</PARAMS>
<CHUNK LABEL="main.start"><REGS>dummy</REGS><CODE>
return
</CODE></CHUNK>
</SUB>
</BLOCK>
<BLOCK NAME="sys" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" ALIGN="NONE" POS="[library:/prog8lib/virtual/syslib.p8: line 3 col 2-4]">
<SUB NAME="sys.wait" RETURNS="" POS="[library:/prog8lib/virtual/syslib.p8: line 15 col 6-8]">
<PARAMS>
uword sys.wait.jiffies
</PARAMS>
<ASM LABEL="sys.wait" IR="true" POS="[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]">
loadm.w r0.w,[sys.wait.jiffies]
</ASM>
<CHUNK><REGS>dummy</REGS><CODE>
return
</CODE></CHUNK>
</SUB>
</BLOCK>
</PROGRAM>
"""
val program = IRFileReader().read(source)
program.name shouldBe "test-ir-reader"
program.blocks.size shouldBe 2
program.st.allVariables().count() shouldBe 3
program.st.allConstants().count() shouldBe 1
val var1 = program.st.lookup("sys.wait.jiffies") as IRStStaticVariable
val var2 = program.st.lookup("sys.bssvar") as IRStStaticVariable
val var3 = program.st.lookup("sys.emptystring") as IRStStaticVariable
var1.uninitialized shouldBe false
var2.uninitialized shouldBe true
var3.uninitialized shouldBe true
}
test("test IR reader with struct containing pointer fields") {
val source="""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test-struct-pointer" COMPILERVERSION="99.99" IRFORMAT="4">
<OPTIONS>
compTarget=virtual
output=PRG
launcher=BASIC
zeropage=KERNALSAFE
loadAddress=$0000
</OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<VARS>
<NOINITCLEAN>
uword main.start zp=DONTCARE
</NOINITCLEAN>
<NOINITDIRTY>
</NOINITDIRTY>
<INIT>
</INIT>
<STRUCTINSTANCESNOINIT>
</STRUCTINSTANCESNOINIT>
<STRUCTINSTANCES>
re.State testinst size=8 values=uword:$0100,^^re.State:0,^^re.State:0,uword:0
</STRUCTINSTANCES>
<CONSTANTS>
</CONSTANTS>
<MEMORYMAPPED>
</MEMORYMAPPED>
<MEMORYSLABS>
</MEMORYSLABS>
</VARS>
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" NOPREFIXING="false" VERAFXMULS="false" ALIGN="NONE" POS="[test.p8: line 1 col 1-2]">
<SUB NAME="main.start" RETURNS="" POS="[test.p8: line 1 col 1-2]">
<PARAMS>
</PARAMS>
<CHUNK LABEL="main.start"><REGS>dummy</REGS><CODE>
load.b r1.b,#0.b
</CODE></CHUNK>
</SUB>
</BLOCK>
</PROGRAM>
"""
val program = IRFileReader().read(source)
program.name shouldBe "test-struct-pointer"
val struct = program.st.allStructInstances().first()
struct.name shouldBe "testinst"
struct.structName shouldBe "re.State"
}
test("test IR writer preserves decimal precision in float struct fields") {
// regression test: struct field floats used to be serialized via .toInt().toHex()
// which truncated the decimal portion (e.g. 1463.87 became 1463.0 in the loaded VM memory)
val target = Cx16Target()
val options = CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(0xffffu)
.outputDir(Path(""))
.build()
val program = IRProgram("unittest-float-struct", IRSymbolTable(), options, target)
val structName = "main.Country"
val fields = listOf(
IRStStructField(DataType.forDt(BaseDataType.UWORD), "name"),
IRStStructField(DataType.forDt(BaseDataType.FLOAT), "population"),
IRStStructField(DataType.forDt(BaseDataType.UWORD), "area")
)
program.st.add(IRStStructDef(structName, fields, 8u))
val instanceName = "main.india_instance"
val population = 1463.87
val area = 3287
program.st.add(IRStStructInstance(
instanceName,
structName,
listOf(
IRStructInitValue(BaseDataType.UWORD, IRStSymbolicReference.Numeric(0.0)),
IRStructInitValue(BaseDataType.FLOAT, IRStSymbolicReference.Numeric(population)),
IRStructInitValue(BaseDataType.UWORD, IRStSymbolicReference.Numeric(area.toDouble()))
),
8u
))
val writer = IRFileWriter(program, Path("intermediate-float-struct-test-output.p8ir"))
val generatedFile = writer.write()
val program2 = IRFileReader().read(generatedFile)
generatedFile.deleteExisting()
val instance = program2.st.allStructInstances().single { it.name == instanceName }
instance.values.size shouldBe 3
val floatValue = instance.values[1].value as IRStSymbolicReference.Numeric
floatValue.value shouldBe population
val intValue = instance.values[2].value as IRStSymbolicReference.Numeric
intValue.value shouldBe area.toDouble()
}
test("test IR reader parses loadhr/storehr sN immediate encoding") {
val source="""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test-sn-immediate" COMPILERVERSION="99.99" IRFORMAT="4">
<OPTIONS>
compTarget=virtual
output=PRG
launcher=BASIC
zeropage=KERNALSAFE
loadAddress=$0000
</OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<VARS>
<NOINITCLEAN>
</NOINITCLEAN>
<NOINITDIRTY>
</NOINITDIRTY>
<INIT>
</INIT>
<STRUCTINSTANCESNOINIT>
</STRUCTINSTANCESNOINIT>
<STRUCTINSTANCES>
</STRUCTINSTANCES>
<CONSTANTS>
</CONSTANTS>
<MEMORYMAPPED>
</MEMORYMAPPED>
<MEMORYSLABS>
</MEMORYSLABS>
</VARS>
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" NOPREFIXING="false" VERAFXMULS="false" ALIGN="NONE" POS="[test.p8: line 1 col 1-2]">
<SUB NAME="main.start" RETURNS="" POS="[test.p8: line 1 col 1-2]">
<PARAMS>
</PARAMS>
<CHUNK LABEL="main.start"><REGS>dummy</REGS><CODE>
loadhr.b r1.b,s0.b
storehr.b r1.b,s2.b
</CODE></CHUNK>
</SUB>
</BLOCK>
</PROGRAM>
"""
val program = IRFileReader().read(source)
val sub = program.blocks.single().children.single() as IRSubroutine
val instructions = sub.chunks.flatMap { it.instructions }
instructions.size shouldBe 2
instructions[0].opcode shouldBe Opcode.LOADHR
instructions[0].type shouldBe IRDataType.BYTE
instructions[0].requireDest().registerNumber shouldBe 1
instructions[0].requireHardwareSlot() shouldBe HardwareSlotOperand(CallingConventionSlot(0), IRDataType.BYTE)
instructions[1].opcode shouldBe Opcode.STOREHR
instructions[1].type shouldBe IRDataType.BYTE
instructions[1].requireSrcA().registerNumber shouldBe 1
instructions[1].requireHardwareSlot() shouldBe HardwareSlotOperand(CallingConventionSlot(2), IRDataType.BYTE)
}
test("test IR callfar round-trip with negative amiga LVO address") {
val target = Amiga500Target()
val options = CompilationOptions.builder(target)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(target.PROGRAM_MEMTOP_ADDRESS)
.outputDir(Path(""))
.quiet(true)
.build()
val program = IRProgram("unittest-callfar-lvo", IRSymbolTable(), options, target)
val block = IRBlock("main", library=false, IRBlock.Options(), Position("unittest", 1, 1, 1))
val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position("unittest", 1, 1, 1))
val chunk = IRCodeChunk("main.start", null)
chunk.instructions += IRInstructions.call(
Opcode.CALLFAR,
CallSite(CallTarget.AmigaLibrary(1, -552))
)
sub += chunk
block += sub
program.addBlock(block)
val generatedFile = IRFileWriter(program, Path("intermediate-callfar-test-output.p8ir")).write()
val readProgram = IRFileReader().read(generatedFile)
generatedFile.deleteExisting()
val readSub = readProgram.blocks.single().children.single() as IRSubroutine
val instr = readSub.chunks.flatMap { it.instructions }.single()
instr.opcode shouldBe Opcode.CALLFAR
instr.requireCallSite().target shouldBe CallTarget.AmigaLibrary(1, -552)
}
test("test IR file round-trip retains structured call details") {
val target = Cx16Target()
val options = CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(0xffffu)
.outputDir(Path(""))
.build()
val program = IRProgram("unittest-structured-calls", IRSymbolTable(), options, target)
val block = IRBlock("main", library = false, IRBlock.Options(), Position.DUMMY)
val start = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY)
val startChunk = IRCodeChunk("main.start", null)
startChunk += IRInstructions.call(
CallSite(
CallTarget.Direct(codeLabel("main.callee")),
arguments = listOf(Calls.argument(1, IRDataType.BYTE, CallLocation.ParameterMemory("main.callee.arg")))
)
)
startChunk += IRInstructions.call(
CallSite(
CallTarget.Direct(codeAddress(0xffd2u), "kernal.chrout"),
arguments = listOf(Calls.argument(1, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0)))),
results = listOf(
Calls.result(2, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0))),
CallResult(null, CallLocation.StatusFlag(Statusflag.Pc))
),
effects = CallEffects(MemoryEffect.READ, StatusEffect.READS)
)
)
startChunk += IRInstructions.returnVoid()
start += startChunk
val callee = IRSubroutine("main.callee", emptyList(), emptyList(), Position.DUMMY)
val calleeChunk = IRCodeChunk("main.callee", null)
calleeChunk += IRInstructions.returnVoid()
callee += calleeChunk
block += start
block += callee
program.addBlock(block)
val generatedFile = IRFileWriter(program, Path("intermediate-structured-calls-test-output.p8ir")).write()
val readProgram = IRFileReader().read(generatedFile)
generatedFile.deleteExisting()
val calls = (readProgram.blocks.single().children[0] as IRSubroutine)
.chunks.flatMap { it.instructions }.filter { it.opcode == Opcode.CALL }
calls[0].requireCallSite().arguments.single().location shouldBe CallLocation.ParameterMemory("main.callee.arg")
val asmCall = calls[1].requireCallSite()
(asmCall.target as CallTarget.Direct).externalName shouldBe "kernal.chrout"
asmCall.results[0].location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(0))
asmCall.results[1] shouldBe CallResult(null, CallLocation.StatusFlag(Statusflag.Pc))
asmCall.effects shouldBe CallEffects(MemoryEffect.READ, StatusEffect.READS)
}
test("test IR reader parses block-level CHUNK (label and align)") {
val source="""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test-block-level-chunk" COMPILERVERSION="99.99" IRFORMAT="4">
<OPTIONS>
compTarget=virtual
output=PRG
launcher=BASIC
zeropage=KERNALSAFE
loadAddress=$0000
</OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<VARS>
<NOINITCLEAN>
</NOINITCLEAN>
<NOINITDIRTY>
</NOINITDIRTY>
<INIT>
</INIT>
<STRUCTINSTANCESNOINIT>
</STRUCTINSTANCESNOINIT>
<STRUCTINSTANCES>
</STRUCTINSTANCES>
<CONSTANTS>
</CONSTANTS>
<MEMORYMAPPED>
</MEMORYMAPPED>
<MEMORYSLABS>
</MEMORYSLABS>
</VARS>
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" POS="[test.p8: line 1 col 1-2]">
<CHUNK><REGS><![CDATA[]]></REGS><CODE>
align #$100.l
</CODE></CHUNK>
<CHUNK LABEL="main.mylabel"><REGS><![CDATA[]]></REGS><CODE>
</CODE></CHUNK>
<SUB NAME="main.start" RETURNS="" POS="[test.p8: line 2 col 2-4]">
<PARAMS>
</PARAMS>
<CHUNK LABEL="main.start"><REGS><![CDATA[]]></REGS><CODE>
return
</CODE></CHUNK>
</SUB>
</BLOCK>
</PROGRAM>
"""
val program = IRFileReader().read(source)
val block = program.blocks.single()
// 1 align chunk + 1 label chunk + 1 sub
block.children.size shouldBe 3
val alignChunk = block.children[0] as IRCodeChunk
alignChunk.label shouldBe null
alignChunk.instructions.size shouldBe 1
alignChunk.instructions[0].opcode shouldBe Opcode.ALIGN
alignChunk.instructions[0].requireImmediateInt() shouldBe 256
val labelChunk = block.children[1] as IRCodeChunk
labelChunk.label shouldBe "main.mylabel"
}
test("test IR union round-trip preserves isUnion and size") {
val target = Cx16Target()
val options = CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(0xffffu)
.outputDir(Path(""))
.build()
val program = IRProgram("unittest-union-roundtrip", IRSymbolTable(), options, target)
val structName = "main.MyUnion"
val fields = listOf(
IRStStructField(DataType.forDt(BaseDataType.UBYTE), "byteField"),
IRStStructField(DataType.forDt(BaseDataType.UWORD), "wordField")
)
program.st.add(IRStStructDef(structName, fields, 2u, true))
program.st.add(IRStStructInstance("main.myUnion", structName, emptyList(), 2u))
val writer = IRFileWriter(program, Path("intermediate-union-roundtrip-test-output.p8ir"))
val generatedFile = writer.write()
val program2 = IRFileReader().read(generatedFile)
generatedFile.deleteExisting()
val def = program2.st.lookup(structName) as IRStStructDef
def.isUnion shouldBe true
def.size shouldBe 2u
def.fields.size shouldBe 2
val instance = program2.st.lookup("main.myUnion") as IRStStructInstance
instance.size shouldBe 2u
}
test("test IR pointer array round-trip preserves ARRAY+POINTER+pointeeSub") {
val target = Amiga500Target()
val options = CompilationOptions.builder(target)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
.memtopAddress(target.PROGRAM_MEMTOP_ADDRESS)
.outputDir(Path(""))
.quiet(true)
.build()
val program = IRProgram("unittest-pointer-array-roundtrip", IRSymbolTable(), options, target)
val structDef = IRStStructDef(
"re.State",
listOf(IRStStructField(DataType.forDt(BaseDataType.UWORD), "value")),
2u
)
program.st.add(structDef)
program.st.add(IRStStaticVariable(
"main.primptrs",
DataType.arrayOfPointersTo(BaseDataType.UBYTE),
null,
4u,
ZeropageWish.DONTCARE,
0u,
false
))
program.st.add(IRStStaticVariable(
"main.stateptrs",
DataType.arrayOfPointersTo(IRStructSubtype(structDef)),
null,
2u,
ZeropageWish.DONTCARE,
0u,
false
))
val generatedFile = IRFileWriter(program, Path("intermediate-pointer-array-roundtrip-test-output.p8ir")).write()
val lines = generatedFile.readLines()
val readProgram = IRFileReader().read(generatedFile)
generatedFile.deleteExisting()
lines.any { "^^ubyte[4]" in it } shouldBe true
lines.any { "^^re.State[2]" in it } shouldBe true
val prim = readProgram.st.lookup("main.primptrs") as IRStStaticVariable
prim.dt.isPointerArray shouldBe true
prim.dt.base shouldBe BaseDataType.ARRAY
prim.dt.sub shouldBe BaseDataType.POINTER
prim.dt.pointeeSub shouldBe BaseDataType.UBYTE
prim.length shouldBe 4u
val state = readProgram.st.lookup("main.stateptrs") as IRStStaticVariable
state.dt.isPointerArray shouldBe true
state.dt.base shouldBe BaseDataType.ARRAY
state.dt.sub shouldBe BaseDataType.POINTER
state.dt.pointeeSub shouldBe BaseDataType.STRUCT_INSTANCE
state.dt.subType shouldNotBe null
(state.dt.subType as IRStructSubtype).def.name shouldBe "re.State"
state.length shouldBe 2u
}
})