improved code gen for passing string and array types.

This commit is contained in:
Irmen de Jong 2020-06-02 01:23:41 +02:00
parent d665489054
commit 58a83c0439
9 changed files with 37 additions and 87 deletions

View File

@ -284,7 +284,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
assignment.target.accept(this) assignment.target.accept(this)
if (assignment.aug_op != null) if (assignment.aug_op != null && assignment.aug_op != "setvalue")
output(" ${assignment.aug_op} ") output(" ${assignment.aug_op} ")
else else
output(" = ") output(" = ")

View File

@ -24,7 +24,7 @@ enum class DataType {
* is the type assignable to the given other type? * is the type assignable to the given other type?
*/ */
infix fun isAssignableTo(targetType: DataType) = infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision? // what types are assignable to others, perhaps via a typecast, without loss of precision?
when(this) { when(this) {
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT) UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT) BYTE -> targetType in setOf(BYTE, WORD, FLOAT)

View File

@ -897,6 +897,8 @@ internal class AstChecker(private val program: Program,
} }
} }
} else if(target is Subroutine) { } else if(target is Subroutine) {
if(target.regXasResult())
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
if(args.size!=target.parameters.size) if(args.size!=target.parameters.size)
errors.err("invalid number of arguments", position) errors.err("invalid number of arguments", position)
else { else {
@ -915,7 +917,7 @@ internal class AstChecker(private val program: Program,
if(target.isAsmSubroutine) { if(target.isAsmSubroutine) {
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) { if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference) if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position) errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
} }
// check if the argument types match the register(pairs) // check if the argument types match the register(pairs)

View File

@ -4,9 +4,6 @@ import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
internal class StatementReorderer(val program: Program) : AstWalker() { internal class StatementReorderer(val program: Program) : AstWalker() {
@ -125,75 +122,6 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return emptyList() return emptyList()
} }
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
}
return emptyList()
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
}
return emptyList()
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
val replacements = mutableListOf<IAstModification>()
for(argparam in subroutine.parameters.withIndex().zip(args)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && variable.datatype in IterableDatatypes) {
replacements += IAstModification.ReplaceNode(
args[argparam.first.index],
AddressOf(idref, idref.position),
parent as Node)
}
}
}
}
return replacements
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
val replacements = mutableListOf<IAstModification>()
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
replacements += IAstModification.ReplaceNode(
args[arg.first.index],
AddressOf(argvalue, argvalue.position),
parent as Node)
}
}
return replacements
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> { private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()

View File

@ -4,9 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.*
import prog8.ast.base.ErrorReporter
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -45,10 +43,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
val targettype = targetItype.typeOrElse(DataType.STRUCT) val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT) val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) { if (valuetype != targettype) {
return listOf(IAstModification.ReplaceNode( if(valuetype.isAssignableTo(targettype)) {
assignment.value, return listOf(IAstModification.ReplaceNode(
TypecastExpression(assignment.value, targettype, true, assignment.value.position), assignment.value,
assignment)) TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
}
} }
} }
return emptyList() return emptyList()
@ -77,6 +77,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
call.args[arg.second.index], call.args[arg.second.index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position), TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
call as Node)) call as Node))
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
return listOf(IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node))
} }
} }
} }

View File

@ -716,6 +716,8 @@ class Subroutine(override val name: String,
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
} }
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun amountOfRtsInAsm(): Int = statements fun amountOfRtsInAsm(): Int = statements
.asSequence() .asSequence()
.filter { it is InlineAssembly } .filter { it is InlineAssembly }

View File

@ -42,7 +42,7 @@ fun compileProgram(filepath: Path,
optimizeAst(programAst, errors) optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions) postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst) // TODO printAst(programAst) // TODO
if(writeAssembly) if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions) programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)

View File

@ -40,8 +40,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if (builtinFunc != null) { if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc) asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else { } else {
asmgen.translateFunctionCall(expression)
val sub = expression.target.targetSubroutine(program.namespace)!! val sub = expression.target.targetSubroutine(program.namespace)!!
asmgen.translateFunctionCall(expression)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) { for ((_, reg) in returns) {
if (!reg.stack) { if (!reg.stack) {
@ -51,7 +51,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex") RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex") RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex") RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable") RegisterOrPair.X -> {
// return value in X register has been discarded, just push a zero
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
}
} }
} }
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc // return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc

View File

@ -19,7 +19,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// output the code to setup the parameters and perform the actual call // output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values! // does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(Register.X in sub.asmClobbers) val saveX = Register.X in sub.asmClobbers || sub.regXasResult()
if(saveX)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y... asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
val subName = asmgen.asmIdentifierName(stmt.target) val subName = asmgen.asmIdentifierName(stmt.target)
@ -30,7 +31,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
asmgen.out(" jsr $subName") asmgen.out(" jsr $subName")
if(Register.X in sub.asmClobbers) if(saveX)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
} }