mirror of
https://github.com/irmen/prog8.git
synced 2024-11-23 22:33:12 +00:00
improved code gen for passing string and array types.
This commit is contained in:
parent
d665489054
commit
58a83c0439
@ -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(" = ")
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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,12 +43,14 @@ 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) {
|
||||||
|
if(valuetype.isAssignableTo(targettype)) {
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
assignment.value,
|
assignment.value,
|
||||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||||
assignment))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user