mirror of
https://github.com/irmen/prog8.git
synced 2025-11-03 04:17:16 +00:00
register params support for normal subroutines
This commit is contained in:
@@ -266,7 +266,7 @@ class StExtSub(name: String,
|
||||
|
||||
|
||||
|
||||
class StSubroutineParameter(val name: String, val type: DataType)
|
||||
class StSubroutineParameter(val name: String, val type: DataType, val register: RegisterOrPair?)
|
||||
class StExtSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) {
|
||||
init {
|
||||
|
||||
@@ -55,7 +55,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
|
||||
}
|
||||
is PtSub -> {
|
||||
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
|
||||
val params = node.parameters.map {StSubroutineParameter(it.name, it.type, it.register) }
|
||||
StSub(node.name, params, node.returntype, node)
|
||||
}
|
||||
is PtVariable -> {
|
||||
|
||||
@@ -23,14 +23,14 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
"&"
|
||||
}
|
||||
is PtArray -> {
|
||||
val valuelist = node.children.map {
|
||||
val valuelist = node.children.joinToString(", ") {
|
||||
when (it) {
|
||||
is PtBool -> it.toString()
|
||||
is PtNumber -> it.number.toString()
|
||||
is PtIdentifier -> it.name
|
||||
else -> "?"
|
||||
}
|
||||
}.joinToString(", ")
|
||||
}
|
||||
"array len=${node.children.size} ${type(node.type)} [ $valuelist ]"
|
||||
}
|
||||
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
|
||||
@@ -121,7 +121,10 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
}
|
||||
}
|
||||
is PtSub -> {
|
||||
val params = node.parameters.joinToString(", ") { "${it.type} ${it.name}" }
|
||||
val params = node.parameters.joinToString(", ") {
|
||||
val reg = if(it.register!=null) "@${it.register}" else ""
|
||||
"${it.type} ${it.name} $reg"
|
||||
}
|
||||
var str = "sub ${node.name}($params) "
|
||||
if(node.returntype!=null)
|
||||
str += "-> ${node.returntype.name.lowercase()}"
|
||||
@@ -156,7 +159,10 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
is PtProgram -> "PROGRAM ${node.name}"
|
||||
is PtRepeatLoop -> "repeat"
|
||||
is PtReturn -> "return"
|
||||
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
|
||||
is PtSubroutineParameter -> {
|
||||
val reg = if(node.register!=null) "@${node.register}" else ""
|
||||
"${node.type.name.lowercase()} ${node.name} $reg"
|
||||
}
|
||||
is PtWhen -> "when"
|
||||
is PtWhenChoice -> {
|
||||
if(node.isElse)
|
||||
|
||||
@@ -39,7 +39,7 @@ class PtSub(
|
||||
}
|
||||
|
||||
|
||||
class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
|
||||
class PtSubroutineParameter(name: String, val type: DataType, val register: RegisterOrPair?, position: Position): PtNamedNode(name, position)
|
||||
|
||||
|
||||
sealed interface IPtAssignment {
|
||||
|
||||
@@ -95,6 +95,17 @@ enum class RegisterOrPair {
|
||||
else -> throw IllegalArgumentException("no cpu hardware register for $this")
|
||||
}
|
||||
|
||||
fun asScopedNameVirtualReg(type: DataType?): List<String> {
|
||||
require(this in Cx16VirtualRegisters)
|
||||
val suffix = when(type) {
|
||||
DataType.UBYTE, DataType.BOOL -> "L"
|
||||
DataType.BYTE -> "sL"
|
||||
DataType.WORD -> "s"
|
||||
DataType.UWORD, null -> ""
|
||||
else -> throw kotlin.IllegalArgumentException("invalid register param type")
|
||||
}
|
||||
return listOf("cx16", name.lowercase()+suffix)
|
||||
}
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
enum class Statusflag {
|
||||
|
||||
@@ -15,10 +15,19 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
|
||||
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
|
||||
when(sub.parameters.size) {
|
||||
1 -> sub.parameters[0].type in IntegerDatatypesWithBoolean
|
||||
2 -> sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean
|
||||
@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>) =
|
||||
when(params.size) {
|
||||
1 -> params[0].type in IntegerDatatypesWithBoolean
|
||||
2 -> params[0].type in ByteDatatypesWithBoolean && params[1].type in ByteDatatypesWithBoolean
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -124,19 +133,21 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
}
|
||||
else if(sub is PtSub) {
|
||||
if(optimizeIntArgsViaRegisters(sub)) {
|
||||
when(sub.parameters.size) {
|
||||
val (paramsViaRegisters, normalParams) = sub.parameters.withIndex().partition { it.value.register!=null }
|
||||
if(normalParams.isNotEmpty()) {
|
||||
if(optimizeIntArgsViaRegisters(normalParams)) {
|
||||
when(normalParams.size) {
|
||||
1 -> {
|
||||
val register = if (sub.parameters[0].type in ByteDatatypesWithBoolean) RegisterOrPair.A else RegisterOrPair.AY
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register)
|
||||
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(sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean) {
|
||||
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, sub.parameters[0]), call.args[0], RegisterOrPair.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, sub.parameters[1]), call.args[1], RegisterOrPair.Y)
|
||||
argumentViaRegister(sub, IndexedValue(1, normalParams[1].value), call.args[1], RegisterOrPair.Y)
|
||||
if(asmgen.needAsaveForExpr(call.args[1]))
|
||||
asmgen.out(" pla")
|
||||
} else {
|
||||
@@ -147,7 +158,13 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
} else {
|
||||
// arguments via variables
|
||||
for(arg in sub.parameters.withIndex().zip(call.args))
|
||||
for(arg in normalParams.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")
|
||||
@@ -219,9 +236,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(!isArgumentTypeCompatible(value.type, parameter.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val reg = parameter.register
|
||||
if(reg!=null) {
|
||||
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val varName = "cx16.${reg.name.lowercase()}"
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||
} else {
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
|
||||
// pass argument via a register parameter
|
||||
|
||||
@@ -421,21 +421,22 @@ internal class ProgramAndVarsGen(
|
||||
if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main"))
|
||||
entrypointInitialization()
|
||||
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||
val normalParams = sub.parameters.filter { it.register==null }
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(normalParams)) {
|
||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||
when(sub.parameters.size) {
|
||||
when(normalParams.size) {
|
||||
1 -> {
|
||||
val dt = sub.parameters[0].type
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
|
||||
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)
|
||||
else
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
}
|
||||
2 -> {
|
||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
|
||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, sub.parameters[1].position, variableAsmName = sub.parameters[1].name)
|
||||
if(sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean) {
|
||||
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) {
|
||||
// 2 byte args, first in A, second in Y
|
||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||
|
||||
@@ -3464,11 +3464,9 @@ $endLabel""")
|
||||
RegisterOrPair.AX -> { }
|
||||
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
asmgen.out("""
|
||||
sta cx16.${target.register.toString().lowercase()}
|
||||
stx cx16.${target.register.toString().lowercase()}+1
|
||||
""")
|
||||
stx cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||
}
|
||||
@@ -3477,11 +3475,9 @@ $endLabel""")
|
||||
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
||||
RegisterOrPair.XY -> { asmgen.out(" tax") }
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
asmgen.out("""
|
||||
sta cx16.${target.register.toString().lowercase()}
|
||||
sty cx16.${target.register.toString().lowercase()}+1
|
||||
""")
|
||||
sty cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||
}
|
||||
@@ -3490,11 +3486,9 @@ $endLabel""")
|
||||
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
||||
RegisterOrPair.XY -> { }
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
asmgen.out("""
|
||||
stx cx16.${target.register.toString().lowercase()}
|
||||
sty cx16.${target.register.toString().lowercase()}+1
|
||||
""")
|
||||
sty cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||
}
|
||||
@@ -3570,15 +3564,13 @@ $endLabel""")
|
||||
asmgen.out("""
|
||||
lda #${(word and 255).toHex()}
|
||||
sta ${target.asmVarname}
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
sta ${target.asmVarname}+1""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda #<${word.toHex()}
|
||||
ldy #>${word.toHex()}
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1
|
||||
""")
|
||||
sty ${target.asmVarname}+1""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
@@ -3605,13 +3597,11 @@ $endLabel""")
|
||||
RegisterOrPair.AY -> asmgen.out(" ldy #>${word.toHex()} | lda #<${word.toHex()}")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy #>${word.toHex()} | ldx #<${word.toHex()}")
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
asmgen.out("""
|
||||
lda #<${word.toHex()}
|
||||
sta cx16.${target.register.toString().lowercase()}
|
||||
lda #>${word.toHex()}
|
||||
sta cx16.${target.register.toString().lowercase()}+1
|
||||
""")
|
||||
sta cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid register for word value")
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtRange,
|
||||
is PtArray,
|
||||
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
|
||||
else -> throw AssemblyError("weird expression")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,12 +564,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
val paramDt = irType(parameter.type)
|
||||
if(parameter.register==null) {
|
||||
val tr = translateExpression(arg)
|
||||
result += tr.chunks
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, null)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
|
||||
result += tr.chunks
|
||||
} else {
|
||||
require(parameter.register in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
|
||||
val assign = PtAssignment(fcall.position)
|
||||
val target = PtAssignTarget(true, fcall.position)
|
||||
target.add(PtIdentifier(regname, parameter.type, fcall.position))
|
||||
assign.add(target)
|
||||
assign.add(arg)
|
||||
result += codeGen.translateNode(assign)
|
||||
}
|
||||
}
|
||||
// return value (always singular for normal Subs)
|
||||
val returnRegSpec = if(fcall.void) null else {
|
||||
|
||||
@@ -147,7 +147,6 @@ class IRCodeGen(
|
||||
}
|
||||
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
|
||||
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next)
|
||||
else -> throw AssemblyError("invalid chunk")
|
||||
}
|
||||
sub.chunks.removeAt(0)
|
||||
sub.chunks.add(0, replacement)
|
||||
@@ -363,9 +362,6 @@ class IRCodeGen(
|
||||
is IRInlineBinaryChunk -> {
|
||||
IRInlineBinaryChunk(label, first.data, first.next)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("invalid chunk")
|
||||
}
|
||||
}
|
||||
return listOf(labeledFirstChunk) + chunks.drop(1)
|
||||
}
|
||||
@@ -1750,13 +1746,24 @@ class IRCodeGen(
|
||||
return irBlock
|
||||
}
|
||||
|
||||
private fun translate(parameters: List<PtSubroutineParameter>) =
|
||||
parameters.map {
|
||||
private fun translate(parameters: List<PtSubroutineParameter>): List<IRSubroutine.IRParam> {
|
||||
val result = mutableListOf<IRSubroutine.IRParam>()
|
||||
parameters.forEach {
|
||||
if(it.register==null) {
|
||||
val flattenedName = it.definingISub()!!.name + "." + it.name
|
||||
if (symbolTable.lookup(flattenedName) == null)
|
||||
TODO("fix missing lookup for: $flattenedName parameter")
|
||||
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
|
||||
IRSubroutine.IRParam(flattenedName, orig.dt)
|
||||
result += IRSubroutine.IRParam(flattenedName, orig.dt)
|
||||
} else {
|
||||
val reg = it.register
|
||||
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = it.register!!.asScopedNameVirtualReg(it.type).joinToString(".")
|
||||
val targetVar = symbolTable.lookup(regname) as StMemVar
|
||||
result += IRSubroutine.IRParam(regname, targetVar.dt)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private var labelSequenceNumber = 0
|
||||
|
||||
@@ -22,7 +22,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in functions.purefunctionNames) {
|
||||
if("ignore_unused" !in parent.definingBlock.options())
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
errors.info("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,11 +501,28 @@ internal class AstChecker(private val program: Program,
|
||||
val statusFlagsNoCarry = subroutine.asmParameterRegisters.mapNotNull { it.statusflag }.toSet() - Statusflag.Pc
|
||||
if(statusFlagsNoCarry.isNotEmpty())
|
||||
err("can only use Carry as status flag parameter")
|
||||
} else {
|
||||
// normal subroutines only can have R0-R15 as param registers
|
||||
val paramsWithRegs = subroutine.parameters.filter { it.registerOrPair!=null }
|
||||
val regsUsed = paramsWithRegs.map { it.registerOrPair!! }
|
||||
if(regsUsed.size != regsUsed.toSet().size) {
|
||||
err("a register is used multiple times in the parameters")
|
||||
}
|
||||
}
|
||||
|
||||
// Non-string and non-ubytearray Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
for(p in subroutine.parameters) {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(p.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", p.position)
|
||||
|
||||
@@ -913,7 +930,7 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("string variables cannot be @dirty", decl.position)
|
||||
else {
|
||||
if(decl.value==null)
|
||||
errors.info("dirty variable: initial value will be undefined", decl.position)
|
||||
errors.warn("\uD83D\uDCA3 footgun: dirty variable, initial value will be undefined", decl.position)
|
||||
else
|
||||
errors.err("dirty variable can't have initialization value", decl.position)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class AstPreprocessor(val program: Program,
|
||||
val constval = range.from.constValue(program)
|
||||
if (constval != null)
|
||||
modifications += IAstModification.ReplaceNode(range.from, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
} catch (_: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
@@ -214,18 +214,34 @@ class AstPreprocessor(val program: Program,
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// For non-kernal subroutines and non-asm parameters:
|
||||
// inject subroutine params as local variables (if they're not there yet).
|
||||
// If the param should be in a R0-R15 register, don't make a local variable but an alias instead.
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
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 }
|
||||
if(normalParams.isNotEmpty()) {
|
||||
val existingVars = subroutine.statements.asSequence().filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
return subroutine.parameters
|
||||
normalParams
|
||||
.filter { it.name !in namesInSub && it.name !in existingVars }
|
||||
.map {
|
||||
.forEach {
|
||||
val vardecl = VarDecl.fromParameter(it)
|
||||
IAstModification.InsertFirst(vardecl, subroutine)
|
||||
mods += IAstModification.InsertFirst(vardecl, subroutine)
|
||||
}
|
||||
}
|
||||
if(registerParams.isNotEmpty()) {
|
||||
val existingAliases = subroutine.statements.asSequence().filterIsInstance<Alias>().map { it.alias }.toSet()
|
||||
registerParams
|
||||
.filter { it.name !in namesInSub && it.name !in existingAliases }
|
||||
.forEach {
|
||||
val regname = it.registerOrPair!!.asScopedNameVirtualReg(it.type)
|
||||
var alias = Alias(it.name, IdentifierReference(regname, it.position), it.position)
|
||||
mods += IAstModification.InsertFirst(alias, subroutine)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
|
||||
@@ -487,7 +487,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
|
||||
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) })
|
||||
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.registerOrPair, it.position) })
|
||||
val varbank = if(srcSub.asmAddress?.varbank==null) null else transform(srcSub.asmAddress!!.varbank!!)
|
||||
val asmAddr = if(srcSub.asmAddress==null) null else {
|
||||
val constAddr = srcSub.asmAddress!!.address.constValue(program)
|
||||
@@ -539,7 +539,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
|
||||
// do not bother about the 'inline' hint of the source subroutine.
|
||||
val sub = PtSub(srcSub.name,
|
||||
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) },
|
||||
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.registerOrPair, it.position) },
|
||||
returntype,
|
||||
srcSub.position)
|
||||
sub.parameters.forEach { it.parent=sub }
|
||||
|
||||
@@ -193,7 +193,7 @@ internal class StatementReorderer(
|
||||
// change 'str' and 'ubyte[]' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR || it.type==DataType.ARRAY_UB }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.zp, it.position)
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.zp, it.registerOrPair, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
// change 'str' and 'ubyte[]' return types into 'uword' (just treat it as an address)
|
||||
|
||||
@@ -453,9 +453,9 @@ main {
|
||||
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
val result = compileText(C64Target(), optimize=false, src, writeAssembly=false, errors=errors)!!
|
||||
errors.errors.size shouldBe 0
|
||||
errors.infos.size shouldBe 6
|
||||
errors.infos.all { "dirty variable" in it } shouldBe true
|
||||
errors.warnings.size shouldBe 6
|
||||
errors.infos.size shouldBe 0
|
||||
errors.warnings.all { "dirty variable" in it } shouldBe true
|
||||
val start = result.compilerAst.entrypoint
|
||||
val st = start.statements
|
||||
st.size shouldBe 9
|
||||
|
||||
@@ -209,7 +209,8 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
else {
|
||||
output("sub ${subroutine.name} (")
|
||||
for(param in subroutine.parameters) {
|
||||
output("${datatypeString(param.type)} ${param.name}")
|
||||
val reg = if(param.registerOrPair!=null) " @${param.registerOrPair}" else ""
|
||||
output("${datatypeString(param.type)} ${param.name}$reg")
|
||||
if(param!==subroutine.parameters.last())
|
||||
output(", ")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.ast.antlr
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.Token
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.SyntaxError
|
||||
@@ -199,7 +200,7 @@ private fun Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
||||
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.position) }
|
||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.registerOrPair, it.position) }
|
||||
val normalReturntypes = returns.map { it.type }
|
||||
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
@@ -208,9 +209,9 @@ private fun Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
|
||||
private class AsmSubroutineParameter(name: String,
|
||||
type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?,
|
||||
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, position)
|
||||
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, registerOrPair, position)
|
||||
|
||||
private class AsmSubroutineReturn(val type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
@@ -240,7 +241,18 @@ private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
var datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
|
||||
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
|
||||
datatype = ElementToArrayTypes.getValue(datatype)
|
||||
val register = it.register.text
|
||||
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
|
||||
val identifiers = vardecl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME()
|
||||
AsmSubroutineParameter(identifiername.text, datatype, registerorpair, statusregister, toPosition())
|
||||
}
|
||||
|
||||
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
|
||||
if(registerTok==null)
|
||||
return Pair(null, null)
|
||||
val register = registerTok.text
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
@@ -248,16 +260,11 @@ private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> {
|
||||
val p = toPosition()
|
||||
throw SyntaxError("invalid register or status flag", Position(p.file, it.register.line, it.register.charPositionInLine, it.register.charPositionInLine+1))
|
||||
throw SyntaxError("invalid register or status flag", Position(pos.file, registerTok.line, registerTok.charPositionInLine, registerTok.charPositionInLine+1))
|
||||
}
|
||||
}
|
||||
}
|
||||
val identifiers = vardecl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME()
|
||||
AsmSubroutineParameter(identifiername.text, datatype, registerorpair, statusregister, toPosition())
|
||||
return Pair(registerorpair, statusregister)
|
||||
}
|
||||
|
||||
private fun Functioncall_stmtContext.toAst(): Statement {
|
||||
@@ -322,22 +329,28 @@ private fun SubroutineContext.toAst() : Subroutine {
|
||||
}
|
||||
|
||||
private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
vardecl().map {
|
||||
val options = it.decloptions()
|
||||
sub_param().map {
|
||||
val decl = it.vardecl()
|
||||
val options = decl.decloptions()
|
||||
if(options.ALIGNPAGE().isNotEmpty() || options.ALIGNWORD().isNotEmpty())
|
||||
throw SyntaxError("cannot use alignments on parameters", it.toPosition())
|
||||
if(options.DIRTY().isNotEmpty())
|
||||
throw SyntaxError("cannot use @dirty on parameters", it.toPosition())
|
||||
val zp = getZpOption(options)
|
||||
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
|
||||
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
|
||||
var datatype = decl.datatype()?.toAst() ?: DataType.UNDEFINED
|
||||
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
|
||||
datatype = ElementToArrayTypes.getValue(datatype)
|
||||
|
||||
val identifiers = it.identifier()
|
||||
val identifiers = decl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME()
|
||||
SubroutineParameter(identifiername.text, datatype, zp, it.toPosition())
|
||||
|
||||
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
|
||||
if(statusregister!=null) {
|
||||
throw SyntaxError("can't use status register as param for normal subroutines", Position(toPosition().file, it.register.line, it.register.charPositionInLine, it.register.charPositionInLine+1))
|
||||
}
|
||||
SubroutineParameter(identifiername.text, datatype, zp, registerorpair, it.toPosition())
|
||||
}
|
||||
|
||||
private fun getZpOption(options: DecloptionsContext?): ZeropageWish {
|
||||
@@ -382,7 +395,7 @@ private fun ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.NAME().map { it.text }
|
||||
try {
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
} catch(ax: IllegalArgumentException) {
|
||||
} catch(_: IllegalArgumentException) {
|
||||
throw SyntaxError("invalid cpu register", toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,6 +878,7 @@ class Subroutine(override val name: String,
|
||||
open class SubroutineParameter(val name: String,
|
||||
val type: DataType,
|
||||
val zp: ZeropageWish,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
final override val position: Position) : Node {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@@ -889,17 +890,19 @@ open class SubroutineParameter(val name: String,
|
||||
throw FatalAstException("can't replace anything in a subroutineparameter node")
|
||||
}
|
||||
|
||||
override fun copy() = SubroutineParameter(name, type, zp, position)
|
||||
override fun copy() = SubroutineParameter(name, type, zp, registerOrPair, position)
|
||||
override fun toString() = "Param($type:$name)"
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = nameInSource.size==1 && name==nameInSource[0]
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is SubroutineParameter) return false
|
||||
return name == other.name && type == other.type && zp == other.zp
|
||||
return name == other.name && type == other.type && zp == other.zp && registerOrPair == other.registerOrPair
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(name, type, zp)
|
||||
|
||||
|
||||
}
|
||||
|
||||
class IfElse(var condition: Expression,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
document the @R0 - @R15 register support for normal subroutine parameters (footgun!)
|
||||
add unit tests for it too.
|
||||
|
||||
make a compiler switch to disable footgun warnings
|
||||
|
||||
turn some existing warnings into INFO
|
||||
|
||||
what to do with bankof(): keep it? add another syntax like \`value or ^value to get the bank byte?
|
||||
add a function like addr() or lsw() to complement bnk() in getting easy access to the lower 16 bits of a long integer?
|
||||
-> added unary ^ operator as alternative to bankof()
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
%import floats
|
||||
%import math
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
floats.print(floats.interpolate(0, 0, 10, 1000, 2000))
|
||||
txt.spc()
|
||||
txt.print_uw(math.interpolate(10, 10, 20, 100, 200))
|
||||
foo(42)
|
||||
foo2(42)
|
||||
bar(9999,55)
|
||||
bar2(9999,55)
|
||||
txt.nl()
|
||||
floats.print(floats.interpolate(2.22, 0, 10, 1000, 2000))
|
||||
txt.spc()
|
||||
txt.print_uw(math.interpolate(12, 10, 20, 100, 200))
|
||||
}
|
||||
|
||||
sub foo(ubyte arg) {
|
||||
txt.print_ub(arg)
|
||||
txt.nl()
|
||||
floats.print(floats.interpolate(5.0, 0, 10, 1000, 2000))
|
||||
txt.spc()
|
||||
txt.print_uw(math.interpolate(15, 10, 20, 100, 200))
|
||||
}
|
||||
|
||||
sub foo2(ubyte arg @R2) {
|
||||
txt.print_ub(arg)
|
||||
txt.nl()
|
||||
floats.print(floats.interpolate(10, 0, 10, 1000, 2000))
|
||||
}
|
||||
|
||||
sub bar(uword arg, ubyte arg2) {
|
||||
txt.print_uw(arg)
|
||||
txt.spc()
|
||||
txt.print_uw(math.interpolate(20, 10, 20, 100, 200))
|
||||
txt.print_ub(arg2)
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bar2(uword arg @R0, ubyte arg2 @R1) {
|
||||
txt.print_uw(arg)
|
||||
txt.spc()
|
||||
txt.print_ub(arg2)
|
||||
txt.nl()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,9 @@ statement_block :
|
||||
;
|
||||
|
||||
|
||||
sub_params : vardecl (',' EOL? vardecl)* ;
|
||||
sub_params : sub_param (',' EOL? sub_param)* ;
|
||||
|
||||
sub_param: vardecl ('@' register=NAME)? ;
|
||||
|
||||
asmsubroutine :
|
||||
inline? 'asmsub' asmsub_decl EOL? (statement_block EOL?)
|
||||
@@ -303,7 +305,7 @@ asmsub_decl : identifier '(' asmsub_params? ')' asmsub_clobbers? asmsub_returns?
|
||||
|
||||
asmsub_params : asmsub_param (',' EOL? asmsub_param)* ;
|
||||
|
||||
asmsub_param : vardecl '@' register=NAME ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed.
|
||||
asmsub_param : vardecl '@' register=NAME ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed
|
||||
|
||||
asmsub_clobbers : 'clobbers' '(' clobber? ')' ;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user