From 396fcbc9270178a1e68eb150e49fd3653b8c8b94 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 7 Oct 2025 17:43:28 +0200 Subject: [PATCH] implement missing long typecasts --- .../codegen/cpu6502/BuiltinFunctionsAsmGen.kt | 26 +++++++++++- .../cpu6502/assignment/AssignmentAsmGen.kt | 42 ++++++++++++++++--- .../codegen/intermediate/ExpressionGen.kt | 29 ++++++++++--- docs/source/todo.rst | 7 +++- examples/c64/multiplexer.p8 | 2 +- examples/test.p8 | 24 +++++------ 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index ac3c97fa0..0a7cac613 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -1620,7 +1620,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, else -> throw AssemblyError("invalid reg") } } else { - TODO("msw(expression) ${fcall.position} - use a temporary variable for now") + asmgen.assignExpressionToRegister(arg, RegisterOrPair.R0R1_32, true) + when(resultRegister) { + RegisterOrPair.AX -> asmgen.out(" lda cx16.r1 | ldx cx16.r1+1") + null, RegisterOrPair.AY -> asmgen.out(" lda cx16.r1 | ldy cx16.r1+1") + RegisterOrPair.XY -> asmgen.out(" ldx cx16.r1 | ldy cx16.r1+1") + in Cx16VirtualRegisters -> { + val regname = resultRegister.name.lowercase() + asmgen.out(" lda cx16.r1 | sta cx16.$regname | lda cx16.r1+1 | sta cx16.$regname+1") + } + else -> throw AssemblyError("invalid reg") + } } } @@ -1643,7 +1653,19 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, else -> throw AssemblyError("invalid reg") } } else { - TODO("lsw(expression) ${fcall.position} - use a temporary variable for now") + asmgen.assignExpressionToRegister(arg, RegisterOrPair.R0R1_32, true) + when(resultRegister) { + RegisterOrPair.AX -> asmgen.out(" lda cx16.r0 | ldx cx16.r0+1") + null, RegisterOrPair.AY -> asmgen.out(" lda cx16.r0 | ldy cx16.r0+1") + RegisterOrPair.XY -> asmgen.out(" ldx cx16.r0 | ldy cx16.r0+1") + in Cx16VirtualRegisters -> { + if(resultRegister!=RegisterOrPair.R0) { + val regname = resultRegister.name.lowercase() + asmgen.out(" lda cx16.r0 | sta cx16.$regname | lda cx16.r0+1 | sta cx16.$regname+1") + } + } + else -> throw AssemblyError("invalid reg") + } } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 0cc318840..e3e49c328 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -2455,7 +2455,7 @@ $endLabel""") is PtArrayIndexer -> { if(targetDt.isByte && valueDt.isWord) { // just assign the lsb from the array value - return assignCastViaLsbFunc(value, target) + return assignCastWordViaLsbFunc(value, target) } else if(targetDt.isBool && valueDt.isWord) { return assignWordToBool(value, target) } @@ -2513,7 +2513,7 @@ $endLabel""") if(parentTc!=null && parentTc.type.isUnsignedWord) { // typecast a word value to ubyte and directly back to uword // generate code for lsb(value) here instead of the ubyte typecast - return assignCastViaLsbFunc(value, target) + return assignCastWordViaLsbFunc(value, target) } } @@ -2545,7 +2545,7 @@ $endLabel""") else // cast an uword to a byte register, do this via lsb(value) // generate code for lsb(value) here instead of the ubyte typecast - assignCastViaLsbFunc(value, target) + assignCastWordViaLsbFunc(value, target) } RegisterOrPair.AX, RegisterOrPair.AY, @@ -2569,7 +2569,7 @@ $endLabel""") if(!(valueDt isAssignableTo targetDt)) { return if(valueDt.isWord && targetDt.isByte) { // word to byte, just take the lsb - assignCastViaLsbFunc(value, target) + assignCastWordViaLsbFunc(value, target) } else if(valueDt.isWord && targetDt.isBool) { // word to bool assignWordToBool(value, target) @@ -2582,6 +2582,12 @@ $endLabel""") } else if(valueDt.isByteOrBool && targetDt.isWord) { // byte to word, just assign assignExpressionToRegister(value, target.register!!, valueDt.isSigned) + } else if(valueDt.isLong && targetDt.isByte) { + // long to byte, just take the lsb + assignCastLongToByte(value, target) + } else if(valueDt.isLong && targetDt.isWord) { + // long to word, just take the lsw + assignCastViaLswFunc(value, target) } else throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker") } @@ -2664,7 +2670,24 @@ $endLabel""") asmgen.assignExpressionTo(origTypeCastExpression, target) } - private fun assignCastViaLsbFunc(value: PtExpression, target: AsmAssignTarget) { + private fun assignCastLongToByte(value: PtExpression, target: AsmAssignTarget) { + // long to byte, can't use lsb() because that only works on words + when(value) { + is PtIdentifier -> { + val longvar = asmgen.asmVariableName(value) + asmgen.out(" lda $longvar") + assignRegisterByte(target, CpuRegister.A, true, false) + } + is PtNumber -> throw AssemblyError("casting a long number to byte should have been const-folded away ${value.position}") + else -> { + assignExpressionToRegister(value, RegisterOrPair.R0R1_32, true) + asmgen.out(" lda cx16.r0") + assignRegisterByte(target, CpuRegister.A, true, false) + } + } + } + + private fun assignCastWordViaLsbFunc(value: PtExpression, target: AsmAssignTarget) { val lsb = PtBuiltinFunctionCall("lsb", false, true, DataType.UBYTE, value.position) lsb.parent = value.parent lsb.add(value) @@ -2673,6 +2696,15 @@ $endLabel""") translateNormalAssignment(assign, value.definingISub()) } + private fun assignCastViaLswFunc(value: PtExpression, target: AsmAssignTarget) { + val lsb = PtBuiltinFunctionCall("lsw", false, true, DataType.UWORD, value.position) + lsb.parent = value.parent + lsb.add(value) + val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = lsb) + val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position) + translateNormalAssignment(assign, value.definingISub()) + } + private fun assignWordToBool(value: PtExpression, target: AsmAssignTarget) { assignExpressionToRegister(value, RegisterOrPair.AY, false) asmgen.out(""" diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index a7ea013a5..d2158ba9f 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -686,7 +686,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null) actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result) } - valueDt.isLong -> TODO("typecast long ${cast.position}") + valueDt.isLong -> { + addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=tr.resultReg, immediate = 0), null) + actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result) + } valueDt.isFloat -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { @@ -706,7 +709,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) } - BaseDataType.LONG -> TODO("cast UBYTE to LONG ${cast.position}") + BaseDataType.LONG -> { + actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) + val wordReg = codeGen.registers.next(IRDataType.WORD) + addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1=wordReg, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=wordReg, immediate = 0), null) + } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) @@ -723,7 +731,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) } - BaseDataType.LONG -> TODO("cast BYTE to LONG ${cast.position}") + BaseDataType.LONG -> { + actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) + val wordReg = codeGen.registers.next(IRDataType.WORD) + addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1=wordReg, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=wordReg, immediate = 0), null) + } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) @@ -746,7 +759,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.WORD -> { actualResultReg2 = tr.resultReg } - BaseDataType.LONG -> TODO("cast UWORD to LONG ${cast.position}") + BaseDataType.LONG -> { + actualResultReg2 = codeGen.registers.next(IRDataType.WORD) + addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.WORD) addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) @@ -772,7 +788,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.UWORD -> { actualResultReg2 = tr.resultReg } - BaseDataType.LONG -> TODO("cast WORD to LONG ${cast.position}}") + BaseDataType.LONG -> { + actualResultReg2 = codeGen.registers.next(IRDataType.WORD) + addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.WORD) addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index d3de5b5ae..5d0dbd280 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -28,6 +28,7 @@ Future Things and Ideas - fix the line, cols in Position, sometimes they count from 0 sometimes from 1 - is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ? - enums? +- fix the c64 multiplexer example - romable: should we have a way to explicitly set the memory address for the BSS area (add a -varsaddress and -slabsaddress options?) - romable: fix remaining codegens (some for loops, see ForLoopsAsmGen) - Kotlin: can we use inline value classes in certain spots? (domain types instead of primitives) @@ -41,10 +42,10 @@ Future Things and Ideas Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters(). - Does it make codegen easier if everything is an expression? Start with the PtProgram ast classes, change statements to expressions that have (new) VOID data type - Can we support signed % (remainder) somehow? -- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful once we have typed pointers. (addressed in 'struct' branch) +- Two- or even multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays? - make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type (this is already done hardcoded for several of the builtin functions) -- [much work:] more support for (64tass) SEGMENTS in the prog8 syntax itself? +- more support for (64tass) SEGMENTS in the prog8 syntax itself? maybe %segment blah in blocks? - ability to use a sub instead of only a var for @bank ? what for though? dynamic bank/overlay loading? - Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions @@ -77,6 +78,7 @@ IR/VM - getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!) - fix call() return value handling (... what's wrong with it again?) - proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg +- register reuse to reduce the number of required variables in memory eventually. But can only re-use a register if a) it's the same type and b) if the second occurrence is not called from the first occurrence (otherwise the value gets overwritten!) - encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction) - implement fast code paths for TODO("inplace split.... - implement more TODOs in AssignmentGen @@ -161,6 +163,7 @@ Libraries Optimizations ------------- +- optimize inplaceLongShiftRight() for byte aligned cases - more optimized operator handling of different types, for example uword a ^ byte b now does a type cast of b to word first - optimize longEqualsValue() for const and variable operands to not assign needlessly to R0-R3. - optimize optimizedBitwiseExpr() for const and variable operands to not assign needlessly to R0-R3. diff --git a/examples/c64/multiplexer.p8 b/examples/c64/multiplexer.p8 index 98d799c21..f7556a61b 100644 --- a/examples/c64/multiplexer.p8 +++ b/examples/c64/multiplexer.p8 @@ -81,7 +81,7 @@ main { ; TODO keep working with the previously sorted result instead of rewriting the list every time, makes sorting faster if not much changes in the Y positions } - ; TODO remove this simplistic anim but it's here to test the algorithms + ; TODO remove this simplistic animation but it's here to test the algorithms sprite = sprites[0] sprite.y++ sort_ypositions[0] = sprites[0].y diff --git a/examples/test.p8 b/examples/test.p8 index e7c4d91d6..f0123c45d 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,19 +3,19 @@ main { sub start() { - long lv1 = 12345678 - - txt.print_l(lv1) + long @shared lv1 = -9999 + txt.print_uw(lsw(lv1)) txt.spc() - txt.print_uw(&lv1) + txt.print_w(lv1 as word) txt.spc() - txt.print_l(peekl(&lv1)) - txt.nl() - - pokel(&lv1, -6666666) - sys.pushl(lv1) - - long lv2 = sys.popl() - txt.print_l(lv2) + txt.print_uw(lv1 as uword) + txt.spc() + txt.print_b(lv1 as byte) + txt.spc() + txt.print_ub(lv1 as ubyte) + txt.spc() + txt.print_w(msw(lv1 << 8) as word) + txt.spc() + txt.print_w(lsw(lv1 >> 8) as word) } }