change for subroutine return values via registers instead of stack

This commit is contained in:
Irmen de Jong 2020-10-28 00:29:34 +01:00
parent 83cc19ad6f
commit 44949460ed
14 changed files with 165 additions and 112 deletions

View File

@ -31,7 +31,7 @@ enum class DataType {
WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType == UWORD
in ArrayDatatypes -> targetType == this
in ArrayDatatypes -> targetType == this || targetType == UWORD
else -> false
}

View File

@ -35,6 +35,10 @@ object InferredTypes {
}
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
infix fun isAssignableTo(targetDt: InferredType): Boolean {
return isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
}
}
private val unknownInstance = InferredType.unknown()

View File

@ -380,7 +380,7 @@ internal class AstChecker(private val program: Program,
val targetDt = assignment.target.inferType(program, assignment)
val valueDt = assignment.value.inferType(program)
if(valueDt.isKnown && valueDt != targetDt) {
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
errors.err("cannot assign value to string or array", assignment.value.position)
else
@ -1350,9 +1350,7 @@ internal class AstChecker(private val program: Program,
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else {
if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes)
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)
else
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
}

View File

@ -66,6 +66,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),

View File

@ -697,10 +697,9 @@ class Subroutine(override val name: String,
private fun determineReturnRegisters(returntypes: List<DataType>): List<RegisterOrStatusflag> {
// for non-asm subroutines, determine the return registers based on the type of the return value
return when(returntypes.singleOrNull()) {
in NumericDatatypes -> listOf(RegisterOrStatusflag(null, null, true)) // TODO for now, all return values via the stack
// in ByteDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, false))
// in WordDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, false))
// DataType.FLOAT -> listOf(RegisterOrStatusflag(null, null, true)) // TODO floats eventually via pointer in AY as well
in ByteDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, false))
in WordDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, false))
DataType.FLOAT -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, false))
null -> emptyList()
else -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, false))
}

View File

