diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt index 7f6533998..4502ee4d7 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt @@ -957,8 +957,11 @@ class AsmGen(private val program: Program, internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpression, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) = builtinFunctionsAsmGen.translateFunctioncallExpression(functionCallExpr, signature, resultToStack, resultRegister) - internal fun translateBuiltinFunctionCallStatement(functionCallStmt: FunctionCallStatement, signature: FSignature) = - builtinFunctionsAsmGen.translateFunctioncallStatement(functionCallStmt, signature) + internal fun translateBuiltinFunctionCallExpression(name: String, args: List, scope: Subroutine): DataType = + builtinFunctionsAsmGen.translateFunctioncall(name, args, false, scope) + + internal fun translateBuiltinFunctionCallStatement(name: String, args: List, scope: Subroutine) = + builtinFunctionsAsmGen.translateFunctioncall(name, args, true, scope) internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) = functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression) @@ -1626,13 +1629,14 @@ $label nop""") assemblyLines.add(assembly) } - internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair? { + internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair { return when (val targetRoutine = it.targetStatement(program)!!) { is BuiltinFunctionPlaceholder -> { when (BuiltinFunctions.getValue(targetRoutine.name).known_returntype) { in ByteDatatypes -> RegisterOrPair.A in WordDatatypes -> RegisterOrPair.AY - else -> return null + DataType.FLOAT -> RegisterOrPair.FAC1 + else -> throw AssemblyError("weird returntype") } } is Subroutine -> targetRoutine.asmReturnvaluesRegisters.single().registerOrPair!! @@ -3479,33 +3483,36 @@ $label nop""") // TODO more efficient code generation to avoid needless assignments to the temp var - val subroutine = scope.definingSubroutine - var valueDt = expressions.first().inferType(program).getOrElse { throw FatalAstException("invalid dt") } - var valueVar = getTempVarName(valueDt) - assignExpressionToVariable(expressions.first(), asmVariableName(valueVar), valueDt, subroutine) + // the first term: an expression (could be anything) producing a value. + val subroutine = scope.definingSubroutine!! + val firstTerm = expressions.first() + var valueDt = firstTerm.inferType(program).getOrElse { throw FatalAstException("invalid dt") } + var valueSource: AsmAssignSource = + if(firstTerm is IFunctionCall) { + val resultReg = returnRegisterOfFunction(firstTerm.target) + assignExpressionToRegister(firstTerm, resultReg, valueDt in listOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)) + AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg) + } else { + AsmAssignSource.fromAstSource(firstTerm, program, this) + } + + // the 2nd to N-1 terms: unary function calls taking a single param and producing a value. + // directly assign their argument from the previous call's returnvalue. expressions.drop(1).dropLast(1).forEach { - valueDt = functioncallAsmGen.translateFunctionCall(it as IdentifierReference, listOf(IdentifierReference(valueVar, it.position)), scope) - // assign result value from the functioncall back to the temp var: - valueVar = getTempVarName(valueDt) - val valueVarTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, valueDt, subroutine, variableAsmName = valueVar.joinToString(".")) - val returnRegister = returnRegisterOfFunction(it)!! - assignRegister(returnRegister, valueVarTarget) + valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(it as IdentifierReference, valueSource, false, subroutine) + val resultReg = returnRegisterOfFunction(it) + valueSource = AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg) } + // the last term: unary function call taking a single param and optionally producing a result value. if(isStatement) { // the last term in the pipe, don't care about return var: - functioncallAsmGen.translateFunctionCallStatement( - expressions.last() as IdentifierReference, - listOf(IdentifierReference(valueVar, expressions.last().position)), - scope - ) + functioncallAsmGen.translateUnaryFunctionCallWithArgSource( + expressions.last() as IdentifierReference, valueSource, true, subroutine) } else { // the last term in the pipe, regular function call with returnvalue: - valueDt = functioncallAsmGen.translateFunctionCall( - expressions.last() as IdentifierReference, - listOf(IdentifierReference(valueVar, expressions.last().position)), - scope - ) + valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource( + expressions.last() as IdentifierReference, valueSource, false, subroutine) if(pushResultOnEstack) { when (valueDt) { in ByteDatatypes -> { diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/BuiltinFunctionsAsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/BuiltinFunctionsAsmGen.kt index 70d4141da..f70e6b157 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/BuiltinFunctionsAsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/BuiltinFunctionsAsmGen.kt @@ -13,6 +13,7 @@ import prog8.ast.toHex import prog8.codegen.target.AssemblyError import prog8.codegen.target.Cx16Target import prog8.codegen.target.cpu6502.codegen.assignment.* +import prog8.compilerinterface.BuiltinFunctions import prog8.compilerinterface.CpuType import prog8.compilerinterface.FSignature import prog8.compilerinterface.subroutineFloatEvalResultVar2 @@ -27,6 +28,36 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null) } + internal fun translateFunctioncall(name: String, args: List, isStatement: Boolean, scope: Subroutine): DataType { + val func = BuiltinFunctions.getValue(name) + val argExpressions = args.map { src -> + when(src.kind) { + SourceStorageKind.LITERALNUMBER -> src.number!! + SourceStorageKind.EXPRESSION -> src.expression!! + SourceStorageKind.ARRAY -> src.array!! + else -> { + // TODO make it so that we can assign efficiently from something else as an expression....namely: register(s) + // but for now, first assign it to a temporary variable + val tempvar = asmgen.getTempVarName(src.datatype) + val assignTempvar = AsmAssignment( + src, + AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, src.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)), + false, program.memsizer, Position.DUMMY + ) + assignAsmGen.translateNormalAssignment(assignTempvar) + // now use an expression to assign this tempvar + val ident = IdentifierReference(tempvar, Position.DUMMY) + ident.linkParents(scope) + ident + } + } + }.toMutableList() + val fcall = FunctionCallExpression(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY) + fcall.linkParents(scope) + translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null) + return if(isStatement) DataType.UNDEFINED else func.known_returntype!! + } + private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) { if (discardResult && func.pure) return // can just ignore the whole function call altogether diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/FunctionCallAsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/FunctionCallAsmGen.kt index ef4a5653c..b2b11e6b8 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/FunctionCallAsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/FunctionCallAsmGen.kt @@ -4,14 +4,16 @@ import prog8.ast.IFunctionCall import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.* -import prog8.ast.expressions.* +import prog8.ast.expressions.AddressOf +import prog8.ast.expressions.Expression +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.NumericLiteralValue import prog8.ast.statements.* import prog8.codegen.target.AssemblyError import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind -import prog8.compilerinterface.BuiltinFunctions import prog8.compilerinterface.CpuType @@ -127,46 +129,60 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller } - internal fun translateFunctionCall(target: IdentifierReference, args: Iterable, scope: Node): DataType { + internal fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, arg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType { when(val targetStmt = target.targetStatement(program)!!) { is BuiltinFunctionPlaceholder -> { - val call = FunctionCallExpression(target, args.toMutableList(), scope.position) - call.linkParents(scope) - val signature = BuiltinFunctions.getValue(targetStmt.name) - asmgen.translateBuiltinFunctionCallExpression(call, signature, false, null) - return signature.known_returntype!! - } - is Subroutine -> { - val call = FunctionCallExpression(target, args.toMutableList(), scope.position) - call.linkParents(scope) - translateFunctionCall(call, true) - return call.inferType(program).getOrElse { throw AssemblyError("invalid dt") } - } - else -> throw AssemblyError("invalid call target") - } - } - - internal fun translateFunctionCallStatement(target: IdentifierReference, args: Iterable, scope: Node) { - when(val targetStmt = target.targetStatement(program)!!) { - is BuiltinFunctionPlaceholder -> { - val call = FunctionCallStatement(target, args.toMutableList(), true, scope.position) - call.linkParents(scope) - val signature = BuiltinFunctions.getValue(targetStmt.name) - asmgen.translateBuiltinFunctionCallStatement(call, signature) - } - is Subroutine -> { - if(targetStmt.isAsmSubroutine) { - val call = FunctionCallStatement(target, args.toMutableList(), true, scope.position) - call.linkParents(scope) - translateFunctionCallStatement(call) + return if(isStatement) { + asmgen.translateBuiltinFunctionCallStatement(targetStmt.name, listOf(arg), scope) + DataType.UNDEFINED } else { - // have to use manual parameter assignment and jsr, because no codegen for FunctionCallStmt here - val tempVar = args.single() - tempVar.linkParents(scope) - argumentViaVariable(targetStmt, targetStmt.parameters.single(), tempVar) - asmgen.out(" jsr ${asmgen.asmSymbolName(target)}") + asmgen.translateBuiltinFunctionCallExpression(targetStmt.name, listOf(arg), scope) } } + is Subroutine -> { + val argDt = targetStmt.parameters.single().type + if(targetStmt.isAsmSubroutine) { + // argument via registers + val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!! + val assignArgument = AsmAssignment( + arg, + AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, asmgen), + false, program.memsizer, target.position + ) + asmgen.translateNormalAssignment(assignArgument) + } else { + val assignArgument: AsmAssignment = + if(optimizeIntArgsViaRegisters(targetStmt)) { + // argument goes via registers as optimization + val paramReg: RegisterOrPair = when(argDt) { + in ByteDatatypes -> RegisterOrPair.A + in WordDatatypes -> RegisterOrPair.AY + DataType.FLOAT -> RegisterOrPair.FAC1 + else -> throw AssemblyError("invalid dt") + } + AsmAssignment( + arg, + AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, argDt, scope, register = paramReg), + false, program.memsizer, target.position + ) + } else { + // arg goes via parameter variable + val argVarName = asmgen.asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name) + AsmAssignment( + arg, + AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, argDt, scope, argVarName), + false, program.memsizer, target.position + ) + } + asmgen.translateNormalAssignment(assignArgument) + } + if(targetStmt.shouldSaveX()) + asmgen.saveRegisterLocal(CpuRegister.X, scope) + asmgen.out(" jsr ${asmgen.asmSymbolName(target)}") + if(targetStmt.shouldSaveX()) + asmgen.restoreRegisterLocal(CpuRegister.X) + return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single() + } else -> throw AssemblyError("invalid call target") } } diff --git a/compilerAst/src/prog8/ast/base/Base.kt b/compilerAst/src/prog8/ast/base/Base.kt index a44579d15..1fa01073e 100644 --- a/compilerAst/src/prog8/ast/base/Base.kt +++ b/compilerAst/src/prog8/ast/base/Base.kt @@ -131,6 +131,7 @@ val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE) val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD) val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) +val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT) val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD) val IterableDatatypes = arrayOf( diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 589e38b49..679c05fcc 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,7 @@ TODO For next compiler release (7.7) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- optimize codegen of pipe operator to avoid needless assigns to temp var +... Need help with @@ -53,6 +53,7 @@ Future Things and Ideas More optimization ideas ^^^^^^^^^^^^^^^^^^^^^^^ +- translateFunctioncall() in BuiltinFunctionsAsmGen: should be able to assign parameters directly from register(s) - translateNormalAssignment() -> better code gen for assigning boolean comparison expressions - if a for loop's loopvariable isn't referenced in the body, replace by a repeatloop - automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements, instead of just a warning diff --git a/examples/test.p8 b/examples/test.p8 index 2514fc2de..786a0088c 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -5,42 +5,39 @@ main { sub start() { - uword ww - ubyte bb + float fl + test_stack.test() - derp("aaaa") - bb = ww==0 - bb++ - if ww==0 { - bb++ - } + fl = addfloat3(addfloat2(addfloat1(1.234))) + floats.print_f(fl) + txt.nl() - sub derp(str name) { - txt.print(name) - } + fl = 1.234 |> addfloat1 |> addfloat2 |> addfloat3 + floats.print_f(fl) + txt.nl() + 1.234 |> addfloat1 + |> addfloat2 |> addfloat3 |> floats.print_f + txt.nl() -; fl = 1.234 |> addfloat1 |> addfloat2 |> addfloat3 -; floats.print_f(fl) -; txt.nl() -; 1.234 |> addfloat1 -; |> addfloat2 |> addfloat3 |> floats.print_f -; txt.nl() -; -; 9+3 |> assemblything -; |> sin8u -; |> add_one -; |> times_two -; |> txt.print_uw -; txt.nl() + txt.print_uw(times_two(add_one(sin8u(add_one(assemblything(9+3)))))) + txt.nl() -; test_stack.test() -; uword @shared uw= 9+3 |> assemblything -; |> sin8u -; |> add_one -; |> times_two -; txt.print_uw(uw) -; txt.nl() -; test_stack.test() + 9+3 |> assemblything + |> add_one + |> sin8u + |> add_one + |> times_two + |> txt.print_uw + txt.nl() + + uword @shared uw= 9+3 |> assemblything + |> add_one + |> sin8u + |> add_one + |> times_two + txt.print_uw(uw) + txt.nl() + test_stack.test() } sub func() -> ubyte { @@ -65,7 +62,7 @@ main { } sub times_two(ubyte input) -> uword { - return input*$6464642 + return input*$0002 } asmsub assemblything(ubyte input @A) clobbers(X,Y) -> ubyte @A {