Compare commits

..

10 Commits

Author SHA1 Message Date
Irmen de Jong
5ada80779d Merge branch 'refs/heads/master' into structs6502
# Conflicts:
#	examples/test.p8
2025-08-07 21:25:07 +02:00
Irmen de Jong
e56f533e38 more basic pointer inplace operations 2025-08-07 00:36:47 +02:00
Irmen de Jong
324fb7dbf7 more basic pointer inplace operations (float)
basic pointers unit test now passes
2025-08-06 22:26:23 +02:00
Irmen de Jong
44285b9b5d more basic pointer inplace operations 2025-08-06 00:32:15 +02:00
Irmen de Jong
a68f477d61 Merge branch 'master' into structs6502
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-08-05 23:29:58 +02:00
Irmen de Jong
9e10c15e2e working on 6502 pointer inplace assignments 2025-08-04 23:32:17 +02:00
Irmen de Jong
6bd7752bac working on 6502 pointer dereferencing 2025-08-04 20:22:13 +02:00
Irmen de Jong
83ec437e8a testpointers unit test now also for 6502 targets
implementing first simple pointer operations
pointer vars also allocated in ZP for dontcare
2025-08-03 22:12:03 +02:00
Irmen de Jong
4a1d05dd46 first 6502 codegen results 2025-08-03 16:10:00 +02:00
Irmen de Jong
aa324e355a remove 6502 pointer check, TODOs for pointer assignments 2025-08-03 13:28:39 +02:00
19 changed files with 968 additions and 222 deletions

View File

@@ -96,6 +96,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)), "prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)), "prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)), "prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)), "abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)), "abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)), "abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),

View File

@@ -350,6 +350,8 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isUnsigned = !base.isSigned val isUnsigned = !base.isSigned
val isArray = base.isArray val isArray = base.isArray
val isPointer = base.isPointer val isPointer = base.isPointer
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
val isPointerToWord = base.isPointer && sub?.isWord==true
val isStructInstance = base.isStructInstance val isStructInstance = base.isStructInstance
val isPointerArray = base.isPointerArray val isPointerArray = base.isPointerArray
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
@@ -422,6 +424,9 @@ enum class RegisterOrPair {
} }
return listOf("cx16", name.lowercase()+suffix) return listOf("cx16", name.lowercase()+suffix)
} }
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
} // only used in parameter and return value specs in asm subroutines } // only used in parameter and return value specs in asm subroutines
enum class Statusflag { enum class Statusflag {

View File

@@ -72,6 +72,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
val size: Int = val size: Int =
when { when {
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null) datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
datatype.isPointer -> options.compTarget.memorySize(datatype, null)
datatype.isString || datatype.isArray -> { datatype.isString || datatype.isArray -> {
val memsize = options.compTarget.memorySize(datatype, numElements!!) val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null) if(position!=null)
@@ -122,6 +123,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
datatype.isNumericOrBool -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments datatype.isNumericOrBool -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
datatype.isString -> VarAllocation(address, datatype, size) datatype.isString -> VarAllocation(address, datatype, size)
datatype.isArray -> VarAllocation(address, datatype, size) datatype.isArray -> VarAllocation(address, datatype, size)
datatype.isPointer -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt") else -> throw AssemblyError("invalid dt")
} }
} }

View File

@@ -256,7 +256,8 @@ class AsmGen6502Internal (
private val functioncallAsmGen = FunctionCallAsmGen(program, this) private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage) private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
private val anyExprGen = AnyExprAsmGen(this) private val anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator) private val pointerGen = PointerAssignmentsGen(this, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, this, pointerGen, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen) private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, assignmentAsmGen, errors) private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, assignmentAsmGen, errors)
private val ifExpressionAsmgen = IfExpressionAsmGen(this, assignmentAsmGen, errors) private val ifExpressionAsmgen = IfExpressionAsmGen(this, assignmentAsmGen, errors)

View File

@@ -397,10 +397,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcStructAlloc(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?) { private fun funcStructAlloc(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult) if(discardResult)
throw AssemblyError("should not discard result of struct allocation at $fcall") throw AssemblyError("should not discard result of struct allocation at $fcall")
if(fcall.args.isEmpty()) val struct = fcall.type.subType!!
TODO("struct alloc in BSS") // ... don't need to pay attention to args here because struct instance is put together elsewhere we just have to get a pointer to it
else val slabname = PtIdentifier("????TODO-STRUCTINSTANCENAME????", DataType.UWORD, fcall.position) // TODO STRUCTNAME
TODO("static struct alloc with values") val addressOf = PtAddressOf(fcall.type, true, fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, fcall.type, expression = addressOf)
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
val assign = AsmAssignment(src, listOf(target), program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign, fcall.definingISub())
} }

View File

@@ -333,7 +333,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else { } else {
val scope = value.definingISub() val scope = value.definingISub()
val target: AsmAssignTarget = val target: AsmAssignTarget =
if(parameter.value.type.isByte && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters)) if(parameter.value.type.isByte && register.isWord())
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register) AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register)
else { else {
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen) AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)

View File

@@ -63,6 +63,11 @@ internal class IfElseAsmGen(private val program: PtProgram,
} }
} }
val deref = stmt.condition as? PtPointerDeref
if(deref!=null) {
TODO("ptr deref resulting in bool ${deref.position}")
}
throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}") throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}")
} }

View File

@@ -923,7 +923,7 @@ internal class ProgramAndVarsGen(
else else
"-$$hexnum" "-$$hexnum"
} }
dt.isArray && dt.elementType().isUnsignedWord -> array.map { dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
val number = it.number!!.toInt() val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0') "$" + number.toString(16).padStart(4, '0')
} }

View File

@@ -94,7 +94,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) { if(errors.noErrors()) {
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString } val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
for (variable in sortedList) { for (variable in sortedList) {
if(variable.dt.isIntegerOrBool) { if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
if(zeropage.free.isEmpty()) { if(zeropage.free.isEmpty()) {
break break
} else { } else {

View File

@@ -6,10 +6,11 @@ import prog8.codegen.cpu6502.AsmGen6502Internal
internal enum class TargetStorageKind { internal enum class TargetStorageKind {
VARIABLE, VARIABLE, // non-pointer variable
ARRAY, ARRAY,
MEMORY, MEMORY,
REGISTER, REGISTER,
POINTER, // wherever the pointer variable points to
VOID // assign nothing - used in multi-value assigns for void placeholders VOID // assign nothing - used in multi-value assigns for void placeholders
} }
@@ -32,6 +33,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val array: PtArrayIndexer? = null, val array: PtArrayIndexer? = null,
val memory: PtMemoryByte? = null, val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null, val register: RegisterOrPair? = null,
val pointer: PtPointerDeref? = null,
val origAstTarget: PtAssignTarget? = null val origAstTarget: PtAssignTarget? = null
) )
{ {
@@ -84,6 +86,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
} }
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this) array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this) memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
pointerDeref != null -> return AsmAssignTarget(TargetStorageKind.POINTER, asmgen, type, definingSub, target.position, pointer = pointerDeref, origAstTarget = this)
else -> throw AssemblyError("weird target") else -> throw AssemblyError("weird target")
} }
} }
@@ -146,6 +149,9 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
left isSameAs memory!! left isSameAs memory!!
} }
TargetStorageKind.POINTER -> {
TODO("is pointer deref target same as expression? ${this.position}")
}
TargetStorageKind.REGISTER -> false TargetStorageKind.REGISTER -> false
TargetStorageKind.VOID -> false TargetStorageKind.VOID -> false
} }

View File