@ -58,7 +58,7 @@ fun compileProgram(filepath: Path,
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
printAst(programAst) // TODO
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
@ -223,7 +223,7 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
printAst(programAst) // TODO
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(

View File

@ -743,8 +743,8 @@ internal class AsmGen(private val program: Program,
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack)
internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
@ -1164,15 +1164,20 @@ $counterVar .byte 0""")
else -> throw AssemblyError("normal subroutines can't return value in status register directly")
}
if (returnType in NumericDatatypes) {
val src = AsmAssignSource.fromAstSource(returnvalue, program, this)
assignmentAsmGen.translateNormalAssignment(AsmAssignment(src, returnValueTarget, false, ret.position))
}
else {
// all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
val src = AsmAssignSource.fromAstSource(addrofValue, program, this)
assignmentAsmGen.translateNormalAssignment(AsmAssignment(src, returnValueTarget, false, ret.position))
when (returnType) {
in IntegerDatatypes -> {
val src = AsmAssignSource.fromAstSource(returnvalue, program, this)
assignmentAsmGen.translateNormalAssignment(AsmAssignment(src, returnValueTarget, false, ret.position))
}
DataType.FLOAT -> {
TODO("must return the float's address in AY")
}
else -> {
// all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
val src = AsmAssignSource.fromAstSource(addrofValue, program, this)
assignmentAsmGen.translateNormalAssignment(AsmAssignment(src, returnValueTarget, false, ret.position))
}
}
}
out(" rts")

View File

@ -17,37 +17,36 @@ import prog8.functions.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
translateFunctioncall(fcall, func, false)
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean) {
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
translateFunctioncall(fcall, func, true)
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean) {
val functionName = fcall.target.nameInSource.last()
if (discardResult) {
if (func.pure)
return // can just ignore the whole function call altogether
else if (func.returntype != null)
throw AssemblyError("discarding result of non-pure function $fcall")
}
if (discardResult && func.pure)
return // can just ignore the whole function call altogether
if(discardResult && resultToStack)
throw AssemblyError("cannot both discard the result AND put it onto stack")
when (functionName) {
"msb" -> funcMsb(fcall)
"lsb" -> funcLsb(fcall)
"mkword" -> funcMkword(fcall, func)
"abs" -> funcAbs(fcall, func)
"msb" -> funcMsb(fcall, resultToStack)
"lsb" -> funcLsb(fcall, resultToStack)
"mkword" -> funcMkword(fcall, func) // TODO resultToStack
"abs" -> funcAbs(fcall, func) // TODO resultToStack
"swap" -> funcSwap(fcall)
"strlen" -> funcStrlen(fcall)
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName)
"any", "all" -> funcAnyAll(fcall, functionName)
"sgn" -> funcSgn(fcall, func)
"strlen" -> funcStrlen(fcall) // TODO resultToStack
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName) // TODO resultToStack
"any", "all" -> funcAnyAll(fcall, functionName) // TODO resultToStack
"sgn" -> funcSgn(fcall, func) // TODO resultToStack
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName) // TODO resultToStack
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
@ -69,7 +68,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"set_irqd" -> asmgen.out(" sei")
else -> {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr prog8_lib.func_$functionName")
asmgen.out(" jsr prog8_lib.func_$functionName") // TODO resultToStack
}
}
}
@ -777,7 +776,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" inx | lda P8ESTACK_LO,x | sta P8ESTACK_HI+1,x")
}
private fun funcMsb(fcall: IFunctionCall) {
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
@ -785,14 +784,17 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
asmgen.out(" lda $sourceName+1")
if(resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
TODO("msb from non-identifier expression $arg")
// asmgen.translateExpression(arg)
// asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
}
}
private fun funcLsb(fcall: IFunctionCall) {
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("lsb required word argument")
@ -800,9 +802,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
asmgen.out(" lda $sourceName")
if(resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else {
asmgen.translateExpression(arg)
TODO("lsb from non-identifier expression $arg")
// asmgen.translateExpression(arg)
// just ignore any high-byte
}
}

View File

@ -4,6 +4,8 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
@ -1041,12 +1043,14 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateFunctionCallResultOntoStack(expression: FunctionCall) {
val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc)
// only for use in nested expression evaluation
val sub = expression.target.targetStatement(program.namespace)
if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(expression, builtinFunc, true)
} else {
val sub = expression.target.targetSubroutine(program.namespace)!!
sub as Subroutine
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
asmgen.translateFunctionCall(expression, preserveStatusRegisterAfterCall)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)

View File

@ -3,10 +3,7 @@ package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen
@ -121,33 +118,26 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
else -> {
if(value is FunctionCall) {
// functioncall.
val asmSub = value.target.targetStatement(program.namespace)
if(asmSub is Subroutine && asmSub.isAsmSubroutine) {
when (asmSub.asmReturnvaluesRegisters.count { rr -> rr.registerOrPair!=null }) {
0 -> throw AssemblyError("can't translate zero return values in assignment")
1 -> {
// assignment generation itself must make sure the status register is correct after the subroutine call, if status register is involved!
val reg = asmSub.asmReturnvaluesRegisters.single { rr->rr.registerOrPair!=null }.registerOrPair!!
val dt = when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> DataType.UBYTE
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> DataType.UWORD
}
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
}
else -> throw AssemblyError("can't translate multiple return values in assignment")
}
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
is BuiltinFunctionStatementPlaceholder -> {
val returnType = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
else -> {
throw AssemblyError("weird call")
}
}
}
else -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
}
}
}

View File

@ -10,6 +10,7 @@ import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen, private val exprAsmgen: ExpressionsAsmGen) {
@ -124,28 +125,36 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, assign)
is FunctionCall -> {
if(value.target.targetSubroutine(program.namespace)?.isAsmSubroutine==true) {
// handle asmsub functioncalls specifically, without shoving stuff on the estack
val sub = value.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any { it.statusflag != null }
asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
when((sub.asmReturnvaluesRegisters.single { it.registerOrPair!=null }).registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> throw AssemblyError("should be just one register byte result value")
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any { it.statusflag != null }
asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
when ((sub.asmReturnvaluesRegisters.single { it.registerOrPair != null }).registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> throw AssemblyError("should be just one register byte result value")
}
if (preserveStatusRegisterAfterCall)
asmgen.out(" plp\t; restore status flags from call")
}
is BuiltinFunctionStatementPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false)
when(signature.returntype) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A)
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
DataType.FLOAT -> TODO("assign float result from ${sub.name}")
null -> {}
else -> throw AssemblyError("weird result type ${signature.returntype}")
}
}
else -> {
throw AssemblyError("weird func call")
}
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp\t; restore status flags from call")
} else {
// regular subroutine, return values are (for now) always done via the stack... TODO optimize this
asmgen.translateExpression(value)
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
}
}
else -> {
@ -763,7 +772,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in WordDatatypes)
require(target.datatype in NumericDatatypes)
if(target.datatype==DataType.FLOAT) {
if (regs == RegisterOrPair.AY) {
asmgen.out(" brk ; TODO FLOAT RETURN VALUE") // TODO
return
}
else throw AssemblyError("float reaturn value should be via AY return pointer")
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
when(regs) {

View File

@ -2,6 +2,7 @@
TODO
====
- get rid of all the .typeOrElse(STRUCT) 'shortcuts' and replace them with proper error handling
- make memset(w) and memcopy able to work with >256 bytes
- make memset and memcopy use the ROM routines on the CX16
- calling convention for builtin functions no longer via stack but via statically allocated vars inside the subroutine proc (just as normal subroutines)

View File

@ -4,6 +4,8 @@
; Note: this program is compatible with C64 and CX16.
; TODO the LINES are all wrong...
main {
sub start() {

View File

@ -6,13 +6,38 @@
main {
sub start() {
ubyte char = c64.CHRIN()
ubyte char2 = chrin()
uword ssss = getstr()
float fl = getfloat()
ubyte char
uword ssss
float fl
;char = 1+(lsb(ssss) * 2)
;fl = 2.0*(abs(fl) + 1.0)
char = lsb(ssss)
char++
char2++
char = msb(ssss)
char++
char = c64.CHRIN()
txt.print_ub(char)
txt.chrout('\n')
char = chrin()
txt.print_ub(char)
txt.chrout('\n')
void getstr()
ssss = getstr()
txt.print_uwhex(ssss, true)
txt.chrout('\n')
; fl = getfloat()
;
; floats.print_f(fl)
; txt.chrout('\n')
testX()
;char=strlen(ssss)
}
@ -22,12 +47,13 @@ main {
}
sub getstr() -> str {
@($d020)++
return "foo"
}
sub getfloat() -> float {
return 4.56789
}
; sub getfloat() -> float {
; return 4.56789
; }
sub mcp(uword from, uword dest, ubyte length) {
txt.print_uw(from)