mirror of
https://github.com/irmen/prog8.git
synced 2024-11-27 19:49:24 +00:00
call convention for @Rx parameters, also use cpu registers if possible, like normal parameters
This commit is contained in:
parent
d58f9f56c4
commit
2eed75f602
@ -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:"
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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<IndexedValue<PtSubroutineParameter>>) =
|
||||
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<PtSubroutineParameter>) =
|
||||
internal fun optimizeIntArgsViaCpuRegisters(params: List<PtSubroutineParameter>) =
|
||||
when(params.size) {
|
||||
1 -> params[0].type in IntegerDatatypesWithBoolean
|
||||
2 -> params[0].type in ByteDatatypesWithBoolean && params[1].type in ByteDatatypesWithBoolean
|
||||
@ -133,40 +124,24 @@ 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")
|
||||
}
|
||||
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(paramsViaRegisters.isNotEmpty()) {
|
||||
if (registerParams.isNotEmpty()) {
|
||||
// the R0-R15 'registers' are not really registers. They're just special variables.
|
||||
for(arg in paramsViaRegisters.zip(call.args))
|
||||
for (arg in registerParams.zip(call.args))
|
||||
argumentViaVariable(sub, arg.first.value, arg.second)
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
else throw AssemblyError("invalid sub type")
|
||||
@ -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<PtExpression>, 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) {
|
||||
|
@ -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) {
|
||||
1 -> {
|
||||
val dt = normalParams[0].type
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, normalParams[0].position, variableAsmName = normalParams[0].name)
|
||||
if(dt in ByteDatatypesWithBoolean)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
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
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
param.register!!.asScopedNameVirtualReg(param.type).joinToString(".")
|
||||
|
||||
when(params.size) {
|
||||
1 -> {
|
||||
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) // single byte in A
|
||||
else
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,10 +286,10 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): 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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<IAstModification>()
|
||||
var (normalParams, registerParams) = subroutine.parameters.partition { it.registerOrPair==null }
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val (normalParams, registerParams) = subroutine.parameters.partition { it.registerOrPair==null }
|
||||
if(normalParams.isNotEmpty()) {
|
||||
val existingVars = subroutine.statements.asSequence().filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
normalParams
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
|
||||
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<IRInstruction, String> {
|
||||
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<IRInstruction, String> {
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user