@@ -10,10 +10,11 @@ import prog8.codegen.cpu6502.VariableAllocator
internal class AssignmentAsmGen( internal class AssignmentAsmGen(
private val program: PtProgram, private val program: PtProgram,
private val asmgen: AsmGen6502Internal, private val asmgen: AsmGen6502Internal,
private val pointergen: PointerAssignmentsGen,
private val anyExprGen: AnyExprAsmGen, private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator private val allocator: VariableAllocator
) { ) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator) private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, pointergen, allocator)
fun translate(assignment: PtAssignment) { fun translate(assignment: PtAssignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment.target, assignment.definingISub(), asmgen) val target = AsmAssignTarget.fromAstAssignment(assignment.target, assignment.definingISub(), asmgen)
@@ -214,13 +215,14 @@ internal class AssignmentAsmGen(
} }
SourceStorageKind.LITERALNUMBER -> { SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number // simple case: assign a constant number
require(assign.target.datatype.isNumericOrBool) require(assign.target.datatype.isNumericOrBool || (assign.target.datatype.isPointer))
val num = assign.source.number!!.number val num = assign.source.number!!.number
when (assign.target.datatype.base) { when (assign.target.datatype.base) {
BaseDataType.BOOL -> assignConstantByte(assign.target, if(num==0.0) 0 else 1) BaseDataType.BOOL -> assignConstantByte(assign.target, if(num==0.0) 0 else 1)
BaseDataType.UBYTE, BaseDataType.BYTE -> assignConstantByte(assign.target, num.toInt()) BaseDataType.UBYTE, BaseDataType.BYTE -> assignConstantByte(assign.target, num.toInt())
BaseDataType.UWORD, BaseDataType.WORD -> assignConstantWord(assign.target, num.toInt()) BaseDataType.UWORD, BaseDataType.WORD -> assignConstantWord(assign.target, num.toInt())
BaseDataType.FLOAT -> assignConstantFloat(assign.target, num) BaseDataType.FLOAT -> assignConstantFloat(assign.target, num)
BaseDataType.POINTER -> assignConstantWord(assign.target, num.toInt())
else -> throw AssemblyError("weird numval type") else -> throw AssemblyError("weird numval type")
} }
} }
@@ -243,7 +245,8 @@ internal class AssignmentAsmGen(
} }
targetDt.isFloat -> assignVariableFloat(assign.target, variable) targetDt.isFloat -> assignVariableFloat(assign.target, variable)
targetDt.isString -> assignVariableString(assign.target, variable) targetDt.isString -> assignVariableString(assign.target, variable)
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}") targetDt.isPointer -> assignVariableWord(assign.target, variable, assign.source.datatype)
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype} ${assign.position}")
} }
} }
SourceStorageKind.ARRAY -> { SourceStorageKind.ARRAY -> {
@@ -258,7 +261,7 @@ internal class AssignmentAsmGen(
val constIndex = value.index.asConstInteger() val constIndex = value.index.asConstInteger()
if(value.splitWords) { if(value.splitWords) {
require(elementDt.isWord) require(elementDt.isWord || elementDt.isPointer)
if(constIndex!=null) { if(constIndex!=null) {
asmgen.out(" lda ${arrayVarName}_lsb+$constIndex | ldy ${arrayVarName}_msb+$constIndex") asmgen.out(" lda ${arrayVarName}_lsb+$constIndex | ldy ${arrayVarName}_msb+$constIndex")
assignRegisterpairWord(assign.target, RegisterOrPair.AY) assignRegisterpairWord(assign.target, RegisterOrPair.AY)
@@ -480,6 +483,7 @@ internal class AssignmentAsmGen(
} }
} }
is PtIfExpression -> asmgen.assignIfExpression(assign.target, value) is PtIfExpression -> asmgen.assignIfExpression(assign.target, value)
is PtPointerDeref -> pointergen.assignPointerDerefExpression(assign.target, value)
else -> throw AssemblyError("weird assignment value type $value") else -> throw AssemblyError("weird assignment value type $value")
} }
} }
@@ -721,7 +725,7 @@ internal class AssignmentAsmGen(
} }
assignRegisterByte(target, CpuRegister.A, false, false) assignRegisterByte(target, CpuRegister.A, false, false)
} }
target.datatype.isWord -> assignRegisterpairWord(target, register) target.datatype.isWord || target.datatype.isPointer -> assignRegisterpairWord(target, register)
else -> throw AssemblyError("expected byte or word") else -> throw AssemblyError("expected byte or word")
} }
} }
@@ -944,6 +948,7 @@ internal class AssignmentAsmGen(
assignTrue.add(PtNumber.fromBoolean(true, assign.position)) assignTrue.add(PtNumber.fromBoolean(true, assign.position))
} }
} }
TargetStorageKind.POINTER -> TODO("optimized comparison for pointer-deref $expr.position")
TargetStorageKind.REGISTER -> { /* handled earlier */ return true } TargetStorageKind.REGISTER -> { /* handled earlier */ return true }
TargetStorageKind.VOID -> { /* do nothing */ return true } TargetStorageKind.VOID -> { /* do nothing */ return true }
} }
@@ -2475,7 +2480,7 @@ $endLabel""")
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
} }
sourceDt.isSignedWord -> { sourceDt.isSignedWord || sourceDt.isPointer -> {
when(targetDt.base) { when(targetDt.base) {
BaseDataType.BOOL -> { BaseDataType.BOOL -> {
asmgen.out(""" asmgen.out("""
@@ -2488,7 +2493,7 @@ $endLabel""")
BaseDataType.BYTE, BaseDataType.UBYTE -> { BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
} }
BaseDataType.UWORD -> { BaseDataType.UWORD, BaseDataType.POINTER -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
} }
BaseDataType.FLOAT -> { BaseDataType.FLOAT -> {
@@ -2818,6 +2823,7 @@ $endLabel""")
else -> throw AssemblyError("can't load address in a single 8-bit register") else -> throw AssemblyError("can't load address in a single 8-bit register")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignAddressOf(PtrTarget(target), sourceName, msb, arrayDt, arrayIndexExpr)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -2858,7 +2864,7 @@ $endLabel""")
assignRegisterpairWord(target, RegisterOrPair.AY) assignRegisterpairWord(target, RegisterOrPair.AY)
return return
} }
require(sourceDt.isWord || sourceDt.isUnsignedByte || sourceDt.isBool) { "weird source dt for word variable" } require(sourceDt.isWord || sourceDt.isUnsignedByte || sourceDt.isBool || sourceDt.isPointer) { "weird source dt for word variable" }
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
if(sourceDt.isUnsignedByte || sourceDt.isBool) { if(sourceDt.isUnsignedByte || sourceDt.isBool) {
@@ -2961,6 +2967,7 @@ $endLabel""")
} }
} }
} }
TargetStorageKind.POINTER -> pointergen.assignWordVar(PtrTarget(target), sourceName, sourceDt)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -2995,6 +3002,7 @@ $endLabel""")
else if (target.register!! != RegisterOrPair.FAC1) else if (target.register!! != RegisterOrPair.FAC1)
throw AssemblyError("can't assign Fac1 float to another register") throw AssemblyError("can't assign Fac1 float to another register")
} }
TargetStorageKind.POINTER -> pointergen.assignFAC1(PtrTarget(target))
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3032,6 +3040,7 @@ $endLabel""")
else -> throw AssemblyError("can only assign float to Fac1 or 2") else -> throw AssemblyError("can only assign float to Fac1 or 2")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignFloatAY(PtrTarget(target))
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3069,6 +3078,7 @@ $endLabel""")
else -> throw AssemblyError("can only assign float to Fac1 or 2") else -> throw AssemblyError("can only assign float to Fac1 or 2")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignFloatVar(PtrTarget(target), sourceName)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3113,6 +3123,7 @@ $endLabel""")
else -> throw AssemblyError("weird register") else -> throw AssemblyError("weird register")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignByteVar(PtrTarget(target), sourceName)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3493,6 +3504,7 @@ $endLabel""")
} }
} }
} }
TargetStorageKind.POINTER -> pointergen.assignByteReg(PtrTarget(target), register, signed, extendWord)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3529,7 +3541,7 @@ $endLabel""")
} }
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) { internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype.isNumeric || target.datatype.isPassByRef) { require(target.datatype.isNumeric || target.datatype.isPassByRef || target.datatype.isPointer) {
"assign target must be word type ${target.position}" "assign target must be word type ${target.position}"
} }
if(target.datatype.isFloat) if(target.datatype.isFloat)
@@ -3717,6 +3729,7 @@ $endLabel""")
} }
} }
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte") TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
TargetStorageKind.POINTER -> pointergen.assignWordRegister(PtrTarget(target), regs)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3758,6 +3771,7 @@ $endLabel""")
else -> throw AssemblyError("invalid register for word value") else -> throw AssemblyError("invalid register for word value")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignWord(PtrTarget(target), 0)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
@@ -3814,6 +3828,7 @@ $endLabel""")
else -> throw AssemblyError("invalid register for word value") else -> throw AssemblyError("invalid register for word value")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignWord(PtrTarget(target), word)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3856,6 +3871,7 @@ $endLabel""")
} }
else -> throw AssemblyError("weird register") else -> throw AssemblyError("weird register")
} }
TargetStorageKind.POINTER -> pointergen.assignByte(PtrTarget(target), 0)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
@@ -3901,6 +3917,7 @@ $endLabel""")
} }
else -> throw AssemblyError("weird register") else -> throw AssemblyError("weird register")
} }
TargetStorageKind.POINTER -> pointergen.assignByte(PtrTarget(target), byte)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -3944,6 +3961,7 @@ $endLabel""")
else -> throw AssemblyError("can only assign float to Fac1 or 2") else -> throw AssemblyError("can only assign float to Fac1 or 2")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignFloat(PtrTarget(target), 0.0)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} else { } else {
@@ -3982,6 +4000,7 @@ $endLabel""")
else -> throw AssemblyError("can only assign float to Fac1 or 2") else -> throw AssemblyError("can only assign float to Fac1 or 2")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignFloat(PtrTarget(target), float)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -4020,6 +4039,7 @@ $endLabel""")
} }
else -> throw AssemblyError("weird register") else -> throw AssemblyError("weird register")
} }
TargetStorageKind.POINTER -> pointergen.assignByteMemory(PtrTarget(target), address)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} else if (identifier != null) { } else if (identifier != null) {
@@ -4055,6 +4075,7 @@ $endLabel""")
else -> throw AssemblyError("weird register") else -> throw AssemblyError("weird register")
} }
} }
TargetStorageKind.POINTER -> pointergen.assignByteMemory(PtrTarget(target), identifier)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -4242,6 +4263,7 @@ $endLabel""")
val invertOperator = if(assign.target.datatype.isBool) "not" else "~" val invertOperator = if(assign.target.datatype.isBool) "not" else "~"
assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign(invertOperator, assign), scope) assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign(invertOperator, assign), scope)
} }
TargetStorageKind.POINTER -> pointergen.inplaceByteInvert(PtrTarget(target))
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -4266,6 +4288,7 @@ $endLabel""")
} }
} }
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("~", assign), scope) TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("~", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceWordInvert(PtrTarget(target))
else -> throw AssemblyError("weird target") else -> throw AssemblyError("weird target")
} }
} }
@@ -4314,6 +4337,7 @@ $endLabel""")
} }
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that") TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope) TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target), ignoreDatatype, scope)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }
@@ -4373,6 +4397,7 @@ $endLabel""")
} }
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that") TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope) TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target), ignoreDatatype, scope)
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }
} }

