diff --git a/codeCore/src/prog8/code/core/Position.kt b/codeCore/src/prog8/code/core/Position.kt index b87874319..d749c0d1a 100644 --- a/codeCore/src/prog8/code/core/Position.kt +++ b/codeCore/src/prog8/code/core/Position.kt @@ -15,7 +15,7 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC return try { val path = Path(file).absolute().normalize().toString() "file://$path:$line:$startCol:" - } catch(x: InvalidPathException) { + } catch(_: InvalidPathException) { // this can occur on Windows when the source origin contains "invalid" characters such as ':' "file://$file:$line:$startCol:" } diff --git a/codeCore/src/prog8/code/target/c64/C64MachineDefinition.kt b/codeCore/src/prog8/code/target/c64/C64MachineDefinition.kt index f780cc1a1..375f68333 100644 --- a/codeCore/src/prog8/code/target/c64/C64MachineDefinition.kt +++ b/codeCore/src/prog8/code/target/c64/C64MachineDefinition.kt @@ -55,7 +55,7 @@ class C64MachineDefinition: IMachineDefinition { val process: Process try { process=processb.start() - } catch(x: IOException) { + } catch(_: IOException) { continue // try the next emulator executable } process.waitFor() diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt index aa44b85b7..3a40adce5 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt @@ -15,16 +15,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as // just ignore any result values from the function call. } - @JvmName("optimizeIntArgsViaRegisters") - internal fun optimizeIntArgsViaRegisters(params: List>) = - when(params.size) { - 1 -> params[0].value.type in IntegerDatatypesWithBoolean - 2 -> params[0].value.type in ByteDatatypesWithBoolean && params[1].value.type in ByteDatatypesWithBoolean - else -> false - } - - @JvmName("optimizeIntArgsViaRegistersNotIndexed") - internal fun optimizeIntArgsViaRegisters(params: List) = + internal fun optimizeIntArgsViaCpuRegisters(params: List) = when(params.size) { 1 -> params[0].type in IntegerDatatypesWithBoolean 2 -> params[0].type in ByteDatatypesWithBoolean && params[1].type in ByteDatatypesWithBoolean @@ -133,39 +124,23 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as } } else if(sub is PtSub) { - val (paramsViaRegisters, normalParams) = sub.parameters.withIndex().partition { it.value.register!=null } - if(normalParams.isNotEmpty()) { - if(optimizeIntArgsViaRegisters(normalParams)) { - when(normalParams.size) { - 1 -> { - val register = if (normalParams[0].value.type in ByteDatatypesWithBoolean) RegisterOrPair.A else RegisterOrPair.AY - argumentViaRegister(sub, IndexedValue(0, normalParams[0].value), call.args[0], register) - } - 2 -> { - if(normalParams[0].value.type in ByteDatatypesWithBoolean && normalParams[1].value.type in ByteDatatypesWithBoolean) { - // 2 byte params, second in Y, first in A - argumentViaRegister(sub, IndexedValue(0, normalParams[0].value), call.args[0], RegisterOrPair.A) - if(asmgen.needAsaveForExpr(call.args[1])) - asmgen.out(" pha") - argumentViaRegister(sub, IndexedValue(1, normalParams[1].value), call.args[1], RegisterOrPair.Y) - if(asmgen.needAsaveForExpr(call.args[1])) - asmgen.out(" pla") - } else { - throw AssemblyError("cannot use registers for word+byte") - } - } - else -> throw AssemblyError("cannot use registers for >2 arguments") - } - } else { - // arguments via variables - for(arg in normalParams.zip(call.args)) + if(optimizeIntArgsViaCpuRegisters(sub.parameters)) { + // Note that if the args fit into cpu registers, we don't concern ourselves here + // if they should be put into regular subroutine parameter variables, or the R0-R15 register variables. + // That is now up to the subroutine itself. + useCpuRegistersForArgs(call.args, sub) + } else { + // arguments via variables + val (normalParams, registerParams) = sub.parameters.withIndex().partition { it.value.register == null } + if (normalParams.isNotEmpty()) { + for (arg in normalParams.zip(call.args)) + argumentViaVariable(sub, arg.first.value, arg.second) + } + if (registerParams.isNotEmpty()) { + // the R0-R15 'registers' are not really registers. They're just special variables. + for (arg in registerParams.zip(call.args)) argumentViaVariable(sub, arg.first.value, arg.second) } - } - if(paramsViaRegisters.isNotEmpty()) { - // the R0-R15 'registers' are not really registers. They're just special variables. - for(arg in paramsViaRegisters.zip(call.args)) - argumentViaVariable(sub, arg.first.value, arg.second) } asmgen.out(" jsr $subAsmName") } @@ -174,6 +149,30 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller } + private fun useCpuRegistersForArgs(args: List, sub: PtSub) { + val params = sub.parameters + when(params.size) { + 1 -> { + val register = if (params[0].type in ByteDatatypesWithBoolean) RegisterOrPair.A else RegisterOrPair.AY + argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], register) + } + 2 -> { + if(params[0].type in ByteDatatypesWithBoolean && params[1].type in ByteDatatypesWithBoolean) { + // 2 byte params, second in Y, first in A + argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A) + if(asmgen.needAsaveForExpr(args[1])) + asmgen.out(" pha") + argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y) + if(asmgen.needAsaveForExpr(args[1])) + asmgen.out(" pla") + } else { + throw AssemblyError("cannot use registers for word+byte") + } + } + else -> throw AssemblyError("cannot use cpu registers for >2 arguments") + } + } + private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean { return when(arg) { diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt index 9ca65a2b4..768f5042a 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt @@ -421,28 +421,35 @@ internal class ProgramAndVarsGen( if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main")) entrypointInitialization() - val normalParams = sub.parameters.filter { it.register==null } - if(functioncallAsmGen.optimizeIntArgsViaRegisters(normalParams)) { - asmgen.out("; simple int arg(s) passed via register(s)") - when(normalParams.size) { + val params = sub.parameters + if(functioncallAsmGen.optimizeIntArgsViaCpuRegisters(params)) { + asmgen.out("; simple int arg(s) passed via cpu register(s)") + + fun varname(param: PtSubroutineParameter): String = + if(param.register==null) + param.name + else + param.register!!.asScopedNameVirtualReg(param.type).joinToString(".") + + when(params.size) { 1 -> { - val dt = normalParams[0].type - val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, normalParams[0].position, variableAsmName = normalParams[0].name) + val dt = params[0].type + val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, params[0].position, variableAsmName = varname(params[0])) if(dt in ByteDatatypesWithBoolean) - asmgen.assignRegister(RegisterOrPair.A, target) + asmgen.assignRegister(RegisterOrPair.A, target) // single byte in A else - asmgen.assignRegister(RegisterOrPair.AY, target) + asmgen.assignRegister(RegisterOrPair.AY, target) // word in AY } 2 -> { - val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, normalParams[0].type, sub, normalParams[0].position, variableAsmName = normalParams[0].name) - val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, normalParams[1].type, sub, normalParams[1].position, variableAsmName = normalParams[1].name) - if(normalParams[0].type in ByteDatatypesWithBoolean && normalParams[1].type in ByteDatatypesWithBoolean) { + val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, params[0].type, sub, params[0].position, variableAsmName = varname(params[0])) + val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, params[1].type, sub, params[1].position, variableAsmName = varname(params[1])) + if(params[0].type in ByteDatatypesWithBoolean && params[1].type in ByteDatatypesWithBoolean) { // 2 byte args, first in A, second in Y asmgen.assignRegister(RegisterOrPair.A, target1) asmgen.assignRegister(RegisterOrPair.Y, target2) - } else throw AssemblyError("cannot use registers for word+byte") + } else throw AssemblyError("cannot use registers for word+byte args") } - else -> throw AssemblyError("cannot use registers for >2 arguments") + else -> throw AssemblyError("cannot use registers for >2 args") } } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 9f6fb0386..19fb36b49 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -286,10 +286,10 @@ private class BuiltinFunctionsFacade(functions: Map): IBuilt if(exprfunc!=null) { return try { exprfunc(args, position, program) - } catch(x: NotConstArgumentException) { + } catch(_: NotConstArgumentException) { // const-evaluating the builtin function call failed. null - } catch(x: CannotEvaluateException) { + } catch(_: CannotEvaluateException) { // const-evaluating the builtin function call failed. null } @@ -354,7 +354,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget else try { ZeropageType.valueOf(zpoption) - } catch (x: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { ZeropageType.KERNALSAFE // error will be printed by the astchecker } @@ -383,7 +383,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget } else { try { OutputType.valueOf(outputTypeStr) - } catch (x: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { // set default value; actual check and error handling of invalid option is handled in the AstChecker later OutputType.PRG } @@ -396,7 +396,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget } else { try { CbmPrgLauncherType.valueOf(launcherTypeStr) - } catch (x: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { // set default value; actual check and error handling of invalid option is handled in the AstChecker later CbmPrgLauncherType.BASIC } diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 8ac67496d..bf12426eb 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -516,7 +516,7 @@ internal class AstChecker(private val program: Program, if (!subroutine.isAsmSubroutine && p.registerOrPair!=null) { if (p.registerOrPair !in Cx16VirtualRegisters) errors.err("can only use R0-R15 as register param for normal subroutines", p.position) else { - errors.warn("\uD83D\uDCA3 footgun: reusing R0-R15 as parameters risks overwriting due to no callstack or clobbering", subroutine.position) + errors.warn("\uD83D\uDCA3 footgun: reusing R0-R15 as parameters risks overwriting due to clobbering or no callstack", subroutine.position) if(p.type !in WordDatatypes && p.type !in ByteDatatypesWithBoolean) { errors.err("can only use register param when type is boolean, byte or word", p.position) } diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 812dab1fe..1b420eea0 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -219,8 +219,8 @@ class AstPreprocessor(val program: Program, val namesInSub = symbolsInSub.map{ it.first }.toSet() if(subroutine.asmAddress==null) { if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) { - var mods = mutableListOf() - var (normalParams, registerParams) = subroutine.parameters.partition { it.registerOrPair==null } + val mods = mutableListOf() + val (normalParams, registerParams) = subroutine.parameters.partition { it.registerOrPair==null } if(normalParams.isNotEmpty()) { val existingVars = subroutine.statements.asSequence().filterIsInstance().map { it.name }.toSet() normalParams diff --git a/compiler/test/ast/TestProg8Parser.kt b/compiler/test/ast/TestProg8Parser.kt index 6ab778b44..cd591774b 100644 --- a/compiler/test/ast/TestProg8Parser.kt +++ b/compiler/test/ast/TestProg8Parser.kt @@ -1050,7 +1050,7 @@ main { val src=""" main { sub start() { cx16.r0++ cx16.r1++ } } other { asmsub thing() { %asm {{ inx }} } } - """; + """ val result = compileText(VMTarget(), false, src, writeAssembly = false)!! val st = result.compilerAst.entrypoint.statements st.size shouldBe 2 diff --git a/docs/source/comparing.rst b/docs/source/comparing.rst index e85a5f62a..caa604ddf 100644 --- a/docs/source/comparing.rst +++ b/docs/source/comparing.rst @@ -58,7 +58,9 @@ Variables Subroutines ----------- -- There is no call stack. Subroutine parameters are overwritten when called again (recursion is not easily possible, but you can do it with manual stack manipulations). +- Subroutines can be nested. Inner subroutines can directly access variables from their parent. +- Subroutine parameters are just local variables in the subroutine. (you can access them directly as such via their scoped name, if you want) +- There is no call stack. So subroutine parameters are overwritten when called again. Thus recursion is not easily possible, but you can do it with manual stack manipulations. - There is no function overloading (except for a couple of builtin functions). - Some subroutine types can return multiple return values, and you can multi-assign those in a single statement. - Because every declared variable allocates some memory, it might be beneficial to share the same variables over different subroutines diff --git a/examples/test.p8 b/examples/test.p8 index b18fed581..4ecf199e9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,29 +1,43 @@ +%import textio +%zeropage basicsafe + main { sub start() { foo(42) - bar(9999,55) -; faulty1(false) - faulty2(42) - faulty3(9999,55) + foo(42) + foo(42) + bar(9999) + bar(9999) + bar(9999) + baz(42, 123) + baz(42, 123) + baz(42, 123) + meh(42, 9999) + meh(42, 9999) + meh(42, 9999) } - sub foo(ubyte arg @R2) { - arg++ + sub foo(ubyte arg @R0) { + txt.print_ub(arg) + txt.nl() } - sub bar(uword arg @R0, ubyte arg2 @R1) { - arg += arg2 + sub bar(uword arg @R0) { + txt.print_uw(arg) + txt.nl() } -; sub faulty1(bool flag @Pc) { -; cx16.r0++ -; } - - sub faulty2(byte arg @Y) { - arg++ + sub baz(ubyte arg1 @R0, ubyte arg2 @R1) { + txt.print_ub(arg1) + txt.spc() + txt.print_ub(arg2) + txt.nl() } - sub faulty3(uword arg @R1, ubyte arg2 @R1) { - arg += arg2 + sub meh(ubyte arg1 @R0, uword arg2 @R1) { + txt.print_ub(arg1) + txt.spc() + txt.print_uw(arg2) + txt.nl() } } diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index fd1c82473..958367bc2 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -72,7 +72,7 @@ fun parseIRCodeLine(line: String): Either { val (instr, typestr, rest) = match.destructured val opcode = try { Opcode.valueOf(instr.uppercase()) - } catch (ax: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { throw IRParseException("invalid vmasm instruction: $instr") } var type: IRDataType? = convertIRType(typestr) @@ -181,11 +181,11 @@ fun parseIRCodeLine(line: String): Either { throw IRParseException("needs value or symbol for $line") when (type) { IRDataType.BYTE -> { - if (immediateInt!=null && (immediateInt!! < -128 || immediateInt!! > 255)) + if (immediateInt!=null && (immediateInt < -128 || immediateInt > 255)) throw IRParseException("immediate value out of range for byte: $immediateInt") } IRDataType.WORD -> { - if (immediateInt!=null && (immediateInt!! < -32768 || immediateInt!! > 65535)) + if (immediateInt!=null && (immediateInt < -32768 || immediateInt > 65535)) throw IRParseException("immediate value out of range for word: $immediateInt") } IRDataType.FLOAT -> {} @@ -198,13 +198,13 @@ fun parseIRCodeLine(line: String): Either { var offset: Int? = null if(labelSymbol!=null) { - if (labelSymbol!![0] == 'r' && labelSymbol!![1].isDigit()) + if (labelSymbol[0] == 'r' && labelSymbol[1].isDigit()) throw IRParseException("labelsymbol confused with register?: $labelSymbol") - if('+' in labelSymbol!!) { - val offsetStr = labelSymbol!!.substringAfterLast('+') + if('+' in labelSymbol) { + val offsetStr = labelSymbol.substringAfterLast('+') if (offsetStr.isNotEmpty()) { offset = offsetStr.toInt() - labelSymbol = labelSymbol!!.substringBeforeLast('+') + labelSymbol = labelSymbol.substringBeforeLast('+') } } } @@ -320,10 +320,10 @@ internal fun parseRegisterOrStatusflag(sourceregs: String): RegisterOrStatusflag try { reg = RegisterOrPair.valueOf(regs) - } catch (x: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { try { sf = Statusflag.valueOf(regs) - } catch(x: IllegalArgumentException) { + } catch(_: IllegalArgumentException) { throw IRParseException("invalid IR register or statusflag: $regs") } } diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index 9fbabd5a8..a3606109b 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -270,7 +270,7 @@ object SysCalls { val trimmed = memstring.takeWhile { it in " +-0123456789.eE" } try { trimmed.toDouble() - } catch(x: NumberFormatException) { + } catch(_: NumberFormatException) { 0.0 } } @@ -567,7 +567,7 @@ object SysCalls { val height = response.next().toInt() return returnValue(callspec.returns.single(), height*256 + width, vm) } - } catch (x: Exception) { + } catch (_: Exception) { // don't know what happened... } return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30