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 | ||
|
|
4030a1b9e5 | ||
|
|
0217d05c68 | ||
|
|
8553f3c9f1 | ||
|
|
d157d03ea0 | ||
|
|
1799b5f00c | ||
|
|
2ddb5e11b7 | ||
|
|
3aa8b9fd12 | ||
|
|
51300196e8 | ||
|
|
f7b2f19cba | ||
|
|
014a82a1ee | ||
|
|
745cf3d958 | ||
|
|
c4aa681d58 | ||
|
|
feb0774bbd | ||
|
|
2ccfb2ac90 | ||
|
|
2191c4b7d8 | ||
|
|
39c1cf0c3c | ||
|
|
c693be0dd6 | ||
|
|
0f433055a7 | ||
|
|
76c95ba6fa | ||
|
|
bd272a8567 | ||
|
|
b38fff76e9 | ||
|
|
07b7639ff2 | ||
|
|
6871561c09 | ||
|
|
77a71138ad | ||
|
|
165f94bc93 | ||
|
|
b75090f928 | ||
|
|
62b0c82f93 | ||
|
|
b1ef863c7f | ||
|
|
e78345410f | ||
|
|
8b86f97aaa | ||
|
|
93135774e6 | ||
|
|
a64f27c6b0 | ||
|
|
69ef63c96d | ||
|
|
df1a2a1611 | ||
|
|
d19a3af9ed | ||
|
|
352c11ad9f | ||
|
|
9504711fc7 | ||
|
|
590feda903 | ||
|
|
6e7e2922bf | ||
|
|
01df1f0083 | ||
|
|
397299bd1d | ||
|
|
c275aacd38 | ||
|
|
6a6e18773e | ||
|
|
99e037489b | ||
|
|
fb5290e17b | ||
|
|
818774ab84 | ||
|
|
d667312acc | ||
|
|
64d0cd87a8 | ||
|
|
2fffbdde89 | ||
|
|
dc8ea31c49 | ||
|
|
f4ead66e91 | ||
|
|
314e7f5691 | ||
|
|
da31465b7f | ||
|
|
397a907088 | ||
|
|
50c6962e1f | ||
|
|
b7d1fb1342 | ||
|
|
afb458a7da | ||
|
|
da3c7f267f | ||
|
|
b7ba7c50b1 | ||
|
|
bdbfe7048b |
No files matched your search
@@ -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" />
|
||||
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -2,15 +2,28 @@
|
||||
|
||||
Several benchmarks of https://thred.github.io/c-bench-64/
|
||||
have been ported to equivalent Prog8 code and the benchmark results have been penciled in in the graphs below.
|
||||
Because they use the CIA timer for measuring the duration of the runs, the code only works on a C64.
|
||||
|
||||
Maybe one day I'll try to integrate the prog8 data properly but their benchmark site is comparing C compilers, which Prog8 clearly is not.
|
||||
|
||||
However conclusions so far (note: these are micro benchmarks so interpret the results as you will!)
|
||||
|
||||
* Prog8 program size is consistently the smallest by a fair bit.
|
||||
* Prog8 program size is consistently one of the smallest.
|
||||
* Prog8 execution speed places it more or less in the middle of the stack.
|
||||
|
||||
Measured with Prog8 V12.0 .
|
||||
Benchmarks ran on a PAL C64 emulated through VICE.
|
||||
|
||||
textual results:
|
||||
|
||||
| benchmark | size | time |
|
||||
|-----------|------|----------|
|
||||
| crc8 | 1568 | 2.0 sec |
|
||||
| crc16 | 1608 | 2.5 sec |
|
||||
| crc32 | 1812 | 4.2 sec |
|
||||
| pow | 2152 | 11.7 sec |
|
||||
| sieve | 1772 | 22.1 sec |
|
||||
| sieve_bit | 1918 | 40.1 sec |
|
||||
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
%import textio
|
||||
|
||||
cia {
|
||||
ubyte freq
|
||||
const ubyte CNT = 200
|
||||
|
||||
sub calibrate() {
|
||||
txt.print("calibrating frequency: ")
|
||||
tod_init(0)
|
||||
txt.print_ub(tod_freq())
|
||||
txt.print(" hz\n")
|
||||
tod_reset()
|
||||
}
|
||||
|
||||
sub print_time() {
|
||||
uword t = tod_get10()
|
||||
txt.print("(cia) time: ")
|
||||
txt.print_uw(t / 10)
|
||||
txt.chrout('.')
|
||||
txt.print_uw(t % 10)
|
||||
txt.print(" sec.\n")
|
||||
}
|
||||
|
||||
sub tod_reset() {
|
||||
; set the tod to 0
|
||||
c64.CIA2TODHR = 0
|
||||
c64.CIA2TODMIN = 0
|
||||
c64.CIA2TODSEC = 0
|
||||
c64.CIA2TOD10 = 0
|
||||
}
|
||||
|
||||
sub tod_get10() -> uword {
|
||||
ubyte h, m, s, t
|
||||
uword time
|
||||
h = c64.CIA2TODHR
|
||||
m = c64.CIA2TODMIN
|
||||
s = c64.CIA2TODSEC
|
||||
t = c64.CIA2TOD10
|
||||
time = t
|
||||
time += (s & $0f) * (10 as uword)
|
||||
time += (s >> 4) * (100 as uword)
|
||||
time += (m & $0f) * 600
|
||||
time += (m >> 4) * 6000
|
||||
return time
|
||||
}
|
||||
|
||||
sub tod_init(ubyte f) {
|
||||
if (f == 0)
|
||||
freq = tod_detect_freq()
|
||||
else
|
||||
freq = f
|
||||
|
||||
if (freq == 50)
|
||||
c64.CIA2CRA |= $80
|
||||
else
|
||||
c64.CIA2CRA &= $7f
|
||||
}
|
||||
|
||||
sub tod_freq() -> ubyte {
|
||||
return freq
|
||||
}
|
||||
|
||||
sub tod_detect_freq() -> ubyte {
|
||||
uword cbl
|
||||
|
||||
c64.CIA2CRB = $40 ; stop timer
|
||||
c64.CIA2CRA = $80 ; stop timer
|
||||
|
||||
; set ta to overflow every 10000 count (~= 10ms)
|
||||
c64.CIA2TAL = $10
|
||||
c64.CIA2TAH = $27
|
||||
c64.CIA2TBL = CNT
|
||||
c64.CIA2TBH = 0
|
||||
|
||||
tod_reset()
|
||||
|
||||
c64.CIA2CRB = $41 ; input from tim1 overflow, continuous, start timer
|
||||
c64.CIA2CRA = $81 ; start timer, continuous tod 50HZ
|
||||
|
||||
|
||||
while c64.CIA2TODSEC == 0 {
|
||||
; wait for tod to count 1s
|
||||
}
|
||||
|
||||
; cal=CIA2.ta_lo;
|
||||
; cah=CIA2.ta_hi;
|
||||
cbl = c64.CIA2TBL
|
||||
; cbh=CIA2.tb_hi;
|
||||
|
||||
; printf("count2 = %d %d %d %d\n",cah, cal, cbh, cbl);
|
||||
; printf("elapsed ~= %d0ms\n",CNT-cbl);
|
||||
|
||||
if CNT - cbl > 90
|
||||
return 50
|
||||
else
|
||||
return 60
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
; note: Prog8 has a complete CRC16 routine in its library: math.crc16
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
cbm.SETTIM(0,0,0)
|
||||
test.benchmark()
|
||||
txt.print_f(floats.time() / 60)
|
||||
txt.print(" seconds\n")
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
@@ -40,12 +39,13 @@ test {
|
||||
|
||||
if crc_result == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]")
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_uwhex(EXPECTED, true)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
; note: Prog8 has a complete CRC32 routine in its library: math.crc32
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
cbm.SETTIM(0,0,0)
|
||||
test.benchmark()
|
||||
txt.print_f(floats.time() / 60)
|
||||
txt.print(" seconds\n")
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
@@ -40,12 +39,13 @@ test {
|
||||
|
||||
if crc_result == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]")
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_ulhex(EXPECTED, true)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
cbm.SETTIM(0,0,0)
|
||||
test.benchmark()
|
||||
txt.print_f(floats.time() / 60)
|
||||
txt.print(" seconds\n")
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
@@ -36,12 +35,13 @@ test {
|
||||
|
||||
if crc_result == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]")
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_ubhex(EXPECTED, true)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
cbm.SETTIM(0,0,0)
|
||||
test.benchmark()
|
||||
txt.print_f(floats.time() / 60)
|
||||
txt.print(" seconds\n")
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 65 KiB |
@@ -1,16 +1,15 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
cbm.SETTIM(0,0,0)
|
||||
test.benchmark()
|
||||
txt.print_f(floats.time() / 60)
|
||||
txt.print(" seconds\n")
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
@@ -46,12 +45,13 @@ test {
|
||||
|
||||
if prime_count == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]")
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_uw(EXPECTED)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
cbm.SETTIM(0,0,0)
|
||||
test.benchmark()
|
||||
txt.print_f(floats.time() / 60)
|
||||
txt.print(" seconds\n")
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
@@ -46,12 +45,13 @@ test {
|
||||
|
||||
if prime_count == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]")
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_uw(EXPECTED)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ adpcm {
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
adpcm.init(0,0)
|
||||
uword @requirezp nibbles_ptr = $a000 ; for benchmark purposes, the exact nibbles don't really matter, so we just take the basic ROM as input
|
||||
uword @requirezp nibbles_ptr = $0800 ; for benchmark purposes, the exact nibbles don't really matter, so we just take this benchmark program itself as input
|
||||
repeat 252/2 {
|
||||
unroll 2 {
|
||||
ubyte @zp nibble = @(nibbles_ptr)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,8 +14,11 @@ fun Number.toHex(): String {
|
||||
// larger -> "$12345678"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toLong()
|
||||
if(integer<0)
|
||||
if(integer<0) {
|
||||
if(integer==-2147483648L)
|
||||
return "$80000000" // the exception to the rule, because -$80000000 is not a valid hex number
|
||||
return '-' + abs(integer).toHex()
|
||||
}
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
|
||||
@@ -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
|
||||
@@ -962,14 +960,14 @@ class AsmGen6502Internal (
|
||||
}
|
||||
} else throw AssemblyError("weird casted type")
|
||||
} else {
|
||||
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - use simple expressions and temporary variables for now")
|
||||
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
|
||||
}
|
||||
} else if(target.kind == TargetStorageKind.REGISTER) {
|
||||
assignExpressionToRegister(value, target.register!!, true)
|
||||
} else if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
assignExpressionToVariable(value, target.asmVarname, target.datatype)
|
||||
} else {
|
||||
TODO("assign long expression $value to a target ${target.kind} at ${target.position} - use simple expressions and temporary variables for now")
|
||||
TODO("assign long expression $value to a target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
|
||||
}
|
||||
}
|
||||
target.datatype.isFloat -> {
|
||||
@@ -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
|
||||
@@ -2003,9 +2002,9 @@ $repeatLabel""")
|
||||
lda #<$leftName
|
||||
ldy #>$leftName""")
|
||||
if(lessOrEquals)
|
||||
out("jsr floats.vars_lesseq_f")
|
||||
out(" jsr floats.vars_lesseq_f")
|
||||
else
|
||||
out("jsr floats.vars_less_f")
|
||||
out(" jsr floats.vars_less_f")
|
||||
}
|
||||
fun lessf(expr: PtExpression, rightName: String) {
|
||||
assignExpressionToRegister(expr, RegisterOrPair.FAC1, 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) {
|
||||
@@ -2454,12 +2482,13 @@ $repeatLabel""")
|
||||
}
|
||||
|
||||
internal fun pushLongRegisters(startreg: RegisterOrPair, numberOfLongs: Int) {
|
||||
// may clobber P8ZP_SCRATCH_B1, X, Y.
|
||||
if(numberOfLongs==0) return
|
||||
|
||||
val startregName = startreg.startregname()
|
||||
if(numberOfLongs==1) {
|
||||
out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tax
|
||||
lda cx16.$startregName
|
||||
pha
|
||||
lda cx16.$startregName+1
|
||||
@@ -2468,12 +2497,12 @@ $repeatLabel""")
|
||||
pha
|
||||
lda cx16.$startregName+3
|
||||
pha
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
txa""")
|
||||
return
|
||||
}
|
||||
val numbytes = numberOfLongs * options.compTarget.memorySize(BaseDataType.LONG)
|
||||
out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tax
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
- lda cx16.$startregName,y
|
||||
@@ -2482,7 +2511,7 @@ $repeatLabel""")
|
||||
cpy #$numbytes
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
txa""")
|
||||
}
|
||||
|
||||
internal fun popLongRegisters(startreg: RegisterOrPair, numberOfLongs: Int) {
|
||||
@@ -2491,7 +2520,7 @@ $repeatLabel""")
|
||||
val startregName = startreg.startregname()
|
||||
if(numberOfLongs==1) {
|
||||
out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tax
|
||||
pla
|
||||
sta cx16.$startregName+3
|
||||
pla
|
||||
@@ -2500,12 +2529,12 @@ $repeatLabel""")
|
||||
sta cx16.$startregName+1
|
||||
pla
|
||||
sta cx16.$startregName
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
txa""")
|
||||
return
|
||||
}
|
||||
val numbytes = numberOfLongs * options.compTarget.memorySize(BaseDataType.LONG)
|
||||
out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tax
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ldy #$numbytes-1
|
||||
- pla
|
||||
@@ -2513,7 +2542,7 @@ $repeatLabel""")
|
||||
dey
|
||||
bpl -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
txa""")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2531,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()
|
||||
@@ -544,7 +544,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
val third = lines[2].value
|
||||
|
||||
if(!haslabel(second)) {
|
||||
if ((" jmp" in first || "\tjmp" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
if ((" jmp" in first || "\tjmp" in first || " bra" in first || "\tbra" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
else if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
@@ -279,7 +280,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
|
||||
// pass argument via a register parameter
|
||||
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
throw AssemblyError("argument type incompatible ${value.type} vs param ${parameter.value.type} at ${parameter.value.position}")
|
||||
|
||||
val paramRegister: RegisterOrStatusflag = when(sub) {
|
||||
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
|
||||
@@ -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 {
|
||||
@@ -362,12 +363,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
if(argType isAssignableTo paramType)
|
||||
return true
|
||||
if(argType.isBool && paramType.isBool)
|
||||
return true
|
||||
|
||||
// signed/unsigned args vs parameters are considered compatible
|
||||
if(argType.isByte && paramType.isByte)
|
||||
return true
|
||||
if(argType.isWord && paramType.isWord)
|
||||
return true
|
||||
// only signed longs and floats are available so these were already covered
|
||||
// if(argType.isLong && paramType.isLong)
|
||||
// return true
|
||||
// if(argType.isFloat && paramType.isFloat)
|
||||
// return true
|
||||
|
||||
// we have a special rule for some types.
|
||||
// strings are assignable to UWORD, for example, and vice versa
|
||||
|
||||
@@ -942,7 +942,7 @@ _jump jmp (${target.asmLabel})
|
||||
asmgen.out(" lda $varname+1")
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
require(!long)
|
||||
require(!long) {"addresses must still be words not longs"}
|
||||
if(value.isFromArrayElement) {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" cpy #0")
|
||||
@@ -955,9 +955,14 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
require(!long)
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" cpy #0")
|
||||
if(long) {
|
||||
// note: clobbers R14+R15
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out(" lda cx16.r14+3")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" cpy #0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1268,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) {
|
||||
@@ -1294,7 +1299,11 @@ _jump jmp (${target.asmLabel})
|
||||
|
||||
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(lessEquals) {
|
||||
TODO("long <= 0 ${value.position}")
|
||||
checkLongLeZeroIntoCarry(value)
|
||||
if (jump != null)
|
||||
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bcc", stmt)
|
||||
} else {
|
||||
loadAndCmp0MSB(value, true)
|
||||
if (jump != null)
|
||||
@@ -1304,15 +1313,59 @@ _jump jmp (${target.asmLabel})
|
||||
}
|
||||
}
|
||||
|
||||
private fun longGreaterZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
|
||||
if(lessEquals) {
|
||||
TODO("long >= 0 ${value.position}")
|
||||
} 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 checkLongGtZeroIntoCarry(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_gt_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_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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1435,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("""
|
||||
@@ -1493,17 +1546,13 @@ _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(left !is PtNumber && left !is PtIdentifier || right !is PtNumber && right !is PtIdentifier) {
|
||||
TODO("long comparison $operator with expressions - use temporary long variable instead to simplify for now ${left.position}")
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1938,7 +1987,7 @@ _jump jmp (${target.asmLabel})
|
||||
if(notEquals)
|
||||
translateAYNotEquals("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
else
|
||||
translateAYNotEquals("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
translateAYEquals("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,10 +412,9 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
|
||||
asmgen.out("; struct types")
|
||||
symboltable.allStructInstances.distinctBy { it.structName }.forEach {
|
||||
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
|
||||
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
|
||||
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
|
||||
asmgen.out("${it.structName} .struct $structargs\n")
|
||||
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
|
||||
structtype.fields.withIndex().forEach { (index, field) ->
|
||||
val dt = field.first
|
||||
val varname = "f${index}"
|
||||
@@ -425,7 +424,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .endstruct\n")
|
||||
}
|
||||
|
||||
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
|
||||
val (instancesNoInit, instances) = symboltable.allStructInstances().partition { it.initialValues.isEmpty() }
|
||||
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
|
||||
asmgen.out(" .section BSS\n")
|
||||
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -296,17 +296,7 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
elementDt.isLong -> {
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
asmgen.out("""
|
||||
lda $arrayVarName+$indexValue
|
||||
sta cx16.r14
|
||||
lda $arrayVarName+$indexValue+1
|
||||
sta cx16.r14+1
|
||||
lda $arrayVarName+$indexValue+2
|
||||
sta cx16.r14+2
|
||||
lda $arrayVarName+$indexValue+3
|
||||
sta cx16.r14+3""")
|
||||
assignRegisterLong(assign.target, RegisterOrPair.R14R15_32)
|
||||
assignVariableLong(assign.target, "$arrayVarName+$indexValue", DataType.LONG)
|
||||
}
|
||||
elementDt.isFloat -> {
|
||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue)")
|
||||
@@ -328,18 +318,7 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AX)
|
||||
}
|
||||
elementDt.isLong -> {
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,y
|
||||
sta cx16.r14
|
||||
lda $arrayVarName+1,y
|
||||
sta cx16.r14+1
|
||||
lda $arrayVarName+2,y
|
||||
sta cx16.r14+2
|
||||
lda $arrayVarName+3,y
|
||||
sta cx16.r14+3""")
|
||||
assignRegisterLong(assign.target, RegisterOrPair.R14R15_32)
|
||||
assignVariableLongIndexed(assign.target, arrayVarName, value)
|
||||
}
|
||||
elementDt.isFloat -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.A)
|
||||
@@ -374,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
|
||||
@@ -546,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 {
|
||||
@@ -659,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.
|
||||
@@ -671,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
|
||||
@@ -726,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)
|
||||
@@ -780,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")
|
||||
@@ -804,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)
|
||||
}
|
||||
@@ -1199,7 +1252,6 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
}
|
||||
else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
|
||||
if(expr.definingBlock()!!.options.veraFxMuls){
|
||||
// cx16 verafx hardware mul
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r1")
|
||||
@@ -1282,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
|
||||
}
|
||||
}
|
||||
@@ -1421,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
|
||||
}
|
||||
}
|
||||
@@ -1670,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)
|
||||
@@ -1832,11 +1916,11 @@ 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 {
|
||||
TODO("add/subtract long const into ${target.kind} ${target.position} - use simple expressions and temporary variables for now")
|
||||
TODO("add/subtract long const into ${target.kind} ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
@@ -1877,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
|
||||
@@ -1910,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
|
||||
}
|
||||
}
|
||||
@@ -2094,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)
|
||||
@@ -2108,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
|
||||
}
|
||||
@@ -2621,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)
|
||||
@@ -2669,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
|
||||
@@ -2695,37 +2779,37 @@ $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)
|
||||
asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R2R3_32 -> {
|
||||
RegisterOrPair.R2R3 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R2, false)
|
||||
asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R4R5_32 -> {
|
||||
RegisterOrPair.R4R5 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R4, false)
|
||||
asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R6R7_32 -> {
|
||||
RegisterOrPair.R6R7 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R6, false)
|
||||
asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R8R9_32 -> {
|
||||
RegisterOrPair.R8R9 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R8, false)
|
||||
asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R10R11_32 -> {
|
||||
RegisterOrPair.R10R11 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R10, false)
|
||||
asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R12R13_32 -> {
|
||||
RegisterOrPair.R12R13 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R12, false)
|
||||
asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R14R15_32 -> {
|
||||
RegisterOrPair.R14R15 -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.R14, false)
|
||||
asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
|
||||
return asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -2825,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) {
|
||||
@@ -2856,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3047,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
|
||||
@@ -3077,7 +3178,14 @@ $endLabel""")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> TODO("cast float to long")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.cast_as_long""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@@ -3087,6 +3195,15 @@ $endLabel""")
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
} else if(targetDt.isWord || targetDt.isPointer) {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
asmgen.out("""
|
||||
lda #<$sourceAsmVarName
|
||||
ldy #>$sourceAsmVarName
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type")
|
||||
}
|
||||
@@ -3096,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")
|
||||
|
||||
@@ -3119,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
|
||||
@@ -3153,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")
|
||||
}
|
||||
@@ -3163,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")
|
||||
@@ -3173,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)
|
||||
@@ -3197,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")
|
||||
@@ -3209,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")
|
||||
@@ -3236,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")
|
||||
@@ -3248,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")
|
||||
@@ -3300,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3517,9 +3641,8 @@ $endLabel""")
|
||||
lda $varName+3
|
||||
sta ${target.asmVarname}+3,y""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in combinedLongRegisters)
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val regstart = target.register!!.startregname()
|
||||
when(sourceDt) {
|
||||
DataType.BYTE -> {
|
||||
@@ -3568,6 +3691,59 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignLongVar(target.pointer!!, varName)
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableLongIndexed(target: AsmAssignTarget, arrayVarName: String, index: PtArrayIndexer) {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(index, CpuRegister.Y)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,y
|
||||
sta ${target.asmVarname}
|
||||
lda $arrayVarName+1,y
|
||||
sta ${target.asmVarname}+1
|
||||
lda $arrayVarName+2,y
|
||||
sta ${target.asmVarname}+2
|
||||
lda $arrayVarName+3,y
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val regstart = target.register!!.startregname()
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,y
|
||||
sta cx16.$regstart
|
||||
lda $arrayVarName+1,y
|
||||
sta cx16.$regstart+1
|
||||
lda $arrayVarName+2,y
|
||||
sta cx16.$regstart+2
|
||||
lda $arrayVarName+3,y
|
||||
sta cx16.$regstart+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointergen.assignLongVar(IndexedPtrTarget(target), arrayVarName)
|
||||
return
|
||||
}
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.X)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,y
|
||||
sta ${target.asmVarname},x
|
||||
lda $arrayVarName+1,y
|
||||
sta ${target.asmVarname}+1,x
|
||||
lda $arrayVarName+2,y
|
||||
sta ${target.asmVarname}+2,x
|
||||
lda $arrayVarName+3,y
|
||||
sta ${target.asmVarname}+3,x""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignIndexedPointer(target, arrayVarName, index, DataType.arrayFor(BaseDataType.LONG))
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
@@ -3669,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 {
|
||||
@@ -3684,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")
|
||||
}
|
||||
}
|
||||
@@ -3863,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")
|
||||
}
|
||||
}
|
||||
@@ -4058,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
|
||||
@@ -4089,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()
|
||||
@@ -4209,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) {
|
||||
@@ -4267,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!!) {
|
||||
@@ -4321,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")
|
||||
}
|
||||
}
|
||||
@@ -4597,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 */ }
|
||||
@@ -4643,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("""
|
||||
@@ -4993,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)
|
||||
@@ -5029,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
|
||||
@@ -5143,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()
|
||||
}
|
||||
@@ -5252,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")
|
||||
}
|
||||
}
|
||||
@@ -5326,7 +5519,7 @@ $endLabel""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
|
||||
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target), ignoreDatatype, scope)
|
||||
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target), scope)
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
@@ -5381,13 +5574,13 @@ $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")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
|
||||
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target), ignoreDatatype, scope)
|
||||
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target), scope)
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
@@ -5411,8 +5604,14 @@ $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.POINTER -> TODO("long negate via pointer ${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 */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +113,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(target.asmVarname, operator, value.asmVarname)
|
||||
SourceStorageKind.EXPRESSION -> inplacemodificationLongWithExpression(target.asmVarname, operator, value.expression!!)
|
||||
SourceStorageKind.REGISTER -> TODO("32 bits register inplace modification? ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace modify long with array ${target.position}")
|
||||
SourceStorageKind.MEMORY -> TODO("memread into long ${target.position}")
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(target.asmVarname, operator, regName(value))
|
||||
SourceStorageKind.ARRAY -> TODO("inplace modify long with array value ${target.position}")
|
||||
SourceStorageKind.MEMORY -> TODO("inplace modify long with memread value ${target.position}")
|
||||
}
|
||||
}
|
||||
target.datatype.isFloat -> {
|
||||
@@ -124,7 +124,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(target.asmVarname, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(target.asmVarname, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(target.asmVarname, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float ${target.position}")
|
||||
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(target.asmVarname, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(target.asmVarname, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
@@ -164,18 +164,18 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
is PtIdentifier -> {
|
||||
val pointer = memory.address as PtIdentifier
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationBytePointerWithLiteralval(pointer, operator, value.boolean!!.asInt())
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationBytePointerWithLiteralval(pointer, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationBytePointerWithVariable(pointer, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationBytePointerWithVariable(pointer, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into pointer ${target.position}")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationBytePointerWithValue(pointer, operator, value.array!!)
|
||||
SourceStorageKind.LITERALBOOLEAN -> ptrgen.inplacemodificationBytePointerWithLiteralval(pointer, operator, value.boolean!!.asInt())
|
||||
SourceStorageKind.LITERALNUMBER -> ptrgen.inplacemodificationBytePointerWithLiteralval(pointer, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> ptrgen.inplacemodificationBytePointerWithVariable(pointer, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> ptrgen.inplacemodificationBytePointerWithVariable(pointer, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> ptrgen. inplacemodificationBytePointerWithValue(pointer, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> ptrgen.inplacemodificationBytePointerWithValue(pointer, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationBytePointerWithValue(pointer, operator, value.expression)
|
||||
ptrgen.inplacemodificationBytePointerWithValue(pointer, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationBytePointerWithValue(pointer, operator, value.expression!!)
|
||||
ptrgen.inplacemodificationBytePointerWithValue(pointer, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,14 +309,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modifiication ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modifiication ${target.position}")
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modifiication ${target.position}")
|
||||
TODO("inplace long modification ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modifiication ${target.position}")
|
||||
TODO("inplace long modification ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,7 +328,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float array ${target.position}")
|
||||
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(targetVarName, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
@@ -519,7 +519,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float ${target.position}")
|
||||
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(tempvar, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
@@ -555,8 +555,15 @@ 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)
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -711,13 +718,21 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
lda $variable
|
||||
sta $variable+1
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable""")
|
||||
if(shift==1) {
|
||||
asmgen.out("""
|
||||
asl $variable+1
|
||||
rol $variable+2
|
||||
rol $variable+3""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$shift
|
||||
- asl $variable+1
|
||||
rol $variable+2
|
||||
rol $variable+3
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
}
|
||||
value == 16 -> {
|
||||
asmgen.out("""
|
||||
@@ -738,12 +753,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
sta $variable+2
|
||||
lda #0
|
||||
sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+1""")
|
||||
if(shift==1) {
|
||||
asmgen.out(" asl $variable+2 | rol $variable+3")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$shift
|
||||
- asl $variable+2
|
||||
rol $variable+3
|
||||
dey
|
||||
bne -""")
|
||||
}
|
||||
}
|
||||
value == 24 -> {
|
||||
asmgen.out("""
|
||||
@@ -756,12 +776,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
value <= 31 -> {
|
||||
val shift = value-24
|
||||
asmgen.out("""
|
||||
if(shift==1) {
|
||||
asmgen.out(" lda $variable | asl a")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
ldy #$shift
|
||||
- asl a
|
||||
dey
|
||||
bne -
|
||||
bne -""")
|
||||
}
|
||||
asmgen.out("""
|
||||
sta $variable+3
|
||||
lda #0
|
||||
sta $variable
|
||||
@@ -792,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
|
||||
@@ -885,41 +948,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
1 -> {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
bne +
|
||||
dec $variable+1
|
||||
bne +
|
||||
dec $variable+2
|
||||
bne +++
|
||||
lda $variable+1
|
||||
bne ++
|
||||
lda $variable+2
|
||||
bne +
|
||||
dec $variable+3
|
||||
+ dec $variable+2
|
||||
+ dec $variable+1
|
||||
+ dec $variable""")
|
||||
}
|
||||
else -> {
|
||||
if(value in 1..255) {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
sec
|
||||
lda $variable
|
||||
sbc #$value
|
||||
sta $variable
|
||||
bcs +
|
||||
dec $variable+1
|
||||
bne +
|
||||
dec $variable+2
|
||||
bne +
|
||||
dec $variable+3
|
||||
+""")
|
||||
} else if(value in 1..65535) {
|
||||
asmgen.out("""
|
||||
lda $variable
|
||||
sec
|
||||
sbc #<$value
|
||||
sta $variable
|
||||
bcs +++
|
||||
lda $variable+1
|
||||
sbc #>$value
|
||||
sta $variable+1
|
||||
bcs +
|
||||
dec $variable+2
|
||||
bne ++
|
||||
lda $variable+2
|
||||
bne +
|
||||
dec $variable+3
|
||||
+ dec $variable+2
|
||||
+ dec $variable+1
|
||||
+""")
|
||||
} else {
|
||||
val hex = value.toLongHex()
|
||||
@@ -1386,188 +1439,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
return false
|
||||
}
|
||||
|
||||
private fun inplacemodificationBytePointerWithValue(pointervar: PtIdentifier, operator: String, value: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||
inplacemodificationBytePointerWithVariable(pointervar, operator, "P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
private fun inplacemodificationBytePointerWithVariable(pointervar: PtIdentifier, operator: String, otherName: String) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
|
||||
when (operator) {
|
||||
"+" -> asmgen.out(" clc | adc $otherName")
|
||||
"-" -> asmgen.out(" sec | sbc $otherName")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- asl a
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
">>" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"&" -> asmgen.out(" and $otherName")
|
||||
"|" -> asmgen.out(" ora $otherName")
|
||||
"^" -> asmgen.out(" eor $otherName")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
cmp $otherName
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.out("""
|
||||
cmp $otherName
|
||||
bne +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
// pretty uncommon, who's going to assign a comparison boolean expression to a pointer?
|
||||
"<", "<=", ">", ">=" -> TODO("byte-var-to-pointer comparisons ${pointervar.position}")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
|
||||
private fun inplacemodificationBytePointerWithLiteralval(pointervar: PtIdentifier, operator: String, value: Int) {
|
||||
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
if(value==1) {
|
||||
if(asmgen.options.romable) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" inc a")
|
||||
else
|
||||
asmgen.out(" clc | adc #1")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
asmgen.out($$"+\tinc $ffff\t; modified")
|
||||
}
|
||||
} else {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(value==1) {
|
||||
if(asmgen.options.romable) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" dec a")
|
||||
else
|
||||
asmgen.out(" sec | sbc #1")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
asmgen.out($$"+\tdec $ffff\t; modified")
|
||||
}
|
||||
} else {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr prog8_math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"/" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm | tya")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"%" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.remainder_ub_asm")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"<<" -> {
|
||||
if (value > 0) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" asl a") }
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (value > 0) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" lsr a") }
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
}
|
||||
"&" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"|"-> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"^" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"==" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out("""
|
||||
cmp #$value
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"!=" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out("""
|
||||
cmp #$value
|
||||
bne +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
// pretty uncommon, who's going to assign a comparison boolean expression to a pointer?:
|
||||
"<", "<=", ">", ">=" -> TODO("byte-litval-to-pointer comparisons ${pointervar.position}")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationByteWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
|
||||
require(dt.isByteOrBool)
|
||||
if(!value.isSimple()) {
|
||||
|
||||
@@ -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)
|
||||
@@ -191,7 +191,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
">>=" -> {
|
||||
val opc = if (signed) Opcode.ASRN else Opcode.LSRN
|
||||
addInstr(inplaceInstrs, IRInstruction(opc, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
}
|
||||
"or=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
|
||||
@@ -557,7 +560,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
if(ptrWithOffset!=null) {
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
|
||||
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
|
||||
if(constOffset in 0..255) {
|
||||
if(constOffset in 0..65535) {
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -567,18 +570,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
return result
|
||||
}
|
||||
}
|
||||
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
|
||||
// STOREIX only works with byte index.
|
||||
val tr = if(offsetTypecast?.value?.type?.isByte==true)
|
||||
expressionEval.translateExpression(offsetTypecast.value)
|
||||
else
|
||||
expressionEval.translateExpression(ptrWithOffset.right)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val tr = expressionEval.translateExpression(memory!!.address)
|
||||
|
||||
@@ -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())
|
||||
@@ -406,7 +406,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(ptrWithOffset!=null) {
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
|
||||
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
|
||||
if(constOffset in 0..255) {
|
||||
if(constOffset in 0..65535) {
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -416,18 +416,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
}
|
||||
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
|
||||
// LOADIX only works with byte index.
|
||||
val tr = if(offsetTypecast?.value?.type?.isByte==true)
|
||||
translateExpression(offsetTypecast.value)
|
||||
else
|
||||
translateExpression(ptrWithOffset.right)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
}
|
||||
|
||||
val tr = translateExpression(mem.address)
|
||||
@@ -819,6 +807,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast $valueDt to long ${cast.position}")
|
||||
}
|
||||
}
|
||||
@@ -837,6 +829,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
BaseDataType.WORD -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
}
|
||||
@@ -932,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)
|
||||
@@ -953,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)
|
||||
@@ -1846,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)
|
||||
}
|
||||
}
|
||||
@@ -1859,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 {
|
||||
@@ -1868,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,6 +55,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||
|| simplifyConstantReturns(chunk1, indexedInstructions)
|
||||
|| removeNeedlessLoads(chunk1, indexedInstructions)
|
||||
|| loadfieldsAndStorefields(chunk1, indexedInstructions)
|
||||
|| removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop
|
||||
} while (changed)
|
||||
}
|
||||
}
|
||||
@@ -151,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())
|
||||
@@ -161,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
|
||||
@@ -300,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
|
||||
}
|
||||
@@ -309,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
|
||||
}
|
||||
@@ -400,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) {
|
||||
@@ -440,6 +481,54 @@ jump p8_label_gen_2
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
fun optimizeImmediateLoadAssociative(replacementOpcode: Opcode) {
|
||||
|
||||
fun getImmediateLoad(reg: Int): Pair<Int, Int>? {
|
||||
// look if the given register gets an immediate value 1 or 2 istructions back
|
||||
// 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 && 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 && previous.immediate!=null)
|
||||
return idx - 2 to previous.immediate!!
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if(ins.reg1!=null) {
|
||||
val immediate1 = getImmediateLoad(ins.reg1!!)
|
||||
if(immediate1!=null) {
|
||||
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second)
|
||||
chunk.instructions.removeAt(immediate1.first)
|
||||
changed=true
|
||||
} else {
|
||||
val immediate2 = getImmediateLoad(ins.reg2!!)
|
||||
if (immediate2 != null) {
|
||||
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second)
|
||||
chunk.instructions.removeAt(immediate2.first)
|
||||
changed=true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to use immediate arithmetic instruction if possible
|
||||
when(ins.opcode) {
|
||||
Opcode.ADDR -> optimizeImmediateLoadAssociative(Opcode.ADD)
|
||||
Opcode.MULR -> optimizeImmediateLoadAssociative(Opcode.MUL)
|
||||
Opcode.MULSR -> optimizeImmediateLoadAssociative(Opcode.MULS)
|
||||
// Opcode.SUBR -> TODO("ir peephole Subr")
|
||||
// Opcode.DIVR -> TODO("ir peephole Divr")
|
||||
// Opcode.DIVSR -> TODO("ir peephole Divsr")
|
||||
// Opcode.MODR -> TODO("ir peephole Modr")
|
||||
// Opcode.DIVMODR -> TODO("ir peephole DivModr")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
@@ -505,4 +594,22 @@ jump p8_label_gen_2
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun loadfieldsAndStorefields(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +85,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
|
||||
private fun reportUnreachable(stmt: Statement) {
|
||||
when(val next = stmt.nextSibling()) {
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is Break, is Jump -> {}
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,65 @@ smallringbuffer {
|
||||
}
|
||||
}
|
||||
|
||||
; note: for a "small stack" (256 bytes size) just use the CPU stack via sys.push[w] / sys.pop[w].
|
||||
|
||||
smallstack {
|
||||
; -- A small stack (LIFO) that uses just 256 bytes and is independent of the CPU stack. Stack is growing downward from the top of the buffer.
|
||||
; You can store and retrieve bytes and words. There are no guards against stack over/underflow.
|
||||
; note: for a "small stack" (256 bytes size) you might also perhaps just use the CPU stack via sys.push[w] / sys.pop[w].
|
||||
|
||||
ubyte[256] buffer
|
||||
ubyte sp = 255
|
||||
|
||||
sub init() {
|
||||
sp = 255
|
||||
}
|
||||
|
||||
sub size() -> ubyte {
|
||||
return 255-sp
|
||||
}
|
||||
|
||||
sub free() -> ubyte {
|
||||
return sp
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==255
|
||||
}
|
||||
|
||||
sub push(ubyte value) {
|
||||
; -- put a byte on the stack
|
||||
buffer[sp] = value
|
||||
sp--
|
||||
}
|
||||
|
||||
sub pushw(uword value) {
|
||||
; -- put a word on the stack (lsb first then msb)
|
||||
buffer[sp] = lsb(value)
|
||||
sp--
|
||||
buffer[sp] = msb(value)
|
||||
sp--
|
||||
}
|
||||
|
||||
sub pop() -> ubyte {
|
||||
; -- pops a byte off the stack
|
||||
sp++
|
||||
return buffer[sp]
|
||||
}
|
||||
|
||||
sub popw() -> uword {
|
||||
; -- pops a word off the stack.
|
||||
sp++
|
||||
cx16.r0H = buffer[sp]
|
||||
sp++
|
||||
cx16.r0L = buffer[sp]
|
||||
return cx16.r0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stack {
|
||||
; -- A stack (LIFO) that uses a block of 8 KB of memory. Growing downward from the top of the buffer.
|
||||
|
||||
@@ -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
|
||||
@@ -358,7 +360,7 @@ extsub $FF71 = JMPFAR() clobbers(A,X) ; jump without r
|
||||
extsub $FF74 = INDFET(ubyte zpaddr @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> ubyte @A ; fetch byte from another bank (address in zp, ptr in A)
|
||||
extsub $FF77 = INDSTA(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) ; store byte to another bank (address in zp, ptr in $02b9)
|
||||
extsub $FF7A = INDCMP(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> bool @Pz, bool @Pc, bool @Pv; compare byte in another bank (address in zp, ptr in $02c8)
|
||||
extsub $FF7D = PRIMM() ; print immediate string
|
||||
extsub $FF7D = PRIMM() ; print immediate string (the 0-terminated string immediately following the JSR PRIMM instruction)
|
||||
|
||||
|
||||
; ---- C128 specific system utility routines: ----
|
||||
@@ -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
|
||||
@@ -1140,6 +955,16 @@ cx16 {
|
||||
&word r14s = $1bfc
|
||||
&word r15s = $1bfe
|
||||
|
||||
; signed long versions
|
||||
&long r0r1sl = $1be0
|
||||
&long r2r3sl = $1be4
|
||||
&long r4r5sl = $1be8
|
||||
&long r6r7sl = $1bec
|
||||
&long r8r9sl = $1bf0
|
||||
&long r10r11sl = $1bf4
|
||||
&long r12r13sl = $1bf8
|
||||
&long r14r15sl = $1bfc
|
||||
|
||||
; ubyte versions (low and high bytes)
|
||||
&ubyte r0L = $1be0
|
||||
&ubyte r1L = $1be2
|
||||
@@ -1289,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
|
||||
@@ -1302,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
|
||||
@@ -1325,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
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
FL_ONE_const .byte 129 ; 1.0
|
||||
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
|
||||
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
||||
; note: don't add too many constants here because they all end up in the resulting program
|
||||
|
||||
|
||||
.section BSS
|
||||
floats_temp_var .byte ?,?,?,?,? ; temporary storage for a float
|
||||
@@ -126,6 +127,40 @@ cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||
.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
|
||||
|
||||
; save the target variable pointer on the stack
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr MOVFM
|
||||
jsr QINT
|
||||
|
||||
; restore the target variable pointer from the stack, and put the result in it
|
||||
pla
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
pla
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
|
||||
ldx #3
|
||||
ldy #0
|
||||
- lda FACHO,x
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
@@ -485,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
|
||||
@@ -11,6 +11,7 @@ floats {
|
||||
; 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 = $61
|
||||
|
||||
; 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.
|
||||
@@ -167,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
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
|
||||
func_sign_f_into_A .proc
|
||||
; sign in A, also sets status flags
|
||||
jsr MOVFM
|
||||
jmp SIGN
|
||||
jsr SIGN
|
||||
cmp #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1150,6 +934,16 @@ cx16 {
|
||||
&word r14s = $cffc
|
||||
&word r15s = $cffe
|
||||
|
||||
; signed long versions
|
||||
&long r0r1sl = $cfe0
|
||||
&long r2r3sl = $cfe4
|
||||
&long r4r5sl = $cfe8
|
||||
&long r6r7sl = $cfec
|
||||
&long r8r9sl = $cff0
|
||||
&long r10r11sl = $cff4
|
||||
&long r12r13sl = $cff8
|
||||
&long r14r15sl = $cffc
|
||||
|
||||
; ubyte versions (low and high bytes)
|
||||
&ubyte r0L = $cfe0
|
||||
&ubyte r1L = $cfe2
|
||||
@@ -1313,6 +1107,8 @@ asmsub init_system() {
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
lda #$80
|
||||
sta 650 ; enable key repeat for normal keys too
|
||||
lda #PROG8_C64_BANK_CONFIG ; apply bank config
|
||||
sta $01
|
||||
and #1
|
||||
@@ -1349,6 +1145,8 @@ asmsub cleanup_at_exit() {
|
||||
jsr cbm.MEMTOP ; adjust MEMTOP down again
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr enable_runstop_and_charsetswitch
|
||||
lda #0
|
||||
sta 650 ; disable keyrepeats
|
||||
lda _exitcarry
|
||||
lsr a
|
||||
lda _exitcode
|
||||
|
||||
@@ -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,6 +572,85 @@ _try_iso
|
||||
}}
|
||||
}
|
||||
|
||||
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 $)
|
||||
; result in R0:R1, number of characters processed also remains in cx16.r15L if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty cx16.r0
|
||||
sty cx16.r0+1
|
||||
sty cx16.r0+2
|
||||
sty cx16.r0+3
|
||||
sty cx16.r15H
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'$'
|
||||
bne _loop
|
||||
iny
|
||||
_loop
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #7 ; screencode letters A-F are 1-6
|
||||
bcc _add_letter
|
||||
and #127
|
||||
cmp #97
|
||||
bcs _try_iso ; maybe letter is iso:'a'-iso:'f' (97-102)
|
||||
cmp #'g'
|
||||
bcs _stop
|
||||
cmp #'a'
|
||||
bcs _add_letter
|
||||
cmp #'0'
|
||||
bcc _stop
|
||||
cmp #'9'+1
|
||||
bcs _stop
|
||||
_calc
|
||||
asl cx16.r0
|
||||
rol cx16.r0+1
|
||||
rol cx16.r0+2
|
||||
rol cx16.r0+3
|
||||
asl cx16.r0
|
||||
rol cx16.r0+1
|
||||
rol cx16.r0+2
|
||||
rol cx16.r0+3
|
||||
asl cx16.r0
|
||||
rol cx16.r0+1
|
||||
rol cx16.r0+2
|
||||
rol cx16.r0+3
|
||||
asl cx16.r0
|
||||
rol cx16.r0+1
|
||||
rol cx16.r0+2
|
||||
rol cx16.r0+3
|
||||
and #$0f
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ora cx16.r0
|
||||
sta cx16.r0
|
||||
iny
|
||||
bne _loop
|
||||
_stop
|
||||
sty cx16.r15L
|
||||
rts
|
||||
_add_letter
|
||||
pha
|
||||
lda #9
|
||||
sta P8ZP_SCRATCH_B1
|
||||
pla
|
||||
jmp _calc
|
||||
_try_iso
|
||||
cmp #103
|
||||
bcs _stop
|
||||
and #63
|
||||
bne _add_letter
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @AY) -> uword @AY {
|
||||
; -- binary string (with or without '%') to uword.
|
||||
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||
|
||||
@@ -68,7 +68,65 @@ smallringbuffer {
|
||||
}
|
||||
}
|
||||
|
||||
; note: for a "small stack" (256 bytes size) just use the CPU stack via sys.push[w] / sys.pop[w].
|
||||
|
||||
smallstack {
|
||||
; -- A small stack (LIFO) that uses just 256 bytes and is independent of the CPU stack. Stack is growing downward from the top of the buffer.
|
||||
; You can store and retrieve bytes and words. There are no guards against stack over/underflow.
|
||||
; note: for a "small stack" (256 bytes size) you might also perhaps just use the CPU stack via sys.push[w] / sys.pop[w].
|
||||
|
||||
ubyte[256] buffer
|
||||
ubyte sp = 255
|
||||
|
||||
sub init() {
|
||||
sp = 255
|
||||
}
|
||||
|
||||
sub size() -> ubyte {
|
||||
return 255-sp
|
||||
}
|
||||
|
||||
sub free() -> ubyte {
|
||||
return sp
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==255
|
||||
}
|
||||
|
||||
sub push(ubyte value) {
|
||||
; -- put a byte on the stack
|
||||
buffer[sp] = value
|
||||
sp--
|
||||
}
|
||||
|
||||
sub pushw(uword value) {
|
||||
; -- put a word on the stack (lsb first then msb)
|
||||
buffer[sp] = lsb(value)
|
||||
sp--
|
||||
buffer[sp] = msb(value)
|
||||
sp--
|
||||
}
|
||||
|
||||
sub pop() -> ubyte {
|
||||
; -- pops a byte off the stack
|
||||
sp++
|
||||
return buffer[sp]
|
||||
}
|
||||
|
||||
sub popw() -> uword {
|
||||
; -- pops a word off the stack.
|
||||
sp++
|
||||
cx16.r0H = buffer[sp]
|
||||
sp++
|
||||
cx16.r0L = buffer[sp]
|
||||
return cx16.r0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stack {
|
||||
; -- A stack (LIFO) that uses a block of 8 KB of memory. Growing downward from the top of the buffer.
|
||||
@@ -78,7 +136,7 @@ stack {
|
||||
ubyte bank
|
||||
|
||||
sub init(ubyte rambank) {
|
||||
; -- initialize the stack, must be called before use. Supply the HiRAM bank to use as buffer space.
|
||||
; -- initialize the stack, must be called before use. Supply the HIRAM bank to use as buffer space.
|
||||
sp = $bfff
|
||||
bank = rambank
|
||||
}
|
||||
@@ -157,7 +215,7 @@ ringbuffer {
|
||||
ubyte bank = 255 ; set via init()
|
||||
|
||||
sub init(ubyte rambank) {
|
||||
; -- initialize the ringbuffer, must be called before use. Supply the HiRAM bank to use as buffer space.
|
||||
; -- initialize the ringbuffer, must be called before use. Supply the HIRAM bank to use as buffer space.
|
||||
head = $a000
|
||||
tail = $bfff
|
||||
fill = 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
; **experimental** data compression/decompression routines, API subject to change!!
|
||||
; data compression/decompression routines
|
||||
|
||||
%import shared_compression
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -244,6 +282,7 @@ io_error:
|
||||
; this version it only returns directory entries!
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
cbm.SETNAM(5, "$:*=d")
|
||||
diskio.lf_start_list.pattern_ptr = pattern_ptr
|
||||
goto diskio.lf_start_list.start_list_internal
|
||||
}
|
||||
|
||||
@@ -252,6 +291,20 @@ io_error:
|
||||
; this version only returns actual file entries!
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
cbm.SETNAM(5, "$:*=p")
|
||||
diskio.lf_start_list.pattern_ptr = pattern_ptr
|
||||
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
|
||||
}
|
||||
|
||||
@@ -259,69 +312,100 @@ io_error:
|
||||
; -- 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 {
|
||||
@@ -809,6 +893,7 @@ io_error:
|
||||
if cbm.READST()!=0
|
||||
cx16.r0 = 0
|
||||
}
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
return cx16.r0
|
||||
}
|
||||
@@ -999,15 +1084,12 @@ io_error:
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
sub f_seek(long position) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
; Note: this will not work if you have already read the last byte of the file! Then you must close and reopen the file first.
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
|
||||
command[2] = lsb(pos_loword)
|
||||
command[3] = msb(pos_loword)
|
||||
command[4] = lsb(pos_hiword)
|
||||
command[5] = msb(pos_hiword)
|
||||
pokel(&command+2, position)
|
||||
cbm.SETNAM(sizeof(command), &command)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
@@ -1016,13 +1098,10 @@ io_error:
|
||||
}
|
||||
|
||||
|
||||
sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
sub f_seek_w(long position) {
|
||||
; -- seek in the output file opened with f_open_w_seek, to the given 32-bits position
|
||||
diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses this secondary address
|
||||
diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
diskio.f_seek.command[3] = msb(pos_loword)
|
||||
diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
pokel(&diskio.f_seek.command+2, position)
|
||||
cbm.SETNAM(sizeof(diskio.f_seek.command), &diskio.f_seek.command)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
@@ -1030,54 +1109,63 @@ io_error:
|
||||
reset_write_channel() ; back to the write io channel
|
||||
}
|
||||
|
||||
asmsub f_tell() -> uword @R0, uword @R1, uword @R2, uword @R3 {
|
||||
; -- Returns the current read position of the opened read file,
|
||||
; in R0 and R1 (low + high words) and the file size in R2 and R3 (low + high words).
|
||||
; Returns 0 as size if the command is not supported by the DOS implementation/version.
|
||||
%asm {{
|
||||
jmp internal_f_tell
|
||||
}}
|
||||
}
|
||||
|
||||
sub internal_f_tell() {
|
||||
; gets the (32 bits) position + file size of the opened read file channel
|
||||
ubyte[2] command = ['t',0]
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
|
||||
cbm.SETNAM(sizeof(command), &command)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
sub f_tell() -> long, long {
|
||||
; -- Returns the current read position of the opened read file, and the file size.
|
||||
; Returns 0,0 if the command is not supported by the DOS implementation/version.
|
||||
ubyte[3] command = ['?','?',0]
|
||||
bool success=false
|
||||
; valid response starts with "07," followed by hex notations of the position and filesize
|
||||
if cbm.CHRIN()=='0' and cbm.CHRIN()=='7' and cbm.CHRIN()==',' {
|
||||
cx16.r1 = read4hex()
|
||||
cx16.r0 = read4hex() ; position in R1:R0
|
||||
void cbm.CHRIN() ; separator space
|
||||
cx16.r3 = read4hex()
|
||||
cx16.r2 = read4hex() ; filesize in R3:R2
|
||||
success = true
|
||||
long filesize, filepos
|
||||
ubyte commandoffset
|
||||
|
||||
command[1] = 't'
|
||||
commandoffset = 1
|
||||
f_tell_internal()
|
||||
return filepos, filesize
|
||||
|
||||
sub f_tell_internal() {
|
||||
; this code is used for both the T (tell) and FL (fatlba) commands
|
||||
command[2] = READ_IO_CHANNEL ; f_open uses this secondary address
|
||||
cbm.SETNAM(sizeof(command)-commandoffset, &command+commandoffset)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
|
||||
; valid response starts with "07," followed by hex notations of the position and filesize
|
||||
if cbm.CHRIN()=='0' and cbm.CHRIN()=='7' and cbm.CHRIN()==',' {
|
||||
filepos = read8hex()
|
||||
void cbm.CHRIN() ; separator space
|
||||
filesize = read8hex()
|
||||
success = true
|
||||
} else {
|
||||
filesize = filepos = 0
|
||||
}
|
||||
|
||||
while cbm.READST()==0 {
|
||||
cx16.r5L = cbm.CHRIN()
|
||||
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||
break
|
||||
}
|
||||
|
||||
cbm.CLOSE(15)
|
||||
reset_read_channel() ; back to the read io channel
|
||||
}
|
||||
|
||||
while cbm.READST()==0 {
|
||||
cx16.r5L = cbm.CHRIN()
|
||||
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||
break
|
||||
}
|
||||
|
||||
cbm.CLOSE(15)
|
||||
reset_read_channel() ; back to the read io channel
|
||||
if success
|
||||
return
|
||||
|
||||
cx16.r0 = cx16.r1 = cx16.r2 = cx16.r3 = 0
|
||||
}
|
||||
|
||||
sub read4hex() -> uword {
|
||||
str hex = "0000"
|
||||
hex[0] = cbm.CHRIN()
|
||||
hex[1] = cbm.CHRIN()
|
||||
hex[2] = cbm.CHRIN()
|
||||
hex[3] = cbm.CHRIN()
|
||||
return conv.hex2uword(hex)
|
||||
sub f_fatlba() -> long, long {
|
||||
; -- Return the current LBA, cluster number (sector index and shift value are ignored for now)
|
||||
diskio.f_tell.command[0] = 'f'
|
||||
diskio.f_tell.command[1] = 'l'
|
||||
diskio.f_tell.commandoffset = 0
|
||||
diskio.f_tell.f_tell_internal()
|
||||
return diskio.f_tell.filepos, diskio.f_tell.filesize
|
||||
}
|
||||
|
||||
|
||||
sub read8hex() -> long {
|
||||
str hex = "00000000"
|
||||
for cx16.r0L in 0 to 7 {
|
||||
hex[cx16.r0L] = cbm.CHRIN()
|
||||
}
|
||||
return conv.hex2long(hex)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -14,6 +14,8 @@ floats {
|
||||
; 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.
|
||||
|
||||
const uword FAC_ADDR = $c3
|
||||
|
||||
extsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD. (might throw ILLEGAL QUANTITY)
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
@@ -137,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
|
||||
@@ -217,6 +217,16 @@ cx16 {
|
||||
&word r14s = $001e
|
||||
&word r15s = $0020
|
||||
|
||||
; signed long versions
|
||||
&long r0r1sl = $0002
|
||||
&long r2r3sl = $0006
|
||||
&long r4r5sl = $000a
|
||||
&long r6r7sl = $000e
|
||||
&long r8r9sl = $0012
|
||||
&long r10r11sl = $0016
|
||||
&long r12r13sl = $001a
|
||||
&long r14r15sl = $001e
|
||||
|
||||
; ubyte versions (low and high bytes)
|
||||
&ubyte r0L = $0002
|
||||
&ubyte r1L = $0004
|
||||
@@ -402,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
|
||||
|
||||
@@ -414,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
|
||||
@@ -427,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
|
||||
@@ -463,7 +478,7 @@ extsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobb
|
||||
extsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
extsub $ff74 = fetch(ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
extsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X) ; note: The the zero page address containing the base address is passed in stavec ($03B2)
|
||||
extsub $ff7d = PRIMM()
|
||||
extsub $ff7d = PRIMM() ; print immediate string (the 0-terminated string immediately following the JSR PRIMM instruction)
|
||||
|
||||
; high level graphics & fonts
|
||||
extsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
@@ -535,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)
|
||||
@@ -543,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).
|
||||
@@ -620,6 +640,10 @@ const ubyte EXTAPI_scnsiz = $0D
|
||||
const ubyte EXTAPI_kbd_leds = $0E
|
||||
const ubyte EXTAPI_memory_decompress_from_func = $0F
|
||||
const ubyte EXTAPI_default_palette = $10
|
||||
const ubyte EXTAPI_has_machine_property = $11
|
||||
const ubyte EXTAPI_kbdbuf_get = $12
|
||||
const ubyte EXTAPI_kbdbuf_clear = $13
|
||||
const ubyte EXTAPI_blink_enable = $14
|
||||
|
||||
; extapi16 call numbers
|
||||
const ubyte EXTAPI16_test = $00
|
||||
@@ -627,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) {
|
||||
@@ -676,6 +704,30 @@ sub mouse_present() -> bool {
|
||||
return cx16.r0L != $fc ; $fc = BAT_FAIL
|
||||
}
|
||||
|
||||
asmsub get_charset() -> ubyte @A {
|
||||
;Get charset mode. result: 0=unknown, 1=ISO, 2=PETSCII upper case/gfx, 3=PETSCII lowercase.
|
||||
%asm {{
|
||||
|
||||
KERNAL_MODE = $0372 ; internal kernal variable, risky to read it, but it's ben stable for many releases.
|
||||
lda KERNAL_MODE
|
||||
beq _end
|
||||
|
||||
bit #$40 ;ISO mode flag
|
||||
beq + ;usually KERNAL_MODE 1 or 6 (| $40)
|
||||
lda #1
|
||||
bra _end
|
||||
|
||||
+ bit #1 ;PETSCII upper case/graphics
|
||||
bne + ;usually KERNAL_MODE 2 or 4
|
||||
lda #2
|
||||
bra _end
|
||||
|
||||
+ lda #3 ;PETSCII upper/lower case
|
||||
_end:
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
; shims for the kernal routines called via the extapi call:
|
||||
|
||||
asmsub mouse_set_pos(uword xpos @R0, uword ypos @R1) clobbers(X) {
|
||||
@@ -758,31 +810,42 @@ asmsub set_default_palette() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_charset() -> ubyte @A {
|
||||
;Get charset mode. result: 0=unknown, 1=ISO, 2=PETSCII upper case/gfx, 3=PETSCII lowercase.
|
||||
asmsub has_machine_property(ubyte property @X) clobbers(A,X) -> bool @Pc {
|
||||
; requires rom v49+
|
||||
%asm {{
|
||||
lda #EXTAPI_has_machine_property
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
KERNAL_MODE = $0372 ; internal kernal variable, risky to read it, but it's ben stable for many releases.
|
||||
lda KERNAL_MODE
|
||||
beq _end
|
||||
asmsub kbdbuf_get() clobbers(X,Y) -> ubyte @A {
|
||||
; requires rom v49+
|
||||
%asm {{
|
||||
lda #EXTAPI_kbdbuf_get
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
bit #$40 ;ISO mode flag
|
||||
beq + ;usually KERNAL_MODE 1 or 6 (| $40)
|
||||
lda #1
|
||||
bra _end
|
||||
asmsub kbdbuf_clear() clobbers(A) {
|
||||
; requires rom v49+
|
||||
%asm {{
|
||||
lda #EXTAPI_kbdbuf_clear
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
+ bit #1 ;PETSCII upper case/graphics
|
||||
bne + ;usually KERNAL_MODE 2 or 4
|
||||
lda #2
|
||||
bra _end
|
||||
|
||||
+ lda #3 ;PETSCII upper/lower case
|
||||
_end:
|
||||
rts
|
||||
asmsub blink_enable(bool enable @X) clobbers(A,X) {
|
||||
; requires rom v49+
|
||||
%asm {{
|
||||
txa
|
||||
and #1
|
||||
eor #1
|
||||
tax
|
||||
lda #EXTAPI_blink_enable
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
; TODO : implement shims for the remaining extapi calls.
|
||||
|
||||
|
||||
; ---- end of kernal routines ----
|
||||
@@ -1191,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
|
||||
@@ -1273,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 {{
|
||||
@@ -1281,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
|
||||
@@ -1299,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
|
||||
@@ -1308,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
|
||||
@@ -1328,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
|
||||
@@ -1373,6 +1453,8 @@ _sprcol_handler
|
||||
jmp (_sprcol_vec)
|
||||
_aflow_handler
|
||||
jmp (_aflow_vec)
|
||||
_timer1_handler
|
||||
jmp (_timer1_vec)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1437,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.
|
||||
@@ -1479,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)
|
||||
@@ -1491,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
|
||||
@@ -1555,6 +1659,8 @@ _continue iny
|
||||
}
|
||||
}
|
||||
|
||||
%import shared_sys_functions
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@@ -1632,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
|
||||
@@ -1807,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
|
||||
@@ -1997,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 {{
|
||||
@@ -2077,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
|
||||
@@ -2141,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
|
||||
@@ -2154,7 +2037,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popl() -> long @R0R1_32 {
|
||||
inline asmsub popl() -> long @R0R1 {
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.r0+3
|
||||
@@ -2224,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
|
||||
@@ -605,6 +444,16 @@ cx16 {
|
||||
&word r14s = $7ffc
|
||||
&word r15s = $7ffe
|
||||
|
||||
; signed long versions
|
||||
&long r0r1sl = $7fe0
|
||||
&long r2r3sl = $7fe4
|
||||
&long r4r5sl = $7fe8
|
||||
&long r6r7sl = $7fec
|
||||
&long r8r9sl = $7fe0
|
||||
&long r10r11sl = $7fe4
|
||||
&long r12r13sl = $7fe8
|
||||
&long r14r15sl = $7fec
|
||||
|
||||
; ubyte versions (low and high bytes)
|
||||
&ubyte r0L = $7fe0
|
||||
&ubyte r1L = $7fe2
|
||||
|
||||
@@ -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_ignored) {
|
||||
; ---- set char at the given position on the screen. 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
|
||||
@@ -660,3 +682,117 @@ long_shiftrightX_inplace .proc
|
||||
bne -
|
||||
_end rts
|
||||
.pend
|
||||
|
||||
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
|
||||
bmi _fail
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
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
|
||||
|
||||
@@ -222,6 +222,7 @@ io_error:
|
||||
; this version it only returns directory entries!
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
cbm.SETNAM(5, "$:*=c") ; on C64 (1581 diskdrive) the type for directories is CBM
|
||||
diskio.lf_start_list.pattern_ptr = pattern_ptr
|
||||
goto diskio.lf_start_list.start_list_internal
|
||||
}
|
||||
|
||||
@@ -230,6 +231,20 @@ io_error:
|
||||
; this version only returns actual file entries!
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
cbm.SETNAM(5, "$:*=p")
|
||||
diskio.lf_start_list.pattern_ptr = pattern_ptr
|
||||
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
|
||||
}
|
||||
|
||||
@@ -719,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) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
; **experimental** data compression/decompression routines, API subject to change!!
|
||||
; data compression/decompression routines
|
||||
; This file contains the shared routines that work on all targets.
|
||||
|
||||
compression {
|
||||
|
||||
@@ -160,6 +160,9 @@ sub log2(float value) -> float {
|
||||
ldy #>FL_LOG2_const
|
||||
jsr MOVFM
|
||||
jmp FDIVT
|
||||
|
||||
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -302,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
|
||||
|
||||
@@ -279,6 +279,26 @@ sub hex2uword(str string) -> uword {
|
||||
}
|
||||
}
|
||||
|
||||
sub hex2long(str string) -> long {
|
||||
; -- hexadecimal string (with or without '$') to long.
|
||||
; stops parsing at the first character that's not a hex digit (except leading $)
|
||||
long result
|
||||
ubyte char
|
||||
if @(string)=='$'
|
||||
string++
|
||||
repeat {
|
||||
char = @(string)
|
||||
if char==0
|
||||
return result
|
||||
result <<= 4
|
||||
if char>='0' and char<='9'
|
||||
result |= char-'0'
|
||||
else
|
||||
result |= char-'a'+10
|
||||
string++
|
||||
}
|
||||
}
|
||||
|
||||
sub bin2uword(str string) -> uword {
|
||||
; -- binary string (with or without '%') to uword.
|
||||
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||
|
||||
@@ -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 {
|
||||
@@ -307,6 +315,16 @@ cx16 {
|
||||
&word r14s = $ff1e
|
||||
&word r15s = $ff20
|
||||
|
||||
; signed long versions
|
||||
&long r0r1sl = $ff02
|
||||
&long r2r3sl = $ff06
|
||||
&long r4r5sl = $ff0a
|
||||
&long r6r7sl = $ff0e
|
||||
&long r8r9sl = $ff12
|
||||
&long r10r11sl = $ff16
|
||||
&long r12r13sl = $ff1a
|
||||
&long r14r15sl = $ff1e
|
||||
|
||||
; ubyte versions (low and high bytes)
|
||||
&ubyte r0L = $ff02
|
||||
&ubyte r1L = $ff04
|
||||
|
||||
@@ -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++
|
||||
}
|
||||
}
|
||||
}
|
||||