mirror of
https://github.com/irmen/prog8.git
synced 2026-10-24 03:02:20 +00:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ea8ca59b7 | ||
|
|
71ffbe2ba7 | ||
|
|
e63921009c | ||
|
|
db1aa3f257 | ||
|
|
8abdb837b2 | ||
|
|
cdeabd4b66 | ||
|
|
efff74c0f1 | ||
|
|
0e177cb531 | ||
|
|
35b921b062 | ||
|
|
a7d98e43b8 | ||
|
|
845ee2dd83 | ||
|
|
f9b0bfe31b | ||
|
|
f303d15628 | ||
|
|
67f7ffa52d | ||
|
|
88c5d9783a | ||
|
|
e7fc0360ad | ||
|
|
cd1862dd9f | ||
|
|
045c4909a8 | ||
|
|
f1bfe619b2 | ||
|
|
e0107bacbd | ||
|
|
b3bd2a6a09 | ||
|
|
ff1f58e022 | ||
|
|
557b12668d | ||
|
|
b058f1c7c2 | ||
|
|
3e07b6ca70 | ||
|
|
d66dc664de | ||
|
|
a2b9d78cf3 | ||
|
|
44f70da113 | ||
|
|
c87eaf576d | ||
|
|
c09b1395f6 | ||
|
|
d6a8201291 | ||
|
|
4187f97f7a | ||
|
|
9d3b2f12fd | ||
|
|
87c1bbbf40 | ||
|
|
a7ad6abdb9 | ||
|
|
a611406020 | ||
|
|
75da38224d | ||
|
|
8d6f3301c8 | ||
|
|
86b52a1c5e | ||
|
|
2c8b1c2022 | ||
|
|
ab1f065752 | ||
|
|
2c7256a443 | ||
|
|
97420b28e5 | ||
|
|
1467c7039d | ||
|
|
d319badc6c | ||
|
|
65d6c1c438 | ||
|
|
abeefb5655 | ||
|
|
50fecbcebe | ||
|
|
4fe8b72d42 | ||
|
|
f3b060df51 | ||
|
|
09d1cb6925 | ||
|
|
54fa72fa98 | ||
|
|
fd62fe7511 | ||
|
|
bfb34dff62 | ||
|
|
817b623596 | ||
|
|
f6dbeb1f63 | ||
|
|
c4b9bdd33f | ||
|
|
68e0d5f1b5 | ||
|
|
4939e3df55 | ||
|
|
19f19f3880 | ||
|
|
ad0c767ea8 | ||
|
|
ed5f4d5855 | ||
|
|
c2f5d37486 |
No files matched your search
@@ -0,0 +1,17 @@
|
||||
# An .aiignore file follows the same syntax as a .gitignore file.
|
||||
# .gitignore documentation: https://git-scm.com/docs/gitignore
|
||||
|
||||
# you can ignore files
|
||||
.DS_Store
|
||||
*.log
|
||||
*.tmp
|
||||
*.bin
|
||||
*.prg
|
||||
|
||||
# or folders
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
output/
|
||||
.gradle/
|
||||
.kotlin/
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Build and test with Gradle
|
||||
|
||||
Generated
+1
@@ -15,6 +15,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
|
||||
|
||||
@@ -37,6 +37,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val returns: ReturnConvention = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R0R1_32)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
|
||||
null -> ReturnConvention(null, null)
|
||||
@@ -45,6 +46,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R0R1_32)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
@@ -61,6 +63,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R0R1_32, false)
|
||||
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
|
||||
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ParamConvention(paramType, null, false)
|
||||
@@ -88,10 +91,10 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns a status in the carry flag, but not a proper return value
|
||||
"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)),
|
||||
@@ -100,6 +103,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
|
||||
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
@@ -117,21 +121,26 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
|
||||
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
|
||||
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
|
||||
|
||||
@@ -13,6 +13,7 @@ class CompilationOptions(val output: OutputType,
|
||||
val noSysInit: Boolean,
|
||||
val romable: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
val compilerVersion: String,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
|
||||
@@ -50,8 +50,10 @@ enum class BaseDataType {
|
||||
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
|
||||
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
@@ -143,7 +145,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
|
||||
return if(splitwordarray && actualElementDt.isWord)
|
||||
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
|
||||
else {
|
||||
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
DataType(BaseDataType.ARRAY, actualElementDt, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
@@ -222,6 +224,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
|
||||
BaseDataType.WORD -> "word[]"
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
@@ -284,6 +287,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
|
||||
BaseDataType.BOOL -> "bool["
|
||||
BaseDataType.BYTE -> "byte["
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
@@ -344,6 +348,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
|
||||
val isUnsignedWord = base == BaseDataType.UWORD
|
||||
val isSignedWord = base == BaseDataType.WORD
|
||||
val isInteger = base.isInteger
|
||||
val isWordOrByteOrBool = base.isWordOrByteOrBool
|
||||
val isIntegerOrBool = base.isIntegerOrBool
|
||||
val isNumeric = base.isNumeric
|
||||
val isNumericOrBool = base.isNumericOrBool
|
||||
@@ -362,6 +367,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
|
||||
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isLongArray = base.isArray && sub == BaseDataType.LONG
|
||||
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
@@ -394,7 +400,9 @@ enum class RegisterOrPair {
|
||||
FAC2,
|
||||
// cx16 virtual registers:
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
R8, R9, R10, R11, R12, R13, R14, R15;
|
||||
R8, R9, R10, R11, R12, R13, R14, R15,
|
||||
// combined virtual registers to store 32 bits longs:
|
||||
R0R1_32, R2R3_32, R4R5_32, R6R7_32, R8R9_32, R10R11_32, R12R13_32, R14R15_32;
|
||||
|
||||
companion object {
|
||||
val names by lazy { entries.map { it.toString()} }
|
||||
@@ -407,6 +415,18 @@ enum class RegisterOrPair {
|
||||
}
|
||||
}
|
||||
|
||||
fun startregname() = when(this) {
|
||||
R0R1_32 -> "r0"
|
||||
R2R3_32 -> "r2"
|
||||
R4R5_32 -> "r4"
|
||||
R6R7_32 -> "r6"
|
||||
R8R9_32 -> "r8"
|
||||
R10R11_32 -> "r10"
|
||||
R12R13_32 -> "r12"
|
||||
R14R15_32 -> "r14"
|
||||
else -> throw IllegalArgumentException("must be a combined virtual register $this")
|
||||
}
|
||||
|
||||
fun asCpuRegister(): CpuRegister = when(this) {
|
||||
A -> CpuRegister.A
|
||||
X -> CpuRegister.X
|
||||
@@ -421,12 +441,13 @@ enum class RegisterOrPair {
|
||||
BaseDataType.BYTE -> "sL"
|
||||
BaseDataType.WORD -> "s"
|
||||
BaseDataType.UWORD, null -> ""
|
||||
else -> throw IllegalArgumentException("invalid register param type")
|
||||
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
|
||||
}
|
||||
return listOf("cx16", name.lowercase()+suffix)
|
||||
}
|
||||
|
||||
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
|
||||
fun isLong() = this in combinedLongRegisters
|
||||
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
@@ -463,6 +484,17 @@ val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||
)
|
||||
|
||||
val combinedLongRegisters = arrayOf(
|
||||
RegisterOrPair.R0R1_32,
|
||||
RegisterOrPair.R2R3_32,
|
||||
RegisterOrPair.R4R5_32,
|
||||
RegisterOrPair.R6R7_32,
|
||||
RegisterOrPair.R8R9_32,
|
||||
RegisterOrPair.R10R11_32,
|
||||
RegisterOrPair.R12R13_32,
|
||||
RegisterOrPair.R14R15_32
|
||||
)
|
||||
|
||||
val CpuRegisters = arrayOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
|
||||
@@ -14,6 +14,7 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT-> numElements * floatsize
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
|
||||
@@ -558,7 +558,7 @@ class AsmGen6502Internal (
|
||||
} else {
|
||||
return if (allocator.isZpVar((target as PtNamedNode).scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
loadAFromZpPointerVar(sourceName, true)
|
||||
loadAFromZpPointerVar(sourceName)
|
||||
sourceName
|
||||
} else {
|
||||
out("""
|
||||
@@ -617,7 +617,7 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean) {
|
||||
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean=false) {
|
||||
if (isTargetCpu(CpuType.CPU65C02))
|
||||
out(" lda ($zpPointerVar)")
|
||||
else {
|
||||
@@ -806,6 +806,7 @@ class AsmGen6502Internal (
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
|
||||
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
|
||||
in combinedLongRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
|
||||
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
|
||||
RegisterOrPair.FAC2 -> assignmentAsmGen.assignFAC2float(target)
|
||||
else -> throw AssemblyError("invalid register")
|
||||
@@ -813,6 +814,9 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
|
||||
|
||||
// this is basically the fallback assignment routine, it is RARELY called
|
||||
|
||||
when {
|
||||
target.datatype.isByteOrBool -> {
|
||||
if (value.asConstInteger()==0) {
|
||||
@@ -860,6 +864,98 @@ class AsmGen6502Internal (
|
||||
), value.definingISub()
|
||||
)
|
||||
}
|
||||
target.datatype.isLong -> {
|
||||
if(value is PtNumber) {
|
||||
val hex = value.asConstInteger()!!.toString(16).padStart(8, '0')
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
out("""
|
||||
lda #${hex.substring(6,8)}
|
||||
sta ${target.asmVarname}
|
||||
lda #${hex.substring(4, 6)}
|
||||
sta ${target.asmVarname}+1
|
||||
lda #${hex.substring(2, 4)}
|
||||
sta ${target.asmVarname}+2
|
||||
lda #${hex.take(2)}
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else if(value is PtIdentifier && value.type.isLong) {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
val valuesym = asmSymbolName(value)
|
||||
out(
|
||||
"""
|
||||
lda $valuesym
|
||||
sta ${target.asmVarname}
|
||||
lda $valuesym+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $valuesym+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $valuesym+3
|
||||
sta ${target.asmVarname}+3"""
|
||||
)
|
||||
}
|
||||
|
||||
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else if(value is PtTypeCast && value.type.isLong) {
|
||||
if(value.value is PtIdentifier) {
|
||||
val valuesym = asmSymbolName((value.value as PtIdentifier).name)
|
||||
if(value.value.type.isByte) {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
out(" lda $valuesym | sta ${target.asmVarname}")
|
||||
signExtendLongVariable(target.asmVarname, value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else if(value.value.type.isWord) {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
out("""
|
||||
lda $valuesym
|
||||
sta ${target.asmVarname}
|
||||
lda $valuesym+1
|
||||
sta ${target.asmVarname}+1""")
|
||||
signExtendLongVariable(target.asmVarname, value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
val startreg = target.register.toString().take(2).lowercase()
|
||||
out("""
|
||||
lda $valuesym
|
||||
sta cx16.$startreg
|
||||
lda $valuesym+1
|
||||
sta cx16.$startreg+1""")
|
||||
signExtendLongVariable("cx16.$startreg", value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else throw AssemblyError("weird casted type")
|
||||
} else {
|
||||
TODO("assign typecasted expression $value to a long target ${target.position} - use simple expressions and temporary variables for now")
|
||||
}
|
||||
} else {
|
||||
TODO("assign long expression $value to a target ${target.position} - use simple expressions and temporary variables for now")
|
||||
}
|
||||
}
|
||||
target.datatype.isFloat -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1)
|
||||
assignRegister(RegisterOrPair.FAC1, target)
|
||||
@@ -1190,7 +1286,7 @@ $repeatLabel""")
|
||||
if(returnvalue!=null) {
|
||||
val returnDt = sub.signature.returns.single()
|
||||
if (returnDt.isNumericOrBool || returnDt.isPointer) {
|
||||
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!)
|
||||
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!, returnDt.isSigned)
|
||||
}
|
||||
else {
|
||||
// all else take its address and assign that also to AY register pair
|
||||
@@ -1211,7 +1307,7 @@ $repeatLabel""")
|
||||
assignExpressionTo(it.first as PtExpression, tgt)
|
||||
}
|
||||
assigns.first().also {
|
||||
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!)
|
||||
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!, (it.first as PtExpression).type.isSigned)
|
||||
}
|
||||
}
|
||||
out(" rts")
|
||||
@@ -1297,6 +1393,50 @@ $repeatLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendLongVariable(asmvar: String, valueDt: BaseDataType) {
|
||||
// sign extend signed word in a var to a full long in that variable
|
||||
when(valueDt) {
|
||||
BaseDataType.UBYTE -> {
|
||||
if(isTargetCpu(CpuType.CPU65C02)) {
|
||||
out("""
|
||||
stz $asmvar+1
|
||||
stz $asmvar+2
|
||||
stz $asmvar+3""")
|
||||
}
|
||||
else {
|
||||
out("""
|
||||
lda #0
|
||||
sta $asmvar+1
|
||||
sta $asmvar+2
|
||||
sta $asmvar+3""")
|
||||
}
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
out("""
|
||||
lda $asmvar
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta $asmvar+1
|
||||
sta $asmvar+2
|
||||
sta $asmvar+3""")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
out(" lda #0 | sta $asmvar+2 | sta $asmvar+3")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
out("""
|
||||
lda $asmvar+1
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta $asmvar+2
|
||||
sta $asmvar+3""")
|
||||
}
|
||||
else -> throw AssemblyError("need byte or word type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isZpVar(variable: PtIdentifier): Boolean = allocator.isZpVar(variable.name)
|
||||
|
||||
internal fun jmp(asmLabel: String, indirect: Boolean=false, indexedX: Boolean=false) {
|
||||
|
||||
@@ -463,15 +463,19 @@ private fun optimizeStoreLoadSame(
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
|
||||
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated
|
||||
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated IF NOT IO ADDRESS
|
||||
if (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") ||
|
||||
first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") ||
|
||||
first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ")
|
||||
) {
|
||||
val firstVal = first.substring(4).trimStart()
|
||||
val thirdVal = third.substring(4).trimStart()
|
||||
if (firstVal == thirdVal)
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
if (firstVal == thirdVal) {
|
||||
val address = getAddressArg(third, symbolTable)
|
||||
if (address != null && !machine.isIOAddress(address)) {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
|
||||
@@ -27,15 +27,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val sscope = fcall.definingISub()
|
||||
|
||||
when (fcall.name) {
|
||||
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
|
||||
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
|
||||
"msw" -> funcMsw(fcall, resultRegister)
|
||||
"lsw" -> funcLsw(fcall, resultRegister)
|
||||
"msb" -> funcMsb(fcall, resultRegister)
|
||||
"lsb" -> funcLsb(fcall, resultRegister)
|
||||
"mkword" -> funcMkword(fcall, resultRegister)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(fcall, resultRegister)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
|
||||
"mklong", "mklong2" -> funcMklong(fcall) // result is in R0:R1
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(fcall, resultRegister)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(fcall, resultRegister)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(fcall, resultRegister)
|
||||
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, resultRegister, sscope)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
|
||||
"divmod__ubyte" -> funcDivmod(fcall)
|
||||
@@ -48,10 +49,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"setmsb" -> funcSetLsbMsb(fcall, true)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peekl" -> funcPeekL(fcall, resultRegister)
|
||||
"peekf" -> funcPeekF(fcall, resultRegister)
|
||||
"peekbool" -> funcPeekBool(fcall, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokel" -> funcPokeL(fcall)
|
||||
"pokef" -> funcPokeF(fcall)
|
||||
"pokemon" -> {
|
||||
val memread = PtMemoryByte(fcall.position)
|
||||
@@ -346,8 +349,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
} else {
|
||||
// arg1 is a word
|
||||
} else if(arg1.type.isWord) {
|
||||
if(arg2.type.isWord) {
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
@@ -378,6 +380,60 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
} else if(arg1.type.isLong) {
|
||||
if(arg2.type.isLong) {
|
||||
if(arg1 is PtIdentifier && arg2 is PtNumber) {
|
||||
val var1 = asmgen.asmVariableName(arg1)
|
||||
val hex = arg2.number.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda $var1
|
||||
cmp #${hex.substring(6, 8)}
|
||||
bne +
|
||||
lda $var1+1
|
||||
cmp #${hex.substring(4, 6)}
|
||||
bne +
|
||||
lda $var1+2
|
||||
cmp #${hex.substring(2, 4)}
|
||||
bne +
|
||||
lda $var1+3
|
||||
cmp #${hex.take(2)}
|
||||
+""")
|
||||
} else if(arg1 is PtIdentifier && arg2 is PtIdentifier) {
|
||||
val var1 = asmgen.asmVariableName(arg1)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
asmgen.out("""
|
||||
lda $var1
|
||||
cmp $var2
|
||||
bne +
|
||||
lda $var1+1
|
||||
cmp $var2+1
|
||||
bne +
|
||||
lda $var1+2
|
||||
cmp $var2+2
|
||||
bne +
|
||||
lda $var1+3
|
||||
cmp $var2+3
|
||||
+""")
|
||||
} else {
|
||||
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R2R3_32, true)
|
||||
assignAsmGen.assignExpressionToRegister(arg1, RegisterOrPair.R0R1_32, true)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
cmp cx16.r2
|
||||
bne +
|
||||
lda cx16.r0+1
|
||||
cmp cx16.r2+1
|
||||
bne +
|
||||
lda cx16.r0+2
|
||||
cmp cx16.r2+2
|
||||
bne +
|
||||
lda cx16.r0+3
|
||||
cmp cx16.r2+3
|
||||
+""")
|
||||
}
|
||||
// TODO: carry flag is not correct......
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +480,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
BaseDataType.LONG -> TODO("sqrt LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
|
||||
@@ -480,6 +537,26 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
// a bit strange, rotating a signed type, but it has to do for now
|
||||
when(what) {
|
||||
is PtArrayIndexer -> TODO("ror2 long array ${what.position}")
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out("""
|
||||
lsr $variable+3
|
||||
ror $variable+2
|
||||
ror $variable+1
|
||||
ror $variable
|
||||
bcc +
|
||||
lda $variable+3
|
||||
ora #$80
|
||||
sta $variable+3
|
||||
+""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@@ -544,6 +621,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
// a bit strange, rotating a signed type, but it has to do for now
|
||||
when(what) {
|
||||
is PtArrayIndexer -> TODO("ror long array ${what.position}")
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable+3 | ror $variable+2 | ror $variable+1 | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@@ -595,6 +683,24 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
// a bit strange, rotating a signed type, but it has to do for now
|
||||
when(what) {
|
||||
is PtArrayIndexer -> TODO("rol2 long array ${what.position}")
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out("""
|
||||
asl $variable
|
||||
rol $variable+1
|
||||
rol $variable+2
|
||||
rol $variable+3
|
||||
bcc +
|
||||
inc $variable
|
||||
+""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@@ -658,6 +764,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
// a bit strange, rotating a signed type, but it has to do for now
|
||||
when(what) {
|
||||
is PtArrayIndexer -> TODO("rol long array ${what.position}")
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable | rol $variable+1 | rol $variable+2 | rol $variable+3")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@@ -729,6 +846,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_into_A")
|
||||
BaseDataType.LONG -> TODO("sgn LONG")
|
||||
BaseDataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
@@ -747,6 +865,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, scope, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_l_into_R0R1")
|
||||
assignAsmGen.assignRegisterLong(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.R0R1_32, true, fcall.position, scope, asmgen), RegisterOrPair.R0R1_32)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.func_abs_f_into_FAC1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
|
||||
@@ -884,6 +1006,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.func_pokew")
|
||||
}
|
||||
|
||||
private fun funcPokeL(fcall: PtBuiltinFunctionCall) {
|
||||
val addrExpr = fcall.args[0]
|
||||
val value = fcall.args[1]
|
||||
TODO("pokel $addrExpr, $value")
|
||||
}
|
||||
|
||||
private fun funcPeekF(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out(" jsr floats.MOVFM")
|
||||
@@ -980,6 +1108,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPeekL(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val addrExpr = fcall.args[0]
|
||||
val value = fcall.args[1]
|
||||
TODO("peekl $addrExpr, $value")
|
||||
}
|
||||
|
||||
|
||||
private fun funcClamp(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type.isSigned
|
||||
when {
|
||||
@@ -999,6 +1134,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
assignAsmGen.assignRegisterpairWord(targetReg, RegisterOrPair.AY)
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
TODO("clamp long ${fcall.position}")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
@@ -1055,6 +1193,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
TODO("min long ${fcall.position}")
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("min float not supported")
|
||||
}
|
||||
@@ -1063,55 +1204,109 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
|
||||
private fun funcMax(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type.isSigned
|
||||
if(fcall.type.isByte) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
} else if(fcall.type.isWord) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if (signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bmi +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+"""
|
||||
)
|
||||
val targetReg = AsmAssignTarget.fromRegisters(
|
||||
resultRegister ?: RegisterOrPair.A,
|
||||
signed,
|
||||
fcall.position,
|
||||
fcall.definingISub(),
|
||||
asmgen
|
||||
)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bmi +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(
|
||||
resultRegister ?: RegisterOrPair.AY,
|
||||
signed,
|
||||
fcall.position,
|
||||
fcall.definingISub(),
|
||||
asmgen
|
||||
)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
TODO("max long ${fcall.position}")
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("max float not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMklong(fcall: PtBuiltinFunctionCall) {
|
||||
// result long in R0:R1 (r0=lsw, r1=msw)
|
||||
|
||||
fun isArgRegister(expression: PtExpression, reg: RegisterOrPair): Boolean {
|
||||
if(expression !is PtIdentifier)
|
||||
return false
|
||||
return expression.name.startsWith("cx16.${reg.name.lowercase()}")
|
||||
}
|
||||
|
||||
if(fcall.args.size==2) {
|
||||
// mklong2(msw, lsw)
|
||||
if(isArgRegister(fcall.args[0], RegisterOrPair.R0) || isArgRegister(fcall.args[0], RegisterOrPair.R1) ||
|
||||
isArgRegister(fcall.args[1], RegisterOrPair.R0) || isArgRegister(fcall.args[1], RegisterOrPair.R1)) {
|
||||
error("cannot use R0 and/or R1 as arguments for mklong2 because the result should go into R0:R1 ${fcall.position}")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[0], "cx16.r1", DataType.UWORD)
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "cx16.r0", DataType.UWORD)
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
} else {
|
||||
throw AssemblyError("max float not supported")
|
||||
// mklong(msb, b2, b1, lsb)
|
||||
if(isArgRegister(fcall.args[0], RegisterOrPair.R0) || isArgRegister(fcall.args[0], RegisterOrPair.R1) ||
|
||||
isArgRegister(fcall.args[1], RegisterOrPair.R0) || isArgRegister(fcall.args[1], RegisterOrPair.R1) ||
|
||||
isArgRegister(fcall.args[2], RegisterOrPair.R0) || isArgRegister(fcall.args[2], RegisterOrPair.R1) ||
|
||||
isArgRegister(fcall.args[3], RegisterOrPair.R0) || isArgRegister(fcall.args[3], RegisterOrPair.R1)) {
|
||||
error("cannot use R0 and/or R1 as arguments for mklong because the result should go into R0:R1 ${fcall.position}")
|
||||
} else {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[0], "cx16.r1H", DataType.UBYTE)
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "cx16.r1L", DataType.UBYTE)
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "cx16.r0H", DataType.UBYTE)
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[3], "cx16.r0L", DataType.UBYTE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,7 +1380,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isWord)
|
||||
throw AssemblyError("msb required word argument")
|
||||
throw AssemblyError("msb requires word argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
if (arg is PtIdentifier) {
|
||||
@@ -1371,6 +1566,53 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMsw(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msw requires long argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msw(const) should have been const-folded away")
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultRegister) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+2 | ldx $sourceName+3")
|
||||
null, RegisterOrPair.AY -> asmgen.out(" lda $sourceName+2 | ldy $sourceName+3")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+2 | ldy $sourceName+3")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
asmgen.out(" lda $sourceName+2 | sta cx16.$regname | lda $sourceName+3 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
TODO("msw(expression) ${fcall.position} - use a temporary variable for now")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsw(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("lsw requires long argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("lsw(const) should have been const-folded away")
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultRegister) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
null, RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | lda $sourceName+1 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
TODO("lsw(expression) ${fcall.position} - use a temporary variable for now")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
|
||||
val signature = BuiltinFunctions.getValue(call.name)
|
||||
val callConv = signature.callConvention(call.args.map {
|
||||
@@ -1413,6 +1655,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val varname = "prog8_lib.func_${call.name}._arg_${paramName}"
|
||||
val src = when {
|
||||
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
|
||||
conv.dt==BaseDataType.LONG -> TODO("LONG argument for builtin func")
|
||||
conv.dt.isPassByRef -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false, value.position)
|
||||
@@ -1431,6 +1674,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
conv.reg != null -> {
|
||||
val src = when {
|
||||
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
|
||||
conv.dt==BaseDataType.LONG -> AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
conv.dt.isPassByRef -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false,value.position)
|
||||
@@ -1442,7 +1686,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, value.position, null, asmgen)
|
||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, conv.dt.isSigned, value.position, null, asmgen)
|
||||
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign, scope)
|
||||
}
|
||||
|
||||
@@ -505,137 +505,158 @@ $endLabel""")
|
||||
is StMemVar -> symbol.length!!
|
||||
else -> 0u
|
||||
}
|
||||
when {
|
||||
iterableDt.isString -> {
|
||||
if(asmgen.options.romable) {
|
||||
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
asmgen.out("""
|
||||
|
||||
fun iterateStrings() {
|
||||
if(asmgen.options.romable) {
|
||||
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
sty $indexVar
|
||||
$loopLabel lda $iterableName,y
|
||||
beq $endLabel
|
||||
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
inc $indexVar
|
||||
ldy $indexVar
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
} else {
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
} else {
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
sty $indexVar
|
||||
$loopLabel lda $iterableName,y
|
||||
beq $endLabel
|
||||
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
inc $indexVar
|
||||
ldy $indexVar
|
||||
bne $loopLabel
|
||||
$indexVar .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
iterableDt.isByteArray || iterableDt.isBoolArray -> {
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
}
|
||||
|
||||
fun iterateBytes() {
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
lda $iterableName,y
|
||||
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=255u) {
|
||||
asmgen.out("""
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=255u) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
cpy #$numElements
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
// length is 256
|
||||
asmgen.out("""
|
||||
} else {
|
||||
// length is 256
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(!asmgen.options.romable) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isSplitWordArray -> {
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
if(!asmgen.options.romable) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
fun iterateSplitWords() {
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
lda ${iterableName}_lsb,y
|
||||
sta $loopvarName
|
||||
lda ${iterableName}_msb,y
|
||||
sta $loopvarName+1""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=255u) {
|
||||
asmgen.out("""
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=255u) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
cpy #$numElements
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
// length is 256
|
||||
asmgen.out("""
|
||||
} else {
|
||||
// length is 256
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(!asmgen.options.romable) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isWordArray -> {
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
if(!asmgen.options.romable) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
fun iterateWords(actuallyLongs: Boolean = false) {
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
lda $iterableName,y
|
||||
sta $loopvarName
|
||||
lda $iterableName+1,y
|
||||
sta $loopvarName+1""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=127u) {
|
||||
if(actuallyLongs) {
|
||||
asmgen.out("""
|
||||
lda $iterableName+2,y
|
||||
sta $loopvarName+2
|
||||
lda $iterableName+3,y
|
||||
sta $loopvarName+3""")
|
||||
}
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=127u) {
|
||||
if(actuallyLongs) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
iny
|
||||
iny
|
||||
iny
|
||||
cpy #${numElements*4u}
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
@@ -643,6 +664,19 @@ $loopLabel sty $indexVar
|
||||
cpy #${numElements*2u}
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
}
|
||||
} else {
|
||||
if(actuallyLongs) {
|
||||
// array size is 64 longs, 256 bytes
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
iny
|
||||
iny
|
||||
iny
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
|
||||
} else {
|
||||
// array size is 128 words, 256 bytes
|
||||
asmgen.out("""
|
||||
@@ -652,23 +686,29 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(!asmgen.options.romable) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
}
|
||||
if(!asmgen.options.romable) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isFloatArray -> {
|
||||
throw AssemblyError("for loop with floating point variables is not supported")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
when {
|
||||
iterableDt.isString -> iterateStrings()
|
||||
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
|
||||
iterableDt.isSplitWordArray -> iterateSplitWords()
|
||||
iterableDt.isWordArray -> iterateWords()
|
||||
iterableDt.isLongArray -> iterateWords(true)
|
||||
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
|
||||
else -> throw AssemblyError("can't iterate over $iterableDt")
|
||||
}
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
|
||||
@@ -21,7 +21,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// we consider them NOT to be optimized into (possibly different) CPU registers.
|
||||
// Just load them in whatever the register spec says.
|
||||
return when (params.size) {
|
||||
1 -> params[0].register == null && (params[0].type.isIntegerOrBool || params[0].type.isPointer)
|
||||
1 -> params[0].register == null && (params[0].type.isWordOrByteOrBool || params[0].type.isPointer)
|
||||
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null
|
||||
else -> false
|
||||
}
|
||||
@@ -206,8 +206,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
if (arg.name == "mkword")
|
||||
if (arg.name == "mkword" || arg.name == "mklong2")
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||
if (arg.name == "mklong")
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
|
||||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
|
||||
return !arg.isSimple()
|
||||
}
|
||||
is PtAddressOf -> false
|
||||
|
||||
@@ -46,6 +46,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
return when {
|
||||
rightDt.isByteOrBool -> translateIfByte(stmt, jumpAfterIf)
|
||||
rightDt.isWord || rightDt.isPointer -> translateIfWord(stmt, compareCond, jumpAfterIf)
|
||||
rightDt.isLong -> translateIfLong(stmt, compareCond, jumpAfterIf)
|
||||
rightDt.isFloat -> translateIfFloat(stmt, compareCond, jumpAfterIf)
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -575,6 +576,32 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfLong(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
|
||||
val constValue = condition.right.asConstInteger()
|
||||
if(constValue==0) {
|
||||
// optimized comparisons with zero
|
||||
return when (condition.operator) {
|
||||
"==" -> longEqualsZero(condition.left, false, jumpAfterIf, stmt)
|
||||
"!=" -> longEqualsZero(condition.left, true, jumpAfterIf, stmt)
|
||||
"<" -> longLessZero(condition.left, false, jumpAfterIf, stmt)
|
||||
"<=" -> longLessZero(condition.left, true, jumpAfterIf, stmt)
|
||||
">" -> longGreaterZero(condition.left, false, jumpAfterIf, stmt)
|
||||
">=" -> longGreaterZero(condition.left, true, jumpAfterIf, stmt)
|
||||
else -> throw AssemblyError("expected comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
return when (condition.operator) {
|
||||
"==" -> longEqualsValue(condition.left, condition.right, false, jumpAfterIf, stmt)
|
||||
"!=" -> longEqualsValue(condition.left, condition.right, true, jumpAfterIf, stmt)
|
||||
"<" -> TODO("long < 0")
|
||||
"<=" -> TODO("long <= 0")
|
||||
">" -> TODO("long > 0")
|
||||
">=" -> TODO("long >= 0")
|
||||
else -> throw AssemblyError("expected comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
|
||||
val signed = condition.left.type.isSigned
|
||||
val constValue = condition.right.asConstInteger()
|
||||
@@ -606,7 +633,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
private fun wordGreaterEqualsZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
// special case for word>=0
|
||||
if(signed) {
|
||||
loadAndCmp0MSB(value)
|
||||
loadAndCmp0MSB(value, false)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
|
||||
else
|
||||
@@ -619,7 +646,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
private fun wordLessZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
// special case for word<0
|
||||
if(signed) {
|
||||
loadAndCmp0MSB(value)
|
||||
loadAndCmp0MSB(value, false)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
|
||||
else
|
||||
@@ -894,23 +921,31 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
|
||||
|
||||
private fun loadAndCmp0MSB(value: PtExpression) {
|
||||
private fun loadAndCmp0MSB(value: PtExpression, long: Boolean) {
|
||||
when(value) {
|
||||
is PtArrayIndexer -> {
|
||||
if(value.variable==null)
|
||||
TODO("support for ptr indexing ${value.position}")
|
||||
val varname = asmgen.asmVariableName(value.variable!!)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
|
||||
if(value.splitWords)
|
||||
if(value.splitWords) {
|
||||
require(!long)
|
||||
asmgen.out(" lda ${varname}_msb,y")
|
||||
}
|
||||
else if(long)
|
||||
asmgen.out(" lda $varname+3,y")
|
||||
else
|
||||
asmgen.out(" lda $varname+1,y")
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(value)
|
||||
asmgen.out(" lda $varname+1")
|
||||
if(long)
|
||||
asmgen.out(" lda $varname+3")
|
||||
else
|
||||
asmgen.out(" lda $varname+1")
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
require(!long)
|
||||
if(value.isFromArrayElement) {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" cpy #0")
|
||||
@@ -923,6 +958,7 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
require(!long)
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" cpy #0")
|
||||
}
|
||||
@@ -1226,6 +1262,50 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
}
|
||||
|
||||
private fun longEqualsZero(value: PtExpression, notEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.R0R1_32, value.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
ora cx16.r0+1
|
||||
ora cx16.r0+2
|
||||
ora cx16.r0+3""")
|
||||
if(notEquals) {
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("beq", stmt)
|
||||
} else {
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bne", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(lessEquals) {
|
||||
TODO("long <= 0")
|
||||
} else {
|
||||
loadAndCmp0MSB(value, true)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bpl", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun longGreaterZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(lessEquals) {
|
||||
TODO("long >= 0")
|
||||
} else {
|
||||
loadAndCmp0MSB(value, true)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bmi", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun wordEqualsZero(value: PtExpression, notEquals: Boolean, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
|
||||
// special case for (u)word == 0
|
||||
@@ -1303,6 +1383,93 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
}
|
||||
|
||||
private fun longEqualsValue(
|
||||
left: PtExpression,
|
||||
right: PtExpression,
|
||||
notEquals: Boolean,
|
||||
jump: PtJump?,
|
||||
stmt: PtIfElse
|
||||
) {
|
||||
// TODO this can be optimized somewhat more when the left operand is a variable as well
|
||||
// we only optimize for a const right value for now
|
||||
|
||||
val constRight = right.asConstInteger()
|
||||
val variableRight = (right as? PtIdentifier)?.name
|
||||
if(constRight!=null) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.R0R1_32, left.type.isSigned)
|
||||
val hex = constRight.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
cmp #$${hex.substring(6,8)}
|
||||
bne +
|
||||
lda cx16.r0+1
|
||||
cmp #$${hex.substring(4, 6)}
|
||||
bne +
|
||||
lda cx16.r0+2
|
||||
cmp #$${hex.substring(2, 4)}
|
||||
bne +
|
||||
lda cx16.r0+3
|
||||
cmp #$${hex.take(2)}
|
||||
+""")
|
||||
} else if(variableRight!=null) {
|
||||
require(right.type.isLong)
|
||||
val variableLeft = (left as? PtIdentifier)?.name
|
||||
if(variableLeft!=null) {
|
||||
asmgen.out("""
|
||||
lda #<$variableLeft
|
||||
ldy #>$variableLeft
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$variableRight
|
||||
ldy #>$variableRight
|
||||
jsr prog8_lib.long_not_equals""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.R0R1_32, left.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
cmp $variableRight
|
||||
bne +
|
||||
lda cx16.r0+1
|
||||
cmp $variableRight+1
|
||||
bne +
|
||||
lda cx16.r0+2
|
||||
cmp $variableRight+2
|
||||
bne +
|
||||
lda cx16.r0+3
|
||||
cmp $variableRight+3
|
||||
+""")
|
||||
}
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.R2R3_32, left.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.R0R1_32, right.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
cmp cx16.r2
|
||||
bne +
|
||||
lda cx16.r0+1
|
||||
cmp cx16.r2+1
|
||||
bne +
|
||||
lda cx16.r0+2
|
||||
cmp cx16.r2+2
|
||||
bne +
|
||||
lda cx16.r0+3
|
||||
cmp cx16.r2+3
|
||||
+""")
|
||||
}
|
||||
|
||||
if(notEquals) {
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("beq", stmt)
|
||||
} else {
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bne", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun wordEqualsValue(
|
||||
left: PtExpression,
|
||||
right: PtExpression,
|
||||
|
||||
@@ -32,6 +32,14 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.R0R1_32, true)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(falseLabel)
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.R0R1_32, true)
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R0R1_32)
|
||||
}
|
||||
expr.type.isFloat -> {
|
||||
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
|
||||
asmgen.jmp(endLabel)
|
||||
@@ -114,6 +122,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
return when {
|
||||
rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel)
|
||||
rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel)
|
||||
rightDt.isLong -> translateIfExpressionLongConditionBranch(condition, falseLabel)
|
||||
rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel)
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -200,6 +209,36 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
|
||||
private fun translateIfExpressionLongConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
|
||||
// TODO can we reuse this whole thing from IfElse ?
|
||||
val constValue = condition.right.asConstInteger()
|
||||
if(constValue!=null) {
|
||||
if (constValue == 0) {
|
||||
when (condition.operator) {
|
||||
"==" -> return translateLongExprIsZero(condition.left, falseLabel)
|
||||
"!=" -> return translateLongExprIsNotZero(condition.left, falseLabel)
|
||||
}
|
||||
}
|
||||
if (constValue != 0) {
|
||||
when (condition.operator) {
|
||||
"==" -> return translateLongExprEqualsNumber(condition.left, constValue, falseLabel)
|
||||
"!=" -> return translateLongExprNotEqualsNumber(condition.left, constValue, falseLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
val variable = condition.right as? PtIdentifier
|
||||
if(variable!=null) {
|
||||
when (condition.operator) {
|
||||
"==" -> return translateLongExprEqualsVariable(condition.left, variable, falseLabel)
|
||||
"!=" -> return translateLongExprNotEqualsVariable(condition.left, variable, falseLabel)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
|
||||
asmgen.assignConditionValueToRegisterAndTest(condition)
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
|
||||
private fun translateIfExpressionFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) {
|
||||
val constValue = (condition.right as? PtNumber)?.number
|
||||
if(constValue==0.0) {
|
||||
@@ -339,6 +378,89 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateLongExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
|
||||
// if L==number
|
||||
// TODO reuse code from ifElse?
|
||||
val hex = number.toUInt().toString(16).padStart(8, '0')
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #${hex.substring(6, 8)}
|
||||
bne $falseLabel
|
||||
lda $varname+1
|
||||
cmp #${hex.substring(4, 6)}
|
||||
bne $falseLabel
|
||||
lda $varname+2
|
||||
cmp #${hex.substring(2, 4)}
|
||||
bne $falseLabel
|
||||
lda $varname+3
|
||||
cmp #${hex.take(2)}
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
cmp #${hex.substring(6, 8)}
|
||||
bne $falseLabel
|
||||
lda cx16.r0+1
|
||||
cmp #${hex.substring(4, 6)}
|
||||
bne $falseLabel
|
||||
lda cx16.r0+2
|
||||
cmp #${hex.substring(2, 4)}
|
||||
bne $falseLabel
|
||||
lda cx16.r0+3
|
||||
cmp #${hex.take(2)}
|
||||
bne $falseLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateLongExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
|
||||
// if L==variable
|
||||
// TODO reuse code from ifElse?
|
||||
val varname2 = asmgen.asmVariableName(variable)
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp $varname2
|
||||
bne $falseLabel
|
||||
lda $varname+1
|
||||
cmp $varname2+1
|
||||
bne $falseLabel
|
||||
lda $varname+2
|
||||
cmp $varname2+2
|
||||
bne $falseLabel
|
||||
lda $varname+3
|
||||
cmp $varname2+3
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
cmp $varname2
|
||||
bne $falseLabel
|
||||
lda cx16.r0+1
|
||||
cmp $varname2+1
|
||||
bne $falseLabel
|
||||
lda cx16.r0+2
|
||||
cmp $varname2+2
|
||||
bne $falseLabel
|
||||
lda cx16.r0+3
|
||||
cmp $varname2+3
|
||||
bne $falseLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateLongExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
|
||||
TODO("if expression LONG != number ${expr.position}")
|
||||
}
|
||||
|
||||
private fun translateLongExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
|
||||
// if L!=variable
|
||||
TODO("if expression LONG != variable ${expr.position}")
|
||||
}
|
||||
|
||||
private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) {
|
||||
// if w!=0
|
||||
// TODO reuse code from ifElse?
|
||||
@@ -369,6 +491,50 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateLongExprIsZero(expr: PtExpression, falseLabel: String) {
|
||||
// if L==0
|
||||
// TODO reuse code from ifElse?
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
ora $varname+1
|
||||
ora $varname+2
|
||||
ora $varname+3
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
ora cx16.r0+1
|
||||
ora cx16.r0+2
|
||||
ora cx16.r0+3
|
||||
bne $falseLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateLongExprIsNotZero(expr: PtExpression, falseLabel: String) {
|
||||
// if L!=0
|
||||
// TODO reuse code from ifElse?
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
ora $varname+1
|
||||
ora $varname+2
|
||||
ora $varname+3
|
||||
beq $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
|
||||
asmgen.out("""
|
||||
lda cx16.r0
|
||||
ora cx16.r0+1
|
||||
ora cx16.r0+2
|
||||
ora cx16.r0+3
|
||||
beq $falseLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
|
||||
// optimized code for byte comparisons with 0
|
||||
|
||||
|
||||
@@ -377,6 +377,7 @@ internal class ProgramAndVarsGen(
|
||||
dt.isSignedByte -> ".char"
|
||||
dt.isUnsignedWord || dt.isPointer -> ".word"
|
||||
dt.isSignedWord -> ".sint"
|
||||
dt.isLong -> ".dint"
|
||||
dt.isFloat -> ".byte"
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
@@ -418,14 +419,7 @@ internal class ProgramAndVarsGen(
|
||||
structtype.fields.withIndex().forEach { (index, field) ->
|
||||
val dt = field.first
|
||||
val varname = "f${index}"
|
||||
val type = when {
|
||||
dt.isBool || dt.isUnsignedByte -> ".byte"
|
||||
dt.isSignedByte -> ".char"
|
||||
dt.isUnsignedWord || dt.isPointer -> ".word"
|
||||
dt.isSignedWord -> ".sint"
|
||||
dt.isFloat -> ".byte" // TODO check that float bytes are passed as an array parameter
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
val type = asmTypeString(dt)
|
||||
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
|
||||
}
|
||||
asmgen.out(" .endstruct\n")
|
||||
@@ -571,6 +565,7 @@ internal class ProgramAndVarsGen(
|
||||
else when(dt) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
|
||||
BaseDataType.LONG -> asmgen.out("$name .sint ?")
|
||||
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
|
||||
else -> throw AssemblyError("weird dt for extravar $dt")
|
||||
}
|
||||
@@ -781,6 +776,7 @@ internal class ProgramAndVarsGen(
|
||||
dt.isSignedByte -> asmgen.out("${variable.name}\t.char ?")
|
||||
dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ?")
|
||||
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
|
||||
dt.isLong -> asmgen.out("${variable.name}\t.dint ?")
|
||||
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
|
||||
dt.isSplitWordArray -> {
|
||||
alignVar(variable.align)
|
||||
@@ -829,6 +825,7 @@ internal class ProgramAndVarsGen(
|
||||
// dt.isSignedByte -> asmgen.out("${variable.name}\t.char $initialValue")
|
||||
// dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
|
||||
// dt.isSignedWord -> asmgen.out("${variable.name}\t.sint $initialValue")
|
||||
// dt.isLong -> asmgen.out("${variable.name}\t.dint $initialValue")
|
||||
// dt.isFloat -> {
|
||||
// if(initialValue==0) {
|
||||
// asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
|
||||
@@ -905,6 +902,16 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .sint " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
dt.isLongArray -> {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
if (data.size <= 16)
|
||||
asmgen.out("$varname\t.dint ${data.joinToString()}")
|
||||
else {
|
||||
asmgen.out(varname)
|
||||
for (chunk in data.chunked(16))
|
||||
asmgen.out(" .dint " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
dt.isFloatArray -> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
val floatFills = array.map {
|
||||
@@ -1027,7 +1034,7 @@ internal class ProgramAndVarsGen(
|
||||
val number = it.number!!.toInt()
|
||||
"$" + number.toString(16).padStart(4, '0')
|
||||
}
|
||||
dt.isArray && dt.elementType().isSignedWord -> array.map {
|
||||
dt.isSignedWordArray -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if(number>=0)
|
||||
@@ -1035,6 +1042,14 @@ internal class ProgramAndVarsGen(
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
dt.isLongArray -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(8, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
require(expr.left.type.isLong && expr.right.type.isLong) {
|
||||
"both operands must be longs"
|
||||
}
|
||||
throw AssemblyError("expression should have been handled otherwise: long ${expr.operator} at ${expr.position}")
|
||||
}
|
||||
expr.type.isFloat -> {
|
||||
require(expr.left.type.isFloat && expr.right.type.isFloat) {
|
||||
"both operands must be floats"
|
||||
|
||||
@@ -10,8 +10,8 @@ internal enum class TargetStorageKind {
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
POINTER, // wherever the pointer variable points to
|
||||
VOID // assign nothing - used in multi-value assigns for void placeholders
|
||||
POINTER,
|
||||
VOID // assign nothing - used in multi-value assigns for void placeholders
|
||||
}
|
||||
|
||||
internal enum class SourceStorageKind {
|
||||
@@ -114,25 +114,17 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
RegisterOrPair.FAC2 -> {
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
|
||||
}
|
||||
RegisterOrPair.R0,
|
||||
RegisterOrPair.R1,
|
||||
RegisterOrPair.R2,
|
||||
RegisterOrPair.R3,
|
||||
RegisterOrPair.R4,
|
||||
RegisterOrPair.R5,
|
||||
RegisterOrPair.R6,
|
||||
RegisterOrPair.R7,
|
||||
RegisterOrPair.R8,
|
||||
RegisterOrPair.R9,
|
||||
RegisterOrPair.R10,
|
||||
RegisterOrPair.R11,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
RegisterOrPair.R14,
|
||||
RegisterOrPair.R15 -> {
|
||||
in Cx16VirtualRegisters -> {
|
||||
val dt = if(signed) DataType.WORD else DataType.UWORD
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
val dt = if(signed) DataType.LONG
|
||||
else
|
||||
TODO("unsigned long")
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
|
||||
}
|
||||
else -> throw AssemblyError("weird register $registers")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,7 @@ internal class AssignmentAsmGen(
|
||||
when (assign.target.datatype.base) {
|
||||
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> assignConstantByte(assign.target, num)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> assignConstantWord(assign.target, num)
|
||||
BaseDataType.LONG -> assignConstantLong(assign.target, num)
|
||||
BaseDataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
}
|
||||
@@ -222,6 +223,7 @@ internal class AssignmentAsmGen(
|
||||
BaseDataType.BOOL -> assignConstantByte(assign.target, if(num==0.0) 0 else 1)
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> assignConstantByte(assign.target, num.toInt())
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> assignConstantWord(assign.target, num.toInt())
|
||||
BaseDataType.LONG -> assignConstantLong(assign.target, num.toInt())
|
||||
BaseDataType.FLOAT -> assignConstantFloat(assign.target, num)
|
||||
BaseDataType.POINTER -> assignConstantWord(assign.target, num.toInt())
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
@@ -244,6 +246,7 @@ internal class AssignmentAsmGen(
|
||||
else
|
||||
assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
}
|
||||
targetDt.isLong -> assignVariableLong(assign.target, variable, assign.source.datatype)
|
||||
targetDt.isFloat -> assignVariableFloat(assign.target, variable)
|
||||
targetDt.isString -> assignVariableString(assign.target, variable)
|
||||
targetDt.isPointer -> assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
@@ -393,7 +396,7 @@ internal class AssignmentAsmGen(
|
||||
|
||||
// fallback assignmen through temporary pointer var
|
||||
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
@@ -536,7 +539,7 @@ internal class AssignmentAsmGen(
|
||||
private fun assignPrefixExpr(assign: AsmAssignment, value: PtPrefix, scope: IPtSubroutine?) {
|
||||
if(assign.target.array==null) {
|
||||
if(assign.source.datatype isAssignableTo assign.target.datatype || (assign.source.datatype.isBool && assign.target.datatype.isByte)) {
|
||||
if(assign.source.datatype.isIntegerOrBool) {
|
||||
if(assign.source.datatype.isWordOrByteOrBool) {
|
||||
val signed = assign.source.datatype.isSigned
|
||||
if(assign.source.datatype.isByteOrBool) {
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.A, signed)
|
||||
@@ -651,6 +654,10 @@ internal class AssignmentAsmGen(
|
||||
else -> throw AssemblyError("str return value type mismatch with target")
|
||||
}
|
||||
}
|
||||
returnDt== BaseDataType.LONG -> {
|
||||
// longs are in R0:R1 (r0=lsw, r1=msw)
|
||||
assignRegisterLong(target, RegisterOrPair.R0R1_32)
|
||||
}
|
||||
returnDt==BaseDataType.FLOAT -> {
|
||||
// float result from function sits in FAC1
|
||||
assignFAC1float(target)
|
||||
@@ -703,22 +710,8 @@ internal class AssignmentAsmGen(
|
||||
RegisterOrPair.AX -> assignVirtualRegister(target, RegisterOrPair.AX)
|
||||
RegisterOrPair.AY -> assignVirtualRegister(target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignVirtualRegister(target, RegisterOrPair.XY)
|
||||
RegisterOrPair.R0 -> assignVirtualRegister(target, RegisterOrPair.R0)
|
||||
RegisterOrPair.R1 -> assignVirtualRegister(target, RegisterOrPair.R1)
|
||||
RegisterOrPair.R2 -> assignVirtualRegister(target, RegisterOrPair.R2)
|
||||
RegisterOrPair.R3 -> assignVirtualRegister(target, RegisterOrPair.R3)
|
||||
RegisterOrPair.R4 -> assignVirtualRegister(target, RegisterOrPair.R4)
|
||||
RegisterOrPair.R5 -> assignVirtualRegister(target, RegisterOrPair.R5)
|
||||
RegisterOrPair.R6 -> assignVirtualRegister(target, RegisterOrPair.R6)
|
||||
RegisterOrPair.R7 -> assignVirtualRegister(target, RegisterOrPair.R7)
|
||||
RegisterOrPair.R8 -> assignVirtualRegister(target, RegisterOrPair.R8)
|
||||
RegisterOrPair.R9 -> assignVirtualRegister(target, RegisterOrPair.R9)
|
||||
RegisterOrPair.R10 -> assignVirtualRegister(target, RegisterOrPair.R10)
|
||||
RegisterOrPair.R11 -> assignVirtualRegister(target, RegisterOrPair.R11)
|
||||
RegisterOrPair.R12 -> assignVirtualRegister(target, RegisterOrPair.R12)
|
||||
RegisterOrPair.R13 -> assignVirtualRegister(target, RegisterOrPair.R13)
|
||||
RegisterOrPair.R14 -> assignVirtualRegister(target, RegisterOrPair.R14)
|
||||
RegisterOrPair.R15 -> assignVirtualRegister(target, RegisterOrPair.R15)
|
||||
in Cx16VirtualRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
in combinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
else -> {
|
||||
val sflag = returnValue.first.statusflag
|
||||
if(sflag!=null)
|
||||
@@ -771,6 +764,10 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPointer -> assignRegisterpairWord(target, register)
|
||||
target.datatype.isLong -> {
|
||||
require(register in combinedLongRegisters)
|
||||
assignRegisterLong(target, register)
|
||||
}
|
||||
else -> throw AssemblyError("expected byte or word")
|
||||
}
|
||||
}
|
||||
@@ -1125,7 +1122,7 @@ internal class AssignmentAsmGen(
|
||||
asmgen.out(" pla")
|
||||
asmgen.out(" sta cx16.r0 | sty cx16.r0+1")
|
||||
}
|
||||
asmgen.out(" jsr verafx.muls")
|
||||
asmgen.out(" jsr verafx.muls16")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
} else {
|
||||
@@ -1190,7 +1187,7 @@ internal class AssignmentAsmGen(
|
||||
asmgen.out("""
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
jsr verafx.muls""")
|
||||
jsr verafx.muls16""")
|
||||
} else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "prog8_math.multiply_words.multiplier")
|
||||
asmgen.out(" jsr prog8_math.multiply_words")
|
||||
@@ -1237,7 +1234,7 @@ internal class AssignmentAsmGen(
|
||||
asmgen.out(" jsr prog8_math.asl_byte_A")
|
||||
assignRegisterByte(target, CpuRegister.A, signed, true)
|
||||
return true
|
||||
} else {
|
||||
} else if(dt.isWord) {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, true)
|
||||
if(expr.operator==">>")
|
||||
@@ -1249,6 +1246,27 @@ internal class AssignmentAsmGen(
|
||||
asmgen.out(" jsr prog8_math.asl_word_AY")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
} else if(dt.isLong) {
|
||||
|
||||
if(target.kind==TargetStorageKind.VARIABLE) {
|
||||
asmgen.out(" pla")
|
||||
asmgen.assignExpressionTo(expr.left, target)
|
||||
require(expr.right.type.isByte)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.X)
|
||||
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname}")
|
||||
if (expr.operator == "<<") {
|
||||
asmgen.out(" jsr prog8_lib.long_shiftleftX_inplace")
|
||||
} else {
|
||||
asmgen.out(" jsr prog8_lib.long_shiftrightX_inplace")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R0R1_32, signed)
|
||||
asmgen.out(" pla | sta P8ZP_SCRATCH_REG")
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r0", expr.operator, "P8ZP_SCRATCH_REG")
|
||||
assignRegisterLong(target, RegisterOrPair.R0R1_32)
|
||||
return true
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -1368,12 +1386,32 @@ internal class AssignmentAsmGen(
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if(dt.isLong) {
|
||||
|
||||
if(target.kind==TargetStorageKind.VARIABLE) {
|
||||
asmgen.assignExpressionTo(expr.left, target)
|
||||
require(expr.right.type.isByte)
|
||||
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname}")
|
||||
asmgen.out(" ldx #$shifts")
|
||||
if (expr.operator == "<<") {
|
||||
asmgen.out(" jsr prog8_lib.long_shiftleftX_inplace")
|
||||
} else {
|
||||
asmgen.out(" jsr prog8_lib.long_shiftrightX_inplace")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R0R1_32, signed)
|
||||
augmentableAsmGen.inplacemodificationLongWithLiteralval("cx16.r0", expr.operator, shifts)
|
||||
assignRegisterLong(target, RegisterOrPair.R0R1_32)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun optimizedPlusMinExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
|
||||
// TODO can this use the target directly as intermediate, when it's a variable? To save the final assignRegister call.
|
||||
val dt = expr.type
|
||||
val left = expr.left
|
||||
val right = expr.right
|
||||
@@ -1688,9 +1726,87 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(dt.isLong) {
|
||||
return optimizedPlusMinLongExpr(expr, target)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun optimizedPlusMinLongExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
|
||||
val left = expr.left
|
||||
val right = expr.right
|
||||
when(right) {
|
||||
is PtIdentifier -> {
|
||||
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
asmgen.assignExpressionTo(left, target)
|
||||
val rightsym = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightsym
|
||||
ldy #>$rightsym
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}""")
|
||||
if (expr.operator == "+") {
|
||||
asmgen.out(" jsr prog8_lib.long_add_inplace")
|
||||
} else {
|
||||
asmgen.out(" jsr prog8_lib.long_sub_inplace")
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
TODO("add/subtract long ${target.position} - use simple expressions and temporary variables for now")
|
||||
}
|
||||
}
|
||||
is PtNumber -> {
|
||||
val hex = right.number.toInt().toString(16).padStart(8, '0')
|
||||
if (target.kind == TargetStorageKind.VARIABLE) {
|
||||
asmgen.assignExpressionTo(left, target)
|
||||
if (expr.operator == "+") {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
clc
|
||||
adc #$${hex.substring(6,8)}
|
||||
sta ${target.asmVarname}
|
||||
lda ${target.asmVarname}+1
|
||||
adc #$${hex.substring(4, 6)}
|
||||
sta ${target.asmVarname}+1
|
||||
lda ${target.asmVarname}+2
|
||||
adc #$${hex.substring(2, 4)}
|
||||
sta ${target.asmVarname}+2
|
||||
lda ${target.asmVarname}+3
|
||||
adc #$${hex.take(2)}
|
||||
sta ${target.asmVarname}+3""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
sec
|
||||
sbc #$${hex.substring(6, 8)}
|
||||
sta ${target.asmVarname}
|
||||
lda ${target.asmVarname}+1
|
||||
sbc #$${hex.substring(4, 6)}
|
||||
sta ${target.asmVarname}+1
|
||||
lda ${target.asmVarname}+2
|
||||
sbc #$${hex.substring(2, 4)}
|
||||
sta ${target.asmVarname}+2
|
||||
lda ${target.asmVarname}+3
|
||||
sbc #$${hex.take(2)}
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
TODO("add/subtract long ${target.position} - use simple expressions and temporary variables for now")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R2R3_32, expr.left.type.isSigned)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R0R1_32, expr.right.type.isSigned)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r2", expr.operator, "cx16.r0")
|
||||
assignRegisterLong(target, RegisterOrPair.R2R3_32)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizedPointerIndexPlusMinusByteIntoA(value: PtExpression, operator: String, mem: PtMemoryByte): Boolean {
|
||||
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
|
||||
val address = mem.address as? PtBinaryExpression
|
||||
@@ -1811,6 +1927,58 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
else if (expr.left.type.isLong && expr.right.type.isLong) {
|
||||
|
||||
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
if(expr.left is PtIdentifier) {
|
||||
asmgen.assignExpressionTo(expr.right, target)
|
||||
val varname = asmgen.asmVariableName(expr.left as PtIdentifier)
|
||||
asmgen.out("""
|
||||
lda #<$varname
|
||||
ldy #>$varname
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}""")
|
||||
when(expr.operator) {
|
||||
"|" -> asmgen.out(" jsr prog8_lib.long_or_inplace")
|
||||
"&" -> asmgen.out(" jsr prog8_lib.long_and_inplace")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.long_xor_inplace")
|
||||
else -> throw AssemblyError("wrong bitwise operator")
|
||||
}
|
||||
return true
|
||||
} else if(expr.right is PtIdentifier) {
|
||||
asmgen.assignExpressionTo(expr.left, target)
|
||||
val varname = asmgen.asmVariableName(expr.right as PtIdentifier)
|
||||
asmgen.out("""
|
||||
lda #<$varname
|
||||
ldy #>$varname
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}""")
|
||||
when(expr.operator) {
|
||||
"|" -> asmgen.out(" jsr prog8_lib.long_or_inplace")
|
||||
"&" -> asmgen.out(" jsr prog8_lib.long_and_inplace")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.long_xor_inplace")
|
||||
else -> throw AssemblyError("wrong bitwise operator")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R2R3_32, expr.left.type.isSigned)
|
||||
val constval = expr.right.asConstInteger()
|
||||
val varname = (expr.right as? PtIdentifier)?.name
|
||||
if(constval!=null)
|
||||
augmentableAsmGen.inplacemodificationLongWithLiteralval("cx16.r2", expr.operator, constval)
|
||||
else if(varname!=null)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r2", expr.operator, varname)
|
||||
else
|
||||
augmentableAsmGen.inplacemodificationLongWithExpression("cx16.r2", expr.operator, expr.right)
|
||||
assignRegisterLong(target, RegisterOrPair.R2R3_32)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2245,7 +2413,7 @@ $endLabel""")
|
||||
|
||||
fun assignViaExprEval(addressExpression: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out(" ldy #0")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
@@ -2320,6 +2488,9 @@ $endLabel""")
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base)
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
TODO("assign typecasted long to $targetDt ${value.position}")
|
||||
}
|
||||
valueDt.isWord || valueDt.isPointer -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base)
|
||||
@@ -2417,9 +2588,9 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
if(targetDt.isInteger && valueDt.isIntegerOrBool && valueDt.isAssignableTo(targetDt)) {
|
||||
require(targetDt.isWord && valueDt.isByteOrBool) {
|
||||
"should be byte to word assignment ${origTypeCastExpression.position}"
|
||||
if(targetDt.isInteger && valueDt.isByteOrBool && valueDt.isAssignableTo(targetDt)) {
|
||||
require(targetDt.isWord || targetDt.isLong) {
|
||||
"should be byte to word or long assignment ${origTypeCastExpression.position}"
|
||||
}
|
||||
when(target.kind) {
|
||||
// TargetStorageKind.VARIABLE -> {
|
||||
@@ -2440,7 +2611,7 @@ $endLabel""")
|
||||
return
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
// byte to word, just assign to registers
|
||||
// byte to word or long, just assign to registers
|
||||
assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
|
||||
return
|
||||
}
|
||||
@@ -2554,6 +2725,7 @@ $endLabel""")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> TODO("assign typecasted to LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
@@ -2581,6 +2753,11 @@ $endLabel""")
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
@@ -2609,6 +2786,7 @@ $endLabel""")
|
||||
BaseDataType.WORD -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> TODO("assign typecasted to LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
@@ -2638,6 +2816,14 @@ $endLabel""")
|
||||
BaseDataType.UWORD, BaseDataType.POINTER -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
sta $targetAsmVarName
|
||||
lda $sourceAsmVarName+1
|
||||
sta $targetAsmVarName+1""")
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
@@ -2698,6 +2884,7 @@ $endLabel""")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> TODO("assign typecasted to LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> asmgen.out(" tay")
|
||||
@@ -2737,6 +2924,7 @@ $endLabel""")
|
||||
asmgen.signExtendAYlsb(sourceDt)
|
||||
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> TODO("assign typecasted to LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> {}
|
||||
@@ -2768,6 +2956,7 @@ $endLabel""")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> TODO("assign typecasted to LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
if(regs!=RegisterOrPair.AY)
|
||||
throw AssemblyError("only supports AY here")
|
||||
@@ -2797,6 +2986,15 @@ $endLabel""")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
if(regs!=RegisterOrPair.AY)
|
||||
throw AssemblyError("only supports AY here")
|
||||
@@ -3003,6 +3201,112 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableLong(target: AsmAssignTarget, varName: String, sourceDt: DataType) {
|
||||
require(sourceDt.isByte || sourceDt.isWord || sourceDt.isLong) {
|
||||
"need byte/word/long as source value to assign to long variable $varName ${target.position}"
|
||||
}
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
when(sourceDt) {
|
||||
DataType.BYTE -> {
|
||||
TODO("signed byte to long var")
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
TODO("ubyte to long var")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
TODO("signed word to long var")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
TODO("ubyte to long var")
|
||||
}
|
||||
DataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta ${target.asmVarname}
|
||||
lda $varName+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $varName+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $varName+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
else -> throw AssemblyError("wrong dt ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
require(sourceDt.isLong)
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointergen.assignLongVar(IndexedPtrTarget(target), varName)
|
||||
return
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta ${target.asmVarname},y
|
||||
lda $varName+1
|
||||
sta ${target.asmVarname}+1,y
|
||||
lda $varName+2
|
||||
sta ${target.asmVarname}+2,y
|
||||
lda $varName+3
|
||||
sta ${target.asmVarname}+3,y""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
val regstart = target.register.toString().take(2).lowercase()
|
||||
when(sourceDt) {
|
||||
DataType.BYTE -> {
|
||||
asmgen.out(" lda $varName | sta cx16.$regstart")
|
||||
asmgen.signExtendLongVariable("cx16.$regstart", sourceDt.base)
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta cx16.$regstart
|
||||
lda #0
|
||||
sta cx16.$regstart+1
|
||||
sta cx16.$regstart+2
|
||||
sta cx16.$regstart+3""")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta cx16.$regstart
|
||||
lda $varName+1
|
||||
sta cx16.$regstart+1""")
|
||||
asmgen.signExtendLongVariable("cx16.$regstart", sourceDt.base)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta cx16.$regstart
|
||||
lda $varName+1
|
||||
sta cx16.$regstart+1
|
||||
lda #0
|
||||
sta cx16.$regstart+2
|
||||
sta cx16.$regstart+3""")
|
||||
}
|
||||
DataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta cx16.$regstart
|
||||
lda $varName+1
|
||||
sta cx16.$regstart+1
|
||||
lda $varName+2
|
||||
sta cx16.$regstart+2
|
||||
lda $varName+3
|
||||
sta cx16.$regstart+3""")
|
||||
}
|
||||
else -> throw AssemblyError("wrong dt ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableWord(target: AsmAssignTarget, varName: String, sourceDt: DataType) {
|
||||
if(sourceDt.isSignedByte) {
|
||||
// need to sign extend
|
||||
@@ -3482,6 +3786,49 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignRegisterLong(target: AsmAssignTarget, pairedRegisters: RegisterOrPair) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(pairedRegisters in combinedLongRegisters) {
|
||||
val startreg = pairedRegisters.startregname()
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
sta ${target.asmVarname}
|
||||
lda cx16.$startreg+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda cx16.$startreg+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda cx16.$startreg+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
else throw AssemblyError("only combined vreg allowed as long target ${target.position}")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
TODO("assign 32 bits int in R0:R1 into array ${target.position}")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val targetreg = target.register!!
|
||||
require(targetreg in combinedLongRegisters && pairedRegisters in combinedLongRegisters)
|
||||
if(targetreg!=pairedRegisters) {
|
||||
val sourceStartReg = pairedRegisters.startregname()
|
||||
val targetStartReg = targetreg.startregname()
|
||||
asmgen.out("""
|
||||
lda cx16.$sourceStartReg
|
||||
sta cx16.$targetStartReg
|
||||
lda cx16.$sourceStartReg+1
|
||||
sta cx16.$targetStartReg+1
|
||||
lda cx16.$sourceStartReg+2
|
||||
sta cx16.$targetStartReg+2
|
||||
lda cx16.$sourceStartReg+3
|
||||
sta cx16.$targetStartReg+3""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean, extendWord: Boolean) {
|
||||
val assignAsWord = target.datatype.isWord
|
||||
|
||||
@@ -3920,7 +4267,7 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
|
||||
@@ -3929,6 +4276,92 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantLong(target: AsmAssignTarget, long: Int) {
|
||||
if(long==0 && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
// optimize setting zero value for this processor
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
stz ${target.asmVarname}
|
||||
stz ${target.asmVarname}+1
|
||||
stz ${target.asmVarname}+2
|
||||
stz ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointergen.assignLong(IndexedPtrTarget(target), 0)
|
||||
return
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta ${target.asmVarname},y
|
||||
sta ${target.asmVarname}+1,y
|
||||
sta ${target.asmVarname}+2,y
|
||||
sta ${target.asmVarname}+3,y""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
fun store(hexbyte: String, offset: Int) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02) && hexbyte=="00") {
|
||||
asmgen.out(" stz ${target.asmVarname}+$offset")
|
||||
} else {
|
||||
asmgen.out(" lda #$$hexbyte | sta ${target.asmVarname}+$offset")
|
||||
}
|
||||
}
|
||||
val hex = long.toUInt().toString(16).padStart(8, '0')
|
||||
store(hex.substring(6,8), 0)
|
||||
store(hex.substring(4,6), 1)
|
||||
store(hex.substring(2,4), 2)
|
||||
store(hex.substring(0,2), 3)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointergen.assignLong(IndexedPtrTarget(target), long)
|
||||
return
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
val hex = long.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ${target.asmVarname},y
|
||||
lda #$${hex.substring(4, 6)}
|
||||
sta ${target.asmVarname}+1,y
|
||||
lda #$${hex.substring(2, 4)}
|
||||
sta ${target.asmVarname}+2,y
|
||||
lda #$${hex.take(2)}
|
||||
sta ${target.asmVarname}+3,y""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
val regstart = target.register!!.startregname()
|
||||
val hex = long.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta cx16.$regstart
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta cx16.$regstart+1
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta cx16.$regstart+2
|
||||
lda #$${hex.take(2)}
|
||||
sta cx16.$regstart+3""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
|
||||
if(word==0 && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
// optimize setting zero value for this processor
|
||||
@@ -3942,7 +4375,7 @@ $endLabel""")
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointergen.assignWord(IndexedPtrTarget(target), word)
|
||||
pointergen.assignWord(IndexedPtrTarget(target), 0)
|
||||
return
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
@@ -4518,6 +4951,26 @@ $endLabel""")
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
targetDt.isLong -> {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
eor #255
|
||||
sta ${target.asmVarname}
|
||||
lda ${target.asmVarname}+1
|
||||
eor #255
|
||||
sta ${target.asmVarname}+1
|
||||
lda ${target.asmVarname}+2
|
||||
eor #255
|
||||
sta ${target.asmVarname}+2
|
||||
lda ${target.asmVarname}+3
|
||||
eor #255
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
else -> TODO("LONG INVERT ${target.kind} ${target.position}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
}
|
||||
}
|
||||
@@ -4627,6 +5080,31 @@ $endLabel""")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
datatype.isLong -> {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sec
|
||||
sbc ${target.asmVarname}
|
||||
sta ${target.asmVarname}
|
||||
lda #0
|
||||
sbc ${target.asmVarname}+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda #0
|
||||
sbc ${target.asmVarname}+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda #0
|
||||
sbc ${target.asmVarname}+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO(" - long array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
|
||||
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
datatype.isFloat -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.REGISTER -> {
|
||||
|
||||
+484
-8
@@ -106,6 +106,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
target.datatype.isLong -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt())
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(target.asmVarname, operator, value.asmVarname)
|
||||
SourceStorageKind.EXPRESSION -> inplacemodificationLongWithExpression(target.asmVarname, operator, value.expression!!)
|
||||
SourceStorageKind.REGISTER -> TODO("32 bits register inplace modification? ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace modify long with array ${target.position}")
|
||||
SourceStorageKind.MEMORY -> TODO("memread into long ${target.position}")
|
||||
}
|
||||
}
|
||||
target.datatype.isFloat -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt().toDouble())
|
||||
@@ -291,6 +302,25 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
target.datatype.isLong -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.boolean!!.asInt())
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modifiication ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modifiication ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modifiication ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modifiication ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target.datatype.isFloat -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.boolean!!.asInt().toDouble())
|
||||
@@ -523,6 +553,448 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R0R1_32, value.type.isSigned)
|
||||
inplacemodificationLongWithVariable(targetVar, operator, "cx16.r0")
|
||||
}
|
||||
|
||||
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $targetVar
|
||||
adc $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
adc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
adc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
"-" -> {
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $targetVar
|
||||
sbc $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
sbc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
sbc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
sbc $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $sourceVar
|
||||
- asl $targetVar
|
||||
rol $targetVar+1
|
||||
rol $targetVar+2
|
||||
rol $targetVar+3
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
">>" -> {
|
||||
asmgen.out("""
|
||||
ldy $sourceVar
|
||||
- lda $targetVar+3
|
||||
asl a ; save sign bit
|
||||
ror $targetVar+3
|
||||
ror $targetVar+2
|
||||
ror $targetVar+1
|
||||
ror $targetVar
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
ora $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
ora $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
ora $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
ora $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
and $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
and $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
and $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
and $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
eor $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
eor $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
eor $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
eor $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
else -> {
|
||||
TODO("in-place modify LONG with variable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplacemodificationLongWithLiteralval(variable: String, operator: String, value: Int) {
|
||||
|
||||
fun inplaceLongShiftLeft() {
|
||||
when {
|
||||
value in 0..2 -> {
|
||||
repeat(value) {
|
||||
asmgen.out("""
|
||||
asl $variable
|
||||
rol $variable+1
|
||||
rol $variable+2
|
||||
rol $variable+3""")
|
||||
}
|
||||
}
|
||||
value in 3..7 -> {
|
||||
asmgen.out("""
|
||||
ldy #$value
|
||||
- asl $variable
|
||||
rol $variable+1
|
||||
rol $variable+2
|
||||
rol $variable+3
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
value == 8 -> {
|
||||
asmgen.out("""
|
||||
lda $variable+2
|
||||
sta $variable+3
|
||||
lda $variable+1
|
||||
sta $variable+2
|
||||
lda $variable
|
||||
sta $variable+1
|
||||
lda #0
|
||||
sta $variable""")
|
||||
}
|
||||
value in 9..15 -> {
|
||||
val shift = value - 8
|
||||
asmgen.out("""
|
||||
lda $variable+2
|
||||
sta $variable+3
|
||||
lda $variable+1
|
||||
sta $variable+2
|
||||
lda $variable
|
||||
sta $variable+1
|
||||
lda #0
|
||||
sta $variable
|
||||
ldy #$shift
|
||||
- asl $variable+1
|
||||
rol $variable+2
|
||||
rol $variable+3
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
value == 16 -> {
|
||||
asmgen.out("""
|
||||
lda $variable+1
|
||||
sta $variable+3
|
||||
lda $variable
|
||||
sta $variable+2
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1""")
|
||||
}
|
||||
value in 17..23 -> {
|
||||
val shift = value-16
|
||||
asmgen.out("""
|
||||
lda $variable+1
|
||||
sta $variable+3
|
||||
lda $variable
|
||||
sta $variable+2
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1
|
||||
ldy #$shift
|
||||
- asl $variable+2
|
||||
rol $variable+3
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
value == 24 -> {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
sta $variable+3
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+2""")
|
||||
}
|
||||
value <= 31 -> {
|
||||
val shift = value-24
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
ldy #$shift
|
||||
- asl a
|
||||
dey
|
||||
bne -
|
||||
sta $variable+3
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+2""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+2
|
||||
sta $variable+3""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inplaceLongShiftRight() {
|
||||
when {
|
||||
value in 0..2 -> {
|
||||
repeat(value) {
|
||||
asmgen.out("""
|
||||
lda $variable+3
|
||||
asl a ; save sign bit
|
||||
ror $variable+3
|
||||
ror $variable+2
|
||||
ror $variable+1
|
||||
ror $variable""")
|
||||
}
|
||||
}
|
||||
// TODO optimize for more cases 8, 16, 24 etc but don't forget to take the sign bit into account!
|
||||
value <= 31 -> {
|
||||
asmgen.out("""
|
||||
ldy #$value
|
||||
- lda $variable+3
|
||||
asl a ; save sign bit
|
||||
ror $variable+3
|
||||
ror $variable+2
|
||||
ror $variable+1
|
||||
ror $variable
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+2
|
||||
sta $variable+3""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
when(value) {
|
||||
0 -> {}
|
||||
1 -> {
|
||||
asmgen.out("""
|
||||
inc $variable
|
||||
bne +
|
||||
inc $variable+1
|
||||
bne +
|
||||
inc $variable+2
|
||||
bne +
|
||||
inc $variable+3
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
if(value in 1..255) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $variable
|
||||
adc #$value
|
||||
sta $variable
|
||||
bcc +
|
||||
inc $variable+1
|
||||
bne +
|
||||
inc $variable+2
|
||||
bne +
|
||||
inc $variable+3
|
||||
+""")
|
||||
} else if(value in 1..65535) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $variable
|
||||
adc #<$value
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
adc #>$value
|
||||
sta $variable+1
|
||||
bcc +
|
||||
inc $variable+2
|
||||
bne +
|
||||
inc $variable+3
|
||||
+""")
|
||||
} else {
|
||||
val hex = value.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $variable
|
||||
adc #$${hex.substring(6,8)}
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
adc #$${hex.substring(4, 6)}
|
||||
sta $variable+1
|
||||
lda $variable+2
|
||||
adc #$${hex.substring(2, 4)}
|
||||
sta $variable+2
|
||||
lda $variable+3
|
||||
adc #$${hex.take(2)}
|
||||
sta $variable+3""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
when(value) {
|
||||
0 -> {}
|
||||
1 -> {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
bne +
|
||||
dec $variable+1
|
||||
bne +
|
||||
dec $variable+2
|
||||
bne +
|
||||
dec $variable+3
|
||||
+ dec $variable""")
|
||||
}
|
||||
else -> {
|
||||
if(value in 1..255) {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
sec
|
||||
sbc #$value
|
||||
sta $variable
|
||||
bcs +
|
||||
dec $variable+1
|
||||
bne +
|
||||
dec $variable+2
|
||||
bne +
|
||||
dec $variable+3
|
||||
+""")
|
||||
} else if(value in 1..65535) {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
sec
|
||||
sbc #<$value
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
sbc #>$value
|
||||
sta $variable+1
|
||||
bcs +
|
||||
dec $variable+2
|
||||
bne +
|
||||
dec $variable+3
|
||||
+""")
|
||||
} else {
|
||||
val hex = value.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $variable
|
||||
sbc #$${hex.substring(6,8)}
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
sbc #$${hex.substring(4, 6)}
|
||||
sta $variable+1
|
||||
lda $variable+2
|
||||
sbc #$${hex.substring(2, 4)}
|
||||
sta $variable+2
|
||||
lda $variable+3
|
||||
sbc #$${hex.take(2)}
|
||||
sta $variable+3""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"<<" -> if (value > 0) inplaceLongShiftLeft()
|
||||
">>" -> if (value > 0) inplaceLongShiftRight()
|
||||
"|" -> {
|
||||
val hex = value.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
ora #$${hex.substring(6,8)}
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
ora #$${hex.substring(4, 6)}
|
||||
sta $variable+1
|
||||
lda $variable+2
|
||||
ora #$${hex.substring(2, 4)}
|
||||
sta $variable+2
|
||||
lda $variable+3
|
||||
ora #$${hex.take(2)}
|
||||
sta $variable+3""")
|
||||
}
|
||||
"&" -> {
|
||||
val hex = value.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
and #$${hex.substring(6,8)}
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
and #$${hex.substring(4, 6)}
|
||||
sta $variable+1
|
||||
lda $variable+2
|
||||
and #$${hex.substring(2, 4)}
|
||||
sta $variable+2
|
||||
lda $variable+3
|
||||
and #$${hex.take(2)}
|
||||
sta $variable+3""")
|
||||
}
|
||||
"^" -> {
|
||||
val hex = value.toUInt().toString(16).padStart(8, '0')
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
eor #$${hex.substring(6,8)}
|
||||
sta $variable
|
||||
lda $variable+1
|
||||
eor #$${hex.substring(4, 6)}
|
||||
sta $variable+1
|
||||
lda $variable+2
|
||||
eor #$${hex.substring(2, 4)}
|
||||
sta $variable+2
|
||||
lda $variable+3
|
||||
eor #$${hex.take(2)}
|
||||
sta $variable+3""")
|
||||
}
|
||||
else -> {
|
||||
TODO("inplace long $operator $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryIndexedIncDec(array: PtArrayIndexer, operator: String): Boolean {
|
||||
val arrayVar = array.variable
|
||||
if(arrayVar==null) {
|
||||
@@ -1851,7 +2323,7 @@ $shortcutLabel:""")
|
||||
if(value in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $lsb | ldy $msb | jsr prog8_math.mul_word_$value | sta $lsb | sty $msb")
|
||||
} else {
|
||||
if(block?.options?.veraFxMuls==true)
|
||||
if(block?.options?.veraFxMuls==true) {
|
||||
// cx16 verafx hardware mul
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
@@ -1862,9 +2334,10 @@ $shortcutLabel:""")
|
||||
ldy #>$value
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
jsr verafx.muls
|
||||
jsr verafx.muls16
|
||||
sta $lsb
|
||||
sty $msb""")
|
||||
}
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
@@ -2349,9 +2822,10 @@ $shortcutLabel:""")
|
||||
ldy $name+1
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
jsr verafx.muls
|
||||
jsr verafx.muls16
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
|
||||
} else {
|
||||
if(valueDt.isUnsignedByte) {
|
||||
asmgen.out(" lda $otherName | sta prog8_math.multiply_words.multiplier")
|
||||
@@ -2494,7 +2968,7 @@ $shortcutLabel:""")
|
||||
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
|
||||
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
|
||||
"*" -> {
|
||||
if(block?.options?.veraFxMuls==true)
|
||||
if(block?.options?.veraFxMuls==true) {
|
||||
// cx16 verafx hardware muls
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
@@ -2505,9 +2979,10 @@ $shortcutLabel:""")
|
||||
ldy $otherName+1
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
jsr verafx.muls
|
||||
jsr verafx.muls16
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
@@ -2698,7 +3173,7 @@ $shortcutLabel:""")
|
||||
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
|
||||
require(dt.isWord)
|
||||
fun multiplyVarByWordInAX() {
|
||||
if(block?.options?.veraFxMuls==true)
|
||||
if(block?.options?.veraFxMuls==true) {
|
||||
// cx16 verafx hardware muls
|
||||
asmgen.out("""
|
||||
sta cx16.r1
|
||||
@@ -2707,9 +3182,10 @@ $shortcutLabel:""")
|
||||
ldx $name+1
|
||||
sta cx16.r0
|
||||
stx cx16.r0+1
|
||||
jsr verafx.muls
|
||||
jsr verafx.muls16
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
else
|
||||
asmgen.out("""
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
@@ -3003,7 +3479,7 @@ $shortcutLabel:""")
|
||||
if(value is PtNumber && value.number<=255) {
|
||||
TODO("shift a word var by ${value.number}")
|
||||
} else {
|
||||
throw AssemblyError("shift by a word value not supported, max is a byte")
|
||||
throw AssemblyError("bit shift value can not be larger than a byte")
|
||||
}
|
||||
}
|
||||
"&" -> {
|
||||
|
||||
@@ -406,6 +406,10 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
TODO("array ptr assign const word ${target.position}")
|
||||
}
|
||||
|
||||
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
|
||||
TODO("array ptr assign const long ${target.position}")
|
||||
}
|
||||
|
||||
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
|
||||
TODO("array ptr assign const float ${target.position}")
|
||||
}
|
||||
@@ -442,6 +446,10 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
TODO("array ptr assign word var ${target.position}")
|
||||
}
|
||||
|
||||
internal fun assignLongVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign long var ${target.position}")
|
||||
}
|
||||
|
||||
internal fun operatorDereference(binExpr: PtBinaryExpression): Triple<String, UByte, DataType> {
|
||||
// the only case we support here is: a.b.c[i] . value
|
||||
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero), and finally the datatype of the field
|
||||
@@ -499,10 +507,9 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
private fun addUnsignedByteOrWordToAY(value: PtExpression, scale: Int?) {
|
||||
// TODO generate more optimal code from this, a bit too much stack and temporary variable juggling seems to be done in some easy cases...
|
||||
|
||||
fun complicatedFallback() {
|
||||
// slow fallback routine that can deal with any expression for value
|
||||
// slow fallback routine that can deal with any expression for value (const number and variable have been dealt with already)
|
||||
|
||||
fun restoreAYandAddAsmVariable(varname: String, isByte: Boolean) {
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false) // msb
|
||||
|
||||
@@ -12,12 +12,14 @@ internal object DummyMemsizer : IMemSizer {
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
|
||||
BaseDataType.LONG -> numElements*4
|
||||
BaseDataType.FLOAT -> numElements*5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class TestCodegen: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
||||
@@ -76,6 +76,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum), null)
|
||||
in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null)
|
||||
in combinedLongRegisters -> {
|
||||
require(returns.type.isLong)
|
||||
val startreg = returns.register.registerOrPair!!.startregname()
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.LONG, reg1=regNum, labelSymbol = "cx16.${startreg}"), null)
|
||||
}
|
||||
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum), null)
|
||||
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum), null)
|
||||
null -> if(returns.register.statusflag!=null)
|
||||
@@ -463,10 +468,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
|
||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||
val targetIdent = assignment.target.identifier
|
||||
val targetMemory = assignment.target.memory
|
||||
val targetArray = assignment.target.array
|
||||
val targetPointerDeref = assignment.target.pointerDeref
|
||||
val valueDt = irType(assignment.value.type)
|
||||
val targetDt = irType(assignment.target.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -507,98 +508,104 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
}
|
||||
|
||||
if(targetIdent!=null) {
|
||||
val instruction = if(zero) {
|
||||
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
|
||||
} else {
|
||||
if (targetDt == IRDataType.FLOAT) {
|
||||
require(valueFpRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
|
||||
}
|
||||
else {
|
||||
require(valueRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
|
||||
}
|
||||
}
|
||||
result += IRCodeChunk(null, null).also { it += instruction }
|
||||
return result
|
||||
}
|
||||
else if(targetArray!=null) {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(targetArray.type, null)
|
||||
val variable = targetArray.variable
|
||||
if(variable==null)
|
||||
translateRegularAssignPointerIndexed(result, targetArray.pointerderef!!, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else if(variable.type.isPointer)
|
||||
assignToIndexedSimplePointer(result, variable, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else
|
||||
translateRegularAssignArrayIndexed(result, variable.name, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
|
||||
return result
|
||||
}
|
||||
else if(targetMemory!=null) {
|
||||
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
|
||||
if(zero) {
|
||||
if(targetMemory.address is PtNumber) {
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
|
||||
result += chunk
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val constAddress = targetMemory.address as? PtNumber
|
||||
if(constAddress!=null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
|
||||
return result
|
||||
}
|
||||
val ptrWithOffset = targetMemory.address as? PtBinaryExpression
|
||||
if(ptrWithOffset!=null) {
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
|
||||
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
|
||||
if(constOffset in 0..255) {
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
|
||||
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
|
||||
}
|
||||
return result
|
||||
with(assignment.target) {
|
||||
when {
|
||||
identifier != null -> {
|
||||
val instruction = if(zero) {
|
||||
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = identifier!!.name)
|
||||
} else {
|
||||
if (targetDt == IRDataType.FLOAT) {
|
||||
require(valueFpRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = identifier!!.name)
|
||||
}
|
||||
else {
|
||||
require(valueRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = identifier!!.name)
|
||||
}
|
||||
}
|
||||
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
|
||||
// STOREIX only works with byte index.
|
||||
val tr = if(offsetTypecast?.value?.type?.isByte==true)
|
||||
expressionEval.translateExpression(offsetTypecast.value)
|
||||
else
|
||||
expressionEval.translateExpression(ptrWithOffset.right)
|
||||
result += IRCodeChunk(null, null).also { it += instruction }
|
||||
return result
|
||||
}
|
||||
memory != null -> {
|
||||
require(targetDt == IRDataType.BYTE) { "must be byte type ${memory!!.position}"}
|
||||
if(zero) {
|
||||
if(memory!!.address is PtNumber) {
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (memory!!.address as PtNumber).number.toInt()) }
|
||||
result += chunk
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(memory!!.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val constAddress = memory!!.address as? PtNumber
|
||||
if(constAddress!=null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
|
||||
return result
|
||||
}
|
||||
val ptrWithOffset = memory!!.address as? PtBinaryExpression
|
||||
if(ptrWithOffset!=null) {
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
|
||||
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
|
||||
if(constOffset in 0..255) {
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
|
||||
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
|
||||
// STOREIX only works with byte index.
|
||||
val tr = if(offsetTypecast?.value?.type?.isByte==true)
|
||||
expressionEval.translateExpression(offsetTypecast.value)
|
||||
else
|
||||
expressionEval.translateExpression(ptrWithOffset.right)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val tr = expressionEval.translateExpression(memory!!.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
array != null -> {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(array!!.type, null)
|
||||
val variable = array!!.variable
|
||||
if(variable==null)
|
||||
translateRegularAssignPointerIndexed(result, array!!.pointerderef!!, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else if(variable.type.isPointer)
|
||||
assignToIndexedSimplePointer(result, variable, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else
|
||||
translateRegularAssignArrayIndexed(result, variable.name, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
|
||||
return result
|
||||
}
|
||||
pointerDeref != null -> {
|
||||
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, pointerDeref!!)
|
||||
val actualValueReg = if(pointerDeref!!.type.isFloat) valueFpRegister else valueRegister
|
||||
codeGen.storeValueAtPointersLocation(result, addressReg, offset, pointerDeref!!.type, zero, actualValueReg)
|
||||
return result
|
||||
|
||||
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
|
||||
return result
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird assigntarget")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
else if(targetPointerDeref!=null) {
|
||||
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, targetPointerDeref)
|
||||
val actualValueReg = if(targetPointerDeref.type.isFloat) valueFpRegister else valueRegister
|
||||
codeGen.storeValueAtPointersLocation(result, addressReg, offset, targetPointerDeref.type, zero, actualValueReg)
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
}
|
||||
|
||||
private fun assignToIndexedSimplePointer(
|
||||
@@ -1102,14 +1109,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
if(constAddress==null && memory!=null)
|
||||
return null // TODO("optimized memory in-place -"")
|
||||
|
||||
val constValue = (operand as? PtNumber)?.number
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
if(constValue==1.0) {
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.DECM, vmDt, address = constAddress)
|
||||
else
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol)
|
||||
, null)
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
|
||||
}
|
||||
else {
|
||||
val tr = expressionEval.translateExpression(operand)
|
||||
@@ -1117,16 +1124,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, address = constAddress)
|
||||
else
|
||||
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, labelSymbol = symbol)
|
||||
, null)
|
||||
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, labelSymbol = symbol), null)
|
||||
}
|
||||
} else {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
if(constValue==1.0) {
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.DECM, vmDt, address = constAddress)
|
||||
else
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol)
|
||||
, null)
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
|
||||
}
|
||||
else {
|
||||
val tr = expressionEval.translateExpression(operand)
|
||||
@@ -1134,8 +1139,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, address = constAddress)
|
||||
else
|
||||
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
|
||||
, null)
|
||||
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -1201,9 +1205,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
return null // TODO("optimized memory in-place +"")
|
||||
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val constValue = (operand as? PtNumber)?.number
|
||||
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
if(constValue==1.0) {
|
||||
addInstr(result, if (constAddress != null)
|
||||
IRInstruction(Opcode.INCM, vmDt, address = constAddress)
|
||||
else
|
||||
@@ -1218,7 +1223,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
IRInstruction(Opcode.ADDM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol) , null)
|
||||
}
|
||||
} else {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
if(constValue==1.0) {
|
||||
addInstr(result, if (constAddress != null)
|
||||
IRInstruction(Opcode.INCM, vmDt, address = constAddress)
|
||||
else
|
||||
|
||||
@@ -14,7 +14,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
return when(call.name) {
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
|
||||
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(call)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
|
||||
@@ -24,25 +24,28 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"callfar" -> funcCallfar(call)
|
||||
"callfar2" -> funcCallfar2(call)
|
||||
"call" -> funcCall(call)
|
||||
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
|
||||
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
|
||||
"msw" -> funcMsw(call)
|
||||
"lsw" -> funcLsw(call)
|
||||
"msb" -> funcMsb(call)
|
||||
"lsb" -> funcLsb(call)
|
||||
"memory" -> funcMemory(call)
|
||||
"peek" -> funcPeek(call, IRDataType.BYTE)
|
||||
"peekbool" -> funcPeek(call, IRDataType.BYTE)
|
||||
"peekw" -> funcPeek(call, IRDataType.WORD)
|
||||
"peekl" -> funcPeek(call, IRDataType.LONG)
|
||||
"peekf" -> funcPeek(call, IRDataType.FLOAT)
|
||||
"poke" -> funcPoke(call, IRDataType.BYTE)
|
||||
"pokebool" -> funcPoke(call, IRDataType.BYTE)
|
||||
"pokebowl" -> funcPoke(call, IRDataType.BYTE)
|
||||
"pokew" -> funcPoke(call, IRDataType.WORD)
|
||||
"pokel" -> funcPoke(call, IRDataType.LONG)
|
||||
"pokef" -> funcPoke(call, IRDataType.FLOAT)
|
||||
"pokemon" -> funcPokemon(call)
|
||||
"mkword" -> funcMkword(call)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
|
||||
"mklong", "mklong2" -> funcMklong(call)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(call)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(call)
|
||||
"setlsb" -> funcSetLsbMsb(call, false)
|
||||
"setmsb" -> funcSetLsbMsb(call, true)
|
||||
"rol" -> funcRolRor(call)
|
||||
@@ -204,6 +207,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
|
||||
@@ -253,6 +267,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
BaseDataType.LONG -> TODO("SQRT LONG")
|
||||
BaseDataType.FLOAT -> {
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
@@ -283,6 +298,52 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMklong(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultReg = codeGen.registers.next(IRDataType.LONG)
|
||||
if(call.args.size==2) {
|
||||
// mklong2(word, word)
|
||||
if((call.args[0] as? PtNumber)?.number == 0.0) {
|
||||
// msw is 0, use EXT
|
||||
val lswTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lswTr, lswTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null)
|
||||
} else {
|
||||
val mswTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, mswTr, mswTr.resultReg, -1)
|
||||
val lswTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lswTr, lswTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null)
|
||||
}
|
||||
} else {
|
||||
// mklong(msb, b3, b2, lsb)
|
||||
if((call.args[0] as? PtNumber)?.number == 0.0 && (call.args[1] as? PtNumber)?.number == 0.0 && (call.args[2] as? PtNumber)?.number == 0.0) {
|
||||
// use EXT.b + EXT.w
|
||||
val lsbTr = exprGen.translateExpression(call.args[3])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
val wordReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=wordReg, reg2 = lsbTr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = wordReg), null)
|
||||
} else {
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
val b2Tr = exprGen.translateExpression(call.args[1])
|
||||
val b1Tr = exprGen.translateExpression(call.args[2])
|
||||
val lsbTr = exprGen.translateExpression(call.args[3])
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
addToResult(result, b2Tr, b2Tr.resultReg, -1)
|
||||
addToResult(result, b1Tr, b1Tr.resultReg, -1)
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
val lswReg = codeGen.registers.next(IRDataType.WORD)
|
||||
val mswReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null)
|
||||
}
|
||||
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.LONG, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val type = irType(call.type)
|
||||
@@ -307,6 +368,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
|
||||
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
|
||||
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
|
||||
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
@@ -525,6 +587,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcLsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
@@ -535,6 +606,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.MSIG, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val arg = call.args[0]
|
||||
|
||||
@@ -446,7 +446,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else (it as PtNumber).number.toInt()
|
||||
}
|
||||
when {
|
||||
elementDt.isIntegerOrBool -> {
|
||||
elementDt.isWordOrByteOrBool -> {
|
||||
if (elementDt.isByteOrBool) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
|
||||
if (elementDt.isWord) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
|
||||
val gottemLabel = codeGen.createLabelName()
|
||||
@@ -686,6 +686,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null)
|
||||
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
|
||||
}
|
||||
valueDt.isLong -> TODO("typecast long ${cast.position}")
|
||||
valueDt.isFloat -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -705,6 +706,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
|
||||
}
|
||||
BaseDataType.LONG -> TODO("cast UBYTE to LONG ${cast.position}")
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
@@ -721,6 +723,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
|
||||
}
|
||||
BaseDataType.LONG -> TODO("cast BYTE to LONG ${cast.position}")
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
@@ -743,6 +746,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
BaseDataType.WORD -> {
|
||||
actualResultReg2 = tr.resultReg
|
||||
}
|
||||
BaseDataType.LONG -> TODO("cast UWORD to LONG ${cast.position}")
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
@@ -768,6 +772,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
BaseDataType.UWORD -> {
|
||||
actualResultReg2 = tr.resultReg
|
||||
}
|
||||
BaseDataType.LONG -> TODO("cast WORD to LONG ${cast.position}}")
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
@@ -775,6 +780,33 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
when(valueDt.base) {
|
||||
BaseDataType.UBYTE, BaseDataType.BOOL -> {
|
||||
// ubyte to long: double sign extend
|
||||
val wordreg = codeGen.registers.next(IRDataType.WORD)
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
// byte to long: double sign extend
|
||||
val wordreg = codeGen.registers.next(IRDataType.WORD)
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultFpReg2 = codeGen.registers.next(IRDataType.FLOAT)
|
||||
when(valueDt.base) {
|
||||
|
||||
@@ -47,6 +47,7 @@ class IRCodeGen(
|
||||
ensureFirstChunkLabels(irProg)
|
||||
irProg.linkChunks()
|
||||
irProg.convertAsmChunks()
|
||||
irProg.splitSSAchunks()
|
||||
|
||||
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
|
||||
val optimizer = IRPeepholeOptimizer(irProg)
|
||||
@@ -1975,12 +1976,17 @@ class IRCodeGen(
|
||||
in Cx16VirtualRegisters -> {
|
||||
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
require(paramDt==IRDataType.LONG)
|
||||
val startreg = registerOrFlag.registerOrPair!!.startregname()
|
||||
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}")
|
||||
}
|
||||
null -> when(registerOrFlag.statusflag) {
|
||||
// TODO: do the statusflag argument as last
|
||||
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
|
||||
else -> throw AssemblyError("unsupported statusflag as param")
|
||||
}
|
||||
else -> throw AssemblyError("unsupported register arg")
|
||||
else -> throw AssemblyError("unsupported register arg $registerOrFlag")
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
@@ -161,10 +161,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
if(chunk.label!=null)
|
||||
return false
|
||||
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
||||
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
|
||||
// if the previous chunk doesn't end in a SSA branching instruction, flow continues into the next chunk, so they may be joined
|
||||
val lastInstruction = previous.instructions.lastOrNull()
|
||||
if(lastInstruction!=null)
|
||||
return lastInstruction.opcode !in OpcodesThatJump
|
||||
return lastInstruction.opcode !in OpcodesThatEndSSAblock
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -300,16 +300,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
// remove useless RETURN
|
||||
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
|
||||
val previous = chunk.instructions[idx-1]
|
||||
if(previous.opcode in OpcodesThatJump) {
|
||||
if(previous.opcode in OpcodesThatBranchUnconditionally) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
// replace subsequent opcodes that jump by just the first
|
||||
if(idx>0 && (ins.opcode in OpcodesThatJump)) {
|
||||
if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) {
|
||||
val previous = chunk.instructions[idx-1]
|
||||
if(previous.opcode in OpcodesThatJump) {
|
||||
if(previous.opcode in OpcodesThatBranchUnconditionally) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
@@ -362,7 +362,7 @@ jump p8_label_gen_2
|
||||
*/
|
||||
var changed=false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx>=2 && ins.opcode in OpcodesThatJump) {
|
||||
if(idx>=2 && ins.opcode in OpcodesThatBranchUnconditionally) {
|
||||
val previous = indexedInstructions[idx-1].value
|
||||
val previous2 = indexedInstructions[idx-2].value
|
||||
if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) {
|
||||
|
||||
@@ -7,6 +7,7 @@ internal class RegisterPool {
|
||||
// everything from 99000 onwards is reserved for special purposes:
|
||||
// 99000 - 99099 : WORD registers for syscall arguments and response value(s)
|
||||
// 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
|
||||
// 99200 - 99299 : LONG registers for syscall arguments and response value(s)
|
||||
|
||||
private var nextRegister: Int=1
|
||||
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
|
||||
@@ -18,6 +19,8 @@ internal class RegisterPool {
|
||||
registerTypes[i] = IRDataType.WORD
|
||||
for(i in 99100..99199)
|
||||
registerTypes[i] = IRDataType.BYTE
|
||||
for(i in 99200..99299)
|
||||
registerTypes[i] = IRDataType.LONG
|
||||
}
|
||||
|
||||
fun next(type: IRDataType): Int {
|
||||
|
||||
@@ -10,12 +10,14 @@ internal object DummyMemsizer : IMemSizer {
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
|
||||
BaseDataType.LONG -> numElements*4
|
||||
BaseDataType.FLOAT -> numElements*5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
noSysInit = true,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ class TestVmCodeGen: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
@@ -340,7 +341,7 @@ class TestVmCodeGen: FunSpec({
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
irChunks.size shouldBe 1
|
||||
irChunks.size shouldBe 2
|
||||
}
|
||||
|
||||
test("integer comparison expressions against zero") {
|
||||
@@ -536,7 +537,7 @@ class TestVmCodeGen: FunSpec({
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
irChunks.size shouldBe 1
|
||||
irChunks.size shouldBe 2
|
||||
}
|
||||
|
||||
test("extsub allowed in ir-codegen") {
|
||||
|
||||
@@ -39,7 +39,10 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// see if LONG values may be reduced to something smaller
|
||||
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
|
||||
if(smaller.type!=BaseDataType.LONG) {
|
||||
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
|
||||
if(parent !is Assignment || !parent.target.inferType(program).isLong) {
|
||||
// do NOT reduce the type if the target of the assignment is a long
|
||||
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +88,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
@@ -438,7 +443,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val numval = decl.value as? NumericLiteral
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(valueDt issimpletype BaseDataType.LONG) {
|
||||
if(valueDt issimpletype BaseDataType.LONG || decl.datatype.isLong) {
|
||||
return noModifications // this is handled in the numericalvalue case
|
||||
}
|
||||
if(!(valueDt istype decl.datatype)) {
|
||||
|
||||
@@ -28,17 +28,13 @@ class VarConstantValueTypeAdjuster(
|
||||
if(decl.parent is AnonymousScope)
|
||||
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
|
||||
|
||||
try {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& declConstValue.type != decl.datatype.base) {
|
||||
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
|
||||
// avoid silent float roundings
|
||||
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
|
||||
}
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& declConstValue.type != decl.datatype.base) {
|
||||
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
|
||||
// avoid silent float roundings
|
||||
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
}
|
||||
|
||||
// replace variables by constants, if possible
|
||||
@@ -173,7 +169,12 @@ class VarConstantValueTypeAdjuster(
|
||||
val replaceFunc = if(t1.isBytes) {
|
||||
if(t1 issimpletype BaseDataType.BYTE) "clamp__byte" else "clamp__ubyte"
|
||||
} else if(t1.isInteger) {
|
||||
if(t1 issimpletype BaseDataType.WORD) "clamp__word" else "clamp__uword"
|
||||
when {
|
||||
t1 issimpletype BaseDataType.WORD -> "clamp__word"
|
||||
t1 issimpletype BaseDataType.UWORD -> "clamp__uword"
|
||||
t1 issimpletype BaseDataType.LONG -> "clamp__long"
|
||||
else -> throw FatalAstException("clamp type")
|
||||
}
|
||||
} else {
|
||||
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
|
||||
return noModifications
|
||||
@@ -195,10 +196,12 @@ class VarConstantValueTypeAdjuster(
|
||||
else
|
||||
"${funcName}__ubyte"
|
||||
} else if(t1.isInteger && t2.isInteger) {
|
||||
replaceFunc = if(t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD)
|
||||
"${funcName}__word"
|
||||
else
|
||||
"${funcName}__uword"
|
||||
replaceFunc = when {
|
||||
t1 issimpletype BaseDataType.LONG || t2 issimpletype BaseDataType.LONG -> "${funcName}__long"
|
||||
t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD -> "${funcName}__word"
|
||||
t1 issimpletype BaseDataType.UWORD || t2 issimpletype BaseDataType.UWORD -> "${funcName}__uword"
|
||||
else -> throw FatalAstException("min/max type")
|
||||
}
|
||||
} else if(t1.isNumeric && t2.isNumeric) {
|
||||
errors.err("min/max not supported for floats", functionCallExpr.position)
|
||||
return noModifications
|
||||
@@ -218,6 +221,7 @@ class VarConstantValueTypeAdjuster(
|
||||
val replaceFunc = when {
|
||||
dt.isSignedByte -> "abs__byte"
|
||||
dt.isSignedWord -> "abs__word"
|
||||
dt.isLong -> "abs__long"
|
||||
dt.isFloat -> "abs__float"
|
||||
dt.isUnsignedByte || dt.isUnsignedWord -> {
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
|
||||
@@ -313,41 +317,36 @@ internal class ConstantIdentifierReplacer(
|
||||
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
|
||||
return noModifications
|
||||
|
||||
try {
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
val arrayIdx = identifier.parent as? ArrayIndexedExpression
|
||||
if(arrayIdx!=null && cval.type.isNumeric) {
|
||||
// special case when the identifier is used as a pointer var
|
||||
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
|
||||
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
|
||||
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
|
||||
return if(arrayIdx.parent is AssignTarget) {
|
||||
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||
}
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
val arrayIdx = identifier.parent as? ArrayIndexedExpression
|
||||
if(arrayIdx!=null && cval.type.isNumeric) {
|
||||
// special case when the identifier is used as a pointer var
|
||||
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
|
||||
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
|
||||
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
|
||||
return if(arrayIdx.parent is AssignTarget) {
|
||||
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||
}
|
||||
when {
|
||||
cval.type.isNumericOrBool -> {
|
||||
if(parent is AddressOf)
|
||||
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(cval.type, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
}
|
||||
when {
|
||||
cval.type.isNumericOrBool -> {
|
||||
if(parent is AddressOf)
|
||||
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(cval.type, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
}
|
||||
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
|
||||
else -> return noModifications
|
||||
)
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
return noModifications
|
||||
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
@@ -90,6 +91,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
val newExpr = applyAbsorptionLaws(expr)
|
||||
if(newExpr!=null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
@@ -244,15 +247,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (rightVal?.number == 1.0) {
|
||||
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rightVal?.number == 0.0) {
|
||||
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,15 +273,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (rightVal?.number == 1.0) {
|
||||
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rightVal?.number == 0.0) {
|
||||
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,19 +435,92 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
}
|
||||
|
||||
// fun isPowerOfTwo(number: Double): Boolean {
|
||||
// val intValue = number.toInt()
|
||||
// return intValue > 0 && (intValue and (intValue - 1)) == 0
|
||||
// }
|
||||
|
||||
fun isFactorOf256(number: Double): Boolean {
|
||||
val intValue = number.toInt()
|
||||
return intValue >= 256 && (intValue and 0xFF) == 0
|
||||
}
|
||||
|
||||
if (leftDt.isUnsignedWord && rightVal!=null) {
|
||||
if (expr.operator == ">" && rightVal.number == 255.0 || expr.operator == ">=" && rightVal.number == 256.0) {
|
||||
// uword > 255 --> msb(value)!=0
|
||||
// uword >= 256 --> msb(value)!=0
|
||||
expr.operator = "!="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator==">=" && isFactorOf256(rightVal.number)) {
|
||||
// uword >= $xx00 --> msb(value)>=xx
|
||||
val msbFraction = floor(rightVal.number / 256.0)
|
||||
expr.operator = ">="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator==">" && isFactorOf256(rightVal.number+1)) {
|
||||
// uword > $xxFF --> msb(value)>xx
|
||||
val msbFraction = floor((rightVal.number) / 256.0)
|
||||
expr.operator = ">"
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator == "<" && rightVal.number == 256.0 || expr.operator == "<=" && rightVal.number == 255.0) {
|
||||
// uword < 256 --> msb(value)==0
|
||||
// uword <= 255 --> msb(value)==0
|
||||
expr.operator = "=="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator == "<" && isFactorOf256(rightVal.number)) {
|
||||
// uword < $xx00 --> msb(value)<xx
|
||||
val msbFraction = floor(rightVal.number / 256.0)
|
||||
expr.operator = "<"
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator=="<=" && isFactorOf256(rightVal.number+1)) {
|
||||
// uword <= $xxFF --> msb(value)<=xx
|
||||
val msbFraction = floor((rightVal.number) / 256.0)
|
||||
expr.operator = "<="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(arrayIndexedExpression.indexer.constIndex()==0) {
|
||||
if(arrayIndexedExpression.plainarrayvar!=null) {
|
||||
if((arrayIndexedExpression.parent as? BinaryExpression)?.operator !=".") {
|
||||
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
|
||||
if(binexprParent?.operator!=".") {
|
||||
val dt = arrayIndexedExpression.plainarrayvar!!.inferType(program).getOrUndef()
|
||||
if(dt.isPointer) {
|
||||
// pointer[0] --> pointer^^
|
||||
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
|
||||
}
|
||||
} else if(arrayIndexedExpression.pointerderef==null) {
|
||||
// possibly pointer[0].field --> pointer.field
|
||||
val target = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
|
||||
if(target?.datatype?.isPointer==true) {
|
||||
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
|
||||
if(field!=null) {
|
||||
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val ptrDeref = arrayIndexedExpression.pointerderef
|
||||
|
||||
@@ -2,7 +2,8 @@ plugins {
|
||||
id("application")
|
||||
kotlin("jvm")
|
||||
// id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
id("io.github.goooler.shadow") version "8.1.8"
|
||||
// id("io.github.goooler.shadow") version "8.1.8"
|
||||
id("com.gradleup.shadow") version "9.2.2"
|
||||
id("com.peterabeles.gversion") version "1.10.3"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
bcd {
|
||||
; Decimal addition an subtraction routines.
|
||||
; For CPUs that support BCD mode (binary coded decimal) (not all 6502 variants support this mode...)
|
||||
; This is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string. Just print the hex representation.
|
||||
|
||||
sub addb(byte a, byte b) -> byte {
|
||||
setbcd()
|
||||
a += b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub addub(ubyte a, ubyte b) -> ubyte {
|
||||
setbcd()
|
||||
a += b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub addw(word a, word b) -> word {
|
||||
setbcd()
|
||||
a += b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub adduw(uword a, uword b) -> uword {
|
||||
setbcd()
|
||||
a += b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub addl(long a, long b) -> long {
|
||||
setbcd()
|
||||
a += b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub subb(byte a, byte b) -> byte {
|
||||
setbcd()
|
||||
a -= b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub subub(ubyte a, ubyte b) -> ubyte {
|
||||
setbcd()
|
||||
a -= b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub subuw(uword a, uword b) -> uword {
|
||||
setbcd()
|
||||
a -= b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
sub subl(long a, long b) -> long {
|
||||
setbcd()
|
||||
a -= b
|
||||
clearbcd()
|
||||
return a
|
||||
}
|
||||
|
||||
inline asmsub setbcd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sed
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clearbcd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
ldx cx16.r0+1
|
||||
ldy cx16.r0+2
|
||||
jmp SETTIM
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM
|
||||
sta cx16.r0
|
||||
stx cx16.r0+1
|
||||
sty cx16.r0+2
|
||||
lda #0
|
||||
sta cx16.r0+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub CLEARST() {
|
||||
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
|
||||
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
|
||||
@@ -506,6 +529,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
@@ -513,7 +537,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
@@ -545,6 +569,7 @@ _vector .word ?
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda #<cbm.IRQDFRT
|
||||
sta cbm.CINV
|
||||
@@ -554,24 +579,34 @@ asmsub restore_irq() clobbers(A) {
|
||||
sta c64.IREQMASK ; enable raster irq
|
||||
lda #%10000001
|
||||
sta c64.CIA1ICR ; restore CIA1 irq
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
sta user_vector
|
||||
sty user_vector+1
|
||||
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr _setup_raster_irq
|
||||
jsr sys.set_rasterline
|
||||
lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
@@ -593,34 +628,46 @@ _raster_irq_handler
|
||||
pla
|
||||
rti
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
jmp (user_vector)
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
user_vector .word ?
|
||||
.send BSS
|
||||
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
and c64.SCROLY
|
||||
sta c64.SCROLY ; clear most significant bit of raster position
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
pla
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
cpy #0
|
||||
beq +
|
||||
lda c64.SCROLY
|
||||
ora #%10000000
|
||||
sta c64.SCROLY ; set most significant bit of raster position
|
||||
+ lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
rts
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; -- just update the IRQ handler and raster line position for the raster IRQ
|
||||
; this is much more efficient than calling set_rasterirq() again every time.
|
||||
; (but you have to call that one initially at least once to setup the prog8 handler itself)
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta sys.set_rasterirq.user_vector
|
||||
sty sys.set_rasterirq.user_vector+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr sys.set_rasterline
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
; -- only set a new raster line for the raster IRQ
|
||||
%asm {{
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
lda c64.SCROLY
|
||||
and #%01111111
|
||||
cpy #0
|
||||
beq +
|
||||
ora #%10000000
|
||||
+ sta c64.SCROLY ; clear most significant bit of raster position
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
@@ -680,6 +727,26 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitrasterline(uword line @AY) {
|
||||
; -- CPU busy wait until the given raster line is reached
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _larger
|
||||
- cmp c64.RASTER
|
||||
bne -
|
||||
bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
_larger
|
||||
cmp c64.RASTER
|
||||
bne _larger
|
||||
bit c64.SCROLY
|
||||
bpl _larger
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
@@ -989,6 +1056,48 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
lda cx16.r0+1
|
||||
pha
|
||||
lda cx16.r0+2
|
||||
pha
|
||||
lda cx16.r0+3
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
pla
|
||||
sta cx16.r0+2
|
||||
pla
|
||||
sta cx16.r0+1
|
||||
pla
|
||||
sta cx16.r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
; The SuperCPU expansion for the C64/C128 contains a 65816.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -1165,12 +1274,6 @@ cx16 {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
||||
@@ -132,6 +132,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
ldx cx16.r0+1
|
||||
ldy cx16.r0+2
|
||||
jmp SETTIM
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM
|
||||
sta cx16.r0
|
||||
stx cx16.r0+1
|
||||
sty cx16.r0+2
|
||||
lda #0
|
||||
sta cx16.r0+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub CLEARST() {
|
||||
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
|
||||
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
|
||||
@@ -313,12 +336,13 @@ asmsub banks(ubyte banks @A) {
|
||||
%asm {{
|
||||
and #%00000111
|
||||
sta P8ZP_SCRATCH_REG
|
||||
php
|
||||
sei
|
||||
lda $01
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta $01
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -511,6 +535,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
@@ -518,7 +543,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
@@ -551,6 +576,7 @@ _vector .word ?
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda #<cbm.IRQDFRT
|
||||
sta cbm.CINV
|
||||
@@ -560,24 +586,34 @@ asmsub restore_irq() clobbers(A) {
|
||||
sta c64.IREQMASK ; disable raster irq
|
||||
lda #%10000001
|
||||
sta c64.CIA1ICR ; restore CIA1 irq
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
sta user_vector
|
||||
sty user_vector+1
|
||||
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr _setup_raster_irq
|
||||
jsr sys.set_rasterline
|
||||
lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
@@ -600,33 +636,46 @@ _raster_irq_handler
|
||||
rti
|
||||
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
jmp (user_vector)
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
user_vector .word ?
|
||||
.send BSS
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
and c64.SCROLY
|
||||
sta c64.SCROLY ; clear most significant bit of raster position
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
pla
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
cpy #0
|
||||
beq +
|
||||
lda c64.SCROLY
|
||||
ora #%10000000
|
||||
sta c64.SCROLY ; set most significant bit of raster position
|
||||
+ lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
rts
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; -- just update the IRQ handler and raster line position for the raster IRQ
|
||||
; this is much more efficient than calling set_rasterirq() again every time.
|
||||
; (but you have to call that one initially at least once to setup the prog8 handler itself)
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta sys.set_rasterirq.user_vector
|
||||
sty sys.set_rasterirq.user_vector+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr sys.set_rasterline
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
; -- only set a new raster line for the raster IRQ
|
||||
%asm {{
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
lda c64.SCROLY
|
||||
and #%01111111
|
||||
cpy #0
|
||||
beq +
|
||||
ora #%10000000
|
||||
+ sta c64.SCROLY ; clear most significant bit of raster position
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
@@ -687,6 +736,26 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitrasterline(uword line @AY) {
|
||||
; -- CPU busy wait until the given raster line is reached
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _larger
|
||||
- cmp c64.RASTER
|
||||
bne -
|
||||
bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
_larger
|
||||
cmp c64.RASTER
|
||||
bne _larger
|
||||
bit c64.SCROLY
|
||||
bpl _larger
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
@@ -995,6 +1064,49 @@ _no_msb_size
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
lda cx16.r0+1
|
||||
pha
|
||||
lda cx16.r0+2
|
||||
pha
|
||||
lda cx16.r0+3
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
pla
|
||||
sta cx16.r0+2
|
||||
pla
|
||||
sta cx16.r0+1
|
||||
pla
|
||||
sta cx16.r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
; The SuperCPU expansion for the C64/C128 contains a 65816.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -1172,12 +1284,6 @@ cx16 {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
||||
+108
-10
@@ -155,6 +155,30 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
sub str_ulhex(long value) -> str {
|
||||
%asm {{
|
||||
lda value+3
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out
|
||||
sty string_out+1
|
||||
lda value+2
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out+2
|
||||
sty string_out+3
|
||||
lda value+1
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out+4
|
||||
sty string_out+5
|
||||
lda value
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out+6
|
||||
sty string_out+7
|
||||
lda #0
|
||||
sta string_out+8
|
||||
}}
|
||||
return &string_out
|
||||
}
|
||||
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
@@ -228,15 +252,95 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
sub str_l (long value) -> str {
|
||||
; ---- convert the long value into decimal string form, without left padding 0s
|
||||
; source: https://codebase64.net/doku.php?id=base:32_bit_hexadecimal_to_decimal_conversion
|
||||
; NOTE: this is a pretty slow conversion, don't use for time critical displays!
|
||||
bool negative
|
||||
if value<0 {
|
||||
negative = true
|
||||
value = -value
|
||||
}
|
||||
%asm {{
|
||||
jsr hex2dec
|
||||
|
||||
ldx #0
|
||||
- lda result,x
|
||||
bne +
|
||||
inx ; skip leading zeros
|
||||
cpx #9
|
||||
bne -
|
||||
|
||||
; convert to petscii numbers and return ptr to first nonzero
|
||||
+ stx P8ZP_SCRATCH_B1
|
||||
inc P8ZP_SCRATCH_B1
|
||||
- lda result,x
|
||||
ora #$30
|
||||
sta result,x
|
||||
inx
|
||||
cpx #10
|
||||
bne -
|
||||
lda #0
|
||||
sta result,x
|
||||
; sign needed?
|
||||
lda negative
|
||||
beq +
|
||||
dec P8ZP_SCRATCH_B1
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
lda #'-'
|
||||
sta result_sign,x
|
||||
+ lda #<result_sign
|
||||
ldy #>result_sign
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
iny
|
||||
+ rts
|
||||
|
||||
; converts 10 digits (32 bit values have max. 10 decimal digits)
|
||||
hex2dec
|
||||
ldx #9
|
||||
l3 jsr div10
|
||||
sta result,x
|
||||
dex
|
||||
bpl l3
|
||||
rts
|
||||
|
||||
; divides a 32 bit value by 10
|
||||
; remainder is returned in akku
|
||||
div10
|
||||
ldy #32 ; 32 bits
|
||||
lda #0
|
||||
clc
|
||||
l4 rol
|
||||
cmp #10
|
||||
bcc skip
|
||||
sbc #10
|
||||
skip rol value
|
||||
rol value+1
|
||||
rol value+2
|
||||
rol value+3
|
||||
dey
|
||||
bpl l4
|
||||
rts
|
||||
|
||||
.section BSS
|
||||
result_sign .byte ? ; room for the '-' sign
|
||||
result .fill 10
|
||||
.byte ? ; string 0 terminator
|
||||
.send
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- string conversion to numbers -----
|
||||
|
||||
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
asmsub any2uword(str string @AY) -> uword @AY, ubyte @X {
|
||||
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
|
||||
; (the latter two require a $ or % prefix to be recognised)
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
|
||||
; if the string was invalid, 0 will be returned in A.
|
||||
; returns the parsed number word in AY, and the number of processed characters (including the prefix symbol) in X.
|
||||
; if the string was invalid, 0 will be returned as count in X (and the word value in AY will be undefined).
|
||||
%asm {{
|
||||
pha
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@@ -257,13 +361,7 @@ _hex pla
|
||||
_bin pla
|
||||
jsr bin2uword
|
||||
_result
|
||||
pha
|
||||
lda cx16.r15
|
||||
sta P8ZP_SCRATCH_B1 ; result value
|
||||
pla
|
||||
sta cx16.r15
|
||||
sty cx16.r15+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
ldx cx16.r15
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ palette {
|
||||
; NOTE: this routine requires rom version 49+
|
||||
alias set_default = cx16.set_default_palette
|
||||
|
||||
; get the bank and address of the word-array containing the 256 default palette colors
|
||||
; get the bank and address (in A, and XY) of the word-array containing the 256 default palette colors
|
||||
; NOTE: this routine requires rom version 49+
|
||||
alias get_default = cx16.get_default_palette
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
; Simple routines to control sprites.
|
||||
|
||||
; They're not written for high performance, but for simplicity.
|
||||
; That's why they control 1 sprite at a time. \
|
||||
; That's why they control 1 sprite at a time.
|
||||
; The exceptions are pos_batch() and pos_batch_split(); these are quite efficient
|
||||
; to update sprite positions of multiple sprites in one call.
|
||||
|
||||
; HIGH PERFORMANCE sprite handling would probably have a copy of the sprite registers for each sprite instead,
|
||||
; and a unrolled loop that copies those into the VERA registers when needed.
|
||||
|
||||
; note: sprites z-order will be in front of all layers.
|
||||
; note: collision mask is not supported here yet.
|
||||
; note: "palette offset" is counted as 0-15 (vera multiplies the offset by 16 to get at the actual color index)
|
||||
|
||||
@@ -127,6 +127,28 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
ldx cx16.r0+1
|
||||
ldy cx16.r0+2
|
||||
jmp SETTIM
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
; -- like RDTIM_safe() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM_safe
|
||||
sta cx16.r0
|
||||
stx cx16.r0+1
|
||||
sty cx16.r0+2
|
||||
stz cx16.r0+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub CLEARST() {
|
||||
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
|
||||
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
|
||||
@@ -1454,22 +1476,6 @@ sub search_x16edit() -> ubyte {
|
||||
return 255
|
||||
}
|
||||
|
||||
asmsub cpu_is_65816() -> bool @A {
|
||||
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub set_program_args(str args_ptr, ubyte args_size) {
|
||||
; -- Set the inter-program arguments.
|
||||
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
|
||||
@@ -1576,6 +1582,7 @@ sys {
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
; Sets the handler for the VSYNC interrupt, and enable that interrupt.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
@@ -1585,7 +1592,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta cbm.CINV+1
|
||||
lda #1
|
||||
tsb cx16.VERA_IEN ; enable the vsync irq
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_irq_handler
|
||||
@@ -1615,6 +1622,7 @@ _vector .word ?
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda _orig_irqvec
|
||||
sta cbm.CINV
|
||||
@@ -1624,7 +1632,7 @@ asmsub restore_irq() clobbers(A) {
|
||||
and #%11110000 ; disable all Vera IRQs but the vsync
|
||||
ora #%00000001
|
||||
sta cx16.VERA_IEN
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
.section BSS_NOCLEAR
|
||||
_orig_irqvec .word ?
|
||||
@@ -1636,9 +1644,10 @@ _orig_irqvec .word ?
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; Sets the handler for the LINE interrupt, and enable (only) that interrupt.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
sta user_vector
|
||||
sty user_vector+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
lda cx16.VERA_IEN
|
||||
@@ -1652,7 +1661,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
@@ -1668,15 +1677,32 @@ _raster_irq_handler
|
||||
pla
|
||||
rti
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
jmp (user_vector)
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
user_vector .word ?
|
||||
.send BSS
|
||||
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; -- just update the IRQ handler and raster line position for the raster IRQ
|
||||
; this is much more efficient than calling set_rasterirq() again every time.
|
||||
; (but you have to call that one initially at least once to setup the prog8 handler itself)
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta sys.set_rasterirq.user_vector
|
||||
sty sys.set_rasterirq.user_vector+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr set_rasterline
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
%asm {{
|
||||
php
|
||||
@@ -1732,13 +1758,15 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ sei
|
||||
+ php
|
||||
sei
|
||||
jsr cbm.RDTIM
|
||||
cli
|
||||
plp
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- sei
|
||||
- php
|
||||
sei
|
||||
jsr cbm.RDTIM
|
||||
cli
|
||||
plp
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
@@ -1759,6 +1787,26 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitrasterline(uword line @AY) {
|
||||
; -- CPU busy wait until the given raster line is reached
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _larger
|
||||
- cmp cx16.VERA_SCANLINE_L
|
||||
bne -
|
||||
bit cx16.VERA_IEN
|
||||
bvs -
|
||||
rts
|
||||
_larger
|
||||
cmp cx16.VERA_SCANLINE_L
|
||||
bne _larger
|
||||
bit cx16.VERA_IEN
|
||||
bvc _larger
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
@@ -2093,6 +2141,47 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
lda cx16.r0+1
|
||||
pha
|
||||
lda cx16.r0+2
|
||||
pha
|
||||
lda cx16.r0+3
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
pla
|
||||
sta cx16.r0+2
|
||||
pla
|
||||
sta cx16.r0+1
|
||||
pla
|
||||
sta cx16.r0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cpu_is_65816() -> bool @A {
|
||||
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
|
||||
bvc +
|
||||
lda #1
|
||||
plp
|
||||
rts
|
||||
+ lda #0
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
||||
@@ -116,26 +116,29 @@ verafx {
|
||||
|
||||
|
||||
asmsub mult16(uword value1 @R0, uword value2 @R1) clobbers(X) -> uword @AY {
|
||||
; Returns the 16 bits unsigned result of R0*R1 in AY.
|
||||
; Returns the lower 16 bits unsigned result of R0*R1 in AY
|
||||
; Note: only the lower 16 bits! (the upper 16 bits are not valid for unsigned word multiplications, only for signed)
|
||||
; Verafx doesn't support unsigned values like this for full 32 bit result.
|
||||
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
jsr verafx.muls
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
stx cx16.r0
|
||||
ldx P8ZP_SCRATCH_W1+1
|
||||
stx cx16.r0+1
|
||||
jmp muls16
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub muls16(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
|
||||
; Returns just the lower 16 bits signed result of the multiplication in cx16.AY.
|
||||
; Note: clobbers R0, R1, and VRAM $1f9bc - $1f9bf (inclusive)
|
||||
%asm {{
|
||||
jsr muls
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
|
||||
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
|
||||
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> long @R0R1_32 {
|
||||
; Returns the 32 bits signed result in R0:R1 (lower word, upper word).
|
||||
; Vera Fx multiplication support only works on signed values!
|
||||
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
|
||||
%asm {{
|
||||
@@ -171,12 +174,14 @@ verafx {
|
||||
stz cx16.VERA_DATA0 ; multiply and write out result
|
||||
lda #%00010001 ; $01 with Increment 1
|
||||
sta cx16.VERA_ADDR_H ; so we can read out the result
|
||||
lda cx16.VERA_DATA0 ; store the lower 16 bits of the result in AY
|
||||
lda cx16.VERA_DATA0 ; store the lower 16 bits of the result in R0
|
||||
ldy cx16.VERA_DATA0
|
||||
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in R0
|
||||
stx cx16.r0s
|
||||
ldx cx16.VERA_DATA0
|
||||
stx cx16.r0s+1
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda cx16.VERA_DATA0 ; store the upper 16 bits of the result in R1
|
||||
ldy cx16.VERA_DATA0 ; store the upper 16 bits of the result in R1
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
stz cx16.VERA_FX_CTRL ; Cache write disable
|
||||
stz cx16.VERA_FX_MULT ; $9F2C reset multiply bit
|
||||
stz cx16.VERA_CTRL ; reset DCSEL
|
||||
|
||||
@@ -220,6 +220,12 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
sub mul32(uword a, uword b) -> long {
|
||||
; return 32 bits result of a*b
|
||||
cx16.r2 = a*b
|
||||
return mklong2(mul16_last_upper(), cx16.r2)
|
||||
}
|
||||
|
||||
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
|
||||
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
|
||||
cx16.r0L = 3 ; quadrant
|
||||
@@ -602,10 +608,9 @@ log2_tab
|
||||
return cx16.r15
|
||||
}
|
||||
|
||||
sub crc32(^^ubyte data, uword length) {
|
||||
sub crc32(^^ubyte data, uword length) -> long {
|
||||
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer.
|
||||
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
|
||||
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
|
||||
; Clobbers R12 through R15, returns the 32 bits result as long
|
||||
; There are also "streaming" crc32_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
|
||||
crc32_start()
|
||||
cx16.r12 = data
|
||||
@@ -614,7 +619,7 @@ log2_tab
|
||||
crc32_update(@(cx16.r12))
|
||||
cx16.r12++
|
||||
}
|
||||
crc32_end()
|
||||
return crc32_end()
|
||||
}
|
||||
|
||||
sub crc32_start() {
|
||||
@@ -666,13 +671,12 @@ log2_tab
|
||||
; }
|
||||
}
|
||||
|
||||
sub crc32_end() {
|
||||
; finalize the "streaming" crc32
|
||||
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
|
||||
void crc32_end_result()
|
||||
sub crc32_end() -> long {
|
||||
; finalize the "streaming" crc32 and returns the result
|
||||
return crc32_end_result()
|
||||
}
|
||||
|
||||
asmsub crc32_end_result() -> uword @R15, uword @R14 {
|
||||
asmsub crc32_end_result() -> long @R14R15_32 {
|
||||
; finalize the "streaming" crc32
|
||||
; returns the result value in cx16.r15 (high word) and r14 (low word)
|
||||
%asm {{
|
||||
|
||||
@@ -81,6 +81,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
ldx cx16.r0+1
|
||||
ldy cx16.r0+2
|
||||
jmp SETTIM
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM
|
||||
sta cx16.r0
|
||||
stx cx16.r0+1
|
||||
sty cx16.r0+2
|
||||
lda #0
|
||||
sta cx16.r0+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub kbdbuf_clear() {
|
||||
; -- convenience helper routine to clear the keyboard buffer
|
||||
%asm {{
|
||||
@@ -509,6 +532,37 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
lda cx16.r0+1
|
||||
pha
|
||||
lda cx16.r0+2
|
||||
pha
|
||||
lda cx16.r0+3
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
pla
|
||||
sta cx16.r0+2
|
||||
pla
|
||||
sta cx16.r0+1
|
||||
pla
|
||||
sta cx16.r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@@ -686,11 +740,6 @@ cx16 {
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
|
||||
@@ -29,6 +29,36 @@ abs_w_into_AY .proc
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
abs_l_into_R0R1 .proc
|
||||
; -- R0:R1 = abs(R0:R1)
|
||||
lda cx16.r1H
|
||||
bmi + ; Negative if high bit of highest byte is set
|
||||
rts
|
||||
+
|
||||
; Invert all four bytes
|
||||
lda cx16.r0L
|
||||
eor #$FF
|
||||
sta cx16.r0L
|
||||
lda cx16.r0H
|
||||
eor #$FF
|
||||
sta cx16.r0H
|
||||
lda cx16.r1L
|
||||
eor #$FF
|
||||
sta cx16.r1L
|
||||
lda cx16.r1H
|
||||
eor #$FF
|
||||
sta cx16.r1H
|
||||
; Add 1 to whole 32-bit value
|
||||
inc cx16.r0L
|
||||
bne +
|
||||
inc cx16.r0H
|
||||
bne +
|
||||
inc cx16.r1L
|
||||
bne +
|
||||
inc cx16.r1H
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
func_sign_b_into_A .proc
|
||||
cmp #0
|
||||
beq _zero
|
||||
|
||||
@@ -449,3 +449,195 @@ _fullpage lda (P8ZP_SCRATCH_W1),y
|
||||
bne _fullpage
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
long_not_equals .proc
|
||||
; checks if the 32 bits long value pointed to by AY is NOT equal to the one pointed to by P8ZP_SCRATCH_W1
|
||||
; returns A=1 if NOT equals otherwise A=0
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #3
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _notequal
|
||||
dey
|
||||
bpl -
|
||||
lda #0
|
||||
rts
|
||||
_notequal lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
long_add_inplace .proc
|
||||
; long pointed to by AY += long pointed to by P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
clc
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
long_sub_inplace .proc
|
||||
; long pointed to by AY -= long pointed to by P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sec
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
long_or_inplace .proc
|
||||
; long pointed to by AY OR= long pointed to by P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
long_and_inplace .proc
|
||||
; long pointed to by AY AND= long pointed to by P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
and (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
and (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
and (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
and (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
long_xor_inplace .proc
|
||||
; long pointed to by AY XOR= long pointed to by P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
long_shiftleftX_inplace .proc
|
||||
; bit shift left X bits the long value pointed to by AY
|
||||
cpx #0
|
||||
beq _end
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
- ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
asl a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
rol a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
rol a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
rol a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dex
|
||||
bne -
|
||||
_end rts
|
||||
.pend
|
||||
|
||||
long_shiftrightX_inplace .proc
|
||||
; bit shift right X bits the long value pointed to by AY
|
||||
cpx #0
|
||||
beq _end
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
- ldy #3
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
asl a ; save sign bit
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dex
|
||||
bne -
|
||||
_end rts
|
||||
.pend
|
||||
@@ -109,6 +109,12 @@ txt {
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_ulhex(long value, bool prefix) {
|
||||
if prefix
|
||||
cbm.CHROUT('$')
|
||||
print(conv.str_ulhex(value))
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
@@ -165,6 +171,11 @@ _allzero lda #'0'
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_l(long value) {
|
||||
; prints a 32 bit value to the screen
|
||||
print(conv.str_l(value))
|
||||
}
|
||||
|
||||
asmsub input_chars (^^ubyte buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
|
||||
@@ -102,7 +102,7 @@ sub str_uwbin (uword value) -> str {
|
||||
}
|
||||
|
||||
sub str_uwhex (uword value) -> str {
|
||||
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
|
||||
; ---- convert the uword in hexadecimal string form (4 digits)
|
||||
ubyte bits = msb(value)
|
||||
string_out[0] = hex_digits[bits>>4]
|
||||
string_out[1] = hex_digits[bits&15]
|
||||
@@ -113,6 +113,26 @@ sub str_uwhex (uword value) -> str {
|
||||
return string_out
|
||||
}
|
||||
|
||||
sub str_ulhex (long value) -> str {
|
||||
; ---- convert the long in hexadecimal string form (8 digits)
|
||||
uword upperw = msw(value)
|
||||
uword lowerw = lsw(value)
|
||||
ubyte bits = msb(upperw)
|
||||
string_out[0] = hex_digits[bits>>4]
|
||||
string_out[1] = hex_digits[bits&15]
|
||||
bits = lsb(upperw)
|
||||
string_out[2] = hex_digits[bits>>4]
|
||||
string_out[3] = hex_digits[bits&15]
|
||||
bits = msb(lowerw)
|
||||
string_out[4] = hex_digits[bits>>4]
|
||||
string_out[5] = hex_digits[bits&15]
|
||||
bits = lsb(lowerw)
|
||||
string_out[6] = hex_digits[bits>>4]
|
||||
string_out[7] = hex_digits[bits&15]
|
||||
string_out[8] = 0
|
||||
return string_out
|
||||
}
|
||||
|
||||
sub str_uw0 (uword value) -> str {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
uword value2 = value/10
|
||||
@@ -141,7 +161,7 @@ sub str_uw (uword value) -> str {
|
||||
}
|
||||
|
||||
sub str_w (word value) -> str {
|
||||
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
|
||||
; ---- convert the (signed) word into decimal string form, without left padding 0's
|
||||
^^ubyte out_ptr = &string_out
|
||||
if value<0 {
|
||||
@(out_ptr) = '-'
|
||||
@@ -152,6 +172,16 @@ sub str_w (word value) -> str {
|
||||
return string_out
|
||||
}
|
||||
|
||||
sub str_l (long value) -> str {
|
||||
; ---- convert the (signed) long into decimal string form, without left padding 0's
|
||||
%ir {{
|
||||
loadm.l r99200,conv.str_l.value
|
||||
load.w r99000,conv.string_out
|
||||
syscall 60 (r99200.l, r99000.w) : r99000.w
|
||||
returnr.w r99000
|
||||
}}
|
||||
}
|
||||
|
||||
sub internal_str_uw(uword value, str out_ptr) {
|
||||
uword value2 = value/10
|
||||
ubyte digits = value-value2*10 as ubyte
|
||||
@@ -267,12 +297,15 @@ sub bin2uword(str string) -> uword {
|
||||
}
|
||||
}
|
||||
|
||||
sub any2uword(str string) -> uword {
|
||||
sub any2uword(str string) -> uword, ubyte {
|
||||
; -- convert any number string (any prefix allowed) to uword.
|
||||
; returns the parsed word value, and the number of processed characters (including the prefix symbol)
|
||||
ubyte length
|
||||
while string[length]!=0 length++
|
||||
when string[0] {
|
||||
'$' -> return hex2uword(string)
|
||||
'%' -> return bin2uword(string)
|
||||
else -> return str2uword(string)
|
||||
'$' -> return hex2uword(string), length
|
||||
'%' -> return bin2uword(string), length
|
||||
else -> return str2uword(string), length
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -304,6 +304,12 @@ math {
|
||||
}}
|
||||
}
|
||||
|
||||
sub mul32(uword a, uword b) -> long {
|
||||
; return 32 bits result of a*b
|
||||
cx16.r2 = a*b
|
||||
return mklong2(mul16_last_upper(), cx16.r2)
|
||||
}
|
||||
|
||||
sub diff(ubyte b1, ubyte b2) -> ubyte {
|
||||
if b1>b2
|
||||
return b1-b2
|
||||
@@ -353,10 +359,9 @@ math {
|
||||
return cx16.r15
|
||||
}
|
||||
|
||||
sub crc32(^^ubyte data, uword length) {
|
||||
sub crc32(^^ubyte data, uword length) -> long {
|
||||
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer.
|
||||
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
|
||||
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
|
||||
; Clobbers R12 through R15, returns the result in R0:R1
|
||||
; There are also "streaming" crc32_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
|
||||
crc32_start()
|
||||
cx16.r12 = data
|
||||
@@ -365,7 +370,7 @@ math {
|
||||
crc32_update(@(cx16.r12))
|
||||
cx16.r12++
|
||||
}
|
||||
crc32_end()
|
||||
return crc32_end()
|
||||
}
|
||||
|
||||
sub crc32_start() {
|
||||
@@ -391,11 +396,11 @@ math {
|
||||
}
|
||||
}
|
||||
|
||||
sub crc32_end() {
|
||||
; finalize the "streaming" crc32
|
||||
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
|
||||
sub crc32_end() -> long {
|
||||
; finalize the "streaming" crc32 and return the result in R0:R1
|
||||
cx16.r15 ^= $ffff
|
||||
cx16.r14 ^= $ffff
|
||||
return mklong2(cx16.r15, cx16.r14)
|
||||
}
|
||||
|
||||
; there's no crc32_end_result() here because IR cannot return multiple values yet
|
||||
|
||||
@@ -192,6 +192,14 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub pushl(long l) {
|
||||
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
|
||||
%ir {{
|
||||
loadm.l r99200,sys.pushl.l
|
||||
push.l r99200
|
||||
}}
|
||||
}
|
||||
|
||||
sub push_returnaddress(uword w) {
|
||||
; note: this actually doesn't do anything useful on the VM because the code execution doesn't use the simulated cpu stack
|
||||
%ir {{
|
||||
@@ -222,6 +230,14 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub popl() -> long {
|
||||
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
|
||||
%ir {{
|
||||
pop.l r99200
|
||||
returnr.l r99200
|
||||
}}
|
||||
}
|
||||
|
||||
sub read_flags() -> ubyte {
|
||||
; "simulate" the 6502 status register a little bit
|
||||
if_neg {
|
||||
@@ -244,6 +260,11 @@ sys {
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
@@ -136,6 +136,13 @@ sub print_uwhex (uword value, bool prefix) {
|
||||
print(conv.str_uwhex(value))
|
||||
}
|
||||
|
||||
sub print_ulhex (long value, bool prefix) {
|
||||
; ---- print the ulong in hexadecimal form (4 digits)
|
||||
if prefix
|
||||
chrout('$')
|
||||
print(conv.str_ulhex(value))
|
||||
}
|
||||
|
||||
sub print_uw0 (uword value) {
|
||||
; ---- print the uword value in decimal form, with left padding 0s (5 positions total)
|
||||
print(conv.str_uw0(value))
|
||||
@@ -151,6 +158,14 @@ sub print_w (word value) {
|
||||
print(conv.str_w(value))
|
||||
}
|
||||
|
||||
sub print_l (long value) {
|
||||
; ---- print the (signed) long in decimal form, without left padding 0's
|
||||
%ir {{
|
||||
loadm.l r99200,txt.print_l.value
|
||||
syscall 59 (r99200.l)
|
||||
}}
|
||||
}
|
||||
|
||||
sub input_chars (str buffer) -> ubyte {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
@@ -26,6 +26,8 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
|
||||
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()} },
|
||||
"msw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 16 and 65535).toDouble()} },
|
||||
"mkword" to ::builtinMkword,
|
||||
"mklong" to ::builtinMklong,
|
||||
"mklong2" to ::builtinMklong,
|
||||
"clamp__ubyte" to ::builtinClampUByte,
|
||||
"clamp__byte" to ::builtinClampByte,
|
||||
"clamp__uword" to ::builtinClampUWord,
|
||||
@@ -34,10 +36,12 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
|
||||
"min__byte" to ::builtinMinByte,
|
||||
"min__uword" to ::builtinMinUWord,
|
||||
"min__word" to ::builtinMinWord,
|
||||
"min__long" to ::builtinMinLong,
|
||||
"max__ubyte" to ::builtinMaxUByte,
|
||||
"max__byte" to ::builtinMaxByte,
|
||||
"max__uword" to ::builtinMaxUWord,
|
||||
"max__word" to ::builtinMaxWord
|
||||
"max__word" to ::builtinMaxWord,
|
||||
"max__long" to ::builtinMaxLong
|
||||
)
|
||||
|
||||
internal fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
|
||||
@@ -203,6 +207,29 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteral(BaseDataType.UWORD, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinMklong(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
when(args.size) {
|
||||
2 -> {
|
||||
val constMsw = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constLsw = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = (constMsw.number.toInt() shl 16) or constLsw.number.toInt()
|
||||
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
|
||||
}
|
||||
4 -> {
|
||||
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constB2 = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constB1 = args[2].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constLsb = args[3].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = (constMsb.number.toInt() shl 24) or
|
||||
(constB2.number.toInt() shl 16) or
|
||||
(constB1.number.toInt() shl 8) or
|
||||
constLsb.number.toInt()
|
||||
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
|
||||
}
|
||||
else -> throw SyntaxError("mkword requires msw and lsw, or msb,b2,b1,lsb arguments", position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("sgn requires one argument", position)
|
||||
@@ -246,6 +273,15 @@ private fun builtinMinUWord(args: List<Expression>, position: Position, program:
|
||||
return NumericLiteral(BaseDataType.UWORD, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinMinLong(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("min requires 2 arguments", position)
|
||||
val val1 = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val val2 = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = min(val1.number.toInt(), val2.number.toInt())
|
||||
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinMaxByte(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("max requires 2 arguments", position)
|
||||
@@ -282,6 +318,15 @@ private fun builtinMaxUWord(args: List<Expression>, position: Position, program:
|
||||
return NumericLiteral(BaseDataType.UWORD, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinMaxLong(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("max requires 2 arguments", position)
|
||||
val val1 = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val val2 = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = max(val1.number.toInt(), val2.number.toInt())
|
||||
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinClampUByte(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if(args.size!=3)
|
||||
throw SyntaxError("clamp requires 3 arguments", position)
|
||||
|
||||
@@ -5,6 +5,7 @@ import prog8.ast.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.buildversion.VERSION
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.PtProgram
|
||||
@@ -456,7 +457,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
|
||||
return CompilationOptions(
|
||||
outputType, launcherType,
|
||||
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, rombale,
|
||||
compTarget, 0u, 0xffffu
|
||||
compTarget, VERSION, 0u, 0xffffu
|
||||
)
|
||||
}
|
||||
|
||||
@@ -499,6 +500,8 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
}
|
||||
|
||||
removeUnusedCode(program, errors,compilerOptions)
|
||||
program.constantFold(errors, compilerOptions)
|
||||
|
||||
for(numCycles in 0..10000) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = program.simplifyExpressions(errors)
|
||||
|
||||
@@ -82,7 +82,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
checkLongType(identifier)
|
||||
val stmt = identifier.targetStatement(program.builtinFunctions)
|
||||
if(stmt==null) {
|
||||
if(identifier.parent is ArrayIndexedExpression) {
|
||||
@@ -241,6 +240,11 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
|
||||
BaseDataType.LONG -> {
|
||||
if(!iterableDt.elementType().isInteger)
|
||||
errors.err("long loop variable can only loop over integers", forLoop.position)
|
||||
}
|
||||
|
||||
BaseDataType.FLOAT -> {
|
||||
// Looping over float variables is very inefficient because the loopvar is going to
|
||||
// get copied over with new values all the time. We don't support this for now.
|
||||
@@ -363,10 +367,6 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(numLiteral: NumericLiteral) {
|
||||
checkLongType(numLiteral)
|
||||
}
|
||||
|
||||
private fun hasReturnOrExternalJumpOrRts(scope: IStatementContainer): Boolean {
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
@@ -813,7 +813,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(addressOf: AddressOf) {
|
||||
checkLongType(addressOf)
|
||||
val variable=addressOf.identifier?.targetVarDecl()
|
||||
if (variable!=null) {
|
||||
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
|
||||
@@ -862,8 +861,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(decl.names.size>1)
|
||||
throw InternalCompilerException("vardecls with multiple names should have been converted into individual vardecls")
|
||||
|
||||
if(decl.datatype.isLong && decl.type!=VarDeclType.CONST)
|
||||
errors.err("cannot use long type for variables; only for constants", decl.position)
|
||||
if(decl.type==VarDeclType.MEMORY) {
|
||||
if (decl.datatype.isString)
|
||||
errors.err("strings cannot be memory-mapped", decl.position)
|
||||
@@ -949,6 +946,9 @@ internal class AstChecker(private val program: Program,
|
||||
dt.isWordArray ->
|
||||
if(arraySize > 128)
|
||||
err("regular word array length must be 1-128, use split array to get to 256")
|
||||
dt.isLongArray ->
|
||||
if(arraySize > 64)
|
||||
err("long array length must be 1-64")
|
||||
dt.isFloatArray ->
|
||||
if(arraySize > 51)
|
||||
err("float array length must be 1-51")
|
||||
@@ -1052,6 +1052,10 @@ internal class AstChecker(private val program: Program,
|
||||
if (length == 0 || length > 128)
|
||||
err("regular word array length must be 1-128, use split array to get to 256")
|
||||
}
|
||||
decl.datatype.isLongArray -> {
|
||||
if (length == 0 || length > 64)
|
||||
err("long array length must be 1-64")
|
||||
}
|
||||
decl.datatype.isFloatArray -> {
|
||||
if (length == 0 || length > 51)
|
||||
err("float array length must be 1-51")
|
||||
@@ -1092,7 +1096,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if(decl.datatype.isStructInstance && decl.origin!=VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
errors.err("cannot declare memory mapped struct instances, use a pointer and memory allocation call or direct address assignment instead", decl.position)
|
||||
errors.err("struct instances cannot be declared as memory-mapped currently, use a pointer and memory allocation call or direct address assignment instead", decl.position)
|
||||
else
|
||||
errors.err("struct instances cannot be declared directly, use a pointer and memory allocation call or direct address assignment instead", decl.position)
|
||||
}
|
||||
@@ -1329,7 +1333,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
checkLongType(expr)
|
||||
val dt = expr.expression.inferType(program).getOrUndef()
|
||||
if(!dt.isUndefined) {
|
||||
|
||||
@@ -1474,8 +1477,6 @@ internal class AstChecker(private val program: Program,
|
||||
return
|
||||
}
|
||||
|
||||
checkLongType(expr)
|
||||
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown) {
|
||||
@@ -1521,7 +1522,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(rightDt.isWord) {
|
||||
val shift = expr.right.constValue(program)?.number?.toInt()
|
||||
if(shift==null || shift > 255) {
|
||||
errors.err("shift by a word value not supported, max is a byte", expr.position)
|
||||
errors.err("bit shift value can not be larger than a byte", expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1552,8 +1553,13 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("can only use string repeat with a constant number value", expr.left.position)
|
||||
} else if(leftDt.isBool && rightDt.isByte || leftDt.isByte && rightDt.isBool) {
|
||||
// expression with one side BOOL other side (U)BYTE is allowed; bool==byte
|
||||
} else if((expr.operator == "<<" || expr.operator == ">>") && (leftDt.isWord && rightDt.isByte)) {
|
||||
// exception allowed: shifting a word by a byte
|
||||
} else if((expr.operator == "<<" || expr.operator == ">>")
|
||||
&& (leftDt.isByte && rightDt.isByte ||
|
||||
leftDt.isWord && rightDt.isByte ||
|
||||
leftDt.isWord && rightDt.isWord ||
|
||||
leftDt.isLong && rightDt.isByte ||
|
||||
leftDt.isLong && rightDt.isWord)) {
|
||||
// exception allowed: shifting a word by a byte, long by a word or byte
|
||||
} else if((expr.operator in BitwiseOperators) && (leftDt.isInteger && rightDt.isInteger)) {
|
||||
// exception allowed: bitwise operations with any integers
|
||||
} else if((leftDt.isUnsignedWord && rightDt.isString) || (leftDt.isString && rightDt.isUnsignedWord)) {
|
||||
@@ -1614,7 +1620,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
checkLongType(typecast)
|
||||
if(typecast.type.isPassByRef)
|
||||
errors.err("cannot type cast to string or array type", typecast.position)
|
||||
|
||||
@@ -1671,7 +1676,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
checkLongType(functionCallExpr)
|
||||
// this function call is (part of) an expression, which should be in a statement somewhere.
|
||||
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
||||
@@ -1905,7 +1909,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
args.forEach{
|
||||
checkLongType(it)
|
||||
if(it.inferType(program).isStructInstance)
|
||||
errors.err("structs can only be passed via a pointer", it.position)
|
||||
}
|
||||
@@ -1918,7 +1921,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
checkLongType(arrayIndexedExpression)
|
||||
val target = arrayIndexedExpression.plainarrayvar?.targetStatement(program.builtinFunctions)
|
||||
if(target is VarDecl) {
|
||||
if (!target.datatype.isIterable && !target.datatype.isUnsignedWord && !target.datatype.isPointer)
|
||||
@@ -1952,7 +1954,10 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
} else if(target!=null) {
|
||||
throw FatalAstException("target is not a variable")
|
||||
if(target is Subroutine || target is Label )
|
||||
errors.err("cannot array index a subroutine or label directly, use an intermediate uword pointer = &symbol instead", arrayIndexedExpression.position)
|
||||
else
|
||||
errors.err("indexed symbol is not a suitable variable", arrayIndexedExpression.position)
|
||||
}
|
||||
|
||||
if(arrayIndexedExpression.pointerderef!=null) {
|
||||
@@ -2157,17 +2162,8 @@ internal class AstChecker(private val program: Program,
|
||||
else
|
||||
errors.err("unable to determine type of dereferenced pointer expression", deref.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkLongType(expression: Expression) {
|
||||
if(expression.inferType(program) issimpletype BaseDataType.LONG) {
|
||||
if((expression.parent as? VarDecl)?.type!=VarDeclType.CONST) {
|
||||
if (expression.parent !is RepeatLoop) {
|
||||
if (errors.noErrorForLine(expression.position))
|
||||
errors.err("integer overflow", expression.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visit(deref)
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
|
||||
@@ -2260,6 +2256,20 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
return err("invalid word array initialization value ${value.type}, expected $targetDt")
|
||||
}
|
||||
targetDt.isPointerArray -> {
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
val maxLength = if(targetDt.isSplitWordArray) 256 else 128
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize>maxLength)
|
||||
return err("array length must be 1-$maxLength")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
}
|
||||
return err("invalid array size, must be 1-$maxLength")
|
||||
}
|
||||
targetDt.isFloatArray -> {
|
||||
// value may be either a single float, or a float arraysize
|
||||
if(value.type istype targetDt) {
|
||||
@@ -2485,17 +2495,16 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(onGoto: OnGoto) {
|
||||
val t = onGoto.index.inferType(program)
|
||||
if(!onGoto.index.inferType(program).getOrUndef().isUnsignedByte) {
|
||||
errors.err("on..goto index must be an unsigned byte", onGoto.index.position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(deref: ArrayIndexedPtrDereference) {
|
||||
if(deref.parent is AssignTarget)
|
||||
errors.err("no support for assigning to a array indexed pointer target like this yet. Split the assignment statement by using an intermediate variable.", deref.position)
|
||||
else
|
||||
if(deref.parent !is AssignTarget)
|
||||
errors.err("no support for getting the target value of pointer array indexing like this yet. Split the expression by using an intermediate variable.", deref.position) // this may never occur anymore since more ArrayIndexedPtrDereference got rewritten
|
||||
|
||||
super.visit(deref)
|
||||
}
|
||||
|
||||
override fun visit(initializer: StaticStructInitializer) {
|
||||
@@ -2525,6 +2534,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
super.visit(initializer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {
|
||||
|
||||
@@ -165,8 +165,15 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
visitFunctionCall(functionCallExpr)
|
||||
super.visit(functionCallExpr)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
visitFunctionCall(functionCallStatement)
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(initializer: StaticStructInitializer) {
|
||||
val struct = initializer.structname.targetStructDecl()
|
||||
|
||||
@@ -174,6 +174,8 @@ class AstPreprocessor(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
if(expr.operator=="in") {
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
@@ -267,7 +269,7 @@ class AstPreprocessor(val program: Program,
|
||||
.filter { it.name !in namesInSub && it.name !in existingAliases }
|
||||
.forEach {
|
||||
if (it.registerOrPair in Cx16VirtualRegisters) {
|
||||
if(it.type.isIntegerOrBool) {
|
||||
if(it.type.isWordOrByteOrBool) {
|
||||
val mappedParamVar = VarDecl.fromParameter(it)
|
||||
mods += IAstModification.InsertFirst(mappedParamVar, subroutine)
|
||||
} else {
|
||||
|
||||
@@ -153,6 +153,8 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
if (options.compTarget.name == VMTarget.NAME)
|
||||
return noModifications
|
||||
|
||||
|
||||
@@ -79,18 +79,26 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1.isUnsignedByte) BaseDataType.UWORD else BaseDataType.WORD, dt1, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
|
||||
} else {
|
||||
} else if(dt1.isWord) {
|
||||
if(dt2.isWord)
|
||||
return noModifications
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2.isUnsignedByte) BaseDataType.UWORD else BaseDataType.WORD, dt2, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||
} else if(dt1.isLong) {
|
||||
if(dt2.isLong)
|
||||
return noModifications
|
||||
val (replaced, cast) = arg2.typecastTo(BaseDataType.LONG, dt2, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
if(expr.operator=="<<" || expr.operator==">>") {
|
||||
val shifts = expr.right.constValue(program)
|
||||
if(shifts!=null) {
|
||||
|
||||
@@ -662,6 +662,61 @@ _after:
|
||||
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
// get rid of the ArrayIndexedPtrDereference AST node, replace it with other AST nodes that are equivalent
|
||||
|
||||
fun pokeFunc(dt: DataType): Pair<String, DataType?> {
|
||||
return when {
|
||||
dt.isBool -> "pokebool" to null
|
||||
dt.isUnsignedByte -> "poke" to null
|
||||
dt.isSignedByte -> "poke" to DataType.UBYTE
|
||||
dt.isUnsignedWord -> "pokew" to null
|
||||
dt.isSignedWord -> "pokew" to DataType.UWORD
|
||||
dt.isLong -> "pokel" to null
|
||||
dt.isFloat -> "pokef" to null
|
||||
else -> throw FatalAstException("can only deref a numeric or boolean pointer here")
|
||||
}
|
||||
}
|
||||
|
||||
if(parent is AssignTarget) {
|
||||
if(!deref.derefLast) {
|
||||
val assignment = parent.parent as Assignment
|
||||
val field = deref.chain.last()
|
||||
val ptr = deref.chain.dropLast(1)
|
||||
if(field.second==null && ptr.last().second!=null) {
|
||||
val ptrName = ptr.map { it.first }
|
||||
val ptrVar = deref.definingScope.lookup(ptrName) as? VarDecl
|
||||
if(ptrVar!=null && (ptrVar.datatype.isPointer || ptrVar.datatype.isPointerArray)) {
|
||||
val struct = ptrVar.datatype.subType!! as StructDecl
|
||||
val offsetNumber = NumericLiteral.optimalInteger(struct.offsetof(field.first, program.memsizer)!!.toInt(), deref.position)
|
||||
val pointerIdentifier = IdentifierReference(ptrName, deref.position)
|
||||
val address: Expression
|
||||
if(ptrVar.datatype.isPointer) {
|
||||
// pointer[idx].field = value --> pokeXXX(pointer as uword + idx*sizeof(Struct) + offsetof(Struct.field), value)
|
||||
val structSize = ptrVar.datatype.dereference().size(program.memsizer)
|
||||
val pointerAsUword = TypecastExpression(pointerIdentifier, DataType.UWORD, true, deref.position)
|
||||
val idx = ptr.last().second!!.indexExpr
|
||||
val scaledIndex = BinaryExpression(idx, "*", NumericLiteral(BaseDataType.UWORD, structSize.toDouble(), deref.position), deref.position)
|
||||
val structAddr = BinaryExpression(pointerAsUword, "+", scaledIndex, deref.position)
|
||||
address = BinaryExpression(structAddr, "+", offsetNumber, deref.position)
|
||||
}
|
||||
else {
|
||||
// pointerarray[idx].field = value --> pokeXXX(pointerarray[idx] as uword + offsetof(Struct.field), value)
|
||||
val index = ArrayIndexedExpression(pointerIdentifier, null, ptr.last().second!!, deref.position)
|
||||
val pointerAsUword = TypecastExpression(index, DataType.UWORD, true, deref.position)
|
||||
address = BinaryExpression(pointerAsUword, "+", offsetNumber, deref.position)
|
||||
}
|
||||
val (pokeFunc, valueCast) = pokeFunc(parent.inferType(program).getOrUndef())
|
||||
val value = if(valueCast==null) assignment.value else TypecastExpression(assignment.value, valueCast, true, assignment.value.position)
|
||||
val pokeCall = FunctionCallStatement(IdentifierReference(listOf(pokeFunc), assignment.position),
|
||||
mutableListOf(address, value), false, assignment.position)
|
||||
|
||||
if(assignment.isAugmentable)
|
||||
errors.warn("in-place assignment of indexed pointer variable currently is very inefficient, maybe use a temporary pointer variable", assignment.position)
|
||||
return listOf(IAstModification.ReplaceNode(assignment, pokeCall, assignment.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(deref.chain.last().second!=null && deref.derefLast && deref.chain.dropLast(1).all { it.second==null } ) {
|
||||
|
||||
// parent could be Assigment directly, or a binexpr chained pointer expression (with '.' operator)
|
||||
@@ -714,15 +769,7 @@ _after:
|
||||
val dt = deref.inferType(program).getOrUndef()
|
||||
if(dt.isNumericOrBool) {
|
||||
// if it's something else beside number (like, a struct instance) we don't support rewriting that...
|
||||
val (pokeFunc, cast) =
|
||||
if (dt.isBool) "pokebool" to null
|
||||
else if (dt.isUnsignedByte) "poke" to null
|
||||
else if (dt.isSignedByte) "poke" to DataType.UBYTE
|
||||
else if (dt.isUnsignedWord) "pokew" to null
|
||||
else if (dt.isSignedWord) "pokew" to DataType.UWORD
|
||||
else if (dt.isLong) "pokel" to null
|
||||
else if (dt.isFloat) "pokef" to null
|
||||
else throw FatalAstException("can only deref a numeric or boolean pointer here")
|
||||
val (pokeFunc, cast) = pokeFunc(dt)
|
||||
val indexer = deref.chain.last().second!!
|
||||
val identifier = IdentifierReference(deref.chain.map { it.first }, deref.position)
|
||||
val indexed = ArrayIndexedExpression(identifier, null, indexer, deref.position)
|
||||
|
||||
@@ -270,7 +270,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
srcTarget.arrayindexed!=null -> target.add(transform(srcTarget.arrayindexed!!))
|
||||
srcTarget.memoryAddress!=null -> target.add(transform(srcTarget.memoryAddress!!))
|
||||
srcTarget.pointerDereference!=null -> target.add(transform(srcTarget.pointerDereference!!))
|
||||
srcTarget.arrayIndexedDereference!=null -> TODO("this array indexed dereference should have been converted to some other ast nodes ${srcTarget.position}")
|
||||
srcTarget.arrayIndexedDereference!=null -> throw FatalAstException("this should have been converted to some other ast nodes ${srcTarget.position}")
|
||||
!srcTarget.void -> throw FatalAstException("invalid AssignTarget")
|
||||
}
|
||||
return target
|
||||
|
||||
@@ -172,7 +172,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
|
||||
is PtAddressOf -> value.arrayIndexExpr == null || notComplex(value.arrayIndexExpr!!)
|
||||
is PtBuiltinFunctionCall -> {
|
||||
when (value.name) {
|
||||
in arrayOf("msb", "lsb", "msw", "lsw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> value.args.all { notComplex(it) }
|
||||
in arrayOf("msb", "lsb", "msw", "lsw", "mkword", "mklong", "mklong2", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> value.args.all { notComplex(it) }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,4 +384,25 @@ internal class StatementReorderer(
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||
}
|
||||
|
||||
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
if(parent is AssignTarget) {
|
||||
val zeroIndexer = deref.chain.firstOrNull { it.second?.constIndex()==0 }
|
||||
if(zeroIndexer!=null) {
|
||||
val target = deref.definingScope.lookup(listOf(zeroIndexer.first))
|
||||
if(target is VarDecl && target.datatype.isPointer) {
|
||||
val position = deref.chain.indexOf(zeroIndexer)
|
||||
val rest = deref.chain.drop(position + 1)
|
||||
if (rest.size == 1 && rest[0].second == null) {
|
||||
// pointer[0]^^.field = xxx --> pointer^^.field = xxx
|
||||
val noindexer = zeroIndexer.first to null
|
||||
val newchain = deref.chain.take(position) + noindexer + rest
|
||||
val newDeref = PtrDereference(newchain.map { it.first }, false, deref.position)
|
||||
return listOf(IAstModification.ReplaceNode(deref, newDeref, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
val leftCv = expr.left.constValue(program)
|
||||
@@ -170,7 +173,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
}
|
||||
|
||||
|
||||
if((expr.operator!="<<" && expr.operator!=">>") || !leftDt.isWords || !rightDt.isBytes) {
|
||||
if((expr.operator!="<<" && expr.operator!=">>") || !leftDt.isInteger || !rightDt.isBytes) {
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOrUndef(), rightDt.getOrUndef(), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
|
||||
@@ -12,6 +12,7 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
|
||||
var pushTypecast: DataType? = null
|
||||
var pushWord = false
|
||||
var pushFloat = false
|
||||
var pushLong = false
|
||||
|
||||
when {
|
||||
value.type.isBool -> {
|
||||
@@ -29,13 +30,14 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
|
||||
}
|
||||
value.type.isUnsignedByte -> {}
|
||||
value.type.isUnsignedWord -> pushWord = true
|
||||
value.type.isLong -> pushLong = true
|
||||
value.type.isPassByRef -> pushWord = true
|
||||
value.type.isFloat -> pushFloat = true
|
||||
else -> throw FatalAstException("unsupported return value type ${value.type} with defer")
|
||||
}
|
||||
|
||||
val pushFunc = if(pushFloat) "floats.push" else if(pushWord) "sys.pushw" else "sys.push"
|
||||
val popFunc = if(pushFloat) "floats.pop" else if(pushWord) "sys.popw" else "sys.pop"
|
||||
val pushFunc = if(pushFloat) "floats.push" else if(pushWord) "sys.pushw" else if (pushLong) "sys.pushl" else "sys.push"
|
||||
val popFunc = if(pushFloat) "floats.pop" else if(pushWord) "sys.popw" else if(pushLong) "sys.popl" else "sys.pop"
|
||||
val pushCall = PtFunctionCall(pushFunc, true, DataType.UNDEFINED, value.position)
|
||||
if(pushTypecast!=null) {
|
||||
val typecast = PtTypeCast(pushTypecast, value.position).also {
|
||||
@@ -47,7 +49,7 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
|
||||
}
|
||||
val popCall = if(popTypecast!=null) {
|
||||
PtTypeCast(popTypecast, value.position).also {
|
||||
val returnDt = if(pushWord) DataType.UWORD else DataType.UBYTE
|
||||
val returnDt = if(pushWord) DataType.UWORD else if(pushLong) DataType.LONG else if(pushFloat) DataType.FLOAT else DataType.UBYTE
|
||||
it.add(PtFunctionCall(popFunc, false, returnDt, value.position))
|
||||
}
|
||||
} else
|
||||
|
||||
@@ -49,9 +49,11 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
val constValue = decl.value!!.constValue(program)!!
|
||||
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
|
||||
} else {
|
||||
// don't make it signed if it was unsigned and vice versa
|
||||
if(valueDt.isSigned && decl.datatype.isUnsigned ||
|
||||
valueDt.isUnsigned && decl.datatype.isSigned) {
|
||||
// don't make it signed if it was unsigned and vice versa, except when it is a long const declaration
|
||||
if(!decl.datatype.isLong &&
|
||||
(valueDt.isSigned && decl.datatype.isUnsigned ||
|
||||
valueDt.isUnsigned && decl.datatype.isSigned))
|
||||
{
|
||||
val constValue = decl.value!!.constValue(program)!!
|
||||
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
|
||||
} else {
|
||||
|
||||
@@ -97,6 +97,7 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
"library/main",
|
||||
"plasma",
|
||||
"rasterbars",
|
||||
"simplemultiplexer",
|
||||
"sprites",
|
||||
"starfield",
|
||||
"tehtriz",
|
||||
@@ -149,6 +150,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"bdmusic",
|
||||
"bobs",
|
||||
"bubbleuniverse",
|
||||
"charfade",
|
||||
"charsets",
|
||||
"circles",
|
||||
"cobramk3-gfx",
|
||||
|
||||
@@ -21,6 +21,7 @@ class TestGoldenRam: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = VMTarget(),
|
||||
compilerVersion="99.99",
|
||||
loadAddress = 999u,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ class TestLaunchEmu: FunSpec({
|
||||
val target = VMTarget()
|
||||
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
||||
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
|
||||
<PROGRAM NAME="test">
|
||||
<PROGRAM NAME="test" COMPILERVERSION="99.99">
|
||||
<OPTIONS>
|
||||
</OPTIONS>
|
||||
|
||||
|
||||
@@ -386,9 +386,6 @@ class TestMemory: FunSpec({
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
target.memorySize(BaseDataType.UNDEFINED)
|
||||
}
|
||||
shouldThrow<NoSuchElementException> {
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.LONG), 10)
|
||||
}
|
||||
target.memorySize(BaseDataType.BOOL) shouldBe 1
|
||||
target.memorySize(BaseDataType.BYTE) shouldBe 1
|
||||
target.memorySize(BaseDataType.WORD) shouldBe 2
|
||||
@@ -397,6 +394,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
target.memorySize(DataType.BOOL, null) shouldBe 1
|
||||
target.memorySize(DataType.WORD, null) shouldBe 2
|
||||
target.memorySize(DataType.LONG, null) shouldBe 4
|
||||
target.memorySize(DataType.FLOAT, null) shouldBe target.FLOAT_MEM_SIZE
|
||||
|
||||
target.memorySize(DataType.STR, null) shouldBe 2
|
||||
@@ -409,6 +407,7 @@ class TestMemory: FunSpec({
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.BYTE), 10) shouldBe 10
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.WORD), 10) shouldBe 20
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.UWORD), 10) shouldBe 20
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.LONG), 10) shouldBe 40
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.FLOAT), 10) shouldBe 10*target.FLOAT_MEM_SIZE
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.WORD, true), 10) shouldBe 20
|
||||
target.memorySize(DataType.arrayFor(BaseDataType.UWORD, true), 10) shouldBe 20
|
||||
|
||||
+89
-136
@@ -3,7 +3,6 @@ package prog8tests.compiler
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
@@ -764,7 +763,6 @@ main {
|
||||
test("address-of struct fields") {
|
||||
val src="""
|
||||
%import floats
|
||||
%import textio
|
||||
|
||||
main {
|
||||
struct List {
|
||||
@@ -1027,118 +1025,6 @@ main {
|
||||
st[8] shouldBe instanceOf<Assignment>()
|
||||
}
|
||||
|
||||
test("indexing pointers with index 0 is just a direct pointer dereference except when followed by a struct field lookup") {
|
||||
val src="""
|
||||
%import floats
|
||||
main {
|
||||
struct List {
|
||||
^^uword s
|
||||
ubyte n
|
||||
}
|
||||
sub start() {
|
||||
^^List l1 = ^^List : []
|
||||
^^word @shared wptr
|
||||
^^float @shared fptr
|
||||
float f1,f2
|
||||
|
||||
cx16.r0 = l1.s^^
|
||||
cx16.r1 = l1^^.s^^
|
||||
cx16.r2 = l1.s^^
|
||||
cx16.r3 = l1.s[0]
|
||||
cx16.r4 = l1^^.s[0]
|
||||
|
||||
l1.s^^ = 4242
|
||||
l1^^.s^^ = 4242
|
||||
l1.s^^ = 4242
|
||||
l1.s[0] = 4242
|
||||
;; l1^^.s[0] = 4242 ; TODO fix parse syntax error
|
||||
|
||||
cx16.r0s = wptr[0]
|
||||
cx16.r1s = wptr^^
|
||||
wptr^^ = 4242
|
||||
wptr[0] = 4242
|
||||
|
||||
f1 = fptr^^
|
||||
f2 = fptr[0]
|
||||
fptr^^ = 1.234
|
||||
fptr[0] = 1.234
|
||||
|
||||
; not changed to dereference:
|
||||
cx16.r0L = l1[0].n
|
||||
cx16.r1L = l1[1].n
|
||||
}
|
||||
}"""
|
||||
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 30
|
||||
val dr0 = (st[10] as Assignment).value as PtrDereference
|
||||
val dr1 = (st[11] as Assignment).value as PtrDereference
|
||||
val dr2 = (st[12] as Assignment).value as PtrDereference
|
||||
val dr3 = (st[13] as Assignment).value as PtrDereference
|
||||
val dr4 = (st[14] as Assignment).value as PtrDereference
|
||||
|
||||
val dr5 = (st[15] as Assignment).target.pointerDereference!!
|
||||
val dr6 = (st[16] as Assignment).target.pointerDereference!!
|
||||
val dr7 = (st[17] as Assignment).target.pointerDereference!!
|
||||
val dr8 = (st[18] as Assignment).target.pointerDereference!!
|
||||
|
||||
val dr9 = (st[19] as Assignment).value as FunctionCallExpression
|
||||
val dr10 = (st[20] as Assignment).value as PtrDereference
|
||||
val dr11 = (st[21] as Assignment).target.pointerDereference!!
|
||||
(st[22] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokew")
|
||||
|
||||
val dr13 = (st[23] as Assignment).value as PtrDereference
|
||||
((st[24] as Assignment).value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekf")
|
||||
val dr15 = (st[25] as Assignment).target.pointerDereference!!
|
||||
(st[26] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokef")
|
||||
|
||||
dr0.chain shouldBe listOf("l1", "s")
|
||||
dr0.derefLast shouldBe true
|
||||
dr1.chain shouldBe listOf("l1", "s")
|
||||
dr1.derefLast shouldBe true
|
||||
dr2.chain shouldBe listOf("l1", "s")
|
||||
dr2.derefLast shouldBe true
|
||||
dr3.chain shouldBe listOf("l1", "s")
|
||||
dr3.derefLast shouldBe true
|
||||
dr4.chain shouldBe listOf("l1", "s")
|
||||
dr4.derefLast shouldBe true
|
||||
|
||||
dr5.chain shouldBe listOf("l1", "s")
|
||||
dr5.derefLast shouldBe true
|
||||
dr6.chain shouldBe listOf("l1", "s")
|
||||
dr6.derefLast shouldBe true
|
||||
dr7.chain shouldBe listOf("l1", "s")
|
||||
dr7.derefLast shouldBe true
|
||||
dr8.chain shouldBe listOf("l1", "s")
|
||||
dr8.derefLast shouldBe true
|
||||
|
||||
dr9.target.nameInSource shouldBe listOf("peekw")
|
||||
dr10.chain shouldBe listOf("wptr")
|
||||
dr10.derefLast shouldBe true
|
||||
dr11.chain shouldBe listOf("wptr")
|
||||
dr11.derefLast shouldBe true
|
||||
|
||||
dr13.chain shouldBe listOf("fptr")
|
||||
dr13.derefLast shouldBe true
|
||||
dr15.chain shouldBe listOf("fptr")
|
||||
dr15.derefLast shouldBe true
|
||||
|
||||
val list0 = (st[27] as Assignment).value as BinaryExpression
|
||||
val list1 = (st[28] as Assignment).value as BinaryExpression
|
||||
|
||||
list0.operator shouldBe "."
|
||||
(list0.right as IdentifierReference).nameInSource shouldBe listOf("n")
|
||||
val list0left = list0.left as ArrayIndexedExpression
|
||||
list0left.plainarrayvar!!.nameInSource shouldBe listOf("l1")
|
||||
list0left.indexer.constIndex() shouldBe 0
|
||||
list1.operator shouldBe "."
|
||||
(list1.right as IdentifierReference).nameInSource shouldBe listOf("n")
|
||||
val list1left = list0.left as ArrayIndexedExpression
|
||||
list1left.plainarrayvar!!.nameInSource shouldBe listOf("l1")
|
||||
list1left.indexer.constIndex() shouldBe 0
|
||||
}
|
||||
|
||||
test("indexing pointers to structs") {
|
||||
val src="""
|
||||
%import floats
|
||||
@@ -1458,33 +1344,32 @@ other {
|
||||
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("a.b.c[i]^^.value = X where pointer is struct gives good error message") {
|
||||
test("support for assigning to indexed pointers") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
other.foo.listarray[3]^^.value = cx16.r0
|
||||
other.foo()
|
||||
}
|
||||
}
|
||||
sprptr[2]^^.y = 99
|
||||
pokew(sprptr as uword + (sizeof(Sprite) as uword)*2 + offsetof(Sprite.y), 99)
|
||||
sprptr[cx16.r0L]^^.y = 99
|
||||
pokew(sprptr as uword + (sizeof(Sprite) as uword)*cx16.r0L + offsetof(Sprite.y), 99)
|
||||
|
||||
other {
|
||||
sub foo() {
|
||||
struct List {
|
||||
bool b
|
||||
uword value
|
||||
}
|
||||
|
||||
^^List[10] listarray
|
||||
listarray[3]^^.value = cx16.r0
|
||||
listarray[3]^^ = 999 ; cannot assign word value to struct instance
|
||||
sprites[2]^^.y = 99
|
||||
pokew(sprites[2] as uword + offsetof(Sprite.y), 99)
|
||||
sprites[cx16.r0L]^^.y = 99
|
||||
pokew(sprites[cx16.r0L] as uword + offsetof(Sprite.y), 99)
|
||||
}
|
||||
|
||||
struct Sprite {
|
||||
ubyte x
|
||||
uword y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
^^Sprite @shared sprptr
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBeGreaterThanOrEqualTo 3
|
||||
errors.errors[0] shouldContain "no support for"
|
||||
errors.errors[1] shouldContain "no support for"
|
||||
errors.errors[2] shouldContain "assigning this value to struct instance not supported"
|
||||
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
xtest("array indexed assignment parses with and without explicit dereference after struct pointer [IGNORED because it's a parser error right now]") {
|
||||
@@ -1572,7 +1457,6 @@ other {
|
||||
test("passing arrays to subroutines via typed pointer parameters") {
|
||||
val src="""
|
||||
%import floats
|
||||
%import textio
|
||||
|
||||
main {
|
||||
struct Node {
|
||||
@@ -2552,4 +2436,73 @@ structdefs {
|
||||
a5t.pointerDereference!!.chain shouldBe listOf("structdefs", "element", "value2")
|
||||
a6t.pointerDereference!!.chain shouldBe listOf("structdefs", "element", "value2")
|
||||
}
|
||||
|
||||
test("assigning field with same name should not confuse compiler") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte @shared ok = sprites[2].y ; this one is fine...
|
||||
ubyte @shared y = sprites[2].y ; this used to crash
|
||||
}
|
||||
|
||||
struct Sprite {
|
||||
uword x
|
||||
ubyte y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("0-indexed optimizations") {
|
||||
val src="""
|
||||
main {
|
||||
struct Sprite {
|
||||
uword x
|
||||
ubyte y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
^^Sprite @shared sprptr
|
||||
|
||||
sub start() {
|
||||
sprptr.y = 99
|
||||
sprptr[0]^^.y = 99
|
||||
;; sprites[0]^^.y = 99 ; no change here. TODO: this syntax doesn't compile yet...
|
||||
cx16.r0 = &sprptr[0]
|
||||
|
||||
cx16.r2L = sprptr.y
|
||||
cx16.r0L = sprptr[0].y
|
||||
cx16.r1L = sprites[0].y ; no change here, need first array element
|
||||
cx16.r0 = sprites[0] ; no change here, need first array element
|
||||
cx16.r0 = sprites[0] ; no change here, need first array element
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 8
|
||||
val a1 = st[0] as Assignment
|
||||
val a2 = st[1] as Assignment
|
||||
val a3 = st[2] as Assignment
|
||||
val a4 = st[3] as Assignment
|
||||
val a5 = st[4] as Assignment
|
||||
val a6 = st[5] as Assignment
|
||||
val a7 = st[6] as Assignment
|
||||
|
||||
a1.target.arrayIndexedDereference shouldBe null
|
||||
a1.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
|
||||
a2.target.arrayIndexedDereference shouldBe null
|
||||
a2.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
|
||||
|
||||
(a3.value as? AddressOf)?.identifier?.nameInSource shouldBe listOf("sprptr")
|
||||
(a4.value as? PtrDereference)?.chain shouldBe listOf("sprptr", "y")
|
||||
(a5.value as? PtrDereference)?.chain shouldBe listOf("sprptr", "y")
|
||||
val be6 = a6.value as BinaryExpression // this one is an actual array and we need the first element so no change here
|
||||
be6.operator shouldBe "."
|
||||
be6.left shouldBe instanceOf<ArrayIndexedExpression>()
|
||||
be6.right shouldBe instanceOf<IdentifierReference>()
|
||||
(a7.value as? ArrayIndexedExpression)?.indexer?.constIndex() shouldBe 0
|
||||
}
|
||||
})
|
||||
@@ -240,6 +240,33 @@ main {
|
||||
errors.errors[1] shouldContain "9:25: invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on normal subroutine (in expression)") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(ubyte a1, ubyte a2) -> ubyte {
|
||||
return 42
|
||||
}
|
||||
|
||||
sub wrapper(ubyte value) {
|
||||
value++
|
||||
}
|
||||
|
||||
sub start() {
|
||||
wrapper(thing(1))
|
||||
wrapper(thing(1,2))
|
||||
wrapper(thing(1,2,3))
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "12:33: invalid number of arguments"
|
||||
errors.errors[1] shouldContain "14:33: invalid number of arguments"
|
||||
}
|
||||
|
||||
|
||||
test("invalid number of args check on asm subroutine") {
|
||||
val text="""
|
||||
main {
|
||||
@@ -261,6 +288,35 @@ main {
|
||||
errors.errors[1] shouldContain "9:25: invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on asm subroutine (in expression)") {
|
||||
val text="""
|
||||
main {
|
||||
asmsub thing(ubyte a1 @A, ubyte a2 @Y) -> ubyte @A {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub wrapper(ubyte value) {
|
||||
value++
|
||||
}
|
||||
|
||||
sub start() {
|
||||
wrapper(thing(1))
|
||||
wrapper(thing(1,2))
|
||||
wrapper(thing(1,2,3))
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "14:33: invalid number of arguments"
|
||||
errors.errors[1] shouldContain "16:33: invalid number of arguments"
|
||||
}
|
||||
|
||||
|
||||
test("invalid number of args check on call to label and builtin func") {
|
||||
val text="""
|
||||
main {
|
||||
|
||||
@@ -49,6 +49,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = C64Target(),
|
||||
compilerVersion="99.99",
|
||||
loadAddress = 999u,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
@@ -69,7 +70,7 @@ class TestC64Zeropage: FunSpec({
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = c64target, loadAddress = 999u, memtopAddress = 0xffffu
|
||||
compTarget = c64target, compilerVersion="99.99", loadAddress = 999u, memtopAddress = 0xffffu
|
||||
))
|
||||
|
||||
var result = zp.allocate("", DataType.UBYTE, null, null, errors)
|
||||
@@ -84,33 +85,33 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled floats" }
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled ZP use" }
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp3.allocate("", DataType.FLOAT, null, null, errors)
|
||||
}
|
||||
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
}
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
}
|
||||
}
|
||||
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
println(zp.free)
|
||||
zp.availableBytes() shouldBe 0
|
||||
val result = zp.allocate("", DataType.BYTE, null, null, errors)
|
||||
@@ -118,13 +119,13 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 15
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 85
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp3.availableBytes() shouldBe 94
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp4.availableBytes() shouldBe 205
|
||||
zp4.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 204
|
||||
@@ -133,7 +134,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 205
|
||||
4u shouldNotBeIn zp1.free
|
||||
5u shouldNotBeIn zp1.free
|
||||
@@ -146,7 +147,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 105
|
||||
4u shouldNotBeIn zp2.free
|
||||
5u shouldNotBeIn zp2.free
|
||||
@@ -159,7 +160,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 105
|
||||
4u shouldBeIn zp3.free
|
||||
5u shouldBeIn zp3.free
|
||||
@@ -168,7 +169,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 15
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -190,7 +191,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 205
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -222,7 +223,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 15
|
||||
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
|
||||
@@ -239,7 +240,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
@@ -252,18 +253,18 @@ class TestCx16Zeropage: FunSpec({
|
||||
val cx16target = Cx16Target()
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 86
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 173
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp3.availableBytes() shouldBe 214
|
||||
zp3.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp3.availableBytes() shouldBe 213
|
||||
@@ -272,7 +273,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 214
|
||||
0x22u shouldNotBeIn zp1.free
|
||||
0x23u shouldNotBeIn zp1.free
|
||||
@@ -284,7 +285,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("preallocated zp vars") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
|
||||
zp1.allocatedVariables["test"] shouldBe null
|
||||
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
|
||||
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
|
||||
|
||||
@@ -466,4 +466,15 @@ main {
|
||||
compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
|
||||
}
|
||||
|
||||
test("const long with small values") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
const long notkaputt = 42
|
||||
cx16.r0L = notkaputt
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), true, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -871,6 +871,7 @@ main {
|
||||
DataType.forDt(BaseDataType.FLOAT).isFloat shouldBe true
|
||||
|
||||
DataType.arrayFor(BaseDataType.UBYTE, true).isUnsignedByteArray shouldBe true
|
||||
DataType.arrayFor(BaseDataType.LONG).isLongArray shouldBe true
|
||||
DataType.arrayFor(BaseDataType.FLOAT).isFloatArray shouldBe true
|
||||
DataType.arrayFor(BaseDataType.UWORD).isUnsignedWordArray shouldBe true
|
||||
DataType.arrayFor(BaseDataType.UWORD).isArray shouldBe true
|
||||
@@ -886,9 +887,6 @@ main {
|
||||
shouldThrow<NoSuchElementException> {
|
||||
DataType.arrayFor(BaseDataType.ARRAY)
|
||||
}
|
||||
shouldThrow<NoSuchElementException> {
|
||||
DataType.arrayFor(BaseDataType.LONG)
|
||||
}
|
||||
shouldThrow<NoSuchElementException> {
|
||||
DataType.arrayFor(BaseDataType.UNDEFINED)
|
||||
}
|
||||
@@ -1173,5 +1171,30 @@ main {
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("breakpoint after expression") {
|
||||
val src="""
|
||||
main {
|
||||
ubyte a,b
|
||||
|
||||
sub start() {
|
||||
func1()
|
||||
func2()
|
||||
}
|
||||
|
||||
sub func1() {
|
||||
a = b
|
||||
%breakpoint ; parse error
|
||||
}
|
||||
|
||||
sub func2() {
|
||||
a = b
|
||||
%breakpoint! ; parse ok
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[1] shouldContain "12:10: undefined symbol: breakpoint"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -88,7 +88,7 @@ class TestAsmGenSymbols: StringSpec({
|
||||
|
||||
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), 999u, 0xffffu)
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), "99.99", 999u, 0xffffu)
|
||||
val astchecker = AstChecker(program, errors, options)
|
||||
astchecker.visit(program)
|
||||
errors.report()
|
||||
|
||||
@@ -28,12 +28,14 @@ internal object DummyMemsizer : IMemSizer {
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
|
||||
BaseDataType.LONG -> numElements*4
|
||||
BaseDataType.FLOAT -> numElements*5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package prog8tests.vm
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.ints.shouldBeLessThanOrEqual
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
@@ -12,10 +14,7 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.IRDataType
|
||||
import prog8.intermediate.IRFileReader
|
||||
import prog8.intermediate.IRSubroutine
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.*
|
||||
import prog8.vm.VmRunner
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
@@ -548,4 +547,42 @@ main {
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), true)
|
||||
}
|
||||
|
||||
test("correct reg types with sqrt") {
|
||||
val src= """
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0L= sqrt(cx16.r1)
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("SSA basic blocks") {
|
||||
val src= """
|
||||
main {
|
||||
sub start() {
|
||||
func()
|
||||
}
|
||||
|
||||
sub func() {
|
||||
if cx16.r0<10 or cx16.r0>319 {
|
||||
cx16.r1++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
val irProgram = IRFileReader().read(virtfile)
|
||||
val func = irProgram.blocks[0].children[1] as IRSubroutine
|
||||
func.label shouldBe "main.func"
|
||||
func.chunks.size shouldBe 11
|
||||
for(chunk in func.chunks) {
|
||||
if(chunk.next == null) {
|
||||
chunk.instructions.last().opcode shouldBeIn OpcodesThatBranchUnconditionally
|
||||
}
|
||||
val branches = chunk.instructions.filter { it.opcode in OpcodesThatEndSSAblock }
|
||||
branches.size shouldBeLessThanOrEqual 1
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -571,7 +571,18 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(deref: ArrayIndexedPtrDereference) {
|
||||
output("???? Array Indexed Ptr Dereference should have been converted to other AST nodes ????")
|
||||
for(c in deref.chain) {
|
||||
output(c.first)
|
||||
c.second?.let {
|
||||
output("[")
|
||||
it.accept(this)
|
||||
output("]")
|
||||
}
|
||||
if(c !== deref.chain.last())
|
||||
output("^^.")
|
||||
}
|
||||
if(deref.derefLast)
|
||||
output("^^")
|
||||
}
|
||||
|
||||
override fun visit(initializer: StaticStructInitializer) {
|
||||
|
||||
@@ -427,6 +427,7 @@ fun defaultZero(dt: BaseDataType, position: Position) = when(dt) {
|
||||
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, 0.0, position)
|
||||
BaseDataType.UWORD, BaseDataType.STR -> NumericLiteral(BaseDataType.UWORD, 0.0, position)
|
||||
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, 0.0, position)
|
||||
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, 0.0, position)
|
||||
BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, 0.0, position)
|
||||
BaseDataType.POINTER -> NumericLiteral(BaseDataType.UWORD, 0.0, position)
|
||||
else -> throw FatalAstException("can only determine default zero value for a numeric type")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.code.core.Position
|
||||
|
||||
/**
|
||||
@@ -19,6 +18,3 @@ open class SyntaxError(override var message: String, val position: Position) : A
|
||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "${position.toClickableStr()} Error: $message"
|
||||
}
|
||||
|
||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||
@@ -372,7 +372,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
else if(literalText.length>2)
|
||||
datatype = BaseDataType.UWORD
|
||||
try {
|
||||
integer = literalText.toInt(16)
|
||||
integer = literalText.lowercase().toLong(16).toInt()
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid hexadecimal literal ${x.message}", ctx.toPosition())
|
||||
}
|
||||
|
||||
@@ -126,11 +126,13 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
"~" -> {
|
||||
if(inferred.isBytes) InferredTypes.knownFor(BaseDataType.UBYTE)
|
||||
else if(inferred.isWords) InferredTypes.knownFor(BaseDataType.UWORD)
|
||||
else if(inferred.isLong) InferredTypes.knownFor(BaseDataType.LONG) // TODO should be ULONG, but we don't have that type yet
|
||||
else InferredTypes.unknown()
|
||||
}
|
||||
"-" -> {
|
||||
if(inferred.isBytes) InferredTypes.knownFor(BaseDataType.BYTE)
|
||||
else if(inferred.isWords) InferredTypes.knownFor(BaseDataType.WORD)
|
||||
else if(inferred.isLong) InferredTypes.knownFor(BaseDataType.LONG)
|
||||
else inferred
|
||||
}
|
||||
else -> throw FatalAstException("weird prefix expression operator")
|
||||
@@ -179,7 +181,12 @@ class BinaryExpression(
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean {
|
||||
return if(operator==".")
|
||||
left.referencesIdentifier(nameInSource)
|
||||
else
|
||||
left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
|
||||
}
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
|
||||
val leftDt = left.inferType(program)
|
||||
@@ -326,6 +333,7 @@ class BinaryExpression(
|
||||
BaseDataType.BYTE -> Pair(DataType.BYTE, left)
|
||||
BaseDataType.UWORD -> Pair(DataType.UWORD, left)
|
||||
BaseDataType.WORD -> Pair(DataType.WORD, left)
|
||||
BaseDataType.LONG -> Pair(DataType.LONG, left)
|
||||
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
@@ -336,6 +344,7 @@ class BinaryExpression(
|
||||
BaseDataType.BYTE -> Pair(DataType.BYTE, null)
|
||||
BaseDataType.UWORD -> Pair(DataType.WORD, left)
|
||||
BaseDataType.WORD -> Pair(DataType.WORD, left)
|
||||
BaseDataType.LONG -> Pair(DataType.LONG, left)
|
||||
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
@@ -346,6 +355,7 @@ class BinaryExpression(
|
||||
BaseDataType.BYTE -> Pair(DataType.WORD, right)
|
||||
BaseDataType.UWORD -> Pair(DataType.UWORD, null)
|
||||
BaseDataType.WORD -> Pair(DataType.WORD, left)
|
||||
BaseDataType.LONG -> Pair(DataType.LONG, left)
|
||||
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
@@ -356,6 +366,18 @@ class BinaryExpression(
|
||||
BaseDataType.BYTE -> Pair(DataType.WORD, right)
|
||||
BaseDataType.UWORD -> Pair(DataType.WORD, right)
|
||||
BaseDataType.WORD -> Pair(DataType.WORD, null)
|
||||
BaseDataType.LONG -> Pair(DataType.LONG, left)
|
||||
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
when (rightDt.base) {
|
||||
BaseDataType.UBYTE -> Pair(DataType.LONG, right)
|
||||
BaseDataType.BYTE -> Pair(DataType.LONG, right)
|
||||
BaseDataType.UWORD -> Pair(DataType.LONG, right)
|
||||
BaseDataType.WORD -> Pair(DataType.LONG, right)
|
||||
BaseDataType.LONG -> Pair(DataType.LONG, null)
|
||||
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
@@ -1493,7 +1515,7 @@ class FunctionCallExpression(override var target: IdentifierReference,
|
||||
|
||||
override fun copy() = FunctionCallExpression(target.copy(), args.map { it.copy() }.toMutableList(), position)
|
||||
override val isSimple = when (target.nameInSource.singleOrNull()) {
|
||||
in arrayOf("msb", "lsb", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple }
|
||||
in arrayOf("msb", "lsb", "mkword", "mklong", "mklong2", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple }
|
||||
else -> false
|
||||
}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
@@ -1804,6 +1826,35 @@ class ArrayIndexedPtrDereference(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(parent !is BinaryExpression || (parent as BinaryExpression).operator!=".") {
|
||||
val indexPosition = this.chain.indexOfFirst { it.second!=null }
|
||||
val identifier = this.chain.take(indexPosition+1).map {it.first}
|
||||
val target = this.definingScope.lookup(identifier)
|
||||
if(target is VarDecl && (target.datatype.isPointer || target.datatype.isPointerArray)) {
|
||||
val sub = target.datatype.subType
|
||||
if(sub!=null) {
|
||||
val field = this.chain.drop(indexPosition+1).singleOrNull()
|
||||
if(field==null) {
|
||||
return InferredTypes.knownFor(DataType.structInstance(sub))
|
||||
} else {
|
||||
val fieldDt= sub.getFieldType(field.first)!!
|
||||
return if(derefLast)
|
||||
InferredTypes.knownFor(fieldDt.dereference())
|
||||
else
|
||||
InferredTypes.knownFor(fieldDt)
|
||||
}
|
||||
} else {
|
||||
require(indexPosition==this.chain.size-1) {"when indexing primitive pointer type there cannot be a field after it"}
|
||||
return if(derefLast)
|
||||
InferredTypes.knownFor(target.datatype.dereference())
|
||||
else
|
||||
InferredTypes.knownFor(target.datatype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// too hard to determine the type....?
|
||||
return InferredTypes.unknown()
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ object InferredTypes {
|
||||
val isBool = datatype?.isBool==true
|
||||
val isBytes = datatype?.isByte==true
|
||||
val isWords = datatype?.isWord==true
|
||||
val isLong = datatype?.isLong==true
|
||||
val isPointer = datatype?.isPointer==true
|
||||
val isStructInstance = datatype?.isStructInstance==true
|
||||
val isUnsignedWord = datatype?.isUnsignedWord==true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
; all library modules for the c128 compiler target
|
||||
|
||||
%import bcd
|
||||
%import buffers
|
||||
%import compression
|
||||
%import conv
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
; all library modules for the c64 compiler target
|
||||
|
||||
%import bcd
|
||||
%import buffers
|
||||
%import compression
|
||||
%import conv
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
; all library modules for the cx16 compiler target
|
||||
|
||||
%import buffers
|
||||
%import bcd
|
||||
%import bmx
|
||||
%import buffers
|
||||
%import compression
|
||||
%import conv
|
||||
%import coroutines
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
; all library modules for the pet32 compiler target
|
||||
|
||||
%import bcd
|
||||
%import buffers
|
||||
%import compression
|
||||
%import conv
|
||||
|
||||
@@ -34,11 +34,14 @@ No linker
|
||||
|
||||
Data types
|
||||
----------
|
||||
- There are byte, word (16 bits) and float datatypes for numbers. There are no bigger integer types natively available.
|
||||
- There is no automatic type enlargement: calculations remain within the data type of the operands. Any overflow silently wraps or truncates.
|
||||
- There are byte, word (16 bits), long (32 bits) and float datatypes for numbers.
|
||||
- floats are available as native data type on systems that have a supported floating point library in ROM.
|
||||
- **There is no automatic type enlargement:** all calculations remain within the data type of the operands. Any overflow silently wraps or truncates.
|
||||
You'll have to add explicit casts to increase the size of the value if required.
|
||||
For example when adding two byte variables having values 100 and 200, the result won't be 300, because that doesn't fit in a byte. It will be 44.
|
||||
You'll have to cast one or both of the *operands* to a word type first if you want to accomodate the actual result value of 300.
|
||||
Similarly, ``long v = w1 * w2`` doesn't automatically give you the full 32 bits multiplication result, instead it is still constrained in the word range.
|
||||
If you need the full 32 bits result you'll have to call a specialized routine such as ``math.mul32`` or ``math.mul16_last_upper()``.
|
||||
- strings and arrays are allocated once, statically, and never resized.
|
||||
- strings and arrays are mutable: you can change their contents, but always keep the original storage size in mind to avoid overwriting memory outside of the buffer.
|
||||
- maximum string length is 255 characters + a trailing 0 byte.
|
||||
@@ -80,9 +83,9 @@ Subroutines
|
||||
Pointers and Structs
|
||||
--------------------
|
||||
|
||||
Legacy 'untyped' pointers:
|
||||
*Legacy 'untyped' pointers:*
|
||||
|
||||
- In Prog8 versions before 12.0 there was no support for typed pointers, only 'untyped' ones:
|
||||
- In Prog8 versions **before 12.0** there was no support for typed pointers, only 'untyped' ones:
|
||||
Variables of the ``uword`` datatype can be used as a pointer to one of the possible 65536 memory locations,
|
||||
so the value it points to is always a single byte. This is similar to ``uint8_t*`` from C.
|
||||
You have to deal with the uword manually if the object it points to is something different.
|
||||
@@ -92,9 +95,9 @@ Legacy 'untyped' pointers:
|
||||
- Pointers don't have to be a variable, you can immediately access the value of a given memory location using ``@($d020)`` for instance.
|
||||
Reading is done by assigning it to a variable, writing is done by just assigning the new value to it.
|
||||
|
||||
Typed pointers and structs:
|
||||
*Typed pointers and structs:*
|
||||
|
||||
- Since version 12, prog8 supports struct types and typed pointers.
|
||||
- Since **version 12.0**, prog8 supports struct types and typed pointers.
|
||||
- Structs are a grouping of one or more fields, that together make up the struct type.
|
||||
- Typed pointers are just that: a pointer to a specific type (which can be a simple type such as float, or a struct type.)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ Mac OS (and Linux, and WSL2 on Windows):
|
||||
|
||||
**Or, use the Gradle build system to build it yourself from source:**
|
||||
|
||||
The Gradle build system is used to build the compiler.
|
||||
The Gradle build system is used to build the compiler. You will also need at least Java version 17 or higher to build it.
|
||||
The most interesting gradle commands to run are probably the ones listed below.
|
||||
(Note: if you have a recent gradle installed on your system already, you can probably replace the ``./gradlew`` wrapper commands with just the regular ``gradle`` command.)
|
||||
|
||||
@@ -68,7 +68,7 @@ For normal use, the ``installDist`` task should suffice and after succesful comp
|
||||
|
||||
.. hint::
|
||||
Development and testing is done on Linux using the IntelliJ IDEA IDE,
|
||||
but the actual prog8 compiler should run on all operating systems that provide a java runtime (version 11 or newer).
|
||||
but the actual prog8 compiler should run on all operating systems that provide a Java runtime (version 11 or newer).
|
||||
If you do have trouble building or running the compiler on your operating system, please let me know!
|
||||
|
||||
To successfully build and debug in IDEA, you have to do two things manually first:
|
||||
@@ -99,7 +99,8 @@ It's easy to compile yourself, but a recent precompiled .exe (only for Windows)
|
||||
*You need at least version 1.58.0 of this assembler.*
|
||||
If you are on Linux, there's probably a "64tass" package in the repositories, but check if it is a recent enough version.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself. Version 17 or higher if you want to
|
||||
build the compiler from source.
|
||||
If you're scared of Oracle's licensing terms, get one of the versions of another vendor. Even Microsoft provides their own version.
|
||||
Other OpenJDK builds can be found at `Adoptium <https://adoptium.net/temurin/releases/?version=11>`_ .
|
||||
For MacOS you can also use the Homebrew system to install a recent version of OpenJDK.
|
||||
@@ -381,8 +382,9 @@ Fix it by adding an ``%import floats``.
|
||||
Gradle error when building the compiler yourself
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
If you get a gradle build error containing the line "No matching toolchains found for requested specification"
|
||||
somewhere, it means that the Gradle build tool can't locate the correct version of the JDK to use.
|
||||
You will need a JDK for Java version 11 or higher to build the compiler.
|
||||
or "Gradle requires JVM 17 or later to run", it means that the Gradle build tool can't locate the correct version of the JDK to use.
|
||||
You will need a Java JDK version 17 or higher to build the compiler.
|
||||
(the compiler itself only needs Java 11 or higher to run though.)
|
||||
|
||||
Strange assembler errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+72
-14
@@ -114,10 +114,28 @@ mkword (msb, lsb)
|
||||
So mkword($80, $22) results in $8022.
|
||||
|
||||
.. note::
|
||||
The arguments to the mkword() function are in 'natural' order that is first the msb then the lsb.
|
||||
The arguments are in 'natural' left to right reading order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then msb)
|
||||
|
||||
mklong (msb, b2, b1, lsb)
|
||||
Efficiently create a long value from four bytes (the msb, second, first and finally the lsb). Avoids multiplication and shifting.
|
||||
So mklong($12, $34, $56, $78) results in $12345678.
|
||||
|
||||
.. note::
|
||||
The arguments are in 'natural' left to right reading order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 32-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then b1, b2 and finally the msb)
|
||||
|
||||
mklong2 (msw, lsw)
|
||||
Efficiently create a long value from two words (the msw, and the lsw). Avoids multiplication and shifting.
|
||||
So mklong2($1234, $abcd) results in $1234abcd.
|
||||
|
||||
.. note::
|
||||
The arguments are in 'natural' left to right reading order that is first the msw then the lsw.
|
||||
Don't get confused by how the system actually stores this 32-bit word value in memory (which is
|
||||
in little-endian format, so lsw first then the msw)
|
||||
|
||||
offsetof (Struct.field)
|
||||
The offset in bytes of the given field in the struct. The first field will always have offset 0.
|
||||
Usually you just reference the fields directly but in some cases it might be useful to know how many
|
||||
@@ -135,6 +153,9 @@ peekw (address)
|
||||
Caution: when using peekw to get words out of an array pointer, make sure the array is *not* a split word array
|
||||
(peekw requires the LSB and MSB of the word value to be consecutive in memory).
|
||||
|
||||
peekl (address)
|
||||
reads the signed long value at the given address in memory. Long is read as usual little-endian lsb/msb byte order.
|
||||
|
||||
peekf (address)
|
||||
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
|
||||
|
||||
@@ -148,6 +169,9 @@ pokebool (address, value)
|
||||
pokew (address, value)
|
||||
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
|
||||
|
||||
pokel (address, value)
|
||||
writes the signed long value at the given address in memory, in usual little-endian lsb/msb byte order.
|
||||
|
||||
pokef (address, value)
|
||||
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
|
||||
|
||||
@@ -258,6 +282,24 @@ Grouped per compilation target.
|
||||
* `pet32 <_static/symboldumps/skeletons-pet32.txt>`_
|
||||
* `virtual <_static/symboldumps/skeletons-virtual.txt>`_
|
||||
|
||||
bcd
|
||||
---
|
||||
Decimal addition and subtraction routines. Utilize the BCD mode of the CPU.
|
||||
(note: not all 6502 variants support this mode).
|
||||
This mode is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string:
|
||||
just print the hexadecimal score representation.
|
||||
Available routines:
|
||||
|
||||
- ``sub addb(byte a, byte b) -> byte``
|
||||
- ``sub addub(ubyte a, ubyte b) -> ubyte``
|
||||
- ``sub addw(word a, word b) -> word``
|
||||
- ``sub adduw(uword a, uword b) -> uword``
|
||||
- ``sub addl(long a, long b) -> long``
|
||||
- ``sub subb(byte a, byte b) -> byte``
|
||||
- ``sub subub(ubyte a, ubyte b) -> ubyte``
|
||||
- ``sub subuw(uword a, uword b) -> uword``
|
||||
- ``sub subl(long a, long b) -> long``
|
||||
|
||||
|
||||
bmx (cx16 only)
|
||||
----------------
|
||||
@@ -422,10 +464,6 @@ On the other targets, it only contains the definition of the 16 memory-mapped vi
|
||||
``restore_virtual_registers()``
|
||||
restore the values of all 16 virtual registers r0 - r15 from the buffer. Might be useful in an IRQ handler to avoid clobbering them.
|
||||
|
||||
``cpu_is_65816()``
|
||||
Returns true if the CPU in the computer is a 65816, false otherwise (6502 cpu).
|
||||
Note that Prog8 itself has no support yet for this CPU other than detecting its presence.
|
||||
|
||||
``reset_system ()``
|
||||
Soft-reset the system back to initial power-on BASIC prompt. (same as the routine in sys)
|
||||
|
||||
@@ -778,6 +816,9 @@ but perhaps the provided ones can be of service too.
|
||||
Returns the absolute difference, or distance, between the two word values.
|
||||
(This routine is more efficient than doing a compare and a subtract separately, or using abs)
|
||||
|
||||
``mul32 (woord w1, word w2) -> long``
|
||||
Returns the 32 bits signed long result of w1 * w2
|
||||
|
||||
``mul16_last_upper () -> uword``
|
||||
Fetches the upper 16 bits of the previous 16*16 bit multiplication.
|
||||
To avoid corrupting the result, it is best performed immediately after the multiplication.
|
||||
@@ -800,15 +841,14 @@ but perhaps the provided ones can be of service too.
|
||||
Call the start() routine first, feed it bytes with the update() routine, finalize with calling the end() routine which returns the crc16 value.
|
||||
Note: after calling the crc16_end() routine you must start over.
|
||||
|
||||
``crc32 (uword data, uword length)``
|
||||
``crc32 (uword data, uword length) -> long``
|
||||
Calculates a CRC-32 (ISO-HDLC/PKZIP) checksum over the given data buffer.
|
||||
The 32 bits result is stored in cx16.r14 (low word) and cx16.r15 (high word).
|
||||
The 32 bits result is returned as a long value. The routine clobbers R0/R1 and R12 through R15.
|
||||
|
||||
``crc32_start() / crc32_update(ubyte value) / crc32_end() / crc32_end_result()``
|
||||
"streaming" crc32 calculation routines, when the data doesn't fit in a single buffer.
|
||||
Tracks the crc32 checksum in cx16.r14 and cx16.r15! If your code uses these, it must save/restore them before calling this routine!
|
||||
Call the start() routine first, feed it bytes with the update() routine, finalize with calling the end() routine.
|
||||
The 32 bits result is stored in cx16.r14 (low word) and cx16.r15 (high word).
|
||||
Call the start() routine first, feed it bytes with the update() routine, finalize with calling the end() routine that returns the result value as a long.
|
||||
Instead of the normal end() routine you can also call crc32_end_result() which finalizes the calculation,
|
||||
and actually returns the high and low words of the 32 bits result value as two return word values.
|
||||
Note: after calling the crc32_end() or crc32_end_result() routine you must start over.
|
||||
@@ -1060,6 +1100,10 @@ sys (part of syslib)
|
||||
- 128 = Commodore 128
|
||||
- 255 = Virtual machine
|
||||
|
||||
``cpu_is_65816()``
|
||||
Returns true if the CPU in the computer is a 65816, false otherwise (6502 cpu).
|
||||
Note that Prog8 itself has no support yet for this CPU other than detecting its presence.
|
||||
|
||||
``exit (returncode)``
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
@@ -1159,12 +1203,19 @@ sys (part of syslib)
|
||||
This allows other code to run that might clobber these values temporarily.
|
||||
|
||||
``push (value)``
|
||||
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
|
||||
pushes a byte value on the CPU hardware stack.
|
||||
Low-level function that is seldomly used in user code.
|
||||
|
||||
``pushw (value)``
|
||||
pushes a 16-bit word value on the CPU hardware stack. Low-level function that should normally not be used.
|
||||
pushes a 16-bit word value on the CPU hardware stack.
|
||||
Low-level function that is seldomly used in user code.
|
||||
Don't assume anything about the order in which the bytes are pushed - popw will make sense of them again.
|
||||
|
||||
``pushl (value)``
|
||||
pushes a 32-bit value on the CPU hardware stack.
|
||||
Low-level function that is seldomly used in user code.
|
||||
Don't assume anything about the order in which the bytes are pushed - popl will make sense of them again.
|
||||
|
||||
``push_returnaddress (address)``
|
||||
pushes a 16 bit memory address on the CPU hardware stack in the same byte order as a JSR instruction would,
|
||||
which means the next RTS instruction will jump to that address instead.you
|
||||
@@ -1172,11 +1223,15 @@ sys (part of syslib)
|
||||
|
||||
``pop ()``
|
||||
pops a byte value off the CPU hardware stack and returns it.
|
||||
Low-level function that should normally not be used.
|
||||
Low-level function that is seldomly used in user code.
|
||||
|
||||
``popw ()``
|
||||
pops a 16-bit word value off the CPU hardware stack that was pushed before by pushw, and returns it.
|
||||
Low-level function that should normally not be used.
|
||||
Low-level function that is seldomly used in user code.
|
||||
|
||||
``popl ()``
|
||||
pops a 32-bit value off the CPU hardware stack that was pushed before by pushl, and returns it.
|
||||
Low-level function that is seldomly used in user code.
|
||||
|
||||
|
||||
textio (txt.*)
|
||||
@@ -1211,7 +1266,10 @@ Available for the Cx16 target. Routines that use the Vera FX logic to accelerate
|
||||
But it depends on some Vera manipulation and 4 bytes in vram just below the PSG registers for storage.
|
||||
Note: there is a block level %option "verafxmuls" that automatically replaces all word multiplications in that block
|
||||
by calls to verafx, but be careful with it because it may interfere with other Vera operations or IRQs.
|
||||
The full 32 bits result value is returned in two result values: lower word, upper word.
|
||||
The full 32 bits result value is returned as a long.
|
||||
|
||||
``muls16``
|
||||
Like ``muls`` but only returns the lower word of the result, which is sometimes useful if you're just interested in word values.
|
||||
|
||||
``mult16``
|
||||
VeraFX hardware multiplication of two unsigned words.
|
||||
|
||||
@@ -340,10 +340,20 @@ Directives
|
||||
}
|
||||
|
||||
|
||||
.. data:: %breakpoint
|
||||
.. data:: %breakpoint! or %breakpoint
|
||||
|
||||
Level: not at module scope.
|
||||
Defines a debugging breakpoint at this location. See :ref:`debugging`
|
||||
The version with the explamation point '!' at the end can be used even
|
||||
if the breakpoint follows an expression. If you don't use the '!' version in this case
|
||||
the compiler may think it is just a term in the expression (modulo operator and breakpoint operand value),
|
||||
instead of a breakpoint directive::
|
||||
|
||||
a = b
|
||||
%breakpoint ; parse error because it thinks it is part of the previous line
|
||||
|
||||
a = b
|
||||
%breakpoint! ; parsed correctly as directive
|
||||
|
||||
|
||||
.. data:: %encoding <encodingname>
|
||||
@@ -1140,6 +1150,13 @@ so pay attention to any jumps and rts instructions in the inlined code!
|
||||
You can use them directly but their name isn't very descriptive, so it may be useful to define
|
||||
an alias for them when using them regularly.
|
||||
|
||||
.. note::
|
||||
Dealing with **long** arguments and return values:
|
||||
A long takes 4 bytes (or 2 words, if you will). *There is no register definition specific to long types*.
|
||||
The way you specify the 'register' for a long argument or return value for an asmsub is by using a *virtual register pair*.
|
||||
For example, you can use R0+R1, R2+R3, R4+R5 and so on to take a long value instead.
|
||||
The syntax to use as a 'register' name for those pairs is ``R0R1_32``, ``R2R3_32``, ``R4R5_32`` and so on.
|
||||
|
||||
|
||||
External subroutines
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -15,23 +15,24 @@ Structs and Pointers
|
||||
priority over other variables to be placed into zeropage.
|
||||
|
||||
.. note::
|
||||
Due to some limitations in the language parser, not all pointer related syntax is currently supported
|
||||
if it is a pointer to a struct type.
|
||||
Due to a few limitations in the language parser, some pointer related syntax is currently unsupported.
|
||||
The compiler tries its best to give a descriptive error message but sometimes there is still a
|
||||
parser limitation that has to be worked around at the moment. For example, this pointer arithmetic
|
||||
indexing syntax is not supported right now and will result in a parse error::
|
||||
parser limitation that has to be worked around at the moment. For example, this assigment syntax doesn't parse correctly::
|
||||
|
||||
^^Node np
|
||||
np[2].field = 9999
|
||||
np[2].field = 9999 ; cannot use this syntax as assignment target right now
|
||||
ubyte value = np[2].field ; note that using it as expression value works fine
|
||||
|
||||
To work around this (and similar) cases you'll have to break up the expression in multiple steps,
|
||||
in this case something like::
|
||||
To work around this you'll have to explicitly write the pointer dereferencing operator,
|
||||
or break up the expression in multiple steps (which can be beneficial too when you are assigning multiple fields
|
||||
because it will save a pointer calculation for every assignment)::
|
||||
|
||||
^^Node thirdnode = &&np[2]
|
||||
thirdnode.field = 9999
|
||||
|
||||
*Note: this example is not regular array indexing syntax, it's pointer arithmetic by indexing on the pointer itself. Regular array syntax is supported just fine for arrays containing pointers etc.*
|
||||
^^Node np
|
||||
np[2]^^.field = 9999
|
||||
|
||||
; alternatively, split up:
|
||||
^^Node thenode = &&np[2]
|
||||
thenode.field = 9999
|
||||
|
||||
|
||||
Legacy untyped pointers (uword)
|
||||
@@ -129,10 +130,10 @@ dealing with all of them separately. You first define the struct type like so::
|
||||
bool elite
|
||||
}
|
||||
|
||||
You can use boolean fields, numeric fields (byte, word, float), and pointer fields (including str, which is translated into ^^ubyte).
|
||||
You can use boolean fields, numeric fields (byte, word, long, float), and pointer fields (including str, which is translated into ^^ubyte).
|
||||
You cannot nest struct types nor put arrays in them as a field.
|
||||
Fields in a struct are 'packed' (meaning the values are placed back-to-back in memory), and placed in memory in order of declaration. This guarantees exact size and place of the fields.
|
||||
``sizeof()`` knows how to calculate the size of a struct.
|
||||
``sizeof()`` knows how to calculate the combined size of a struct, and ``offsetof()`` can be used to get the byte offset of a given field in the struct.
|
||||
The size of a struct cannot exceed 1 memory page (256 bytes).
|
||||
|
||||
You can copy the whole contents of a struct to another one by assigning the dereferenced pointers::
|
||||
@@ -154,9 +155,11 @@ Because it implies pointer dereferencing you can usually omit the explicit `^^`,
|
||||
|
||||
|
||||
.. note::
|
||||
Structs are only supported as a *reference type* (via a pointer). It is currently not possible to use them as a value type.
|
||||
Structs are currently only supported as a *reference type* (they always have to be accessed through a pointer).
|
||||
It is not yet possible to use them as a value type, or as memory-mapped types.
|
||||
This means you cannot create an array of structs either - only arrays of pointers to structs.
|
||||
There are a couple of simple case where the compiler allows assignment of struct instances. You are allowed to write::
|
||||
There are a couple of simple case where the compiler does allow assignment of struct instances though, and it will
|
||||
automatically copy all the fields for you. You are allowed to write::
|
||||
|
||||
ptr2^^ = ptr1^^
|
||||
ptr2^^ = ptr1[2]
|
||||
@@ -166,14 +169,14 @@ Because it implies pointer dereferencing you can usually omit the explicit `^^`,
|
||||
In the future more cases may be supported.
|
||||
|
||||
.. note::
|
||||
Using structs instead of plain arrays may result in less efficent code being generated.
|
||||
Using structs instead of plain arrays usually results in more and less efficent code being generated.
|
||||
This is because the 6502 CPU is not particularly well equipped to dealing with pointers and accessing struct fields via offsets,
|
||||
as compared to direct variable access or array indexing. The prog8 program code may be easier to work with though!
|
||||
|
||||
.. note::
|
||||
Accessing the first field in a struct is more efficient than subsequent fields, because it
|
||||
is at offset 0 so no additional addition has to be done on a pointer to reach the first field.
|
||||
Try to put the most often accessed field as the first field to gain a rather substantial code size and efficiency boost.
|
||||
is at offset 0 so no additional addition has to be computed on a pointer to reach the first field.
|
||||
Try to put the most often accessed field as the first field to potentially gain a rather substantial boost in code efficiency.
|
||||
|
||||
|
||||
Static initialization of structs
|
||||
|
||||
@@ -196,6 +196,8 @@ These routines are::
|
||||
|
||||
sys.set_irq(uword handler_address)
|
||||
sys.set_rasterirq(uword handler_address, uword rasterline)
|
||||
sys.update_rasterirq(uword handler_address, uword rasterline)
|
||||
sys.set_rasterline(uword rasterline)
|
||||
sys.restore_irq() ; set everything back to the systems default irq handler
|
||||
|
||||
The IRQ handler routine must return a boolean value (0 or 1) in the A register:
|
||||
|
||||
+29
-23
@@ -135,7 +135,7 @@ Subroutine Calling Convention
|
||||
Calling a subroutine requires three steps:
|
||||
|
||||
#. preparing the arguments (if any) and passing them to the routine.
|
||||
Numeric types are passed by value (bytes, words, booleans, floats),
|
||||
Numeric types are passed by value (bytes, words, longs, booleans, floats),
|
||||
but array types passed by reference which means as ``uword`` being a pointer to their address in memory.
|
||||
Strings are passed as a pointer to a byte: ``^^ubyte``.
|
||||
#. calling the subroutine
|
||||
@@ -151,29 +151,16 @@ Regular subroutines
|
||||
- The arguments passed in a subroutine call are evaluated by the caller, and then put into those variables by the caller.
|
||||
The order of evaluation of subroutine call arguments *is unspecified* and should not be relied upon.
|
||||
- The subroutine is invoked.
|
||||
- The return value is not put into a variable, but the subroutine passes it back to the caller via register(s):
|
||||
- The return value is not put into a variable, but the subroutine passes it back to the caller via register(s). See below.
|
||||
|
||||
- A byte value will be put in ``A`` .
|
||||
- A boolean value will be put in ``A`` too, as 0 or 1.
|
||||
- A word or pointer value will be put in ``A`` + ``Y`` register pair (lsb in A, msb in Y).
|
||||
- A float value will be put in the ``FAC1`` float 'register'.
|
||||
.. sidebar::
|
||||
**Builtin functions can be different:**
|
||||
|
||||
- In case of *multiple* return values:
|
||||
some builtin functions are special and won't exactly follow the rules in this paragraph.
|
||||
|
||||
- for an ``asmsub`` or ``extsub`` the subroutine's signature specifies the output registers that contain the values explicitly,
|
||||
just as for a single return value.
|
||||
- for regular subroutines, the compiler will return the first of the return values via the cpu register ``A``` (or ``A + Y``` if it's a word value),
|
||||
just like for subroutines that only return a single value.
|
||||
The remainder of the return values are returned via the "virtual registers" cx16.r16-cx16.r0 (using R15 first and counting down to R0).
|
||||
A floating point value is passed via FAC1 as usual (only a single floating point value is supported,
|
||||
using FAC1 and FAC2 together unfortunately interferes with the values).
|
||||
**Single arguments will often be passed in registers:**
|
||||
|
||||
|
||||
**Builtin functions can be different:**
|
||||
some builtin functions are special and won't exactly follow these rules.
|
||||
|
||||
**Some arguments will be passed in registers:**
|
||||
For single byte, word, and pointer arguments, the values are simply loaded in cpu registers by the caller before calling the subroutine.
|
||||
For *single* byte, word, and pointer arguments (not long or float), the values are simply loaded in cpu registers by the caller before calling the subroutine.
|
||||
*The subroutine itself will take care of putting the values into the parameter variables.* This saves on code size because
|
||||
otherwise all callers would have to store the values in those variables themselves.
|
||||
Note that his convention is also still used for subroutines that specify parameters to be put into
|
||||
@@ -192,13 +179,32 @@ Single word parameter: ``sub foo(uword bar) { ... }``
|
||||
Single pointer parameter: ``sub foo(^^ubyte bar) { ... }``
|
||||
gets bar in the register pair A + Y (lsb in A, msb in Y), *subroutine* stores it into parameter variable
|
||||
|
||||
Floating point parameter: ``sub foo(float bar) { ... }``
|
||||
value for bar gets copied into the parameter variable *by the caller*
|
||||
Long or Floating point parameter: ``sub foo(long bar) { ... }``, ``sub foo(float bar) { ... }``
|
||||
value for bar gets stored into the parameter variable *by the caller*
|
||||
|
||||
Other: ``sub foo(ubyte bar, ubyte baz, ubyte zoo) { ... }``
|
||||
register values indeterminate, values all get stored in the parameter variables *by the caller*
|
||||
not using registers; all values get stored in the subroutine's parameter variables *by the caller*
|
||||
|
||||
|
||||
**Return value**
|
||||
|
||||
- A byte return value will be put in ``A`` .
|
||||
- A boolean return value will be put in ``A`` too, as 0 or 1.
|
||||
- A word return or pointer value will be put in ``A`` + ``Y`` register pair (lsb in A, msb in Y).
|
||||
- A long return value will be put into ``cx16.r0 : cx16.r1`` (2 combined word registers to make up a single 32 bits long)
|
||||
- A float return value will be put in the ``FAC1`` float 'register'.
|
||||
|
||||
In case of *multiple* return values:
|
||||
|
||||
- for an ``asmsub`` or ``extsub`` the subroutine's signature specifies the output registers that contain the values explicitly,
|
||||
just as for a single return value.
|
||||
- for regular subroutines, the compiler will return the first of the return values via the cpu register ``A``` (or ``A + Y``` if it's a word value),
|
||||
just like for subroutines that only return a single value.
|
||||
The remainder of the return values are returned via the "virtual registers" cx16.r16-cx16.r0 (using R15 first and counting down to R0).
|
||||
Long values will take a pair of those "virtual registers" that combined make up a single 32 bits value.
|
||||
A floating point value is passed via FAC1 as usual (only a single floating point value is supported,
|
||||
using FAC1 and FAC2 together unfortunately interferes with the values).
|
||||
|
||||
|
||||
``asmsub`` and ``extsub`` routines
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+109
-109
@@ -1,6 +1,108 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
LONG TYPE
|
||||
---------
|
||||
- implement the other comparison operators (<,>,<=,>=) on longs
|
||||
- implement LONG testcases in testmemory
|
||||
|
||||
|
||||
STRUCTS and TYPED POINTERS
|
||||
--------------------------
|
||||
|
||||
- implement the remaining TODO's in PointerAssignmentsGen.
|
||||
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
|
||||
- optimize the float copying in assignIndexedPointer() (also word?)
|
||||
- optimize augmented assignments to indexed pointer targets like sprptr[2]^^.y++ (these are now not performend in-place but as a regular assignment)
|
||||
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
|
||||
- support @nosplit pointer arrays?
|
||||
- support pointer to pointer?
|
||||
- support for typed function pointers? (&routine could be typed by default as well then)
|
||||
- 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- After long variable type is completed: make all constants long by default (remove type name altogether), reduce to target type implictly if the actual value fits.
|
||||
This will break some existing programs that depend on value wrap arounds, but gives more intuitive constant number handling.
|
||||
Can give descriptive error message for old syntax that still includes the type name?
|
||||
- improve ANTLR grammar with better error handling (as suggested by Qwen AI)
|
||||
- allow memory() to occur in array initializer
|
||||
- fix carry bit for cmp(long), check if it even worked for cmp(word)...
|
||||
- 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? (domain types instead of primitives)
|
||||
- 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
|
||||
-----
|
||||
- is it possible to use LOADFIELD/STOREFIELD instructions even more?
|
||||
- make multiple classes of registers and maybe also categorize by life time , to prepare for better register allocation in the future
|
||||
SYSCALL_ARGS, // Reserved for syscall arguments (r99000-99099, r99100-99199)
|
||||
FUNCTION_PARAMS, // For passing function parameters
|
||||
FUNCTION_RETURNS, // For function return values
|
||||
TEMPORARY, // Short-lived temporary values
|
||||
LOCAL_VARIABLES, // Local variables within functions
|
||||
GLOBAL_VARIABLES, // Global/static variables
|
||||
HARDWARE_MAPPED, // Mapped to CPU hardware registers
|
||||
LOOP_INDICES, // Used as loop counters
|
||||
ADDRESS_CALCULATION // Used for pointer arithmetic
|
||||
Categorizing registers by lifetime can significantly improve allocation:
|
||||
- Short-lived: Temporary registers used in expressions
|
||||
- Medium-lived: Local variables within a function
|
||||
Registers could be categorized by how frequently they're accessed:
|
||||
- Hot Registers: Frequently accessed (should be allocated to faster physical registers)
|
||||
- Warm Registers: Moderately accessed
|
||||
- Cold Registers: Rarely accessed (can be spilled to memory if needed)
|
||||
We already have type-based pools
|
||||
- byte, word, float registers
|
||||
|
||||
- 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?)
|
||||
- proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg
|
||||
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)
|
||||
- implement fast code paths for TODO("inplace split....
|
||||
- implement more TODOs in AssignmentGen
|
||||
- do something with the 'split' tag on split word arrays
|
||||
- add more optimizations in IRPeepholeOptimizer
|
||||
- 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? Or not? 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...? Should array operations be encoded in a more high level form in 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.. :(
|
||||
- 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 }
|
||||
more detailed example:
|
||||
|
||||
not all source lines are correctly reported in the IR file,
|
||||
for example the below subroutine only shows the sub() line::
|
||||
|
||||
@@ -55,115 +157,9 @@ and for example the below code omits line 5::
|
||||
}
|
||||
|
||||
|
||||
STRUCTS and TYPED POINTERS
|
||||
--------------------------
|
||||
|
||||
- the type of struct initializer arrays should not be uword[] but ^^struct[] ?
|
||||
- implement the remaining TODO's in PointerAssignmentsGen.
|
||||
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
|
||||
- optimize addUnsignedByteOrWordToAY in PointerAssignmentsGen a bit more
|
||||
- optimize the float copying in assignIndexedPointer() (also word?)
|
||||
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
|
||||
- support @nosplit pointer arrays?
|
||||
- support pointer to pointer?
|
||||
- support for typed function pointers? (&routine could be typed by default as well then)
|
||||
- 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- optimization conflict detection:
|
||||
1 fun applyModificationsSafely(modifications: List<IAstModification>) {
|
||||
2 // Can check for conflicts before applying
|
||||
3 modifications.groupBy { getTargetNode(it) }.forEach { target, mods ->
|
||||
4 if (mods.size > 1) {
|
||||
5 // Handle or report conflicting modifications
|
||||
6 }
|
||||
7 }
|
||||
8 modifications.forEach { it.perform() }
|
||||
9 }
|
||||
- improve ANTLR grammar with better error handling (according to Qwen AI)
|
||||
- allow memory() to occur in array initializer
|
||||
- %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? (domain types instead of primitives)
|
||||
- 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
|
||||
-----
|
||||
- is it possible to use LOADFIELD/STOREFIELD instructions more?
|
||||
- make multiple classes of registers and maybe also categorize by life time , to prepare for better register allocation in the future
|
||||
SYSCALL_ARGS, // Reserved for syscall arguments (r99000-99099, r99100-99199)
|
||||
FUNCTION_PARAMS, // For passing function parameters
|
||||
FUNCTION_RETURNS, // For function return values
|
||||
TEMPORARY, // Short-lived temporary values
|
||||
LOCAL_VARIABLES, // Local variables within functions
|
||||
GLOBAL_VARIABLES, // Global/static variables
|
||||
HARDWARE_MAPPED, // Mapped to CPU hardware registers
|
||||
LOOP_INDICES, // Used as loop counters
|
||||
ADDRESS_CALCULATION // Used for pointer arithmetic
|
||||
Categorizing registers by lifetime can significantly improve allocation:
|
||||
- Short-lived: Temporary registers used in expressions
|
||||
- Medium-lived: Local variables within a function
|
||||
Registers could be categorized by how frequently they're accessed:
|
||||
- Hot Registers: Frequently accessed (should be allocated to faster physical registers)
|
||||
- Warm Registers: Moderately accessed
|
||||
- Cold Registers: Rarely accessed (can be spilled to memory if needed)
|
||||
We already have type-based pools
|
||||
- byte, word, float registers
|
||||
|
||||
- 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)?
|
||||
@@ -172,12 +168,16 @@ Libraries
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
- more optimized operator handling of different types, for example uword a ^ byte b now does a type cast of b to word first
|
||||
- optimize longEqualsValue() for const and variable operands to not assign needlessly to R0-R3.
|
||||
- optimize optimizedBitwiseExpr() for const and variable operands to not assign needlessly to R0-R3.
|
||||
- optimize inplacemodificationLongWithLiteralval() for more shift values such as 8, 16, 24 etc but take sign bit into account!
|
||||
- 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?
|
||||
- Since fixing the missing zp-var initialization, programs grew in size again because STZ's reappeared. 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
|
||||
This will probably need the register categorization from the IR explained there, for the old 6502 codegen there is not enough information to act on
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably all be removed, or be made permanent
|
||||
+21
-12
@@ -39,6 +39,7 @@ Various examples::
|
||||
byte counter = len([1, 2, 3]) * 20
|
||||
byte age = 2018 - 1974
|
||||
float wallet = 55.25
|
||||
long large = 998877
|
||||
ubyte x,y,z ; declare three ubyte variables x y and z
|
||||
str name = "my name is Alice"
|
||||
uword address = &counter
|
||||
@@ -173,8 +174,8 @@ type identifier type storage size example var declara
|
||||
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar == false``
|
||||
``word`` signed word 2 bytes = 16 bits ``word myvar = -12345``
|
||||
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
|
||||
``long`` signed 32 bits integer n/a ``const long LARGE = $12345678``
|
||||
(only for consts)
|
||||
``long`` signed 32 bits integer 4 bytes ``long large = $12345678``
|
||||
there is no unsigned long type at the moment.
|
||||
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
|
||||
stored in 5-byte cbm MFLPT format
|
||||
``byte[x]`` signed byte array x bytes ``byte[4] myvar``
|
||||
@@ -195,10 +196,10 @@ type identifier type storage size example var declara
|
||||
``^^type`` typed pointer 2 bytes pointer types are explained in their own chapter :ref:`pointers`
|
||||
=============== ======================= ================= =========================================
|
||||
|
||||
Integers (bytes, words)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Integers (bytes, words, longs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
|
||||
Integers are 8, 16 or 32 bit numbers and can be written in normal decimal notation,
|
||||
in hexadecimal and in binary notation. There is no octal notation. Hexadecimal has the '$' prefix,
|
||||
binary has the '%' prefix. Note that ``%`` is also the remainder operator so be careful: if you want to take the remainder
|
||||
of something with an operand starting with 1 or 0, you'll have to add a space in between, otherwise
|
||||
@@ -210,7 +211,9 @@ For instance ``3_000_000`` is a valid decimal number and so is ``%1001_0001`` a
|
||||
A single character in single quotes such as ``'a'`` is translated into a byte integer,
|
||||
which is the PETSCII value for that character. You can prefix it with the desired encoding, like with strings, see :ref:`encodings`.
|
||||
|
||||
**bytes versus words:**
|
||||
*Endianness:* all integers are stored in *little endian* byte order, so the Least significant byte first and the Most significant byte last.
|
||||
|
||||
**bytes versus words versus longs:**
|
||||
|
||||
Prog8 tries to determine the data type of integer values according to the table below,
|
||||
and sometimes the context in which they are used.
|
||||
@@ -222,7 +225,7 @@ value datatype
|
||||
0 .. 255 ubyte
|
||||
-32768 .. 32767 word
|
||||
0 .. 65535 uword
|
||||
-2147483647 .. 2147483647 long (only for const)
|
||||
-2147483647 .. 2147483647 long (there is no unsigned long right now)
|
||||
========================= =================
|
||||
|
||||
If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword``
|
||||
@@ -233,15 +236,21 @@ to be done on word values, and don't want to explicitly have to cast everything
|
||||
uword offset = column * 64 ; does (column * 64) as uword, wrong result?
|
||||
uword offset = column * $0040 ; does (column as uword) * 64 , a word calculation
|
||||
|
||||
Only for ``const`` numbers, you can use larger values (32 bits signed integers). The compiler can handle those
|
||||
internally in expressions. As soon as you have to actually store it into a variable,
|
||||
you have to make sure the resulting value fits into the byte or word size of the variable.
|
||||
|
||||
.. attention::
|
||||
Doing math on signed integers can result in code that is a lot larger and slower than
|
||||
when using unsigned integers. Make sure you really need the signed numbers, otherwise
|
||||
stick to unsigned integers for efficiency.
|
||||
|
||||
.. attention::
|
||||
Not all operations on Long integers are supported at the moment, although most common
|
||||
operations should work fine.
|
||||
There is no unsigned long type at the moment, but you can sometimes simply treat the signed
|
||||
long value as an unsigned 32 bits value just fine.
|
||||
Operations on long integers take a lot of instructions on 8 bit cpu's so code that uses them
|
||||
a lot will be much slower than when you restrict yourself to 8 or 16 bit values. Use long values sparingly.
|
||||
**Several operations on long values require the use of the R0 and R1 virtual register as temporary storage**
|
||||
so if you are working with long values, you should assume that the contents of R0 and R1 are destroyed.
|
||||
|
||||
|
||||
Booleans
|
||||
^^^^^^^^
|
||||
@@ -538,7 +547,7 @@ Constants
|
||||
When using ``const``, the value of the 'variable' cannot be changed; it has become a compile-time constant value instead.
|
||||
You'll have to specify the initial value expression. This value is then used
|
||||
by the compiler everywhere you refer to the constant (and no memory is allocated
|
||||
for the constant itself). Onlythe simple numeric types (byte, word, float) can be defined as a constant.
|
||||
for the constant itself). Onlythe simple numeric types (byte, word, long, float) can be defined as a constant.
|
||||
If something is defined as a constant, very efficient code can usually be generated from it.
|
||||
Variables on the other hand can't be optimized as much, need memory, and more code to manipulate them.
|
||||
Note that a subset of the library routines in the ``math``, ``strings`` and ``floats`` modules are recognised in
|
||||
|
||||
@@ -27,11 +27,9 @@ charset {
|
||||
; copies the charset from ROM to RAM so we can modify it
|
||||
|
||||
sys.set_irqd()
|
||||
ubyte bank = @($0001)
|
||||
@($0001) = bank & %11111011 ; enable CHAREN, so the character rom accessible at $d000
|
||||
c64.banks(%011) ; enable CHAREN, so the character rom accessible at $d000
|
||||
sys.memcopy($d000, CHARSET, 256*8*2) ; copy the charset to RAM
|
||||
|
||||
@($0001) = bank ; reset previous memory banking
|
||||
c64.banks(%111) ; enable I/O registers again
|
||||
sys.clear_irqd()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
; TODO in progress attempts to make a flexible sprite multiplexer
|
||||
; For an easy sprite multiplexer (actually, just a sprite duplicator) see the "simplemultiplexer" example.
|
||||
; inspiration: https://codebase64.net/doku.php?id=base:sprite_multiplexing
|
||||
|
||||
%import syslib
|
||||
%import math
|
||||
%import textio
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
const ubyte NUM_VSPRITES = 16
|
||||
const uword spritedata_base = $3000 ; NOTE: in VIC bank 0 (and 2), the Char Rom is at $1000 so we can't store sprite data there
|
||||
|
||||
sub start() {
|
||||
setup_sprite_graphics()
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
|
||||
method1_wait_raster_before_each_sprite()
|
||||
|
||||
; Theoretically, I think all sprite Y values can be set in one go, and again after the last of the 8 hw sprites has started drawing, etc.
|
||||
; There's no strict need to set it right before the sprite is starting to draw. But it only saves a few cycles?
|
||||
; method2_after_old_sprite()
|
||||
|
||||
}
|
||||
|
||||
sub setup_sprite_graphics() {
|
||||
; prepare the sprite graphics (put a different letter in each of the balloons)
|
||||
sys.set_irqd()
|
||||
c64.banks(%011) ; enable CHAREN, so the character rom accessible at $d000 (inverse at $d400)
|
||||
ubyte i
|
||||
for i in 0 to NUM_VSPRITES-1 {
|
||||
uword sprdat = spritedata_base + $0040*i
|
||||
sys.memcopy(balloonsprite, sprdat, 64)
|
||||
ubyte cptr
|
||||
for cptr in 0 to 7 {
|
||||
sprdat[7+cptr*3] = @($d400+(i+sc:'a')*$0008 + cptr)
|
||||
}
|
||||
^^Sprite sprite = sprites[i]
|
||||
sprite.dataptr = lsb(spritedata_base/64) + i
|
||||
}
|
||||
c64.banks(%111) ; enable IO registers again
|
||||
; sys.clear_irqd() ; do not re-enable IRQ as this will glitch the display because it interferes with raster timing
|
||||
}
|
||||
|
||||
sub method1_wait_raster_before_each_sprite() {
|
||||
repeat {
|
||||
animate_sprites()
|
||||
; wait for raster lines and update hardware sprites
|
||||
ubyte vs_index
|
||||
for vs_index in 0 to NUM_VSPRITES-1 {
|
||||
ubyte virtual_sprite = sort_virtualsprite[vs_index]
|
||||
ubyte hw_sprite = virtual_sprite & 7
|
||||
^^Sprite sprite = sprites[virtual_sprite]
|
||||
sys.waitrasterline(sprite.y-3) ; wait for a few lines above the sprite so we have time to set all attributes properly
|
||||
c64.EXTCOL++
|
||||
c64.SPXY[hw_sprite*2+1] = sprite.y
|
||||
c64.SPXY[hw_sprite*2] = lsb(sprite.x)
|
||||
if sprite.x >= 256
|
||||
c64.MSIGX |= msigx_setmask[hw_sprite]
|
||||
else
|
||||
c64.MSIGX &= msigx_clearmask[hw_sprite]
|
||||
c64.SPCOL[hw_sprite] = sprite.color
|
||||
c64.SPRPTR[hw_sprite] = sprite.dataptr
|
||||
c64.EXTCOL--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ubyte tt
|
||||
sub animate_sprites() {
|
||||
tt += 2
|
||||
ubyte st = tt
|
||||
ubyte virtual_sprite
|
||||
for virtual_sprite in 0 to NUM_VSPRITES-1 {
|
||||
^^Sprite sprite = sprites[virtual_sprite]
|
||||
sprite.x = $0028 + math.sin8u(st)
|
||||
; TODO nice anim sprite.y = $50 + math.cos8u(st*3)/2
|
||||
st += 10
|
||||
sort_ypositions[virtual_sprite] = sprite.y
|
||||
sort_virtualsprite[virtual_sprite] = virtual_sprite
|
||||
; 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
|
||||
sprite = sprites[0]
|
||||
sprite.y++
|
||||
sort_ypositions[0] = sprites[0].y
|
||||
sprite = sprites[1]
|
||||
sprite.y--
|
||||
sort_ypositions[1] = sprites[1].y
|
||||
|
||||
;c64.EXTCOL--
|
||||
sort_virtual_sprites()
|
||||
;c64.EXTCOL++
|
||||
}
|
||||
|
||||
ubyte[NUM_VSPRITES] sort_ypositions
|
||||
ubyte[NUM_VSPRITES] sort_virtualsprite
|
||||
|
||||
; TODO rewrite in assembly, sorting must be super fast
|
||||
sub sort_virtual_sprites() {
|
||||
ubyte @zp pos=1
|
||||
while pos != NUM_VSPRITES {
|
||||
if sort_ypositions[pos]>=sort_ypositions[pos-1]
|
||||
pos++
|
||||
else {
|
||||
; swap elements
|
||||
cx16.r0L = sort_ypositions[pos-1]
|
||||
sort_ypositions[pos-1] = sort_ypositions[pos]
|
||||
sort_ypositions[pos] = cx16.r0L
|
||||
; swap virtual sprite indexes
|
||||
cx16.r0L = sort_virtualsprite[pos-1]
|
||||
sort_virtualsprite[pos-1] = sort_virtualsprite[pos]
|
||||
sort_virtualsprite[pos] = cx16.r0L
|
||||
pos--
|
||||
if_z
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ubyte[8] msigx_setmask = [
|
||||
%00000001,
|
||||
%00000010,
|
||||
%00000100,
|
||||
%00001000,
|
||||
%00010000,
|
||||
%00100000,
|
||||
%01000000,
|
||||
%10000000
|
||||
]
|
||||
|
||||
ubyte[8] msigx_clearmask = [
|
||||
%11111110,
|
||||
%11111101,
|
||||
%11111011,
|
||||
%11110111,
|
||||
%11101111,
|
||||
%11011111,
|
||||
%10111111,
|
||||
%01111111
|
||||
]
|
||||
|
||||
struct Sprite {
|
||||
ubyte color
|
||||
ubyte dataptr
|
||||
uword x
|
||||
ubyte y
|
||||
}
|
||||
|
||||
|
||||
^^Sprite[NUM_VSPRITES] sprites = [
|
||||
[0, $ff, 20, 60],
|
||||
[1, $ff, 40, 70],
|
||||
[2, $ff, 60, 80],
|
||||
[3, $ff, 80, 90],
|
||||
[4, $ff, 100, 100],
|
||||
[5, $ff, 120, 110],
|
||||
[7, $ff, 140, 120], ; skip color 6 (blue on blue)
|
||||
[8, $ff, 160, 130],
|
||||
[9, $ff, 180, 140],
|
||||
[10, $ff, 200, 150],
|
||||
[11, $ff, 220, 160],
|
||||
[12, $ff, 240, 170],
|
||||
[13, $ff, 260, 180],
|
||||
[14, $ff, 280, 190],
|
||||
[15, $ff, 300, 200],
|
||||
[0, $ff, 320, 210],
|
||||
]
|
||||
|
||||
|
||||
ubyte[] balloonsprite = [
|
||||
%00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
; Simple sprite multiplexer.
|
||||
; This is just a sprite duplicator routine that displays the 8 hardware sprites multiple times in rows,
|
||||
; it does not do any complicated sprite Y manipulation/multiplexing.
|
||||
; No assembly code is used.
|
||||
|
||||
|
||||
%import syslib
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in 0 to 7 {
|
||||
c64.set_sprite_ptr(i, &sprites.balloonsprite) ; alternatively, set directly: c64.SPRPTR[i] = $0a00 / 64
|
||||
}
|
||||
sprites.set_sprites_X(0)
|
||||
sprites.set_sprites_Y(sprites.sprites_Y_start)
|
||||
c64.SPCOL[5] = 8 ; change blue balloon to different color
|
||||
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
irq.first_sprite_X = 0
|
||||
irq.sprites_Y = sprites.sprites_Y_start
|
||||
sys.set_rasterirq(&irq.multiplexer, irq.sprites_Y+1)
|
||||
|
||||
; exit program back to basic, balloons will keep flying :-)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
irq {
|
||||
uword first_sprite_X
|
||||
ubyte sprites_Y
|
||||
|
||||
; Here is the actual multiplexing routine.
|
||||
; it's a raster irq just after the start of the sprite,
|
||||
; that updates the Y position of all the sprits,
|
||||
; and registers a new rater irq for that next row of sprites.
|
||||
; If the bottom of the screen is reached, it resets the X position of the sprites as well,
|
||||
; and moves the sprites back to the top of the screen.
|
||||
sub multiplexer() -> bool {
|
||||
c64.EXTCOL++
|
||||
sprites_Y += 24
|
||||
bool system_irq = false
|
||||
if sprites_Y > (255-24-1) {
|
||||
cx16.r2 = c64.RASTER + 23
|
||||
sprites_Y = sprites.sprites_Y_start
|
||||
first_sprite_X++
|
||||
if first_sprite_X >= 340
|
||||
first_sprite_X =0
|
||||
sprites.set_sprites_Y(sprites_Y)
|
||||
while c64.RASTER != cx16.r2 {
|
||||
; wait until raster line after sprite has been fully drawn (at least 24 lines down)
|
||||
}
|
||||
sprites.set_sprites_X(first_sprite_X) ; we can now update the X positions without risk of sprite tearing
|
||||
system_irq = true
|
||||
} else {
|
||||
sprites.set_sprites_Y(sprites_Y)
|
||||
}
|
||||
|
||||
sys.set_rasterline(sprites_Y+1)
|
||||
c64.EXTCOL--
|
||||
return system_irq
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sprites {
|
||||
const ubyte sprites_Y_start = 55
|
||||
|
||||
sub set_sprites_Y(ubyte y) {
|
||||
for cx16.r0L in 1 to 15 step 2 {
|
||||
c64.SPXY[cx16.r0L] = y
|
||||
}
|
||||
}
|
||||
|
||||
sub set_sprites_X(uword x) {
|
||||
for cx16.r0L in 0 to 14 step 2 {
|
||||
x += 20
|
||||
if x >= 340
|
||||
x -= 340
|
||||
c64.SPXY[cx16.r0L] = lsb(x)
|
||||
cx16.r1L = msb(x)>>1
|
||||
ror(c64.MSIGX) ; rotate that MSB bit of the sprite X position into the MSB sprite X register for each sprite
|
||||
}
|
||||
}
|
||||
|
||||
ubyte[] @align64 balloonsprite = [
|
||||
%00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000
|
||||
]
|
||||
}
|
||||
|
||||
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user