View File

@@ -9,6 +9,7 @@ import prog8.codegen.cpu6502.VariableAllocator
internal class AugmentableAssignmentAsmGen(private val program: PtProgram, internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private val assignmentAsmGen: AssignmentAsmGen, private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen6502Internal, private val asmgen: AsmGen6502Internal,
private val ptrgen: PointerAssignmentsGen,
private val allocator: VariableAllocator private val allocator: VariableAllocator
) { ) {
fun translate(assign: AsmAugmentedAssignment, scope: IPtSubroutine?) { fun translate(assign: AsmAugmentedAssignment, scope: IPtSubroutine?) {
@@ -514,6 +515,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} }
} }
} }
TargetStorageKind.POINTER -> ptrgen.inplaceModification(PtrTarget(target), operator, value)
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification") TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
TargetStorageKind.VOID -> { /* do nothing */ } TargetStorageKind.VOID -> { /* do nothing */ }
} }

View File

@@ -0,0 +1,794 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StStruct
import prog8.code.ast.IPtSubroutine
import prog8.code.ast.PtExpression
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtPointerDeref
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
internal class PtrTarget(target: AsmAssignTarget) {
val dt = target.datatype
val pointer = target.pointer!!
val scope = target.scope
val position = target.position
}
internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, private val allocator: VariableAllocator) {
internal fun assignAddressOf(
target: PtrTarget,
sourceName: String,
msb: Boolean,
arrayDt: DataType?,
arrayIndexExpr: PtExpression?
) {
TODO("assign address of to pointer deref ${target.position}")
}
internal fun assignWordVar(target: PtrTarget, sourceName: String, sourceDt: DataType) {
val zpPtrVar = deref(target.pointer)
storeIndirectWordVar(sourceName, sourceDt, zpPtrVar)
}
internal fun assignFAC1(target: PtrTarget) {
TODO("assign FAC1 float to pointer deref ${target.position}")
}
internal fun assignFloatAY(target: PtrTarget) {
TODO("assign float from AY to pointer deref ${target.position}")
}
internal fun assignFloatVar(target: PtrTarget, sourceName: String) {
val zpPtrVar = deref(target.pointer)
storeIndirectFloatVar(sourceName, zpPtrVar)
}
internal fun assignByteVar(target: PtrTarget, sourceName: String) {
val zpPtrVar = deref(target.pointer)
storeIndirectByteVar(sourceName, zpPtrVar)
}
internal fun assignByteReg(target: PtrTarget, register: CpuRegister, signed: Boolean, extendWord: Boolean) {
TODO("assign register byte to pointer deref ${target.position}")
}
internal fun assignWordRegister(target: PtrTarget, regs: RegisterOrPair) {
TODO("assign register pair word to pointer deref ${target.position}")
}
internal fun assignWord(target: PtrTarget, word: Int) {
val zpPtrVar = deref(target.pointer)
storeIndirectWord(word, zpPtrVar)
}
internal fun assignByte(target: PtrTarget, byte: Int) {
val zpPtrVar = deref(target.pointer)
storeIndirectByte(byte, zpPtrVar)
}
internal fun assignFloat(target: PtrTarget, float: Double) {
val zpPtrVar = deref(target.pointer)
storeIndirectFloat(float, zpPtrVar)
}
internal fun assignByteMemory(target: PtrTarget, address: UInt) {
TODO("assign memory byte to pointer deref ${target.position}")
}
internal fun assignByteMemory(target: PtrTarget, identifier: PtIdentifier) {
TODO("assign memory byte to pointer deref ${target.position}")
}
internal fun inplaceByteInvert(target: PtrTarget) {
TODO("inplace byte invert pointer deref ${target.position}")
}
internal fun inplaceWordInvert(target: PtrTarget) {
TODO("inplace word invert pointer deref ${target.position}")
}
internal fun inplaceByteNegate(target: PtrTarget, ignoreDatatype: Boolean, scope: IPtSubroutine?) {
TODO("inplace byte negate to pointer deref ${target.position}")
}
internal fun inplaceWordNegate(target: PtrTarget, ignoreDatatype: Boolean, scope: IPtSubroutine?) {
TODO("inplace word negate pointer deref ${target.position}")
}
private fun deref(pointer: PtPointerDeref): String {
// walk the pointer deref chain and leaves the final pointer value in a ZP var
// this will often be the temp var P8ZP_SCRATCH_W1 but can also be the original pointer variable if it is already in zeropage
if(pointer.chain.isEmpty()) {
// TODO: do we have to look at derefLast ?
if(allocator.isZpVar(pointer.startpointer.name))
return pointer.startpointer.name
else {
// have to copy it to temp zp var
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_W1", DataType.UWORD)
return "P8ZP_SCRATCH_W1"
}
}
// walk pointer chain, calculate pointer address using P8ZP_SCRATCH_W1
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_W1", DataType.UWORD)
fun addFieldOffset(fieldoffset: UInt) {
if(fieldoffset==0u)
return
require(fieldoffset<=0xffu)
asmgen.out("""
lda P8ZP_SCRATCH_W1
clc
adc #$fieldoffset
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+""")
}
fun updatePointer() {
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W1),y
tax
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_W1""")
}
// traverse deref chain
var struct: StStruct? = null
if(pointer.startpointer.type.subType!=null)
struct = pointer.startpointer.type.subType as StStruct
for(deref in pointer.chain.dropLast(1)) {
val fieldinfo = struct!!.getField(deref, asmgen.program.memsizer)
val fieldoffset = fieldinfo.second
struct = fieldinfo.first.subType as StStruct
// get new pointer from field (P8ZP_SCRATCH_W1 += fieldoffset, read pointer from new location)
addFieldOffset(fieldoffset)
updatePointer()
}
val field = pointer.chain.last()
val fieldinfo = struct!!.getField(field, asmgen.program.memsizer)
addFieldOffset(fieldinfo.second)
if(pointer.derefLast) {
require(fieldinfo.first.isPointer)
updatePointer()
}
return "P8ZP_SCRATCH_W1"
}
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
val zpPtrVar = deref(value)
if(value.type.isByteOrBool) {
loadIndirectByte(zpPtrVar)
asmgen.assignRegister(RegisterOrPair.A, target)
}
else if(value.type.isWord || value.type.isPointer) {
loadIndirectWord(zpPtrVar)
asmgen.assignRegister(RegisterOrPair.AY, target)
}
else if(value.type.isFloat) {
loadIndirectFloat(zpPtrVar)
asmgen.assignRegister(RegisterOrPair.FAC1, target)
}
else if(value.type.isLong)
TODO("load long")
else
throw AssemblyError("weird dt ${value.type} in pointer deref assignment ${target.position}")
}
private fun loadIndirectFloat(zpPtrVar: String) {
// loads float pointed to by the ptrvar into FAC1
asmgen.out("""
lda $zpPtrVar
ldy $zpPtrVar+1
jsr floats.MOVFM
""")
}
private fun loadIndirectByte(zpPtrVar: String) {
// loads byte pointed to by the ptrvar into A
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda ($zpPtrVar)")
else
asmgen.out(" ldy #0 | lda ($zpPtrVar),y")
}
private fun loadIndirectWord(zpPtrVar: String) {
// loads word pointed to by the ptr var into AY
asmgen.out("""
ldy #0
lda ($zpPtrVar),y
tax
iny
lda ($zpPtrVar),y
tay
txa""")
}
private fun storeIndirectByte(byte: Int, zpPtrVar: String) {
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda #$byte | sta ($zpPtrVar)")
} else {
if (byte == 0) {
asmgen.out(" lda #0 | tay | sta ($zpPtrVar),y")
} else {
asmgen.out(" lda #$byte | ldy #0 | sta ($zpPtrVar),y")
}
}
}
private fun storeIndirectByteVar(varname: String, zpPtrVar: String) {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda $varname | sta ($zpPtrVar)")
else
asmgen.out(" lda $varname | ldy #0 | sta ($zpPtrVar),y")
}
private fun storeIndirectWord(word: Int, zpPtrVar: String) {
if(word==0) {
asmgen.out("""
lda #0
tay
sta ($zpPtrVar),y
iny
sta ($zpPtrVar),y""")
} else {
asmgen.out("""
lda #<$word
ldy #0
sta ($zpPtrVar),y
lda #>$word
iny
sta ($zpPtrVar),y""")
}
}
private fun storeIndirectWordVar(varname: String, sourceDt: DataType, zpPtrVar: String) {
if(sourceDt.isByteOrBool) TODO("implement byte/bool to word pointer assignment")
asmgen.out("""
lda $varname
ldy #0
sta ($zpPtrVar),y
lda $varname+1
iny
sta ($zpPtrVar),y""")
}
private fun storeIndirectFloat(float: Double, zpPtrVar: String) {
val floatConst = allocator.getFloatAsmConst(float)
asmgen.out("""
lda #<$floatConst
ldy #>$floatConst
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda $zpPtrVar
ldy $zpPtrVar+1
jsr floats.copy_float2""")
}
private fun storeIndirectFloatVar(varname: String, zpPtrVar: String) {
asmgen.out("""
lda #<$varname
ldy #>$varname+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $zpPtrVar
ldy $zpPtrVar+1
jsr floats.copy_float""")
}
fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
when (operator) {
"+" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore
if(target.dt.isWord) inplaceWordAdd(target, value)
else if(target.dt.isFloat) inplaceFloatAddMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.position}")
}
"-" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore
if(target.dt.isWord) inplaceWordSub(target, value)
else if(target.dt.isFloat) inplaceFloatSubDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
}
"*" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore
if(target.dt.isWord) inplaceWordMul(target, value)
else if(target.dt.isFloat) inplaceFloatAddMul(target, "FMULT", value)
else throw AssemblyError("weird dt ${target.position}")
}
"/" -> {
if(target.dt.isWord) inplaceWordDiv(target, value)
else if(target.dt.isFloat) inplaceFloatSubDiv(target, "FDIV", value)
else throw AssemblyError("weird dt ${target.position}")
}
"%" -> TODO("inplace ptr %")
"<<" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore
if(target.dt.isWord) inplaceWordShiftLeft(target, value)
else throw AssemblyError("weird dt ${target.position}")
}
">>" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore
if(target.dt.isWord) inplaceWordShiftRight(target, value)
else throw AssemblyError("weird dt ${target.position}")
}
"&", "and" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
TODO("inplace ptr &")
}
"|", "or" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
TODO("inplace ptr |")
}
"^", "xor" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
if(target.dt.isByteOrBool) inplaceByteXor(target, value)
else if(target.dt.isWord) inplaceWordXor(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"==" -> TODO("inplace ptr ==")
"!=" -> TODO("inplace ptr !=")
"<" -> TODO("inplace ptr <")
"<=" -> TODO("inplace ptr <=")
">" -> TODO("inplace ptr >")
">=" -> TODO("inplace ptr >=")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
private fun inplaceWordShiftRight(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
if(target.dt.isSigned)
TODO("signed word shift rigth ${target.position} $value")
fun shift1unsigned() {
asmgen.out("""
ldy #1
lda ($ptrZpVar),y
lsr a
sta ($ptrZpVar),y
dey
lda ($ptrZpVar),y
ror a
sta ($ptrZpVar),y""")
}
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number==1) {
shift1unsigned()
} else if(number>1) {
asmgen.out(" ldx #$number")
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
TODO("<< variable")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX, false)
TODO("<< expression")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_W1"))
require(register.isWord())
TODO("<< register")
}
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceWordShiftLeft(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
fun shift1() {
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
asl a
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
rol a
sta ($ptrZpVar),y""")
}
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number==1) {
shift1()
} else if(number>1) {
asmgen.out(" ldx #$number")
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
TODO("<< variable")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX, false)
TODO("<< expression")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_W1"))
require(register.isWord())
TODO("<< register")
}
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceWordAdd(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
clc
adc #<$number
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
adc #>$number
sta ($ptrZpVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
clc
adc $varname
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
adc $varname+1
sta ($ptrZpVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX, false)
asmgen.out("""
ldy #0
clc
adc ($ptrZpVar),y
sta ($ptrZpVar),y
iny
txa
adc ($ptrZpVar),y
sta ($ptrZpVar),y""")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_W1"))
require(register.isWord())
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
clc
adc P8ZP_SCRATCH_W1
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
adc P8ZP_SCRATCH_W1+1
sta ($ptrZpVar),y""")
}
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceFloatAddMul(target: PtrTarget, floatoperation: String, value: AsmAssignSource) {
require(floatoperation=="FADD" || floatoperation=="FMULT")
val ptrZpVar = deref(target.pointer)
asmgen.out("""
lda $ptrZpVar
ldy $ptrZpVar+1
jsr floats.MOVFM""")
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val floatConst = allocator.getFloatAsmConst(value.number!!.number)
asmgen.out("""
lda #<$floatConst
ldy #>$floatConst
jsr floats.$floatoperation
ldx $ptrZpVar
ldy $ptrZpVar+1
jsr floats.MOVMF""")
}
SourceStorageKind.VARIABLE -> TODO("variable + * float")
SourceStorageKind.EXPRESSION -> TODO("expression + * float")
SourceStorageKind.REGISTER -> TODO("register + * float")
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceWordSub(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
sec
sbc #<$number
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
sbc #>$number
sta ($ptrZpVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
sec
sbc $varname
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
sbc $varname+1
sta ($ptrZpVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX, false)
asmgen.out("""
ldy #0
sec
sbc ($ptrZpVar),y
sta ($ptrZpVar),y
iny
txa
sbc ($ptrZpVar),y
sta ($ptrZpVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register - word")
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceWordMul(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
fun multiply() {
// on entry here: number placed in routine argument variable
loadIndirectWord(ptrZpVar)
asmgen.out("""
jsr prog8_math.multiply_words
tax
tya
ldy #1
sta ($ptrZpVar),y
dey
txa
sta ($ptrZpVar),y""")
}
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number in powersOfTwoInt)
throw AssemblyError("multiply by power of two should have been a shift $value.position")
asmgen.out("""
lda #<$number
ldy #>$number
sta prog8_math.multiply_words.multiplier
sty prog8_math.multiply_words.multiplier+1""")
multiply()
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
lda $varname
ldy $varname+1
sta prog8_math.multiply_words.multiplier
sty prog8_math.multiply_words.multiplier+1""")
multiply()
}
SourceStorageKind.REGISTER -> {
val register = value.register!!
require(register.isWord())
val multiplyArg = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = " prog8_math.multiply_words.multiplier")
asmgen.assignRegister(register, multiplyArg)
multiply()
}
SourceStorageKind.EXPRESSION -> TODO("ptr * expr (word)")
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceWordDiv(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
fun divide(signed: Boolean) {
// on entry here: number placed in P8ZP_SCRATCH_W1, divisor placed in AY
if(signed) asmgen.out("jsr prog8_math.divmod_w_asm")
else asmgen.out("jsr prog8_math.divmod_uw_asm")
asmgen.out("""
tax
tya
ldy #1
sta ($ptrZpVar),y
dey
txa
sta ($ptrZpVar),y""")
}
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number in powersOfTwoInt)
throw AssemblyError("divide by power of two should have been a shift $value.position")
loadIndirectWord(ptrZpVar)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$number
ldy #>$number""")
divide(target.dt.isSigned)
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
TODO("inplace variable word divide")
}
SourceStorageKind.REGISTER -> {
val register = value.register!!
require(register.isWord())
TODO("inplace register word divide")
}
SourceStorageKind.EXPRESSION -> TODO("ptr / expr (word)")
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceFloatSubDiv(target: PtrTarget, floatoperation: String, value: AsmAssignSource) {
require(floatoperation=="FSUB" || floatoperation=="FDIV")
val ptrZpVar = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val floatConst = allocator.getFloatAsmConst(value.number!!.number)
asmgen.out("""
lda #<$floatConst
ldy #>$floatConst
jsr floats.MOVFM
lda $ptrZpVar
ldy $ptrZpVar+1
jsr floats.$floatoperation
ldx $ptrZpVar
ldy $ptrZpVar+1
jsr floats.MOVMF""")
}
SourceStorageKind.VARIABLE -> TODO("variable - / float")
SourceStorageKind.EXPRESSION -> TODO("expression - / float")
SourceStorageKind.REGISTER -> TODO("register - / float")
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceByteXor(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($ptrZpVar)
eor #$number
sta ($ptrZpVar)""")
else
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
eor #$number
sta ($ptrZpVar),y""")
}
SourceStorageKind.VARIABLE -> {
val varname = value.asmVarname
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($ptrZpVar)
eor $varname
sta ($ptrZpVar)""")
else
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
eor $varname
sta ($ptrZpVar),y""")
}
SourceStorageKind.EXPRESSION -> {
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A, false)
asmgen.out("""
ldy #0
eor ($ptrZpVar),y
sta ($ptrZpVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register ^ byte")
else -> throw AssemblyError("weird source value ${value}")
}
}
private fun inplaceWordXor(target: PtrTarget, value: AsmAssignSource) {
val ptrZpVar = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
eor #<$number
sta ($ptrZpVar),y
iny
lda ($ptrZpVar),y
eor #>$number
sta ($ptrZpVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #0
lda ($ptrZpVar),y
eor $varname
sta ($ptrZpVar),y
lda ($ptrZpVar),y
eor $varname+1
sta ($ptrZpVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX, false)
asmgen.out("""
ldy #0
eor ($ptrZpVar),y
sta ($ptrZpVar),y
iny
txa
eor ($ptrZpVar),y
sta ($ptrZpVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register ^ word")
else -> throw AssemblyError("weird source value ${value}")
}
}
}

