Got rid of problematic attempts to save status register after function calls. If you really need it (for instance for if_XX instructions) it's probably better to use a short asmsub wrapper.

For function calls, register saves go via stack (to allow nested saves) for simpler cases, registers are saved in a local variable.
Fixed too agressive removal of sta-lda sequence if the lda is followed by a branching instruction.
Insert missing cmp #0 after functioncall if the value of the A register is needed in a comparison expression (could otherwise test wrong status flag)
This commit is contained in:
Irmen de Jong 2020-12-22 03:35:00 +01:00
parent f1d55c688a
commit 928611eb20
17 changed files with 134 additions and 82 deletions

View File

@ -225,6 +225,27 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
; ---- end of C64 ROM kernal routines ----
; ---- utilities -----
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
txa
pha
jsr c64.STOP
beq +
pla
tax
lda #0
rts
+ pla
tax
lda #1
rts
}}
}
; ---- C64 specific system utility routines: ----

View File

@ -56,6 +56,23 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- utility
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
phx
jsr c64.STOP
beq +
plx
lda #0
rts
+ plx
lda #1
rts
}}
}
}
cx16 {

View File

@ -3,6 +3,8 @@
; Note: this code is compatible with C64 and CX16.
; TODO directory() BROKEN ON C64, SEEMS TO WORK ON CX16
diskio {
@ -30,16 +32,17 @@ diskio {
txt.print_uw(mkword(high, low))
txt.chrout(' ')
ubyte @zp char
do {
repeat {
char = c64.CHRIN()
if char==0 ; TODO doesn't work???
break
txt.chrout(char)
} until char==0
}
txt.chrout('\n')
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_z
if c64.STOP2()
break
}
@ -160,7 +163,7 @@ io_error:
; read the filename
repeat {
ubyte char = c64.CHRIN()
if_z
if char==0
break
if char=='\"'
break

View File

@ -757,7 +757,6 @@ class Subroutine(override val name: String,
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun shouldPreserveStatusRegisterAfterCall() = asmReturnvaluesRegisters.any { it.statusflag != null } || shouldSaveX()
fun shouldSaveX() = CpuRegister.X in asmClobbers || regXasResult() || regXasParam()
fun amountOfRtsInAsm(): Int = statements

View File

@ -547,8 +547,20 @@ internal class AsmGen(private val program: Program,
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine) {
if(dontUseStack) {
internal fun saveRegister(register: CpuRegister, scope: Subroutine, useStack: Boolean) {
if(useStack) {
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
}
}
} else {
when (register) {
CpuRegister.A -> {
out(" sta _prog8_regsaveA")
@ -563,48 +575,28 @@ internal class AsmGen(private val program: Program,
scope.asmGenInfo.usedRegsaveY = true
}
}
} else {
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else {
out(" stx _prog8_regsaveX")
scope.asmGenInfo.usedRegsaveX = true
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else {
out(" sty _prog8_regsaveY")
scope.asmGenInfo.usedRegsaveY = true
}
}
}
}
}
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
if(dontUseStack) {
when (register) {
CpuRegister.A -> out(" sta _prog8_regsaveA")
CpuRegister.X -> out(" ldx _prog8_regsaveX")
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
}
} else {
internal fun restoreRegister(register: CpuRegister, useStack: Boolean) {
if(useStack) {
when (register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else out(" ldx _prog8_regsaveX")
else out(" pla | tax")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else out(" ldy _prog8_regsaveY")
else out(" pla | tay")
}
}
} else {
when (register) {
CpuRegister.A -> out(" sta _prog8_regsaveA")
CpuRegister.X -> out(" ldx _prog8_regsaveX")
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
}
}
}
@ -738,8 +730,8 @@ internal class AsmGen(private val program: Program,
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)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)

View File

@ -179,7 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
@ -196,10 +196,14 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
val third = pair[2].value.trimStart()
if(!third.startsWith("b")) {
// no branch instruction follows, we can potentiall remove the load instruction
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}
}
}
}

View File

@ -120,8 +120,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R0)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R1)
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.A)
val sub = (fcall as FunctionCallStatement).definingSubroutine()!!
asmgen.saveRegister(CpuRegister.X, false, sub)
asmgen.saveRegister(CpuRegister.X, scope, false)
asmgen.out(" jsr cx16.memory_fill")
asmgen.restoreRegister(CpuRegister.X, false)
}
@ -139,8 +138,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R0)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R1)
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R2)
val sub = (fcall as FunctionCallStatement).definingSubroutine()!!
asmgen.saveRegister(CpuRegister.X, false, sub)
asmgen.saveRegister(CpuRegister.X, scope, false)
asmgen.out(" jsr cx16.memory_copy")
asmgen.restoreRegister(CpuRegister.X, false)
}

View File

