implement missing long typecasts

This commit is contained in:
Irmen de Jong
2025-10-07 17:43:28 +02:00
parent 0f564b301d
commit 396fcbc927
6 changed files with 103 additions and 27 deletions

View File

@@ -1620,7 +1620,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg") else -> throw AssemblyError("invalid reg")
} }
} else { } 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 -> throw AssemblyError("invalid reg")
} }
} else { } 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")
}
} }
} }

View File

@@ -2455,7 +2455,7 @@ $endLabel""")
is PtArrayIndexer -> { is PtArrayIndexer -> {
if(targetDt.isByte && valueDt.isWord) { if(targetDt.isByte && valueDt.isWord) {
// just assign the lsb from the array value // just assign the lsb from the array value
return assignCastViaLsbFunc(value, target) return assignCastWordViaLsbFunc(value, target)
} else if(targetDt.isBool && valueDt.isWord) { } else if(targetDt.isBool && valueDt.isWord) {
return assignWordToBool(value, target) return assignWordToBool(value, target)
} }
@@ -2513,7 +2513,7 @@ $endLabel""")
if(parentTc!=null && parentTc.type.isUnsignedWord) { if(parentTc!=null && parentTc.type.isUnsignedWord) {
// typecast a word value to ubyte and directly back to uword // typecast a word value to ubyte and directly back to uword
// generate code for lsb(value) here instead of the ubyte typecast // 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 else
// cast an uword to a byte register, do this via lsb(value) // cast an uword to a byte register, do this via lsb(value)
// generate code for lsb(value) here instead of the ubyte typecast // generate code for lsb(value) here instead of the ubyte typecast
assignCastViaLsbFunc(value, target) assignCastWordViaLsbFunc(value, target)
} }
RegisterOrPair.AX, RegisterOrPair.AX,
RegisterOrPair.AY, RegisterOrPair.AY,
@@ -2569,7 +2569,7 @@ $endLabel""")
if(!(valueDt isAssignableTo targetDt)) { if(!(valueDt isAssignableTo targetDt)) {
return if(valueDt.isWord && targetDt.isByte) { return if(valueDt.isWord && targetDt.isByte) {
// word to byte, just take the lsb // word to byte, just take the lsb
assignCastViaLsbFunc(value, target) assignCastWordViaLsbFunc(value, target)
} else if(valueDt.isWord && targetDt.isBool) { } else if(valueDt.isWord && targetDt.isBool) {
// word to bool // word to bool
assignWordToBool(value, target) assignWordToBool(value, target)
@@ -2582,6 +2582,12 @@ $endLabel""")
} else if(valueDt.isByteOrBool && targetDt.isWord) { } else if(valueDt.isByteOrBool && targetDt.isWord) {
// byte to word, just assign // byte to word, just assign
assignExpressionToRegister(value, target.register!!, valueDt.isSigned) 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 } else
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker") 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) 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) val lsb = PtBuiltinFunctionCall("lsb", false, true, DataType.UBYTE, value.position)
lsb.parent = value.parent lsb.parent = value.parent
lsb.add(value) lsb.add(value)
@@ -2673,6 +2696,15 @@ $endLabel""")
translateNormalAssignment(assign, value.definingISub()) 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) { private fun assignWordToBool(value: PtExpression, target: AsmAssignTarget) {
assignExpressionToRegister(value, RegisterOrPair.AY, false) assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.out(""" asmgen.out("""

View File

@@ -686,7 +686,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null) addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result) 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 -> { valueDt.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also { result += IRCodeChunk(null, null).also {
@@ -706,7 +709,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) 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 -> { BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) 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) actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) 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 -> { BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) 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 -> { BaseDataType.WORD -> {
actualResultReg2 = tr.resultReg 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 -> { BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD) actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) 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 -> { BaseDataType.UWORD -> {
actualResultReg2 = tr.resultReg 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 -> { BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD) actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null) addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)

View File

@@ -28,6 +28,7 @@ Future Things and Ideas
- fix the line, cols in Position, sometimes they count from 0 sometimes from 1 - 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" ? - is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
- enums? - 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: 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) - romable: fix remaining codegens (some for loops, see ForLoopsAsmGen)
- Kotlin: can we use inline value classes in certain spots? (domain types instead of primitives) - 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(). 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 - 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? - 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 - 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) (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? - 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 - 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!) - 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?) - 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 - 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) - 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 fast code paths for TODO("inplace split....
- implement more TODOs in AssignmentGen - implement more TODOs in AssignmentGen
@@ -161,6 +163,7 @@ Libraries
Optimizations 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 - 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 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. - optimize optimizedBitwiseExpr() for const and variable operands to not assign needlessly to R0-R3.

View File

@@ -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 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 = sprites[0]
sprite.y++ sprite.y++
sort_ypositions[0] = sprites[0].y sort_ypositions[0] = sprites[0].y

View File

@@ -3,19 +3,19 @@
main { main {
sub start() { sub start() {
long lv1 = 12345678 long @shared lv1 = -9999
txt.print_uw(lsw(lv1))
txt.print_l(lv1)
txt.spc() txt.spc()
txt.print_uw(&lv1) txt.print_w(lv1 as word)
txt.spc() txt.spc()
txt.print_l(peekl(&lv1)) txt.print_uw(lv1 as uword)
txt.nl() txt.spc()
txt.print_b(lv1 as byte)
pokel(&lv1, -6666666) txt.spc()
sys.pushl(lv1) txt.print_ub(lv1 as ubyte)
txt.spc()
long lv2 = sys.popl() txt.print_w(msw(lv1 << 8) as word)
txt.print_l(lv2) txt.spc()
txt.print_w(lsw(lv1 >> 8) as word)
} }
} }