import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import prog8.code.SymbolTableMaker import prog8.code.ast.* import prog8.code.core.* import prog8.code.source.SourceCode import prog8.code.target.VMTarget import prog8.codegen.vm.VmAssemblyProgram import prog8.codegen.vm.VmCodeGen import prog8.intermediate.IRSubroutine import prog8.intermediate.Opcode import prog8.intermediate.toAddress import prog8tests.helpers.DummyMemsizer import prog8tests.helpers.DummyStringEncoder import prog8tests.helpers.ErrorReporterForTests class TestVmCodeGen: FunSpec({ fun getTestOptions(): CompilationOptions { val target = VMTarget() return CompilationOptions.builder(target) .output(OutputType.RAW) .zeropage(ZeropageType.DONTUSE) .floats(true) .compilerVersion("99.99") .build() } test("augmented assigns") { //main { // sub start() { // ubyte[] particleX = [1,2,3] // ubyte[] particleDX = [1,2,3] // particleX[2] += particleDX[2] // // word @shared xx = 1 // xx = -xx // xx += 42 // xx += cx16.r0 // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "pi", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) sub.add(PtVariable( "particleX", DataType.arrayFor(BaseDataType.UBYTE), ZeropageWish.DONTCARE, 0u, false, null, 3u, Position.DUMMY )) sub.add(PtVariable( "particleDX", DataType.arrayFor(BaseDataType.UBYTE), ZeropageWish.DONTCARE, 0u, false, null, 3u, Position.DUMMY )) sub.add(PtVariable( "xx", DataType.WORD, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val assign = PtAugmentedAssign("+=", Position.DUMMY) val target = PtAssignTarget(false, Position.DUMMY).also { val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx -> idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY)) idx.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY)) } it.add(targetIdx) } val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY) value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY)) value.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY)) assign.add(target) assign.add(value) sub.add(assign) val prefixAssign = PtAugmentedAssign("-", Position.DUMMY) val prefixTarget = PtAssignTarget(false, Position.DUMMY).also { it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY)) } prefixAssign.add(prefixTarget) prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY)) sub.add(prefixAssign) val numberAssign = PtAugmentedAssign("+=", Position.DUMMY) val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also { it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY)) } numberAssign.add(numberAssignTarget) numberAssign.add(PtNumber(BaseDataType.WORD, 42.0, Position.DUMMY)) sub.add(numberAssign) val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY) val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also { it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY)) } cxregAssign.add(cxregAssignTarget) cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY)) sub.add(cxregAssign) block.add(sub) program.add(block) // define the "cx16.r0" virtual register val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY)) program.add(cx16block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBe 1 } test("augmented assigns with PtMemoryByte target - all register opcodes") { // Tests the fix for using ADDR/SUBR/ANDR/ORR/XORR (register-to-register) // instead of ADD/SUB/AND/OR/XOR (register+immediate) when the target is a // PtMemoryByte with a non-constant address and the operand is a variable. // // Pattern: @(ptr+offset) = @(ptr+offset) + val i.e. ptr[offset] += val // Non-zero offsets are used to avoid potential optimization of the address expression. val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable("ptr", DataType.pointer(BaseDataType.UBYTE), ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY)) sub.add(PtVariable("val", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY)) fun makeMemByteAssign(op: String, offset: Int): PtAugmentedAssign { // Use non-zero offset to avoid optimization val addrExpr = PtBinaryExpression("+", DataType.UWORD, Position.DUMMY) addrExpr.add(PtIdentifier("main.start.ptr", DataType.pointer(BaseDataType.UBYTE), Position.DUMMY)) addrExpr.add(PtNumber(BaseDataType.UWORD, offset.toDouble(), Position.DUMMY)) val memByte = PtMemoryByte(Position.DUMMY) memByte.add(addrExpr) val target = PtAssignTarget(false, Position.DUMMY) target.add(memByte) val assign = PtAugmentedAssign(op, Position.DUMMY) assign.add(target) assign.add(PtIdentifier("main.start.val", DataType.UBYTE, Position.DUMMY)) return assign } sub.add(makeMemByteAssign("+=", 3)) sub.add(makeMemByteAssign("-=", 5)) sub.add(makeMemByteAssign("|=", 7)) sub.add(makeMemByteAssign("&=", 11)) sub.add(makeMemByteAssign("^=", 13)) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBe 1 val instructions = irChunks[0].instructions // Verify correct register-to-register opcodes are used (not immediate variants) val addrCount = instructions.count { it.opcode == Opcode.ADDR } val subrCount = instructions.count { it.opcode == Opcode.SUBR } val orrCount = instructions.count { it.opcode == Opcode.ORR } val andrCount = instructions.count { it.opcode == Opcode.ANDR } val xorrCount = instructions.count { it.opcode == Opcode.XORR } addrCount shouldBe 1 subrCount shouldBe 1 orrCount shouldBe 1 andrCount shouldBe 1 xorrCount shouldBe 1 // Verify that the immediate-only variants are NOT used with reg2 val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.reg2 != null } val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.reg2 != null } val wrongOr = instructions.any { it.opcode == Opcode.OR && it.reg2 != null } val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.reg2 != null } val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.reg2 != null } (wrongAdd || wrongSub || wrongOr || wrongAnd || wrongXor) shouldBe false } test("float comparison expressions against zero") { //main { // sub start() { // float @shared f1 // // if f1==0 // nop // if f1!=0 // nop // if f1>0 // nop // if f1<0 // nop // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "f1", DataType.FLOAT, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val if1 = PtIfElse(Position.DUMMY) val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY) cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp1.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY)) if1.add(cmp1) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if1) val if2 = PtIfElse(Position.DUMMY) val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY) cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp2.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY)) if2.add(cmp2) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if2) val if3 = PtIfElse(Position.DUMMY) val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY) cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp3.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY)) if3.add(cmp3) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if3) val if4 = PtIfElse(Position.DUMMY) val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY) cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp4.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY)) if4.add(cmp4) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if4) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBeGreaterThan 4 } test("float comparison expressions against nonzero") { //main { // sub start() { // float @shared f1 // // if f1==42 // nop // if f1!=42 // nop // if f1>42 // nop // if f1<42 // nop // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "f1", DataType.FLOAT, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val if1 = PtIfElse(Position.DUMMY) val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY) cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp1.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY)) if1.add(cmp1) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if1) val if2 = PtIfElse(Position.DUMMY) val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY) cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp2.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY)) if2.add(cmp2) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if2) val if3 = PtIfElse(Position.DUMMY) val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY) cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp3.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY)) if3.add(cmp3) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if3) val if4 = PtIfElse(Position.DUMMY) val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY) cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp4.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY)) if4.add(cmp4) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if4) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBeGreaterThan 4 } test("float conditional jump") { //main { // sub start() { // float @shared f1 // // if f1==42 // goto $c000 // if f1>42 // goto $c000 // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "f1", DataType.FLOAT, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val if1 = PtIfElse(Position.DUMMY) val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY) cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp1.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY)) if1.add(cmp1) if1.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also { it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) }) if1.add(PtNodeGroup()) sub.add(if1) val if2 = PtIfElse(Position.DUMMY) val cmp2 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY) cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY)) cmp2.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY)) if2.add(cmp2) if2.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also { it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) }) if2.add(PtNodeGroup()) sub.add(if2) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBe 1 } test("integer comparison expressions against zero") { //main { // sub start() { // byte @shared sb1 // // if sb1==0 // nop // if sb1!=0 // nop // if sb1>0 // nop // if sb1<0 // nop // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "sb1", DataType.BYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val if1 = PtIfElse(Position.DUMMY) val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY) cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp1.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY)) if1.add(cmp1) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if1) val if2 = PtIfElse(Position.DUMMY) val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY) cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp2.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY)) if2.add(cmp2) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if2) val if3 = PtIfElse(Position.DUMMY) val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY) cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp3.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY)) if3.add(cmp3) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if3) val if4 = PtIfElse(Position.DUMMY) val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY) cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp4.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY)) if4.add(cmp4) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if4) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBeGreaterThan 4 } test("integer comparison expressions against nonzero") { //main { // sub start() { // byte @shared sb1 // // if sb1==42 // nop // if sb1!=42 // nop // if sb1>42 // nop // if sb1<42 // nop // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "sb1", DataType.BYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val if1 = PtIfElse(Position.DUMMY) val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY) cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp1.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY)) if1.add(cmp1) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if1) val if2 = PtIfElse(Position.DUMMY) val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY) cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp2.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY)) if2.add(cmp2) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if2) val if3 = PtIfElse(Position.DUMMY) val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY) cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp3.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY)) if3.add(cmp3) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if3) val if4 = PtIfElse(Position.DUMMY) val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY) cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY)) cmp4.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY)) if4.add(cmp4) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) }) sub.add(if4) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBeGreaterThan 4 } test("integer conditional jump") { //main { // sub start() { // ubyte @shared ub1 // // if ub1==42 // goto $c000 // if ub1>42 // goto $c000 // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "ub1", DataType.BYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY )) val if1 = PtIfElse(Position.DUMMY) val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY) cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY)) cmp1.add(PtNumber(BaseDataType.UBYTE, 42.0, Position.DUMMY)) if1.add(cmp1) if1.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also { it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) }) if1.add(PtNodeGroup()) sub.add(if1) val if2 = PtIfElse(Position.DUMMY) val cmp2 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY) cmp2.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY)) cmp2.add(PtNumber(BaseDataType.UBYTE, 42.0, Position.DUMMY)) if2.add(cmp2) if2.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also {it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) }) if2.add(PtNodeGroup()) sub.add(if2) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBe 1 } test("extsub allowed in ir-codegen") { //main { // extsub $5000 = routine() // // sub start() { // routine() // } //} val codegen = VmCodeGen(false) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY) block.add(extsub) val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) val call = PtFunctionCall("main.routine", false, false, emptyArray(), Position.DUMMY) sub.add(call) block.add(sub) program.add(block) val options = getTestOptions() val st = SymbolTableMaker(program, options).make() val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks irChunks.size shouldBe 1 irChunks[0].instructions.size shouldBe 1 val callInstr = irChunks[0].instructions[0] callInstr.opcode shouldBe Opcode.CALL callInstr.address shouldBe 0x5000u.toAddress() } })