View File

@@ -149,6 +149,29 @@ copy_float .proc
rts rts
.pend .pend
copy_float2 .proc
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W2,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
rts
.pend
inc_var_f .proc inc_var_f .proc
; -- add 1 to float pointed to by A/Y ; -- add 1 to float pointed to by A/Y
; clobbers X ; clobbers X

View File

@@ -2154,9 +2154,14 @@ internal class AstChecker(private val program: Program,
} }
} }
} }
if (deref.inferType(program).isUnknown) if (deref.inferType(program).isUnknown) {
val symbol = deref.definingScope.lookup(deref.chain.take(1))
if(symbol==null)
errors.err("undefined symbol: ${deref.chain[0]}", deref.position)
else
errors.err("unable to determine type of dereferenced pointer expression", deref.position) errors.err("unable to determine type of dereferenced pointer expression", deref.position)
} }
}
private fun checkLongType(expression: Expression) { private fun checkLongType(expression: Expression) {
if(expression.inferType(program) issimpletype BaseDataType.LONG) { if(expression.inferType(program) issimpletype BaseDataType.LONG) {

View File

@@ -15,29 +15,6 @@ internal fun postprocessSimplifiedAst(
) { ) {
processDefers(program, st, errors) processDefers(program, st, errors)
processSubtypesIntoStReferences(program, st) processSubtypesIntoStReferences(program, st)
if(option.compTarget.cpu!=CpuType.VIRTUAL)
checkForPointerTypesOn6502(program, errors) // TODO remove this once the 6502 codegen can deal with pointer types and structs
}
private fun checkForPointerTypesOn6502(program: PtProgram, errors: IErrorReporter) {
fun check(node: PtNode) {
when(node) {
//is PtAddressOf -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtAssignTarget -> if(!node.void && node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
//is PtBinaryExpression -> if(node.left.type.isPointer || node.right.type.isPointer) errors.err("cannot do pointer arithmetic yet on 6502 target $node", node.position)
is PtIdentifier -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtPointerDeref -> errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtPrefix -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtTypeCast -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtStructDecl -> errors.err("cannot use struct type yet on 6502 target $node", node.position)
is PtSubroutineParameter -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtVariable -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtSubSignature -> if(node.returns.any{it.isPointer}) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
else -> {}
}
}
walkAst(program) { node, _ -> check(node) }
} }

View File

@@ -16,6 +16,7 @@ import prog8.code.core.DataType
import prog8.code.core.IMemSizer import prog8.code.core.IMemSizer
import prog8.code.core.ISubType import prog8.code.core.ISubType
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget import prog8.code.target.VMTarget
import prog8.vm.VmRunner import prog8.vm.VmRunner
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
@@ -29,7 +30,7 @@ class TestPointers: FunSpec( {
test("basic pointers") { test("basic pointers") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
^^bool g_bp ^^bool g_bp
@@ -173,12 +174,13 @@ other {
} }
""" """
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("struct pointers") { test("struct pointers") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
struct Node { struct Node {
@@ -218,7 +220,8 @@ main {
} }
""" """
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("pointer walking using simple dot notation should be equivalent to explicit dereference chain") { test("pointer walking using simple dot notation should be equivalent to explicit dereference chain") {
@@ -292,7 +295,7 @@ main {
test("word size pointer indexing on pointers") { test("word size pointer indexing on pointers") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
@@ -416,7 +419,8 @@ thing {
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("pointers in subroutine parameters") { test("pointers in subroutine parameters") {
@@ -455,7 +459,8 @@ thing {
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("str or ubyte array params or return type replaced by pointer to ubyte") { test("str or ubyte array params or return type replaced by pointer to ubyte") {
@@ -476,7 +481,6 @@ main {
} }
}""" }"""
// TODO check this for C64 target too
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!! val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
val main = result.compilerAst.allBlocks.first {it.name=="main"} val main = result.compilerAst.allBlocks.first {it.name=="main"}
val test1 = main.statements[1] as Subroutine val test1 = main.statements[1] as Subroutine
@@ -514,8 +518,10 @@ thing {
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(VMTarget(), true, src, outputDir) shouldNotBe null compileText(VMTarget(), true, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(C64Target(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), true, src, outputDir) shouldNotBe null compileText(C64Target(), true, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), true, src, outputDir) shouldNotBe null
} }
test("creating instances for unused vars should all be removed") { test("creating instances for unused vars should all be removed") {
@@ -549,7 +555,8 @@ thing {
start.children[0] shouldBe instanceOf<PtSubSignature>() start.children[0] shouldBe instanceOf<PtSubSignature>()
start.children[1] shouldBe instanceOf<PtReturn>() start.children[1] shouldBe instanceOf<PtReturn>()
} }
// TODO compileText(C64Target(), true, src, outputDir) shouldNotBe null compileText(C64Target(), true, src, outputDir) shouldNotBe null
compileText(Cx16Target(), true, src, outputDir) shouldNotBe null
} }
test("creating instances should have correct number of args") { test("creating instances should have correct number of args") {
@@ -656,7 +663,7 @@ main {
test("untyped and typed address-of operators") { test("untyped and typed address-of operators") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
sub start() { sub start() {
@@ -666,7 +673,8 @@ main {
} }
}""" }"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!! compileText(C64Target(), false, src, outputDir) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir)!!
val st = result.codegenAst!!.entrypoint()!!.children val st = result.codegenAst!!.entrypoint()!!.children
st.size shouldBe 6 st.size shouldBe 6
val r0v = (st[3] as PtAssignment).value as PtBinaryExpression val r0v = (st[3] as PtAssignment).value as PtBinaryExpression
@@ -696,7 +704,7 @@ main {
test("address-of struct fields") { test("address-of struct fields") {
val src=""" val src="""
%option enable_floats %import floats
%import textio %import textio
main { main {
@@ -723,6 +731,8 @@ main {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("address-of pointer arithmetic on alias") { test("address-of pointer arithmetic on alias") {
@@ -740,6 +750,7 @@ main {
cx16.r4 = &curframe + index cx16.r4 = &curframe + index
} }
}""" }"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir)!! val result = compileText(VMTarget(), false, src, outputDir)!!
val st = result.compilerAst.entrypoint.statements val st = result.compilerAst.entrypoint.statements
st.size shouldBe 9 st.size shouldBe 9
@@ -776,6 +787,8 @@ main {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("uword as pointer versus pointer to uword difference") { test("uword as pointer versus pointer to uword difference") {
@@ -790,6 +803,7 @@ main {
} }
}""" }"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir)!! val result = compileText(VMTarget(), false, src, outputDir)!!
val st = result.codegenAst!!.entrypoint()!!.children val st = result.codegenAst!!.entrypoint()!!.children
st.size shouldBe 8 st.size shouldBe 8
@@ -863,7 +877,7 @@ main {
test("indexing pointers with index 0 is just a direct pointer dereference") { test("indexing pointers with index 0 is just a direct pointer dereference") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
struct List { struct List {
^^uword s ^^uword s
@@ -977,6 +991,8 @@ main {
} }
}""" }"""
compileText(VMTarget(), true, src, outputDir) shouldNotBe null compileText(VMTarget(), true, src, outputDir) shouldNotBe null
compileText(C64Target(), true, src, outputDir) shouldNotBe null
compileText(Cx16Target(), true, src, outputDir) shouldNotBe null
} }
test("global struct var deref type") { test("global struct var deref type") {
@@ -998,6 +1014,8 @@ main {
}""" }"""
compileText(VMTarget(), true, src, outputDir) shouldNotBe null compileText(VMTarget(), true, src, outputDir) shouldNotBe null
compileText(C64Target(), true, src, outputDir) shouldNotBe null
compileText(Cx16Target(), true, src, outputDir) shouldNotBe null
} }
test("local struct var deref type") { test("local struct var deref type") {
@@ -1018,11 +1036,13 @@ main {
}""" }"""
compileText(VMTarget(), true, src, outputDir) shouldNotBe null compileText(VMTarget(), true, src, outputDir) shouldNotBe null
compileText(C64Target(), true, src, outputDir) shouldNotBe null
compileText(Cx16Target(), true, src, outputDir) shouldNotBe null
} }
test("assigning pointer dereferences via memcopy") { test("assigning pointer dereferences via memcopy") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
sub start() { sub start() {
@@ -1045,6 +1065,7 @@ main {
} }
}""" }"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir)!! val result = compileText(VMTarget(), false, src, outputDir)!!
val st = result.compilerAst.entrypoint.statements val st = result.compilerAst.entrypoint.statements
st.size shouldBe 10 st.size shouldBe 10
@@ -1053,7 +1074,7 @@ main {
test("assigning pointer dereferences should be same type") { test("assigning pointer dereferences should be same type") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
sub start() { sub start() {
@@ -1110,11 +1131,13 @@ other {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("a.b.c[i]^^ as expression where pointer is primitive type") { test("a.b.c[i]^^ as expression where pointer is primitive type") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
sub start() { sub start() {
@@ -1140,6 +1163,8 @@ other {
} }
""" """
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("a.b.c[i]^^.value = X where pointer is struct gives good error message") { test("a.b.c[i]^^.value = X where pointer is struct gives good error message") {
@@ -1222,7 +1247,7 @@ other {
test("a.b.c[i]^^ as assignment target where pointer is primitive type") { test("a.b.c[i]^^ as assignment target where pointer is primitive type") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
sub start() { sub start() {
@@ -1249,6 +1274,8 @@ other {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("passing arrays to subroutines via typed pointer parameters") { test("passing arrays to subroutines via typed pointer parameters") {
@@ -1289,11 +1316,13 @@ main {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("array of pointers as subroutine param are all passed as ^^ubyte because of split word arrays") { test("array of pointers as subroutine param are all passed as ^^ubyte because of split word arrays") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
@@ -1320,6 +1349,8 @@ main {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("hoist variable decl and initializer correctly in case of pointer type variable as well") { test("hoist variable decl and initializer correctly in case of pointer type variable as well") {
@@ -1343,6 +1374,7 @@ main {
} }
} }
}""" }"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir)!! val result = compileText(VMTarget(), false, src, outputDir)!!
val st = result.compilerAst.entrypoint.statements val st = result.compilerAst.entrypoint.statements
st.size shouldBe 6 st.size shouldBe 6
@@ -1370,6 +1402,8 @@ main {
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("type error for invalid bool field initializer") { test("type error for invalid bool field initializer") {
@@ -1453,6 +1487,8 @@ db {
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("str can be used without explicit cast where ^^ubyte is expected") { test("str can be used without explicit cast where ^^ubyte is expected") {
@@ -1481,7 +1517,7 @@ main {
test("initializing arrays of pointers") { test("initializing arrays of pointers") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
@@ -1505,6 +1541,8 @@ main {
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("array indexing a pointer and a pointer array both work") { test("array indexing a pointer and a pointer array both work") {
@@ -1523,6 +1561,8 @@ main {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("passing nosplit array of structpointers to a subroutine in various forms should be param type ptr to struct") { test("passing nosplit array of structpointers to a subroutine in various forms should be param type ptr to struct") {
@@ -1574,6 +1614,7 @@ main {
} }
}""" }"""
val errors=ErrorReporterForTests(keepMessagesAfterReporting = true) val errors=ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), false, src, outputDir, errors=errors) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir, errors=errors) val result = compileText(VMTarget(), false, src, outputDir, errors=errors)
errors.errors.size shouldBe 0 errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0 errors.warnings.size shouldBe 0
@@ -1639,6 +1680,8 @@ main {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("boolean field in if statement condition") { test("boolean field in if statement condition") {
@@ -1658,6 +1701,8 @@ main {
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
test("^^str is not valid") { test("^^str is not valid") {
@@ -1742,7 +1787,7 @@ other {
test("float ptr inplace operations") { test("float ptr inplace operations") {
val src=""" val src="""
%option enable_floats %import floats
main { main {
^^float g_floats ^^float g_floats
@@ -1795,8 +1840,8 @@ other {
} }
}""" }"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null compileText(VMTarget(), false, src, outputDir) shouldNotBe null
//compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(C64Target(), false, src, outputDir) shouldNotBe null
//compileText(Cx16Target(), false, src, outputDir) shouldNotBe null compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
} }
}) })

View File

@@ -9,7 +9,7 @@ Structs and Pointers
The 6502 cpu lacks some features (addressing modes, registers) to make pointers work efficiently. The 6502 cpu lacks some features (addressing modes, registers) to make pointers work efficiently.
Also it requires that pointer variables have to be in zero page, or copied to a temporary zero page variable, Also it requires that pointer variables have to be in zero page, or copied to a temporary zero page variable,
before they can even be used as a pointer. This means that pointer operations in prog8 compile before they can even be used as a pointer. This means that pointer operations in prog8 compile
to rather inefficient assembly code most of the time, when compared to direct array access or regular variables. to rather large and inefficient assembly code most of the time, when compared to direct array access or regular variables.
At least try to place heavily used pointer variables in zero page using ``@requirezp`` on their declaration, At least try to place heavily used pointer variables in zero page using ``@requirezp`` on their declaration,
if zero page space allows. if zero page space allows.

View File

@@ -2,164 +2,13 @@ TODO
==== ====
STRUCTS and TYPED POINTERS STRUCTS and TYPED POINTERS (6502 codegen specific)
-------------------------- --------------------------------------------------
'DONE' means working in the 'virtual' compiler target... (no 6502 codegen has been touched yet)
- DONE: add ast type check for assignments to struct fields; node_ptr.nextnode = enemy_ptr should error
- DONE: declare struct as a separate entity so you can then declare multiple variables (pointers) of the same struct type. Like usual.
- DONE: struct is a 'packed' struct, fields are placed in order of declaration. This guarantees exact size and place of the fields
- DONE: structs only supported as a reference type (uword pointer). This removes a lot of the problems related to introducing a variable length value type.
- DONE: need to introduce typed pointer datatype in prog8 to allow this to make any sense. Syntax to declare a pointer type: ^^datatype (double hat to avoid parsing confusion with the eor operator)
- DONE: initially only a pointer-to-struct should actually work, pointer-to-other-type is possible but that can come later.
- DONE: a struct can contain only numeric type fields (byte,word,float) or str fields (translated into ^^ubyte) or other pointer fields. No nested structs, no arrays.
- DONE: max 1 page of memory total size to allow regular register indexing
- DONE: assigning ptrs of different types is only allowed via a cast as usual. For simple address (uword) assignments, no cast is needed (but allowed)
- DONE: how to dereference a pointer? Pascal does it like this: ptr^ But this conflicts with the existing eor operator so we now use ptr^^^ (double hat)
- DONE: dereferencing a pointer to struct could look like Pascal's ptr^.field as well, but the ^ is actually redundant here; compiler already knows it's a pointer type.
Note that actually dereferencing a pointer to a struct as an explicit operation, conflicts with the third axiom on this list (structs only as reference types) so it can only be done for basic types?
So... setting struct fields can simply be ``structvar.field = 42`` and reading them ``a = structvar.field``
- DONE: you should be able to get the address of an individual field: ``&structpointer.field``
- DONE: teach sizeof() how to calculate struct sizes (need unit test + doc)
- DONE: sizeof(ptr^^) works
- DONE: implicit cast of pointer to uword in conditional expressions
- DONE: subroutine parameters and return values should be able to accept pointers as well now
- DONE (for basic types only): allow array syntax on pointers too: ptr[2] means ptr+sizeof()*2, ptr[0] just means ptr^^ .
- DONE (?) allow array syntax on pointers to structs too, but what type will ptr[2] have? And it will require ptr[2].field to work as well now. Actually that will be the only thing to work for now.
- DONE: allow multi-field declarations in structs
- DONE: static initialization of structs. It behaves like arrays; it won't reset to the original value when program is restarted, so beware.
Syntax: ^^Node ptr = Node(1,2,3,4) statically allocates a Node with fields set to 1,2,3,4 and puts the address in ptr.
Node() without arguments allocates a node in BSS variable space instead that gets zeroed out at startup.
(Internally this gets translated into a structalloc(1,2,3,4) builtin function call that has a pointer to the struct as its return type)
- DONE: pointer arrays are split-words only, enforce this (variable dt + initializer array dt)
- DONE: make an error message for all pointer expressions (prefixed, binary) so we can start implementing the ones we need one by one.
- DONE: start by making ptr.value++ work , and ptr.value = ptr.value+20, and ptr.value = cx16.r0L+20+ptr.value Likewise for subtraction. DON'T FORGET C POINTER SEMANTICS. Other operators are nonsensical for ptr arith
- DONE: support @dirty on pointer vars -> uninitialized pointer placed in BSS_noclear segment
- DONE: support comparison operators on pointers
- DONE: implement augmented assignment on pointer dereference
- DONE: pointer types in subroutine signatures (both normal and asm-subs, parameters and return values)
- DONE: arrays of structs? No -> Just an array of uword pointers to said structs.
- DONE: what about pointers to subroutines? should these be typed as well now? Probably not, just stick with UWORD untyped pointer to avoid needless complexity.
- DONE: implement inplace logical and & or, with short-cirtuit, on dereferenced pointer
- DONE: existing ARRAY type remains unchanged (it doesn't become a typed pointer) so we can keep doing register-indexed LDA array,Y addressing directly on them.
- DONE: passing STR to a subroutine: parameter type becomes ^^UBYTE (rather than UWORD) (we still lose the bounds check)
- DONE: passing ARRAY to a subroutine: parameter type becomes ^^ElementDt (rather than UWORD) (we still lose the bounds check)
- DONE: @(ptr) complains that ptr is not uword when ptr is ^^ubyte (should be allowed)
- DONE: pointer[0] should be replaced with @(pointer) if pointer is ^^ubyte, so these are now all identical: ptr[0], ptr^^, @(ptr) if ptr is ^^ubyte
- DONE: STR should be asssignment compatible with UBYTE^^ but local scoped STR should still be accessed directly using LDA str,Y instead of through the pointer, like arrays.
- DONE: disallow ^^str
- DONE: allow return ubyte/uword when pointer type is expected as return value type
- DONE: fix _msb/_lsb storage of the split-words pointer-arrays
- DONE: what about static initialization of an array of struct pointers? -> impossible right now because the pointer values are not constants.
- DONE: make typeForAddressOf() be even more specific about the typed pointers it returns for the address-of operator.
- DONE: existing '&' address-of still returns untyped uword (for backward compatibility). New '&&' operator returns typed pointer.
- DONE: allow list1^^ = list2^^ (value wise assignment of List structures) by replacing it with a sys.memcopy(list2, list1, sizeof(List)) call.
- DONE: allow a.b.ptr[i].value (equiv to a.b.ptr[i]^^.value) expressions (assignment target doesn't parse yet, see below)
- DONE: check passing arrays to typed ptr sub-parameters. NOTE: word array can only be a @nosplit array if the parameter type is ^^word, because the words need to be sequential in memory there
- DONE: allow str assign to ^^ubyte without cast (take address)
- DONE: added peekbool() and pokebool() and pokebowl() boolean peek and poke, the latter is equivalent to pokebool()
- DONE: fixed support for (expression) array index dereferencing "array[2]^^" where array contains pointers to primitives: replace with peek()
- DONE: fixed support for (assigntarget) array index dereferencing "array[2]^^" where array contains pointers to primitives: replace with poke()
- DONE: replace str or ubyte[] param and returnvalue type into ^^ubyte rather than uword
- DONE: allow sizeof(^^type) to return the size of a pointer
- TODO: allow initializing a pointer array with initialized structs: ^^Node[] nodes = [ Node(), Node(), Node() ] (fix missing variable error) (update sorting example)
- try to add support for array index dereferencing as assign target "array[2]^^.value = 99" where array is struct pointers (currently a 'no support' error)
- try to add support for array index dereferencing as assign target "array[2].value = 99" where array is struct pointers (currently a parser error)
- try to fix parse error l1^^.s[0] = 4242 (equivalent to l1.s[0]=4242 , which does parse correctly)
- perhaps add ?. null-propagation operator (for expression and assignment)?
- 6502 codegen: make all/most of the TestPointers unit tests also run on 6502 target
- 6502 codegen: remove checks in checkForPointerTypesOn6502()
- 6502 codegen should warn about writing to initialized struct instances when using romable code, like with arrays "can only be used as read-only in ROMable code" - 6502 codegen should warn about writing to initialized struct instances when using romable code, like with arrays "can only be used as read-only in ROMable code"
- 6502 asm symbol name prefixing should work for dereferences too. - 6502 asm symbol name prefixing should work for dereferences too.
- 6502 statementreorderer: fix todo for str -> ^^ubyte instead of uword - 6502 statementreorderer: fix todo for str -> ^^ubyte instead of uword
- update structpointers.rst docs with 6502 specific things? - update structpointers.rst docs with 6502 specific things?
- scan through 6502 library modules to change untyped uword pointers to typed pointers - scan through 6502 library modules to change untyped uword pointers to typed pointers
- scan through 6502 examples to change untyped uword pointers to typed pointers - scan through 6502 examples to change untyped uword pointers to typed pointers
- support for typed function pointers? (&routine could be typed by default as well then)
- support @nosplit pointer arrays?
- support pointer to pointer?
- really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
- (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field)
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- %breakpoint after an assignment is parsed as part of the expression (x % breakpoint), that should not happen
- when a complete block is removed because unused, suppress all info messages about everything in the block being removed
- 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?
- 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?
- add float support to the configurable compiler targets
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
- Improve register load order in subroutine call args assignments:
in certain situations (need examples!), the "wrong" order of evaluation of function call arguments is done which results
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
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)
- 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?
- 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
IR/VM
-----
- possible to use LOADFIELD/STOREFIELD instructions more?
- pointer dt's are all reduced to just an uword (in the irTypeString method) - is this okay or could it be beneficial to reintroduce the actual pointer type information? See commit 88b074c208450c58aa32469745afa03e4c5f564a
- change the instruction format so an indirect register (a pointer) can be used more often, at least for the inplace assignment operators that operate on pointer
- 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?)
- 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)
- proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg
- implement fast code paths for TODO("inplace split....
- implement more TODOs in AssignmentGen
- sometimes source lines end up missing in the output p8ir, for example the first assignment is gone in:
sub start() {
cx16.r0L = cx16.r1 as ubyte
cx16.r0sL = cx16.r1s as byte }
- do something with the 'split' tag on split word arrays
- add more optimizations in IRPeepholeOptimizer
- apparently for SSA form, the IRCodeChunk is not a proper "basic block" yet because the last operation should be a branch or return, and no other branches
- reduce register usage via linear-scan algorithm (based on live intervals) https://anoopsarkar.github.io/compilers-class/assets/lectures/opt3-regalloc-linearscan.pdf
don't forget to take into account the data type of the register when it's going to be reused!
- idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
global initialization values are simply a list of LOAD instructions.
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
- the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
maybe another representation is needed once actual codegeneration is done from the IR...?
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers? maybe not, this requires hundreds of lines to change
Libraries
---------
- Add split-word array sorting routines to sorting module?
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
- pet32 target: make syslib more complete (missing kernal routines)?
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
- c128 target: make syslib more complete (missing kernal routines)?
Optimizations
-------------
- Port benchmarks from https://thred.github.io/c-bench-64/ to prog8 and see how it stacks up.
- Since fixing the missing zp-var initialization, programs grew in size again because STZ's reappered. Can we add more intelligent (and correct!) optimizations to remove those STZs that might be redundant again?
- in Identifier: use typedarray of strings instead of listOf? Other places?
- Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably be removed, or be made permanent