optimizing pipe codegen

This commit is contained in:
Irmen de Jong 2022-01-11 21:10:40 +01:00
parent 056ec986c2
commit e425c4cca8
6 changed files with 148 additions and 95 deletions

View File

@ -957,8 +957,11 @@ class AsmGen(private val program: Program,
internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpression, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) = internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpression, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCallExpr, signature, resultToStack, resultRegister) builtinFunctionsAsmGen.translateFunctioncallExpression(functionCallExpr, signature, resultToStack, resultRegister)
internal fun translateBuiltinFunctionCallStatement(functionCallStmt: FunctionCallStatement, signature: FSignature) = internal fun translateBuiltinFunctionCallExpression(name: String, args: List<AsmAssignSource>, scope: Subroutine): DataType =
builtinFunctionsAsmGen.translateFunctioncallStatement(functionCallStmt, signature) builtinFunctionsAsmGen.translateFunctioncall(name, args, false, scope)
internal fun translateBuiltinFunctionCallStatement(name: String, args: List<AsmAssignSource>, scope: Subroutine) =
builtinFunctionsAsmGen.translateFunctioncall(name, args, true, scope)
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) = internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression) functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression)
@ -1626,13 +1629,14 @@ $label nop""")
assemblyLines.add(assembly) assemblyLines.add(assembly)
} }
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair? { internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
return when (val targetRoutine = it.targetStatement(program)!!) { return when (val targetRoutine = it.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> { is BuiltinFunctionPlaceholder -> {
when (BuiltinFunctions.getValue(targetRoutine.name).known_returntype) { when (BuiltinFunctions.getValue(targetRoutine.name).known_returntype) {
in ByteDatatypes -> RegisterOrPair.A in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY in WordDatatypes -> RegisterOrPair.AY
else -> return null DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("weird returntype")
} }
} }
is Subroutine -> targetRoutine.asmReturnvaluesRegisters.single().registerOrPair!! 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 // TODO more efficient code generation to avoid needless assignments to the temp var
val subroutine = scope.definingSubroutine // the first term: an expression (could be anything) producing a value.
var valueDt = expressions.first().inferType(program).getOrElse { throw FatalAstException("invalid dt") } val subroutine = scope.definingSubroutine!!
var valueVar = getTempVarName(valueDt) val firstTerm = expressions.first()
assignExpressionToVariable(expressions.first(), asmVariableName(valueVar), valueDt, subroutine) 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 { expressions.drop(1).dropLast(1).forEach {
valueDt = functioncallAsmGen.translateFunctionCall(it as IdentifierReference, listOf(IdentifierReference(valueVar, it.position)), scope) valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(it as IdentifierReference, valueSource, false, subroutine)
// assign result value from the functioncall back to the temp var: val resultReg = returnRegisterOfFunction(it)
valueVar = getTempVarName(valueDt) valueSource = AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
val valueVarTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, valueDt, subroutine, variableAsmName = valueVar.joinToString("."))
val returnRegister = returnRegisterOfFunction(it)!!
assignRegister(returnRegister, valueVarTarget)
} }
// the last term: unary function call taking a single param and optionally producing a result value.
if(isStatement) { if(isStatement) {
// the last term in the pipe, don't care about return var: // the last term in the pipe, don't care about return var:
functioncallAsmGen.translateFunctionCallStatement( functioncallAsmGen.translateUnaryFunctionCallWithArgSource(
expressions.last() as IdentifierReference, expressions.last() as IdentifierReference, valueSource, true, subroutine)
listOf(IdentifierReference(valueVar, expressions.last().position)),
scope
)
} else { } else {
// the last term in the pipe, regular function call with returnvalue: // the last term in the pipe, regular function call with returnvalue:
valueDt = functioncallAsmGen.translateFunctionCall( valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(
expressions.last() as IdentifierReference, expressions.last() as IdentifierReference, valueSource, false, subroutine)
listOf(IdentifierReference(valueVar, expressions.last().position)),
scope
)
if(pushResultOnEstack) { if(pushResultOnEstack) {
when (valueDt) { when (valueDt) {
in ByteDatatypes -> { in ByteDatatypes -> {

View File

@ -13,6 +13,7 @@ import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.codegen.target.AssemblyError
import prog8.codegen.target.Cx16Target import prog8.codegen.target.Cx16Target
import prog8.codegen.target.cpu6502.codegen.assignment.* import prog8.codegen.target.cpu6502.codegen.assignment.*
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2 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) translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
} }
internal fun translateFunctioncall(name: String, args: List<AsmAssignSource>, 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?) { private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure) if (discardResult && func.pure)
return // can just ignore the whole function call altogether return // can just ignore the whole function call altogether

View File

@ -4,14 +4,16 @@ import prog8.ast.IFunctionCall
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* 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.ast.statements.*
import prog8.codegen.target.AssemblyError import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType 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 // 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<Expression>, scope: Node): DataType { internal fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, arg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
when(val targetStmt = target.targetStatement(program)!!) { when(val targetStmt = target.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> { is BuiltinFunctionPlaceholder -> {
val call = FunctionCallExpression(target, args.toMutableList(), scope.position) return if(isStatement) {
call.linkParents(scope) asmgen.translateBuiltinFunctionCallStatement(targetStmt.name, listOf(arg), scope)
val signature = BuiltinFunctions.getValue(targetStmt.name) DataType.UNDEFINED
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<Expression>, 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)
} else { } else {
// have to use manual parameter assignment and jsr, because no codegen for FunctionCallStmt here asmgen.translateBuiltinFunctionCallExpression(targetStmt.name, listOf(arg), scope)
val tempVar = args.single()
tempVar.linkParents(scope)
argumentViaVariable(targetStmt, targetStmt.parameters.single(), tempVar)
asmgen.out(" jsr ${asmgen.asmSymbolName(target)}")
} }
} }
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") else -> throw AssemblyError("invalid call target")
} }
} }

View File

@ -131,6 +131,7 @@ val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD) val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, 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 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 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 StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val IterableDatatypes = arrayOf( val IterableDatatypes = arrayOf(

View File

@ -3,7 +3,7 @@ TODO
For next compiler release (7.7) For next compiler release (7.7)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- optimize codegen of pipe operator to avoid needless assigns to temp var ...
Need help with Need help with
@ -53,6 +53,7 @@ Future Things and Ideas
More optimization 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 - 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 - 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 - 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

View File

@ -5,42 +5,39 @@
main { main {
sub start() { sub start() {
uword ww float fl
ubyte bb test_stack.test()
derp("aaaa") fl = addfloat3(addfloat2(addfloat1(1.234)))
bb = ww==0 floats.print_f(fl)
bb++ txt.nl()
if ww==0 {
bb++
}
sub derp(str name) { fl = 1.234 |> addfloat1 |> addfloat2 |> addfloat3
txt.print(name) floats.print_f(fl)
} txt.nl()
1.234 |> addfloat1
|> addfloat2 |> addfloat3 |> floats.print_f
txt.nl()
; fl = 1.234 |> addfloat1 |> addfloat2 |> addfloat3 txt.print_uw(times_two(add_one(sin8u(add_one(assemblything(9+3))))))
; floats.print_f(fl) txt.nl()
; 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()
; test_stack.test() 9+3 |> assemblything
; uword @shared uw= 9+3 |> assemblything |> add_one
; |> sin8u |> sin8u
; |> add_one |> add_one
; |> times_two |> times_two
; txt.print_uw(uw) |> txt.print_uw
; txt.nl() txt.nl()
; test_stack.test()
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 { sub func() -> ubyte {
@ -65,7 +62,7 @@ main {
} }
sub times_two(ubyte input) -> uword { sub times_two(ubyte input) -> uword {
return input*$6464642 return input*$0002
} }
asmsub assemblything(ubyte input @A) clobbers(X,Y) -> ubyte @A { asmsub assemblything(ubyte input @A) clobbers(X,Y) -> ubyte @A {