mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5ae9ad4e0 | ||
|
|
6329388a4e | ||
|
|
ac851a35bc | ||
|
|
bc0d6453ae | ||
|
|
8c65e0a537 | ||
|
|
d415cf356f | ||
|
|
2bd74b8c44 | ||
|
|
88b48f58c9 | ||
|
|
75accf4059 | ||
|
|
c3f9959891 | ||
|
|
ee492a1e9a | ||
|
|
415f0fb071 | ||
|
|
8e7bf4227b | ||
|
|
d2b743c296 | ||
|
|
cb44e76626 | ||
|
|
b2983e912d | ||
|
|
10ae42ed59 | ||
|
|
5cbd4aafcc | ||
|
|
4aa326d5ca | ||
|
|
39b1bbcf58 | ||
|
|
4661d3bada | ||
|
|
ab98f4a5e2 | ||
|
|
b99b054a09 | ||
|
|
5d5ad8a70e | ||
|
|
9a5edeebbc | ||
|
|
3fb45a611a | ||
|
|
2e0db6f211 | ||
|
|
51ac096a31 | ||
|
|
dfad83650d | ||
|
|
492cd686a0 | ||
|
|
561e05a764 | ||
|
|
f56037142b | ||
|
|
a41cb60328 | ||
|
|
ca3e4b161b | ||
|
|
4971ebd41f | ||
|
|
e2276f000c | ||
|
|
520a12194e | ||
|
|
6c61e1e5e8 | ||
|
|
a385412e70 | ||
|
|
928443469a | ||
|
|
823f1cd6e2 | ||
|
|
3009ddaaab | ||
|
|
bc0aecb1af | ||
|
|
573702111e | ||
|
|
eaab2cfe5f | ||
|
|
666285a002 | ||
|
|
d477e9b2bd | ||
|
|
d774bad2b4 | ||
|
|
e3bb09d2a1 | ||
|
|
112bce0ac0 | ||
|
|
aac7d68455 | ||
|
|
620793011e | ||
|
|
cb4ffaa028 | ||
|
|
8c4944d703 | ||
|
|
f5fbb5f930 | ||
|
|
40ce40db32 | ||
|
|
ee556434bc | ||
|
|
5cd175d54f | ||
|
|
b51e116b63 | ||
|
|
5c7ac0efb7 | ||
|
|
34b228cfca | ||
|
|
2b26952044 | ||
|
|
db38c27224 | ||
|
|
560013e58b | ||
|
|
cdb41f4352 | ||
|
|
13cd68ba96 | ||
|
|
f69b293b7f | ||
|
|
d4bf0bf5b7 | ||
|
|
8c6a95f5e2 | ||
|
|
6301a8b040 | ||
|
|
daa9b40883 | ||
|
|
aa045bb383 | ||
|
|
9d7049a659 | ||
|
|
bbfb2eb367 | ||
|
|
447271f3f2 | ||
|
|
78d6d2d59a | ||
|
|
c6cd39b4ab | ||
|
|
cd34c4eed0 | ||
|
|
652b585ea4 | ||
|
|
2913fa47b2 | ||
|
|
d6e4175e21 | ||
|
|
3a0add60fd | ||
|
|
bc4470aeda | ||
|
|
97dade3655 | ||
|
|
54f23387d0 | ||
|
|
77161e0f39 | ||
|
|
70d99bebb0 | ||
|
|
75156f7dbd | ||
|
|
9827be0047 | ||
|
|
495dd526e1 | ||
|
|
3d20bc13b5 | ||
|
|
6028cc49ab | ||
|
|
23d3fb96b9 | ||
|
|
51503e054a | ||
|
|
3f6177cbb8 | ||
|
|
c1c88e0327 | ||
|
|
18ba25d6a4 | ||
|
|
e74162671a | ||
|
|
2290308e06 | ||
|
|
82693cc3ee | ||
|
|
50b8cae519 | ||
|
|
3a74931a61 | ||
|
|
c87e920c61 | ||
|
|
2c2de8bfb3 | ||
|
|
d386343fe7 | ||
|
|
5b32cfd172 | ||
|
|
46dffcfb96 | ||
|
|
b156c876f0 | ||
|
|
71b66f506d | ||
|
|
28d087b8b4 | ||
|
|
a57815153d | ||
|
|
f910f73f28 | ||
|
|
6635515bf0 | ||
|
|
cc0425a2c4 | ||
|
|
c7052a183e | ||
|
|
d9a0f3cb4a | ||
|
|
80d6eb232d | ||
|
|
d9ffb10eb5 | ||
|
|
463d53345d | ||
|
|
4b1063530e | ||
|
|
9266943e74 | ||
|
|
1e3ae2cb1d | ||
|
|
55bc242e8b | ||
|
|
aa3bbbb867 | ||
|
|
45ad0911df | ||
|
|
946def4613 | ||
|
|
3000c399bd | ||
|
|
1682400988 | ||
|
|
6ad435df1f | ||
|
|
cd433b2111 | ||
|
|
a50eebb369 | ||
|
|
13d4af59c7 | ||
|
|
b6f4f2adb7 | ||
|
|
e57d9f59a2 | ||
|
|
071d5dc0c5 |
No files matched your search
-16
@@ -534,7 +534,6 @@
|
||||
<inspection_tool class="GroovyUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GroovyUnusedIncOrDec" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GroovyVariableNotAssigned" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GrpcSchemes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="Guava" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HardcodedFileSeparators" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_recognizeExampleMediaType" value="false" />
|
||||
@@ -585,20 +584,6 @@
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlWrongAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpClientDuplicateImportInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpClientInappropriateProtocolUsageInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpClientRunRequestNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpClientUnresolvedAuthId" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpClientUnresolvedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestContentLengthIsIgnored" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestCustomHttpMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestEnvironmentAuthConfigurationValidationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestJsonBodyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestPlaceholder" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestRequestSeparatorJsonBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestRequestSeparatorXmlBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestRequestSeparatorYamlBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpRequestWhitespaceInsideRequestTargetPath" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
@@ -629,7 +614,6 @@
|
||||
<option name="reportPerFile" value="true" />
|
||||
<option name="kotlinOnly" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="IncrementDecrementUsedAsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="InfiniteLoopJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
|
||||
Generated
+10
-10
@@ -1,23 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21" />
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.21/kotlin-stdlib-jdk8-2.2.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.21/kotlin-stdlib-jdk7-2.2.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.21/kotlin-stdlib-jdk8-2.2.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.21/kotlin-stdlib-jdk7-2.2.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.21/kotlin-stdlib-jdk8-2.2.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.21/kotlin-stdlib-jdk7-2.2.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -175,7 +175,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
}
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
when compiled and ran on a C-64 you'll get:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ main {
|
||||
|
||||
str[20] benchmark_names
|
||||
uword[20] benchmark_score
|
||||
str version = "12.1"
|
||||
|
||||
|
||||
sub start() {
|
||||
@@ -35,8 +36,10 @@ main {
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
|
||||
txt.print("\n\n\n prog8 compiler benchmark tests.\n\n benchmark version v12\n\n")
|
||||
sys.wait(60)
|
||||
txt.print("\n\n\n prog8 compiler benchmark tests.\n\n benchmark version ")
|
||||
txt.print(version)
|
||||
txt.print("\n\n")
|
||||
sys.wait(120)
|
||||
|
||||
benchmark_number = 0
|
||||
|
||||
@@ -88,7 +91,10 @@ main {
|
||||
txt.color2(1, 6)
|
||||
uword total_score
|
||||
benchmark_number = 0
|
||||
txt.print("\nscore benchmark (v12)\n\n")
|
||||
txt.print("\nscore benchmark (")
|
||||
txt.print(version)
|
||||
txt.print(")\n\n")
|
||||
|
||||
do {
|
||||
txt.spc()
|
||||
txt.print_uw(benchmark_score[benchmark_number])
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.2.21"
|
||||
kotlin("jvm") version "2.3.0"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -18,7 +18,7 @@ allprojects {
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf("-Xwhen-guards")
|
||||
freeCompilerArgs = listOf()
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
|
||||
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
|
||||
|
||||
@@ -37,7 +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.R14R15_32)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
|
||||
null -> ReturnConvention(null, null)
|
||||
@@ -46,7 +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.R14R15_32)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
@@ -63,7 +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.R14R15_32, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R14R15, 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)
|
||||
@@ -89,8 +89,8 @@ 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)),
|
||||
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"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)),
|
||||
@@ -100,6 +100,8 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
|
||||
"prog8_lib_copylong" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"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)),
|
||||
@@ -114,6 +116,11 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"sqrt__uword" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.LONG)),
|
||||
"sqrt__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"swap" to FSignature(false, null, FParam("var1", *NumericDatatypes + BaseDataType.BOOL), FParam("var2", *NumericDatatypes + BaseDataType.BOOL)),
|
||||
"swap__byte" to FSignature(false, null, FParam("var1", BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL), FParam("var2",BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL)),
|
||||
"swap__word" to FSignature(false, null, FParam("var1", BaseDataType.WORD, BaseDataType.UWORD), FParam("var2",BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"swap__long" to FSignature(false, null, FParam("var1", BaseDataType.LONG), FParam("var2",BaseDataType.LONG)),
|
||||
"swap__float" to FSignature(false, null, FParam("var1", BaseDataType.FLOAT), FParam("var2",BaseDataType.FLOAT)),
|
||||
"divmod" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)),
|
||||
"divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)),
|
||||
@@ -167,5 +174,11 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
"setlsb", "setmsb",
|
||||
"rol", "ror", "rol2", "ror2",
|
||||
"divmod", "divmod__ubyte", "divmod__uword"
|
||||
"divmod", "divmod__ubyte", "divmod__uword",
|
||||
"swap", "swap__byte", "swap__word", "swap__long", "swap__float"
|
||||
)
|
||||
|
||||
val SimpleBuiltinFunctions = setOf(
|
||||
"msb", "lsb", "msw", "lsw",
|
||||
"mkword", "mklong", "mklong2",
|
||||
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
@@ -18,6 +18,7 @@ class CompilationOptions(val output: OutputType,
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
var warnSymbolShadowing: Boolean = false,
|
||||
var warnImplicitTypeCast: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
var asmQuiet: Boolean = false,
|
||||
var asmListfile: Boolean = false,
|
||||
|
||||
@@ -51,6 +51,7 @@ val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType
|
||||
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.isFloat get() = this == BaseDataType.FLOAT
|
||||
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)
|
||||
@@ -402,7 +403,7 @@ enum class RegisterOrPair {
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
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;
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
|
||||
|
||||
companion object {
|
||||
val names by lazy { entries.map { it.toString()} }
|
||||
@@ -415,15 +416,19 @@ enum class RegisterOrPair {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the starting virtual register name for the current 32-bit combined virtual register
|
||||
* @return The starting register name as a string, WITHOUT THE cx16 block scope prefix!
|
||||
*/
|
||||
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"
|
||||
R0R1 -> "r0"
|
||||
R2R3 -> "r2"
|
||||
R4R5 -> "r4"
|
||||
R6R7 -> "r6"
|
||||
R8R9 -> "r8"
|
||||
R10R11 -> "r10"
|
||||
R12R13 -> "r12"
|
||||
R14R15 -> "r14"
|
||||
else -> throw IllegalArgumentException("must be a combined virtual register $this")
|
||||
}
|
||||
|
||||
@@ -447,7 +452,7 @@ enum class RegisterOrPair {
|
||||
}
|
||||
|
||||
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
|
||||
fun isLong() = this in combinedLongRegisters
|
||||
fun isLong() = this in CombinedLongRegisters
|
||||
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
@@ -484,15 +489,15 @@ 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 CombinedLongRegisters = arrayOf(
|
||||
RegisterOrPair.R0R1,
|
||||
RegisterOrPair.R2R3,
|
||||
RegisterOrPair.R4R5,
|
||||
RegisterOrPair.R6R7,
|
||||
RegisterOrPair.R8R9,
|
||||
RegisterOrPair.R10R11,
|
||||
RegisterOrPair.R12R13,
|
||||
RegisterOrPair.R14R15
|
||||
)
|
||||
|
||||
val CpuRegisters = arrayOf(
|
||||
|
||||
@@ -4,7 +4,7 @@ interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun info(msg: String, position: Position)
|
||||
fun undefined(symbol: List<String>, position: Position)
|
||||
fun undefined(symbol: List<String>, suggestImport: Boolean=false, position: Position)
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
fun finalizeNumErrors(numErrors: Int, numWarnings: Int, numInfos: Int) {
|
||||
|
||||
@@ -39,7 +39,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
|
||||
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ import kotlin.io.path.Path
|
||||
|
||||
object ImportFileSystem {
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun expandTilde(path: String): String = if (path.startsWith("~")) {
|
||||
val userHome = System.getProperty("user.home")
|
||||
userHome + path.drop(1)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.code.target.zp
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.core.Zeropage
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
// reference: "Mapping the C128" zeropage chapter.
|
||||
@@ -64,6 +61,41 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
private fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
val base = 0x0a // Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
|
||||
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
|
||||
free.remove((base+reg*2).toUInt())
|
||||
free.remove((base+1+reg*2).toUInt())
|
||||
}
|
||||
|
||||
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
|
||||
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
@@ -83,16 +83,28 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||
val base = 0x04 // Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
free.remove((4+reg*2).toUInt())
|
||||
free.remove((5+reg*2).toUInt())
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
|
||||
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
|
||||
free.remove((base+reg*2).toUInt())
|
||||
free.remove((base+1+reg*2).toUInt())
|
||||
}
|
||||
|
||||
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
|
||||
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,19 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((2+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
|
||||
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((3+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
|
||||
}
|
||||
|
||||
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
|
||||
allocatedVariables["cx16.r0r1sl"] = VarAllocation((2+0*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r2r3sl"] = VarAllocation((2+1*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r4r5sl"] = VarAllocation((2+2*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r6r7sl"] = VarAllocation((2+3*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r8r9sl"] = VarAllocation((2+4*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r10r11sl"] = VarAllocation((2+5*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((2+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((2+7*4).toUInt(), DataType.LONG, 4)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,24 @@ class ConfigurableZeropage(
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation(address+1u, DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation(address, DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation(address+1u, DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}bL"] = VarAllocation(address, DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
|
||||
allocatedVariables["cx16.r${reg}bH"] = VarAllocation(address+1u, DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
|
||||
free.remove(address)
|
||||
free.remove(address+1u)
|
||||
}
|
||||
}
|
||||
|
||||
if(virtualRegistersStart<0xffu) {
|
||||
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
|
||||
val start = virtualRegistersStart.toInt()
|
||||
allocatedVariables["cx16.r0r1sl"] = VarAllocation((start + 0 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r2r3sl"] = VarAllocation((start + 1 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r4r5sl"] = VarAllocation((start + 2 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r6r7sl"] = VarAllocation((start + 3 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r8r9sl"] = VarAllocation((start + 4 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r10r11sl"] = VarAllocation((start + 5 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((start + 6 * 4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((start + 7 * 4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.code.target.zp
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.core.Zeropage
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
|
||||
@@ -47,6 +44,41 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
private fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
val base = 0x04 // Unfortunately it cannot be the same as on the Commander X16 ($02). TODO: is this valid on PET?
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
|
||||
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
|
||||
free.remove((base+reg*2).toUInt())
|
||||
free.remove((base+1+reg*2).toUInt())
|
||||
}
|
||||
|
||||
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
|
||||
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
@@ -182,9 +182,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
fun findSubtypeReplacement(sub: ISubType): StStruct? {
|
||||
if(updatedSt.lookup(sub.scopedNameString)!=null)
|
||||
return null
|
||||
val old = st.lookup(sub.scopedNameString)
|
||||
if(old==null)
|
||||
throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
|
||||
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
|
||||
|
||||
val prefixed = ArrayDeque<String>()
|
||||
var node: StNode = old
|
||||
@@ -347,7 +345,7 @@ class AsmGen6502Internal (
|
||||
private val anyExprGen = AnyExprAsmGen(this)
|
||||
private val pointerGen = PointerAssignmentsGen(this, allocator)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, pointerGen, anyExprGen, allocator)
|
||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
|
||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, pointerGen,assignmentAsmGen)
|
||||
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, pointerGen, assignmentAsmGen, errors)
|
||||
private val ifExpressionAsmgen = IfExpressionAsmGen(this, assignmentAsmGen, errors)
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, assignmentAsmGen, this, pointerGen, allocator)
|
||||
@@ -529,7 +527,7 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
|
||||
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier, tempZpPtrVar: String = "P8ZP_SCRATCH_PTR"): String {
|
||||
// returns the source name of the zero page pointervar if it's already in the ZP,
|
||||
// otherwise returns "P8ZP_SCRATCH_PTR" which is the intermediary
|
||||
val symbol = symbolTable.lookup(pointervar.name)
|
||||
@@ -550,10 +548,10 @@ class AsmGen6502Internal (
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
lda (P8ZP_SCRATCH_PTR)""")
|
||||
"P8ZP_SCRATCH_PTR"
|
||||
sta $tempZpPtrVar
|
||||
sty $tempZpPtrVar+1
|
||||
lda ($tempZpPtrVar)""")
|
||||
tempZpPtrVar
|
||||
}
|
||||
} else {
|
||||
return if (allocator.isZpVar((target as PtNamedNode).scopedName)) {
|
||||
@@ -564,11 +562,11 @@ class AsmGen6502Internal (
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
sta $tempZpPtrVar
|
||||
sty $tempZpPtrVar+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_PTR),y""")
|
||||
"P8ZP_SCRATCH_PTR"
|
||||
lda ($tempZpPtrVar),y""")
|
||||
tempZpPtrVar
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -815,7 +813,7 @@ class AsmGen6502Internal (
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
|
||||
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
|
||||
in combinedLongRegisters -> 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")
|
||||
@@ -948,7 +946,7 @@ class AsmGen6502Internal (
|
||||
TargetStorageKind.ARRAY -> TODO("assign typecasted long to array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val startreg = target.register!!.startregname()
|
||||
out("""
|
||||
lda $valuesym
|
||||
@@ -1575,7 +1573,8 @@ $repeatLabel""")
|
||||
if(addrOf.isFromArrayElement) {
|
||||
TODO("address-of array element ${addrOf.position}")
|
||||
} else if(addrOf.dereference!=null) {
|
||||
TODO("read &dereference ${addrOf.position}")
|
||||
if(pointerGen.readByteByAddressOfDereference(addrOf, constOffset))
|
||||
return true
|
||||
} else {
|
||||
out(" lda ${asmSymbolName(addrOf.identifier!!)}+${constOffset}")
|
||||
return true
|
||||
@@ -2124,20 +2123,21 @@ $repeatLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadIndirectLongIntoR14R15(zpPtrVar: String, offset: UByte) {
|
||||
internal fun loadIndirectLongIntoCombinedLongRegister(zpPtrVar: String, offset: UByte, targetRegPair: RegisterOrPair) {
|
||||
val regstart = targetRegPair.startregname()
|
||||
out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
sta cx16.r14
|
||||
sta cx16.$regstart
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta cx16.r14+1
|
||||
sta cx16.$regstart+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta cx16.r14+2
|
||||
sta cx16.$regstart+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta cx16.r14+3""")
|
||||
sta cx16.$regstart+3""")
|
||||
}
|
||||
|
||||
internal fun storeIndirectByte(byte: Int, zpPtrVar: String, offset: UByte) {
|
||||
@@ -2442,6 +2442,34 @@ $repeatLabel""")
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
|
||||
internal fun storeIndirectFloatFP1(zpPtrVar: String, offset: UByte) {
|
||||
val floattempvar = AsmAssignTarget(TargetStorageKind.VARIABLE, this, DataType.FLOAT, null, Position.DUMMY, variableAsmName = "floats.floats_temp_var")
|
||||
assignRegister(RegisterOrPair.FAC1, floattempvar)
|
||||
if(offset>0u) {
|
||||
out("""
|
||||
lda #<floats.floats_temp_var
|
||||
ldy #>floats.floats_temp_var
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $zpPtrVar
|
||||
ldy $zpPtrVar+1
|
||||
clc
|
||||
adc #$offset
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.copy_float""")
|
||||
return
|
||||
}
|
||||
|
||||
out("""
|
||||
lda #<floats.floats_temp_var
|
||||
ldy #>floats.floats_temp_var
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $zpPtrVar
|
||||
ldy $zpPtrVar+1
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
|
||||
internal fun romableError(problem: String, pos: Position, assemblerShouldFail: Boolean = true) {
|
||||
if(options.romable) {
|
||||
@@ -2532,5 +2560,6 @@ internal class SubroutineExtraAsmInfo {
|
||||
}
|
||||
|
||||
|
||||
internal fun Double.toLongHex(): String = this.toUInt().toString(16).padStart(8, '0')
|
||||
internal fun Int.toLongHex(): String = this.toUInt().toString(16).padStart(8, '0')
|
||||
internal fun Long.toLongHex(): String = this.toUInt().toString(16).padStart(8, '0')
|
||||
internal fun Double.toLongHex(): String = this.toLong().toLongHex()
|
||||
@@ -811,44 +811,44 @@ private fun optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByF
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
|
||||
->
|
||||
->
|
||||
|
||||
clc
|
||||
adc P8ZP_SCRATCH_PTR
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
tya
|
||||
adc P8ZP_SCRATCH_PTR+1
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
adc P8ZP_SCRATCH_PTR
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
tya
|
||||
adc P8ZP_SCRATCH_PTR+1
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
|
||||
|
||||
also SECOND SEQUENCE:
|
||||
also SECOND SEQUENCE:
|
||||
|
||||
ldx VALUE/ ldy VALUE
|
||||
sta SOMEWHERE_WITHOUT_,x_OR_,y
|
||||
txa / tya
|
||||
ldy #1
|
||||
sta SOMEWHERE
|
||||
-->
|
||||
sta SOMEWHERE_WITHOUT_,x_OR_,y
|
||||
lda VALUE
|
||||
ldy #1
|
||||
sta SOMEWHERE
|
||||
sta SOMEWHERE_WITHOUT_,x_OR_,y
|
||||
txa / tya
|
||||
ldy #1
|
||||
sta SOMEWHERE
|
||||
-->
|
||||
sta SOMEWHERE_WITHOUT_,x_OR_,y
|
||||
lda VALUE
|
||||
ldy #1
|
||||
sta SOMEWHERE
|
||||
|
||||
|
||||
also THIRD SEQUENCE:
|
||||
|
||||
ldx VALUE
|
||||
ldy #0
|
||||
sta SOMEWHERE_WITHOUT_,x
|
||||
txa
|
||||
iny
|
||||
sta SOMEWHERE
|
||||
ldy #0
|
||||
sta SOMEWHERE_WITHOUT_,x
|
||||
txa
|
||||
iny
|
||||
sta SOMEWHERE
|
||||
-->
|
||||
ldy #0
|
||||
sta SOMEWHERE_WITHOUT_,x
|
||||
lda VALUE
|
||||
iny
|
||||
sta SOMEWHERE
|
||||
ldy #0
|
||||
sta SOMEWHERE_WITHOUT_,x
|
||||
lda VALUE
|
||||
iny
|
||||
sta SOMEWHERE
|
||||
*/
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFourteen) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.core.CombinedLongRegisters
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
|
||||
@@ -8,18 +9,21 @@ import prog8.code.core.RegisterOrPair
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) cx16 virtual word registers,
|
||||
// 2) paired CPU registers,
|
||||
// 3) single CPU registers (order Y,X,A),
|
||||
// 4) floating point registers (FAC1, FAC2),
|
||||
// 5) CPU Carry status flag
|
||||
// 1) combined cx16 virtual registers (longs),
|
||||
// 2) cx16 virtual word registers,
|
||||
// 3) paired CPU registers,
|
||||
// 4) single CPU registers (order Y,X,A),
|
||||
// 5) floating point registers (FAC1, FAC2),
|
||||
// 6) CPU Carry status flag
|
||||
val args = sub.parameters.withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
|
||||
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
|
||||
|
||||
combinedLongRegs.forEach { order += it.index }
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -727,6 +727,11 @@ $loopLabel sty $indexVar
|
||||
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
|
||||
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
|
||||
}
|
||||
else if(iterableDt.isLongArray) {
|
||||
if(range.last==range.first) return translateForSimpleLongRangeAsc(stmt, range)
|
||||
if(range.step==1 && range.last>range.first) return translateForSimpleLongRangeAsc(stmt, range)
|
||||
if(range.step==-1 && range.last<range.first) return translateForSimpleLongRangeDesc(stmt, range)
|
||||
}
|
||||
|
||||
// not one of the easy cases, generate more complex code...
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
@@ -835,7 +840,18 @@ $loopLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
iterableDt.isLongArray -> {
|
||||
// loop over long range via loopvar, step >= 2 or <= -2
|
||||
when(range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
}
|
||||
else -> {
|
||||
TODO("for long range. Report this error please, and use word loops in the meantime ${stmt.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid range expression datatype")
|
||||
}
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
@@ -904,6 +920,7 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==1)
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
@@ -931,6 +948,7 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==-1)
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
@@ -967,6 +985,16 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleLongRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==1)
|
||||
TODO("for long range step +1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
|
||||
}
|
||||
|
||||
private fun translateForSimpleLongRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==-1)
|
||||
TODO("for long range step -1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
|
||||
}
|
||||
|
||||
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
|
||||
@@ -205,13 +205,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
return when(arg) {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
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()
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
else if (arg.name == "mkword" || arg.name == "mklong2")
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||
else if (arg.name == "mklong")
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
|
||||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
|
||||
else
|
||||
!arg.isSimple()
|
||||
}
|
||||
is PtAddressOf -> false
|
||||
is PtIdentifier -> false
|
||||
@@ -290,11 +291,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
val requiredDt = parameter.value.type
|
||||
if(requiredDt!=value.type) {
|
||||
if(value.type.largerSizeThan(requiredDt))
|
||||
throw AssemblyError("can only convert byte values to word param types")
|
||||
throw AssemblyError("can only convert byte values to word param types ${value.position}")
|
||||
}
|
||||
if (statusflag!=null) {
|
||||
if(requiredDt!=value.type)
|
||||
throw AssemblyError("for statusflag, byte or bool value is required")
|
||||
throw AssemblyError("for statusflag, byte or bool value is required ${value.position}")
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this boolean param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
@@ -322,7 +323,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
asmgen.out(" ror a")
|
||||
}
|
||||
}
|
||||
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
} else throw AssemblyError("can only use Carry as status flag parameter ${value.position}")
|
||||
return RegisterOrStatusflag(null, statusflag)
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -957,7 +957,7 @@ _jump jmp (${target.asmLabel})
|
||||
else -> {
|
||||
if(long) {
|
||||
// note: clobbers R14+R15
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15_32, true)
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out(" lda cx16.r14+3")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
@@ -1273,15 +1273,15 @@ _jump jmp (${target.asmLabel})
|
||||
ora $varname+2
|
||||
ora $varname+3""")
|
||||
} else {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15_32, value.type.isSigned)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15, value.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
ora cx16.r14+1
|
||||
ora cx16.r14+2
|
||||
ora cx16.r14+3
|
||||
sta P8ZP_SCRATCH_REG""")
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_REG ; restore flags")
|
||||
}
|
||||
if(notEquals) {
|
||||
@@ -1299,7 +1299,7 @@ _jump jmp (${target.asmLabel})
|
||||
|
||||
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(lessEquals) {
|
||||
loadAndCheck0OrSignIntoCarry(value, true)
|
||||
checkLongLeZeroIntoCarry(value)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
|
||||
else
|
||||
@@ -1313,23 +1313,23 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
}
|
||||
|
||||
private fun longGreaterZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(lessEquals) {
|
||||
loadAndCheck0OrSignIntoCarry(value, false)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bcc", stmt)
|
||||
} else {
|
||||
private fun longGreaterZero(value: PtExpression, greaterEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(greaterEquals) {
|
||||
loadAndCmp0MSB(value, true)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bmi", stmt)
|
||||
} else {
|
||||
checkLongGtZeroIntoCarry(value)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bcc", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAndCheck0OrSignIntoCarry(value: PtExpression, negative: Boolean) {
|
||||
private fun checkLongGtZeroIntoCarry(value: PtExpression) {
|
||||
if(value is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
@@ -1337,17 +1337,35 @@ _jump jmp (${target.asmLabel})
|
||||
ldy #>$varname
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
asmgen.out(if(negative) " sec" else " clc")
|
||||
asmgen.out(" jsr prog8_lib.compare_long_0_and_sign")
|
||||
asmgen.out(" jsr prog8_lib.compare_long_gt_0")
|
||||
} else {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15_32, value.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, value.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda #<cx16.r14
|
||||
ldy #>cx16.r14
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
asmgen.out(if(negative) " sec" else " clc")
|
||||
asmgen.out(" jsr prog8_lib.compare_long_0_and_sign")
|
||||
asmgen.out(" jsr prog8_lib.compare_long_gt_0")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkLongLeZeroIntoCarry(value: PtExpression) {
|
||||
if(value is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
lda #<$varname
|
||||
ldy #>$varname
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
asmgen.out(" jsr prog8_lib.compare_long_le_0")
|
||||
} else {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, value.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda #<cx16.r14
|
||||
ldy #>cx16.r14
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
asmgen.out(" jsr prog8_lib.compare_long_le_0")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1470,7 +1488,7 @@ _jump jmp (${target.asmLabel})
|
||||
else
|
||||
{
|
||||
// TODO cannot easily preserve R14:R15 on stack because we need the status flags of the comparison in between...
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.R14R15_32, left.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.R14R15, left.type.isSigned)
|
||||
if(constRight!=null) {
|
||||
val hex = constRight.toLongHex()
|
||||
asmgen.out("""
|
||||
@@ -1529,12 +1547,12 @@ _jump jmp (${target.asmLabel})
|
||||
// this comparison is not part of an expression but part of an if statement, there's no need to save the previous values of the temp registers
|
||||
|
||||
if(operator=="<" || operator ==">=") {
|
||||
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R14R15_32, left.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R12R13_32, left.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R14R15, left.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R12R13, left.type.isSigned)
|
||||
} else {
|
||||
// flip operands
|
||||
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R14R15_32, left.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R12R13_32, left.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R14R15, left.type.isSigned)
|
||||
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R12R13, left.type.isSigned)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
|
||||
@@ -386,7 +386,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
// TODO cannot easily preserve R14:R15 on stack because we need the status flags of the comparison in between...
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, expr.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
cmp #$${hex.substring(6, 8)}
|
||||
@@ -424,7 +424,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
// TODO cannot easily preserve R14:R15 on stack because we need the status flags of the comparison in between...
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, expr.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
cmp $varname2
|
||||
@@ -492,15 +492,15 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
ora $varname+3
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, expr.type.isSigned)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
ora cx16.r14+1
|
||||
ora cx16.r14+2
|
||||
ora cx16.r14+3
|
||||
sta P8ZP_SCRATCH_REG""")
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_REG | bne $falseLabel")
|
||||
}
|
||||
}
|
||||
@@ -517,15 +517,15 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
ora $varname+3
|
||||
beq $falseLabel""")
|
||||
} else {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, expr.type.isSigned)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
ora cx16.r14+1
|
||||
ora cx16.r14+2
|
||||
ora cx16.r14+3
|
||||
sta P8ZP_SCRATCH_REG""")
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_REG | beq $falseLabel")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import prog8.code.core.DataType
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.PETTarget
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
|
||||
//
|
||||
@@ -191,7 +192,7 @@ internal class AnyExprAsmGen(
|
||||
|
||||
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
|
||||
when(asmgen.options.compTarget.name) {
|
||||
C64Target.NAME -> {
|
||||
C64Target.NAME, PETTarget.NAME -> {
|
||||
// C64 math library has a quirk: you have always make sure FAC2/ARG is loaded last (done using CONUPK)
|
||||
// otherwise the result of certain floating point operations such as FDIVT will be wrong.
|
||||
// see https://www.c64-wiki.com/wiki/CONUPK
|
||||
|
||||
@@ -118,7 +118,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
val dt = if(signed) DataType.WORD else DataType.UWORD
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
in CombinedLongRegisters -> {
|
||||
val dt = if(signed) DataType.LONG
|
||||
else
|
||||
TODO("unsigned long $pos")
|
||||
|
||||
@@ -353,7 +353,44 @@ internal class AssignmentAsmGen(
|
||||
assignMemoryByte(target, null, address)
|
||||
return
|
||||
}
|
||||
else if(address is PtAddressOf) {
|
||||
if(address.identifier!=null) {
|
||||
if(asmgen.isZpVar(address.identifier!!)) {
|
||||
asmgen.loadIndirectByte(asmgen.asmVariableName(address.identifier!!), 0u)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
} else if(address.dereference!=null) {
|
||||
val (zpPtrVar, offset) = pointergen.deref(address.dereference!!, false)
|
||||
asmgen.loadIndirectByte(zpPtrVar, offset)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
asmgen.assignExpressionToVariable(address, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
asmgen.loadIndirectByte("P8ZP_SCRATCH_PTR", 0u)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
else if (address is PtBinaryExpression) {
|
||||
val result = asmgen.pointerViaIndexRegisterPossible(address)
|
||||
if(result!=null) {
|
||||
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
|
||||
if(addressOfIdentifier!=null) {
|
||||
val varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
if(result.second is PtNumber) {
|
||||
val offset = (result.second as PtNumber).number.toInt()
|
||||
asmgen.out(" lda $varname+$offset")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
} else if (result.second is PtIdentifier) {
|
||||
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
|
||||
asmgen.out(" ldx $offsetname | lda $varname,x")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(address, false)) {
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
@@ -525,7 +562,7 @@ internal class AssignmentAsmGen(
|
||||
dt.isLong -> {
|
||||
TODO("read long ${value.position}")
|
||||
}
|
||||
else -> throw AssemblyError("unsupported dereference type ${dt} ${value.position}")
|
||||
else -> throw AssemblyError("unsupported dereference type $dt ${value.position}")
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -638,6 +675,43 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
|
||||
private fun assignBuiltinFunctionCall(target: AsmAssignTarget, value: PtBuiltinFunctionCall) {
|
||||
|
||||
if(value.name=="peekl" && target.kind==TargetStorageKind.VARIABLE) {
|
||||
val arg = value.args[0]
|
||||
if(arg is PtNumber) {
|
||||
val address = arg.number.toInt()
|
||||
asmgen.out("""
|
||||
lda $address
|
||||
sta ${target.asmVarname}
|
||||
lda $address+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $address+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $address+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
return
|
||||
}
|
||||
else if(arg is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
if(asmgen.isZpVar(arg)) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}
|
||||
iny
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}+1
|
||||
iny
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}+2
|
||||
iny
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}+3""")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val returnDt = asmgen.translateBuiltinFunctionCallExpression(value, target.register)
|
||||
if(target.register==null) {
|
||||
// still need to assign the result to the target variable/etc.
|
||||
@@ -650,7 +724,7 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
returnDt== BaseDataType.LONG -> {
|
||||
// longs are in R14:R15 (r14=lsw, r15=msw)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15_32)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
}
|
||||
returnDt==BaseDataType.FLOAT -> {
|
||||
// float result from function sits in FAC1
|
||||
@@ -705,7 +779,7 @@ internal class AssignmentAsmGen(
|
||||
RegisterOrPair.AY -> assignVirtualRegister(target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignVirtualRegister(target, RegisterOrPair.XY)
|
||||
in Cx16VirtualRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
in combinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
in CombinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
else -> {
|
||||
val sflag = returnValue.first.statusflag
|
||||
if(sflag!=null)
|
||||
@@ -759,7 +833,7 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPointer -> assignRegisterpairWord(target, register)
|
||||
target.datatype.isLong -> {
|
||||
require(register in combinedLongRegisters)
|
||||
require(register in CombinedLongRegisters)
|
||||
assignRegisterLong(target, register)
|
||||
}
|
||||
else -> throw AssemblyError("expected byte or word")
|
||||
@@ -783,7 +857,7 @@ internal class AssignmentAsmGen(
|
||||
true
|
||||
else {
|
||||
if(expr.type.isLong && expr.operator in "*/%")
|
||||
TODO("long multiplication or division ${expr.position}")
|
||||
TODO("long multiplication or division - use floats for now ${expr.position} ")
|
||||
|
||||
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
|
||||
}
|
||||
@@ -1260,10 +1334,10 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
|
||||
// TODO: cannot preserve R14:R15 on the stack here because the stack already contains a value that we need to pop
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, signed)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, signed)
|
||||
asmgen.out(" pla | sta P8ZP_SCRATCH_REG")
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "P8ZP_SCRATCH_REG")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15_32)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1399,13 +1473,13 @@ internal class AssignmentAsmGen(
|
||||
return true
|
||||
}
|
||||
|
||||
if(target.register!=RegisterOrPair.R14R15_32)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, signed)
|
||||
if(target.register!=RegisterOrPair.R14R15)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, signed)
|
||||
augmentableAsmGen.inplacemodificationLongWithLiteralval("cx16.r14", expr.operator, shifts)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15_32)
|
||||
if(target.register!=RegisterOrPair.R14R15_32)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(target.register!=RegisterOrPair.R14R15)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1648,23 +1722,55 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
} else {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<${right.number.toHex()}
|
||||
tax
|
||||
tya
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
if(right.number.toInt() and 255 == 0) {
|
||||
// can add only to the msb, which is in Y
|
||||
if(right.number==256.0)
|
||||
asmgen.out(" iny")
|
||||
else if(right.number==512.0)
|
||||
asmgen.out(" iny | iny")
|
||||
else
|
||||
asmgen.out("""
|
||||
tax
|
||||
tya
|
||||
clc
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<${right.number.toHex()}
|
||||
tax
|
||||
tya
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${right.number.toHex()}
|
||||
tax
|
||||
tya
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
if(right.number.toInt() and 255 == 0) {
|
||||
// can sub only from the msb, which is in Y
|
||||
if(right.number==256.0)
|
||||
asmgen.out(" dey")
|
||||
else if(right.number==512.0)
|
||||
asmgen.out(" dey | dey")
|
||||
else
|
||||
asmgen.out("""
|
||||
tax
|
||||
tya
|
||||
sec
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${right.number.toHex()}
|
||||
tax
|
||||
tya
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
@@ -1810,7 +1916,7 @@ internal class AssignmentAsmGen(
|
||||
return true
|
||||
} else if(target.kind==TargetStorageKind.POINTER) {
|
||||
// not an expression, no need to save R14/R15
|
||||
assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, target.datatype.isSigned)
|
||||
assignExpressionToRegister(expr, RegisterOrPair.R14R15, target.datatype.isSigned)
|
||||
pointergen.assignLongVar(target.pointer!!, "cx16.r14")
|
||||
return true
|
||||
} else {
|
||||
@@ -1855,8 +1961,8 @@ internal class AssignmentAsmGen(
|
||||
return true
|
||||
} else {
|
||||
// isn't an expression, so no need to preserve R12-R15
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13_32, left.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15_32, left.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, left.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, left.type.isSigned)
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
@@ -1888,24 +1994,24 @@ internal class AssignmentAsmGen(
|
||||
sbc cx16.r14+3
|
||||
sta cx16.r12+3""")
|
||||
}
|
||||
asmgen.assignRegister(RegisterOrPair.R12R13_32, target)
|
||||
asmgen.assignRegister(RegisterOrPair.R12R13, target)
|
||||
return true
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val targetreg = target.register
|
||||
if(targetreg==RegisterOrPair.R14R15_32)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13_32, 1)
|
||||
if(targetreg==RegisterOrPair.R14R15)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
else
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13_32, 2)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, expr.left.type.isSigned)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13_32, expr.right.type.isSigned)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 2)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, expr.left.type.isSigned)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13, expr.right.type.isSigned)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "cx16.r12")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15_32)
|
||||
if(targetreg==RegisterOrPair.R14R15_32)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13_32, 1)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(targetreg==RegisterOrPair.R14R15)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
else
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 2)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 2)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2072,10 +2178,10 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
|
||||
val targetreg = target.register
|
||||
if(targetreg!=RegisterOrPair.R14R15_32) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
if(targetreg!=RegisterOrPair.R14R15) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, expr.left.type.isSigned)
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, expr.left.type.isSigned)
|
||||
val constval = expr.right.asConstInteger()
|
||||
val varname = (expr.right as? PtIdentifier)?.name
|
||||
if(constval!=null)
|
||||
@@ -2086,9 +2192,9 @@ internal class AssignmentAsmGen(
|
||||
// TODO: preserve R14:R15 on stack here? (the function does't do it for us)
|
||||
augmentableAsmGen.inplacemodificationLongWithExpression("cx16.r14", expr.operator, expr.right)
|
||||
}
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15_32)
|
||||
if(targetreg!=RegisterOrPair.R14R15_32) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(targetreg!=RegisterOrPair.R14R15) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -2599,15 +2705,15 @@ $endLabel""")
|
||||
}
|
||||
valueDt.isByte -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14R15_32, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15_32, valueDt.base)
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14R15, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isWord || valueDt.isPointer -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isFloat -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
|
||||
@@ -2647,7 +2753,7 @@ $endLabel""")
|
||||
assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
|
||||
return
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
in CombinedLongRegisters -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
|
||||
return
|
||||
@@ -2673,35 +2779,35 @@ $endLabel""")
|
||||
// 'cast' uword into a 16 bits register, just assign it
|
||||
return assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
|
||||
}
|
||||
RegisterOrPair.R0R1_32 -> {
|
||||
RegisterOrPair.R0R1 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R0, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R2R3_32 -> {
|
||||
RegisterOrPair.R2R3 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R2, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R4R5_32 -> {
|
||||
RegisterOrPair.R4R5 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R4, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R6R7_32 -> {
|
||||
RegisterOrPair.R6R7 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R6, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R8R9_32 -> {
|
||||
RegisterOrPair.R8R9 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R8, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R10R11_32 -> {
|
||||
RegisterOrPair.R10R11 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R10, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R12R13_32 -> {
|
||||
RegisterOrPair.R12R13 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R12, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R14R15_32 -> {
|
||||
RegisterOrPair.R14R15 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
|
||||
}
|
||||
@@ -2803,6 +2909,24 @@ $endLabel""")
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" jsr floats.GIVAYFAY")
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
lda #<$varname
|
||||
ldy #>$varname
|
||||
jsr floats.internal_long_AY_to_FAC""")
|
||||
}
|
||||
else -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
lda #<cx16.r14
|
||||
ldy #>cx16.r14
|
||||
jsr floats.internal_long_AY_to_FAC""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt at ${target.position}")
|
||||
}
|
||||
if(target.register==RegisterOrPair.FAC2) {
|
||||
@@ -2834,11 +2958,11 @@ $endLabel""")
|
||||
}
|
||||
is PtNumber -> throw AssemblyError("casting a long number to byte should have been const-folded away ${value.position}")
|
||||
else -> {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14R15_32, true)
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out(" lda cx16.r14")
|
||||
assignRegisterByte(target, CpuRegister.A, true, false)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3025,7 +3149,6 @@ $endLabel""")
|
||||
BaseDataType.UWORD, BaseDataType.POINTER -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
@@ -3074,13 +3197,13 @@ $endLabel""")
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda #<$sourceAsmVarName
|
||||
ldy #>$sourceAsmVarName
|
||||
jsr floats.cast_from_long""")
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type")
|
||||
}
|
||||
@@ -3090,7 +3213,7 @@ $endLabel""")
|
||||
|
||||
|
||||
private fun assignTypeCastedRegisters(targetAsmVarName: String, targetDt: BaseDataType,
|
||||
regs: RegisterOrPair, sourceDt: BaseDataType) {
|
||||
regs: RegisterOrPair, sourceDt: BaseDataType, position: Position) {
|
||||
if(sourceDt == targetDt)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
@@ -3113,13 +3236,12 @@ $endLabel""")
|
||||
BaseDataType.BYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> {
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
st${regs.toString().lowercase()} $targetAsmVarName
|
||||
@@ -3147,7 +3269,7 @@ $endLabel""")
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign byte to bool")
|
||||
BaseDataType.BOOL -> TODO("assign byte to bool $position")
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
}
|
||||
@@ -3157,7 +3279,7 @@ $endLabel""")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> asmgen.out(" txa")
|
||||
@@ -3167,7 +3289,6 @@ $endLabel""")
|
||||
asmgen.signExtendAYlsb(sourceDt)
|
||||
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, sourceDt)
|
||||
@@ -3191,11 +3312,11 @@ $endLabel""")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign uword to bool")
|
||||
BaseDataType.BOOL -> TODO("assign uword to bool $position")
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
@@ -3203,7 +3324,6 @@ $endLabel""")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
@@ -3230,11 +3350,11 @@ $endLabel""")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign word to bool")
|
||||
BaseDataType.BOOL -> TODO("assign word to bool $position")
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
BaseDataType.UWORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
@@ -3242,7 +3362,6 @@ $endLabel""")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
@@ -3294,10 +3413,21 @@ $endLabel""")
|
||||
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName")
|
||||
} else if(targetDt.isWord || targetDt.isPointer) {
|
||||
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName | lda cx16.$startreg+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
if(regs==RegisterOrPair.R0R1)
|
||||
throw AssemblyError("cannot assign long in R0R1 to float because r1 is used as a parameter $position")
|
||||
asmgen.out("""
|
||||
lda #<cx16.$startreg
|
||||
ldy #>cx16.$startreg
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type")
|
||||
throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
else -> throw AssemblyError("weird type $sourceDt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3512,7 +3642,7 @@ $endLabel""")
|
||||
sta ${target.asmVarname}+3,y""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val regstart = target.register!!.startregname()
|
||||
when(sourceDt) {
|
||||
DataType.BYTE -> {
|
||||
@@ -3581,7 +3711,7 @@ $endLabel""")
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val regstart = target.register!!.startregname()
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,y
|
||||
@@ -3715,7 +3845,7 @@ $endLabel""")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta cx16.${target.register.toString().lowercase()}+1")
|
||||
}
|
||||
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
else -> throw AssemblyError("can't load word in a single 8-bit register")
|
||||
}
|
||||
} else {
|
||||
@@ -3730,7 +3860,7 @@ $endLabel""")
|
||||
lda $varName+1
|
||||
sta cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
else -> throw AssemblyError("can't load word in a single 8-bit register")
|
||||
}
|
||||
}
|
||||
@@ -3909,7 +4039,7 @@ $endLabel""")
|
||||
lda #0
|
||||
sta cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
}
|
||||
@@ -4104,7 +4234,7 @@ $endLabel""")
|
||||
internal fun assignRegisterLong(target: AsmAssignTarget, pairedRegisters: RegisterOrPair) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(pairedRegisters in combinedLongRegisters) {
|
||||
if(pairedRegisters in CombinedLongRegisters) {
|
||||
val startreg = pairedRegisters.startregname()
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
@@ -4135,7 +4265,7 @@ $endLabel""")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val targetreg = target.register!!
|
||||
require(targetreg in combinedLongRegisters && pairedRegisters in combinedLongRegisters)
|
||||
require(targetreg in CombinedLongRegisters && pairedRegisters in CombinedLongRegisters)
|
||||
if(targetreg!=pairedRegisters) {
|
||||
val sourceStartReg = pairedRegisters.startregname()
|
||||
val targetStartReg = targetreg.startregname()
|
||||
@@ -4255,7 +4385,7 @@ $endLabel""")
|
||||
if(extendSignedBits)
|
||||
extendToMSBofVirtualReg(CpuRegister.A, reg, signed)
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
in CombinedLongRegisters -> {
|
||||
val reg = target.register.startregname()
|
||||
asmgen.out(" sta cx16.$reg")
|
||||
if(extendSignedBits) {
|
||||
@@ -4313,7 +4443,7 @@ $endLabel""")
|
||||
if(extendSignedBits)
|
||||
extendToMSBofVirtualReg(CpuRegister.X, reg, signed)
|
||||
}
|
||||
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
CpuRegister.Y -> when(target.register!!) {
|
||||
@@ -4367,7 +4497,7 @@ $endLabel""")
|
||||
if(extendSignedBits)
|
||||
extendToMSBofVirtualReg(CpuRegister.Y, reg, signed)
|
||||
}
|
||||
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
}
|
||||
@@ -4643,10 +4773,10 @@ $endLabel""")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val startreg = target.register!!.startregname()
|
||||
asmgen.out("""
|
||||
stz $startreg
|
||||
stz $startreg+1
|
||||
stz $startreg+2
|
||||
stz $startreg+3""")
|
||||
stz cx16.$startreg
|
||||
stz cx16.$startreg+1
|
||||
stz cx16.$startreg+2
|
||||
stz cx16.$startreg+3""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignLong(target.pointer!!, 0)
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
@@ -4689,7 +4819,7 @@ $endLabel""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val regstart = target.register!!.startregname()
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
@@ -5039,7 +5169,7 @@ $endLabel""")
|
||||
lda #0
|
||||
sta cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
in combinedLongRegisters -> TODO("assign memory byte into long ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("assign memory byte into long ${target.position}")
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignByteMemory(PtrTarget(target), address)
|
||||
@@ -5075,7 +5205,7 @@ $endLabel""")
|
||||
lda #0
|
||||
sta cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
in CombinedLongRegisters -> {
|
||||
val startreg = target.register.startregname()
|
||||
asmgen.out("""
|
||||
sta cx16.$startreg
|
||||
@@ -5189,6 +5319,23 @@ $endLabel""")
|
||||
asmgen.storeAIntoPointerVar(addressExpr)
|
||||
}
|
||||
addressExpr is PtBinaryExpression -> {
|
||||
val result = asmgen.pointerViaIndexRegisterPossible(addressExpr)
|
||||
if(result!=null) {
|
||||
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
|
||||
if(addressOfIdentifier!=null) {
|
||||
val varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
if(result.second is PtNumber) {
|
||||
val offset = (result.second as PtNumber).number.toInt()
|
||||
asmgen.out(" sta $varname+$offset")
|
||||
return
|
||||
} else if (result.second is PtIdentifier) {
|
||||
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
|
||||
asmgen.out(" ldx $offsetname | sta $varname,x")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
|
||||
storeViaExprEval()
|
||||
}
|
||||
@@ -5298,7 +5445,7 @@ $endLabel""")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
in combinedLongRegisters -> TODO("in place negate long invert ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("in place negate long invert ${target.position}")
|
||||
else -> throw AssemblyError("invalid reg dt for word invert")
|
||||
}
|
||||
}
|
||||
@@ -5427,7 +5574,7 @@ $endLabel""")
|
||||
tay""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
in combinedLongRegisters -> TODO("in place negate long reg ${target.position}")
|
||||
in CombinedLongRegisters -> TODO("in place negate long reg ${target.position}")
|
||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||
}
|
||||
}
|
||||
@@ -5457,7 +5604,13 @@ $endLabel""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO(" - long array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> TODO("32 bits register negate ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val regstart = target.register!!.startregname()
|
||||
asmgen.out("""
|
||||
lda #<cx16.$regstart
|
||||
ldy #>cx16.$regstart
|
||||
jsr prog8_lib.long_negate_inplace""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.inplaceLongNegate(PtrTarget(target), scope)
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
|
||||
@@ -556,7 +556,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
|
||||
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15_32, value.type.isSigned)
|
||||
if(value.type.isSigned) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
} else {
|
||||
if(value.type isAssignableTo DataType.LONG)
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
else
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, false)
|
||||
}
|
||||
inplacemodificationLongWithVariable(targetVar, operator, "cx16.r14")
|
||||
}
|
||||
|
||||
@@ -810,7 +817,45 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
ror $variable""")
|
||||
}
|
||||
}
|
||||
// TODO optimize for more cases 8, 16, 24 etc but don't forget to take the sign bit into account!
|
||||
value==8 -> {
|
||||
asmgen.out("""
|
||||
lda $variable+1
|
||||
sta $variable
|
||||
lda $variable+2
|
||||
sta $variable+1
|
||||
lda $variable+3
|
||||
sta $variable+2
|
||||
bpl +
|
||||
lda #255
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $variable+3""")
|
||||
}
|
||||
value==16-> {
|
||||
asmgen.out("""
|
||||
lda $variable+2
|
||||
sta $variable
|
||||
lda $variable+3
|
||||
sta $variable+1
|
||||
bpl +
|
||||
lda #255
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $variable+2
|
||||
sta $variable+3""")
|
||||
}
|
||||
value==24 -> {
|
||||
asmgen.out("""
|
||||
lda $variable+3
|
||||
sta $variable
|
||||
bpl +
|
||||
lda #255
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $variable+1
|
||||
sta $variable+2
|
||||
sta $variable+3""")
|
||||
}
|
||||
value <= 31 -> {
|
||||
asmgen.out("""
|
||||
ldy #$value
|
||||
|
||||
@@ -17,11 +17,9 @@ internal class PtrTarget(target: AsmAssignTarget) {
|
||||
}
|
||||
|
||||
internal class IndexedPtrTarget(target: AsmAssignTarget) {
|
||||
val dt = target.datatype // TODO unneeded?
|
||||
val pointer = target.array!!.pointerderef!!
|
||||
val index = target.array!!.index
|
||||
val elementDt = target.array!!.type
|
||||
val splitwords = target.array!!.splitWords // TODO unneeded?
|
||||
val scope = target.scope
|
||||
val position = target.position
|
||||
}
|
||||
@@ -41,7 +39,8 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignFAC1(target: PtrTarget) {
|
||||
TODO("assign FAC1 float to pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.storeIndirectFloatFP1(zpPtrVar, offset)
|
||||
}
|
||||
|
||||
internal fun assignFloatAY(target: PtrTarget) {
|
||||
@@ -102,11 +101,15 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignByteMemory(target: PtrTarget, address: UInt) {
|
||||
TODO("assign memory byte to pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out(" lda $address")
|
||||
asmgen.storeIndirectByteReg(CpuRegister.A, zpPtrVar, offset, false, false)
|
||||
}
|
||||
|
||||
internal fun assignByteMemory(target: PtrTarget, identifier: PtIdentifier) {
|
||||
TODO("assign memory byte to pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.loadByteFromPointerIntoA(identifier, tempZpPtrVar = "P8ZP_SCRATCH_W1")
|
||||
asmgen.storeIndirectByteReg(CpuRegister.A, zpPtrVar, offset, false, false)
|
||||
}
|
||||
|
||||
internal fun inplaceByteInvert(target: PtrTarget) {
|
||||
@@ -134,11 +137,10 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, forceTemporary: Boolean=false): Pair<String, UByte> {
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Pair<String, UByte> {
|
||||
// walk the pointer deref chain and leaves the final pointer value in a ZP var
|
||||
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
|
||||
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
|
||||
// TODO optimize 'forceTemporary' to only use a temporary when the offset is >0?
|
||||
|
||||
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
|
||||
if(fieldoffset==0u)
|
||||
@@ -172,7 +174,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
if(pointer.chain.isEmpty()) {
|
||||
require(pointer.derefLast)
|
||||
// just return the pointer itself
|
||||
if (!forceTemporary && allocator.isZpVar(pointer.startpointer.name))
|
||||
if (allocator.isZpVar(pointer.startpointer.name))
|
||||
return pointer.startpointer.name to 0u
|
||||
else {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
@@ -187,7 +189,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else {
|
||||
if (!forceTemporary && allocator.isZpVar(pointer.startpointer.name)) {
|
||||
if (allocator.isZpVar(pointer.startpointer.name)) {
|
||||
if(pointer.derefLast) {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
@@ -254,8 +256,37 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, target)
|
||||
}
|
||||
else if(value.type.isLong) {
|
||||
asmgen.loadIndirectLongIntoR14R15(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15_32, target)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
sta ${target.asmVarname}
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta ${target.asmVarname}+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta ${target.asmVarname}+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta ${target.asmVarname}+3
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register!! in CombinedLongRegisters)
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, target.register)
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
// TODO optimize this so it doesn't need a temporary long var all the time
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15, target)
|
||||
}
|
||||
else -> {
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird dt ${value.type} in pointer deref assignment ${target.position}")
|
||||
@@ -709,6 +740,17 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
internal fun readByteByAddressOfDereference(addressOfDereference: PtAddressOf, constOffset: Int): Boolean {
|
||||
require(addressOfDereference.dereference!=null)
|
||||
val (zpPtrVar, offset) = deref(addressOfDereference.dereference!!)
|
||||
val finalOffset = offset.toInt()+constOffset
|
||||
if(finalOffset<=255) {
|
||||
asmgen.loadIndirectByte(zpPtrVar, finalOffset.toUByte())
|
||||
return true
|
||||
}
|
||||
|
||||
TODO("read &dereference with word offset $finalOffset ${addressOfDereference.position}")
|
||||
}
|
||||
|
||||
|
||||
internal fun operatorDereference(binExpr: PtBinaryExpression): Triple<String, UByte, DataType> {
|
||||
@@ -806,41 +848,63 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
} else {
|
||||
// P8ZP_SCRATCH_PTR = value * scale
|
||||
if(value is PtBinaryExpression && value.operator=="+" && value.right is PtNumber) {
|
||||
// (x + y) * scale == (x * scale) + (y * scale)
|
||||
addUnsignedByteOrWordToAY(value.left, scale)
|
||||
addUnsignedByteOrWordToAY(value.right, scale)
|
||||
} else if(value is PtIdentifier) {
|
||||
// no need to save AY on the stack in this case we can slightly optimize it by storing them in a temp variable instead
|
||||
asmgen.out(" sta P8ZP_SCRATCH_PTR | sty P8ZP_SCRATCH_PTR+1")
|
||||
val mult = PtBinaryExpression("*", DataType.UWORD, value.position)
|
||||
mult.parent = value.parent
|
||||
mult.add(value)
|
||||
mult.add(PtNumber(BaseDataType.UWORD, scale.toDouble(), value.position))
|
||||
val multSrc = AsmAssignSource.fromAstSource(mult, asmgen.program, asmgen)
|
||||
val target = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, value.definingISub(), value.position, register = RegisterOrPair.AY)
|
||||
val assign= AsmAssignment(multSrc, listOf(target), asmgen.program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign, value.definingISub())
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc P8ZP_SCRATCH_PTR
|
||||
pha
|
||||
tya
|
||||
adc P8ZP_SCRATCH_PTR+1
|
||||
tay
|
||||
pla""")
|
||||
} else {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
val mult = PtBinaryExpression("*", DataType.UWORD, value.position)
|
||||
mult.parent = value.parent
|
||||
mult.add(value)
|
||||
mult.add(PtNumber(BaseDataType.UWORD, scale.toDouble(), value.position))
|
||||
val multSrc = AsmAssignSource.fromAstSource(mult, asmgen.program, asmgen)
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, value.definingISub(), value.position, variableAsmName = "P8ZP_SCRATCH_PTR")
|
||||
val assign= AsmAssignment(multSrc, listOf(target), asmgen.program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign, value.definingISub())
|
||||
restoreAYandAddAsmVariable("P8ZP_SCRATCH_PTR", false)
|
||||
when (value) {
|
||||
is PtBinaryExpression if value.operator == "+" && value.right is PtNumber -> {
|
||||
// (x + y) * scale == (x * scale) + (y * scale)
|
||||
addUnsignedByteOrWordToAY(value.left, scale)
|
||||
addUnsignedByteOrWordToAY(value.right, scale)
|
||||
}
|
||||
|
||||
is PtIdentifier -> {
|
||||
// no need to save AY on the stack in this case we can slightly optimize it by storing them in a temp variable instead
|
||||
asmgen.out(" sta P8ZP_SCRATCH_PTR | sty P8ZP_SCRATCH_PTR+1")
|
||||
val mult = PtBinaryExpression("*", DataType.UWORD, value.position)
|
||||
mult.parent = value.parent
|
||||
mult.add(value)
|
||||
mult.add(PtNumber(BaseDataType.UWORD, scale.toDouble(), value.position))
|
||||
val multSrc = AsmAssignSource.fromAstSource(mult, asmgen.program, asmgen)
|
||||
val target = AsmAssignTarget(
|
||||
TargetStorageKind.REGISTER,
|
||||
asmgen,
|
||||
DataType.UWORD,
|
||||
value.definingISub(),
|
||||
value.position,
|
||||
register = RegisterOrPair.AY
|
||||
)
|
||||
val assign = AsmAssignment(multSrc, listOf(target), asmgen.program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign, value.definingISub())
|
||||
asmgen.out(
|
||||
"""
|
||||
clc
|
||||
adc P8ZP_SCRATCH_PTR
|
||||
pha
|
||||
tya
|
||||
adc P8ZP_SCRATCH_PTR+1
|
||||
tay
|
||||
pla"""
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
val mult = PtBinaryExpression("*", DataType.UWORD, value.position)
|
||||
mult.parent = value.parent
|
||||
mult.add(value)
|
||||
mult.add(PtNumber(BaseDataType.UWORD, scale.toDouble(), value.position))
|
||||
val multSrc = AsmAssignSource.fromAstSource(mult, asmgen.program, asmgen)
|
||||
val target = AsmAssignTarget(
|
||||
TargetStorageKind.VARIABLE,
|
||||
asmgen,
|
||||
DataType.UWORD,
|
||||
value.definingISub(),
|
||||
value.position,
|
||||
variableAsmName = "P8ZP_SCRATCH_PTR"
|
||||
)
|
||||
val assign = AsmAssignment(multSrc, listOf(target), asmgen.program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign, value.definingISub())
|
||||
restoreAYandAddAsmVariable("P8ZP_SCRATCH_PTR", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -940,24 +1004,28 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(index, RegisterOrPair.AY)
|
||||
if(eltSize>1) {
|
||||
if(eltSize in powersOfTwoInt) {
|
||||
val shift = log2(eltSize.toDouble()).toInt()
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG")
|
||||
repeat(shift) {
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG")
|
||||
when (eltSize) {
|
||||
in powersOfTwoInt -> {
|
||||
val shift = log2(eltSize.toDouble()).toInt()
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG")
|
||||
repeat(shift) {
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG")
|
||||
}
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_REG")
|
||||
}
|
||||
in asmgen.optimizedWordMultiplications -> {
|
||||
asmgen.out(" jsr prog8_math.mul_word_${eltSize}")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda #<$eltSize
|
||||
ldy #>$eltSize
|
||||
jsr prog8_math.multiply_words"""
|
||||
)
|
||||
}
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_REG")
|
||||
} else if(eltSize in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" jsr prog8_math.mul_word_${eltSize}")
|
||||
} else {
|
||||
asmgen.out(
|
||||
"""
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda #<$eltSize
|
||||
ldy #>$eltSize
|
||||
jsr prog8_math.multiply_words"""
|
||||
)
|
||||
}
|
||||
}
|
||||
addArrayBaseAddressToOffsetInAY()
|
||||
@@ -1431,7 +1499,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15_32, true)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
clc
|
||||
@@ -1501,7 +1569,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15_32, true)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
@@ -2312,7 +2380,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isLong)
|
||||
// not an expression so no need to preserve R14/R15
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15_32, target.dt.isSigned)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15, target.dt.isSigned)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
@@ -2383,7 +2451,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isLong)
|
||||
// not an expression so no need to preserve R14/R15
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15_32, target.dt.isSigned)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15, target.dt.isSigned)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
@@ -2454,7 +2522,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isLong)
|
||||
// not an expression so no need to preserve R14/R15
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15_32, target.dt.isSigned)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.R14R15, target.dt.isSigned)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
@@ -2548,8 +2616,8 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
lda $regname+1
|
||||
pha""")
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
TODO("save on stack long register pair - do we really want to do this?")
|
||||
in CombinedLongRegisters -> {
|
||||
TODO("save on stack long register pair - do we really want to do this? please report this error")
|
||||
}
|
||||
else -> asmgen.saveRegisterStack(regs.asCpuRegister(), false)
|
||||
}
|
||||
@@ -2583,11 +2651,10 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
pla
|
||||
sta $regname""")
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
TODO("restore from stack long register - do we really want to do this?")
|
||||
in CombinedLongRegisters -> {
|
||||
TODO("restore from stack long register - do we really want to do this? please report this error")
|
||||
}
|
||||
else -> asmgen.restoreRegisterStack(regs.asCpuRegister(), false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,8 +66,8 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
@@ -9,7 +9,7 @@ import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
class ExperiCodeGen: ICodeGeneratorBackend {
|
||||
class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
@@ -19,7 +19,7 @@ class ExperiCodeGen: ICodeGeneratorBackend {
|
||||
|
||||
// you could write a code generator directly on the PtProgram AST,
|
||||
// but you can also use the Intermediate Representation to build a codegen on:
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irProgram = irCodeGen.generate()
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ 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 -> {
|
||||
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)
|
||||
|
||||
@@ -58,12 +58,44 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
"prog8_lib_structalloc" -> funcStructAlloc(call)
|
||||
"prog8_lib_copylong" -> funcCopyFromPointer1ToPointer2(call, IRDataType.LONG)
|
||||
"prog8_lib_copyfloat" -> funcCopyFromPointer1ToPointer2(call, IRDataType.FLOAT)
|
||||
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
|
||||
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
|
||||
"swap__byte" -> funcSwap(call)
|
||||
"swap__word" -> funcSwap(call)
|
||||
"swap__long" -> funcSwap(call)
|
||||
"swap__float" -> funcSwap(call)
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCopyFromPointer1ToPointer2(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val trSourceAddr = exprGen.translateExpression(call.args[0])
|
||||
val trTargetAddr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, trSourceAddr, trSourceAddr.resultReg, -1)
|
||||
addToResult(result, trTargetAddr, trTargetAddr.resultReg, -1)
|
||||
when(type) {
|
||||
IRDataType.LONG -> {
|
||||
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg)
|
||||
}
|
||||
}
|
||||
IRDataType.FLOAT -> {
|
||||
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, type, fpReg1=intermediateReg, reg1=trSourceAddr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREI, type, fpReg1=intermediateReg, reg1=trTargetAddr.resultReg)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid type $type")
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
@@ -87,8 +119,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
|
||||
return if(call.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else
|
||||
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.next(IRDataType.WORD), -1) // TODO actually the result is returned in CPU registers AY...
|
||||
else {
|
||||
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=resultvalueReg), null)
|
||||
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
@@ -428,6 +463,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcSwap(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
require(call.args[0].type == call.args[1].type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
val dt = irType(call.args[0].type)
|
||||
val t1 = exprGen.translateExpression(call.args[0])
|
||||
val t2 = exprGen.translateExpression(call.args[1])
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addToResult(result, t1, -1, t1.resultFpReg)
|
||||
addToResult(result, t2, -1, t2.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.SWAP, dt, fpReg1=t1.resultFpReg, fpReg2=t2.resultFpReg), null)
|
||||
result += assignFpRegisterTo(call.args[0], t1.resultFpReg)
|
||||
result += assignFpRegisterTo( call.args[1], t2.resultFpReg)
|
||||
} else {
|
||||
addToResult(result, t1, t1.resultReg, -1)
|
||||
addToResult(result, t2, t2.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.SWAP, dt, reg1=t1.resultReg, reg2=t2.resultReg), null)
|
||||
result += assignRegisterTo(call.args[0], t1.resultReg)
|
||||
result += assignRegisterTo( call.args[1], t2.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
@@ -445,6 +503,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
} else {
|
||||
if (call.args[0] is PtNumber) {
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
val tr = exprGen.translateExpression(call.args[1])
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
@@ -459,6 +518,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
@@ -555,6 +615,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
|
||||
}
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
pokeM(result, addressNum, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
@@ -564,6 +625,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
|
||||
}
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
pokeI(result, addressTr.resultReg, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -708,8 +770,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
if (msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
|
||||
}
|
||||
} else {
|
||||
@@ -718,8 +784,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
if (msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
|
||||
}
|
||||
}
|
||||
@@ -771,7 +841,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val offset = eltSize*constIndex + if(msb) 1 else 0
|
||||
val offset = eltSize*constIndex + if(msb) eltSize-1 else 0
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
|
||||
@@ -782,8 +852,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
|
||||
if(msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
|
||||
if(msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
|
||||
}
|
||||
}
|
||||
@@ -792,7 +866,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val offset = eltSize*constIndex + if(msb) 1 else 0
|
||||
val offset = eltSize*constIndex + if(msb) eltSize-1 else 0
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
|
||||
@@ -803,8 +877,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
|
||||
if(msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
|
||||
if(msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
|
||||
}
|
||||
}
|
||||
@@ -827,4 +905,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += codeGen.translateNode(assignment)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun assignFpRegisterTo(target: PtExpression, fpRegister: Int): IRCodeChunks {
|
||||
require(target.type.isFloat)
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(false, target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtIrRegister(fpRegister, DataType.FLOAT, target.position))
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += codeGen.translateNode(assignment)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = firstField.second.toInt())
|
||||
if (firstField.first.isPointer) {
|
||||
// get the address stored in the pointer and use that for the rest of the chain
|
||||
// LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
|
||||
// LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
|
||||
} else {
|
||||
require(chain.isEmpty())
|
||||
@@ -927,7 +927,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
require(parameter.register in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
|
||||
val assign = PtAssignment(fcall.position)
|
||||
val target = PtAssignTarget(true, fcall.position)
|
||||
val target = PtAssignTarget(false, fcall.position)
|
||||
target.add(PtIdentifier(regname, parameter.type, fcall.position))
|
||||
assign.add(target)
|
||||
assign.add(arg)
|
||||
@@ -948,7 +948,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name,
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegSpecs)), null)
|
||||
return if(fcall.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) // TODO datatype void?
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else if(returnRegSpecs.size==1) {
|
||||
val returnRegSpec = returnRegSpecs.single()
|
||||
if (fcall.type.isFloat)
|
||||
@@ -1841,7 +1841,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// get new pointer from field
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt())
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
|
||||
}
|
||||
}
|
||||
@@ -1854,7 +1854,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
require(fieldinfo.first.isPointer)
|
||||
// add the field offset
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
|
||||
return result to 0u
|
||||
} else {
|
||||
@@ -1863,7 +1863,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
if(targetPointerDeref.derefLast) {
|
||||
require(fieldinfo.first.isPointer)
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
|
||||
}
|
||||
return result to 0u
|
||||
|
||||
@@ -11,7 +11,8 @@ class IRCodeGen(
|
||||
internal val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter
|
||||
internal val errors: IErrorReporter,
|
||||
internal val retainSSA: Boolean
|
||||
) {
|
||||
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
@@ -47,10 +48,12 @@ class IRCodeGen(
|
||||
ensureFirstChunkLabels(irProg)
|
||||
irProg.linkChunks()
|
||||
irProg.convertAsmChunks()
|
||||
irProg.splitSSAchunks()
|
||||
|
||||
if(retainSSA)
|
||||
irProg.splitSSAchunks()
|
||||
|
||||
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
|
||||
val optimizer = IRPeepholeOptimizer(irProg)
|
||||
val optimizer = IRPeepholeOptimizer(irProg, retainSSA)
|
||||
optimizer.optimize(options.optimize, errors)
|
||||
irProg.validate()
|
||||
|
||||
@@ -1978,7 +1981,7 @@ class IRCodeGen(
|
||||
in Cx16VirtualRegisters -> {
|
||||
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
|
||||
}
|
||||
in combinedLongRegisters -> {
|
||||
in CombinedLongRegisters -> {
|
||||
require(paramDt==IRDataType.LONG)
|
||||
val startreg = registerOrFlag.registerOrPair!!.startregname()
|
||||
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}")
|
||||
|
||||
@@ -3,12 +3,12 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.intermediate.*
|
||||
|
||||
class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: Boolean) {
|
||||
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
|
||||
if(!optimizationsEnabled)
|
||||
return optimizeOnlyJoinChunks()
|
||||
return optimizeOnlyJoinChunks(retainSSA)
|
||||
|
||||
peepholeOptimize()
|
||||
peepholeOptimize(retainSSA)
|
||||
val remover = IRUnusedCodeRemover(irprog, errors)
|
||||
var totalRemovals = 0
|
||||
do {
|
||||
@@ -22,21 +22,21 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeOnlyJoinChunks() {
|
||||
private fun optimizeOnlyJoinChunks(retainSSA: Boolean) {
|
||||
// this chunk-joining is REQUIRED (optimization or no) to end up with a structurally sound chunk list
|
||||
irprog.foreachSub { sub ->
|
||||
joinChunks(sub)
|
||||
joinChunks(sub, retainSSA)
|
||||
removeEmptyChunks(sub)
|
||||
joinChunks(sub)
|
||||
joinChunks(sub, retainSSA)
|
||||
}
|
||||
irprog.linkChunks() // re-link
|
||||
}
|
||||
|
||||
private fun peepholeOptimize() {
|
||||
private fun peepholeOptimize(retainSSA: Boolean) {
|
||||
irprog.foreachSub { sub ->
|
||||
joinChunks(sub)
|
||||
joinChunks(sub, retainSSA)
|
||||
removeEmptyChunks(sub)
|
||||
joinChunks(sub)
|
||||
joinChunks(sub, retainSSA)
|
||||
|
||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||
// we don't optimize Inline Asm chunks here.
|
||||
@@ -55,7 +55,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||
|| simplifyConstantReturns(chunk1, indexedInstructions)
|
||||
|| removeNeedlessLoads(chunk1, indexedInstructions)
|
||||
|| useArrayIndexingInsteadOfAdds(chunk1, indexedInstructions)
|
||||
|| loadfieldsAndStorefields(chunk1, indexedInstructions)
|
||||
|| removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop
|
||||
} while (changed)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun joinChunks(sub: IRSubroutine) {
|
||||
private fun joinChunks(sub: IRSubroutine, retainSSA: Boolean) {
|
||||
// Subroutine contains a list of chunks. Some can be joined into one.
|
||||
|
||||
if(sub.chunks.isEmpty())
|
||||
@@ -163,10 +163,12 @@ 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 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 OpcodesThatEndSSAblock
|
||||
if(retainSSA) {
|
||||
// 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 OpcodesThatEndSSAblock
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -302,7 +304,7 @@ 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 OpcodesThatBranchUnconditionally) {
|
||||
if(previous.opcode in OpcodesThatBranchUnconditionally && idx<chunk.instructions.size) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
@@ -311,7 +313,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
// replace subsequent opcodes that jump by just the first
|
||||
if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) {
|
||||
val previous = chunk.instructions[idx-1]
|
||||
if(previous.opcode in OpcodesThatBranchUnconditionally) {
|
||||
if(previous.opcode in OpcodesThatBranchUnconditionally && idx<chunk.instructions.size) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
@@ -402,6 +404,43 @@ jump p8_label_gen_2
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
|
||||
if(!changed && idx < chunk.instructions.size-1) {
|
||||
val nextInstr = chunk.instructions[idx+1]
|
||||
if(nextInstr.reg1==ins.reg1) {
|
||||
when (nextInstr.opcode) {
|
||||
Opcode.INC -> {
|
||||
// INC after ADD or SUB
|
||||
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! + 1 else ins.immediate!! - 1
|
||||
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
|
||||
chunk.instructions.removeAt(idx + 1)
|
||||
changed = true
|
||||
}
|
||||
Opcode.DEC -> {
|
||||
// DEC after ADD or SUB
|
||||
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! - 1 else ins.immediate!! + 1
|
||||
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
|
||||
chunk.instructions.removeAt(idx + 1)
|
||||
changed = true
|
||||
}
|
||||
Opcode.ADD -> {
|
||||
// ADD after ADD or SUB
|
||||
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! + nextInstr.immediate!! else ins.immediate!! - nextInstr.immediate!!
|
||||
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
|
||||
chunk.instructions.removeAt(idx + 1)
|
||||
changed = true
|
||||
}
|
||||
Opcode.SUB -> {
|
||||
// SUB after ADD or SUB
|
||||
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! - nextInstr.immediate!! else ins.immediate!! + nextInstr.immediate!!
|
||||
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
|
||||
chunk.instructions.removeAt(idx + 1)
|
||||
changed = true
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode.AND -> {
|
||||
when (ins.immediate) {
|
||||
@@ -450,13 +489,13 @@ jump p8_label_gen_2
|
||||
// returns (index of load instruction, immediate value) or null.
|
||||
if(idx>=1) {
|
||||
val previous = indexedInstructions[idx-1].value
|
||||
if(previous.opcode==Opcode.LOAD && previous.reg1==reg)
|
||||
if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null)
|
||||
return idx-1 to previous.immediate!!
|
||||
}
|
||||
if(idx>=2) {
|
||||
val previous = indexedInstructions[idx-2].value
|
||||
if(previous.opcode==Opcode.LOAD && previous.reg1==reg)
|
||||
return idx-2 to previous.immediate!!
|
||||
if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null)
|
||||
return idx - 2 to previous.immediate!!
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -556,36 +595,18 @@ jump p8_label_gen_2
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun useArrayIndexingInsteadOfAdds(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
private fun loadfieldsAndStorefields(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.ADD && ins.immediate!=null && idx>0) {
|
||||
val load = indexedInstructions[idx-1].value
|
||||
if((load.opcode==Opcode.LOAD) && load.labelSymbol!=null) {
|
||||
val lastInstruction = indexedInstructions[idx+1].value
|
||||
if(lastInstruction.opcode==Opcode.LOADI) {
|
||||
val targetRegister = lastInstruction.reg1!!
|
||||
if(ins.reg1==lastInstruction.reg2!! && load.reg1==lastInstruction.reg2!!) {
|
||||
val loadm = IRInstruction(Opcode.LOADM, lastInstruction.type, reg1=targetRegister, labelSymbol = load.labelSymbol, symbolOffset = ins.immediate)
|
||||
chunk.instructions[idx-1] = loadm
|
||||
chunk.instructions.removeAt(idx+1)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
} else if(lastInstruction.opcode==Opcode.STOREI) {
|
||||
val targetRegister = lastInstruction.reg1!!
|
||||
if(ins.reg1==lastInstruction.reg2!! && load.reg1==lastInstruction.reg2!!) {
|
||||
val valueLoad = indexedInstructions[idx-2].value
|
||||
if(valueLoad.opcode==Opcode.LOAD && valueLoad.reg1==targetRegister) {
|
||||
val storem = IRInstruction(Opcode.STOREM, lastInstruction.type, reg1=valueLoad.reg1, labelSymbol = load.labelSymbol, symbolOffset = ins.immediate)
|
||||
chunk.instructions[idx-1] = storem
|
||||
chunk.instructions.removeAt(idx+1)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ins.opcode == Opcode.LOADFIELD && ins.immediate==0) {
|
||||
val loadi = IRInstruction(Opcode.LOADI, ins.type, ins.reg1!!, ins.reg2!!)
|
||||
chunk.instructions[idx] = loadi
|
||||
changed = true
|
||||
}
|
||||
else if (ins.opcode == Opcode.STOREFIELD && ins.immediate==0) {
|
||||
val loadi = IRInstruction(Opcode.STOREI, ins.type, ins.reg1!!, ins.reg2!!)
|
||||
chunk.instructions[idx] = loadi
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package prog8.codegen.vm
|
||||
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
import prog8.intermediate.IRProgram
|
||||
|
||||
class VmCodeGen: ICodeGeneratorBackend {
|
||||
class VmCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram {
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irProgram = irCodeGen.generate()
|
||||
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
@@ -64,7 +64,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, position: Position) {
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.NOP)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 3
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
irProg.chunks().single().instructions.size shouldBe 1
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
|
||||
irProg.chunks().size shouldBe 4
|
||||
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val chunks = irProg.chunks()
|
||||
chunks.size shouldBe 4
|
||||
@@ -102,7 +102,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.CLI),
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 2
|
||||
@@ -126,7 +126,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.SEI),
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 2
|
||||
@@ -142,7 +142,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
@@ -165,7 +165,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 10
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
}
|
||||
@@ -176,7 +176,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 2
|
||||
@@ -196,7 +196,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 8
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
}
|
||||
@@ -211,7 +211,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=42, immediate = -1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 6
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 6
|
||||
|
||||
@@ -44,7 +44,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// xx += cx16.r0
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -163,7 +163,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// nop
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -235,7 +235,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// nop
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -303,7 +303,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// goto $c000
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -341,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 2
|
||||
irChunks.size shouldBe 1
|
||||
}
|
||||
|
||||
test("integer comparison expressions against zero") {
|
||||
@@ -359,7 +359,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// nop
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -431,7 +431,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// nop
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -499,7 +499,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// goto $c000
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
@@ -537,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 2
|
||||
irChunks.size shouldBe 1
|
||||
}
|
||||
|
||||
test("extsub allowed in ir-codegen") {
|
||||
@@ -548,7 +548,7 @@ class TestVmCodeGen: FunSpec({
|
||||
// routine()
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
|
||||
@@ -11,6 +11,7 @@ import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.isInteger
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
@@ -109,35 +110,55 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
|
||||
}
|
||||
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
|
||||
// mutiply a string.
|
||||
val part = expr.left as StringLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting string has length zero", part.position)
|
||||
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
|
||||
else if (expr.operator=="*" && expr.left is StringLiteral) {
|
||||
if (rightconst != null) {
|
||||
// mutiply a string.
|
||||
val part = expr.left as StringLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting string has length zero", part.position)
|
||||
if(rightconst.number>255)
|
||||
errors.err("string length must be 0-255", rightconst.position)
|
||||
else if(!rightconst.type.isInteger)
|
||||
errors.err("can only multiply string by integer constant", rightconst.position)
|
||||
else {
|
||||
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
|
||||
}
|
||||
}
|
||||
// other errors are reported later
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.left.inferType(program).isArray) {
|
||||
if (expr.operator=="*" && rightconst!=null) {
|
||||
if (expr.left is ArrayLiteral) {
|
||||
// concatenate array literal.
|
||||
val part = expr.left as ArrayLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting array has length zero", part.position)
|
||||
val tmp = mutableListOf<Expression>()
|
||||
repeat(rightconst.number.toInt()) {
|
||||
part.value.forEach { tmp += it.copy() }
|
||||
if (expr.operator=="*") {
|
||||
if (rightconst != null) {
|
||||
if (expr.left is ArrayLiteral) {
|
||||
// concatenate array literal.
|
||||
val part = expr.left as ArrayLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting array has length zero", part.position)
|
||||
if(rightconst.number.toInt() !in 1..(255/part.value.size))
|
||||
errors.err("array length must be 1-255", rightconst.position)
|
||||
else if(!rightconst.type.isInteger)
|
||||
errors.err("can only multiply array by integer constant", rightconst.position)
|
||||
else {
|
||||
val tmp = mutableListOf<Expression>()
|
||||
repeat(rightconst.number.toInt()) {
|
||||
part.value.forEach { tmp += it.copy() }
|
||||
}
|
||||
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
|
||||
}
|
||||
} else {
|
||||
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
|
||||
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
|
||||
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
|
||||
}
|
||||
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
|
||||
}
|
||||
else {
|
||||
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
|
||||
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
|
||||
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
|
||||
} else {
|
||||
errors.err("can only multiply an array by an integer value", expr.right.position)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +383,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
|
||||
else {
|
||||
val const2 = evaluator.evaluate(functionCallExpr, program)
|
||||
return if(const2!=null)
|
||||
if(const2!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, const2, parent))
|
||||
else
|
||||
noModifications
|
||||
@@ -541,7 +562,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
|
||||
@@ -39,7 +39,7 @@ class VarConstantValueTypeAdjuster(
|
||||
|
||||
// replace variables by constants, if possible
|
||||
if(options.optimize) {
|
||||
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE || !decl.datatype.isNumeric)
|
||||
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE || !decl.datatype.isNumericOrBool)
|
||||
return noModifications
|
||||
if (decl.value != null && decl.value!!.constValue(program) == null)
|
||||
return noModifications
|
||||
@@ -203,7 +203,7 @@ class VarConstantValueTypeAdjuster(
|
||||
else -> throw FatalAstException("min/max type")
|
||||
}
|
||||
} else if(t1.isNumeric && t2.isNumeric) {
|
||||
errors.err("min/max not supported for floats", functionCallExpr.position)
|
||||
errors.err("min/max not supported for floats, use floats.minf/maxf instead", functionCallExpr.position)
|
||||
return noModifications
|
||||
} else {
|
||||
errors.err("expected numeric arguments", functionCallExpr.args[0].position)
|
||||
@@ -299,6 +299,25 @@ class VarConstantValueTypeAdjuster(
|
||||
functionCallStatement))
|
||||
}
|
||||
}
|
||||
else if(func==listOf("swap")) {
|
||||
val t1 = functionCallStatement.args[0].inferType(program)
|
||||
if(t1.isKnown) {
|
||||
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
|
||||
val replaceFunc = when {
|
||||
dt.isByteOrBool -> "swap__byte"
|
||||
dt.isWord || dt.isPointer -> "swap__word"
|
||||
dt.isLong -> "swap__long"
|
||||
dt.isFloat -> "swap__float"
|
||||
else -> {
|
||||
errors.err("expected numeric arguments", functionCallStatement.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
|
||||
functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,6 +329,134 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator=="<<") {
|
||||
val amount = expr.right.constValue(program)?.number
|
||||
val idt = expr.left.inferType(program)
|
||||
if(amount==8.0 && idt.isWords) {
|
||||
// msb(word<<8) --> lsb(word)
|
||||
val msb = expr.parent as? IFunctionCall
|
||||
if(msb?.target?.nameInSource == listOf("msb"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
}
|
||||
|
||||
if(idt.isLong) {
|
||||
when(amount) {
|
||||
8.0 -> {
|
||||
// msb(long<<8) --> lsb(msw(long))
|
||||
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
|
||||
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
16.0 -> {
|
||||
// msw(long<<16) --> lsw(long)
|
||||
val msw = expr.parent as? IFunctionCall
|
||||
if(msw?.target?.nameInSource == listOf("msw"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
// msb(long<<16) --> msb(lsw(lv))
|
||||
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
|
||||
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
24.0 -> {
|
||||
// msb(long<<24) --> lsb__long(long)
|
||||
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(msb?.singleOrNull() in arrayOf("msb__long", "msb"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("lsb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator==">>") {
|
||||
val amount = expr.right.constValue(program)?.number
|
||||
val idt = expr.left.inferType(program)
|
||||
if(amount==8.0 && idt.isWords) {
|
||||
// lsb(word>>8) --> msb(word)
|
||||
// word>>8 as ubyte --> msb(word)
|
||||
val lsb = expr.parent as? IFunctionCall
|
||||
if(lsb?.target?.nameInSource == listOf("lsb"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isByte==true)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
}
|
||||
else if (idt.isLong) {
|
||||
when(amount) {
|
||||
8.0 -> {
|
||||
val lsb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb")) {
|
||||
// lsb(long>>8) --> msb(lsw(long))
|
||||
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isByte==true) {
|
||||
// long>>8 as ubyte --> msb(lsw(long))
|
||||
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
16.0 -> {
|
||||
// lsw(long>>16) --> msw(long)
|
||||
// long>>16 as uword --> msw(long)
|
||||
val lsw = expr.parent as? IFunctionCall
|
||||
if(lsw?.target?.nameInSource == listOf("lsw"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isWord==true)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
val lsb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb") || castbyte?.type?.isByte==true) {
|
||||
// lsb(long>>16) --> lsb(msw(long)
|
||||
// long>>16 as ubyte --> msb(long)
|
||||
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
24.0 -> {
|
||||
val lsb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb")) {
|
||||
// lsb(long>>24) --> msb(long)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isByte==true) {
|
||||
// long>>24 as ubyte --> msb(long)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
|
||||
val newExpr2 = when (expr.operator) {
|
||||
@@ -615,6 +743,15 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
|
||||
}
|
||||
} else if(arg is FunctionCallExpression && arg.target.nameInSource == listOf("msw")) {
|
||||
// lsb(msw(longvar)) --> @(&longvar+2) ; get the bank byte from a long variable
|
||||
val longvar = arg.args[0] as? IdentifierReference
|
||||
if(longvar!=null && longvar.inferType(program).isLong) {
|
||||
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
|
||||
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 2.0, functionCallExpr.position), functionCallExpr.position)
|
||||
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
|
||||
}
|
||||
} else {
|
||||
if(arg is IdentifierReference && arg.nameInSource.size==2
|
||||
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
|
||||
@@ -647,6 +784,15 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
NumericLiteral(valueDt.getOr(DataType.UBYTE).base, 0.0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else if(arg is FunctionCallExpression && arg.target.nameInSource == listOf("lsw")) {
|
||||
// msb(lsw(longvar)) --> @(&longvar+1) ; get the second byte from a long variable
|
||||
val longvar = arg.args[0] as? IdentifierReference
|
||||
if(longvar!=null && longvar.inferType(program).isLong) {
|
||||
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
|
||||
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 1.0, functionCallExpr.position), functionCallExpr.position)
|
||||
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
|
||||
}
|
||||
} else {
|
||||
if(arg is IdentifierReference && arg.nameInSource.size==2
|
||||
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
|
||||
|
||||
@@ -22,6 +22,45 @@ class StatementOptimizer(private val program: Program,
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
when (functionName) {
|
||||
"poke" -> {
|
||||
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
|
||||
if(addrOfIdentifier!=null && addrOfIdentifier.inferType(program).isBytes) {
|
||||
// poke(&bytevar, x) --> bytevar=x
|
||||
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
|
||||
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
|
||||
}
|
||||
}
|
||||
"pokew" -> {
|
||||
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
|
||||
if(addrOfIdentifier!=null && (addrOfIdentifier.inferType(program).isWords || addrOfIdentifier.inferType(program).isPointer)) {
|
||||
// pokew(&wordvar, x) --> wordvar=x
|
||||
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
|
||||
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
|
||||
}
|
||||
}
|
||||
"pokel" -> {
|
||||
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
|
||||
if(addrOfIdentifier!=null && addrOfIdentifier.inferType(program).isLong) {
|
||||
// pokel(&longvar, x) --> longvar=x
|
||||
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
|
||||
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
|
||||
}
|
||||
}
|
||||
"pokef" -> {
|
||||
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
|
||||
if(addrOfIdentifier!=null && addrOfIdentifier.inferType(program).isFloat) {
|
||||
// pokef(&floatvar, x) --> floatvar=x
|
||||
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
|
||||
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
@@ -300,6 +339,17 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
}
|
||||
|
||||
val memwriteAddr = assignment.target.memoryAddress
|
||||
if(memwriteAddr!=null) {
|
||||
val addrOfIdentifier = (memwriteAddr.addressExpression as? AddressOf)?.identifier
|
||||
if(addrOfIdentifier!=null && (addrOfIdentifier.inferType(program).isBytes || addrOfIdentifier.inferType(program).isBool)) {
|
||||
// @(&bytevar) = x --> bytevar = x
|
||||
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = assignment.position)
|
||||
val assign = Assignment(target, assignment.value, AssignmentOrigin.OPTIMIZER, assignment.position)
|
||||
return listOf(IAstModification.ReplaceNode(assignment, assign, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ class UnusedCodeRemover(private val program: Program,
|
||||
val pop = subroutines.single { s -> s.name == "pop" }
|
||||
neverRemoveSubroutines.add(push)
|
||||
neverRemoveSubroutines.add(pop)
|
||||
|
||||
subroutines
|
||||
.filter { s -> s.name == "internal_long_R1_to_float_AY" || s.name=="internal_long_AY_to_FAC" }
|
||||
.forEach { sub -> neverRemoveSubroutines.add(sub) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
asmsub SETTIML(long jiffies @R0R1) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
@@ -143,7 +143,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1 {
|
||||
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM
|
||||
@@ -338,6 +338,8 @@ c128 {
|
||||
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
|
||||
&ubyte VM3 = $0A2E ; starting page for VDC screen mem
|
||||
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
|
||||
&ubyte MMUCR = $ff00 ; MMU control register
|
||||
&ubyte SCREEN_MODE = $d7 ; 40 or 80 column mode active?
|
||||
|
||||
|
||||
extsub $FF47 = SPIN_SPOUT() clobbers(A) ; set up serial bus for fast communications mode
|
||||
@@ -348,7 +350,7 @@ extsub $FF53 = BOOT_CALL(ubyte device @X, ubyte drive @A) clobbers(A,X,Y) ; try
|
||||
extsub $FF56 = PHOENIX() clobbers(A,X,Y) ; search for and autostart ROMs, cartridges, then default disk
|
||||
extsub $FF59 = LKUPLA(ubyte lfn @A) -> bool @Pc, ubyte @X ; look up logical file number to see if it's open; returns device
|
||||
extsub $FF5C = LKUPSA(ubyte sa @Y) -> bool @Pc, ubyte @A, ubyte @X ; look up secondary address to see if it's in use; returns lfn and device
|
||||
extsub $FF5F = SWAPPER() clobbers(A,X,Y) ; swap active screen (between 40- and 80-column)
|
||||
extsub $FF5F = SWAPPER() clobbers(A,X,Y) ; swap active screen (between 40- and 80-column) (see set40/set80 if you want to do it idempotently)
|
||||
extsub $FF62 = DLCHR() clobbers(A,X,Y) ; copy character ROM into VDC video RAM
|
||||
extsub $FF65 = PFKEY(ubyte zpaddr @A, ubyte key @X, ubyte length @Y) ; redefine programmable function key (string descriptor in zp, addr in A)
|
||||
extsub $FF68 = SETBNK(ubyte data_bank @A, ubyte filename_bank @X) ; set memory bank for load/save
|
||||
@@ -366,14 +368,14 @@ extsub $FF7D = PRIMM() ; print immediat
|
||||
inline asmsub banks(ubyte banks @A) {
|
||||
; -- set the memory bank configuration MMU register
|
||||
%asm {{
|
||||
sta $FF00
|
||||
sta MMUCR
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getbanks() -> ubyte @A {
|
||||
; -- get the current memory bank configuration from the MMU register
|
||||
%asm {{
|
||||
lda $FF00
|
||||
lda MMUCR
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -393,11 +395,28 @@ asmsub disable_basic() clobbers(A) {
|
||||
sta $033c
|
||||
|
||||
lda #$0e ; bank out BASIC ROM
|
||||
sta $ff00
|
||||
sta MMUCR
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_fkey_macros() {
|
||||
%asm {{
|
||||
lda #183
|
||||
sta 828
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_fkey_macros() {
|
||||
%asm {{
|
||||
lda #173
|
||||
sta 828
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub x16jsrfar() {
|
||||
%asm {{
|
||||
; setup a JSRFAR call (using X16 call convention)
|
||||
@@ -448,10 +467,52 @@ asmsub x16jsrfar() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub fast() {
|
||||
; -- enable 2 MHz cpu mode, blanks VIC screen
|
||||
%asm {{
|
||||
lda #10
|
||||
sta $d011 ; disable VIC-II display
|
||||
lda $d030
|
||||
ora #$01 ; Set FAST bit
|
||||
sta $d030
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub slow() {
|
||||
; -- disable 2 MHz cpu mode, makes VIC screen visible
|
||||
%asm {{
|
||||
lda $d030
|
||||
and #$fe ; clear FAST bit
|
||||
sta $d030
|
||||
lda #27
|
||||
sta $d011 ; enable VIC-II display
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub is80() -> bool {
|
||||
; -- return true if 80 column mode is active, false if 40 column mode is active
|
||||
return SCREEN_MODE & $80 == $80
|
||||
}
|
||||
|
||||
sub set40() {
|
||||
; -- set 40 column mode.
|
||||
if is80() SWAPPER()
|
||||
}
|
||||
|
||||
sub set80() {
|
||||
; -- set 80 column mode.
|
||||
if not is80() SWAPPER()
|
||||
}
|
||||
|
||||
|
||||
; ---- end of C128 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
%import shared_sys_functions
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@@ -484,48 +545,6 @@ sys {
|
||||
p8_sys_startup.enable_runstop_and_charsetswitch()
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
.section BSS
|
||||
save_SCRATCH_ZPB1 .byte ?
|
||||
save_SCRATCH_ZPREG .byte ?
|
||||
save_SCRATCH_ZPWORD1 .word ?
|
||||
save_SCRATCH_ZPWORD2 .word ?
|
||||
.send BSS
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
@@ -673,7 +692,7 @@ user_vector .word ?
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
sta $ff00 ; default bank 15
|
||||
sta c128.MMUCR ; default bank 15
|
||||
jmp (cbm.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
@@ -747,196 +766,6 @@ _larger
|
||||
}
|
||||
|
||||
|
||||
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 {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
dey
|
||||
beq +
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
+ lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
@@ -988,20 +817,6 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -1056,7 +871,7 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
inline asmsub pushl(long value @R0R1) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
@@ -1069,7 +884,7 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
inline asmsub popl() -> long @R0R1 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
@@ -1299,12 +1114,12 @@ asmsub init_system() {
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
sta $ff00 ; select default bank 15
|
||||
sta c128.MMUCR ; select default bank 15
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #%00001110
|
||||
sta $ff00 ; bank out basic rom so we have ram from $1c00-$bfff
|
||||
sta c128.MMUCR ; bank out basic rom so we have ram from $1c00-$bfff
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
@@ -1312,6 +1127,7 @@ asmsub init_system() {
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
jsr c128.disable_fkey_macros
|
||||
; basic is not banked in, adjust MEMTOP
|
||||
ldx #<$c000
|
||||
ldy #>$c000
|
||||
@@ -1335,13 +1151,14 @@ asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #0
|
||||
sta $ff00 ; default bank 15
|
||||
sta c128.MMUCR ; default bank 15
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr enable_runstop_and_charsetswitch
|
||||
ldx #<$ff00
|
||||
ldy #>$ff00
|
||||
ldx #<c128.MMUCR
|
||||
ldy #>c128.MMUCR
|
||||
clc
|
||||
jsr cbm.MEMTOP ; adjust MEMTOP to original value again
|
||||
jsr c128.enable_fkey_macros
|
||||
lda _exitcarry
|
||||
lsr a
|
||||
lda _exitcode
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
; NOTE: most of the routines here do not work on the 80 column VDC text screen, but only on the 40 column VIC-II text screen.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -33,7 +34,6 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
@@ -75,7 +75,7 @@ asmsub get_cursor() -> ubyte @X, ubyte @Y {
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; ---- fill the VIC-II character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
|
||||
%asm {{
|
||||
@@ -90,7 +90,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
}
|
||||
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; ---- clear the VIC-II character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
@@ -105,7 +105,7 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
}
|
||||
|
||||
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; ---- clear the character screen colors with the given color (leaves characters).
|
||||
; ---- clear the VIC-II character screen colors with the given color (leaves characters).
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
@@ -134,7 +134,7 @@ sub uppercase() {
|
||||
}
|
||||
|
||||
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, X, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; ---- scroll the whole VIC-II text screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
|
||||
@@ -173,7 +173,7 @@ _scroll_screen ; scroll only the screen memory
|
||||
}
|
||||
|
||||
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; ---- scroll the whole VIC-II text screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
@@ -207,7 +207,7 @@ _scroll_screen ; scroll only the screen memory
|
||||
}
|
||||
|
||||
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; ---- scroll the whole VIC-II text screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
@@ -241,7 +241,7 @@ _scroll_screen ; scroll only the screen memory
|
||||
}
|
||||
|
||||
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; ---- scroll the whole VIC-II text screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
@@ -275,22 +275,19 @@ _scroll_screen ; scroll only the screen memory
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
; ---- sets the character in the VIC-II screen matrix at the given position
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
clc
|
||||
adc _screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ pla
|
||||
ldy #0
|
||||
tay
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
|
||||
@@ -300,43 +297,37 @@ _screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
}
|
||||
|
||||
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the character in the screen matrix at the given location
|
||||
; ---- get the character in the VIC-II screen matrix at the given location
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda setchr._screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
clc
|
||||
adc setchr._screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy #0
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
|
||||
; ---- set the color in A on the screen matrix at the given position
|
||||
; ---- set the color in A on the VIC-II screen matrix at the given position
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _colorrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
clc
|
||||
adc _colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ pla
|
||||
ldy #0
|
||||
tay
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
|
||||
@@ -346,48 +337,42 @@ _colorrows .word $d800 + range(0, 1000, 40)
|
||||
}
|
||||
|
||||
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the color in the screen color matrix at the given location
|
||||
; ---- get the color in the VIC-II screen color matrix at the given location
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setclr._colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda setclr._colorrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
clc
|
||||
adc setclr._colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy #0
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
and #15
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
; ---- set char+color at the given position on the VIC-II screen
|
||||
%asm {{
|
||||
_charptr = P8ZP_SCRATCH_W1
|
||||
_colorptr = P8ZP_SCRATCH_W2
|
||||
lda row
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _charptr+1
|
||||
adc #$d4
|
||||
sta _colorptr+1
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc col
|
||||
sta _charptr
|
||||
sta _colorptr
|
||||
bcc +
|
||||
inc _charptr+1
|
||||
inc _colorptr+1
|
||||
+ lda character
|
||||
ldy #0
|
||||
lda setchr._screenrows+1,y
|
||||
sta _charptr+1
|
||||
clc
|
||||
adc #$d4
|
||||
sta _colorptr+1
|
||||
lda character
|
||||
ldy col
|
||||
sta (_charptr),y
|
||||
lda charcolor
|
||||
sta (_colorptr),y
|
||||
|
||||
@@ -127,44 +127,9 @@ cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||
.pend
|
||||
|
||||
|
||||
cast_from_long .proc
|
||||
; convert long pointed to by AY into a float pointed to by R0
|
||||
; a bit slow algorithm implemented below: (msw(l) as word) as float * 65536.0 + (lsw(l) as float)
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #3
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ZP_SCRATCH_REG
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jsr GIVAYFAY
|
||||
lda #<FL_65536_const
|
||||
ldy #>FL_65536_const
|
||||
jsr CONUPK
|
||||
jsr FMULTT
|
||||
jsr pushFAC1
|
||||
ldy #1
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ZP_SCRATCH_REG
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jsr GIVUAYFAY
|
||||
jsr MOVEF
|
||||
clc
|
||||
jsr popFAC
|
||||
jsr FADDT
|
||||
ldx cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jmp MOVMF
|
||||
|
||||
FL_65536_const .byte $91, $00, $00, $00, $00 ; 65536.0
|
||||
; !notreached!
|
||||
.pend
|
||||
|
||||
cast_as_long .proc
|
||||
; convert float pointed to by R0 into a long pointed to by AY
|
||||
; the reverse routine that converts a long to a float is elsewhere as internal_long_to_float()
|
||||
|
||||
FACHO = FAC_ADDR + 1
|
||||
|
||||
@@ -555,3 +520,22 @@ popFAC .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
swap_floats .proc
|
||||
; -- swap float values pointed to by P8ZP_SCRATCH_W1 and AY.
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta floats_temp_var,y
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
bpl -
|
||||
ldy #4
|
||||
- lda floats_temp_var,y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
@@ -168,6 +168,7 @@ asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y)
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
; could use signed GIVAYFAY and add 65536 if negative, but there's a custom routine in rom here
|
||||
%asm {{
|
||||
sty $62 ; facmo
|
||||
sta $63
|
||||
|
||||
@@ -132,7 +132,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
asmsub SETTIML(long jiffies @R0R1) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
@@ -142,7 +142,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1 {
|
||||
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM
|
||||
@@ -458,6 +458,36 @@ _jmpfar_vec .word ?
|
||||
}
|
||||
}
|
||||
|
||||
c128 {
|
||||
asmsub fast() {
|
||||
; -- if in C64 mode on a C128: enable 2 MHz cpu mode. blanks VIC screen.
|
||||
; On a real C64, only blacks the screen.
|
||||
%asm {{
|
||||
lda #10
|
||||
sta $d011 ; disable VIC-II display
|
||||
lda $d030
|
||||
ora #$01 ; Set FAST bit
|
||||
sta $d030
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub slow() {
|
||||
; -- if in C64 mode on a C128: disable 2 MHz cpu mode, makes VIC screen visible
|
||||
; On a real C64, only restores the screen.
|
||||
%asm {{
|
||||
lda $d030
|
||||
and #$fe ; clear FAST bit
|
||||
sta $d030
|
||||
lda #27
|
||||
sta $d011 ; enable VIC-II display
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
%import shared_sys_functions
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@@ -490,48 +520,6 @@ sys {
|
||||
p8_sys_startup.enable_runstop_and_charsetswitch()
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
.section BSS
|
||||
save_SCRATCH_ZPB1 .byte ?
|
||||
save_SCRATCH_ZPREG .byte ?
|
||||
save_SCRATCH_ZPWORD1 .word ?
|
||||
save_SCRATCH_ZPWORD2 .word ?
|
||||
.send BSS
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
@@ -756,196 +744,6 @@ _larger
|
||||
}
|
||||
|
||||
|
||||
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 {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
dey
|
||||
beq +
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
+ lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
@@ -997,20 +795,6 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -1065,7 +849,7 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
inline asmsub pushl(long value @R0R1) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
@@ -1078,7 +862,7 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
inline asmsub popl() -> long @R0R1 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
|
||||
@@ -39,7 +39,6 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
@@ -285,16 +284,13 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
clc
|
||||
adc _screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ pla
|
||||
ldy #0
|
||||
tay
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
|
||||
@@ -310,15 +306,12 @@ asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda setchr._screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
clc
|
||||
adc setchr._screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy #0
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
}}
|
||||
@@ -331,16 +324,13 @@ asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _colorrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
clc
|
||||
adc _colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ pla
|
||||
ldy #0
|
||||
tay
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
|
||||
@@ -356,16 +346,14 @@ asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setclr._colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda setclr._colorrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
clc
|
||||
adc setclr._colorrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy #0
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
and #15
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -378,20 +366,16 @@ _colorptr = P8ZP_SCRATCH_W2
|
||||
lda row
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _charptr+1
|
||||
adc #$d4
|
||||
sta _colorptr+1
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc col
|
||||
sta _charptr
|
||||
sta _colorptr
|
||||
bcc +
|
||||
inc _charptr+1
|
||||
inc _colorptr+1
|
||||
+ lda character
|
||||
ldy #0
|
||||
lda setchr._screenrows+1,y
|
||||
sta _charptr+1
|
||||
clc
|
||||
adc #$d4
|
||||
sta _colorptr+1
|
||||
lda character
|
||||
ldy col
|
||||
sta (_charptr),y
|
||||
lda charcolor
|
||||
sta (_colorptr),y
|
||||
|
||||
@@ -572,7 +572,7 @@ _try_iso
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub hex2long(str string @AY) -> long @R0R1_32 {
|
||||
asmsub hex2long(str string @AY) -> long @R0R1 {
|
||||
; -- hexadecimal string (with or without '$') to long.
|
||||
; string may be in petscii or c64-screencode encoding.
|
||||
; stops parsing at the first character that's not a hex digit (except leading $)
|
||||
|
||||
@@ -176,7 +176,7 @@ io_error:
|
||||
|
||||
sub list_filenames(str pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against (case-sensitive).
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
|
||||
@@ -187,6 +187,7 @@ io_error:
|
||||
uword buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
filenames_buffer[0]=0
|
||||
|
||||
if lf_start_list(pattern_ptr) {
|
||||
while lf_next_entry() {
|
||||
if list_filetype!="dir" {
|
||||
@@ -200,8 +201,45 @@ io_error:
|
||||
}
|
||||
}
|
||||
}
|
||||
lf_end_list()
|
||||
}
|
||||
|
||||
lf_end_list()
|
||||
@(filenames_buffer)=0
|
||||
sys.clear_carry()
|
||||
return files_found
|
||||
}
|
||||
|
||||
sub list_filenames_nocase(str lowercase_pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against (case-insensitive, ISO-encoding).
|
||||
; Pattern needs to be in lowercase already.
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
; Note that no list of pointers of some form is returned, the names are just squashed together.
|
||||
; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names
|
||||
; and registering when the next one starts after the 0-byte separator.
|
||||
uword buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
filenames_buffer[0]=0
|
||||
|
||||
if lf_start_list(lowercase_pattern_ptr) {
|
||||
while lf_next_entry_nocase() {
|
||||
if list_filetype!="dir" {
|
||||
filenames_buffer += strings.copy(list_filename, filenames_buffer) + 1
|
||||
files_found++
|
||||
if filenames_buffer - buffer_start > filenames_buf_size-20 {
|
||||
@(filenames_buffer)=0
|
||||
lf_end_list()
|
||||
sys.set_carry()
|
||||
return files_found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lf_end_list()
|
||||
@(filenames_buffer)=0
|
||||
sys.clear_carry()
|
||||
return files_found
|
||||
@@ -257,73 +295,117 @@ io_error:
|
||||
goto diskio.lf_start_list.start_list_internal
|
||||
}
|
||||
|
||||
sub lf_start_list_having_prefix(str prefix) -> bool {
|
||||
; -- start an iterative directory contents listing for entries with given prefix.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
list_filename[0] = '$'
|
||||
list_filename[1] = ':'
|
||||
cx16.r0L = strings.copy(prefix, &list_filename+2)
|
||||
list_filename[cx16.r0L+2] = '*'
|
||||
list_filename[cx16.r0L+3] = 0
|
||||
cbm.SETNAM(cx16.r0L+3, list_filename)
|
||||
diskio.lf_start_list.pattern_ptr = 0
|
||||
goto diskio.lf_start_list.start_list_internal
|
||||
}
|
||||
|
||||
sub lf_next_entry() -> bool {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
; If a match pattern was specified, it will be matched CASE SENSITIVELY against the filename.
|
||||
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
|
||||
|
||||
if not iteration_in_progress
|
||||
return false
|
||||
|
||||
repeat {
|
||||
reset_read_channel() ; use the input io channel again
|
||||
|
||||
^^ubyte nameptr = &list_filename
|
||||
ubyte blocks_lsb = cbm.CHRIN()
|
||||
ubyte blocks_msb = cbm.CHRIN()
|
||||
|
||||
if cbm.READST()!=0
|
||||
goto close_end
|
||||
|
||||
list_blocks = mkword(blocks_msb, blocks_lsb)
|
||||
|
||||
; read until the filename starts after the first "
|
||||
while cbm.CHRIN()!='\"' {
|
||||
if cbm.READST()!=0
|
||||
goto close_end
|
||||
}
|
||||
|
||||
; read the filename
|
||||
repeat {
|
||||
ubyte character = cbm.CHRIN()
|
||||
if character==0
|
||||
break
|
||||
if character=='\"'
|
||||
break
|
||||
@(nameptr) = character
|
||||
nameptr++
|
||||
}
|
||||
|
||||
@(nameptr) = 0
|
||||
|
||||
do {
|
||||
cx16.r15L = cbm.CHRIN()
|
||||
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
|
||||
list_filetype[0] = cx16.r15L
|
||||
list_filetype[1] = cbm.CHRIN()
|
||||
list_filetype[2] = cbm.CHRIN()
|
||||
while cbm.CHRIN()!=0 {
|
||||
; read the rest of the entry until the end
|
||||
}
|
||||
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
if not internal_next_entry()
|
||||
break
|
||||
|
||||
if not list_skip_disk_name {
|
||||
if list_pattern==0
|
||||
return true
|
||||
if strings.pattern_match(list_filename, list_pattern)
|
||||
if list_pattern==0 or strings.pattern_match(list_filename, list_pattern)
|
||||
return true
|
||||
}
|
||||
list_skip_disk_name = false
|
||||
}
|
||||
|
||||
close_end:
|
||||
lf_end_list()
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_next_entry_nocase() -> bool {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
; If a match pattern was specified, it will be matched CASE INSENSITIVELY against the filename, in ISO-encoding.
|
||||
; (match pattern has to be in lowercase as well)
|
||||
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
|
||||
|
||||
if not iteration_in_progress
|
||||
return false
|
||||
|
||||
repeat {
|
||||
if not internal_next_entry()
|
||||
break
|
||||
|
||||
if not list_skip_disk_name {
|
||||
if list_pattern==0 or strings.pattern_match_nocase(list_filename, list_pattern, true)
|
||||
return true
|
||||
}
|
||||
list_skip_disk_name = false
|
||||
}
|
||||
|
||||
lf_end_list()
|
||||
return false
|
||||
}
|
||||
|
||||
sub internal_next_entry() -> bool {
|
||||
reset_read_channel() ; use the input io channel again
|
||||
|
||||
^^ubyte nameptr = &list_filename
|
||||
ubyte blocks_lsb = cbm.CHRIN()
|
||||
ubyte blocks_msb = cbm.CHRIN()
|
||||
|
||||
if cbm.READST()!=0
|
||||
return false
|
||||
|
||||
list_blocks = mkword(blocks_msb, blocks_lsb)
|
||||
|
||||
; read until the filename starts after the first "
|
||||
while cbm.CHRIN()!='\"' {
|
||||
if cbm.READST()!=0
|
||||
return false
|
||||
}
|
||||
|
||||
; read the filename
|
||||
repeat {
|
||||
ubyte character = cbm.CHRIN()
|
||||
if character==0
|
||||
break
|
||||
if character=='\"'
|
||||
break
|
||||
@(nameptr) = character
|
||||
nameptr++
|
||||
}
|
||||
|
||||
@(nameptr) = 0
|
||||
|
||||
do {
|
||||
cx16.r15L = cbm.CHRIN()
|
||||
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
|
||||
list_filetype[0] = cx16.r15L
|
||||
list_filetype[1] = cbm.CHRIN()
|
||||
list_filetype[2] = cbm.CHRIN()
|
||||
while cbm.CHRIN()!=0 {
|
||||
; read the rest of the entry until the end
|
||||
}
|
||||
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
sub lf_end_list() {
|
||||
; -- end an iterative file listing session (close channels).
|
||||
if iteration_in_progress {
|
||||
@@ -811,6 +893,7 @@ io_error:
|
||||
if cbm.READST()!=0
|
||||
cx16.r0 = 0
|
||||
}
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
@@ -74,9 +74,8 @@ emudbg {
|
||||
EMU_CPUCLK_L = 0
|
||||
}
|
||||
|
||||
asmsub cpu_cycles() -> uword @R0, uword @R1 {
|
||||
; -- returns the 32 bits cpu clock counter in R1:R0,
|
||||
; returns lower 16 bits in R0, upper 16 bits in R1.
|
||||
asmsub cpu_cycles() -> long @R0R1 {
|
||||
; -- returns the 32 bits cpu clock counter in R1:R0
|
||||
%asm {{
|
||||
lda p8v_EMU_CPUCLK_L
|
||||
sta cx16.r0L
|
||||
|
||||
@@ -139,14 +139,19 @@ asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y)
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
; See "basic.sym" kernal symbol file for the facmo memory location.
|
||||
; TODO find a way to not depend on that internal fac memory location. MOVFM doesn't work.
|
||||
%asm {{
|
||||
sty $c4 ; facmo
|
||||
sta $c5 ; facmo+1
|
||||
ldx #$90
|
||||
sec
|
||||
jmp FLOATC
|
||||
; treat as signed first
|
||||
jsr GIVAYFAY
|
||||
jsr SIGN
|
||||
bpl +
|
||||
; add 65536 to make it positive again
|
||||
lda #<_float_const_65536
|
||||
ldy #>_float_const_65536
|
||||
jmp FADD
|
||||
+ rts
|
||||
|
||||
_float_const_65536 .byte $91, $00, $00, $00, $00 ; float 65536.0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
%import syslib
|
||||
|
||||
; NOTE: deprecated, use psg2 module instead. That fixes timing and has other benefits.
|
||||
|
||||
psg {
|
||||
%option ignore_unused
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
; Vera PSG abstraction module, version 2
|
||||
; Supersedes (and is not compatible to) the previous psg module which had some problems (timings were not consistent, etc)
|
||||
; This new module requires setting up the voice parameters in their Voice structure,
|
||||
; and then calling the update() routine periodically to apply the changes.
|
||||
; You can read and write all parameters in the Voice structure as desired.
|
||||
; For efficiency reasons, the volume envelope state is not kept in there though.
|
||||
|
||||
; Vera PSG registers:
|
||||
; $1F9C0 - $1F9FF 16 blocks of 4 PSG registers (16 voices)
|
||||
; 00 frequency word LSB
|
||||
; 01 frequency word MSB. freqword = HERZ / 0.3725290298461914
|
||||
; 02 bit 7 =right, bit 6 = left, bits 5-0 = volume 0-63 levels
|
||||
; 03 bit 7,6 = waveform, bits 5-0 = Pulse width 0-63
|
||||
; waveform: 0=pulse, 1=sawtooth, 2=triangle, 3=noise
|
||||
|
||||
|
||||
psg2 {
|
||||
%option ignore_unused
|
||||
|
||||
; waveform options:
|
||||
const ubyte PULSE = %00000000
|
||||
const ubyte SQUARE = %00000000 ; same as PULSE
|
||||
const ubyte SAWTOOTH = %01000000
|
||||
const ubyte TRIANGLE = %10000000
|
||||
const ubyte NOISE = %11000000
|
||||
|
||||
; channels:
|
||||
const ubyte LEFT = %01000000
|
||||
const ubyte RIGHT = %10000000
|
||||
const ubyte BOTH = %11000000
|
||||
const ubyte DISABLED = %00000000
|
||||
|
||||
; envelope states:
|
||||
const ubyte E_OFF = 0
|
||||
const ubyte E_ATTACK = 1
|
||||
const ubyte E_SUSTAIN = 2
|
||||
const ubyte E_RELEASE = 3
|
||||
|
||||
struct Voice {
|
||||
ubyte channels ; LEFT,RIGHT,BOTH or DISABLED
|
||||
ubyte volume ; 0-63, use setvolume() to adjust this if you are also using ADSR envelope
|
||||
ubyte waveform ; PULSE/SQUARE,SAWTOOTH,TRIANGLE,NOISE
|
||||
ubyte pulsewidth ; 0-63
|
||||
uword frequency
|
||||
}
|
||||
|
||||
|
||||
; envelope parameters are kept in arrays for maximum efficiency
|
||||
ubyte[16] envelope_states
|
||||
ubyte[16] envelope_attacks
|
||||
ubyte[16] envelope_sustains
|
||||
ubyte[16] envelope_releases
|
||||
ubyte[16] envelope_maxvolumes
|
||||
uword[16] envelope_volumes ; 8.8 fixed point (scaled by 256)
|
||||
|
||||
|
||||
^^Voice voices = memory("psg2voices", 16*sizeof(Voice), 0) ; can't use array of structs so use mem block
|
||||
^^Voice vptr
|
||||
|
||||
sub init() {
|
||||
; -- required before using this module; initializes all registers to default (off) values
|
||||
sys.irqsafe_set_irqd()
|
||||
sys.memset(voices, 16*sizeof(Voice), 0)
|
||||
sys.irqsafe_clear_irqd()
|
||||
for cx16.r0L in 0 to 15
|
||||
envelope_states[cx16.r0L] = E_OFF
|
||||
}
|
||||
|
||||
sub off() {
|
||||
; -- turn off all voices immediately
|
||||
sys.irqsafe_set_irqd()
|
||||
vptr = voices
|
||||
for cx16.r0L in 0 to 15 {
|
||||
vptr.channels = DISABLED
|
||||
vptr++
|
||||
envelope_states[cx16.r0L] = E_OFF
|
||||
}
|
||||
sys.irqsafe_clear_irqd()
|
||||
void update()
|
||||
}
|
||||
|
||||
sub voice(ubyte voice_num, ubyte channels, ubyte volume, ubyte waveform, ubyte pulsewidth) {
|
||||
; -- sets all parameters for a single voice (frequency is unchanged - set that separately)
|
||||
if voice_num > 15
|
||||
return
|
||||
volume &= %00111111
|
||||
envelope_states[voice_num] = E_OFF
|
||||
sys.irqsafe_set_irqd()
|
||||
vptr = &voices[voice_num]
|
||||
vptr.channels = channels
|
||||
vptr.volume = volume
|
||||
vptr.waveform = waveform
|
||||
vptr.pulsewidth = pulsewidth & %00111111
|
||||
; do not change frequency, user is expected to call frequency() separately
|
||||
sys.irqsafe_clear_irqd()
|
||||
envelope_volumes[voice_num] = mkword(volume, 0)
|
||||
envelope_maxvolumes[voice_num] = volume
|
||||
}
|
||||
|
||||
sub frequency(ubyte voice_num, uword @nozp freq) {
|
||||
; -- sets new frequence for this voice
|
||||
vptr = &voices[voice_num]
|
||||
vptr.frequency = freq
|
||||
}
|
||||
|
||||
sub volume(ubyte voice_num, ubyte @nozp vol) {
|
||||
; -- Modifies the volume of this voice, adjusting the envelope as needed.
|
||||
; voice_num = 0-15, vol = 0-63 where 0=silent, 63=loudest.
|
||||
envelope_states[voice_num] = E_OFF
|
||||
vptr = &voices[voice_num]
|
||||
vptr.volume = vol
|
||||
envelope_volumes[voice_num] = mkword(vol, 0)
|
||||
envelope_maxvolumes[voice_num] = vol
|
||||
}
|
||||
|
||||
sub envelope(ubyte voice_num, ubyte @nozp attack, ubyte @nozp sustain, ubyte @nozp release) {
|
||||
; -- sets ASR envelope parameters for this voice
|
||||
envelope_states[voice_num] = E_OFF
|
||||
vptr = &voices[voice_num]
|
||||
vptr.volume = 0
|
||||
envelope_attacks[voice_num] = attack
|
||||
envelope_sustains[voice_num] = sustain
|
||||
envelope_releases[voice_num] = release
|
||||
envelope_volumes[voice_num] = 0
|
||||
envelope_states[voice_num] = E_ATTACK
|
||||
}
|
||||
|
||||
sub getvoice(ubyte @nozp voice_num) -> ^^Voice {
|
||||
; -- returns pointer to voice parameters
|
||||
return &voices[voice_num]
|
||||
}
|
||||
|
||||
sub update() -> bool {
|
||||
; -- update adsr envelopes, then write all 16 voices to Vera PSG
|
||||
; This has to be called every time you want to apply changed psg voice parameters.
|
||||
; If you want to use real-time volume envelopes (Attack-Sustain-Release),
|
||||
; you have to call this routine every 1/60th second, for example from your vsync irq handler.
|
||||
; Or just install this routine as the only irq handler if you don't have to do other things there.
|
||||
|
||||
sys.push(cx16.r0L)
|
||||
sys.pushw(cx16.r1)
|
||||
sys.pushw(cx16.r2)
|
||||
sys.pushw(cx16.r3)
|
||||
|
||||
vptr = voices
|
||||
alias voice = cx16.r0L
|
||||
alias maxvolume = cx16.r1
|
||||
alias newvolume = cx16.r2
|
||||
alias currentvolume = cx16.r3
|
||||
|
||||
; update active envelopes
|
||||
for voice in 0 to 15 {
|
||||
when envelope_states[voice] {
|
||||
E_ATTACK -> {
|
||||
; while current volume is less than max volume, increase volume by maxvolume * (attackspeed /256.0)
|
||||
maxvolume = mkword(envelope_maxvolumes[voice], 0)
|
||||
currentvolume = envelope_volumes[voice]
|
||||
if currentvolume < maxvolume {
|
||||
newvolume = currentvolume + (maxvolume >> 8)*envelope_attacks[voice]
|
||||
newvolume = min(newvolume, maxvolume)
|
||||
envelope_volumes[voice] = newvolume
|
||||
vptr.volume = msb(newvolume)
|
||||
} else
|
||||
envelope_states[voice] = E_SUSTAIN
|
||||
}
|
||||
E_SUSTAIN -> {
|
||||
if envelope_sustains[voice] > 0
|
||||
envelope_sustains[voice]--
|
||||
else
|
||||
envelope_states[voice] = E_RELEASE
|
||||
}
|
||||
E_RELEASE -> {
|
||||
; while current volume is not zero, decrease volume by maxvolume * (releasespeed /256.0)
|
||||
currentvolume = envelope_volumes[voice]
|
||||
if currentvolume > 0 {
|
||||
uword subtraction = (currentvolume>>8)*envelope_releases[voice]
|
||||
if subtraction > currentvolume
|
||||
newvolume = 0
|
||||
else
|
||||
newvolume = currentvolume - subtraction
|
||||
envelope_volumes[voice] = newvolume
|
||||
vptr.volume = msb(newvolume)
|
||||
} else
|
||||
envelope_states[voice] = E_OFF
|
||||
}
|
||||
}
|
||||
|
||||
vptr++
|
||||
}
|
||||
|
||||
cx16.save_vera_context()
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR = lsw(cx16.VERA_PSG_BASE)
|
||||
cx16.VERA_ADDR_H = %00010000 | msw(cx16.VERA_PSG_BASE)
|
||||
|
||||
vptr = voices
|
||||
repeat 16 {
|
||||
if vptr.channels != DISABLED {
|
||||
cx16.VERA_DATA0 = lsb(vptr.frequency)
|
||||
cx16.VERA_DATA0 = msb(vptr.frequency)
|
||||
cx16.VERA_DATA0 = vptr.channels | (vptr.volume & %00111111)
|
||||
cx16.VERA_DATA0 = vptr.waveform | (vptr.pulsewidth & %00111111)
|
||||
} else {
|
||||
unroll 4 cx16.VERA_DATA0 = 0
|
||||
}
|
||||
vptr++
|
||||
}
|
||||
cx16.restore_vera_context()
|
||||
cx16.r3 = sys.popw()
|
||||
cx16.r2 = sys.popw()
|
||||
cx16.r1 = sys.popw()
|
||||
cx16.r0L = sys.pop()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
asmsub SETTIML(long jiffies @R0R1) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
@@ -137,7 +137,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1 {
|
||||
; -- like RDTIM_safe() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM_safe
|
||||
@@ -412,10 +412,9 @@ cx16 {
|
||||
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
|
||||
|
||||
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
; VERA_PALETTE_BASE = $1FA00
|
||||
; VERA_SPRITES_BASE = $1FC00
|
||||
const long VERA_PSG_BASE = $1F9C0
|
||||
const long VERA_PALETTE_BASE = $1FA00
|
||||
const long VERA_SPRITES_BASE = $1FC00
|
||||
|
||||
; I/O
|
||||
|
||||
@@ -424,10 +423,13 @@ cx16 {
|
||||
&ubyte via1pra = VIA1_BASE + 1
|
||||
&ubyte via1ddrb = VIA1_BASE + 2
|
||||
&ubyte via1ddra = VIA1_BASE + 3
|
||||
&uword via1t1 = VIA1_BASE + 4
|
||||
&ubyte via1t1l = VIA1_BASE + 4
|
||||
&ubyte via1t1h = VIA1_BASE + 5
|
||||
&uword via1t1lw = VIA1_BASE + 6
|
||||
&ubyte via1t1ll = VIA1_BASE + 6
|
||||
&ubyte via1t1lh = VIA1_BASE + 7
|
||||
&uword via1t2 = VIA1_BASE + 8
|
||||
&ubyte via1t2l = VIA1_BASE + 8
|
||||
&ubyte via1t2h = VIA1_BASE + 9
|
||||
&ubyte via1sr = VIA1_BASE + 10
|
||||
@@ -437,15 +439,18 @@ cx16 {
|
||||
&ubyte via1ier = VIA1_BASE + 14
|
||||
&ubyte via1ora = VIA1_BASE + 15
|
||||
|
||||
const uword VIA2_BASE = $9f10 ;VIA 6522 #2
|
||||
const uword VIA2_BASE = $9f10 ;VIA 6522 #2, not always present
|
||||
&ubyte via2prb = VIA2_BASE + 0
|
||||
&ubyte via2pra = VIA2_BASE + 1
|
||||
&ubyte via2ddrb = VIA2_BASE + 2
|
||||
&ubyte via2ddra = VIA2_BASE + 3
|
||||
&uword via2t1 = VIA2_BASE + 4
|
||||
&ubyte via2t1l = VIA2_BASE + 4
|
||||
&ubyte via2t1h = VIA2_BASE + 5
|
||||
&uword via2t1lw = VIA2_BASE + 6
|
||||
&ubyte via2t1ll = VIA2_BASE + 6
|
||||
&ubyte via2t1lh = VIA2_BASE + 7
|
||||
&uword via2t2 = VIA2_BASE + 8
|
||||
&ubyte via2t2l = VIA2_BASE + 8
|
||||
&ubyte via2t2h = VIA2_BASE + 9
|
||||
&ubyte via2sr = VIA2_BASE + 10
|
||||
@@ -545,7 +550,7 @@ extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbe
|
||||
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
|
||||
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see detect_joysticks() and get_all_joysticks()
|
||||
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see joysticks_detect() and joysticks_getall()
|
||||
|
||||
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
|
||||
extsub $C000 = x16edit_default() clobbers(A,X,Y)
|
||||
@@ -553,6 +558,11 @@ extsub $C003 = x16edit_loadfile(ubyte firstbank @X, ubyte lastbank @Y, str filen
|
||||
extsub $C006 = x16edit_loadfile_options(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0,
|
||||
uword filenameLengthAndOptions @R1, uword tabstopAndWordwrap @R2,
|
||||
uword disknumberAndColors @R3, uword headerAndStatusColors @R4) clobbers(A,X,Y)
|
||||
; note: the following routine is not yet available in currently released ROMS (v49) hopefully the updated X16Edit will be in r50.
|
||||
extsub $C009 = x16edit_loadfile_options2(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0,
|
||||
uword filenameLengthAndOptions @R1, uword tabstopAndWordwrap @R2,
|
||||
uword disknumberAndColors @R3, uword headerAndStatusColors @R4,
|
||||
uword linenumberLM @R5, ubyte linenumberH @R6) clobbers(A,X,Y)
|
||||
|
||||
; Audio (rom bank 10)
|
||||
; NOTE: because these are auto-banked, you should not call them from an IRQ handler routine (due to jsrfar race condition).
|
||||
@@ -641,6 +651,10 @@ const ubyte EXTAPI16_stack_push = $01
|
||||
const ubyte EXTAPI16_stack_pop = $02
|
||||
const ubyte EXTAPI16_stack_enter_kernal_stack = $03
|
||||
const ubyte EXTAPI16_stack_leave_kernal_stack = $04
|
||||
const ubyte EXTAPI16_xmacptr = $05
|
||||
const ubyte EXTAPI16_xmciout = $06
|
||||
const ubyte EXTAPI16_hbload = $07
|
||||
const ubyte EXTAPI16_get_last_far_bank = $08
|
||||
|
||||
|
||||
asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) {
|
||||
@@ -1240,7 +1254,7 @@ asmsub restore_vera_context() clobbers(A) {
|
||||
|
||||
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
|
||||
; Install a custom CHRIN (BASIN) key handler in a safe manner. Call this before each line you want to read.
|
||||
; See https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2003%20-%20Editor.md#custom-basin-petscii-code-override-handler
|
||||
; See https://github.com/X16Community/x16-docs/blob/r49/X16%20Reference%20-%2003%20-%20Editor.md#custom-basin-petscii-code-override-handler
|
||||
%asm {{
|
||||
sei
|
||||
sta P8ZP_SCRATCH_REG
|
||||
@@ -1322,7 +1336,7 @@ inline asmsub disable_irqs() clobbers(A) {
|
||||
|
||||
asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
|
||||
; Install the "master IRQ handler" that will dispatch IRQs
|
||||
; to the registered handler for each type. (Only Vera IRQs supported for now).
|
||||
; to the registered handler for each type.
|
||||
; The handlers don't need to clear its ISR bit, but have to return 0 or 1 in A,
|
||||
; where 1 means: continue with the system IRQ handler, 0 means: don't call that.
|
||||
%asm {{
|
||||
@@ -1330,7 +1344,11 @@ asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
|
||||
sei
|
||||
bcc +
|
||||
lda #%00001111
|
||||
trb cx16.VERA_IEN ; disable all IRQ sources
|
||||
; disable all IRQ sources on VERA and VIA1
|
||||
trb cx16.VERA_IEN
|
||||
lda #%01111111
|
||||
sta cx16.via1ier
|
||||
|
||||
+ lda #<_irq_dispatcher
|
||||
ldy #>_irq_dispatcher
|
||||
sta cbm.CINV
|
||||
@@ -1348,6 +1366,8 @@ asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
|
||||
sty _aflow_vec+1
|
||||
sta _sprcol_vec
|
||||
sty _sprcol_vec+1
|
||||
sta _timer1_vec
|
||||
sty _timer1_vec+1
|
||||
|
||||
plp
|
||||
rts
|
||||
@@ -1357,11 +1377,12 @@ _vsync_vec .word ?
|
||||
_line_vec .word ?
|
||||
_aflow_vec .word ?
|
||||
_sprcol_vec .word ?
|
||||
_timer1_vec .word ?
|
||||
_continue_with_system_handler .byte ?
|
||||
.send BSS
|
||||
|
||||
_irq_dispatcher
|
||||
; order of handling: LINE, SPRCOL, AFLOW, VSYNC.
|
||||
; order of handling: LINE, TIMER1(VIA1), VSYNC, SPRCOL, AFLOW
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
lda cx16.VERA_ISR
|
||||
@@ -1377,7 +1398,17 @@ _irq_dispatcher
|
||||
tsb _continue_with_system_handler
|
||||
pla
|
||||
|
||||
+ lsr a
|
||||
; timer1 (VIA1) irq?
|
||||
+ pha
|
||||
lda #%01000000
|
||||
and cx16.via1ifr
|
||||
beq + ; not timer1 irq
|
||||
sta cx16.via1ifr
|
||||
jsr _timer1_handler
|
||||
tsb _continue_with_system_handler
|
||||
+ pla
|
||||
|
||||
lsr a
|
||||
bcc +
|
||||
pha
|
||||
jsr _vsync_handler
|
||||
@@ -1422,6 +1453,8 @@ _sprcol_handler
|
||||
jmp (_sprcol_vec)
|
||||
_aflow_handler
|
||||
jmp (_aflow_vec)
|
||||
_timer1_handler
|
||||
jmp (_timer1_vec)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1486,6 +1519,28 @@ asmsub set_aflow_irq_handler(uword address @AY) clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_timer1_irq_handler(uword address @AY) {
|
||||
; Sets the VIA1 TIMER1 irq handler to use with enable_irq_handlers(). Does not enable or disable VIA1 timer irqs setting.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta enable_irq_handlers._timer1_vec
|
||||
sty enable_irq_handlers._timer1_vec+1
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub set_timer1(uword delay, bool keeprunning) {
|
||||
; -- set VIA1 timer1 to trigger after the given delay (cycles). Enables VIA timer1 irqs if delay>0, otherwise disables it.
|
||||
cx16.via1acr &= %00111111
|
||||
if keeprunning
|
||||
cx16.via1acr |= %01000000 ; continuous (free-run) mode
|
||||
cx16.via1ier = if delay==0 then %01000000 else %11000000
|
||||
cx16.via1t1lw = delay
|
||||
}
|
||||
|
||||
|
||||
inline asmsub disable_irq_handlers() {
|
||||
; back to the system default IRQ handler.
|
||||
@@ -1528,7 +1583,7 @@ sub search_x16edit() -> ubyte {
|
||||
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.
|
||||
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
|
||||
; see https://github.com/X16Community/x16-docs/blob/r49/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
|
||||
sys.push(getrambank())
|
||||
rambank(0)
|
||||
sys.memcopy(args_ptr, $bf00, args_size)
|
||||
@@ -1540,7 +1595,7 @@ sub search_x16edit() -> ubyte {
|
||||
asmsub get_program_args(^^ubyte buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
|
||||
; -- Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
|
||||
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
|
||||
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
|
||||
; see https://github.com/X16Community/x16-docs/blob/r49/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
|
||||
%asm {{
|
||||
lda #0
|
||||
rol a
|
||||
@@ -1604,6 +1659,8 @@ _continue iny
|
||||
}
|
||||
}
|
||||
|
||||
%import shared_sys_functions
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@@ -1681,6 +1738,16 @@ asmsub restore_irq() clobbers(A) {
|
||||
and #%11110000 ; disable all Vera IRQs but the vsync
|
||||
ora #%00000001
|
||||
sta cx16.VERA_IEN
|
||||
lda #%01111111
|
||||
sta cx16.via1ier
|
||||
; set timer1 back to free running mode
|
||||
lda cx16.via1acr
|
||||
and %00111111
|
||||
ora %01000000
|
||||
sta cx16.via1acr
|
||||
lda #$ff
|
||||
sta cx16.via1t1ll
|
||||
sta cx16.via1t1lh
|
||||
plp
|
||||
rts
|
||||
.section BSS_NOCLEAR
|
||||
@@ -1856,182 +1923,6 @@ _larger
|
||||
}
|
||||
|
||||
|
||||
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 {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; If you have to copy overlapping memory regions, consider using
|
||||
; the cx16 specific kernal routine `memory_copy` (make sure kernal rom is banked in).
|
||||
; note: can't be inlined because is called from asm as well.
|
||||
; also: doesn't use cx16 ROM routine so this always works even when ROM is not banked in.
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
dey
|
||||
beq +
|
||||
- lda (cx16.r0),y
|
||||
sta (cx16.r1),y
|
||||
dey
|
||||
bne -
|
||||
+ lda (cx16.r0),y
|
||||
sta (cx16.r1),y
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
pha ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
sta (cx16.r1),y
|
||||
iny
|
||||
bne -
|
||||
inc cx16.r0+1
|
||||
inc cx16.r1+1
|
||||
dex
|
||||
bne -
|
||||
ply
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx P8ZP_SCRATCH_W1+1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
cmp (cx16.r1),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc cx16.r0+1
|
||||
inc cx16.r1+1
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (cx16.r0),y
|
||||
cmp (cx16.r1),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_W1
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #8
|
||||
@@ -2046,49 +1937,6 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
.section BSS
|
||||
save_SCRATCH_ZPB1 .byte ?
|
||||
save_SCRATCH_ZPREG .byte ?
|
||||
save_SCRATCH_ZPWORD1 .word ?
|
||||
save_SCRATCH_ZPWORD2 .word ?
|
||||
.send BSS
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
@@ -2126,20 +1974,6 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -2190,7 +2024,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
inline asmsub pushl(long value @R0R1) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
@@ -2203,7 +2037,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
inline asmsub popl() -> long @R0R1 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
@@ -2273,6 +2107,7 @@ asmsub init_system() {
|
||||
sta $00 ; select ram bank
|
||||
lda #0
|
||||
sta $01 ; set ROM bank to kernal bank to speed up kernal calls
|
||||
sta cx16.VERA_DC_BORDER
|
||||
tax
|
||||
tay
|
||||
cli
|
||||
|
||||
@@ -137,7 +137,7 @@ verafx {
|
||||
}
|
||||
|
||||
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> long @R0R1_32 {
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> long @R0R1 {
|
||||
; 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)
|
||||
|
||||
@@ -524,7 +524,7 @@ log2_tab
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
eor #255
|
||||
inc a
|
||||
adc #1
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
@@ -543,7 +543,7 @@ log2_tab
|
||||
sta cx16.r0H
|
||||
lda cx16.r0L
|
||||
eor #255
|
||||
inc a
|
||||
adc #1
|
||||
sta cx16.r0L
|
||||
bne +
|
||||
inc cx16.r0H
|
||||
@@ -674,7 +674,7 @@ log2_tab
|
||||
return crc32_end_result()
|
||||
}
|
||||
|
||||
asmsub crc32_end_result() -> long @R14R15_32 {
|
||||
asmsub crc32_end_result() -> long @R14R15 {
|
||||
; finalize the "streaming" crc32
|
||||
; returns the result value in cx16.r15 (high word) and r14 (low word)
|
||||
%asm {{
|
||||
@@ -731,4 +731,48 @@ log2_tab
|
||||
cx16.r15 *= (outputMax-outputMin)
|
||||
return cx16.r15H + outputMin
|
||||
}
|
||||
|
||||
sub gcd(uword @nozp a, uword @nozp b) -> uword {
|
||||
; Calculate the Greatest Common Divisor of two 16-bit unsigned integers using the Binary GCD algorithm (Stein's algorithm). Clobbers R0,R1 !
|
||||
|
||||
alias aa = cx16.r0
|
||||
alias bb = cx16.r1
|
||||
|
||||
aa=a
|
||||
bb=b
|
||||
|
||||
; Handle the case where one number is zero
|
||||
if aa == 0 return bb
|
||||
if bb == 0 return aa
|
||||
|
||||
; Count the number of common factors of 2
|
||||
ubyte shift = 0
|
||||
|
||||
; Count common factors of 2 by shifting both numbers right until at least one is odd
|
||||
while ((aa | bb) & 1) == 0 {
|
||||
aa >>= 1
|
||||
bb >>= 1
|
||||
shift++
|
||||
}
|
||||
|
||||
; Remove remaining factors of 2 from a
|
||||
while (aa & 1) == 0
|
||||
aa >>= 1
|
||||
|
||||
; Now a is odd. Loop until b becomes zero
|
||||
while bb != 0 {
|
||||
; Remove remaining factors of 2 from b
|
||||
while (bb & 1) == 0
|
||||
bb >>= 1
|
||||
|
||||
; Make sure a <= b
|
||||
if aa > bb
|
||||
swap(aa, bb)
|
||||
|
||||
bb -= aa
|
||||
}
|
||||
|
||||
; Return the result multiplied by the common factors of 2
|
||||
return aa << shift
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
%import strings
|
||||
%import syslib
|
||||
|
||||
diskio {
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
const ubyte STATUS_EOF=$40
|
||||
|
||||
ubyte @shared drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
|
||||
|
||||
str list_filename = "?" * 50
|
||||
|
||||
sub reset_read_channel() {
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub reset_write_channel() {
|
||||
cbm.CHKOUT(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub directory() -> bool {
|
||||
; -- Prints the directory contents to the screen. Returns success.
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
|
||||
repeat 4 {
|
||||
void cbm.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not stop key pressed / EOF encountered, read data.
|
||||
status = cbm.READST()
|
||||
if status!=0 {
|
||||
status = 1
|
||||
goto io_error
|
||||
}
|
||||
|
||||
while status==0 {
|
||||
ubyte low = cbm.CHRIN()
|
||||
ubyte high = cbm.CHRIN()
|
||||
txt.print_uw(mkword(high, low))
|
||||
txt.spc()
|
||||
ubyte @zp character
|
||||
repeat {
|
||||
character = cbm.CHRIN()
|
||||
if character==0
|
||||
break
|
||||
txt.chrout(character)
|
||||
}
|
||||
txt.nl()
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
status = cbm.READST()
|
||||
void cbm.STOP()
|
||||
if_z
|
||||
break
|
||||
}
|
||||
status = cbm.READST()
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
|
||||
if status!=0 and status & STATUS_EOF == 0 {
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.nl()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
sub list_filenames(str pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
; Note that no list of pointers of some form is returned, the names are just squashed together.
|
||||
; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names
|
||||
; and registering when the next one starts after the 0-byte separator.
|
||||
|
||||
ubyte files_found = 0
|
||||
uword buf_ptr = filenames_buffer
|
||||
@(buf_ptr) = 0
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
|
||||
repeat 4 {
|
||||
void cbm.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not stop key pressed / EOF encountered, read data.
|
||||
status = cbm.READST()
|
||||
if status!=0 {
|
||||
status = 1
|
||||
goto io_error
|
||||
}
|
||||
|
||||
repeat 4 void cbm.CHRIN() ; skip the 4 prologue bytes
|
||||
|
||||
while cbm.CHRIN()!=0 {
|
||||
; skip the disk name
|
||||
}
|
||||
|
||||
while status==0 {
|
||||
while cbm.CHRIN()!='"' {
|
||||
if cbm.READST()!=0
|
||||
goto end_listing
|
||||
; read up to first quote
|
||||
}
|
||||
|
||||
^^ubyte name_ptr = list_filename
|
||||
|
||||
; read and store file name
|
||||
repeat {
|
||||
cx16.r0L = cbm.CHRIN()
|
||||
if cx16.r0L=='"'
|
||||
break
|
||||
if cx16.r0L==0
|
||||
cx16.r0L = '?'
|
||||
@(name_ptr) = cx16.r0L
|
||||
name_ptr++
|
||||
}
|
||||
@(name_ptr) = 0
|
||||
|
||||
if pattern_ptr==0 or strings.pattern_match(list_filename, pattern_ptr) {
|
||||
buf_ptr += strings.copy(list_filename, buf_ptr) + 1
|
||||
files_found++
|
||||
}
|
||||
|
||||
while cbm.CHRIN()!=0 {
|
||||
; read up to first quote
|
||||
}
|
||||
void cbm.CHRIN()
|
||||
void cbm.CHRIN()
|
||||
|
||||
status = cbm.READST()
|
||||
void cbm.STOP()
|
||||
if_z
|
||||
break
|
||||
}
|
||||
|
||||
end_listing:
|
||||
@(buf_ptr) = 0
|
||||
status = cbm.READST()
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
return files_found
|
||||
}
|
||||
|
||||
sub diskname() -> str {
|
||||
; -- Returns pointer to disk name string or 0 if failure.
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
bool okay = false
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
void cbm.CHRIN()
|
||||
if cbm.READST()!=0
|
||||
goto io_error
|
||||
|
||||
while cbm.CHRIN()!='"' {
|
||||
; skip up to entry name
|
||||
}
|
||||
|
||||
cx16.r0 = &list_filename
|
||||
repeat {
|
||||
@(cx16.r0) = cbm.CHRIN()
|
||||
if @(cx16.r0)=='"' {
|
||||
@(cx16.r0) = ' '
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
|
||||
@(cx16.r0) = 0
|
||||
cx16.r0--
|
||||
}
|
||||
break
|
||||
}
|
||||
cx16.r0++
|
||||
}
|
||||
okay = true
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if okay
|
||||
return &list_filename
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
; If you specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
sub load(str filenameptr, uword address_override) -> uword {
|
||||
cbm.SETNAM(strings.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN()
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
if address_override==0 {
|
||||
; fetch load address from prg header first 2 bytes
|
||||
%asm {{
|
||||
jsr cbm.CHRIN
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
jsr cbm.CHRIN
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
}}
|
||||
} else {
|
||||
; skip the load address from the prg header
|
||||
%asm {{
|
||||
jsr cbm.CHRIN
|
||||
jsr cbm.CHRIN
|
||||
lda address_override
|
||||
ldy address_override+1
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
}}
|
||||
}
|
||||
|
||||
if cbm.READST()!=0 {
|
||||
address_override = 0
|
||||
goto end
|
||||
}
|
||||
|
||||
readloop:
|
||||
%asm {{
|
||||
jsr cbm.READST
|
||||
cmp #STATUS_EOF
|
||||
beq _done
|
||||
cmp #0
|
||||
bne _error
|
||||
jsr cbm.CHRIN
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
inc P8ZP_SCRATCH_PTR
|
||||
bne readloop
|
||||
inc P8ZP_SCRATCH_PTR+1
|
||||
bne readloop
|
||||
|
||||
_error:
|
||||
lda #0
|
||||
beq _done2
|
||||
_done:
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
ldy P8ZP_SCRATCH_PTR+1
|
||||
_done2:
|
||||
sta address_override
|
||||
sty address_override+1
|
||||
}}
|
||||
|
||||
end:
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
return address_override
|
||||
}
|
||||
|
||||
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
|
||||
; No program header is assumed in the file. Everything is loaded.
|
||||
; See comments on load() for more details.
|
||||
sub load_raw(str filenameptr, uword start_address) -> uword {
|
||||
cbm.SETNAM(strings.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN()
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
%asm {{
|
||||
lda start_address
|
||||
ldy start_address+1
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
}}
|
||||
goto diskio.load.readloop
|
||||
}
|
||||
|
||||
|
||||
; saves a block of memory to disk, including the default 2 byte prg header.
|
||||
sub save(str filenameptr, uword startaddress, uword savesize) -> bool {
|
||||
bool skip_header = false
|
||||
internal_save:
|
||||
cbm.SETNAM(strings.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
|
||||
void cbm.OPEN()
|
||||
cbm.CHKOUT(WRITE_IO_CHANNEL)
|
||||
bool status=false
|
||||
|
||||
if cbm.READST()==0 {
|
||||
%asm {{
|
||||
; write 2 bytes prg header
|
||||
lda skip_header
|
||||
bne +
|
||||
lda startaddress
|
||||
jsr cbm.CHROUT
|
||||
lda startaddress+1
|
||||
jsr cbm.CHROUT
|
||||
+ lda startaddress
|
||||
ldy startaddress+1
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
|
||||
jsr cbm.READST
|
||||
bne _error
|
||||
|
||||
; decrease savesize by 1 for the loop
|
||||
lda savesize
|
||||
bne +
|
||||
dec savesize+1
|
||||
+ dec savesize
|
||||
|
||||
ldy #0
|
||||
_loop:
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
jsr cbm.CHROUT
|
||||
|
||||
+ lda savesize
|
||||
bne +
|
||||
lda savesize+1
|
||||
beq _done
|
||||
dec savesize+1
|
||||
+ dec savesize
|
||||
iny
|
||||
bne _loop
|
||||
jsr cbm.READST
|
||||
bne _error
|
||||
inc P8ZP_SCRATCH_PTR+1
|
||||
jmp _loop
|
||||
_done:
|
||||
inc status
|
||||
_error:
|
||||
}}
|
||||
}
|
||||
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
return status
|
||||
}
|
||||
|
||||
; like save() but omits the 2 byte prg header.
|
||||
sub save_raw(str filenameptr, uword startaddress, uword savesize) -> bool {
|
||||
diskio.save.filenameptr = filenameptr
|
||||
diskio.save.startaddress = startaddress
|
||||
diskio.save.savesize = savesize
|
||||
diskio.save.skip_header = true
|
||||
goto diskio.save.internal_save
|
||||
}
|
||||
|
||||
|
||||
sub status() -> str {
|
||||
; -- retrieve the disk drive's current status message
|
||||
|
||||
^^ubyte messageptr = &list_filename
|
||||
cbm.SETNAM(0, list_filename)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN() ; open 15,8,15
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
|
||||
while cbm.READST()==0 {
|
||||
cx16.r5L = cbm.CHRIN()
|
||||
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||
break
|
||||
@(messageptr) = cx16.r5L
|
||||
messageptr++
|
||||
}
|
||||
@(messageptr) = 0
|
||||
|
||||
done:
|
||||
cbm.CLOSE(15)
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
return list_filename
|
||||
}
|
||||
|
||||
; similar to above, but instead of fetching the entire string, it only fetches the status code and returns it as ubyte
|
||||
; in case of IO error, returns 255 (CMDR-DOS itself is physically unable to return such a value)
|
||||
sub status_code() -> ubyte {
|
||||
if cbm.READST()==128 {
|
||||
return 255
|
||||
}
|
||||
|
||||
cbm.SETNAM(0, list_filename)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
|
||||
list_filename[0] = cbm.CHRIN()
|
||||
list_filename[1] = cbm.CHRIN()
|
||||
list_filename[2] = 0
|
||||
|
||||
while cbm.READST()==0 {
|
||||
void cbm.CHRIN()
|
||||
}
|
||||
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(15)
|
||||
return conv.str2ubyte(list_filename)
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(15)
|
||||
return 255
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"filename"
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
cx16.r0L = cbm.CHRIN()
|
||||
cx16.r0H = cbm.CHRIN()
|
||||
if cbm.READST()!=0
|
||||
cx16.r0 = 0
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
; DON'T use this if you already have a file open with f_open!
|
||||
; NOTE: doesn't clear the dos error status and message, you'll have to read/clear that yourself (with status() for example)
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"filename"
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
void cbm.CHRIN()
|
||||
cx16.r0bL = cbm.READST()==0
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
return cx16.r0bL
|
||||
}
|
||||
|
||||
sub delete(str filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
list_filename[0] = 's'
|
||||
list_filename[1] = ':'
|
||||
ubyte flen = strings.copy(filenameptr, &list_filename+2)
|
||||
cbm.SETNAM(flen+2, list_filename)
|
||||
cbm.SETLFS(1, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
}
|
||||
|
||||
sub rename(str oldfileptr, str newfileptr) {
|
||||
; -- rename a file on the drive
|
||||
list_filename[0] = 'r'
|
||||
list_filename[1] = ':'
|
||||
ubyte flen_new = strings.copy(newfileptr, &list_filename+2)
|
||||
list_filename[flen_new+2] = '='
|
||||
ubyte flen_old = strings.copy(oldfileptr, &list_filename+3+flen_new)
|
||||
cbm.SETNAM(3+flen_new+flen_old, list_filename)
|
||||
cbm.SETLFS(1, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
}
|
||||
|
||||
sub send_command(str commandptr) {
|
||||
; -- send a dos command to the drive
|
||||
cbm.SETNAM(strings.length(commandptr), commandptr)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(15)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
%option enable_floats, no_symbol_prefixing, ignore_unused
|
||||
%import shared_floats_functions
|
||||
|
||||
floats {
|
||||
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
|
||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
const uword FAC_ADDR = $5e
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
;extsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||
;extsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
|
||||
;;
|
||||
;
|
||||
;extsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
|
||||
;extsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||
;
|
||||
;
|
||||
;extsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
;extsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
;
|
||||
;extsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||
;extsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||
|
||||
|
||||
; PET32 BASIC 4.0 ADDRESSES FOUND:
|
||||
; (source: https://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt )
|
||||
|
||||
|
||||
;; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
;; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
extsub $c92d = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
extsub $c4bc = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
extsub $c2ea = AYINT() clobbers(A,X,Y) ; fac1-> signed word in ????? MSB FIRST. (might throw ILLEGAL QUANTITY) DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD.
|
||||
|
||||
extsub $cbc2 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
extsub $cd32 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
extsub $cd0a = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
|
||||
extsub $ccd8 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
extsub $cd42 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
extsub $cd45 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
|
||||
extsub $cb20 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
extsub $cb61 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
extsub $cb5e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
extsub $cc48 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
extsub $cc45 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
extsub $cc18 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
extsub $cc34 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
extsub $c9a0 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
extsub $c99d = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
extsub $c97f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
extsub $c989 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
extsub $c986 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
extsub $cd91 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
extsub $d112 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
|
||||
|
||||
extsub $ca0d = NORMAL() clobbers(A) ; normalize FAC1
|
||||
extsub $cd61 = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
extsub $cd6f = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
extsub $cd8e = ABS() ; fac1 = ABS(fac1)
|
||||
extsub $cdd1 = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in FAC, with the MSB FIRST.
|
||||
extsub $ce02 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
extsub $cf93 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
|
||||
extsub $d108 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
extsub $d14b = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
extsub $d184 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
extsub $d229 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
extsub $d282 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
extsub $d289 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
extsub $d2d2 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
extsub $d32c = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
|
||||
asmsub AYINT2() clobbers(X) -> word @AY {
|
||||
; fac1-> signed word in AY. Safe wrapper around the AYINT kernal routine (not reading internal memory locations)
|
||||
; (might throw ILLEGAL QUANTITY)
|
||||
%asm {{
|
||||
jsr AYINT
|
||||
ldx #<floats_temp_var
|
||||
ldy #>floats_temp_var
|
||||
jsr MOVMF
|
||||
lda floats_temp_var+4
|
||||
ldy floats_temp_var+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADUY(ubyte value @ Y) clobbers(A,X,Y) {
|
||||
; -- 8 bit unsigned Y -> float in fac1
|
||||
%asm {{
|
||||
lda #0
|
||||
jmp GIVAYF
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||
; ---- 8 bit signed A -> float in fac1
|
||||
%asm {{
|
||||
tay
|
||||
bpl +
|
||||
lda #$ff
|
||||
jmp GIVAYF
|
||||
+ lda #0
|
||||
jmp GIVAYF
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
jsr GETADR ; this uses the inverse order, Y/A
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
; treat as signed first
|
||||
jsr GIVAYFAY
|
||||
jsr SIGN
|
||||
bpl +
|
||||
; add 65536 to make it positive again
|
||||
lda #<_float_const_65536
|
||||
ldy #>_float_const_65536
|
||||
jmp FADD
|
||||
+ rts
|
||||
|
||||
_float_const_65536 .byte $91, $00, $00, $00, $00 ; float 65536.0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
; Play sounds from your PET speaker
|
||||
; Uses the VIA timer to be able to play them without blocking the program in a delay loop.
|
||||
; Notes lower than A3 cannot be played with this mechanism and need a custom CPU delay loop.
|
||||
;
|
||||
; inspiration: http://blog.tynemouthsoftware.co.uk/2022/05/pet-sounds.html
|
||||
;
|
||||
; Example:
|
||||
; petsnd.on()
|
||||
; petsnd.note(petsnd.C_4)
|
||||
; sys.wait(30)
|
||||
; petsnd.note(petsnd.E_4)
|
||||
; sys.wait(30)
|
||||
; petsnd.note(petsnd.A_4)
|
||||
; sys.wait(30)
|
||||
; petsnd.off()
|
||||
|
||||
petsnd {
|
||||
|
||||
%option ignore_unused
|
||||
|
||||
; octaves:
|
||||
const ubyte OCTAVES_456 = 1
|
||||
const ubyte OCTAVES_567 = 2
|
||||
const ubyte OCTAVES_678 = 3
|
||||
|
||||
; notes:
|
||||
const ubyte A_SHARP_3 = $FF ; A#3 = 233.08 Hz
|
||||
const ubyte B_3 = $FA ; B3 = 246.94 Hz
|
||||
const ubyte C_4 = $EE ; C4 = 261.63 Hz
|
||||
const ubyte C_SHARP_4 = $E0 ; C#4 = 277.18 Hz
|
||||
const ubyte D_4 = $D2 ; D4 = 293.66 Hz
|
||||
const ubyte D_SHARP_4 = $C7 ; D#4 = 311.13 Hz
|
||||
const ubyte E_4 = $BC ; E4 = 329.63 Hz
|
||||
const ubyte F_4 = $B1 ; F4 = 349.23 Hz
|
||||
const ubyte F_SHARP_4 = $A8 ; F#4 = 369.99 Hz
|
||||
const ubyte G_4 = $9E ; G4 = 392.00 Hz
|
||||
const ubyte G_SHARP_4 = $95 ; G#4 = 415.30 Hz
|
||||
const ubyte A_4 = $8C ; A4 = 440.00 Hz
|
||||
const ubyte A_SHARP_4 = $85 ; A#4 = 466.16 Hz
|
||||
const ubyte B_4 = $7D ; B4 = 493.78 Hz
|
||||
const ubyte C_5 = $76 ; C5 = 523.25 Hz
|
||||
const ubyte C_SHARP_5 = $6E ; C#5 = 554.37 Hz
|
||||
const ubyte D_5 = $68 ; D5 = 587.33 Hz
|
||||
const ubyte D_SHARP_5 = $63 ; D#5 = 622.25 Hz
|
||||
const ubyte E_5 = $5D ; E5 = 659.26 Hz
|
||||
const ubyte F_5 = $58 ; F5 = 698.46 Hz
|
||||
const ubyte F_SHARP_5 = $53 ; F#5 = 739.99 Hz
|
||||
const ubyte G_5 = $4E ; G5 = 783.99 Hz
|
||||
const ubyte G_SHARP_5 = $4A ; G#5 = 830.61 Hz
|
||||
const ubyte A_5 = $45 ; A5 = 880.00 Hz
|
||||
const ubyte A_SHARP_5 = $41 ; A#5 = 932.33 Hz
|
||||
const ubyte B_5 = $3D ; B5 = 987.77 Hz
|
||||
const ubyte C_6 = $39 ; C6 = 1046.50 Hz
|
||||
|
||||
sub on() {
|
||||
pet.via1acr = $10 ; "shift out free running at T2 rate"
|
||||
octaves(petsnd.OCTAVES_456)
|
||||
}
|
||||
|
||||
sub off() {
|
||||
pet.via1t2 = 0 ; clear note
|
||||
pet.via1acr = 0 ; stop sound.
|
||||
}
|
||||
|
||||
sub octaves(ubyte oct) {
|
||||
; set octave(s), choice from range 1 to 3. (1=octaves 4,5,6, 2=octaves 5,6,7, 3=octaves 6,7,8)
|
||||
; the VIA shift register value determines octave
|
||||
when oct {
|
||||
OCTAVES_456 -> pet.via1sr = %00001111
|
||||
OCTAVES_567 -> pet.via1sr = %00110011
|
||||
OCTAVES_678 -> pet.via1sr = %01010101
|
||||
else -> pet.via1sr = 0
|
||||
}
|
||||
}
|
||||
|
||||
sub note(ubyte n) {
|
||||
pet.via1t2 = n ; timer 2 rate (L)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,13 @@ cbm {
|
||||
&ubyte TIME_MID = $8e ; .. mid byte
|
||||
&ubyte TIME_LO = $8f ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
&ubyte STATUS = $96 ; kernal status variable for I/O
|
||||
&ubyte FNLEN = $D1 ; Length of filename
|
||||
&ubyte LFN = $D2 ; Current Logical File Number
|
||||
&ubyte SECADR = $D3 ; Secondary address
|
||||
&ubyte DEVNUM = $D4 ; Device number
|
||||
&ubyte CURS_X = $C6 ; Cursor column
|
||||
&ubyte CURS_Y = $D8 ; Cursor row
|
||||
&ubyte FNADR = $DA ; Pointer to file name
|
||||
|
||||
&uword CINV = $0090 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0092 ; BRK vector (in ram)
|
||||
@@ -28,13 +35,44 @@ extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; define an
|
||||
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an output channel
|
||||
extsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
|
||||
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
extsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
|
||||
extsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
|
||||
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A) also see STOP2
|
||||
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character also see GETIN2
|
||||
extsub $FFE7 = CLALL() clobbers(A,X) ; close all files
|
||||
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
|
||||
|
||||
extsub $F563 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; open a logical file
|
||||
extsub $F2E2 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; close a logical file
|
||||
|
||||
asmsub READST() -> ubyte @ A {
|
||||
; read io status
|
||||
%asm {{
|
||||
lda STATUS
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) {
|
||||
; set logical file parameters
|
||||
%asm {{
|
||||
sta LFN ; LFN
|
||||
stx DEVNUM ; Device address
|
||||
sty SECADR ; Secondary address
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETNAM(ubyte namelen @ A, str filename @ XY) {
|
||||
; set filename parameters
|
||||
%asm {{
|
||||
sta FNLEN
|
||||
stx FNADR
|
||||
sty FNADR+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
; -- just like STOP, but omits the special keys result value in A.
|
||||
; just for convenience because most of the times you're only interested in the stop pressed or not status.
|
||||
@@ -81,7 +119,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
asmsub SETTIML(long jiffies @R0R1) {
|
||||
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
@@ -91,7 +129,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
|
||||
asmsub RDTIML() clobbers(X) -> long @R0R1 {
|
||||
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
|
||||
%asm {{
|
||||
jsr RDTIM
|
||||
@@ -116,6 +154,54 @@ asmsub kbdbuf_clear() {
|
||||
|
||||
}
|
||||
|
||||
pet {
|
||||
const uword VIA1_BASE = $e840 ;VIA 6522
|
||||
&ubyte via1prb = VIA1_BASE + 0
|
||||
&ubyte via1pra = VIA1_BASE + 1
|
||||
&ubyte via1ddrb = VIA1_BASE + 2
|
||||
&ubyte via1ddra = VIA1_BASE + 3
|
||||
&uword via1t1 = VIA1_BASE + 4
|
||||
&ubyte via1t1l = VIA1_BASE + 4
|
||||
&ubyte via1t1h = VIA1_BASE + 5
|
||||
&uword via1t1lw = VIA1_BASE + 6
|
||||
&ubyte via1t1ll = VIA1_BASE + 6
|
||||
&ubyte via1t1lh = VIA1_BASE + 7
|
||||
&uword via1t2 = VIA1_BASE + 8
|
||||
&ubyte via1t2l = VIA1_BASE + 8
|
||||
&ubyte via1t2h = VIA1_BASE + 9
|
||||
&ubyte via1sr = VIA1_BASE + 10
|
||||
&ubyte via1acr = VIA1_BASE + 11
|
||||
&ubyte via1pcr = VIA1_BASE + 12
|
||||
&ubyte via1ifr = VIA1_BASE + 13
|
||||
&ubyte via1ier = VIA1_BASE + 14
|
||||
&ubyte via1ora = VIA1_BASE + 15
|
||||
|
||||
; these are not general kernal routines they're the full BASIC comands
|
||||
extsub $ff93 = concat() clobbers (A,X,Y)
|
||||
extsub $ff96 = dopen() clobbers (A,X,Y)
|
||||
extsub $ff99 = dclose() clobbers (A,X,Y)
|
||||
extsub $ff9c = record() clobbers (A,X,Y)
|
||||
extsub $ff9f = header() clobbers (A,X,Y)
|
||||
extsub $ffa2 = collect() clobbers (A,X,Y)
|
||||
extsub $ffa5 = backup() clobbers (A,X,Y)
|
||||
extsub $ffa8 = copy() clobbers (A,X,Y)
|
||||
extsub $ffab = append() clobbers (A,X,Y)
|
||||
extsub $ffae = dsave() clobbers (A,X,Y)
|
||||
extsub $ffb1 = dload() clobbers (A,X,Y)
|
||||
extsub $ffb4 = catalog() clobbers (A,X,Y)
|
||||
extsub $ffb7 = rename() clobbers (A,X,Y)
|
||||
extsub $ffba = scratch() clobbers (A,X,Y)
|
||||
extsub $ffc0 = open() clobbers (A,X,Y)
|
||||
extsub $ffc3 = close() clobbers (A,X,Y)
|
||||
extsub $ffd5 = load() clobbers (A,X,Y)
|
||||
extsub $ffd8 = save() clobbers (A,X,Y)
|
||||
extsub $ffdb = verify() clobbers (A,X,Y)
|
||||
extsub $ffde = sys() clobbers (A,X,Y)
|
||||
|
||||
}
|
||||
|
||||
%import shared_sys_functions
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@@ -186,196 +272,6 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
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 {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
dey
|
||||
beq +
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
+ lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
@@ -384,49 +280,6 @@ _no_msb_size
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
.section BSS
|
||||
save_SCRATCH_ZPB1 .byte ?
|
||||
save_SCRATCH_ZPREG .byte ?
|
||||
save_SCRATCH_ZPWORD1 .word ?
|
||||
save_SCRATCH_ZPWORD2 .word ?
|
||||
.send BSS
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
@@ -464,20 +317,6 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -532,7 +371,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushl(long value @R0R1_32) {
|
||||
inline asmsub pushl(long value @R0R1) {
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
pha
|
||||
@@ -545,7 +384,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
inline asmsub popl() -> long @R0R1 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
|
||||
@@ -60,6 +60,45 @@ sub uppercase() {
|
||||
txt.chrout(142)
|
||||
}
|
||||
|
||||
asmsub column(ubyte col @A) {
|
||||
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||
%asm {{
|
||||
sta $c6
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_column() -> ubyte @A {
|
||||
%asm {{
|
||||
lda $c6
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
ldy $c6
|
||||
tax
|
||||
jmp plot
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @A {
|
||||
%asm {{
|
||||
lda $d8
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_cursor() -> ubyte @X, ubyte @Y {
|
||||
%asm {{
|
||||
ldx $c6
|
||||
ldy $d8
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_left () clobbers(A, X, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
@@ -137,21 +176,21 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
bit $d5
|
||||
bvc +
|
||||
asl a
|
||||
+ tay
|
||||
lda _screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
clc
|
||||
adc _screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ pla
|
||||
ldy #0
|
||||
tay
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
|
||||
_screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
_screenrows .word cbm.Screen + range(0, 2000, 40)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
@@ -162,16 +201,16 @@ asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
bit $d5
|
||||
bvc +
|
||||
asl a
|
||||
+ tay
|
||||
lda setchr._screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda setchr._screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
clc
|
||||
adc setchr._screenrows,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy #0
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
}}
|
||||
@@ -181,25 +220,17 @@ sub setclr (ubyte col, ubyte row, ubyte color) {
|
||||
; --- dummy function on PET
|
||||
}
|
||||
|
||||
inline asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
; ---- dummy function on PET
|
||||
%asm {{
|
||||
lda #1
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
|
||||
; ---- set char at the given position on the screen. NOTE: charcolor is ignored on PET
|
||||
inline asmsub setcc (ubyte col @X, ubyte row @Y, ubyte character @A, ubyte charcolor @R0) {
|
||||
; ---- set char at the given position on the screen. NOTE: charcolor is ignored on PET so identical to setchr
|
||||
%asm {{
|
||||
lda row
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc col
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ lda character
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
jsr txt.setchr
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -208,7 +239,10 @@ asmsub plot (ubyte col @ Y, ubyte row @ X) {
|
||||
stx $d8
|
||||
txa
|
||||
asl a
|
||||
tax
|
||||
bit $d5
|
||||
bvc +
|
||||
asl a
|
||||
+ tax
|
||||
lda setchr._screenrows,x
|
||||
sta $c4
|
||||
lda setchr._screenrows+1,x
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
%import textio
|
||||
|
||||
petgfx {
|
||||
%option ignore_unused
|
||||
|
||||
; Plot at "petscii subpixel" x,y , which is twice the text screen resolution so 80 * 50 , instead of 40*25.
|
||||
; NOTE: assumes the screen is cleared with spaces beforehand.
|
||||
|
||||
sub hline(ubyte x, ubyte y, ubyte length) {
|
||||
; horizontal line
|
||||
repeat length {
|
||||
plot(x, y)
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
sub vline(ubyte x, ubyte y, ubyte length) {
|
||||
; vertical line
|
||||
repeat length {
|
||||
plot(x, y)
|
||||
y++
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(ubyte x, ubyte y) {
|
||||
ubyte subchar
|
||||
x >>= 1
|
||||
rol(subchar)
|
||||
y >>= 1
|
||||
if_cc
|
||||
subchar++
|
||||
else
|
||||
subchar = (subchar<<2) + 4
|
||||
|
||||
ubyte existing = cbm.Screen[y*(txt.DEFAULT_WIDTH as uword) + x]
|
||||
|
||||
; search the index of the current subpixels in the cell
|
||||
%asm {{
|
||||
ldy #0
|
||||
lda p8v_existing
|
||||
- cmp p8v_subpixels,y
|
||||
beq +
|
||||
iny
|
||||
cpy #16
|
||||
bne -
|
||||
+ sty p8v_existing
|
||||
}}
|
||||
; for cx16.r0H in 0 to 15 {
|
||||
; if subpixels[cx16.r0H] == existing {
|
||||
; existing = cx16.r0H
|
||||
; break
|
||||
; }
|
||||
; }
|
||||
|
||||
; update screen cell with new subpixel
|
||||
txt.setchr(x, y, subpixels[subchar | existing])
|
||||
|
||||
; 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
; -- #- -# ## -- #- -# ## -- #- -# ## -- #- -# ##
|
||||
; -- -- -- -- #- #- #- #- -# -# -# -# ## ## ## ##
|
||||
; 32, 126, 124, 226, 123, 97, 255, 236, 108, 127, 225, 251, 98, 252, 254, 160
|
||||
|
||||
ubyte[16] subpixels = [32, 126, 124, 226, 123, 97, 255, 236, 108, 127, 225, 251, 98, 252, 254, 160]
|
||||
}
|
||||
}
|
||||
@@ -323,34 +323,6 @@ _c1_zero lda (P8ZP_SCRATCH_W2),y
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
strncmp_mem .proc
|
||||
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||
; Compares up to maximum length specified in X.
|
||||
; Returns -1,0,1 in A, depending on the ordering. Clobbers X & Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
beq _c1_zero
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
beq _equal
|
||||
bmi _less
|
||||
lda #1
|
||||
rts
|
||||
_less lda #-1
|
||||
rts
|
||||
_equal dex
|
||||
bne +
|
||||
lda #0
|
||||
rts
|
||||
+ iny
|
||||
bne _loop
|
||||
_c1_zero lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #-1
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
|
||||
strlen .proc
|
||||
; -- returns the number of bytes in the string in AY, in Y. Clobbers A.
|
||||
@@ -605,6 +577,56 @@ long_xor_inplace .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
long_negate_inplace .proc
|
||||
; negate a signed long pointed to by AY in place
|
||||
; Uses two's complement: flip all bits then add 1
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
sty P8ZP_SCRATCH_PTR+1
|
||||
|
||||
ldy #$00
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
eor #$FF
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
eor #$FF
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
eor #$FF
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
eor #$FF
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
|
||||
; Add 1 to get two's complement (incrementing the 32-bit value)
|
||||
clc
|
||||
lda #$01
|
||||
ldy #$00
|
||||
adc (P8ZP_SCRATCH_PTR),y
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
bcc +
|
||||
iny
|
||||
lda #$00
|
||||
adc (P8ZP_SCRATCH_PTR),y
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
bcc +
|
||||
iny
|
||||
lda #$00
|
||||
adc (P8ZP_SCRATCH_PTR),y
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
bcc +
|
||||
iny
|
||||
lda #$00
|
||||
adc (P8ZP_SCRATCH_PTR),y
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
long_shiftleftX_inplace .proc
|
||||
; bit shift left X bits the long value pointed to by AY
|
||||
cpx #0
|
||||
@@ -661,30 +683,116 @@ long_shiftrightX_inplace .proc
|
||||
_end rts
|
||||
.pend
|
||||
|
||||
|
||||
compare_long_0_and_sign .proc
|
||||
; pointer to 32 bits signed long in P8ZP_SCRATCH_W1, carry Set=check NEGATIVE (<=0), carry Clear=check POSITIVE (>=0)
|
||||
; returns carry Set if comparison succeeded otherwise carry Clear
|
||||
ldy #0
|
||||
compare_long_gt_0 .proc
|
||||
; pointer to 32 bits signed long in P8ZP_SCRATCH_W1, carry Set: value>0, carry clear: value <=0
|
||||
ldy #3
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bmi _fail
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
beq _success
|
||||
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bcc _checkpositive
|
||||
bmi _success
|
||||
bpl _fail
|
||||
|
||||
_checkpositive bpl _success
|
||||
|
||||
beq _fail
|
||||
sec
|
||||
rts
|
||||
_fail clc
|
||||
rts
|
||||
.pend
|
||||
|
||||
compare_long_le_0 .proc
|
||||
; pointer to 32 bits signed long in P8ZP_SCRATCH_W1, carry Set: value<=0, carry clear: value >0
|
||||
ldy #3
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bmi _success
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
beq _success
|
||||
clc
|
||||
rts
|
||||
_success sec
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
copyfrompointer1topointer2_word .proc
|
||||
; copy word pointed to by P8ZP_SCRATCH_W1 to word pointed to by AY
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
copyfrompointer1topointer2_long .proc
|
||||
; copy long pointed to by P8ZP_SCRATCH_W1 to long pointed to by AY
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
swap_words .proc
|
||||
; -- swap word values pointed to by P8ZP_SCRATCH_W1 and AY
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
tax
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
txa
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
swap_longs .proc
|
||||
; -- swap long values pointed to by P8ZP_SCRATCH_W1 and AY. Clobbers R14 and R15 (temporary swap var)
|
||||
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #3
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta cx16.r14r15sl,y
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
bpl -
|
||||
ldy #3
|
||||
- lda cx16.r14r15sl,y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
@@ -235,6 +235,19 @@ io_error:
|
||||
goto diskio.lf_start_list.start_list_internal
|
||||
}
|
||||
|
||||
sub lf_start_list_having_prefix(str prefix) -> bool {
|
||||
; -- start an iterative directory contents listing for entries with given prefix.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
list_filename[0] = '$'
|
||||
list_filename[1] = ':'
|
||||
cx16.r0L = strings.copy(prefix, &list_filename+2)
|
||||
list_filename[cx16.r0L+2] = '*'
|
||||
list_filename[cx16.r0L+3] = 0
|
||||
cbm.SETNAM(cx16.r0L+3, list_filename)
|
||||
diskio.lf_start_list.pattern_ptr = 0
|
||||
goto diskio.lf_start_list.start_list_internal
|
||||
}
|
||||
|
||||
sub lf_next_entry() -> bool {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||
@@ -721,6 +734,7 @@ io_error:
|
||||
if cbm.READST()!=0
|
||||
cx16.r0 = 0
|
||||
}
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
@@ -229,7 +229,51 @@ _offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub iso2petscii(ubyte iso_char) -> ubyte {
|
||||
; --converts iso 8859-15 character to petscii character (lowercase)
|
||||
if iso_char & $7f <= $20
|
||||
return petscii:' ' ; whitspace
|
||||
if iso_char <= $3f
|
||||
return iso_char ; numbers and symbols
|
||||
if iso_char < $80
|
||||
return translate40to7F[iso_char-$40]
|
||||
return translateA0toFF[iso_char-$a0]
|
||||
|
||||
ubyte[$40] translate40to7F = [
|
||||
$40, $c1, $c2, $c3, $c4, $c5, $c6, $c7, $c8, $c9, $ca, $cb, $cc, $cd, $ce, $cf,
|
||||
$d0, $d1, $d2, $d3, $d4, $d5, $d6, $d7, $d8, $d9, $da, $5b, $3f, $5d, $5e, $e4,
|
||||
$27, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4a, $4b, $4c, $4d, $4e, $4f,
|
||||
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5a, $f3, $7d, $eb, $3f, $3f,
|
||||
]
|
||||
ubyte[$60] translateA0toFF = [
|
||||
$20, $21, $20, $5c, $c5, $d9, $3f, $3f, $3f, $c3, $3f, $28, $3f, $3f, $d2, $e3,
|
||||
$3f, $3f, $32, $33, $3f, $3f, $ff, $3f, $3f, $31, $3f, $3e, $3f, $3f, $d9, $3f,
|
||||
$c1, $c1, $c1, $c1, $c1, $c1, $c1, $c3, $c5, $c5, $c5, $c5, $c9, $c9, $c9, $c9,
|
||||
$c4, $ce, $cf, $cf, $cf, $cf, $cf, $58, $cf, $d5, $d5, $d5, $d5, $d9, $3f, $53,
|
||||
$41, $41, $41, $41, $41, $41, $41, $43, $45, $45, $45, $45, $49, $49, $49, $49,
|
||||
$4f, $4e, $4f, $4f, $4f, $4f, $4f, $3f, $4f, $55, $55, $55, $55, $59, $3f, $59,
|
||||
]
|
||||
}
|
||||
|
||||
asmsub iso2petscii_str(str iso_string @AY) {
|
||||
; -- convert iso string to petscii (lowercase) string
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
sty P8ZP_SCRATCH_REG
|
||||
jsr iso2petscii
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub print_bool(bool value) {
|
||||
|
||||
@@ -305,4 +305,82 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
|
||||
return v * (outputMax - outputMin) + outputMin
|
||||
}
|
||||
|
||||
asmsub internal_long_AY_to_FAC() {
|
||||
; Used by the compiler to cast a long to a a float in FAC
|
||||
; convert the long pointed to by AY into a float and store it in FAC
|
||||
%asm {{
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<floats_temp_var
|
||||
ldy #>floats_temp_var
|
||||
jsr internal_long_R1_to_float_AY
|
||||
lda #<floats_temp_var
|
||||
ldy #>floats_temp_var
|
||||
jmp MOVFM
|
||||
}}
|
||||
}
|
||||
|
||||
sub internal_long_R1_to_float_AY() {
|
||||
; Used by the compiler to cast a long to a a float variable:
|
||||
; make the float pointed to by AY equal to the long pointed to by R1 (as float)
|
||||
%asm {{
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
}}
|
||||
|
||||
long value = peekl(cx16.r1)
|
||||
^^ubyte @zp vptr = &value
|
||||
|
||||
if value==0 {
|
||||
sys.memset(cx16.r0, sizeof(float), 0)
|
||||
return
|
||||
}
|
||||
|
||||
; Determine the sign and work with the absolute value
|
||||
bool is_negative = value < 0
|
||||
if is_negative
|
||||
value = -value
|
||||
|
||||
; Calculate the exponent by finding the highest set bit
|
||||
ubyte highest_bit_pos = internal_get_vptr_highest_bit_pos(vptr)
|
||||
; For the normalized mantissa, the highest bit is an implicit 1 followed by the rest
|
||||
; So we shift the absolute value to get the proper mantissa representation
|
||||
value <<= 31 - highest_bit_pos
|
||||
|
||||
; Store the exponent
|
||||
cx16.r0[0] = highest_bit_pos + 129
|
||||
|
||||
; Store the mantissa with the sign bit
|
||||
cx16.r0[1] = vptr[3] & $7f
|
||||
if is_negative
|
||||
cx16.r0[1] |= $80
|
||||
cx16.r0[2] = vptr[2]
|
||||
cx16.r0[3] = vptr[1]
|
||||
cx16.r0[4] = vptr[0]
|
||||
return
|
||||
}
|
||||
|
||||
sub internal_get_vptr_highest_bit_pos(^^ubyte vptr) -> ubyte {
|
||||
; note: separate subroutine not nested in internal_long_R1_to_float_AY so that 64tass can optimize it out if not used
|
||||
if vptr[3]==0
|
||||
if vptr[2]==0
|
||||
if vptr[1]==0
|
||||
if vptr[0]==0 return 0
|
||||
else return highest_bit_in_byte(vptr[0])
|
||||
else return 8+highest_bit_in_byte(vptr[1])
|
||||
else return 16+highest_bit_in_byte(vptr[2])
|
||||
else return 24+highest_bit_in_byte(vptr[3])
|
||||
}
|
||||
|
||||
asmsub highest_bit_in_byte(ubyte value @A) -> ubyte @Y {
|
||||
; note: separate subroutine not nested in internal_get_vptr_highest_bit_pos so that 64tass can optimize it out if not used
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lsr a
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,52 @@ strings {
|
||||
; the string functions shared across compiler targets
|
||||
%option merge, no_symbol_prefixing, ignore_unused
|
||||
|
||||
sub split(str s, ^^uword parts, ubyte maxparts) -> ubyte {
|
||||
; -- split string into parts (splits on whitespace and other non-printable characters).
|
||||
; Pointers to each part are stored in the given array.
|
||||
; Returns number of parts found (up to the given maximum). Modifies the string in place!
|
||||
|
||||
if s==0 or s[0]==0
|
||||
return 0
|
||||
|
||||
sys.push(cx16.r0L)
|
||||
alias index = cx16.r0L
|
||||
ubyte numparts
|
||||
|
||||
index = numparts = 0
|
||||
|
||||
while numparts < maxparts {
|
||||
skipwhitespace()
|
||||
if s[index]!=0 {
|
||||
parts^^ = s+index
|
||||
numparts++
|
||||
parts++
|
||||
skipchars()
|
||||
if s[index]==0
|
||||
break
|
||||
if numparts < maxparts
|
||||
s[index] = 0
|
||||
index++
|
||||
}
|
||||
}
|
||||
if numparts>0 {
|
||||
parts--
|
||||
strings.strip(parts^^)
|
||||
}
|
||||
cx16.r0L = sys.pop()
|
||||
return numparts
|
||||
|
||||
sub skipwhitespace() {
|
||||
while s[index]!=0 and (strings.isspace(s[index]) or not strings.isprint(s[index]))
|
||||
index++
|
||||
}
|
||||
|
||||
sub skipchars() {
|
||||
while s[index]!=0 and not strings.isspace(s[index])
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
sub strip(str s) {
|
||||
; -- gets rid of whitespace and other non-visible characters at the edges of the string
|
||||
rstrip(s)
|
||||
@@ -116,4 +162,31 @@ strings {
|
||||
sys.clear_carry()
|
||||
return 255
|
||||
}
|
||||
|
||||
sub next_token(str source, str delimiters) -> str {
|
||||
; -- Tokenize the source string according to the list of delimiter characters. Like C's ``strtok`` function.
|
||||
if source == 0
|
||||
source = last_token_source
|
||||
else
|
||||
last_token_source = source
|
||||
|
||||
if last_token_source^^ == 0
|
||||
return 0
|
||||
|
||||
while last_token_source^^ != 0 {
|
||||
^^ubyte dptr = delimiters
|
||||
while dptr^^ != 0 {
|
||||
if last_token_source^^ == dptr^^ {
|
||||
last_token_source^^ = 0
|
||||
last_token_source += 1
|
||||
return source
|
||||
}
|
||||
dptr += 1
|
||||
}
|
||||
last_token_source += 1
|
||||
}
|
||||
return source
|
||||
}
|
||||
^^ubyte last_token_source
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
sys {
|
||||
; the sys functions shared across ALL compiler targets (including the custom defined ones)
|
||||
%option merge, no_symbol_prefixing, ignore_unused
|
||||
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
sta save_SCRATCH_PTR
|
||||
lda P8ZP_SCRATCH_PTR+1
|
||||
sta save_SCRATCH_PTR+1
|
||||
rts
|
||||
.section BSS
|
||||
save_SCRATCH_ZPB1 .byte ?
|
||||
save_SCRATCH_ZPREG .byte ?
|
||||
save_SCRATCH_ZPWORD1 .word ?
|
||||
save_SCRATCH_ZPWORD2 .word ?
|
||||
save_SCRATCH_PTR .word ?
|
||||
.send BSS
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda save_prog8_internals.save_SCRATCH_PTR
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda save_prog8_internals.save_SCRATCH_PTR+1
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
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 {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; If you are on the Commander X16 and have to copy overlapping memory regions, consider using
|
||||
; the cx16 specific kernal routine `memory_copy` (make sure kernal rom is banked in).
|
||||
; note: can't be inlined because is called from asm as well.
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
dey
|
||||
beq +
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
+ lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx P8ZP_SCRATCH_W1+1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
cmp (cx16.r1),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc cx16.r0+1
|
||||
inc cx16.r1+1
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (cx16.r0),y
|
||||
cmp (cx16.r1),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_W1
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -295,7 +295,7 @@ _max_too_small
|
||||
}
|
||||
|
||||
asmsub compare(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting.
|
||||
; Compares two strings for sorting, case-sensitively.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
@@ -308,16 +308,81 @@ _max_too_small
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ncompare(str string1 @R0, str string2 @AY, ubyte length @X) clobbers(X, Y) -> byte @A {
|
||||
; Compares two strings for sorting.
|
||||
asmsub compare_nocase(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-insensitively for PETSCII strings.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Only compares the strings from index 0 up to the length argument.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strncmp_mem
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
_loop
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
beq _c1_zero
|
||||
jsr lowerchar
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
jsr lowerchar
|
||||
cmp P8ZP_SCRATCH_REG
|
||||
beq _equal
|
||||
bpl _less
|
||||
lda #1
|
||||
rts
|
||||
_less
|
||||
lda #-1
|
||||
rts
|
||||
_equal
|
||||
iny
|
||||
bne _loop
|
||||
_c1_zero
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #-1
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub compare_nocase_iso(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-insensitively for ISO strings.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
_loop
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
beq _c1_zero
|
||||
jsr lowerchar_iso
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
jsr lowerchar_iso
|
||||
cmp P8ZP_SCRATCH_REG
|
||||
beq _equal
|
||||
bpl _less
|
||||
lda #1
|
||||
rts
|
||||
_less
|
||||
lda #-1
|
||||
rts
|
||||
_equal
|
||||
iny
|
||||
bne _loop
|
||||
_c1_zero
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #-1
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -344,6 +409,26 @@ _done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub lower_iso(str st @AY) -> ubyte @Y {
|
||||
; Lowercases the ISO string in-place. Returns length of the string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _done
|
||||
cmp #65
|
||||
bcc +
|
||||
cmp #91
|
||||
bcs +
|
||||
ora #%00100000
|
||||
+ sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
_done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub upper(str st @AY) -> ubyte @Y {
|
||||
; Uppercases the petscii string in-place. Returns length of the string.
|
||||
%asm {{
|
||||
@@ -364,7 +449,28 @@ _done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub upper_iso(str st @AY) -> ubyte @Y {
|
||||
; Uppercases the ISO string in-place. Returns length of the string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _done
|
||||
cmp #97
|
||||
bcc +
|
||||
cmp #123
|
||||
bcs +
|
||||
and #%11011111
|
||||
+ sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
_done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub lowerchar(ubyte character @A) -> ubyte @A {
|
||||
; to petscii lower case
|
||||
%asm {{
|
||||
and #$7f
|
||||
cmp #97
|
||||
@@ -376,7 +482,20 @@ _done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub lowerchar_iso(ubyte character @A) -> ubyte @A {
|
||||
; to ISO lowercase
|
||||
%asm{{
|
||||
cmp #65
|
||||
bcc +
|
||||
cmp #91
|
||||
bcs +
|
||||
ora #$20
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub upperchar(ubyte character @A) -> ubyte @A {
|
||||
; to petscii upper case
|
||||
%asm {{
|
||||
cmp #65
|
||||
bcc +
|
||||
@@ -387,8 +506,21 @@ _done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub upperchar_iso(ubyte character @A) -> ubyte @A {
|
||||
; to ISO uppercase
|
||||
%asm{{
|
||||
cmp #97
|
||||
bcc +
|
||||
cmp #121
|
||||
bcs +
|
||||
and #%11011111
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> bool @A {
|
||||
; This routine matches a string against a pattern and returns with the carry bit set if they match, or clear if they don't.
|
||||
; The matching is CASE-SENSITIVE. If you want to match case-insensitively, use pattern_match_nocase().
|
||||
; The two characters ? and * have a special meaning when they appear in the pattern. All other characters match themselves.
|
||||
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
|
||||
; * matches any string, including the empty string.
|
||||
@@ -441,6 +573,100 @@ reg cmp (strptr),y ; are both characters the same?
|
||||
bne next ; not yet, loop
|
||||
found rts ; success, return with c=1
|
||||
|
||||
star inx ; skip star in pattern
|
||||
modify_pattern2
|
||||
cmp $ffff,x ; string of stars equals one star MODIFIED
|
||||
beq star ; so skip them also
|
||||
stloop txa ; we first try to match with * = ""
|
||||
pha ; and grow it by 1 character every
|
||||
tya ; time we loop
|
||||
pha ; save x and y on stack
|
||||
jsr next ; recursive call
|
||||
pla ; restore x and y
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
bcs found ; we found a match, return with c=1
|
||||
iny ; no match yet, try to grow * string
|
||||
lda (strptr),y ; are we at the end of string?
|
||||
bne stloop ; not yet, add a character
|
||||
fail clc ; yes, no match found, return with c=0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub pattern_match_nocase(str string @AY, str lowercase_pattern @R0, bool iso @Pc) clobbers(Y) -> bool @A {
|
||||
; This routine matches a string against a pattern and returns with the carry bit set if they match, or clear if they don't.
|
||||
; The matching is CASE-INSENSITIVE. If you want a slightly faster, but case-sensitive matching, use pattern_match().
|
||||
; The two characters ? and * have a special meaning when they appear in the pattern. All other characters match themselves.
|
||||
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
|
||||
; * matches any string, including the empty string.
|
||||
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
|
||||
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Code taken from http://6502.org/source/strings/patmatch.htm
|
||||
;
|
||||
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||
; AY: A NUL-terminated, <255-length string
|
||||
; Carry : SET for ISO encoding, CLEAR for petscii encoding
|
||||
;
|
||||
; Output: A = 1 if the string matches the pattern, A = 0 if not.
|
||||
;
|
||||
; Notes: Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack.
|
||||
; Does not work in ROM due to self-modifying code.
|
||||
|
||||
%asm {{
|
||||
|
||||
strptr = P8ZP_SCRATCH_W1
|
||||
|
||||
sta strptr
|
||||
sty strptr+1
|
||||
lda cx16.r0
|
||||
sta modify_pattern1+1
|
||||
sta modify_pattern2+1
|
||||
lda cx16.r0+1
|
||||
sta modify_pattern1+2
|
||||
sta modify_pattern2+2
|
||||
|
||||
|
||||
bcs +
|
||||
lda #<lowerchar
|
||||
ldy #>lowerchar
|
||||
bcc ++
|
||||
+ lda #<lowerchar_iso
|
||||
ldy #>lowerchar_iso
|
||||
+ sta modify_lowerchar+1
|
||||
sty modify_lowerchar+2
|
||||
|
||||
jsr _match
|
||||
lda #0
|
||||
rol a
|
||||
rts
|
||||
|
||||
|
||||
_match
|
||||
ldx #$00 ; x is an index in the pattern
|
||||
ldy #$ff ; y is an index in the string
|
||||
modify_pattern1
|
||||
next lda $ffff,x ; look at next pattern character MODIFIED
|
||||
cmp #'*' ; is it a star?
|
||||
beq star ; yes, do the complicated stuff
|
||||
iny ; no, let's look at the string
|
||||
cmp #'?' ; is the pattern caracter a ques?
|
||||
bne reg ; no, it's a regular character
|
||||
lda (strptr),y ; yes, so it will match anything
|
||||
beq fail ; except the end of string
|
||||
reg
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda (strptr),y
|
||||
modify_lowerchar
|
||||
jsr $ffff ; modified
|
||||
cmp P8ZP_SCRATCH_B1 ; are both characters the same (lowercased)?
|
||||
bne fail ; no, so no match
|
||||
inx ; yes, keep checking
|
||||
cmp #0 ; are we at end of string?
|
||||
bne next ; not yet, loop
|
||||
found rts ; success, return with c=1
|
||||
|
||||
star inx ; skip star in pattern
|
||||
modify_pattern2
|
||||
cmp $ffff,x ; string of stars equals one star MODIFIED
|
||||
|
||||
@@ -441,4 +441,42 @@ math {
|
||||
cx16.r15 *= (outputMax-outputMin)
|
||||
return cx16.r15H + outputMin
|
||||
}
|
||||
|
||||
sub gcd(uword aa, uword bb) -> uword {
|
||||
; Calculate the Greatest Common Divisor of two 16-bit unsigned integers using the Binary GCD algorithm (Stein's algorithm).
|
||||
|
||||
; Handle the case where one number is zero
|
||||
if aa == 0 return bb
|
||||
if bb == 0 return aa
|
||||
|
||||
; Count the number of common factors of 2
|
||||
ubyte shift = 0
|
||||
|
||||
; Count common factors of 2 by shifting both numbers right until at least one is odd
|
||||
while ((aa | bb) & 1) == 0 {
|
||||
aa >>= 1
|
||||
bb >>= 1
|
||||
shift++
|
||||
}
|
||||
|
||||
; Remove remaining factors of 2 from a
|
||||
while (aa & 1) == 0
|
||||
aa >>= 1
|
||||
|
||||
; Now a is odd. Loop until b becomes zero
|
||||
while bb != 0 {
|
||||
; Remove remaining factors of 2 from b
|
||||
while (bb & 1) == 0
|
||||
bb >>= 1
|
||||
|
||||
; Make sure a <= b
|
||||
if aa > bb
|
||||
swap(aa, bb)
|
||||
|
||||
bb -= aa
|
||||
}
|
||||
|
||||
; Return the result multiplied by the common factors of 2
|
||||
return aa << shift
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ strings {
|
||||
for ix in 0 to slen-1 {
|
||||
target[ix] = source[ix+offset]
|
||||
}
|
||||
target[ix]=0
|
||||
target[slen]=0
|
||||
}
|
||||
|
||||
sub slice(str source, ubyte start, ubyte slen, str target) {
|
||||
@@ -50,7 +50,7 @@ strings {
|
||||
for ix in 0 to slen-1 {
|
||||
target[ix] = source[ix+start]
|
||||
}
|
||||
target[ix]=0
|
||||
target[slen]=0
|
||||
}
|
||||
|
||||
sub find(str st, ubyte character) -> ubyte, bool {
|
||||
@@ -145,7 +145,7 @@ strings {
|
||||
}
|
||||
|
||||
sub compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
; Compares two strings for sorting (case sensitive).
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
@@ -157,21 +157,21 @@ strings {
|
||||
}}
|
||||
}
|
||||
|
||||
sub ncompare(str st1, str st2, ubyte length) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
sub compare_nocase(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting (case insensitive).
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Only compares the strings from index 0 up to the length argument.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%ir {{
|
||||
loadm.w r99000,strings.ncompare.st1
|
||||
loadm.w r99001,strings.ncompare.st2
|
||||
loadm.b r99100,strings.ncompare.length
|
||||
syscall 58 (r99000.w, r99001.w, r99100.b) : r99100.b
|
||||
loadm.w r99000,strings.compare_nocase.st1
|
||||
loadm.w r99001,strings.compare_nocase.st2
|
||||
syscall 58 (r99000.w, r99001.w) : r99100.b
|
||||
returnr.b r99100
|
||||
}}
|
||||
}
|
||||
|
||||
sub lower(str st) -> ubyte {
|
||||
; Lowercases the petscii string in-place. Returns length of the string.
|
||||
; Lowercases the ISO string in-place. Returns length of the string.
|
||||
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
||||
; but regular text doesn't usually contain those characters anyway.)
|
||||
ubyte ix
|
||||
@@ -186,7 +186,7 @@ strings {
|
||||
}
|
||||
|
||||
sub upper(str st) -> ubyte {
|
||||
; Uppercases the petscii string in-place. Returns length of the string.
|
||||
; Uppercases the ISO string in-place. Returns length of the string.
|
||||
ubyte ix
|
||||
repeat {
|
||||
ubyte char=st[ix]
|
||||
@@ -210,6 +210,12 @@ strings {
|
||||
return char
|
||||
}
|
||||
|
||||
alias lowerchar_iso = strings.lowerchar
|
||||
alias upperchar_iso = strings.upperchar
|
||||
alias lower_iso = strings.lower
|
||||
alias upper_iso = strings.upper
|
||||
alias compare_nocase_iso = strings.compare_nocase
|
||||
|
||||
sub hash(str st) -> ubyte {
|
||||
; experimental 8 bit hashing function.
|
||||
; hash(-1)=179; hash(i) = ROL hash(i-1) XOR string[i]
|
||||
|
||||
@@ -266,6 +266,14 @@ sys {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
sub progstart() -> uword {
|
||||
return $1000 ; just a dummy value
|
||||
}
|
||||
|
||||
sub progend() -> uword {
|
||||
return $c000 ; just a dummy value
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
@@ -220,4 +220,38 @@ sub petscii2scr_str(str petscii_string) {
|
||||
}
|
||||
}
|
||||
|
||||
sub iso2petscii(ubyte iso_char) -> ubyte {
|
||||
; --converts iso 8859-15 character to petscii character (lowercase)
|
||||
if iso_char & $7f <= $20
|
||||
return petscii:' ' ; whitspace
|
||||
if iso_char <= $3f
|
||||
return iso_char ; numbers and symbols
|
||||
if iso_char < $80
|
||||
return translate40to7F[iso_char-$40]
|
||||
return translateA0toFF[iso_char-$a0]
|
||||
|
||||
ubyte[$40] translate40to7F = [
|
||||
$40, $c1, $c2, $c3, $c4, $c5, $c6, $c7, $c8, $c9, $ca, $cb, $cc, $cd, $ce, $cf,
|
||||
$d0, $d1, $d2, $d3, $d4, $d5, $d6, $d7, $d8, $d9, $da, $5b, $3f, $5d, $5e, $e4,
|
||||
$27, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4a, $4b, $4c, $4d, $4e, $4f,
|
||||
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5a, $f3, $7d, $eb, $3f, $3f,
|
||||
]
|
||||
ubyte[$60] translateA0toFF = [
|
||||
$20, $21, $20, $5c, $c5, $d9, $3f, $3f, $3f, $c3, $3f, $28, $3f, $3f, $d2, $e3,
|
||||
$3f, $3f, $32, $33, $3f, $3f, $ff, $3f, $3f, $31, $3f, $3e, $3f, $3f, $d9, $3f,
|
||||
$c1, $c1, $c1, $c1, $c1, $c1, $c1, $c3, $c5, $c5, $c5, $c5, $c9, $c9, $c9, $c9,
|
||||
$c4, $ce, $cf, $cf, $cf, $cf, $cf, $58, $cf, $d5, $d5, $d5, $d5, $d9, $3f, $53,
|
||||
$41, $41, $41, $41, $41, $41, $41, $43, $45, $45, $45, $45, $49, $49, $49, $49,
|
||||
$4f, $4e, $4f, $4f, $4f, $4f, $4f, $3f, $4f, $55, $55, $55, $55, $59, $3f, $59,
|
||||
]
|
||||
}
|
||||
|
||||
sub iso2petscii_str(str iso_string) {
|
||||
; -- convert iso string to petscii (lowercase) string
|
||||
cx16.r0 = iso_string
|
||||
while @(cx16.r0)!=0 {
|
||||
@(cx16.r0) = iso2petscii(@(cx16.r0))
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package prog8
|
||||
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.AstException
|
||||
import prog8.buildversion.VERSION
|
||||
import prog8.code.source.ImportFileSystem
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
import prog8.code.target.CompilationTargets
|
||||
import prog8.code.target.Cx16Target
|
||||
@@ -69,6 +69,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HIRAM bank to use, on other systems this value is ignored.")
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "run a .p8ir IR source file in the embedded VM")
|
||||
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
|
||||
val warnImplicitTypeCasts by cli.option(ArgType.Boolean, fullName = "warnimplicitcasts", description="show compiler warnings about implicit casts from a smaller to a larger type")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||
val version by cli.option(ArgType.Boolean, fullName = "version", description = "print compiler version and exit")
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").optional().multiple(999)
|
||||
@@ -189,6 +190,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
if(checkSource==true) false else dontOptimize != true,
|
||||
if(checkSource==true) false else dontWriteAssembly != true,
|
||||
warnSymbolShadowing == true,
|
||||
warnImplicitTypeCasts == true,
|
||||
quietAll == true,
|
||||
quietAll == true || quietAssembler == true,
|
||||
showTimings == true,
|
||||
@@ -212,6 +214,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
errors = ErrorReporter(txtcolors)
|
||||
)
|
||||
val compilationResult = compileProgram(compilerArgs)
|
||||
ImportFileSystem.clearCaches()
|
||||
|
||||
if(checkSource==true)
|
||||
println("No output produced.")
|
||||
@@ -273,6 +276,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
if(checkSource==true) false else dontOptimize != true,
|
||||
if(checkSource==true) false else dontWriteAssembly != true,
|
||||
warnSymbolShadowing == true,
|
||||
warnImplicitTypeCasts == true,
|
||||
quietAll == true,
|
||||
quietAll == true || quietAssembler == true,
|
||||
showTimings == true,
|
||||
@@ -374,7 +378,7 @@ fun runVm(irFilename: String, quiet: Boolean) {
|
||||
|
||||
|
||||
private fun scanLibraryFiles(dump: String?, searchPattern: String?) {
|
||||
val library_prefix = "/prog8lib"
|
||||
val libraryPrefix = "/prog8lib"
|
||||
|
||||
val dumpPath = if(dump!=null) pathFrom(dump) else null
|
||||
val pattern = searchPattern?.toRegex(RegexOption.IGNORE_CASE)
|
||||
@@ -386,10 +390,11 @@ private fun scanLibraryFiles(dump: String?, searchPattern: String?) {
|
||||
if(dumpPath!=null) {
|
||||
println("Dumping embedded library files into $dumpPath\n")
|
||||
dumpPath.createDirectories()
|
||||
val license = dumpPath / "${library_prefix.drop(1)}-$VERSION/LICENSE.txt"
|
||||
val license = dumpPath / "${libraryPrefix.drop(1)}-${prog8.buildversion.VERSION}/LICENSE.txt"
|
||||
license.parent.createDirectories()
|
||||
license.writeText("These library files belong to the Prog8 compiler project, see https://github.com/irmen/prog8/\n" +
|
||||
"They are licensed under the GNU GPL 3.0 software license, see https://www.gnu.org/licenses/gpl.html\n")
|
||||
println("Note: the exported library source files have the same software license as the compiler itself.\n")
|
||||
}
|
||||
|
||||
fun search(path: Path) {
|
||||
@@ -402,15 +407,15 @@ private fun scanLibraryFiles(dump: String?, searchPattern: String?) {
|
||||
}
|
||||
}
|
||||
if(hits.isNotEmpty()) {
|
||||
println("Found in Library file '${path.absolutePathString().drop(library_prefix.length+1)}':")
|
||||
println("Found in Library file '${path.absolutePathString().drop(libraryPrefix.length+1)}':")
|
||||
hits.forEach(::println)
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
fun dump(path: Path) {
|
||||
val targetfolder = dumpPath!! / (path.first().pathString + "-$VERSION")
|
||||
val target = targetfolder / path.pathString.drop(library_prefix.length+1)
|
||||
val targetfolder = dumpPath!! / (path.first().pathString + "-${prog8.buildversion.VERSION}")
|
||||
val target = targetfolder / path.pathString.drop(libraryPrefix.length+1)
|
||||
target.parent.createDirectories()
|
||||
target.writeBytes(path.readBytes())
|
||||
}
|
||||
@@ -418,7 +423,7 @@ private fun scanLibraryFiles(dump: String?, searchPattern: String?) {
|
||||
// Get the location of the current JAR
|
||||
val jarUrl = object {}.javaClass.protectionDomain.codeSource.location
|
||||
FileSystems.newFileSystem(URI.create("jar:$jarUrl"), emptyMap<String, String>()).use { fs ->
|
||||
val dir = fs.getPath(library_prefix)
|
||||
val dir = fs.getPath(libraryPrefix)
|
||||
Files.walk(dir).use { paths ->
|
||||
paths.filter { Files.isRegularFile(it) }.forEach { path ->
|
||||
if(pattern!=null)
|
||||
|
||||
@@ -7,7 +7,10 @@ import prog8.ast.SyntaxError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.BuiltinFunctions
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.isInteger
|
||||
import kotlin.math.*
|
||||
|
||||
private typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteral
|
||||
@@ -125,12 +128,8 @@ private fun builtinOffsetof(args: List<Expression>, position: Position, program:
|
||||
|
||||
val structname = identifier.dropLast(1)
|
||||
val fieldname = identifier.last()
|
||||
val struct = args[0].definingScope.lookup(structname) as? StructDecl
|
||||
if(struct==null)
|
||||
throw SyntaxError("cannot find struct '$structname'", args[0].position)
|
||||
val offset = struct.offsetof(fieldname, program.memsizer)
|
||||
if(offset==null)
|
||||
throw SyntaxError("no such field '${identifier.joinToString(".")}'", args[0].position)
|
||||
val struct = args[0].definingScope.lookup(structname) as? StructDecl ?: throw SyntaxError("cannot find struct '$structname'", args[0].position)
|
||||
val offset = struct.offsetof(fieldname, program.memsizer) ?: throw SyntaxError("no such field '${identifier.joinToString(".")}'", args[0].position)
|
||||
return NumericLiteral.optimalInteger(offset.toInt(), position)
|
||||
}
|
||||
|
||||
@@ -204,7 +203,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
if(args[0] is StringLiteral)
|
||||
return NumericLiteral.optimalInteger((args[0] as StringLiteral).value.length, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier", position)
|
||||
throw SyntaxError("len argument should be an identifier of type string or array", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl()
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
@@ -219,8 +218,10 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
val refLv = target.value as? StringLiteral ?: throw CannotEvaluateException("len", "stringsize unknown")
|
||||
NumericLiteral.optimalInteger(refLv.value.length, args[0].position)
|
||||
}
|
||||
target.datatype.isNumericOrBool -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||
else -> throw InternalCompilerException("weird datatype")
|
||||
target.datatype.isNumericOrBool -> throw SyntaxError("cannot use len on a numeric value. Perhaps you meant sizeof?", position)
|
||||
target.datatype.isPointerToByte -> throw SyntaxError("cannot use len on pointer to bytes. Perhaps you want to use strings.strlen() here to get the length of the string?", position)
|
||||
target.datatype.isPointer -> throw SyntaxError("cannot use len on pointer, only on strings and arrays", position)
|
||||
else -> throw CannotEvaluateException("len", "weird datatype")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ class CompilerArguments(val filepath: Path,
|
||||
val optimize: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
val warnSymbolShadowing: Boolean,
|
||||
val warnImplicitTypeCasts: Boolean,
|
||||
val quietAll: Boolean,
|
||||
val quietAssembler: Boolean,
|
||||
val showTimings: Boolean,
|
||||
@@ -108,6 +109,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
|
||||
with(compilationOptions) {
|
||||
warnSymbolShadowing = args.warnSymbolShadowing
|
||||
warnImplicitTypeCast = args.warnImplicitTypeCasts
|
||||
optimize = args.optimize
|
||||
asmQuiet = args.quietAssembler
|
||||
quiet = args.quietAll
|
||||
@@ -318,6 +320,9 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
||||
|
||||
override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? {
|
||||
if(funcName=="msw" && !args[0].inferType(program).isLong)
|
||||
return NumericLiteral.optimalInteger(0, position)
|
||||
|
||||
val func = BuiltinFunctions[funcName]
|
||||
if(func!=null) {
|
||||
val exprfunc = constEvaluatorsForBuiltinFuncs[funcName]
|
||||
@@ -572,12 +577,14 @@ private fun createAssemblyAndAssemble(program: PtProgram,
|
||||
lastGeneratedLabelSequenceNr: Int
|
||||
): Boolean {
|
||||
|
||||
val retainSSAforIR = true
|
||||
|
||||
val asmgen = if(compilerOptions.experimentalCodegen)
|
||||
prog8.codegen.experimental.ExperiCodeGen()
|
||||
prog8.codegen.experimental.ExperiCodeGen(retainSSAforIR)
|
||||
else if (compilerOptions.compTarget.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65C02))
|
||||
prog8.codegen.cpu6502.AsmGen6502(prefixSymbols = true, lastGeneratedLabelSequenceNr+1)
|
||||
else if (compilerOptions.compTarget.name == VMTarget.NAME)
|
||||
VmCodeGen()
|
||||
VmCodeGen(retainSSAforIR)
|
||||
else
|
||||
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}")
|
||||
|
||||
|
||||
@@ -25,8 +25,11 @@ internal class ErrorReporter(val colors: IConsoleColors): IErrorReporter {
|
||||
messages.add(CompilerMessage(MessageSeverity.INFO, msg, position))
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
if(suggestImport)
|
||||
err("undefined symbol: ${symbol.joinToString(".")} (maybe you forgot to import the module that defines ${symbol.first()}?)", position)
|
||||
else
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun report() {
|
||||
|
||||
@@ -107,10 +107,7 @@ class ModuleImporter(private val program: Program,
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
if(moduleName=="string")
|
||||
errors.err("the 'string' module is now named 'strings'", import.position)
|
||||
else
|
||||
errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
|
||||
errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
|
||||
return null
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C128Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.PETTarget
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.builtinFunctionReturnType
|
||||
import java.io.CharConversionException
|
||||
@@ -90,7 +89,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(ppExpr?.operator==".")
|
||||
return // identifiers will be checked over at the BinaryExpression itself
|
||||
}
|
||||
errors.undefined(identifier.nameInSource, identifier.position)
|
||||
errors.undefined(identifier.nameInSource, identifier.firstTarget(program.builtinFunctions)==null, identifier.position)
|
||||
}
|
||||
else {
|
||||
val target = stmt as? VarDecl
|
||||
@@ -140,10 +139,7 @@ internal class AstChecker(private val program: Program,
|
||||
val valueDt = actual.inferType(program)
|
||||
if(valueDt.isKnown) {
|
||||
if (expectedDt != valueDt.getOrUndef()) {
|
||||
if(valueDt.isBool && expectedDt.isUnsignedByte) {
|
||||
// if the return value is a bool and the return type is ubyte, allow this. But give a warning.
|
||||
errors.info("return type of the subroutine should probably be bool instead of ubyte", actual.position)
|
||||
} else if(expectedDt.isUnsignedWord && (valueDt.isIterable || valueDt.isPointer)) {
|
||||
if(expectedDt.isUnsignedWord && (valueDt.isIterable || valueDt.isPointer)) {
|
||||
// you can return a string or array or pointer when an uword (pointer) is returned
|
||||
} else if(valueDt issimpletype BaseDataType.UWORD && expectedDt.isString) {
|
||||
// you can return an uword pointer when the return type is a string
|
||||
@@ -768,7 +764,7 @@ internal class AstChecker(private val program: Program,
|
||||
val targetName = targetIdentifier.nameInSource
|
||||
when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
|
||||
null -> {
|
||||
errors.undefined(targetIdentifier.nameInSource, targetIdentifier.position)
|
||||
errors.undefined(targetIdentifier.nameInSource, targetIdentifier.firstTarget(program.builtinFunctions)==null, targetIdentifier.position)
|
||||
return
|
||||
}
|
||||
is StructFieldRef -> {
|
||||
@@ -804,9 +800,13 @@ internal class AstChecker(private val program: Program,
|
||||
// cannot check pointer deref for rom target, assume no there.
|
||||
if(idx.plainarrayvar!=null) {
|
||||
val decl = idx.plainarrayvar!!.targetVarDecl()!!
|
||||
if(decl.type!=VarDeclType.MEMORY && decl.zeropage!=ZeropageWish.REQUIRE_ZEROPAGE) {
|
||||
// memory mapped arrays are assumed to be in RAM. If they're not.... well, POOF
|
||||
errors.err("cannot assign to an array or string that is located in ROM (option romable is enabled)", assignTarget.position)
|
||||
val declvalueNumber = (decl.value as? NumericLiteral)?.number
|
||||
if(decl.type==VarDeclType.MEMORY && (declvalueNumber==null || declvalueNumber>255) ) {
|
||||
errors.err("cannot write to this memory mapped string or array (possibly located in ROM because option romable is enabled)", assignTarget.position)
|
||||
}
|
||||
if(decl.type==VarDeclType.VAR && decl.value!=null) {
|
||||
// arrays with initializer value is not placed in BSS so is no longer mutable
|
||||
errors.err("cannot write to a string or an array with initalization values (located in ROM because option romable is enabled)", assignTarget.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -890,8 +890,8 @@ internal class AstChecker(private val program: Program,
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && (decl.datatype.isFloat || decl.datatype.isFloatArray) && decl.type != VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
else if(compilerOptions.compTarget.name in arrayOf(PETTarget.NAME, C128Target.NAME) && decl.type != VarDeclType.CONST && (decl.datatype.isFloat || decl.datatype.isFloatArray)) {
|
||||
err("pet32 and c128 target do not support floating point numbers yet")
|
||||
else if(compilerOptions.compTarget.name == C128Target.NAME && decl.type != VarDeclType.CONST && (decl.datatype.isFloat || decl.datatype.isFloatArray)) {
|
||||
err("c128 target does not support floating point numbers yet")
|
||||
}
|
||||
|
||||
// ARRAY without size specifier MUST have an iterable initializer value
|
||||
@@ -969,7 +969,7 @@ internal class AstChecker(private val program: Program,
|
||||
valueerr($$"memory address must be valid integer 0..$ffff")
|
||||
}
|
||||
} else {
|
||||
valueerr("value of memory mapped variable can only be a constant, maybe use an address pointer type instead?")
|
||||
valueerr("value of memory mapped variable can only be a constant number, maybe use an address pointer type instead?")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1826,7 +1826,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(fourthArg !is IdentifierReference && fourthArg !is ArrayIndexedExpression)
|
||||
errors.err("this function can only act on an identifier or array element", fourthArg.position)
|
||||
} else {
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead })
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead && it !is PtrDereference })
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ internal fun IdentifierReference.checkFunctionOrLabelExists(program: Program, st
|
||||
return targetStatement
|
||||
}
|
||||
null -> {
|
||||
errors.undefined(this.nameInSource, this.position)
|
||||
errors.undefined(this.nameInSource, this.firstTarget(program.builtinFunctions)==null, this.position)
|
||||
}
|
||||
else -> errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position)
|
||||
}
|
||||
|
||||
@@ -233,56 +233,61 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
|
||||
val target = call.target.targetStatement(program.builtinFunctions)
|
||||
if(target==null) {
|
||||
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
when (val target = call.target.targetStatement(program.builtinFunctions)) {
|
||||
is Subroutine -> {
|
||||
val expectedNumberOfArgs: Int = target.parameters.size
|
||||
if(call.args.size != expectedNumberOfArgs) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
invalidNumberOfArgsError(pos, call.args.size, target.parameters.map { it.name })
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
val expectedNumberOfArgs: Int = func.parameters.size
|
||||
if(call.args.size != expectedNumberOfArgs) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
invalidNumberOfArgsError(pos, call.args.size, func.parameters.map {it.name })
|
||||
}
|
||||
if(target.name=="memory") {
|
||||
val name = call.args[0] as? StringLiteral
|
||||
if(name!=null) {
|
||||
val processed = name.value.map {
|
||||
if(it.isLetterOrDigit())
|
||||
it
|
||||
else
|
||||
'_'
|
||||
}.joinToString("")
|
||||
val textEncoding = (call as Node).definingModule.textEncoding
|
||||
call.args[0] = StringLiteral.create(processed, textEncoding, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
fun check(target: Statement?, aliasDepth: Int) {
|
||||
when (target) {
|
||||
is Subroutine -> {
|
||||
val expectedNumberOfArgs: Int = target.parameters.size
|
||||
if(call.args.size != expectedNumberOfArgs) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
invalidNumberOfArgsError(pos, call.args.size, target.parameters.map { it.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
is Label -> {
|
||||
if(call.args.isNotEmpty()) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("cannot use arguments when calling a label", pos)
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
val expectedNumberOfArgs: Int = func.parameters.size
|
||||
if(call.args.size != expectedNumberOfArgs) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
invalidNumberOfArgsError(pos, call.args.size, func.parameters.map {it.name })
|
||||
}
|
||||
if(target.name=="memory") {
|
||||
val name = call.args[0] as? StringLiteral
|
||||
if(name!=null) {
|
||||
val processed = name.value.map {
|
||||
if(it.isLetterOrDigit())
|
||||
it
|
||||
else
|
||||
'_'
|
||||
}.joinToString("")
|
||||
val textEncoding = (call as Node).definingModule.textEncoding
|
||||
call.args[0] = StringLiteral.create(processed, textEncoding, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Label -> {
|
||||
if(call.args.isNotEmpty()) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("cannot use arguments when calling a label", pos)
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(target.type!=VarDeclType.VAR || !target.datatype.isUnsignedWord)
|
||||
errors.err("wrong address variable datatype, expected uword", call.target.position)
|
||||
}
|
||||
is Alias -> {
|
||||
if(aliasDepth>1000) {
|
||||
errors.err("circular alias", target.position)
|
||||
} else {
|
||||
val actualtarget = target.target.targetStatement(program.builtinFunctions)
|
||||
check(actualtarget, aliasDepth + 1)
|
||||
}
|
||||
}
|
||||
is StructDecl, is StructFieldRef -> {}
|
||||
null -> {} // symbol error is given elsewhere
|
||||
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(target.type!=VarDeclType.VAR || !target.datatype.isUnsignedWord)
|
||||
errors.err("wrong address variable datatype, expected uword", call.target.position)
|
||||
}
|
||||
is Alias, is StructDecl, is StructFieldRef -> {}
|
||||
null -> {}
|
||||
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
|
||||
}
|
||||
|
||||
check(call.target.targetStatement(program.builtinFunctions), 0)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
|
||||
|
||||
class AstPreprocessor(val program: Program,
|
||||
@@ -14,11 +13,13 @@ class AstPreprocessor(val program: Program,
|
||||
val options: CompilationOptions) : AstWalker() {
|
||||
|
||||
override fun before(program: Program): Iterable<IAstModification> {
|
||||
if(options.compTarget.name==C64Target.NAME) {
|
||||
if(options.zeropage==ZeropageType.KERNALSAFE || options.zeropage==ZeropageType.FULL) {
|
||||
// there is enough space in the zero page to put the cx16 virtual registers there.
|
||||
// unfortunately, can't be the same address as CommanderX16.
|
||||
relocateCx16VirtualRegisters(program, 0x0004u)
|
||||
if(options.zeropage==ZeropageType.KERNALSAFE || options.zeropage==ZeropageType.FULL) {
|
||||
// there may be enough space in the zero page to put the cx16 virtual registers there.
|
||||
// unfortunately, can't be the same address as CommanderX16.
|
||||
val reg0 = options.compTarget.zeropage.allocatedVariables["cx16.r0"]
|
||||
if(reg0!=null) {
|
||||
// cx16.r0 is in zeropage, relocate
|
||||
relocateCx16VirtualRegisters(program, reg0.address)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -30,20 +31,43 @@ class AstPreprocessor(val program: Program,
|
||||
.filterIsInstance<VarDecl>()
|
||||
.associateBy { it.name }
|
||||
|
||||
fun getRegValueChecked(key: String): VarDecl = memVars[key] ?: throw NoSuchElementException("no definition found for 'cx16.$key' in syslib.p8")
|
||||
|
||||
for (regnum in 0u..15u) {
|
||||
val rX = memVars.getValue("r$regnum")
|
||||
val rXL = memVars.getValue("r${regnum}L")
|
||||
val rXH = memVars.getValue("r${regnum}H")
|
||||
val rXs = memVars.getValue("r${regnum}s")
|
||||
val rXsL = memVars.getValue("r${regnum}sL")
|
||||
val rXsH = memVars.getValue("r${regnum}sH")
|
||||
val rX = getRegValueChecked("r$regnum")
|
||||
val rXL = getRegValueChecked("r${regnum}L")
|
||||
val rXH = getRegValueChecked("r${regnum}H")
|
||||
val rXs = getRegValueChecked("r${regnum}s")
|
||||
val rXsL = getRegValueChecked("r${regnum}sL")
|
||||
val rXsH = getRegValueChecked("r${regnum}sH")
|
||||
val rXbL = getRegValueChecked("r${regnum}bL")
|
||||
val rXbH = getRegValueChecked("r${regnum}bH")
|
||||
setAddress(rX, baseAddress + 2u * regnum)
|
||||
setAddress(rXL, baseAddress + 2u * regnum)
|
||||
setAddress(rXH, baseAddress + 2u * regnum + 1u)
|
||||
setAddress(rXs, baseAddress + 2u * regnum)
|
||||
setAddress(rXsL, baseAddress + 2u * regnum)
|
||||
setAddress(rXsH, baseAddress + 2u * regnum + 1u)
|
||||
setAddress(rXbL, baseAddress + 2u * regnum)
|
||||
setAddress(rXbH, baseAddress + 2u * regnum + 1u)
|
||||
}
|
||||
|
||||
val r0r1sl = getRegValueChecked("r0r1sl")
|
||||
val r2r3sl = getRegValueChecked("r2r3sl")
|
||||
val r4r5sl = getRegValueChecked("r4r5sl")
|
||||
val r6r7sl = getRegValueChecked("r6r7sl")
|
||||
val r8r9sl = getRegValueChecked("r8r9sl")
|
||||
val r10r11sl = getRegValueChecked("r10r11sl")
|
||||
val r12r13sl = getRegValueChecked("r12r13sl")
|
||||
val r14r15sl = getRegValueChecked("r14r15sl")
|
||||
setAddress(r0r1sl, baseAddress + 0u)
|
||||
setAddress(r2r3sl, baseAddress + 4u)
|
||||
setAddress(r4r5sl, baseAddress + 8u)
|
||||
setAddress(r6r7sl, baseAddress + 12u)
|
||||
setAddress(r8r9sl, baseAddress + 16u)
|
||||
setAddress(r10r11sl, baseAddress + 20u)
|
||||
setAddress(r12r13sl, baseAddress + 24u)
|
||||
setAddress(r14r15sl, baseAddress + 28u)
|
||||
}
|
||||
|
||||
private fun setAddress(vardecl: VarDecl, address: UInt) {
|
||||
|
||||
@@ -195,12 +195,14 @@ _after:
|
||||
before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position)
|
||||
|
||||
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource==listOf("peek")) {
|
||||
val outerFunc = functionCall.target.nameInSource
|
||||
|
||||
if(outerFunc==listOf("peek")) {
|
||||
// peek(a) is synonymous with @(a)
|
||||
val memread = DirectMemoryRead(functionCall.args.single(), position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
|
||||
}
|
||||
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||
if(outerFunc==listOf("poke") && parent !is Assignment) {
|
||||
// poke(a, v) is synonymous with @(a) = v
|
||||
val tgt = AssignTarget(
|
||||
null,
|
||||
@@ -213,6 +215,36 @@ _after:
|
||||
val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||
}
|
||||
|
||||
if(outerFunc==listOf("pokew") || outerFunc==listOf("pokel") || outerFunc==listOf("pokef")) {
|
||||
val innercall = functionCall.args[1] as? IFunctionCall
|
||||
val innerFunc = innercall?.target?.nameInSource
|
||||
val peekname = "peek" + outerFunc[0].substring(4)
|
||||
if(innerFunc==listOf(peekname)) {
|
||||
val targetAddress = functionCall.args[0]
|
||||
val sourceAddress = innercall.args[0]
|
||||
val targetDt = targetAddress.inferType(program).getOrUndef().sub
|
||||
if(targetDt!=null) {
|
||||
val copy = when {
|
||||
targetDt.isLong ->
|
||||
FunctionCallStatement(
|
||||
IdentifierReference(listOf("prog8_lib_copylong"), position),
|
||||
mutableListOf(sourceAddress, targetAddress), false, position
|
||||
)
|
||||
targetDt.isFloat ->
|
||||
FunctionCallStatement(
|
||||
IdentifierReference(listOf("prog8_lib_copyfloat"), position),
|
||||
mutableListOf(sourceAddress, targetAddress), false, position
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
|
||||
if(copy!=null)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, copy, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -249,6 +281,8 @@ _after:
|
||||
val indexExpr = arrayIndexedExpression.indexer.indexExpr
|
||||
val arrayVar = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
|
||||
if(arrayVar!=null && (arrayVar.datatype.isUnsignedWord || arrayVar.datatype.isPointer)) {
|
||||
if(parent is IFunctionCall && parent.target.nameInSource.singleOrNull() in InplaceModifyingBuiltinFunctions)
|
||||
return noModifications
|
||||
val wordIndex = TypecastExpression(indexExpr, DataType.UWORD, true, indexExpr.position)
|
||||
val address = BinaryExpression(
|
||||
arrayIndexedExpression.plainarrayvar!!.copy(),
|
||||
@@ -322,9 +356,10 @@ _after:
|
||||
}
|
||||
} else if(arrayVar!=null && (arrayVar.type==VarDeclType.MEMORY || arrayVar.datatype.isString || arrayVar.datatype.isPointer || arrayVar.datatype.isArray)) {
|
||||
return noModifications
|
||||
} else if(arrayVar!=null) {
|
||||
// it could be a pointer dereference instead of a simple array variable
|
||||
TODO("deref[word] rewrite ???? ${arrayIndexedExpression.position}")
|
||||
}
|
||||
// else if(arrayVar!=null) {
|
||||
// // it could be a pointer dereference instead of a simple array variable
|
||||
// TODO("deref[word] rewrite ???? ${arrayIndexedExpression.position}")
|
||||
// val dt = arrayIndexedExpression.plainarrayvar!!.traverseDerefChainForDt(null)
|
||||
// if(dt.isUnsignedWord) {
|
||||
// // ptr.field[index] --> @(ptr.field + index)
|
||||
@@ -338,7 +373,7 @@ _after:
|
||||
// return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -918,6 +953,54 @@ _after:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// consolidates simple assignment followed by augmented assignment into a single combined assignment.
|
||||
// a = simplevalue | a += expression --> a = simplevalue + expression (for all augmented operators)
|
||||
// NOTE: this is NOT SAFE to do when dealing with pointer arithmetic because the semantics of the combined expression is different!
|
||||
if(!assignment.isAugmentable && assignment.value.isSimple) {
|
||||
val next = assignment.nextSibling() as? Assignment
|
||||
if(next?.isAugmentable==true && next.value is BinaryExpression && next.target.isSameAs(assignment.target, program)) {
|
||||
val combined = next.value as BinaryExpression
|
||||
if(combined.left isSameAs assignment.target) {
|
||||
if(!assignment.target.inferType(program).isPointer && (assignment.value.inferType(program).isPointer || combined.right.inferType(program).isPointer)) {
|
||||
// avoid pointer arithmetic: a = (simplevalue as uword) + expression
|
||||
combined.left = TypecastExpression(assignment.value, DataType.UWORD, true, combined.left.position)
|
||||
} else {
|
||||
// no pointer arithmetic done, just transform it into a = simplevalue + expression
|
||||
combined.left = assignment.value
|
||||
}
|
||||
assignment.value = combined
|
||||
combined.linkParents(assignment)
|
||||
return listOf(IAstModification.Remove(next, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.target.pointerDereference!=null && assignment.value is PtrDereference) {
|
||||
val targetPtr = assignment.target.pointerDereference!!
|
||||
val sourcePtr = assignment.value as PtrDereference
|
||||
val targetDt = targetPtr.inferType(program)
|
||||
if (targetDt == sourcePtr.inferType(program)) {
|
||||
val sourceAddress = IdentifierReference(sourcePtr.chain, assignment.position)
|
||||
val targetAddress = IdentifierReference(targetPtr.chain, assignment.position)
|
||||
|
||||
val copy = when {
|
||||
targetDt.isLong -> FunctionCallStatement(
|
||||
IdentifierReference(listOf("prog8_lib_copylong"), assignment.position),
|
||||
mutableListOf(sourceAddress, targetAddress), false, assignment.position
|
||||
)
|
||||
targetDt.isFloat -> FunctionCallStatement(
|
||||
IdentifierReference(listOf("prog8_lib_copyfloat"), assignment.position),
|
||||
mutableListOf(sourceAddress, targetAddress), false, assignment.position
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
|
||||
if(copy!=null)
|
||||
return listOf(IAstModification.ReplaceNode(assignment, copy, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -947,9 +1030,9 @@ _after:
|
||||
if (numValues>0 && struct.fields.size != numValues) {
|
||||
if (numValues < struct.fields.size) {
|
||||
val missing = struct.fields.drop(numValues).joinToString(", ") { it.second }
|
||||
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}, missing: $missing", array.position)
|
||||
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got $numValues, missing: $missing", array.position)
|
||||
} else
|
||||
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}", array.position)
|
||||
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got $numValues", array.position)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
+28
-2
@@ -162,8 +162,34 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
|
||||
// don't replace an identifier in an Alias or when the alias points to another alias (that will be resolved first elsewhere)
|
||||
if(target is Alias && parent !is Alias) {
|
||||
if(target.target.targetStatement() !is Alias)
|
||||
return listOf(IAstModification.ReplaceNode(identifier, target.target.copy(position = identifier.position), parent))
|
||||
val targetStatement = target.target.targetStatement()
|
||||
if (targetStatement !is Alias) {
|
||||
val replacement: IdentifierReference =
|
||||
when (targetStatement) {
|
||||
is StructFieldRef -> {
|
||||
// replace with target struct field reference
|
||||
target.target.copy(position = identifier.position)
|
||||
}
|
||||
is VarDecl -> {
|
||||
val scoped = if (targetStatement.names.isNotEmpty()) {
|
||||
// points to a vardecl of multiple names in 1 statement
|
||||
(targetStatement.parent as INamedStatement).scopedName + target.target.nameInSource.last()
|
||||
} else {
|
||||
(targetStatement as INamedStatement).scopedName
|
||||
}
|
||||
require(scoped.last() != "<multiple>")
|
||||
IdentifierReference(scoped, identifier.position)
|
||||
}
|
||||
else -> {
|
||||
// replace with scoped identifier
|
||||
val scoped = (targetStatement as INamedStatement).scopedName
|
||||
require(scoped.last() != "<multiple>")
|
||||
IdentifierReference(scoped, identifier.position)
|
||||
}
|
||||
}
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(identifier, replacement, parent))
|
||||
}
|
||||
}
|
||||
|
||||
// experimental code to be able to alias blocks too:
|
||||
|
||||
@@ -845,13 +845,13 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
}
|
||||
} else {
|
||||
if(structSize==1) {
|
||||
// ptr + right, just keep it as it is
|
||||
val plus = PtBinaryExpression("+", DataType.UWORD, expr.position)
|
||||
// ptr +/- right, just keep it as it is
|
||||
val plus = PtBinaryExpression(operator, DataType.UWORD, expr.position)
|
||||
plus.add(transformExpression(expr.left))
|
||||
plus.add(transformExpression(expr.right))
|
||||
return plus
|
||||
} else {
|
||||
// ptr + right * structSize
|
||||
// ptr +/- right * structSize
|
||||
val offset: PtExpression
|
||||
if(structSize in powersOfTwoInt) {
|
||||
// don't multiply simply shift
|
||||
@@ -886,13 +886,13 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
}
|
||||
} else {
|
||||
if(structSize==1) {
|
||||
// ptr + left, just keep it as it is
|
||||
val plus = PtBinaryExpression("+", DataType.UWORD, expr.position)
|
||||
// ptr +/- left, just keep it as it is
|
||||
val plus = PtBinaryExpression(operator, DataType.UWORD, expr.position)
|
||||
plus.add(transformExpression(expr.left))
|
||||
plus.add(transformExpression(expr.right))
|
||||
return plus
|
||||
} else {
|
||||
// ptr + left * structSize
|
||||
// ptr +/- left * structSize
|
||||
val offset: PtExpression
|
||||
if(structSize in powersOfTwoInt) {
|
||||
// don't multiply simply shift
|
||||
@@ -935,14 +935,14 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
expr.add(high)
|
||||
} else {
|
||||
val low = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position)
|
||||
val lowFloat = PtTypeCast(DataType.FLOAT, range.from.position)
|
||||
val lowFloat = PtTypeCast(DataType.FLOAT, true, range.from.position)
|
||||
lowFloat.add(transformExpression(range.from))
|
||||
low.add(lowFloat)
|
||||
low.add(x1)
|
||||
expr.add(low)
|
||||
val high = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position)
|
||||
high.add(x2)
|
||||
val highFLoat = PtTypeCast(DataType.FLOAT, range.to.position)
|
||||
val highFLoat = PtTypeCast(DataType.FLOAT, true, range.to.position)
|
||||
highFLoat.add(transformExpression(range.to))
|
||||
high.add(highFLoat)
|
||||
expr.add(high)
|
||||
@@ -1031,7 +1031,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
PtString(srcString.value, srcString.encoding, srcString.position)
|
||||
|
||||
private fun transform(srcCast: TypecastExpression): PtTypeCast {
|
||||
val cast = PtTypeCast(srcCast.type, srcCast.position)
|
||||
val cast = PtTypeCast(srcCast.type, srcCast.implicit, srcCast.position)
|
||||
cast.add(transformExpression(srcCast.expression))
|
||||
require(cast.type!=cast.value.type) {
|
||||
"bogus typecast shouldn't occur at ${srcCast.position}" }
|
||||
@@ -1040,7 +1040,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
|
||||
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||
return if (SourceCode.isLibraryResource(filename)) {
|
||||
return com.github.michaelbull.result.runCatching {
|
||||
com.github.michaelbull.result.runCatching {
|
||||
val physFilename = SourceCode.withoutPrefix(filename)
|
||||
ImportFileSystem.getResource("/prog8lib/$physFilename").text
|
||||
}.mapError { NoSuchFileException(File(filename)) }
|
||||
|
||||
@@ -116,7 +116,7 @@ private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map<PtSub
|
||||
val scope = defer.parent
|
||||
val idx = scope.children.indexOf(defer)
|
||||
val enableDefer = PtAugmentedAssign("|=", defer.position)
|
||||
val target = PtAssignTarget(true, defer.position)
|
||||
val target = PtAssignTarget(false, defer.position)
|
||||
target.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, defer.position))
|
||||
enableDefer.add(target)
|
||||
// enable the bit for this defer (beginning with high bits so the handler can simply shift right to check them in reverse order)
|
||||
@@ -168,17 +168,12 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
|
||||
node.parent.add(idx, invokedefer)
|
||||
}
|
||||
|
||||
fun notComplex(value: PtExpression): Boolean = when(value) {
|
||||
is PtAddressOf -> value.arrayIndexExpr == null || notComplex(value.arrayIndexExpr!!)
|
||||
is PtBuiltinFunctionCall -> {
|
||||
when (value.name) {
|
||||
in arrayOf("msb", "lsb", "msw", "lsw", "mkword", "mklong", "mklong2", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> value.args.all { notComplex(it) }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
fun isSimple(value: PtExpression): Boolean = when(value) {
|
||||
is PtAddressOf -> value.arrayIndexExpr == null || isSimple(value.arrayIndexExpr!!)
|
||||
is PtBuiltinFunctionCall -> value.isSimple()
|
||||
is PtMemoryByte -> value.address is PtNumber
|
||||
is PtPrefix -> notComplex(value.value)
|
||||
is PtTypeCast -> notComplex(value.value)
|
||||
is PtPrefix -> isSimple(value.value)
|
||||
is PtTypeCast -> isSimple(value.value)
|
||||
is PtArray,
|
||||
is PtIrRegister,
|
||||
is PtBool,
|
||||
@@ -196,7 +191,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
|
||||
|
||||
// return exits
|
||||
for(ret in returnsToAugment) {
|
||||
if(ret.children.isEmpty() || ret.children.all { notComplex(it as PtExpression) }) {
|
||||
if(ret.children.isEmpty() || ret.children.all { isSimple(it as PtExpression) }) {
|
||||
invokedeferbefore(ret)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ internal class StatementReorderer(
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
// - consolidates multiple consecutive additions , subtractions, multiplications.
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%memtop", "%option", "%encoding")
|
||||
|
||||
@@ -302,6 +303,35 @@ internal class StatementReorderer(
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val isIO = try {
|
||||
assignment.target.isIOAddress(options.compTarget)
|
||||
} catch (_: FatalAstException) {
|
||||
false
|
||||
}
|
||||
if(!isIO) {
|
||||
// consolidate subsequent adds / subs / muls into a single statement
|
||||
if (assignment.isAugmentable) {
|
||||
val operator = (assignment.value as? BinaryExpression)?.operator
|
||||
if(operator!=null && operator in "+-*") {
|
||||
val allAssignments = gatherAllConsecutiveOptimiazableAddSubOrMuls(assignment, operator)
|
||||
if(allAssignments.size>1) {
|
||||
val total: Double = when(operator) {
|
||||
"+", "-" -> allAssignments.sumOf { (it.value as BinaryExpression).right.constValue(program)!!.number }
|
||||
"*" -> allAssignments.fold(1.0) { acc, element -> (element.value as BinaryExpression).right.constValue(program)!!.number * acc }
|
||||
else -> throw IllegalStateException("unexpected operator $operator")
|
||||
}
|
||||
val type = assignment.value.inferType(program).getOrUndef()
|
||||
val totalNumber = NumericLiteral(type.base, total, assignment.position)
|
||||
(assignment.value as BinaryExpression).right = totalNumber
|
||||
totalNumber.parent = assignment.value
|
||||
return allAssignments.drop(1).map {
|
||||
IAstModification.Remove(it, parent as IStatementContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
@@ -317,10 +347,27 @@ internal class StatementReorderer(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun gatherAllConsecutiveOptimiazableAddSubOrMuls(assignment: Assignment, operator: String): List<Assignment> {
|
||||
val result = mutableListOf<Assignment>()
|
||||
val initialTarget = assignment.target
|
||||
var assign: Assignment? = assignment
|
||||
while(assign!=null) {
|
||||
if(assign.target.isSameAs(initialTarget, program)) {
|
||||
val expr = assign.value as? BinaryExpression
|
||||
if (expr?.operator==operator && expr.right.constValue(program)!=null) {
|
||||
result.add(assign)
|
||||
assign = assign.nextSibling() as? Assignment
|
||||
}
|
||||
else break
|
||||
}
|
||||
else break
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(whileLoop.body.isEmpty()) {
|
||||
// convert while C {} to do {} until not C (because codegen of the latter is more optimized)
|
||||
|
||||
@@ -278,7 +278,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
// exception for @(...) = -byte (direct memory write) it's just convenient to be able to write negative numbers too (like pokew also allows)
|
||||
if(assignment.target.memoryAddress!=null) {
|
||||
if(valuetype.isByteOrBool && targettype.isUnsignedByte) {
|
||||
require(cvalue.number.toInt() in -128..127)
|
||||
require(cvalue.number.toInt() in -128..127) { "signed byte out of range at ${cvalue.position}" }
|
||||
val asUByte = NumericLiteral(BaseDataType.UBYTE, cvalue.number.toInt().toUByte().toDouble(), assignment.value.position)
|
||||
return listOf(IAstModification.ReplaceNode(assignment.value, asUByte, assignment))
|
||||
}
|
||||
@@ -323,6 +323,8 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
}
|
||||
else -> paramsPossibleDatatypes = emptyList()
|
||||
}
|
||||
if(paramsPossibleDatatypes.size != call.args.size)
|
||||
return emptyList() // proper error message for arg count mismatch will be given elsewhere
|
||||
return fixupArgumentList(paramsPossibleDatatypes, call.args, call as Node)
|
||||
}
|
||||
|
||||
@@ -664,7 +666,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
}
|
||||
|
||||
private fun fixupArgumentList(possibleDatatypes: List<List<DataType>>, args: List<Expression>, parent: Node): List<IAstModification> {
|
||||
require(possibleDatatypes.size==args.size)
|
||||
require(possibleDatatypes.size==args.size) { "argument count mismatch at ${parent.position}"}
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
possibleDatatypes.zip(args).forEach {
|
||||
val possibleTargetDt = it.first.first()
|
||||
|
||||
@@ -40,7 +40,7 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
|
||||
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 {
|
||||
val typecast = PtTypeCast(pushTypecast, true, value.position).also {
|
||||
it.add(value)
|
||||
}
|
||||
pushCall.add(typecast)
|
||||
@@ -48,7 +48,7 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
|
||||
pushCall.add(value)
|
||||
}
|
||||
val popCall = if(popTypecast!=null) {
|
||||
PtTypeCast(popTypecast, value.position).also {
|
||||
PtTypeCast(popTypecast, true, value.position).also {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
if(valueDt.largerSizeThan(decl.datatype)) {
|
||||
val constValue = decl.value!!.constValue(program)!!
|
||||
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
|
||||
} else {
|
||||
} else if(decl.datatype.largerSizeThan(valueDt)) {
|
||||
// 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 ||
|
||||
@@ -63,8 +63,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
}
|
||||
}
|
||||
VarDeclType.MEMORY -> if(!valueType.isWords && !valueType.isBytes)
|
||||
throw FatalAstException("value type for a memory var should be word or byte (address)")
|
||||
VarDeclType.MEMORY -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,12 +169,19 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
}
|
||||
|
||||
if(typecast.type.isWord && typecast.expression.inferType(program).isPointer) {
|
||||
// pointers can be assigned to untyped pointer (uword) without a cast, but not if it's part of an expression!
|
||||
if(parent !is Expression)
|
||||
if(typecast.type.isWord && parent is Assignment && typecast.expression.inferType(program).isPointer) {
|
||||
// a cast to uword can be removed if the assignment target type is uword (because any pointer can be assigned to uword)
|
||||
if(parent.target.inferType(program).isUnsignedWord)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
|
||||
// remove typecasts of arguments to builtin function like swap()
|
||||
if(parent is IFunctionCall) {
|
||||
if(parent.target.nameInSource.singleOrNull() in InplaceModifyingBuiltinFunctions) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -339,6 +345,63 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
val replacement = BinaryExpression(expr.right, newOperator, expr.left, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
|
||||
}
|
||||
|
||||
|
||||
// optimize boolean constant comparisons
|
||||
val leftDt = expr.left.inferType(program).getOrUndef()
|
||||
val rightDt = expr.right.inferType(program).getOrUndef()
|
||||
if(expr.operator=="==") {
|
||||
if(rightDt.isBool && leftDt.isBool) {
|
||||
val rightConstBool = rightConstVal?.asBooleanValue
|
||||
if(rightConstBool==true) {
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
}
|
||||
if (rightConstVal?.number == 1.0) {
|
||||
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rightConstVal?.number == 0.0) {
|
||||
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expr.operator=="!=") {
|
||||
if(rightDt.isBool && leftDt.isBool) {
|
||||
val rightConstBool = rightConstVal?.asBooleanValue
|
||||
if(rightConstBool==false) {
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
}
|
||||
if (rightConstVal?.number == 1.0) {
|
||||
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rightConstVal?.number == 0.0) {
|
||||
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -125,8 +125,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
val target = call.target.targetStatement(program.builtinFunctions)
|
||||
if (target is Subroutine) {
|
||||
val consideredParamTypes: List<DataType> = target.parameters.map { it.type }
|
||||
if(argtypes.size != consideredParamTypes.size)
|
||||
return Pair("invalid number of arguments", call.position)
|
||||
require(argtypes.size == consideredParamTypes.size)
|
||||
val mismatch = argtypes.zip(consideredParamTypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch]
|
||||
@@ -174,8 +173,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
else if (target is BuiltinFunctionPlaceholder) {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
val consideredParamTypes = func.parameters.map { it.possibleDatatypes }
|
||||
if(argtypes.size != consideredParamTypes.size)
|
||||
return Pair("invalid number of arguments", call.position)
|
||||
require(argtypes.size==consideredParamTypes.size)
|
||||
argtypes.zip(consideredParamTypes).forEachIndexed { index, pair ->
|
||||
val anyCompatible = pair.second.any {
|
||||
if(it.isArray)
|
||||
|
||||
@@ -38,8 +38,8 @@ class TestCallgraph: FunSpec({
|
||||
val result = compileText(C64Target(), false, sourcecode, outputDir)!!
|
||||
val graph = CallGraph(result.compilerAst)
|
||||
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
graph.imports.size shouldBe 2
|
||||
graph.importedBy.size shouldBe 2
|
||||
val toplevelModule = result.compilerAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
importedModule.name shouldBe "conv"
|
||||
@@ -79,8 +79,8 @@ class TestCallgraph: FunSpec({
|
||||
val result = compileText(C64Target(), false, sourcecode, outputDir)!!
|
||||
val graph = CallGraph(result.compilerAst)
|
||||
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
graph.imports.size shouldBe 2
|
||||
graph.importedBy.size shouldBe 2
|
||||
val toplevelModule = result.compilerAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
importedModule.name shouldBe "conv"
|
||||
|
||||
@@ -34,6 +34,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
optimize,
|
||||
writeAssembly = true,
|
||||
warnSymbolShadowing = false,
|
||||
warnImplicitTypeCasts= false,
|
||||
quietAll = true,
|
||||
quietAssembler = true,
|
||||
showTimings = false,
|
||||
@@ -94,6 +95,7 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
"cube3d-sprites",
|
||||
"fileselector",
|
||||
"library/main",
|
||||
"petswirl",
|
||||
"plasma",
|
||||
"rasterbars",
|
||||
"simplemultiplexer",
|
||||
@@ -204,6 +206,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
"balls",
|
||||
"boingball",
|
||||
"cube3d",
|
||||
"cube3d-float",
|
||||
"cube3d-gfx",
|
||||
|
||||
@@ -26,6 +26,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
warnSymbolShadowing = false,
|
||||
warnImplicitTypeCasts = false,
|
||||
quietAll = true,
|
||||
quietAssembler = true,
|
||||
showTimings = false,
|
||||
|
||||
@@ -45,6 +45,7 @@ main {
|
||||
INTERNED_STRINGS_MODULENAME,
|
||||
"textio",
|
||||
"syslib",
|
||||
"shared_sys_functions",
|
||||
"conv",
|
||||
"shared_cbm_textio_functions",
|
||||
"floats",
|
||||
@@ -106,7 +107,7 @@ main {
|
||||
listOf(
|
||||
INTERNED_STRINGS_MODULENAME,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "shared_cbm_textio_functions", "floats", "shared_floats_functions", "prog8_math", "prog8_lib"
|
||||
"textio", "syslib", "shared_sys_functions", "conv", "shared_cbm_textio_functions", "floats", "shared_floats_functions", "prog8_math", "prog8_lib"
|
||||
)
|
||||
}
|
||||
options.floats shouldBe true
|
||||
@@ -209,6 +210,28 @@ sub printit(str arg) {
|
||||
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("merge works with charset encoding") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0L = KEY_0
|
||||
cx16.r1L = KEY_9
|
||||
when cx16.r0L {
|
||||
KEY_0 to KEY_9 -> {}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
%option merge
|
||||
|
||||
const ubyte KEY_0 = 'A'
|
||||
const ubyte KEY_9 = 'B'
|
||||
}"""
|
||||
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("merge override existing subroutine") {
|
||||
val src="""
|
||||
%import textio
|
||||
|
||||
@@ -19,12 +19,13 @@ class TestLaunchEmu: FunSpec({
|
||||
<ASMSYMBOLS>
|
||||
</ASMSYMBOLS>
|
||||
|
||||
<VARIABLESNOINIT>
|
||||
</VARIABLESNOINIT>
|
||||
<VARIABLESNOINITDIRTY>
|
||||
</VARIABLESNOINITDIRTY>
|
||||
<VARIABLESWITHINIT>
|
||||
</VARIABLESWITHINIT>
|
||||
<VARS>
|
||||
<NOINITCLEAN>
|
||||
</NOINITCLEAN>
|
||||
<NOINITDIRTY>
|
||||
</NOINITDIRTY>
|
||||
<INIT>
|
||||
</INIT>
|
||||
<STRUCTINSTANCESNOINIT>
|
||||
</STRUCTINSTANCESNOINIT>
|
||||
<STRUCTINSTANCES>
|
||||
@@ -33,11 +34,12 @@ class TestLaunchEmu: FunSpec({
|
||||
<CONSTANTS>
|
||||
</CONSTANTS>
|
||||
|
||||
<MEMORYMAPPEDVARIABLES>
|
||||
</MEMORYMAPPEDVARIABLES>
|
||||
<MEMORYMAPPED>
|
||||
</MEMORYMAPPED>
|
||||
|
||||
<MEMORYSLABS>
|
||||
</MEMORYSLABS>
|
||||
</VARS>
|
||||
|
||||
<INITGLOBALS>
|
||||
</INITGLOBALS>
|
||||
|
||||
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user