From ba76fbfe85281252a271f1cb2caadbf589eb7eca Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 17 Sep 2026 22:39:53 +0200 Subject: [PATCH] fix code generation for "return " --- .../src/prog8/codegen/cpu6502/AsmGen.kt | 10 ++++++ .../prog8/codegen/intermediate/IRCodeGen.kt | 9 ++++- compiler/test/codegeneration/TestM68k.kt | 33 +++++++++++++++++++ .../test/codegeneration/TestVariousCodeGen.kt | 30 +++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 83d3d9d6d..0f18adb3b 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -1982,6 +1982,16 @@ $repeatLabel""") return } + if(sub.signature.returns.isEmpty()) { + // a void subroutine may return the result of a void function call (e.g. "return voidfunc(args)"): + // perform the call for its side effects, then return + val fcall = ret.children.single() as? PtFunctionCall + ?: throw AssemblyError("void subroutine can only return the result of a void function call ${ret.position}") + functioncallAsmGen.translateFunctionCall(fcall) + out(" rts") + return + } + val returnDt = sub.signature.returns.single() if (returnDt.isNumericOrBool || returnDt.isPointer) { assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!, returnDt.isSigned) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 869b027eb..042c94ca1 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -1895,7 +1895,14 @@ class IRCodeGen( } val value = ret.children.singleOrNull() - if(value==null) { + if(ret.numReturnValues()==0) { + // a void subroutine may return the result of a void function call (e.g. "return voidfunc(args)"): + // translate the call for its side effects, then return void + val callValue = value as? PtExpression + if(callValue!=null) + result += expressionEval.translateExpression(callValue).chunks + addInstr(result, IRInstructions.returnVoid(), null) + } else if(value==null) { addInstr(result, IRInstructions.returnVoid(), null) } else { value as PtExpression diff --git a/compiler/test/codegeneration/TestM68k.kt b/compiler/test/codegeneration/TestM68k.kt index 8bc04bed1..f77382216 100644 --- a/compiler/test/codegeneration/TestM68k.kt +++ b/compiler/test/codegeneration/TestM68k.kt @@ -13,6 +13,39 @@ class TestM68k : FunSpec({ val outputDir = tempdir().toPath() + test("void subroutine returning the result of a void function call compiles") { + // Regression test: "return voidfunc(args)" inside a void subroutine is valid Prog8. + // The optimizer only rewrites this to "voidfunc(args); return" when it can inline the + // caller, so in general it reaches the IR codegen as a Return whose value expression is a + // void function call. The m68k/IR backend must translate the call for its side effects and + // then return void instead of dying with 'no IR datatype for undefined'. + val src = """ +main { + ubyte flag + + sub start() { + flag = 1 + benchmark(flag) + } + + sub benchmark(ubyte f) { + when f { + 1 -> return helper(f) + else -> { + } + } + } + + sub helper(ubyte x) { + } +} +""" + compileText(Amiga500Target(), optimize = false, src, outputDir, writeAssembly = true, assemble = false) shouldNotBe null + compileText(Amiga500Target(), optimize = true, src, outputDir, writeAssembly = true, assemble = false) shouldNotBe null + compileText(Qemu68kTarget(), optimize = false, src, outputDir, writeAssembly = true, assemble = false) shouldNotBe null + compileText(Qemu68kTarget(), optimize = true, src, outputDir, writeAssembly = true, assemble = false) shouldNotBe null + } + test("module sub returning uword computed from a long expression assembles on qemu68k") { // Regression test for a m68k backend bug: a module-level sub whose return // type is uword, but whose return expression is a 'long' value cast to uword, diff --git a/compiler/test/codegeneration/TestVariousCodeGen.kt b/compiler/test/codegeneration/TestVariousCodeGen.kt index b10df684a..91f1c07aa 100644 --- a/compiler/test/codegeneration/TestVariousCodeGen.kt +++ b/compiler/test/codegeneration/TestVariousCodeGen.kt @@ -24,6 +24,36 @@ class TestVariousCodeGen: FunSpec({ val outputDir = tempdir().toPath() + test("void subroutine returning the result of a void function call compiles on 6502") { + // Regression test: "return voidfunc(args)" inside a void subroutine is valid Prog8. + // The optimizer only rewrites this to "voidfunc(args); return" when it can inline the + // caller, so in general it reaches the 6502 codegen as a Return whose value expression is a + // void function call. The 6502 backend must call it for its side effects and then return. + val text = """ +main { + ubyte flag + + sub start() { + flag = 1 + benchmark(flag) + } + + sub benchmark(ubyte f) { + when f { + 1 -> return helper(f) + else -> { + } + } + } + + sub helper(ubyte x) { + } +} +""" + compileText(Cx16Target(), false, text, outputDir, writeAssembly = true) shouldNotBe null + compileText(Cx16Target(), true, text, outputDir, writeAssembly = true) shouldNotBe null + } + test("nested scoping") { val text=""" main {