register params support for normal subroutines

This commit is contained in:
Irmen de Jong
2024-11-24 04:57:27 +01:00
parent 857d2eefca
commit 5c6bd9c091
22 changed files with 253 additions and 134 deletions

View File

@@ -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 {

View File

@@ -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 -> {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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 }

View File

@@ -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)

View File

@@ -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

View File

@@ -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(", ")
}

View File

@@ -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())
}
}

View File

@@ -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,

View File

@@ -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()

View File

@@ -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()
}
}

View File

@@ -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? ')' ;