@ -74,6 +74,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is FunctionCall -> {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall)
asmgen.out(" cmp #0")
asmgen.out(" bne $jumpIfFalseLabel")
return
}
@ -112,6 +114,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is FunctionCall -> {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall)
asmgen.out(" cmp #0")
asmgen.out(" beq $jumpIfFalseLabel")
return
}
@ -1329,8 +1333,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.translateBuiltinFunctionCallExpression(expression, builtinFunc, true)
} else {
sub as Subroutine
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
asmgen.translateFunctionCall(expression, preserveStatusRegisterAfterCall)
asmgen.translateFunctionCall(expression)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value in cpu or status registers, put it on the stack

View File

@ -15,22 +15,18 @@ import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
translateFunctionCall(stmt)
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp") // restore status flags from call
}
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
// output the code to setup the parameters and perform the actual call
// 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 saveX = sub.shouldSaveX()
if(saveX)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
asmgen.saveRegister(CpuRegister.X, (stmt as Node).definingSubroutine()!!, true)
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
@ -67,15 +63,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
asmgen.out(" jsr $subName")
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php") // save status flags from call
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
// must take care of popping this value again at the end!
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
asmgen.restoreRegister(CpuRegister.X, true)
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {

View File

@ -91,7 +91,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope!!)
asmgen.saveRegister(CpuRegister.X, scope!!, false)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {

View File

@ -142,8 +142,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
asmgen.translateFunctionCall(value)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.registerOrPair!=null }
when (returnValue.first) {
DataType.STR -> {
@ -182,8 +181,6 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
if (preserveStatusRegisterAfterCall)
asmgen.out(" plp") // restore status flags from call
}
is BuiltinFunctionStatementPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
@ -2003,9 +2000,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.storeByteIntoPointer(addressExpr, null)
}
else -> {
asmgen.saveRegister(register, false, memoryAddress.definingSubroutine()!!)
asmgen.saveRegister(register, memoryAddress.definingSubroutine()!!, true)
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.restoreRegister(CpuRegister.A, false)
asmgen.restoreRegister(CpuRegister.A, true)
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
}
}

View File

@ -1417,7 +1417,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.saveRegister(CpuRegister.X, scope, false)
when (operator) {
"**" -> {
asmgen.out("""
@ -1472,7 +1472,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
throw AssemblyError("float variable expected")
val otherName = asmgen.asmVariableName(ident)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.saveRegister(CpuRegister.X, scope, false)
when (operator) {
"**" -> {
if(CompilationTarget.instance is Cx16Target) {
@ -1550,7 +1550,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
val constValueName = asmgen.getFloatAsmConst(value)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.saveRegister(CpuRegister.X, scope, false)
when (operator) {
"**" -> {
if(CompilationTarget.instance is Cx16Target) {

View File

@ -481,6 +481,12 @@ condition meaning
So ``if_cc goto target`` will directly translate into the single CPU instruction ``BCC target``.
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
If in doubt, check the generated assembly code!
.. note::
For now, the symbols used or declared in the statement block(s) are shared with
the same scope the if statement itself is in.

View File

@ -721,6 +721,13 @@ The XX corresponds to one of the eigth branching instructions so the possibiliti
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``.
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
If in doubt, check the generated assembly code!
when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The structure of a when statement is like this::

View File

@ -7,6 +7,9 @@
; staged speed increase
; some simple sound effects
; TODO BROKEN: DOESN'T REGISTER KEYSTROKES ANYMORE (CAN'T START GAME)
%target c64
%import syslib
%import textio

View File

@ -1,5 +1,5 @@
%import textio
;%import diskio
%import diskio
;%import floats
;%import graphics
%zeropage basicsafe
@ -70,14 +70,24 @@ _y .byte 0
}
sub start () {
while c64.CHRIN() {
; read the rest of the entry until the end
}
; cx16.r0 = 65535
; set_8_pixels_opaque_OLD(111,222,33)
; txt.chrout('\n')
ubyte bb = 44
set_8_pixels_opaque(bb,111,222,33)
txt.chrout('\n')
set_8_pixels_opaque(c64.CHRIN(),111,222,33)
txt.chrout('\n')
; ;ubyte qq=c64.CHKIN(3) ;; TODO fix compiler crash "can't translate zero return values in assignment"
;
; test_stack.test()
; ubyte bb = 44
; set_8_pixels_opaque(bb,111,222,33)
; txt.chrout('\n')
; test_stack.test()
;
; set_8_pixels_opaque(c64.CHRIN(),111,222,33)
; txt.chrout('\n')
test_stack.test()
}

View File

@ -10,6 +10,9 @@
; Note: this program is compatible with C64 and CX16.
; TODO BROKEN IN VICE WHEN LOADING WITHOUT DISK: PRINTS WEIRD CHARACTERS FOR IO-ERROR
main {
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one