Compare commits

...

90 Commits

Author SHA1 Message Date
Irmen de Jong
9daa116148 first setup of LSP languageserver 2025-07-15 20:24:45 +02:00
Irmen de Jong
054c98da7c add link to extra prog8 compilation targets 2025-07-15 00:17:04 +02:00
Irmen de Jong
a4a1b563aa sdks 2025-07-14 21:54:56 +02:00
Irmen de Jong
7dd64b4f13 fix 'cpa' instruction generated in certain boolean assignment, must be 'cmp' 2025-07-06 22:43:19 +02:00
Irmen de Jong
edc353cc24 more kotlin 2.2 settings 2025-07-06 00:37:15 +02:00
Irmen de Jong
0a16dcafc0 update kotlin and gradle wrapper 2025-07-06 00:01:24 +02:00
gillham
1b420f7fe7 Add a preliminary external custom target for the Foenix F256 family of modern retro computers. (#171) 2025-06-29 11:14:34 +02:00
Irmen de Jong
6a9a82ff9d doc 2025-06-27 18:14:36 +02:00
markjreed
aa36e6b19f flesh out C128-specific KERNAL calls (#170)
* flesh out C128-specific KERNAL calls

* fix: typo in comment

* fix: typo in comment

* fix: include return values of INDCMP

* fix: rearrange return values of INDCMP
2025-06-27 18:13:26 +02:00
Irmen de Jong
51cb6aad50 add c128.PRIMM() 2025-06-27 17:39:31 +02:00
Irmen de Jong
b5ce409592 clarify booleans 2025-06-26 20:06:36 +02:00
Irmen de Jong
1efdfe8ea1 much nicer colors in the bubbleuniverse example 2025-06-23 21:25:05 +02:00
Irmen de Jong
67d4180825 lib 2025-06-20 22:01:29 +02:00
Irmen de Jong
be31e190d2 shuffle arguments of 64tass command so the additional assembler options from custom targets actually work 2025-06-19 22:33:50 +02:00
Irmen de Jong
b5d1575823 added boolean typed versions of the cx16.r0-r15 virtual registers 2025-06-18 00:05:10 +02:00
Irmen de Jong
e3e395836d fix splitting of array decl and initializer for non numeric types 2025-06-13 23:31:56 +02:00
Irmen de Jong
8dc2e47507 fix partial unused code removal in vm target 2025-06-11 23:31:29 +02:00
Irmen de Jong
daf7c3357c better detection of missing return statement
preparing 11.4
2025-06-09 16:01:56 +02:00
Irmen de Jong
e8795859c5 added sorting library for target virtual
added sorting routines that sort a values array together with the keys array
optimized gnomesort a little
2025-06-07 19:42:40 +02:00
Irmen de Jong
bebe60b687 fix compiler crash on for x in wordvar, add sys.get_as_returnaddress() 2025-06-05 16:10:40 +02:00
Irmen de Jong
ddceec364e optimized coroutines library 2025-06-04 21:34:32 +02:00
Irmen de Jong
d067fa4b73 added strings.find_eol() 2025-06-03 21:09:44 +02:00
Irmen de Jong
b5e51ab937 cleaner timings output 2025-06-02 19:30:25 +02:00
Irmen de Jong
552e55c29f fix missing cmp #0 when asmsub call is part of a boolean expression 2025-06-02 19:22:00 +02:00
Irmen de Jong
a228908c1a fix wrong address calculation for &wordarray[i] where i is a variable 2025-06-02 03:13:23 +02:00
Irmen de Jong
15fc3b6c04 replace old antlr2kotlin code with the new visitor-based translator 2025-06-02 01:56:07 +02:00
Irmen de Jong
0456badd02 creating on a new visitor-based antlr to kotlin translator 2025-06-02 01:18:07 +02:00
Irmen de Jong
399cf5118d we will get a 11.4 version first before structs will land 2025-06-01 17:47:06 +02:00
Irmen de Jong
a87f2640d3 fixed signed byte comparisons in case of overflowing values 2025-06-01 14:01:25 +02:00
Irmen de Jong
a90ef274d7 fix word*128 codegen.
added cx16/landscape.p8 example that draws procedurally generated landscapes.
found bug in signed byte comparisons with overflow.
2025-05-31 05:27:19 +02:00
Irmen de Jong
341778ba67 added -timings flag 2025-05-30 12:38:16 +02:00
Irmen de Jong
ec50b5a007 homebrew info 2025-05-30 03:43:03 +02:00
Irmen de Jong
31d84c8921 doc 2025-05-29 13:35:20 +02:00
Irmen de Jong
34bedbeef1 optimize byte modulus (%) routine with repeated subtraction instead of using full division 2025-05-29 13:26:04 +02:00
Irmen de Jong
3b1b0985c1 make sizeof(float) work, so you don't have to use sys.SIZEOF_FLOAT anymore etc.
define sys.SIZEOF_FLOAT in terms of sizeof(float)
2025-05-29 12:38:03 +02:00
Irmen de Jong
368387e1a7 allow floats to be (explicitly) cast to integers 2025-05-26 21:39:48 +02:00
Irmen de Jong
9da430ffeb vm: more complete V-flag handling. somd doc and todo updates. 2025-05-23 18:58:14 +02:00
Irmen de Jong
cc063124cf add joystick control to cx16 fileselector.
fix fileselector Basic exasmple.
fixed too aggressive asm peephole optimization that destroyed %jumptable in libraries for example.
2025-05-23 17:50:11 +02:00
Irmen de Jong
3b37b89951 added cx16.joysticks_detect() and cx16.joysticks_getall() 2025-05-23 02:26:21 +02:00
Irmen de Jong
844b537d1e cobramk3 example now draws with new monogfx doublebuffering 2025-05-22 23:29:49 +02:00
Irmen de Jong
caf1d4a22a fix monogfx INVERT draw mode 2025-05-22 21:29:23 +02:00
Irmen de Jong
d8e244df99 fix monogfx example 2025-05-22 00:37:20 +02:00
Irmen de Jong
548e421e27 added doublebuffering to monogfx (in both lores and hires mode) 2025-05-22 00:10:03 +02:00
Irmen de Jong
322fa7ea69 slightly optimize monogfx plot() 2025-05-21 01:16:43 +02:00
Irmen de Jong
cf7bea0985 cleanup RTS insertion and ast postprocessing before assembly generation 2025-05-21 00:19:50 +02:00
Irmen de Jong
25d7f8808f IR: added signed multiplication opcodes 2025-05-20 21:36:05 +02:00
Irmen de Jong
acc630972a make keyboardhandler example restartable 2025-05-15 23:07:54 +02:00
Irmen de Jong
6a33be3fd8 IR: allow returning boolean in Pc/Pv cpu status register (Pz and Pn are not yet possible) 2025-05-15 22:56:45 +02:00
Irmen de Jong
f5fc4e345c fix build error on case-insensitive filesystems 2025-05-15 21:11:34 +02:00
Irmen de Jong
67231af623 fix forloop codegen over non-split word arrays of length >= 64 elements 2025-05-13 23:32:26 +02:00
Irmen de Jong
e31ef6f06f IR: fix temp register type in for x in array 2025-05-13 22:23:04 +02:00
Irmen de Jong
09d188106a different temp var mechanism for certain array expression, remove old tmpvar mechanism. 2025-05-13 21:12:10 +02:00
Irmen de Jong
d8e2116481 different temp var mechanism for for loops, and pokef() 2025-05-13 21:06:33 +02:00
Irmen de Jong
435dfbb932 optimize: rewrite suitable when into on..goto 2025-05-13 01:12:58 +02:00
Irmen de Jong
ba93966474 optimize codegen: shortcut redundant jumps in when statement 2025-05-13 00:35:22 +02:00
Irmen de Jong
ea8d17cdb2 optimized the cx16 multi-irq dispatcher used in cx16.enable_irq_handlers() 2025-05-12 23:26:54 +02:00
Irmen de Jong
082265fb25 todo 2025-05-12 00:24:57 +02:00
Irmen de Jong
9e557ce8ac add keyword 'on' to IDEA syntax 2025-05-11 23:32:54 +02:00
Irmen de Jong
e5d9af75de remove double bra/jmp 2025-05-11 23:01:13 +02:00
Irmen de Jong
31c1bf8bc5 added on..goto/call statement 2025-05-11 21:37:44 +02:00
Irmen de Jong
37d4055036 translate newline '\n' to char code 13 in various encodings such as ISO (used to be 10)
This means that when printed, such newlines will now properly go to the next line in these encodings too (ISO variants, KATAKANA).
2025-05-11 19:45:24 +02:00
Irmen de Jong
78b1076110 some more software links 2025-05-11 19:39:20 +02:00
Irmen de Jong
0a3c748e41 fix code gen bug for certain goto array[idx] 2025-05-11 17:18:20 +02:00
Irmen de Jong
ebf79ef9e2 release 11.3.2 2025-05-11 12:30:36 +02:00
Irmen de Jong
99b9370178 fix various bugs around word-indexing combined with address-of: &buffer[2000] 2025-05-10 16:22:05 +02:00
Irmen de Jong
d634061cd9 Merge pull request #169 from Frosty-J/crc
Specify X16 CRC type
2025-05-08 23:47:17 +02:00
Frosty-J
d59d8ff1fe Specify X16 CRC type 2025-05-08 05:57:30 +01:00
Irmen de Jong
53e442d509 fix regression in 11.3.1: allow bitwise operation between different types as long as they're the same size. 2025-05-07 23:06:45 +02:00
Irmen de Jong
f7cbfdff06 skip this test for now 2025-05-07 21:14:52 +02:00
Irmen de Jong
b28ee0819f revert behavior change of @dirty variables, instead document the initialization behavior correctly in the docs
they get zeroed at program startup (like other uninitialized BSS variables), just not on entry in the subroutine.
2025-05-07 21:07:12 +02:00
Irmen de Jong
522958e0e9 @dirty variables now actually end up in the uninitialized BSS_NOCLEAR section 2025-05-06 01:41:34 +02:00
Irmen de Jong
ccc6b56e35 added link to prog8reu library 2025-05-02 19:38:59 +02:00
Irmen de Jong
7eb079050c fix strings.hash() on 6502 CPU's. NOTE: now takes string pointer in AY instead of R0 on all platforms 2025-05-01 22:47:43 +02:00
Irmen de Jong
2fdd5543b2 release 11.3.1 2025-04-30 22:40:54 +02:00
Irmen de Jong
d04164c0a6 fix const evaluation of bitwise logical expressions (&, |, ^, <<, >>) of signed operands 2025-04-30 22:27:31 +02:00
Irmen de Jong
b047731f82 removed some redundant arguments 2025-04-25 23:27:06 +02:00
Irmen de Jong
4d91f92a2e clearer error message 2025-04-25 19:38:45 +02:00
Irmen de Jong
98505d27b1 IR: fix invalid removal of label chunks and include asmsubs when looking for labels 2025-04-24 23:30:51 +02:00
Irmen de Jong
cd63a58ad9 IR: get rid of problematic PREPARECALL pseudo instruction 2025-04-24 22:32:40 +02:00
Irmen de Jong
170f8dd092 IR: new CALLFAR instructions to support callfar into another bank 2025-04-24 21:17:48 +02:00
Irmen de Jong
619dcb6a84 fix error in IR register typing 2025-04-24 20:50:19 +02:00
Irmen de Jong
99ae8ea52e code cleanup 2025-04-24 18:51:03 +02:00
Irmen de Jong
dc031c30eb add -version option to just print the compiler version and then exit 2025-04-24 18:13:35 +02:00
Irmen de Jong
1e702439b7 avoid JDK 21+/Kotlin method conflict of removeLast()/removeFirst()
background: see https://www.reddit.com/r/androiddev/comments/1gspjrs/dont_use_kotlins_removefirst_and_removelast_when/
https://youtrack.jetbrains.com/issue/KT-71375/Prevent-Kotlins-removeFirst-and-removeLast-from-causing-crashes-on-Android-14-and-below-after-upgrading-to-Android-API-Level-35

it's about Android but the problem also occurs on desktop JDKs for example when running a Java21 compiled prog8 with Java17
2025-04-24 18:11:42 +02:00
Irmen de Jong
8debc42381 slightly stricter assignment type compatibility checking 2025-04-24 15:05:35 +02:00
Irmen de Jong
532d719089 more optimal math.randrange() routines 2025-04-24 04:04:14 +02:00
Irmen de Jong
b40860aca4 get rid of RTS after JMP 2025-04-23 17:08:16 +02:00
Irmen de Jong
2cbe6b5f7f info message when more optimal goto array[idx] is possible 2025-04-23 16:59:18 +02:00
Irmen de Jong
d2cc7ccdfa remove redundant variable=0 initializations (BSS clear takes care of them) 2025-04-23 14:45:38 +02:00
Irmen de Jong
2cb183c6d8 fix regression for goto array[idx] on 6502 cpu which doesn't have jmp (ptr,x) 2025-04-23 02:56:10 +02:00
189 changed files with 6677 additions and 2497 deletions

View File

@@ -68,6 +68,7 @@
<inspection_tool class="BlockingMethodInNonBlockingContext" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="BooleanConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="BooleanLiteralArgument" enabled="true" level="TEXT ATTRIBUTES" enabled_by_default="true" editorAttributes="CONSIDERATION_ATTRIBUTES" />
<inspection_tool class="BooleanMethodIsAlwaysInverted" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="BoxingBoxedValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="BusyWait" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -765,7 +766,6 @@
<inspection_tool class="MathRoundingWithIntArgument" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MavenCoroutinesDeprecation" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="MeaninglessRecordAnnotationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MemberVisibilityCanBePrivate" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="MetaAnnotationWithoutRuntimeRetention" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MethodCanBeVariableArityMethod" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="MethodNameSameAsClassName" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -917,7 +917,6 @@
<inspection_tool class="PrimitiveArrayArgumentToVariableArgMethod" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ProblematicWhitespace" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
<inspection_tool class="ProtectedMemberInFinalClass" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PublicApiImplicitType" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PublicField" enabled="false" level="INFORMATION" enabled_by_default="false">
<option name="ignoreEnums" value="false" />
<option name="ignorableAnnotations">
@@ -995,7 +994,7 @@
<option name="ignoreEmptySuperMethods" value="false" />
<option name="onlyReportWhenAnnotated" value="true" />
</inspection_tool>
<inspection_tool class="RegExpAnonymousGroup" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RegExpAnonymousGroup" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
<inspection_tool class="RemoveLiteralUnderscores" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceAllDot" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="false" level="INFORMATION" enabled_by_default="false">

8
.idea/kotlinc.xml generated
View File

@@ -7,13 +7,13 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.1" />
<option name="languageVersion" value="2.1" />
<option name="apiVersion" value="2.2" />
<option name="languageVersion" value="2.2" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-Xwhen-guards" />
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
<option name="version" value="2.2.0" />
</component>
</project>

View File

@@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.0/kotlin-stdlib-jdk8-2.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.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.1.20/kotlin-stdlib-jdk7-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.0/kotlin-stdlib-jdk7-2.2.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.0/kotlin-stdlib-jdk8-2.2.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.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.1.20/kotlin-stdlib-jdk7-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.0/kotlin-stdlib-jdk7-2.2.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.0/kotlin-stdlib-jdk8-2.2.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.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.1.20/kotlin-stdlib-jdk7-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.0/kotlin-stdlib-jdk7-2.2.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -4,8 +4,8 @@
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

1
.idea/misc.xml generated
View File

@@ -12,6 +12,7 @@
<option name="pkg" value="" />
<option name="language" value="Java" />
<option name="generateListener" value="false" />
<option name="generateVisitor" value="true" />
</PerGrammarGenerationSettings>
</list>
</option>

1
.idea/modules.xml generated
View File

@@ -15,6 +15,7 @@
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />

View File

@@ -71,6 +71,7 @@ What does Prog8 provide?
- high-level program optimizations
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
@@ -95,7 +96,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "pet32": Commodore PET (limited support)
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64OS, ...
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)

View File

@@ -1,9 +1,10 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
plugins {
kotlin("jvm") version "2.1.20"
kotlin("jvm") version "2.2.0"
}
allprojects {
@@ -18,6 +19,7 @@ allprojects {
compilerOptions {
freeCompilerArgs = listOf("-Xwhen-guards")
jvmTarget = JvmTarget.JVM_11
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
}
sourceSets.all {
languageSettings {

View File

@@ -27,7 +27,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: String?
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
fun initializeMemoryAreas(compilerOptions: CompilationOptions)

View File

@@ -14,5 +14,5 @@ interface IErrorReporter {
fun noErrorForLine(position: Position): Boolean
fun print_single_error(errormessage: String)
fun printSingleError(errormessage: String)
}

View File

@@ -6,12 +6,15 @@ import prog8.code.target.zp.C128Zeropage
import java.nio.file.Path
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -7,12 +7,15 @@ import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -27,7 +27,7 @@ class ConfigFileTarget(
override val defaultOutputType: OutputType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: String?,
override val additionalAssemblerOptions: List<String>,
val ioAddresses: List<UIntRange>,
val zpScratchB1: UInt,
val zpScratchReg: UInt,
@@ -37,7 +37,7 @@ class ConfigFileTarget(
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(8) {
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
companion object {
@@ -109,8 +109,6 @@ class ConfigFileTarget(
(customLauncherStr+"\n").lines().map { it.trimEnd() }
else emptyList()
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val assemblerOptions = assemblerOptionsStr.ifBlank { null }
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
@@ -128,7 +126,7 @@ class ConfigFileTarget(
defaultOutputType,
libraryPath,
customLauncher,
assemblerOptions,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
ioAddresses,
props.getInteger("zp_scratch_b1"),
props.getInteger("zp_scratch_reg"),

View File

@@ -6,12 +6,15 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -25,7 +25,7 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isLong -> 4 * (numElements ?: 1)
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}

View File

@@ -6,12 +6,15 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -7,12 +7,15 @@ import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
@@ -88,31 +91,6 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
return numElements // treat it as the size of the given string with the length
?: 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
}

View File

@@ -197,6 +197,7 @@ object AtasciiEncoding {
fun encode(str: String): Result<List<UByte>, CharConversionException> {
val mapped = str.map { chr ->
when (chr) {
'\r' -> 0x9bu
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly

View File

@@ -285,6 +285,7 @@ object C64osEncoding {
val screencode = encodingC64os[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 13u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@@ -5,19 +5,19 @@ import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
object Encoder: IStringEncoding {
class Encoder(val newlineToCarriageReturn: Boolean): IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.ISO -> IsoEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.encode(str, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.C64OS -> C64osEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
@@ -30,12 +30,12 @@ object Encoder: IStringEncoding {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.ISO -> IsoEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.C64OS -> C64osEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}

View File

@@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\n' -> if(newlineToCarriageReturn) 13u else 10u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@@ -27,9 +29,14 @@ open class IsoEncodingBase(charsetName: String) {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@@ -64,10 +64,11 @@ object JapaneseCharacterConverter {
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\n' -> if(newlineToCarriageReturn) 13u else 10u
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
@@ -112,9 +113,14 @@ object KatakanaEncoding {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@@ -21,7 +21,7 @@ object PetsciiEncoding {
'\ufffe', // 0x07 -> UNDEFINED
'\uf118', // 0x08 -> DISABLE CHARACTER SET SWITCHING (CUS)
'\uf119', // 0x09 -> ENABLE CHARACTER SET SWITCHING (CUS)
'\ufffe', // 0x0A -> UNDEFINED
'\n', // 0x0A -> LINE FEED (RETURN)
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\n' , // 0x0D -> LINE FEED (RETURN)
@@ -1117,6 +1117,8 @@ object PetsciiEncoding {
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 141u
'\r' -> 141u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@@ -16,12 +16,6 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
init {
if (options.floats) {
throw InternalCompilerException("C128 target doesn't yet support floating point routines")
// note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed,
// they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1.
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,

View File

@@ -19,7 +19,7 @@ class ConfigurableZeropage(
init {
if (options.floats) {
TODO("floats")
TODO("floats in configurable target zp")
}
if(SCRATCH_REG!=SCRATCH_B1+1u)
@@ -30,7 +30,7 @@ class ConfigurableZeropage(
ZeropageType.FULL -> fullsafe.forEach { free.addAll(it) }
ZeropageType.BASICSAFE -> basicsafe.forEach { free.addAll(it) }
ZeropageType.KERNALSAFE -> kernalsafe.forEach { free.addAll(it) }
ZeropageType.FLOATSAFE -> TODO("floatsafe")
ZeropageType.FLOATSAFE -> TODO("floatsafe in configurable target zp")
}
val distinctFree = free.distinct()

View File

@@ -16,10 +16,6 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
init {
if (options.floats) {
throw InternalCompilerException("PET target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,

View File

@@ -186,7 +186,7 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
else -> throw AssemblyError("weird array value element $elt")
}
}
val result = PtVariable(name, type, zeropage, align, newValue, arraySize, position)
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
result.parent = parent
result
}
@@ -432,23 +432,6 @@ class AsmGen6502Internal (
}
internal val tempVarsCounters = mutableMapOf(
BaseDataType.BOOL to 0,
BaseDataType.BYTE to 0,
BaseDataType.UBYTE to 0,
BaseDataType.WORD to 0,
BaseDataType.UWORD to 0,
BaseDataType.LONG to 0,
BaseDataType.FLOAT to 0
)
internal fun buildTempVarName(dt: BaseDataType, counter: Int): String = "prog8_tmpvar_${dt.toString().lowercase()}_$counter"
internal fun getTempVarName(dt: BaseDataType): String {
tempVarsCounters[dt] = tempVarsCounters.getValue(dt)+1
return buildTempVarName(dt, tempVarsCounters.getValue(dt))
}
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
// returns the source name of the zero page pointervar if it's already in the ZP,
// otherwise returns "P8ZP_SCRATCH_W1" which is the intermediary
@@ -656,16 +639,16 @@ class AsmGen6502Internal (
}
if(expr.splitWords) {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register))
return
}
when {
expr.type.isByteOrBool -> {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register))
}
expr.type.isWord -> {
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out(" asl a")
when (register) {
CpuRegister.A -> {}
@@ -674,8 +657,9 @@ class AsmGen6502Internal (
}
}
expr.type.isFloat -> {
require(options.compTarget.FLOAT_MEM_SIZE == 5) {"invalid float size ${expr.position}"}
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
if(options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${expr.position}")
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out("""
sta P8ZP_SCRATCH_REG
asl a
@@ -750,7 +734,7 @@ class AsmGen6502Internal (
TargetStorageKind.REGISTER -> {
val zero = PtNumber(BaseDataType.UBYTE, 0.0, value.position)
zero.parent = value
assignExpressionToRegister(zero, target.register!!, false)
assignExpressionToRegister(zero, target.register!!)
return
}
else -> { }
@@ -854,7 +838,7 @@ class AsmGen6502Internal (
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
val counterVar = createTempVarReused(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
val loopcount = if(iterations==65536) 0 else if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
out("""
ldy #>$loopcount
@@ -874,7 +858,7 @@ $repeatLabel""")
// note: A/Y must have been loaded with the number of iterations!
// the iny + double dec is microoptimization of the 16 bit loop
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UWORD, false, stmt)
out("""
cmp #0
beq +
@@ -897,13 +881,13 @@ $repeatLabel""")
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65C02)) {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.statements)
out(" dec $counterVar | bne $repeatLabel")
} else {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.statements)
@@ -915,13 +899,13 @@ $repeatLabel""")
val repeatLabel = makeLabel("repeat")
out(" cpy #0")
if(isTargetCpu(CpuType.CPU65C02)) {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.statements)
out(" dec $counterVar | bne $repeatLabel")
} else {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.statements)
@@ -930,43 +914,9 @@ $repeatLabel""")
out(endLabel)
}
private fun createRepeatCounterVar(dt: BaseDataType, preferZeropage: Boolean, stmt: PtRepeatLoop): String {
val scope = stmt.definingISub()!!
val asmInfo = subroutineExtra(scope)
var parent = stmt.parent
while(parent !is PtProgram) {
if(parent is PtRepeatLoop)
break
parent = parent.parent
}
val isNested = parent is PtRepeatLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt && it.second.endsWith("counter") }
if(existingVar!=null) {
if(!preferZeropage || existingVar.third!=null)
return existingVar.second
}
}
val counterVar = makeLabel("counter")
when(dt) {
BaseDataType.UBYTE, BaseDataType.UWORD -> {
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
result.fold(
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
)
return counterVar
}
else -> throw AssemblyError("invalidt dt")
}
}
private fun translate(stmt: PtWhen) {
val endLabel = makeLabel("when_end")
val choiceBlocks = mutableListOf<Pair<String, PtNodeGroup>>()
val choiceBlocks = mutableListOf<Pair<String, PtWhenChoice>>()
val conditionDt = stmt.value.type
if(conditionDt.isByte)
assignExpressionToRegister(stmt.value, RegisterOrPair.A)
@@ -977,13 +927,20 @@ $repeatLabel""")
val choice = choiceNode as PtWhenChoice
var choiceLabel = makeLabel("choice")
if(choice.isElse) {
require(choice.parent.children.last() === choice)
translate(choice.statements)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choiceLabel = endLabel
} else {
choiceBlocks.add(choiceLabel to choice.statements)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
if(onlyJumpLabel==null) {
choiceBlocks.add(choiceLabel to choice)
} else {
choiceLabel = onlyJumpLabel
}
}
for (cv in choice.values.children) {
val value = (cv as PtNumber).number.toInt()
@@ -1000,11 +957,14 @@ $repeatLabel""")
}
}
}
jmp(endLabel)
if(choiceBlocks.isNotEmpty())
jmp(endLabel)
for(choiceBlock in choiceBlocks.withIndex()) {
out(choiceBlock.value.first)
translate(choiceBlock.value.second)
if (choiceBlock.index < choiceBlocks.size - 1)
translate(choiceBlock.value.second.statements)
if (choiceBlock.index < choiceBlocks.size - 1 && !choiceBlock.value.second.isOnlyGotoOrReturn())
jmp(endLabel)
}
out(endLabel)
@@ -1033,6 +993,7 @@ $repeatLabel""")
val target = getJumpTarget(jump)
require(!target.needsExpressionEvaluation)
if(target.indirect) {
require(!target.indexedX)
val complementedInstruction = branchInstruction(stmt.condition, true)
out("""
$complementedInstruction +
@@ -1084,12 +1045,25 @@ $repeatLabel""")
else {
if(evaluateAddressExpression) {
val arrayIdx = jump.target as? PtArrayIndexer
if(arrayIdx!=null && !arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// more optimal assembly can be generated using JMP address,X
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
if (arrayIdx!=null) {
if (isTargetCpu(CpuType.CPU65C02)) {
if (!arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// on the 65c02, more optimal assembly can be generated using JMP (address,X)
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
} else {
// print a message when more optimal code is possible for 65C02 cpu
val variable = symbolTable.lookup(arrayIdx.variable.name)!!
if(variable is StStaticVariable && variable.length!!<=128)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
}
} else {
// print a message when more optimal code is possible for 6502 cpu
if(!arrayIdx.splitWords)
errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
}
}
// we can do the address evaluation right now and just use a temporary pointer variable
assignExpressionToVariable(jump.target, "P8ZP_SCRATCH_W1", DataType.UWORD)
@@ -1279,7 +1253,7 @@ $repeatLabel""")
}
if(addressExpr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr, false)
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr)
if (ptrAndIndex == null) return false
if(write) {
@@ -1549,6 +1523,51 @@ $repeatLabel""")
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
}
internal fun createTempVarReused(dt: BaseDataType, preferZeropage: Boolean, stmt: PtNode): String {
val scope = stmt.definingISub()!!
val asmInfo = subroutineExtra(scope)
var parent = stmt.parent
while(parent !is PtProgram) {
if(parent is PtRepeatLoop || parent is PtForLoop)
break
parent = parent.parent
}
val isNested = parent is PtRepeatLoop || parent is PtForLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt && it.second.endsWith("tempv") }
if(existingVar!=null) {
if(!preferZeropage || existingVar.third!=null) {
// println("reuse temp counter var: $dt ${existingVar.second} @${stmt.position}")
return existingVar.second
}
}
}
val counterVar = makeLabel("tempv")
// println("new temp counter var: $dt $counterVar @${stmt.position}")
when {
dt.isIntegerOrBool -> {
if(preferZeropage) {
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
result.fold(
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
)
} else {
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally
}
return counterVar
}
dt == BaseDataType.FLOAT -> {
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally, floats never on zeropage
return counterVar
}
else -> throw AssemblyError("invalid dt")
}
}
internal fun assignConstFloatToPointerAY(number: PtNumber) {
val floatConst = allocator.getFloatAsmConst(number.number)
out("""
@@ -1569,7 +1588,7 @@ $repeatLabel""")
val compare = if(useSbc) "sec | sbc" else "cmp"
fun cmpViaScratch() {
if(assignmentAsmGen.directIntoY(value)) {
assignExpressionToRegister(value, RegisterOrPair.Y, false)
assignExpressionToRegister(value, RegisterOrPair.Y)
out(" sty P8ZP_SCRATCH_REG")
} else {
out(" pha")
@@ -1628,7 +1647,7 @@ $repeatLabel""")
}
internal fun assignConditionValueToRegisterAndTest(condition: PtExpression) {
assignExpressionToRegister(condition, RegisterOrPair.A, false)
assignExpressionToRegister(condition, RegisterOrPair.A)
when(condition) {
is PtNumber,
is PtBool,

View File

@@ -508,9 +508,11 @@ private fun optimizeIncDec(linesByFour: Sequence<List<IndexedValue<String>>>): L
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
// jmp Sub + rts -> jmp Sub
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx.
// bra/jmp + bra/jmp -> remove second bra/jmp (bra bra / jmp jmp are not removed because this is likely a jump table!)
// and some other optimizations.
val mods = mutableListOf<Modification>()
@@ -520,7 +522,10 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
val third = lines[2].value
if(!haslabel(second)) {
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
if ((" jmp" in first || "\tjmp" 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)) {
if("floats.pushFAC" !in first && "floats.popFAC" !in first) { // these 2 routines depend on being called with JSR!!
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
@@ -563,6 +568,15 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
}
}
}
// only remove bra followed by jmp or jmp followed by bra
// bra bra or jmp jmp is likely part of a jump table, which should keep all entries!
if((" bra" in first || "\tbra" in first) && (" jmp" in second || "\tjmp" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
if((" jmp" in first || "\tjmp" in first) && (" bra" in second || "\tbra" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
}
/*

View File

@@ -28,6 +28,14 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> {
if(options.compTarget.additionalAssemblerOptions.isNotEmpty())
command.addAll(options.compTarget.additionalAssemblerOptions)
command.addAll(listOf("--output", program.toString(), assembly.toString()))
return command
}
when(options.output) {
OutputType.PRG -> {
// CBM machines .prg generation.
@@ -47,8 +55,7 @@ internal class AssemblyProgram(
command.add("--list=$listFile")
}
command.addAll(listOf("--output", prgFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile)
if(!options.quiet)
println("\nCreating prg for target ${compTarget.name}.")
}
@@ -69,8 +76,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", xexFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile)
if(!options.quiet)
println("\nCreating xex for target ${compTarget.name}.")
}
@@ -90,8 +96,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
if(!options.quiet)
println("\nCreating raw binary for target ${compTarget.name}.")
}
@@ -122,14 +127,10 @@ internal class AssemblyProgram(
command.add("--nostart") // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD
}
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
}
}
if(options.compTarget.additionalAssemblerOptions!=null)
assemblerCommand.add(options.compTarget.additionalAssemblerOptions!!)
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()

View File

@@ -53,7 +53,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val memread = PtMemoryByte(fcall.position)
memread.add(fcall.args[0])
memread.parent = fcall
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A)
asmgen.out(" pha")
val memtarget = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.position, memory=memread)
asmgen.assignExpressionTo(fcall.args[1], memtarget)
@@ -676,7 +676,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(fcall.args[1].asConstInteger() == 0) {
assignAsmGen.assignConstantByte(target, 0)
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
}
@@ -739,7 +739,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, fcall)
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
@@ -815,7 +815,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr floats.MOVFM")
if(resultRegister!=null) {
assignAsmGen.assignFAC1float(
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, null, null, null, resultRegister, null))
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position))
}
}

View File

@@ -89,8 +89,8 @@ internal class ForLoopsAsmGen(
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
asmgen.out(" sta $toValueVar")
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
@@ -136,7 +136,7 @@ internal class ForLoopsAsmGen(
// use self-modifying code to store the loop end comparison value
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
@@ -186,7 +186,7 @@ $modifiedLabel cmp #0 ; modified
val stepsize = range.step.asConstInteger()!!
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
@@ -296,7 +296,7 @@ $modifiedLabel cmp #0 ; modified
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out(" sta $toValueVar")
@@ -420,6 +420,7 @@ $endLabel""")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable; there is self-modifying code below
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@@ -442,7 +443,6 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@@ -508,7 +508,7 @@ $endLabel""")
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
@@ -539,7 +539,10 @@ $endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(iterableDt.elementType().base) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
@@ -576,7 +579,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
@@ -616,8 +622,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val length = numElements * 2
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
@@ -627,16 +635,16 @@ $loopLabel sty $indexVar
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127) {
if(numElements<=127) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
cpy #${numElements*2}
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
// array size is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
@@ -645,7 +653,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(length>=16) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(

View File

@@ -234,9 +234,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val param = sub.parameters[it]
val arg = call.args[it]
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
if(!registersUsed.any{r -> r.statusflag!=null || r.registerOrPair in CpuRegisters})
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
else if(registersUsed.any {it.statusflag!=null}) {
else if(registersUsed.any { r-> r.statusflag!=null }) {
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {

View File

@@ -38,7 +38,6 @@ internal class IfElseAsmGen(private val program: PtProgram,
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testBitSet, variable, bitmask) = useBIT
return translateIfBIT(stmt, jumpAfterIf, testBitSet, variable, bitmask)
return
}
val rightDt = compareCond.right.type
@@ -87,6 +86,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target)
}
else
@@ -94,6 +94,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target)
}
else
@@ -107,6 +108,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target)
}
else
@@ -114,6 +116,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target)
}
else
@@ -146,6 +149,93 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", ifElse)
}
private fun translateIfElseBodiesSignedByte(elseConditional: String, value: PtExpression, stmt: PtIfElse) {
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
val afterIfLabel = asmgen.makeLabel("afterif")
asmgen.cmpAwithByteValue(value, true)
if(stmt.hasElse()) {
// if and else blocks
val elseLabel = asmgen.makeLabel("else")
branchElse(elseLabel)
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
// no else block
branchElse(afterIfLabel)
asmgen.translate(stmt.ifScope)
}
asmgen.out(afterIfLabel)
}
private fun translateJumpElseBodiesSignedByte(elseConditional: String, value: PtExpression, jump: PtJump, elseBlock: PtNodeGroup) {
fun branchTarget(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
var target = asmgen.getJumpTarget(jump, false)
asmgen.cmpAwithByteValue(value, true)
if(target.indirect) {
branchElse("+")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
branchTarget(target.asmLabel)
}
asmgen.translate(elseBlock)
}
private fun translateIfElseBodies(elseBranchInstr: String, stmt: PtIfElse) {
// comparison value is already in A
val afterIfLabel = asmgen.makeLabel("afterif")
@@ -154,7 +244,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val elseLabel = asmgen.makeLabel("else")
asmgen.out(" $elseBranchInstr $elseLabel")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -171,10 +261,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(target.indirect) {
asmgen.out(" $falseBranch +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
asmgen.out("""
jmp (${target.asmLabel})
+""")
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
asmgen.out(" $branchInstr ${target.asmLabel}")
@@ -210,38 +299,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", stmt)
}
"<" -> translateByteLess(stmt, signed, jumpAfterIf)
"<=" -> {
// X<=Y -> Y>=X (reverse of >=)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.left, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
"<=" -> translateByteLessEqual(stmt, signed, jumpAfterIf)
">" -> translateByteGreater(stmt, signed, jumpAfterIf)
">=" -> {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
">=" -> translateByteGreaterEqual(stmt, signed, jumpAfterIf)
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
@@ -286,7 +346,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(target.indirect) {
asmgen.out(" bmi + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf, true)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -306,7 +367,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val elseLabel = asmgen.makeLabel("else")
asmgen.out(" bmi $elseLabel | beq $elseLabel")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -353,7 +414,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(target.indirect) {
asmgen.out(" bmi + | bne ++")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf, true)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -375,7 +437,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
bpl $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -402,13 +464,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun translateByteLess(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcc", "bcs", jumpAfterIf, stmt.elseScope)
else
@@ -416,25 +478,43 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteLessEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
// X<=Y -> Y>=X (reverse of >=)
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.left, stmt)
} else {
asmgen.cmpAwithByteValue(condition.left, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateByteGreater(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
asmgen.cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.left, stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A)
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null) {
var target = asmgen.getJumpTarget(jumpAfterIf, false)
if(target.indirect) {
asmgen.out(" bcc + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf, true)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -453,7 +533,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val elseLabel = asmgen.makeLabel("else")
asmgen.out(" bcc $elseLabel | beq $elseLabel")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -466,6 +546,23 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteGreaterEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@@ -535,7 +632,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
eor #128
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -562,7 +660,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
eor #128
+ bmi $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -590,7 +688,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
cmp $valueLsb
bcs +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
_jump jmp (${target.asmLabel})
+""")
@@ -615,7 +714,7 @@ _jump jmp (${target.asmLabel})
sbc $valueMsb
bcs $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -667,7 +766,8 @@ _jump jmp (${target.asmLabel})
eor #128
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -694,7 +794,7 @@ _jump jmp (${target.asmLabel})
eor #128
+ bmi $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -721,7 +821,8 @@ _jump jmp (${target.asmLabel})
sbc $valueMsb
bcc +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -744,7 +845,7 @@ _jump jmp (${target.asmLabel})
sbc $valueMsb
bcc $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -829,7 +930,8 @@ _jump jmp (${target.asmLabel})
lda $valueLsb
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -857,7 +959,7 @@ _jump jmp (${target.asmLabel})
bne $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -908,7 +1010,8 @@ _jump jmp (${target.asmLabel})
cmp #0
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -936,7 +1039,7 @@ _jump jmp (${target.asmLabel})
bne $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -973,7 +1076,8 @@ _jump jmp (${target.asmLabel})
lda $valueLsb
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1001,7 +1105,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1053,7 +1157,8 @@ _jump jmp (${target.asmLabel})
cmp #0
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1081,7 +1186,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1196,7 +1301,8 @@ _jump jmp (${target.asmLabel})
cpy $valueMsb
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1221,7 +1327,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1248,7 +1354,8 @@ _jump jmp (${target.asmLabel})
cpy $valueMsb
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1273,7 +1380,7 @@ _jump jmp (${target.asmLabel})
cpy $valueMsb
bne $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1302,7 +1409,8 @@ _jump jmp (${target.asmLabel})
cmp ${right.name}+1
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1331,7 +1439,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1361,7 +1469,8 @@ _jump jmp (${target.asmLabel})
cmp ${right.name}+1
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1390,7 +1499,7 @@ _jump jmp (${target.asmLabel})
cmp ${right.name}+1
bne $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1423,7 +1532,8 @@ _jump jmp (${target.asmLabel})
cmp #>$value
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1452,7 +1562,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@@ -1481,7 +1591,8 @@ _jump jmp (${target.asmLabel})
cmp #>$value
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1510,7 +1621,7 @@ _jump jmp (${target.asmLabel})
cmp #>$value
bne $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {

View File

@@ -15,18 +15,18 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
evalIfExpressionConditonAndBranchWhenFalse(expr.condition, falseLabel)
when {
expr.type.isByteOrBool -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
@@ -195,7 +195,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
beq $falseLabel
+""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out("""
cmp $varRight
bne +
@@ -219,7 +219,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
cmp $varRight+1
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out("""
cmp $varRight
bne $falseLabel
@@ -242,7 +242,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
beq $falseLabel
+""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out("""
cmp #<$number
bne +
@@ -265,7 +265,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
cmp #>$number
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out( """
cmp #<$number
bne $falseLabel
@@ -284,7 +284,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
ora $varname+1
beq $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | beq $falseLabel")
}
}
@@ -299,7 +299,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
ora $varname+1
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | bne $falseLabel")
}
}

View File

@@ -45,7 +45,6 @@ internal class ProgramAndVarsGen(
}
memorySlabs()
tempVars()
footer()
}
}
@@ -219,28 +218,6 @@ internal class ProgramAndVarsGen(
}
}
private fun tempVars() {
asmgen.out("; expression temp vars\n .section BSS")
for((dt, count) in asmgen.tempVarsCounters) {
if(count>0) {
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.BYTE -> asmgen.out("$name .char ?")
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.WORD -> asmgen.out("$name .sint ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
BaseDataType.LONG -> throw AssemblyError("should not have a variable with long dt only constants")
else -> throw AssemblyError("weird dt for extravar $dt")
}
}
}
}
asmgen.out(" .send BSS")
}
private fun footer() {
var relocateBssVars = false
var relocateBssSlabs = false
@@ -353,7 +330,9 @@ internal class ProgramAndVarsGen(
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
// the other variables that should be set to zero are done so as part of the BSS section.
else
throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear")
// the other variables that should be set to zero are done so as part of the BSS section clear.
}
asmgen.out(" rts\n .bend")
}
@@ -492,14 +471,14 @@ internal class ProgramAndVarsGen(
sub.children.forEach { asmgen.translate(it) }
asmgen.out("; variables")
asmgen.out(" .section BSS")
asmgen.out(" .section BSS_NOCLEAR") // these extra vars are initialized before use
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@@ -508,7 +487,7 @@ internal class ProgramAndVarsGen(
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
asmgen.out(" .send BSS")
asmgen.out(" .send BSS_NOCLEAR")
// normal statically allocated variables
val variables = varsInSubroutine
@@ -636,15 +615,30 @@ internal class ProgramAndVarsGen(
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
if(varsNoInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
asmgen.out(" .section BSS")
val (notAligned, aligned) = varsNoInit.partition { it.align==0 }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
val (dirty, clean) = varsNoInit.partition { it.dirty }
fun generate(section: String, variables: List<StStaticVariable>) {
asmgen.out(" .section $section")
val (notAligned, aligned) = variables.partition { it.align == 0 }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base })
.forEach { uninitializedVariable2asm(it) }
asmgen.out(" .send $section")
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
if(clean.isNotEmpty()) {
// clean vars end up in BSS so they're at least cleared to 0 at startup
generate("BSS", clean)
}
if(dirty.isNotEmpty()) {
// Dirty vars actually are ALSO are put into BSS so they're cleared to 0 at program startup,
// but NOT at each entry of the subroutine they're declared in.
// This saves the STZ's instructions in the subroutine, while still having deterministic start state.
// So there is no actual difference here when compared to the way non-dirty variables are allocated.
generate("BSS", dirty)
}
asmgen.out(" .send BSS")
}
if(varsWithInit.isNotEmpty()) {

View File

@@ -53,7 +53,7 @@ internal class AnyExprAsmGen(
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | clc | adc P8ZP_SCRATCH_B1")
@@ -61,7 +61,7 @@ internal class AnyExprAsmGen(
return true
}
"-" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | sec | sbc P8ZP_SCRATCH_B1")
@@ -76,7 +76,7 @@ internal class AnyExprAsmGen(
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
"&" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | and P8ZP_SCRATCH_B1")
@@ -84,7 +84,7 @@ internal class AnyExprAsmGen(
return true
}
"|" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | ora P8ZP_SCRATCH_B1")
@@ -92,7 +92,7 @@ internal class AnyExprAsmGen(
return true
}
"^", "xor" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | eor P8ZP_SCRATCH_B1")

View File

@@ -438,6 +438,8 @@ internal class AssignmentAsmGen(
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
when(val value = assign.source.expression!!) {
is PtAddressOf -> {
val source = asmgen.symbolTable.lookup(value.identifier.name)
require(source !is StConstant) { "addressOf of a constant should have been rewritten to a simple addition expression" }
val arrayDt = value.identifier.type
val sourceName =
if(value.isMsbForSplitArray)
@@ -971,7 +973,7 @@ internal class AssignmentAsmGen(
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr prog8_math.divmod_ub_asm")
asmgen.out(" jsr prog8_math.remainder_ub_asm")
if(target.register==RegisterOrPair.A)
asmgen.out(" cmp #0") // fix the status register
else
@@ -1186,19 +1188,18 @@ internal class AssignmentAsmGen(
}
} else if(dt.isWord) {
if(shifts==7 && expr.operator == "<<") {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
asmgen.out("""
; shift left 7
sty P8ZP_SCRATCH_REG ; msb
sty P8ZP_SCRATCH_REG
lsr P8ZP_SCRATCH_REG
php ; save carry
ror a
sta P8ZP_SCRATCH_REG
lda #0
plp ; restore carry
ror P8ZP_SCRATCH_REG
ror a
ldy P8ZP_SCRATCH_REG""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@@ -1313,7 +1314,7 @@ internal class AssignmentAsmGen(
// special optimization for bytearray[y] + bytevalue : no need to use a tempvar, just use adc array,y
assignExpressionToRegister(right, RegisterOrPair.A, right.type.isSigned)
if(!leftArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(leftArrayIndexer.index, RegisterOrPair.Y, false)
asmgen.assignExpressionToRegister(leftArrayIndexer.index, RegisterOrPair.Y)
if(!leftArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(leftArrayIndexer.variable)
asmgen.out(" clc | adc $arrayvarname,y")
@@ -1322,7 +1323,7 @@ internal class AssignmentAsmGen(
// special optimization for bytevalue +/- bytearray[y] : no need to use a tempvar, just use adc array,y or sbc array,y
assignExpressionToRegister(left, RegisterOrPair.A, left.type.isSigned)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(rightArrayIndexer.index, RegisterOrPair.Y, false)
asmgen.assignExpressionToRegister(rightArrayIndexer.index, RegisterOrPair.Y)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(rightArrayIndexer.variable)
if (expr.operator == "+")
@@ -1567,8 +1568,8 @@ internal class AssignmentAsmGen(
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar)
if (constOffset != null && constOffset < 256) {
// we have value + @(zpptr + 255), or value - @(zpptr+255)
if (constOffset != null) {
// we have value + @(zpptr + 255), or value - @(zpptr+255). the offset is always <256.
asmgen.out(" ldy #$constOffset")
if (operator == "+")
asmgen.out(" clc | adc ($pointername),y")
@@ -1739,6 +1740,17 @@ internal class AssignmentAsmGen(
}
}
fun requiresCmp(expr: PtExpression) =
when (expr) {
is PtFunctionCall -> {
val function = asmgen.symbolTable.lookup(expr.name)
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
}
is PtBuiltinFunctionCall -> true
is PtIfExpression -> true
else -> false
}
if(!expr.right.isSimple() && expr.operator!="xor") {
// shortcircuit evaluation into A
val shortcutLabel = asmgen.makeLabel("shortcut")
@@ -1746,15 +1758,23 @@ internal class AssignmentAsmGen(
"and" -> {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" beq $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
"or" -> {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" bne $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
else -> throw AssemblyError("invalid logical operator")
@@ -2471,8 +2491,9 @@ $endLabel""")
BaseDataType.UBYTE -> {
when(targetDt) {
BaseDataType.BOOL -> {
val compare = if(regs==RegisterOrPair.A) "cmp" else "cp${regs.toString().lowercase()}"
asmgen.out("""
cp${regs.toString().lowercase()} #0
$compare #0
beq +
ld${regs.toString().lowercase()} #1
+ st${regs.toString().lowercase()} $targetAsmVarName""")
@@ -2607,52 +2628,100 @@ $endLabel""")
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
if(arrayIndexExpr!=null) {
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
if(constIndex>0)
if(constIndex in 1..255)
asmgen.out("""
clc
adc #$constIndex
bcc +
iny
+""")
else if(constIndex>=256) {
asmgen.out("""
clc
adc #<$constIndex
pha
tya
adc #>$constIndex
tay
pla""")
}
}
else {
if(constIndex>0) {
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
} else {
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return
} else {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
clc
adc P8ZP_SCRATCH_REG
bcc +
iny
if(arrayIndexExpr.type.isWord) {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.AY, false)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pla
tay
pla
clc
adc P8ZP_SCRATCH_W1
pha
tya
adc P8ZP_SCRATCH_W1+1
tay
pla""")
}
else {
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
clc
adc P8ZP_SCRATCH_REG
bcc +
iny
+""")
}
}
else {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
val subtype = arrayDt.sub!!
if(subtype.isByteOrBool) {
// elt size 1, we're good
} else if(subtype.isWord) {
if(!arrayDt.isSplitWordArray) {
// elt size 2
asmgen.out(" asl a")
}
} else if(subtype==BaseDataType.FLOAT) {
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${arrayIndexExpr.position}")
asmgen.out("""
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG"""
)
} else throw AssemblyError("weird type $subtype")
asmgen.out("""
ldy #>$arrayName
ldy #>$sourceName
clc
adc #<$arrayName
adc #<$sourceName
bcc +
iny
+""")
@@ -2855,7 +2924,7 @@ $endLabel""")
jsr floats.MOVMF""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W1
@@ -2887,7 +2956,7 @@ $endLabel""")
TargetStorageKind.ARRAY -> {
asmgen.out(" pha")
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" pla")
asmgen.out("""
@@ -2924,7 +2993,7 @@ $endLabel""")
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
@@ -3397,7 +3466,7 @@ $endLabel""")
} else {
require(target.array.index.type.isByteOrBool)
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y)
asmgen.out(" pla | sta ${target.asmVarname},y")
}
}
@@ -3712,7 +3781,7 @@ $endLabel""")
asmgen.out(" stz ${target.asmVarname}+$indexValue")
}
else {
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.X, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.X)
asmgen.out(" stz ${target.asmVarname},x")
}
}
@@ -3802,7 +3871,7 @@ $endLabel""")
sta ${target.asmVarname}+4""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W1
@@ -3836,7 +3905,7 @@ $endLabel""")
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<${constFloat}
sty P8ZP_SCRATCH_W1

View File

@@ -170,7 +170,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(memory.address is PtBinaryExpression && tryOptimizedMemoryInplace(memory.address as PtBinaryExpression, operator, value))
return
// slower method to calculate and use the pointer to access the memory with:
asmgen.assignExpressionToRegister(memory.address, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(memory.address, RegisterOrPair.AY)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.saveRegisterStack(CpuRegister.Y, true)
if(asmgen.isTargetCpu(CpuType.CPU65C02))
@@ -205,7 +205,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, memory)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, DataType.UBYTE, operator, value.expression)
@@ -356,7 +356,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, target.array)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
@@ -439,7 +439,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UWORD)
val tempVar = asmgen.createTempVarReused(BaseDataType.UWORD, false, target.array)
asmgen.out(" sta $tempVar | stx $tempVar+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
@@ -457,7 +457,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
target.datatype.isFloat -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, target.array)
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
@@ -583,7 +583,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(address.left is PtIdentifier && asmgen.isZpVar(address.left as PtIdentifier)) {
return (address.left as PtIdentifier).name
} else {
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY)
asmgen.out(" sta P8ZP_SCRATCH_W2 | sty P8ZP_SCRATCH_W2+1")
return "P8ZP_SCRATCH_W2"
}
@@ -635,9 +635,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
return true
}
if(rightTc!=null)
asmgen.assignExpressionToRegister(rightTc.value, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(rightTc.value, RegisterOrPair.A)
else
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.A)
asmgen.out(" pha") // offset on stack
val zpPointerVarName = addrIntoZpPointer()
assignValueToA()
@@ -910,7 +910,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"-" -> 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.divmod_ub_asm")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
@@ -1019,7 +1019,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy #$value | jsr prog8_math.remainder_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"<<" -> {
@@ -1190,7 +1190,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@@ -1363,7 +1363,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" tay | lda $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@@ -1534,7 +1534,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr prog8_math.divmod_ub_asm
jsr prog8_math.remainder_ub_asm
sta $name""")
}
"<<" -> {
@@ -1921,18 +1921,16 @@ $shortcutLabel:""")
asmgen.out(" lda #0 | sta $lsb")
}
value==7 -> {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
asmgen.out("""
; shift left 7
lsr $msb
php ; save carry
lda $lsb
ror a
sta $msb
lda #0
sta $lsb
plp ; restore carry
ror $msb
ror $lsb""")
ror a
sta $lsb""")
}
value>3 -> asmgen.out("""
ldy #$value
@@ -2740,7 +2738,7 @@ $shortcutLabel:""")
"+" -> {
// name += byteexpression
if(valueDt.isUnsignedByte) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
clc
adc $name

View File

@@ -68,7 +68,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun print_single_error(errormessage: String) { /* prints nothing in tests */ }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }

View File

@@ -58,6 +58,7 @@ class TestCodegen: FunSpec({
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -67,6 +68,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -76,6 +78,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -85,6 +88,7 @@ class TestCodegen: FunSpec({
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY

View File

@@ -1,10 +1,10 @@
package prog8.codegen.experimental
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
@@ -26,7 +26,8 @@ class ExperiCodeGen: ICodeGeneratorBackend {
// this stub only writes the IR program to disk but doesn't generate anything else.
IRFileWriter(irProgram, null).write()
println("** experimental codegen stub: no assembly generated **")
if(!options.quiet)
println("** experimental codegen stub: no assembly generated **")
return EmptyProgram
}
}
@@ -34,7 +35,8 @@ class ExperiCodeGen: ICodeGeneratorBackend {
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
println("** nothing assembled **")
if(!options.quiet)
println("** nothing assembled **")
return true
}

View File

@@ -68,22 +68,22 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun assignCpuRegister(returns: StExtSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val loadCpuRegInstr = when(returns.register.registerOrPair) {
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.X -> IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.Y -> IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
RegisterOrPair.FAC1 -> IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum)
RegisterOrPair.FAC2 -> IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum)
null -> {
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
else -> throw AssemblyError("cannot load register")
when(returns.register.registerOrPair) {
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum), null)
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum), null)
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum), null)
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum), null)
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)
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum), null)
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum), null)
null -> if(returns.register.statusflag!=null)
result += assignCpuStatusFlagReturnvalue(returns.register.statusflag!!, regNum)
else
throw AssemblyError("weird CPU register")
else -> throw AssemblyError("weird CPU register")
}
addInstr(result, loadCpuRegInstr, null)
// build an assignment to store the value in the actual target.
val assign = PtAssignment(target.position)
@@ -93,6 +93,30 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
private fun assignCpuStatusFlagReturnvalue(statusflag: Statusflag, regNum: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
when(statusflag) {
Statusflag.Pc -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0)
it += IRInstruction(Opcode.ROXL, IRDataType.BYTE, reg1=regNum)
}
}
Statusflag.Pz -> TODO("find a way to assign cpu Z status bit to reg $regNum but it can already be clobbered by other return values")
Statusflag.Pn -> TODO("find a way to assign cpu Z status bit to reg $regNum but it can already be clobbered by other return values")
Statusflag.Pv -> {
val skipLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0)
it += IRInstruction(Opcode.BSTVC, labelSymbol = skipLabel)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 1)
}
result += IRCodeChunk(skipLabel, null)
}
}
return result
}
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
// augmented assignment always has just a single target
if (augAssign.target.children.single() is PtIrRegister)
@@ -110,7 +134,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val chunks = when (augAssign.operator) {
"+=" -> operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"-=" -> operatorMinusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"/=" -> operatorDivideInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"|=" -> operatorOrInplace(symbol, array, constAddress, memTarget, targetDt, value)
"or=" -> operatorLogicalOrInplace(symbol, array, constAddress, memTarget, targetDt, value)
@@ -207,7 +231,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val tr = expressionEval.translateExpression(array.index)
addToResult(result, tr, tr.resultReg, -1)
if(!array.splitWords && eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
result += codeGen.multiplyByConst(DataType.UBYTE, tr.resultReg, eltSize)
return tr.resultReg
}
@@ -769,7 +793,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
private fun operatorMultiplyInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? {
private fun operatorMultiplyInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks? {
if(array!=null) {
val eltSize = codeGen.program.memsizer.memorySize(array.type, null)
val result = mutableListOf<IRCodeChunkBase>()
@@ -783,7 +807,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val valueReg=codeGen.registers.next(eltDt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue)
it += IRInstruction(Opcode.MULM, eltDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
it += IRInstruction(opcode, eltDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
}
}
return result
@@ -803,22 +828,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val tr = expressionEval.translateExpression(operand)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.MULM, vmDt, fpReg1 = tr.resultFpReg, address = constAddress)
IRInstruction(Opcode.MULSM, vmDt, fpReg1 = tr.resultFpReg, address = constAddress)
else
IRInstruction(Opcode.MULM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol)
, null)
}
} else {
if(constFactorRight!=null && !constFactorRight.type.isFloat) {
val factor = constFactorRight.number.toInt()
result += codeGen.multiplyByConstInplace(vmDt, constAddress, symbol, factor)
result += codeGen.multiplyByConstInplace(vmDt, signed, constAddress, symbol, factor)
} else {
val tr = expressionEval.translateExpression(operand)
addToResult(result, tr, tr.resultReg, -1)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.MULM, vmDt, reg1=tr.resultReg, address = constAddress)
IRInstruction(opcode, vmDt, reg1=tr.resultReg, address = constAddress)
else
IRInstruction(Opcode.MULM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
IRInstruction(opcode, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
, null)
}
}

View File

@@ -3,6 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.intermediate.*
@@ -78,7 +79,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentwordTr = exprGen.translateExpression(call.args[2])
@@ -91,7 +91,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentA = exprGen.translateExpression(call.args[2])
@@ -143,7 +142,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
@@ -240,11 +238,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
BaseDataType.UWORD -> {
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val resultReg = codeGen.registers.next(IRDataType.BYTE) // sqrt of a word still produces just a byte result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
BaseDataType.FLOAT -> {
addToResult(result, tr, -1, tr.resultFpReg)
@@ -278,7 +276,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
val minimumTr = exprGen.translateExpression(call.args[1])
@@ -666,7 +663,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
@@ -687,7 +684,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)

View File

@@ -1,14 +1,8 @@
package prog8.codegen.intermediate
import prog8.code.StExtSub
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StSub
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.Statusflag
import prog8.code.core.*
import prog8.intermediate.*
@@ -184,12 +178,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexWordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexTr.resultReg), null)
val indexWordReg = if(indexTr.dt==IRDataType.BYTE) {
val ixWord = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=ixWord, reg2=indexTr.resultReg), null)
ixWord
} else indexTr.resultReg
if(expr.identifier.type.isUnsignedWord) {
require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
val ptr = codeGen.symbolTable.lookup(expr.identifier.name)
it += if(ptr is StConstant)
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value.toInt())
else
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
}
} else {
@@ -289,7 +290,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val haystackVar = check.haystackHeapVar!!
when {
haystackVar.type.isString -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@@ -300,7 +300,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isByteArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@@ -314,7 +313,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isWordArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@@ -328,7 +326,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isFloatArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(haystackVar)
@@ -392,7 +389,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
if(eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
result += codeGen.multiplyByConst(DataType.UBYTE, tr.resultReg, eltSize)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=tr.resultReg, labelSymbol = arrayVarSymbol), null)
@@ -565,8 +562,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"*" -> operatorMultiply(binExpr, binExpr.left.type)
"/" -> operatorDivide(binExpr, binExpr.left.type)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
@@ -618,7 +615,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (callTarget) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -670,7 +666,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is StExtSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -709,9 +704,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
Opcode.CALL,
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
}
else {
TODO("callfar into another bank is not implemented for the selected compilation target")
} else if(address.constbank!=null) {
IRInstruction(
Opcode.CALLFAR,
address = address.address.toInt(),
immediate = address.constbank!!.toInt()
)
} else {
val tr = translateExpression(address.varbank!!)
require(tr.dt==IRDataType.BYTE)
result += tr.chunks
IRInstruction(
Opcode.CALLFARVB,
address = address.address.toInt(),
reg1 = tr.resultReg
)
}
}
addInstr(result, call, null)
@@ -1039,7 +1046,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun loadStatusAsBooleanResult(branchForTrue: Opcode, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@@ -1054,7 +1061,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun compareRegisterAsBooleanResult(branchForTrue: Opcode, dt: IRDataType, reg1: Int, reg2: Int, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@@ -1214,7 +1221,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDivide(binExpr: PtBinaryExpression, vmDt: IRDataType, signed: Boolean): ExpressionCodeResult {
private fun operatorDivide(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorRight = binExpr.right as? PtNumber
if(vmDt==IRDataType.FLOAT) {
@@ -1229,7 +1237,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, if(signed)
addInstr(result, if(dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
@@ -1241,13 +1249,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, signed)
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, dt.isSigned)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(binExpr.right is PtNumber) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
addInstr(result, if (signed)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
else
IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
@@ -1258,7 +1266,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, if (signed)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
else
IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
@@ -1269,7 +1277,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorMultiply(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorLeft = binExpr.left as? PtNumber
val constFactorRight = binExpr.right as? PtNumber
@@ -1291,7 +1300,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@@ -1299,20 +1308,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorLeft.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else if(constFactorRight!=null && !constFactorRight.type.isFloat) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
val opcode = if(dt.isSigned) Opcode.MULSR else Opcode.MULR
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}

View File

@@ -48,6 +48,7 @@ class IRCodeGen(
irProg.linkChunks()
irProg.convertAsmChunks()
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors)
irProg.validate()
@@ -64,15 +65,17 @@ class IRCodeGen(
if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) {
val block = variable.parent.astNode as PtBlock
val initialization = (block.children.firstOrNull {
it is PtAssignment && it.target.identifier?.name==variable.scopedName
it is PtAssignment && it.isVarInitializer && it.target.identifier?.name==variable.scopedName
} as PtAssignment?)
val initValue = initialization?.value
when(initValue){
is PtBool -> {
require(initValue.asInt()!=0) { "boolean var should not be initialized with false, it wil be set to false as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.asInt().toDouble())
initsToRemove += block to initialization
}
is PtNumber -> {
require(initValue.number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}
@@ -194,10 +197,7 @@ class IRCodeGen(
old.fpReg1,
old.fpReg2,
immediate = immediateValue,
null,
address = addressValue,
null,
null
address = addressValue
)
}
}
@@ -256,8 +256,8 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("string should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
else -> TODO("missing codegen for $node")
}
@@ -420,35 +420,44 @@ class IRCodeGen(
whenStmt.choices.children.forEach {
val choice = it as PtWhenChoice
if(choice.isElse) {
require(choice.parent.children.last() === choice)
result += translateNode(choice.statements)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
result += IRCodeChunk(null, null).also { chunk ->
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
}
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
val branchLabel: String
if(onlyJumpLabel==null) {
choices.add(choiceLabel to choice)
branchLabel = choiceLabel
} else {
branchLabel = onlyJumpLabel
}
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
result += IRCodeChunk(null, null).also { chunk ->
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = branchLabel)
}
}
}
}
}
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
if(choices.isNotEmpty())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
choices.forEach { (label, choice) ->
result += labelFirstChunk(translateNode(choice.statements), label)
val lastStatement = choice.statements.children.last()
if(lastStatement !is PtReturn && lastStatement !is PtJump)
if(!choice.isOnlyGotoOrReturn())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
}
@@ -469,10 +478,11 @@ class IRCodeGen(
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
val elementDt = irType(iterable.type.elementType())
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(elementDt)
val loopLabel = createLabelName()
val endLabel = createLabelName()
when {
@@ -480,9 +490,9 @@ class IRCodeGen(
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
@@ -493,8 +503,7 @@ class IRCodeGen(
}
iterable.type.isSplitWordArray -> {
// iterate over lsb/msb split word array
val elementDt = iterable.type.elementType()
if(!elementDt.isWord)
if(elementDt!=IRDataType.WORD)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
@@ -504,7 +513,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
@@ -771,7 +780,7 @@ class IRCodeGen(
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
IRInstruction(Opcode.MULS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
@@ -789,38 +798,40 @@ class IRCodeGen(
val factorReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
}
return code
}
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
internal fun multiplyByConst(dt: DataType, reg: Int, factor: Int): IRCodeChunk {
val irdt = irType(dt)
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
code += IRInstruction(Opcode.LSL, irdt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
code += IRInstruction(Opcode.LSLN, irdt, reg1=reg, reg2=pow2reg)
} else {
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0)
IRInstruction(Opcode.LOAD, irdt, reg1=reg, immediate = 0)
} else {
IRInstruction(Opcode.MUL, dt, reg1=reg, immediate = factor)
val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL
IRInstruction(opcode, irdt, reg1=reg, immediate = factor)
}
}
return code
}
internal fun multiplyByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
internal fun multiplyByConstInplace(dt: IRDataType, signed: Boolean, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
@@ -850,10 +861,11 @@ class IRCodeGen(
else {
val factorReg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, dt, reg1=factorReg, address = knownAddress)
IRInstruction(opcode, dt, reg1=factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
IRInstruction(opcode, dt, reg1=factorReg, labelSymbol = symbol)
}
}
return code
@@ -1070,7 +1082,7 @@ class IRCodeGen(
}
// evaluate jump address expression into a register and jump indirectly to it
val tr = expressionEval.translateExpression(goto.target)
for(i in tr.chunks.flatMap { it.instructions }) {
for(i in tr.chunks.flatMap { c -> c.instructions }) {
it += i
}
it += IRInstruction(Opcode.JUMPI, reg1 = tr.resultReg)
@@ -1954,7 +1966,7 @@ class IRCodeGen(
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("weird statusflag as param")
else -> throw AssemblyError("unsupported statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}

View File

@@ -23,6 +23,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun optimizeOnlyJoinChunks() {
// this chunk-joining is REQUIRED (optimization or no) to end up with a structurally sound chunk list
irprog.foreachSub { sub ->
joinChunks(sub)
removeEmptyChunks(sub)
@@ -84,7 +85,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original, otherwise merge both labels into 1.
If next chunk has label -> label name should be the same, in which case remove original, otherwise leave everything untouched.
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
@@ -111,14 +112,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
if(index>0) {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
removeChunks += index
replaceLabels[chunk.label!!] = nextchunk.label!!
replaceLabels.entries.forEach { (key, value) ->
if (value == chunk.label)
replaceLabels[key] = nextchunk.label!!
}
}
}
}
}
@@ -364,7 +357,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MULS, Opcode.MOD -> {
if (ins.immediate == 1) {
chunk.instructions.removeAt(idx)
changed = true

View File

@@ -48,6 +48,36 @@ class IRUnusedCodeRemover(
}
irprog.st.removeTree(blockLabel)
removeBlockInits(irprog, blockLabel)
}
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
val instructions = code.globalInits.instructions
instructions.toTypedArray().forEach {ins ->
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
instructions.remove(ins)
}
}
// remove stray loads
instructions.toTypedArray().forEach { ins ->
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
if(ins.reg1!=0) {
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
else if(ins.fpReg1!=0) {
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
}
}
}
private fun removeUnusedSubroutines(): Int {

View File

@@ -52,7 +52,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align)
variable.align,
variable.dirty)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
@@ -76,7 +77,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align
variable.align,
variable.dirty
)
}
}

View File

@@ -66,7 +66,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun print_single_error(errormessage: String) { /* prints nothing in tests */ }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }

View File

@@ -53,7 +53,7 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().single().instructions.size shouldBe 1
}
test("remove jmp to label below") {
test("remove jmp to label below but keep labels") {
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
val c2 = IRCodeChunk("label", null)
@@ -69,13 +69,16 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().size shouldBe 3
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label2"
irProg.chunks()[2].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe false
irProg.chunks()[2].isEmpty() shouldBe true
val chunks = irProg.chunks()
chunks.size shouldBe 4
chunks[0].label shouldBe "main.start"
chunks[1].label shouldBe "label"
chunks[2].label shouldBe "label2"
chunks[3].label shouldBe "label3"
chunks[0].isEmpty() shouldBe true
chunks[1].isEmpty() shouldBe true
chunks[2].isEmpty() shouldBe false
chunks[3].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP

View File

@@ -52,6 +52,7 @@ class TestVmCodeGen: FunSpec({
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -61,6 +62,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -70,6 +72,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -79,6 +82,7 @@ class TestVmCodeGen: FunSpec({
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -167,6 +171,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -238,6 +243,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -305,6 +311,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -360,6 +367,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -431,6 +439,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -498,6 +507,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -554,11 +564,8 @@ class TestVmCodeGen: FunSpec({
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks[0].instructions.size shouldBe 2
val preparecallInstr = irChunks[0].instructions[0]
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
preparecallInstr.immediate shouldBe 0
val callInstr = irChunks[0].instructions[1]
irChunks[0].instructions.size shouldBe 1
val callInstr = irChunks[0].instructions[0]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}

View File

@@ -5,10 +5,7 @@ import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.BaseDataType
import prog8.code.core.Position
import prog8.code.core.isInteger
import prog8.code.core.isIntegerOrBool
import prog8.code.core.*
import kotlin.math.*
@@ -70,52 +67,45 @@ class ConstExprEvaluator {
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() xor right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
}
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() or right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() or right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() or right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
}
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() and right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() and right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() and right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)

View File

@@ -129,7 +129,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl(program)
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")
}
@@ -332,7 +332,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val constIndex = arrayIndexedExpression.indexer.constIndex()
if (constIndex != null) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl()
if(arrayVar!=null) {
val array =arrayVar.value as? ArrayLiteral
if(array!=null) {
@@ -387,7 +387,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val rangeTo = iterableRange.to as? NumericLiteral
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: return noModifications
val loopvar = forLoop.loopVar.targetVarDecl() ?: return noModifications
val stepLiteral = iterableRange.step as? NumericLiteral
require(loopvar.datatype.sub == null)

View File

@@ -49,7 +49,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
@@ -57,7 +57,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
@@ -560,7 +560,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
else if(functionCallExpr.target.nameInSource == listOf("strings", "contains")) {
val target = (functionCallExpr.args[0] as? IdentifierReference)?.targetVarDecl(program)
val target = (functionCallExpr.args[0] as? IdentifierReference)?.targetVarDecl()
if(target?.value is StringLiteral) {
errors.info("for actual strings, use a regular containment check instead: 'char in string'", functionCallExpr.position)
val contains = ContainmentCheck(functionCallExpr.args[1], functionCallExpr.args[0], functionCallExpr.position)

View File

@@ -16,7 +16,7 @@ import prog8.code.target.VMTarget
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.isEmpty()
// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined.
// inliner potentially enables *ONE LINED* subroutines, without to be inlined.
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
@@ -105,7 +105,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
if (subroutine.inline && subroutine.statements.size > 1) {
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
subroutine.statements.removeLastOrNull() // get rid of the Return, to be able to inline the (single) statement preceding it.
}
}
}
@@ -122,7 +122,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: FunctionCallStatement) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
call.target.targetSubroutine()?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
@@ -134,7 +134,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: FunctionCallExpression) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
call.target.targetSubroutine()?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {

View File

@@ -33,8 +33,8 @@ class StatementOptimizer(private val program: Program,
} else {
arg as? IdentifierReference
}
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
val string = stringVar.targetVarDecl(program)?.value as? StringLiteral
if(stringVar!=null && stringVar.wasStringLiteral()) {
val string = stringVar.targetVarDecl()?.value as? StringLiteral
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
@@ -82,12 +82,11 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse)
)
}
@@ -106,7 +105,7 @@ class StatementOptimizer(private val program: Program,
if(ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
@@ -146,13 +145,13 @@ class StatementOptimizer(private val program: Program,
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl()
if(iterable!=null) {
if(iterable.datatype.isString) {
val sv = iterable.value as StringLiteral
@@ -161,7 +160,7 @@ class StatementOptimizer(private val program: Program,
// loop over string of length 1 -> just assign the single character
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(BaseDataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@@ -173,7 +172,7 @@ class StatementOptimizer(private val program: Program,
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignmentOrigin.OPTIMIZER, forLoop.position))
@@ -466,7 +465,7 @@ class StatementOptimizer(private val program: Program,
return IfElse(
compare,
AnonymousScope(mutableListOf(assign), position),
AnonymousScope(mutableListOf(), position),
AnonymousScope.empty(),
position
)
}
@@ -501,6 +500,27 @@ class StatementOptimizer(private val program: Program,
}
}
if(whenStmt.betterAsOnGoto(program, options)) {
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
var elseJump: Jump? = null
val jumps = mutableListOf<Pair<Int, Jump>>()
whenStmt.choices.forEach { choice ->
if(choice.values==null) {
elseJump = choice.statements.statements.single() as Jump
} else {
choice.values!!.forEach { value ->
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
}
}
}
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, onGoto, parent))
}
return noModifications
}

View File

@@ -8,9 +8,9 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.compiler.CallGraph
@@ -27,10 +27,10 @@ class UnusedCodeRemover(private val program: Program,
program.allBlocks.singleOrNull { it.name=="sys" } ?.let {
val subroutines = it.statements.filterIsInstance<Subroutine>()
val push = subroutines.single { it.name == "push" }
val pushw = subroutines.single { it.name == "pushw" }
val pop = subroutines.single { it.name == "pop" }
val popw = subroutines.single { it.name == "popw" }
val push = subroutines.single { s -> s.name == "push" }
val pushw = subroutines.single { s -> s.name == "pushw" }
val pop = subroutines.single { s -> s.name == "pop" }
val popw = subroutines.single { s -> s.name == "popw" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pushw)
neverRemoveSubroutines.add(pop)
@@ -39,8 +39,8 @@ class UnusedCodeRemover(private val program: Program,
program.allBlocks.singleOrNull { it.name=="floats" } ?.let {
val subroutines = it.statements.filterIsInstance<Subroutine>()
val push = subroutines.single { it.name == "push" }
val pop = subroutines.single { it.name == "pop" }
val push = subroutines.single { s -> s.name == "push" }
val pop = subroutines.single { s -> s.name == "pop" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pop)
}

View File

@@ -317,10 +317,25 @@ c128 {
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
extsub $FF6E = JSRFAR()
extsub $FF68 = SETBNK(ubyte databank @A, ubyte filenamebank @X)
extsub $FF47 = SPIN_SPOUT() clobbers(A) ; set up serial bus for fast communications mode
extsub $FF4A = CLOSE_ALL(ubyte device @X) clobbers(X) ; close all channels to specific device
extsub $FF4D = C64_MODE() ; restart machine in C64 mode (does not return)
extsub $FF50 = DMA_CALL(ubyte bank @X, ubyte command @Y) clobbers(A,X) ; send a command to a DMA device
extsub $FF53 = BOOT_CALL(ubyte device @X, ubyte drive @A) clobbers(A,X,Y) ; try to autoboot the given disk
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 $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
extsub $FF6B = GETCFG(ubyte bank @X) -> ubyte @A ; translate bank number to MMU configuration register value
extsub $FF6E = JSRFAR() clobbers(A,X) ; call routine in another bank (parameters set in zero page addresses 2-8)
extsub $FF71 = JMPFAR() clobbers(A,X) ; jump without return to another bank (parameters set as for JSRFAR)
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
; ---- C128 specific system utility routines: ----
@@ -419,12 +434,13 @@ sys {
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -946,6 +962,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -984,6 +1012,7 @@ cx16 {
&uword r14 = $1bfc
&uword r15 = $1bfe
; signed word versions
&word r0s = $1be0
&word r1s = $1be2
&word r2s = $1be4
@@ -1001,6 +1030,7 @@ cx16 {
&word r14s = $1bfc
&word r15s = $1bfe
; ubyte versions (low and high bytes)
&ubyte r0L = $1be0
&ubyte r1L = $1be2
&ubyte r2L = $1be4
@@ -1035,6 +1065,7 @@ cx16 {
&ubyte r14H = $1bfd
&ubyte r15H = $1bff
; signed byte versions (low and high bytes)
&byte r0sL = $1be0
&byte r1sL = $1be2
&byte r2sL = $1be4
@@ -1069,6 +1100,42 @@ cx16 {
&byte r14sH = $1bfd
&byte r15sH = $1bff
; boolean versions (low and high bytes)
&bool r0bL = $1be0
&bool r1bL = $1be2
&bool r2bL = $1be4
&bool r3bL = $1be6
&bool r4bL = $1be8
&bool r5bL = $1bea
&bool r6bL = $1bec
&bool r7bL = $1bee
&bool r8bL = $1bf0
&bool r9bL = $1bf2
&bool r10bL = $1bf4
&bool r11bL = $1bf6
&bool r12bL = $1bf8
&bool r13bL = $1bfa
&bool r14bL = $1bfc
&bool r15bL = $1bfe
&bool r0bH = $1be1
&bool r1bH = $1be3
&bool r2bH = $1be5
&bool r3bH = $1be7
&bool r4bH = $1be9
&bool r5bH = $1beb
&bool r6bH = $1bed
&bool r7bH = $1bef
&bool r8bH = $1bf1
&bool r9bH = $1bf3
&bool r10bH = $1bf5
&bool r11bH = $1bf7
&bool r12bH = $1bf9
&bool r13bH = $1bfb
&bool r14bH = $1bfd
&bool r15bH = $1bff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@@ -439,12 +439,13 @@ sys {
const ubyte target = 64 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -968,6 +969,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -1006,6 +1019,7 @@ cx16 {
&uword r14 = $cffc
&uword r15 = $cffe
; signed word versions
&word r0s = $cfe0
&word r1s = $cfe2
&word r2s = $cfe4
@@ -1023,6 +1037,7 @@ cx16 {
&word r14s = $cffc
&word r15s = $cffe
; ubyte versions (low and high bytes)
&ubyte r0L = $cfe0
&ubyte r1L = $cfe2
&ubyte r2L = $cfe4
@@ -1057,6 +1072,7 @@ cx16 {
&ubyte r14H = $cffd
&ubyte r15H = $cfff
; signed byte versions (low and high bytes)
&byte r0sL = $cfe0
&byte r1sL = $cfe2
&byte r2sL = $cfe4
@@ -1091,6 +1107,42 @@ cx16 {
&byte r14sH = $cffd
&byte r15sH = $cfff
; boolean versions
&bool r0bL = $cfe0
&bool r1bL = $cfe2
&bool r2bL = $cfe4
&bool r3bL = $cfe6
&bool r4bL = $cfe8
&bool r5bL = $cfea
&bool r6bL = $cfec
&bool r7bL = $cfee
&bool r8bL = $cff0
&bool r9bL = $cff2
&bool r10bL = $cff4
&bool r11bL = $cff6
&bool r12bL = $cff8
&bool r13bL = $cffa
&bool r14bL = $cffc
&bool r15bL = $cffe
&bool r0bH = $cfe1
&bool r1bH = $cfe3
&bool r2bH = $cfe5
&bool r3bH = $cfe7
&bool r4bH = $cfe9
&bool r5bH = $cfeb
&bool r6bH = $cfed
&bool r7bH = $cfef
&bool r8bH = $cff1
&bool r9bH = $cff3
&bool r10bH = $cff5
&bool r11bH = $cff7
&bool r12bH = $cff9
&bool r13bH = $cffb
&bool r14bH = $cffd
&bool r15bH = $cfff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@@ -28,12 +28,20 @@
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
; For now, you MUST call yield() only from the actual subroutine that has been registered as a task!
; (this is because otherwise the cpu call stack gets messed up and an RTS in task1 could suddenly pop a return address belonging to another tasks' call frame)
; - call current() to get the current task id.
; - call kill(taskid) to kill a task by id.
; - call killall() to kill all tasks.
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
;
; TIP: HOW TO WAIT without BLOCKING other coroutines?
; Make sure you call yield() in the waiting loop, for example:
; uword timer = cbm.RDTIM16() + 60
; while cbm.RDTIM16() != timer
; void coroutines.yield()
coroutines {
%option ignore_unused
@@ -41,19 +49,17 @@ coroutines {
const ubyte MAX_TASKS = 64
uword[MAX_TASKS] tasklist
uword[MAX_TASKS] userdatas
uword[MAX_TASKS] returnaddresses
ubyte active_task
uword supervisor
sub add(uword taskaddress, uword userdata) -> ubyte {
sub add(uword @nozp taskaddress, uword @nozp userdata) -> ubyte {
; find the next empty slot in the tasklist and stick it there
; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id!
; also returns the success in the Carry flag (carry set=success, carry clear = task was not added)
for cx16.r0L in 0 to len(tasklist)-1 {
if tasklist[cx16.r0L] == 0 {
tasklist[cx16.r0L] = taskaddress
tasklist[cx16.r0L] = sys.get_as_returnaddress(taskaddress)
userdatas[cx16.r0L] = userdata
returnaddresses[cx16.r0L] = 0
sys.set_carry()
return cx16.r0L
}
@@ -70,57 +76,48 @@ coroutines {
}
}
sub run(uword supervisor_routine) {
sub run(uword @nozp supervisor_routine) {
supervisor = supervisor_routine
for active_task in 0 to len(tasklist)-1 {
if tasklist[active_task]!=0 {
; activate the termination handler and start the first task
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(&termination)
goto tasklist[active_task]
sys.pushw(tasklist[active_task])
return
}
}
}
sub yield() -> uword {
; Store the return address of the yielding task,
; and continue with the next one instead (round-robin)
; Returns the associated userdata value
uword task_start, task_continue
returnaddresses[active_task] = sys.popw()
; Store the return address of the yielding task, and continue with the next one instead (round-robin)
; Returns the associated userdata value.
; NOTE: CAN ONLY BE CALLED FROM THE SCOPE OF THE SUBROUTINE THAT HAS BEEN REGISTERED AS THE TASK!
uword task_return_address
tasklist[active_task] = sys.popw()
resume_with_next_task:
skip_task:
if not next_task() {
void sys.popw() ; remove return to the termination handler
return 0 ; exiting here will now actually return from the start() call back to the calling program :)
return 0 ; exiting here will now actually return back to the calling program that called run()
}
if supervisor!=0 {
if supervisor!=0
if lsb(call(supervisor))==0
goto resume_with_next_task
}
goto skip_task
if task_continue==0 {
; fetch start address of next task.
; address on the stack must be pushed in reverse byte order
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(task_start)
} else
sys.pushw(task_continue)
; returning from yield then continues with the next coroutine
; returning from yield then continues with the next coroutine:
sys.pushw(task_return_address)
return userdatas[active_task]
sub next_task() -> bool {
; search through the task list for the next active task
repeat len(tasklist) {
active_task++
if active_task==len(returnaddresses)
if active_task==len(tasklist)
active_task=0
task_start = tasklist[active_task]
if task_start!=0 {
task_continue = returnaddresses[active_task]
task_return_address = tasklist[active_task]
if task_return_address!=0 {
return true
}
}
@@ -128,9 +125,8 @@ resume_with_next_task:
}
}
sub kill(ubyte taskid) {
sub kill(ubyte @nozp taskid) {
tasklist[taskid] = 0
returnaddresses[taskid] = 0
}
sub current() -> ubyte {
@@ -138,12 +134,10 @@ resume_with_next_task:
}
sub termination() {
; a task has terminated. wipe it from the list.
; this is an internal routine
; internal routine: a task has terminated. wipe it from the list.
kill(active_task)
; reactivate this termination handler
; note: cannot use pushw() because JSR doesn't push the return address in the same way
; reactivate this termination handler and go to the next task
sys.push_returnaddress(&termination)
goto coroutines.yield.resume_with_next_task
goto coroutines.yield.skip_task
}
}

View File

@@ -22,18 +22,23 @@ monogfx {
const ubyte MODE_STIPPLE = %00000001
const ubyte MODE_INVERT = %00000010
uword buffer_visible, buffer_back
sub lores() {
; enable 320*240 bitmap mode
buffer_visible = buffer_back = $0000
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
cx16.VERA_L1_TILEBASE = 0 ; lores
width = 320
height = 240
lores_mode = true
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
@@ -46,14 +51,40 @@ monogfx {
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
cx16.VERA_L1_TILEBASE = %00000001 ; hires
width = 640
height = 480
lores_mode = false
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
sub enable_doublebuffer() {
; enable double buffering mode
if lores_mode {
buffer_visible = $0000
buffer_back = $2800
} else {
buffer_visible = $0000
buffer_back = $9800
}
}
sub swap_buffers(bool wait_for_vsync) {
; flip the buffers: make the back buffer visible and the other one now the backbuffer.
; to avoid any screen tearing it is advised to call this during the vertical blank (pass true)
if wait_for_vsync
sys.waitvsync()
cx16.r0 = buffer_back
buffer_back = buffer_visible
buffer_visible = cx16.r0
cx16.VERA_CTRL = 0
cx16.r0 &= %1111110000000000
cx16.VERA_L1_TILEBASE = cx16.VERA_L1_TILEBASE & 1 | (cx16.r0H >>1 )
}
sub textmode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
@@ -559,6 +590,7 @@ drawmode: ora cx16.r15L
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@@ -597,6 +629,7 @@ drawmode: ora cx16.r15L
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@@ -672,8 +705,7 @@ nostipple:
invert:
prepare()
%asm {{
lda cx16.VERA_DATA0
eor p8v_maskbits,y
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
}}
return
@@ -696,7 +728,7 @@ invert:
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda p8v_xx
@@ -708,18 +740,29 @@ invert:
; width=640 (hires)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx
and #7
pha ; xbits
; xx /= 8
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx
}}
xx /= 8
;xx /= 8
xx += yy*(640/8)
%asm {{
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
plx ; xbits
lda p8v_maskbits,x
}}
@@ -761,11 +804,15 @@ invert:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@@ -848,8 +895,8 @@ skip:
}
sub fill_scanline_right() {
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
while xx <= width-1 {
if pgetset()
break
@@ -884,11 +931,15 @@ skip:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xpos+1
sta cx16.VERA_ADDR_M
lda p8v_xpos
sta cx16.VERA_ADDR_L
lda p8v_xpos+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@@ -935,12 +986,12 @@ _doplot beq +
ror a
lsr a
lsr a
clc
ldy p8v_yy
clc
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00010000 ; autoincr
sta cx16.VERA_ADDR_H
@@ -948,7 +999,33 @@ _doplot beq +
}
else {
cx16.r0 = yy*(640/8)
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
;cx16.r0 += xx/8
%asm {{
ldy p8v_xx+1
lda p8v_xx
sty P8ZP_SCRATCH_B1
lsr P8ZP_SCRATCH_B1
ror a
lsr P8ZP_SCRATCH_B1
ror a
lsr a
clc
adc cx16.r0
sta cx16.r0
bcc +
inc cx16.r0+1
+
stz cx16.VERA_CTRL
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.r0H
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00001000 ; autoincr (1 bit shifted)
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
}}
}
return
}
@@ -1116,15 +1193,11 @@ cdraw_mod2 ora cx16.VERA_DATA1
cmp #0
beq +
lda #255
+ ldy #80
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
+ ldy #40
-
.rept 16
sta cx16.VERA_DATA0
.endrept
dey
bne -
rts

View File

@@ -177,6 +177,7 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
; signed word versions
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
@@ -194,6 +195,7 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@@ -228,6 +230,7 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
; signed byte versions (low and high bytes)
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
@@ -262,6 +265,42 @@ cx16 {
&byte r14sH = $001f
&byte r15sH = $0021
; boolean versions
&bool r0bL = $0002
&bool r1bL = $0004
&bool r2bL = $0006
&bool r3bL = $0008
&bool r4bL = $000a
&bool r5bL = $000c
&bool r6bL = $000e
&bool r7bL = $0010
&bool r8bL = $0012
&bool r9bL = $0014
&bool r10bL = $0016
&bool r11bL = $0018
&bool r12bL = $001a
&bool r13bL = $001c
&bool r14bL = $001e
&bool r15bL = $0020
&bool r0bH = $0003
&bool r1bH = $0005
&bool r2bH = $0007
&bool r3bH = $0009
&bool r4bH = $000b
&bool r5bH = $000d
&bool r6bH = $000f
&bool r7bH = $0011
&bool r8bH = $0013
&bool r9bH = $0015
&bool r10bH = $0017
&bool r11bH = $0019
&bool r12bH = $001b
&bool r13bH = $001d
&bool r14bH = $001f
&bool r15bH = $0021
; VERA registers
const uword VERA_BASE = $9F20
@@ -474,7 +513,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
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()
; 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)
@@ -1168,8 +1207,39 @@ asmsub restore_vera_context() clobbers(A) {
}}
}
sub joysticks_detect() -> ubyte {
; returns bits 0-4, set to 1 if that joystick is present.
; bit 0 = keyboard joystick, bit 1 - 4 = joypads 1 to 4
cx16.r0H = 255
for cx16.r0L in 4 downto 0 {
void cx16.joystick_get(cx16.r0L)
%asm {{
cpy #1 ; present?
}}
rol(cx16.r0H)
}
return ~cx16.r0H
}
; Commander X16 IRQ dispatcher routines
sub joysticks_getall(bool also_keyboard_js) -> uword {
; returns combined pressed buttons from all connected joysticks
; note: returns the 'normal' not inverted status bits for the buttons (1 = button pressed.)
cx16.r0H = 1
if also_keyboard_js
cx16.r0H = 0
cx16.r1 = $ffff
for cx16.r0L in cx16.r0H to 4 {
bool notpresent
cx16.r2, notpresent = cx16.joystick_get(cx16.r0L)
if not notpresent {
cx16.r1 &= cx16.r2
}
}
return ~cx16.r1
}
; Commander X16 IRQ dispatcher routines
inline asmsub disable_irqs() clobbers(A) {
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
@@ -1216,6 +1286,7 @@ _vsync_vec .word ?
_line_vec .word ?
_aflow_vec .word ?
_sprcol_vec .word ?
_continue_with_system_handler .byte ?
.send BSS
_irq_dispatcher
@@ -1224,44 +1295,42 @@ _irq_dispatcher
cld
lda cx16.VERA_ISR
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
sta cx16.VERA_ISR ; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bit #2
stz _continue_with_system_handler
bit #2 ; make sure to test for LINE IRQ first to handle that as soon as we can
beq +
pha
jsr _line_handler
ldy #2
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #4
beq +
jsr _sprcol_handler
ldy #4
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #8
beq +
jsr _aflow_handler
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bra _dispatch_end
+
bit #1
beq +
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
pha
jsr _vsync_handler
cmp #0
bne _dispatch_end
ldy #1
sty cx16.VERA_ISR
bra _return_irq
+
lda #0
_dispatch_end
cmp #0
beq _return_irq
jsr sys.restore_prog8_internals
tsb _continue_with_system_handler
pla
+ lsr a
lsr a
bcc +
pha
jsr _sprcol_handler
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
jsr _aflow_handler
tsb _continue_with_system_handler
+ jsr sys.restore_prog8_internals
lda _continue_with_system_handler
beq _no_sys_handler
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_return_irq
jsr sys.restore_prog8_internals
_no_sys_handler
ply
plx
pla
@@ -1485,12 +1554,13 @@ sys {
const ubyte target = 16 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -1997,6 +2067,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@@ -241,6 +241,28 @@ divmod_ub_asm .proc
rts
.pend
remainder_ub_asm .proc
; -- divide A by Y, returns remainder in A (unsigned)
; division by zero will result in just the original number.
; This routine specialcases 0,1,2 and otherwise is just a repeated subtraction.
cpy #0
beq _zero
cpy #1
bne +
lda #0
rts
+ cpy #2
bne +
and #1
rts
+ sty P8ZP_SCRATCH_REG
sec
- sbc P8ZP_SCRATCH_REG
bcs -
adc P8ZP_SCRATCH_REG
_zero rts
.pend
divmod_w_asm .proc
; signed word division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_W2

View File

@@ -102,43 +102,35 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}
sub randrange(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias).
; NOTE: does not work for code in ROM
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
; -- return random number uniformly distributed from 0 to n-1
; NOTE: does not work for code in ROM, use randrange_rom instead for that
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rnd() * (n as uword)
return cx16.r0H
}
sub randrange_rom(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias).
; NOTE: works for code in ROM, make sure to initialize seed using rndseed_rom
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd_rom()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
; -- return random number uniformly distributed from 0 to n-1
; NOTE: works for code in ROM, make sure you have initialized the seed using rndseed_rom
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rnd_rom() * (n as uword)
return cx16.r0H
}
sub randrangew(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
; -- return random number uniformly distributed from 0 to n-1
; NOTE: does not work for code in ROM
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rndw() * n
return math.mul16_last_upper()
}
sub randrangew_rom(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
; NOTE: works for code in ROM, make sure to initialize seed using rndseed_rom
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw_rom()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
; -- return random number uniformly distributed from 0 to n-1
; NOTE: works for code in ROM, make sure you have initialized the seed using rndseed_rom
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rndw_rom() * n
return math.mul16_last_upper()
}
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
@@ -705,25 +697,36 @@ log2_tab
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}

View File

@@ -98,12 +98,13 @@ sys {
const ubyte target = 32 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 0 ; undefined, no floats supported
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -481,6 +482,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -519,6 +532,7 @@ cx16 {
&uword r14 = $7ffc
&uword r15 = $7ffe
; signed word versions
&word r0s = $7fe0
&word r1s = $7fe2
&word r2s = $7fe4
@@ -536,6 +550,7 @@ cx16 {
&word r14s = $7ffc
&word r15s = $7ffe
; ubyte versions (low and high bytes)
&ubyte r0L = $7fe0
&ubyte r1L = $7fe2
&ubyte r2L = $7fe4
@@ -570,6 +585,7 @@ cx16 {
&ubyte r14H = $7ffd
&ubyte r15H = $7fff
; signed byte versions (low and high bytes)
&byte r0sL = $7fe0
&byte r1sL = $7fe2
&byte r2sL = $7fe4
@@ -604,6 +620,42 @@ cx16 {
&byte r14sH = $7ffd
&byte r15sH = $7fff
; boolean versions
&bool r0bL = $7fe0
&bool r1bL = $7fe2
&bool r2bL = $7fe4
&bool r3bL = $7fe6
&bool r4bL = $7fe8
&bool r5bL = $7fea
&bool r6bL = $7fec
&bool r7bL = $7fee
&bool r8bL = $7ff0
&bool r9bL = $7ff2
&bool r10bL = $7ff4
&bool r11bL = $7ff6
&bool r12bL = $7ff8
&bool r13bL = $7ffa
&bool r14bL = $7ffc
&bool r15bL = $7ffe
&bool r0bH = $7fe1
&bool r1bH = $7fe3
&bool r2bH = $7fe5
&bool r3bH = $7fe7
&bool r4bH = $7fe9
&bool r5bH = $7feb
&bool r6bH = $7fed
&bool r7bH = $7fef
&bool r8bH = $7ff1
&bool r9bH = $7ff3
&bool r10bH = $7ff5
&bool r11bH = $7ff7
&bool r12bH = $7ff9
&bool r13bH = $7ffb
&bool r14bH = $7ffd
&bool r15bH = $7fff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@@ -43,46 +43,72 @@ _done
}}
}
/*
prog8 source code for the above routine:
sub gnomesort_ub(uword @requirezp values, ubyte num_elements) {
sub gnomesort_by_ub(uword @requirezp uw_keys, uword values, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
ubyte @zp pos=1
while pos != num_elements {
if values[pos]>=values[pos-1]
if uw_keys[pos]>=uw_keys[pos-1]
pos++
else {
; swap elements
cx16.r0L = values[pos-1]
values[pos-1] = values[pos]
values[pos] = cx16.r0L
cx16.r0L = uw_keys[pos-1]
uw_keys[pos-1] = uw_keys[pos]
uw_keys[pos] = cx16.r0L
uword @requirezp vptr = values + pos*$0002 -2
cx16.r0 = peekw(vptr)
pokew(vptr, peekw(vptr+2))
pokew(vptr+2, cx16.r0)
pos--
if_z
pos++
}
}
}
*/
sub gnomesort_uw(uword values, ubyte num_elements) {
; TODO optimize this more, rewrite in asm?
ubyte @zp pos = 1
uword @requirezp ptr = values+2
sub gnomesort_uw(uword @requirezp values, ubyte num_elements) {
; Sorts the values array (no-split unsigned words).
; Max number of elements is 128. Clobbers R0 and R1.
ubyte @zp pos=2
num_elements *= 2
while pos != num_elements {
cx16.r0 = peekw(ptr-2)
cx16.r1 = peekw(ptr)
if cx16.r0<=cx16.r1 {
pos++
ptr+=2
}
cx16.r1L = pos-2
if peekw(values+pos) >= peekw(values + cx16.r1L)
pos += 2
else {
; swap elements
pokew(ptr-2, cx16.r1)
pokew(ptr, cx16.r0)
if pos>1 {
pos--
ptr-=2
}
cx16.r0 = peekw(values + cx16.r1L)
pokew(values + cx16.r1L, peekw(values + pos))
pokew(values + pos, cx16.r0)
pos-=2
if_z
pos+=2
}
}
}
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
; Max number of elements is 128. Clobbers R0 and R1.
ubyte @zp pos=2
num_elements *= 2
while pos != num_elements {
cx16.r1L = pos-2
if peekw(uw_keys+pos) >= peekw(uw_keys + cx16.r1L)
pos += 2
else {
; swap elements
cx16.r0 = peekw(uw_keys + cx16.r1L)
pokew(uw_keys + cx16.r1L, peekw(uw_keys+ pos))
pokew(uw_keys + pos, cx16.r0)
cx16.r0 = peekw(wordvalues + cx16.r1L)
pokew(wordvalues + cx16.r1L, peekw(wordvalues + pos))
pokew(wordvalues + pos, cx16.r0)
pos-=2
if_z
pos+=2
}
}
}
@@ -90,6 +116,7 @@ _done
; gnomesort_pointers is not worth it over shellshort_pointers.
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
; sorts the values array (unsigned bytes).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
@@ -112,6 +139,7 @@ _done
}
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
; sorts the values array (no-split unsigned words).
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
@@ -121,13 +149,65 @@ _done
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(values+k*2)
uword @zp v = peekw(values+k*$0002)
if v <= temp break
pokew(values+j*2, v)
pokew(values+j*$0002, v)
j = k
k -= gap
}
pokew(values+j*2, temp)
pokew(values+j*$0002, temp)
}
}
}
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = ub_keys[i]
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = ub_keys[k]
if v <= temp break
if j < gap break
ub_keys[j] = v
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
ub_keys[j] = temp
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(uw_keys+i*$0002)
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(uw_keys+k*2)
if v <= temp break
pokew(uw_keys+j*2, v)
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
pokew(uw_keys+j*2, temp)
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
@@ -144,14 +224,14 @@ _done
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
cx16.r0 = peekw(pointers+k*2)
cx16.r0 = peekw(pointers+k*$0002)
void call(comparefunc)
if_cs break
pokew(pointers+j*2, cx16.r0)
pokew(pointers+j*$0002, cx16.r0)
j = k
k -= gap
}
pokew(pointers+j*2, cx16.r1)
pokew(pointers+j*$0002, cx16.r1)
}
}
}

View File

@@ -151,6 +151,33 @@ _found tya
}}
}
asmsub find_eol(uword string @AY) -> ubyte @A, bool @Pc {
; Locates the position of the first End Of Line character in the string.
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _notfound
cmp #13
beq _found
cmp #10
beq _found
iny
bne -
_notfound lda #255
clc
rts
_found tya
sec
rts
}}
}
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
@@ -391,16 +418,18 @@ fail clc ; yes, no match found, return with c=0
}}
}
asmsub hash(str string @R0) -> ubyte @A {
asmsub hash(str string @AY) -> ubyte @A {
; experimental 8 bit hashing function.
; hash(-1)=179; clear carry; hash(i) = ROL hash(i-1) XOR string[i]
; On the English word list in /usr/share/dict/words it seems to have a pretty even distribution
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #179
sta P8ZP_SCRATCH_REG
ldy #0
clc
- lda (cx16.r0),y
- lda (P8ZP_SCRATCH_W1),y
beq +
rol P8ZP_SCRATCH_REG
eor P8ZP_SCRATCH_REG

View File

@@ -177,21 +177,17 @@ math {
}
sub randrange(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
; -- return random number uniformly distributed from 0 to n-1
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rnd() * (n as uword)
return cx16.r0H
}
sub randrangew(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
; -- return random number uniformly distributed from 0 to n-1
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rndw() * n
return math.mul16_last_upper()
}
sub rndseed(uword seed1, uword seed2) {
@@ -408,25 +404,36 @@ math {
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}

View File

@@ -0,0 +1,103 @@
; **experimental** data sorting routines, API subject to change!!
; NOTE: gnomesort is not implemented here, just use shellshort.
sorting {
%option ignore_unused
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = values[i]
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = values[k]
if v <= temp break
if j < gap break
values[j] = v
j = k
k -= gap
}
values[j] = temp
}
}
}
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(values+i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(values+k*2)
if v <= temp break
pokew(values+j*2, v)
j = k
k -= gap
}
pokew(values+j*2, temp)
}
}
}
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = ub_keys[i]
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = ub_keys[k]
if v <= temp break
if j < gap break
ub_keys[j] = v
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
ub_keys[j] = temp
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(uw_keys+i*$0002)
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(uw_keys+k*2)
if v <= temp break
pokew(uw_keys+j*2, v)
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
pokew(uw_keys+j*2, temp)
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
}

View File

@@ -53,34 +53,47 @@ strings {
target[ix]=0
}
sub find(str st, ubyte character) -> ubyte {
sub find(str st, ubyte character) -> ubyte, bool {
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix]==character {
sys.set_carry()
return ix
return ix, true
}
}
sys.clear_carry()
return 255
return 255, false
}
sub rfind(uword stringptr, ubyte character) -> ubyte {
sub find_eol(str st) -> ubyte, bool {
; Locates the position of the first End Of Line character in the string.
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix] in "\x0a\x0d" {
sys.set_carry()
return ix, true
}
}
sys.clear_carry()
return 255, false
}
sub rfind(uword stringptr, ubyte character) -> ubyte, bool {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in length(stringptr)-1 downto 0 {
if stringptr[ix]==character {
sys.set_carry()
return ix
return ix, true
}
}
sys.clear_carry()
return 255
return 255, false
}
sub contains(str st, ubyte character) -> bool {
@@ -179,7 +192,7 @@ strings {
sub hash(str st) -> ubyte {
; experimental 8 bit hashing function.
; hash(-1)=179; hash(i) = ROL hash(i-1) XOR string[i]
; (experimental because the quality of the resulting hash value still has to be determined)
; On the English word list in /usr/share/dict/words it seems to have a pretty even distribution
ubyte hashcode = 179
ubyte ix
sys.clear_carry()

View File

@@ -7,12 +7,13 @@ sys {
const ubyte target = 255 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 8
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -198,6 +199,12 @@ sys {
}}
}
sub get_as_returnaddress(uword address) -> uword {
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
address--
return mkword(lsb(address), msb(address))
}
sub pop() -> ubyte {
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
%ir {{
@@ -231,9 +238,8 @@ sys {
if_cs
cx16.r0L |= 1
; TODO: overflow flag not yet supported
; if_vs
; cx16.r0L |= %01000000
if_vs
cx16.r0L |= %01000000
return cx16.r0L
}
@@ -260,6 +266,7 @@ cx16 {
&uword r14 = $ff1e
&uword r15 = $ff20
; signed word versions
&word r0s = $ff02
&word r1s = $ff04
&word r2s = $ff06
@@ -277,6 +284,7 @@ cx16 {
&word r14s = $ff1e
&word r15s = $ff20
; ubyte versions (low and high bytes)
&ubyte r0L = $ff02
&ubyte r1L = $ff04
&ubyte r2L = $ff06
@@ -311,6 +319,7 @@ cx16 {
&ubyte r14H = $ff1f
&ubyte r15H = $ff21
; signed byte versions (low and high bytes)
&byte r0sL = $ff02
&byte r1sL = $ff04
&byte r2sL = $ff06
@@ -345,6 +354,42 @@ cx16 {
&byte r14sH = $ff1f
&byte r15sH = $ff21
; boolean versions
&bool r0bL = $ff02
&bool r1bL = $ff04
&bool r2bL = $ff06
&bool r3bL = $ff08
&bool r4bL = $ff0a
&bool r5bL = $ff0c
&bool r6bL = $ff0e
&bool r7bL = $ff10
&bool r8bL = $ff12
&bool r9bL = $ff14
&bool r10bL = $ff16
&bool r11bL = $ff18
&bool r12bL = $ff1a
&bool r13bL = $ff1c
&bool r14bL = $ff1e
&bool r15bL = $ff20
&bool r0bH = $ff03
&bool r1bH = $ff05
&bool r2bH = $ff07
&bool r3bH = $ff09
&bool r4bH = $ff0b
&bool r5bH = $ff0d
&bool r6bH = $ff0f
&bool r7bH = $ff11
&bool r8bH = $ff13
&bool r9bH = $ff15
&bool r10bH = $ff17
&bool r11bH = $ff19
&bool r12bH = $ff1b
&bool r13bH = $ff1d
&bool r14bH = $ff1f
&bool r15bH = $ff21
sub save_virtual_registers() {
uword[32] storage
storage[0] = r0

View File

@@ -57,7 +57,7 @@ private fun compileMain(args: Array<String>): Boolean {
val plainText by cli.option(ArgType.Boolean, fullName = "plaintext", description = "output only plain text, no colors or fancy symbols")
val printAst1 by cli.option(ArgType.Boolean, fullName = "printast1", description = "print out the internal compiler AST")
val printAst2 by cli.option(ArgType.Boolean, fullName = "printast2", description = "print out the simplified AST that is used for code generation")
val quietAll by cli.option(ArgType.Boolean, fullName = "quiet", description = "don't print compiler and assembler messages")
val quietAll by cli.option(ArgType.Boolean, fullName = "quiet", description = "don't print compiler and assembler messages, except warnings and errors")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler messages")
val slabsGolden by cli.option(ArgType.Boolean, fullName = "slabsgolden", description = "put memory() slabs in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
val slabsHighBank by cli.option(ArgType.Int, fullName = "slabshigh", description = "put memory() slabs in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank to use, on other systems this value is ignored.")
@@ -65,12 +65,14 @@ private fun compileMain(args: Array<String>): Boolean {
val dontSplitWordArrays by cli.option(ArgType.Boolean, fullName = "dontsplitarrays", description = "don't store any word array as split lsb/msb in memory, as if all of those have @nosplit")
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of ${CompilationTargets.joinToString(",")} or a custom target properties file) (required)")
val showTimings by cli.option(ArgType.Boolean, fullName = "timings", description = "show internal compiler timings (for performance analysis)")
val varsGolden by cli.option(ArgType.Boolean, fullName = "varsgolden", description = "put uninitialized variables in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank to use, on other systems this value is ignored.")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "run a .p8ir IR source file in the embedded VM")
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
val version by cli.option(ArgType.Boolean, fullName = "version", description = "print compiler version and exit")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").optional().multiple(999)
try {
cli.parse(args)
@@ -80,6 +82,11 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if(version==true) {
banner()
return true
}
if(quietAll!=true)
banner()
@@ -123,6 +130,11 @@ private fun compileMain(args: Array<String>): Boolean {
return true
}
if(moduleFiles.isEmpty()) {
System.err.println("No module file(s) specified")
return false
}
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
return false
@@ -170,6 +182,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
experimentalCodegen == true,
@@ -254,6 +267,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
experimentalCodegen == true,

View File

@@ -1,8 +1,8 @@
package prog8.compiler
import prog8.ast.Program
import prog8.ast.AstException
import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.VarDecl
@@ -115,16 +115,30 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
else -> NumericLiteral(BaseDataType.UBYTE, program.memsizer.memorySize(dt.getOrUndef(), null).toDouble(), position)
}
} else {
val identifier = args[0] as? IdentifierReference
if(identifier?.nameInSource?.size==1) {
when(identifier.nameInSource[0]) {
"ubyte" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.UBYTE), position)
"byte" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.BYTE), position)
"uword" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.UWORD), position)
"word" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.WORD), position)
"long" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.LONG), position)
"float" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.FLOAT), position)
"bool" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.BOOL), position)
}
}
throw SyntaxError("sizeof invalid argument type", position)
}
}
@Suppress("unused")
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl()
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteral.optimalInteger(arraySize, position)
@@ -134,7 +148,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteral.optimalInteger((args[0] as StringLiteral).value.length, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
val target = (args[0] as IdentifierReference).targetVarDecl()
?: throw CannotEvaluateException("len", "no target vardecl")
return when {

View File

@@ -24,9 +24,11 @@ import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.nameWithoutExtension
import kotlin.math.round
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.measureTime
import kotlin.time.measureTimedValue
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
@@ -40,6 +42,7 @@ class CompilerArguments(val filepath: Path,
val warnSymbolShadowing: Boolean,
val quietAll: Boolean,
val quietAssembler: Boolean,
val showTimings: Boolean,
val asmListfile: Boolean,
val includeSourcelines: Boolean,
val experimentalCodegen: Boolean,
@@ -83,9 +86,20 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
try {
val totalTime = measureTimeMillis {
val totalTime = measureTime {
val libraryDirs = if(compTarget.libraryPath!=null) listOf(compTarget.libraryPath.toString()) else emptyList()
val (program, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs, libraryDirs, args.quietAll)
val (parseresult, parseDuration) = measureTimedValue {
parseMainModule(
args.filepath,
args.errors,
compTarget,
args.sourceDirs,
libraryDirs,
args.quietAll
)
}
val (program, options, imported) = parseresult
compilationOptions = options
with(compilationOptions) {
@@ -120,87 +134,119 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
processAst(program, args.errors, compilationOptions)
val processDuration = measureTime {
processAst(program, args.errors, compilationOptions)
}
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
if (compilationOptions.optimize) {
optimizeAst(
program,
compilationOptions,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
)
val optimizeDuration = measureTime {
if (compilationOptions.optimize) {
optimizeAst(
program,
compilationOptions,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
)
}
}
determineProgramLoadAddress(program, compilationOptions, args.errors)
args.errors.report()
postprocessAst(program, args.errors, compilationOptions)
args.errors.report()
val postprocessDuration = measureTime {
determineProgramLoadAddress(program, compilationOptions, args.errors)
args.errors.report()
postprocessAst(program, args.errors, compilationOptions)
args.errors.report()
}
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
// printProgram(program)
var createAssemblyDuration = Duration.ZERO
var simplifiedAstDuration = Duration.ZERO
if (args.writeAssembly) {
// re-initialize memory areas with final compilationOptions
compilationOptions.compTarget.initializeMemoryAreas(compilationOptions)
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
if(args.printAst1) {
if (args.printAst1) {
println("\n*********** COMPILER AST *************")
printProgram(program)
println("*********** COMPILER AST END *************\n")
}
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
val (intermediateAst, simplifiedAstDuration2) = measureTimedValue {
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
postprocessSimplifiedAst(intermediateAst, symbolTable, args.errors)
args.errors.report()
if(compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
postprocessSimplifiedAst(intermediateAst, symbolTable, args.errors)
args.errors.report()
if (compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
}
if (args.printAst2) {
println("\n*********** SIMPLIFIED AST *************")
printAst(intermediateAst, true, ::println)
println("*********** SIMPLIFIED AST END *************\n")
}
verifyFinalAstBeforeAsmGen(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
intermediateAst
}
simplifiedAstDuration =simplifiedAstDuration2
if(args.printAst2) {
println("\n*********** SIMPLIFIED AST *************")
printAst(intermediateAst, true, ::println)
println("*********** SIMPLIFIED AST END *************\n")
}
verifyFinalAstBeforeAsmGen(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions, program.generatedLabelSequenceNumber)) {
System.err.println("Error in codegeneration or assembler")
return null
createAssemblyDuration = measureTime {
if (!createAssemblyAndAssemble(
intermediateAst,
args.errors,
compilationOptions,
program.generatedLabelSequenceNumber
)
) {
System.err.println("Error in codegeneration or assembler")
return null
}
}
ast = intermediateAst
} else {
if(args.printAst1) {
if (args.printAst1) {
println("\n*********** COMPILER AST *************")
printProgram(program)
println("*********** COMPILER AST END *************\n")
}
if(args.printAst2) {
if (args.printAst2) {
System.err.println("There is no simplified Ast available if assembly generation is disabled.")
}
}
System.out.flush()
System.err.flush()
if(!args.quietAll && args.showTimings) {
println("\n**** TIMINGS ****")
println("source parsing : ${parseDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast processing : ${processDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast optimizing : ${optimizeDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast postprocess : ${postprocessDuration.toString(DurationUnit.SECONDS, 3)}")
println("code prepare : ${simplifiedAstDuration.toString(DurationUnit.SECONDS, 3)}")
println("code generation : ${createAssemblyDuration.toString(DurationUnit.SECONDS, 3)}")
val totalDuration = parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration
println(" total : ${totalDuration.toString(DurationUnit.SECONDS, 3)}")
}
}
System.out.flush()
System.err.flush()
if(!args.quietAll) {
val seconds = totalTime / 1000.0
println("\nTotal compilation+assemble time: ${round(seconds * 100.0) / 100.0} sec.")
println("\nTotal compilation+assemble time: ${totalTime.toString(DurationUnit.SECONDS, 3)}.")
}
return CompilationResult(resultingProgram!!, ast, compilationOptions, importedFiles)
} catch (px: ParseError) {
args.errors.print_single_error("${px.position.toClickableStr()} parse error: ${px.message}".trim())
args.errors.printSingleError("${px.position.toClickableStr()} parse error: ${px.message}".trim())
} catch (ac: ErrorsReportedException) {
if(args.printAst1 && resultingProgram!=null) {
println("\n*********** COMPILER AST *************")
@@ -217,17 +263,17 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
}
if(!ac.message.isNullOrEmpty()) {
args.errors.print_single_error(ac.message!!)
args.errors.printSingleError(ac.message!!)
}
} catch (nsf: NoSuchFileException) {
args.errors.print_single_error("File not found: ${nsf.message}")
args.errors.printSingleError("File not found: ${nsf.message}")
} catch (ax: AstException) {
args.errors.print_single_error(ax.toString())
args.errors.printSingleError(ax.toString())
} catch (x: Exception) {
args.errors.print_single_error("\ninternal error")
args.errors.printSingleError("\ninternal error")
throw x
} catch (x: NotImplementedError) {
args.errors.print_single_error("\ninternal error: missing feature/code")
args.errors.printSingleError("\ninternal error: missing feature/code")
throw x
}
@@ -424,7 +470,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors)
errors.report()
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
errors.report()
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
errors.report()
@@ -486,7 +532,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)
@@ -495,8 +541,21 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
program.verifyFunctionArgTypes(errors, compilerOptions)
errors.report()
program.moveMainBlockAsFirst(compilerOptions.compTarget)
val fixer = BeforeAsmAstChanger(program, compilerOptions, errors)
fixer.visit(program)
while (errors.noErrors() && fixer.applyModifications() > 0) {
fixer.visit(program)
}
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
val cleaner = BeforeAsmTypecastCleaner(program, errors)
cleaner.visit(program)
while (errors.noErrors() && cleaner.applyModifications() > 0) {
cleaner.visit(program)
}
}
private fun createAssemblyAndAssemble(program: PtProgram,

View File

@@ -95,7 +95,7 @@ internal class ErrorReporter(val colors: IConsoleColors): IErrorReporter {
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
override fun noErrorForLine(position: Position) = !messages.any { it.position.line==position.line && it.severity!=MessageSeverity.INFO }
override fun print_single_error(errormessage: String) {
override fun printSingleError(errormessage: String) {
System.out.flush()
colors.error(System.err)
System.err.println(errormessage)

View File

@@ -39,7 +39,7 @@ class ModuleImporter(private val program: Program,
println("Compiling program ${Path("").absolute().relativize(programPath)}")
println("Compiler target: $compilationTargetName")
}
val source = ImportFileSystem.getFile(programPath, false)
val source = ImportFileSystem.getFile(programPath)
return Ok(importModule(source))
}
}
@@ -158,7 +158,7 @@ class ModuleImporter(private val program: Program,
normalLocations.forEach {
try {
return Ok(ImportFileSystem.getFile(it.resolve(fileName), false))
return Ok(ImportFileSystem.getFile(it.resolve(fileName)))
} catch (_: NoSuchFileException) {
}
}

View File

@@ -5,7 +5,9 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.C128Target
import prog8.code.target.Cx16Target
import prog8.code.target.PETTarget
import prog8.code.target.VMTarget
import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
@@ -182,21 +184,19 @@ internal class AstChecker(private val program: Program,
val iterableDt = forLoop.iterable.inferType(program).getOrUndef()
if(iterableDt.isNumeric) TODO("iterable type should not be simple numeric!? "+forLoop.position)
if(forLoop.iterable is IFunctionCall) {
errors.err("can not loop over function call return value", forLoop.position)
} else if(!(iterableDt.isIterable) && forLoop.iterable !is RangeExpression) {
errors.err("can only loop over an iterable type", forLoop.position)
} else {
val loopvar = forLoop.loopVar.targetVarDecl(program)
val loopvar = forLoop.loopVar.targetVarDecl()
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position)
} else {
require(loopvar.datatype.isNumericOrBool)
when (loopvar.datatype.base) {
BaseDataType.UBYTE -> {
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedByteArray && !iterableDt.isString) // TODO remove ubyte check?
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedByteArray && !iterableDt.isString)
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
}
@@ -205,7 +205,7 @@ internal class AstChecker(private val program: Program,
errors.err("bool loop variable can only loop over boolean array", forLoop.position)
}
BaseDataType.UWORD -> {
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedWord && !iterableDt.isString && // TODO remove byte and word check?
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedWord && !iterableDt.isString &&
!iterableDt.isUnsignedByteArray && !iterableDt.isUnsignedWordArray &&
!iterableDt.isSplitWordArray)
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
@@ -213,7 +213,7 @@ internal class AstChecker(private val program: Program,
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
}
BaseDataType.BYTE -> {
if(!iterableDt.isSignedByte && !iterableDt.isSignedByteArray) // TODO remove byte check?
if(!iterableDt.isSignedByte && !iterableDt.isSignedByteArray)
errors.err("byte loop variable can only loop over bytes", forLoop.position)
}
BaseDataType.WORD -> {
@@ -392,7 +392,7 @@ internal class AstChecker(private val program: Program,
err("variable bank extsub has no romable code-generation for the required jsrfar call, stick to constant bank, or create a system-ram trampoline")
}
if(varbank.targetVarDecl(program)?.datatype?.isUnsignedByte!=true)
if(varbank.targetVarDecl()?.datatype?.isUnsignedByte!=true)
err("bank variable must be ubyte")
}
if(subroutine.inline && subroutine.asmAddress!=null)
@@ -406,11 +406,42 @@ internal class AstChecker(private val program: Program,
// subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
var haveReturnError = false
if(!hasReturnOrExternalJumpOrRts(subroutine)) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null && !subroutine.inline)
if (subroutine.asmAddress == null && !subroutine.inline) {
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or external 'goto' in it (or the assembler equivalent in case of %asm)")
haveReturnError = true
}
}
}
val lastStatement = subroutine.statements.reversed().dropWhile { it is Subroutine || it is VarDecl || it is Directive || (it is Assignment && it.origin== AssignmentOrigin.VARINIT) }.firstOrNull()
if(!haveReturnError && !subroutine.isAsmSubroutine && !subroutine.inline && subroutine.returntypes.isNotEmpty() && lastStatement !is Return) {
if(lastStatement==null)
err("subroutine '${subroutine.name}' has result value(s) but doesn't end with a return statement")
else {
val returnError = when (lastStatement) {
is Jump, is OnGoto -> false
is IStatementContainer -> !hasReturnOrExternalJumpOrRts(lastStatement as IStatementContainer)
is InlineAssembly -> !lastStatement.hasReturnOrRts()
is ForLoop -> hasReturnOrExternalJumpOrRts(lastStatement.body)
is IfElse -> !hasReturnOrExternalJumpOrRts(lastStatement.truepart) && !hasReturnOrExternalJumpOrRts(lastStatement.elsepart)
is ConditionalBranch -> !hasReturnOrExternalJumpOrRts(lastStatement.truepart) && !hasReturnOrExternalJumpOrRts(lastStatement.elsepart)
is RepeatLoop -> {
lastStatement.iterations!=null || !hasReturnOrExternalJumpOrRts(lastStatement.body)
}
is UntilLoop -> !hasReturnOrExternalJumpOrRts(lastStatement.body)
is WhileLoop -> !hasReturnOrExternalJumpOrRts(lastStatement.body)
is When -> lastStatement.choices.all { !hasReturnOrExternalJumpOrRts(it.statements) }
else -> true
}
if(returnError) {
val pos = if(lastStatement is Subroutine) subroutine.position else lastStatement.position
errors.err("subroutine '${subroutine.name}' has result value(s) but doesn't end with a return statement", pos)
}
}
}
@@ -558,7 +589,7 @@ internal class AstChecker(private val program: Program,
val ident = repeatLoop.iterations as? IdentifierReference
if(ident!=null) {
val targetVar = ident.targetVarDecl(program)
val targetVar = ident.targetVarDecl()
if(targetVar==null)
errors.err("invalid assignment value, maybe forgot '&' (address-of)", ident.position)
}
@@ -598,7 +629,7 @@ internal class AstChecker(private val program: Program,
}
val fcall = assignment.value as? IFunctionCall
val fcallTarget = fcall?.target?.targetSubroutine(program)
val fcallTarget = fcall?.target?.targetSubroutine()
if(assignment.target.multi!=null) {
checkMultiAssignment(assignment, fcall, fcallTarget)
} else if(fcallTarget!=null) {
@@ -681,8 +712,7 @@ internal class AstChecker(private val program: Program,
if (assignment.value !is BinaryExpression && assignment.value !is PrefixExpression && assignment.value !is ContainmentCheck && assignment.value !is IfExpression)
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
} else {
checkAssignmentCompatible(assignTarget, targetDatatype.getOrUndef(),
sourceDatatype.getOrUndef(), assignment.value)
checkAssignmentCompatible(targetDatatype.getOrUndef(),sourceDatatype.getOrUndef(), assignment.value, assignment.value.position)
}
}
}
@@ -691,7 +721,7 @@ internal class AstChecker(private val program: Program,
fun checkRomTarget(target: AssignTarget) {
val idx=target.arrayindexed
if(idx!=null) {
val decl = idx.arrayvar.targetVarDecl(program)!!
val decl = idx.arrayvar.targetVarDecl()!!
if(decl.type!=VarDeclType.MEMORY && decl.zeropage!=ZeropageWish.REQUIRE_ZEROPAGE) {
// memory mapped arrays are assumed to be in RAM. If they're not.... well, POOF
errors.err("cannot assign to an array or string that is located in ROM (option romable is enabled)", assignTarget.position)
@@ -709,7 +739,7 @@ internal class AstChecker(private val program: Program,
override fun visit(addressOf: AddressOf) {
checkLongType(addressOf)
val variable=addressOf.identifier.targetVarDecl(program)
val variable=addressOf.identifier.targetVarDecl()
if (variable!=null) {
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
errors.err("invalid pointer-of operand type", addressOf.position)
@@ -764,6 +794,9 @@ internal class AstChecker(private val program: Program,
// FLOATS enabled?
if(!compilerOptions.floats && (decl.datatype.isFloat || decl.datatype.isFloatArray) && decl.type != VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options")
else if(compilerOptions.compTarget.name in arrayOf(PETTarget.NAME, C128Target.NAME) && decl.type != VarDeclType.CONST && (decl.datatype.isFloat || decl.datatype.isFloatArray)) {
err("pet32 and c128 target do not support floating point numbers yet")
}
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) {
@@ -1124,14 +1157,6 @@ internal class AstChecker(private val program: Program,
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
}
if(array.parent is Assignment) {
val arraydt = array.inferType(program)
val assignTarget = (array.parent as Assignment).target
val targetDt = assignTarget.inferType(program)
if(arraydt!=targetDt)
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
}
super.visit(array)
}
@@ -1162,7 +1187,7 @@ internal class AstChecker(private val program: Program,
override fun visit(expr: PrefixExpression) {
if(expr.expression is IFunctionCall) {
val targetStatement = (expr.expression as IFunctionCall).target.targetSubroutine(program)
val targetStatement = (expr.expression as IFunctionCall).target.targetSubroutine()
if(targetStatement?.returntypes?.isEmpty()==true) {
errors.err("subroutine doesn't return a value", expr.expression.position)
}
@@ -1610,7 +1635,7 @@ internal class AstChecker(private val program: Program,
}
private fun checkPointer(pointervar: IdentifierReference) {
val vardecl = pointervar.targetVarDecl(program)
val vardecl = pointervar.targetVarDecl()
if(vardecl?.zeropage == ZeropageWish.NOT_IN_ZEROPAGE)
errors.info("pointer variable should preferrably be in zeropage but is marked nozp", vardecl.position)
}
@@ -1623,7 +1648,7 @@ internal class AstChecker(private val program: Program,
errors.err("indexing requires an iterable or address uword variable", arrayIndexedExpression.position)
val indexVariable = arrayIndexedExpression.indexer.indexExpr as? IdentifierReference
if(indexVariable!=null) {
if(indexVariable.targetVarDecl(program)?.datatype?.isSigned==true) {
if(indexVariable.targetVarDecl()?.datatype?.isSigned==true) {
errors.err("variable array indexing can't be performed with signed variables", indexVariable.position)
return
}
@@ -1678,6 +1703,9 @@ internal class AstChecker(private val program: Program,
if(whenStmt.condition.constValue(program)!=null)
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
if(whenStmt.betterAsOnGoto(program, compilerOptions))
errors.info("when statement can be replaced with on..goto", whenStmt.position)
super.visit(whenStmt)
}
@@ -2015,25 +2043,23 @@ internal class AstChecker(private val program: Program,
return correct
}
private fun checkAssignmentCompatible(target: AssignTarget,
targetDatatype: DataType,
private fun checkAssignmentCompatible(targetDatatype: DataType,
sourceDatatype: DataType,
sourceValue: Expression) : Boolean {
val position = sourceValue.position
sourceValue: Expression,
position: Position) : Boolean {
if (targetDatatype.isArray) {
if(sourceValue.inferType(program).isArray)
errors.err("cannot assign arrays directly. Maybe use sys.memcopy instead.", target.position)
if(sourceDatatype.isArray)
errors.err("cannot assign arrays directly. Maybe use sys.memcopy instead.", position)
else
errors.err("cannot assign value to array. Maybe use sys.memset/memsetw instead.", target.position)
errors.err("cannot assign value to array. Maybe use sys.memset/memsetw instead.", position)
return false
}
if (sourceValue is ArrayLiteral) {
errors.err("cannot assign array", target.position)
return false
}
if(sourceValue is RangeExpression) {
errors.err("can't assign a range value to something else", position)
if (sourceDatatype.isArray) {
if(targetDatatype.isUnsignedWord)
errors.err("invalid assignment value, maybe forgot '&' (address-of)", position)
else
errors.err("cannot assign array", position) // also includes range expressions
return false
}
if(sourceDatatype.isUndefined) {
@@ -2056,41 +2082,30 @@ internal class AstChecker(private val program: Program,
if(result)
return true
if(sourceDatatype.isWord && targetDatatype.isByte) {
errors.err("cannot assign word to byte, use msb() or lsb()?", position)
}
else if(sourceDatatype.isIterable && targetDatatype.isByte) {
errors.err("cannot assign string or array to a byte", position)
}
val sourceIsBitwiseOperatorExpression = (sourceValue as? BinaryExpression)?.operator in BitwiseOperators
if(sourceDatatype.isWord && targetDatatype.isByte)
errors.err("cannot assign word to byte, maybe use msb() or lsb()", position)
else if(sourceDatatype.isFloat&& targetDatatype.isInteger)
errors.err("cannot assign float to ${targetDatatype}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else if((sourceValue as? BinaryExpression)?.operator in BitwiseOperators && targetDatatype.equalsSize(sourceDatatype)) {
// this is allowed: bitwise operation between different types as long as they're the same size.
}
else if(targetDatatype.isUnsignedWord && sourceDatatype.isPassByRef) {
// this is allowed: a pass-by-reference datatype into an uword (pointer value).
}
else if(sourceDatatype.isArray && targetDatatype.isArray) {
// this is allowed (assigning array to array)
else if(sourceIsBitwiseOperatorExpression && targetDatatype.equalsSize(sourceDatatype)) {
// this is allowed: bitwise operation between different types as long as they're the same size.
}
else if(sourceDatatype.isBool && !targetDatatype.isBool) {
else if(targetDatatype.isString && sourceDatatype.isUnsignedWord)
errors.err("can't assign uword to str. If the source is a string pointer and you actually want to overwrite the target string, use an explicit strings.copy(src,tgt) instead.", position)
else
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else if(targetDatatype.isBool && !sourceDatatype.isBool) {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else if(targetDatatype.isString) {
if(sourceDatatype.isUnsignedWord)
errors.err("can't assign UWORD to STR. If the source is a string and you actually want to overwrite the target string, use an explicit strings.copy(src,tgt) instead.", position)
else
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
return false
}
override fun visit(onGoto: OnGoto) {
if(!onGoto.index.inferType(program).getOrUndef().isUnsignedByte) {
errors.err("on..goto index must be an unsigned byte", onGoto.index.position)
}
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {

View File

@@ -3,11 +3,7 @@ package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.StringLiteral
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@@ -24,19 +20,6 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while (errors.noErrors() && fixer.applyModifications() > 0) {
fixer.visit(this)
}
val cleaner = BeforeAsmTypecastCleaner(this, errors)
cleaner.visit(this)
while (errors.noErrors() && cleaner.applyModifications() > 0) {
cleaner.visit(this)
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
@@ -107,8 +90,8 @@ internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOp
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter) {
val desugar = CodeDesugarer(this, errors)
fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, options.compTarget, errors)
desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this)
@@ -185,8 +168,8 @@ internal fun Program.moveMainBlockAsFirst(target: ICompilationTarget) {
}
}
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val vardecl = this.targetVarDecl(program)
internal fun IdentifierReference.isSubroutineParameter(): Boolean {
val vardecl = this.targetVarDecl()
if(vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}

View File

@@ -1,12 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.findParentNode
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@@ -144,7 +139,7 @@ class AstPreprocessor(val program: Program,
}
} else {
// handle declaration of a single variable
if(decl.value!=null && decl.datatype.isNumericOrBool) {
if(decl.value!=null && !decl.datatype.isIterable) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, null, false, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
@@ -181,7 +176,7 @@ class AstPreprocessor(val program: Program,
val nextAssignment = decl.nextSibling() as? Assignment
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
// check if the following assignment initializes the variable
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) {
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl()===decl) {
if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource))
nextAssignment.origin = AssignmentOrigin.VARINIT
}

View File

@@ -3,6 +3,7 @@ package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.defaultZero
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
@@ -48,18 +49,27 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
val mods = mutableListOf<IAstModification>()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti.
// and if an assembly block doesn't contain a rts/rti. AND if there's no return value(s) because we can't make one up!
if (!subroutine.isAsmSubroutine) {
if(subroutine.isEmpty()) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
if(subroutine.returntypes.isNotEmpty())
errors.err("subroutine is missing a return statement with value(s)", subroutine.position)
else {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
} else {
val last = subroutine.statements.last()
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
if(lastStatement !is Return) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
if(subroutine.returntypes.isNotEmpty()) {
// .... we cannot return this as an error, because that also breaks legitimate cases where the return is done from within a nested scope somewhere
// errors.err("subroutine is missing a return statement with value(s)", subroutine.position)
} else {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
}
}
@@ -76,8 +86,20 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
&& prevStmt !is Subroutine
&& prevStmt !is Return
) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
if(!subroutine.inline) {
if(outerScope is Subroutine && outerScope.returntypes.isNotEmpty()) {
if(outerScope.returntypes.size>1 || !outerScope.returntypes[0].isNumericOrBool) {
errors.err("subroutine is missing a return statement to avoid falling through into nested subroutine", outerStatements[subroutineStmtIdx-1].position)
} else {
val zero = defaultZero(outerScope.returntypes[0].base, Position.DUMMY)
val returnStmt = Return(arrayOf(zero), outerStatements[subroutineStmtIdx - 1].position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
} else {
val returnStmt = Return(arrayOf(), outerStatements[subroutineStmtIdx - 1].position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
}
}
}

View File

@@ -36,7 +36,7 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
if(typecast.type == BaseDataType.UWORD) {
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
return if(identifier.isSubroutineParameter()) {
listOf(
IAstModification.ReplaceNode(
typecast,

View File

@@ -5,13 +5,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.BaseDataType
import prog8.code.core.ComparisonOperators
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val target: ICompilationTarget, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@@ -27,6 +24,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - flatten chained assignments
// - remove alias nodes
// - convert on..goto/call to jumpaddr array and separate goto/call
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
@@ -106,7 +104,7 @@ if not CONDITION
untilLoop.body,
IfElse(invertCondition(untilLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(loopLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos)
), pos)
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
@@ -150,7 +148,7 @@ _after:
loopLabel,
IfElse(invertCondition(whileLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(afterLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos),
whileLoop.body,
program.jumpLabel(loopLabel),
@@ -205,7 +203,7 @@ _after:
// replace pointervar[word] by @(pointervar+word) to avoid the
// "array indexing is limited to byte size 0..255" error for pointervariables.
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl()
if(arrayVar!=null && arrayVar.datatype.isUnsignedWord) {
val wordIndex = TypecastExpression(indexExpr, BaseDataType.UWORD, true, indexExpr.position)
val address = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", wordIndex, arrayIndexedExpression.position)
@@ -279,7 +277,7 @@ _after:
val addressOf = expr.left as? AddressOf
val offset = (expr.right as? NumericLiteral)?.number?.toInt()
if(addressOf!=null && offset==1) {
val variable = addressOf.identifier.targetVarDecl(program)
val variable = addressOf.identifier.targetVarDecl()
if(variable!=null && variable.datatype.isWord) {
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(addressOf.identifier), memread.position)
return listOf(IAstModification.ReplaceNode(memread, msb, parent))
@@ -350,4 +348,60 @@ _after:
return noModifications
}
override fun after(ongoto: OnGoto, parent: Node): Iterable<IAstModification> {
val indexDt = ongoto.index.inferType(program).getOrUndef()
if(!indexDt.isUnsignedByte)
return noModifications
val numlabels = ongoto.labels.size
val split = if(ongoto.isCall)
true // for calls (indirect JSR), split array is always the optimal choice
else
target.cpu==CpuType.CPU6502 // for goto (indirect JMP), split array is optimal for 6502, but NOT for the 65C02 (it has a different JMP addressing mode available)
val arrayDt = DataType.arrayFor(BaseDataType.UWORD, split)
val labelArray = ArrayLiteral(InferredTypes.knownFor(arrayDt), ongoto.labels.toTypedArray(), ongoto.position)
val jumplistArray = VarDecl.createAutoOptionalSplit(labelArray)
val indexValue: Expression
val conditionVar: VarDecl?
val assignIndex: Assignment?
// put condition in temp var, if it is not simple; to avoid evaluating expression multiple times
if (ongoto.index.isSimple) {
indexValue = ongoto.index
assignIndex = null
conditionVar = null
} else {
conditionVar = VarDecl.createAuto(indexDt)
indexValue = IdentifierReference(listOf(conditionVar.name), conditionVar.position)
val varTarget = AssignTarget(indexValue, null, null, null, false, conditionVar.position)
assignIndex = Assignment(varTarget, ongoto.index, AssignmentOrigin.USERCODE, ongoto.position)
}
val callTarget = ArrayIndexedExpression(IdentifierReference(listOf(jumplistArray.name), jumplistArray.position), ArrayIndex(indexValue.copy(), indexValue.position), ongoto.position)
val callIndexed = AnonymousScope.empty(ongoto.position)
if(ongoto.isCall) {
callIndexed.statements.add(FunctionCallStatement(IdentifierReference(listOf("call"), ongoto.position), mutableListOf(callTarget), true, ongoto.position))
} else {
callIndexed.statements.add(Jump(callTarget, ongoto.position))
}
val ifSt = if(ongoto.elsepart==null || ongoto.elsepart!!.isEmpty()) {
// if index<numlabels call(labels[index])
val compare = BinaryExpression(indexValue.copy(), "<", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, callIndexed, AnonymousScope.empty(), ongoto.position)
} else {
// if index>=numlabels elselabel() else call(labels[index])
val compare = BinaryExpression(indexValue.copy(), ">=", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, ongoto.elsepart!!, callIndexed, ongoto.position)
}
val replacementScope = AnonymousScope(if(conditionVar==null)
mutableListOf(ifSt, jumplistArray)
else
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
, ongoto.position)
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
}
}

View File

@@ -9,7 +9,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.ast.PtContainmentCheck
import prog8.code.core.*
import prog8.code.core.IErrorReporter
internal class LiteralsToAutoVars(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
@@ -83,7 +83,7 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.names.size>1) {
val fcallTarget = (decl.value as? IFunctionCall)?.target?.targetSubroutine(program)
val fcallTarget = (decl.value as? IFunctionCall)?.target?.targetSubroutine()
if(fcallTarget!=null) {
// ubyte a,b,c = multi() --> ubyte a,b,c / a,b,c = multi()
val modifications = mutableListOf<IAstModification>()

View File

@@ -76,6 +76,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
is VarDecl -> transform(statement)
is When -> transform(statement)
is WhileLoop -> throw FatalAstException("while loops must have been converted to jumps")
is OnGoto -> throw FatalAstException("ongoto must have been converted to array and separate call/goto")
}
}
@@ -164,6 +165,9 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0)
throw FatalAstException("should not have a redundant block-level variable=0 assignment; it will be zeroed as part of BSS clear")
val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT)
val multi = srcAssign.target.multi
if(multi==null) {
@@ -417,7 +421,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
if(binexpr.operator=="==" || binexpr.operator=="!=") {
val fcall = binexpr.left as? FunctionCallExpression
if(fcall!=null) {
val returnRegs = fcall.target.targetSubroutine(program)?.asmReturnvaluesRegisters
val returnRegs = fcall.target.targetSubroutine()?.asmReturnvaluesRegisters
if(returnRegs!=null && returnRegs.size==1 && returnRegs[0].statusflag!=null) {
return codeForStatusflag(fcall, returnRegs[0].statusflag!!, binexpr.operator == "==")
}
@@ -426,7 +430,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
} else {
val fcall = srcIf.condition as? FunctionCallExpression
if (fcall != null) {
val returnRegs = fcall.target.targetSubroutine(program)?.asmReturnvaluesRegisters
val returnRegs = fcall.target.targetSubroutine()?.asmReturnvaluesRegisters
if(returnRegs!=null && returnRegs.size==1 && returnRegs[0].statusflag!=null) {
return codeForStatusflag(fcall, returnRegs[0].statusflag!!, false)
}
@@ -436,7 +440,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
if(prefix!=null && prefix.operator=="not") {
val prefixedFcall = prefix.expression as? FunctionCallExpression
if (prefixedFcall != null) {
val returnRegs = prefixedFcall.target.targetSubroutine(program)?.asmReturnvaluesRegisters
val returnRegs = prefixedFcall.target.targetSubroutine()?.asmReturnvaluesRegisters
if (returnRegs != null && returnRegs.size == 1 && returnRegs[0].statusflag != null) {
return codeForStatusflag(prefixedFcall, returnRegs[0].statusflag!!, true)
}
@@ -575,6 +579,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
srcVar.datatype,
srcVar.zeropage,
srcVar.alignment,
srcVar.dirty,
value,
srcVar.arraysize?.constIndex()?.toUInt(),
srcVar.position
@@ -619,7 +624,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val dt = srcArr.arrayvar.targetVarDecl(program)!!.datatype
val dt = srcArr.arrayvar.targetVarDecl()!!.datatype
if(!dt.isArray && !dt.isString)
throw FatalAstException("array indexing can only be used on array or string variables ${srcArr.position}")
val eltType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
@@ -768,7 +773,6 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
return cast
}
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (SourceCode.isLibraryResource(filename)) {
return com.github.michaelbull.result.runCatching {

View File

@@ -46,6 +46,7 @@ private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map<PtSub
DataType.UBYTE,
ZeropageWish.NOT_IN_ZEROPAGE,
0u,
true,
null,
null,
sub.position

View File

@@ -52,7 +52,8 @@ internal class StatementReorderer(
if (decl.datatype.isNumericOrBool) {
if(decl !in declsProcessedWithInitAssignment) {
declsProcessedWithInitAssignment.add(decl)
if (decl.value == null) {
if (decl.value == null || decl.value?.constValue(program)?.number==0.0) {
decl.value = null
if (decl.origin==VarDeclOrigin.USERCODE && decl.allowInitializeWithZero) {
if(decl.dirty) {
// no initialization at all!
@@ -62,11 +63,9 @@ internal class StatementReorderer(
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
val canskip = canSkipInitializationWith0(decl)
if (!canskip) {
// (this explicit initialization assignment to 0 is not required for global variables in the block scope, these are zeroed as part of the BSS section clear)
if (!canSkipInitializationWith0(decl)) {
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, null, false, decl.position),
decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
@@ -96,7 +95,7 @@ internal class StatementReorderer(
// (that code only triggers on regular assignment, not on variable initializers)
val ident = decl.value as? IdentifierReference
if(ident!=null) {
val target = ident.targetVarDecl(program)
val target = ident.targetVarDecl()
if(target!=null && target.isArray) {
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
@@ -115,6 +114,11 @@ internal class StatementReorderer(
}
private fun canSkipInitializationWith0(decl: VarDecl): Boolean {
// if the variable is declared in a block, we can omit the init with 0 because
// the variable will be initialized to zero when the BSS section is cleared as a whole.
if(decl.parent is Block)
return true
// if there is an assignment to the variable below it (regular assign, or For loop),
// and there is nothing important in between, we can skip the initialization.
val statements = (decl.parent as? IStatementContainer)?.statements ?: return false
@@ -123,14 +127,14 @@ internal class StatementReorderer(
when(stmt) {
is Assignment -> {
if (!stmt.isAugmentable) {
val assignTargets = stmt.target.multi?.mapNotNull { it.identifier?.targetVarDecl(program) }
val assignTargets = stmt.target.multi?.mapNotNull { it.identifier?.targetVarDecl() }
if(assignTargets!=null) {
if(decl in assignTargets) {
stmt.origin = AssignmentOrigin.VARINIT
return true
}
} else {
val assignTgt = stmt.target.identifier?.targetVarDecl(program)
val assignTgt = stmt.target.identifier?.targetVarDecl()
if (assignTgt == decl) {
stmt.origin = AssignmentOrigin.VARINIT
return true
@@ -143,11 +147,11 @@ internal class StatementReorderer(
is ChainedAssignment -> {
var chained: ChainedAssignment? = stmt
while(chained!=null) {
val assignTgt = chained.target.identifier?.targetVarDecl(program)
val assignTgt = chained.target.identifier?.targetVarDecl()
if (assignTgt == decl)
return true
if(chained.nested is Assignment) {
if ((chained.nested as Assignment).target.identifier?.targetVarDecl(program) == decl) {
if ((chained.nested as Assignment).target.identifier?.targetVarDecl() == decl) {
(chained.nested as Assignment).origin = AssignmentOrigin.VARINIT
return true
}
@@ -325,7 +329,7 @@ internal class StatementReorderer(
private fun checkCopyArrayValue(assign: Assignment) {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program)!!
val targetVar = identifier.targetVarDecl()!!
if(targetVar.arraysize==null) {
errors.err("array has no defined size", assign.position)
@@ -340,7 +344,7 @@ internal class StatementReorderer(
}
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program)!!
val sourceVar = sourceIdent.targetVarDecl()!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
} else {

View File

@@ -185,6 +185,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
}
}
// check if shifts have a positive integer shift type
if(expr.operator=="<<" || expr.operator==">>") {
if(rightDt.isInteger) {
val rconst = expr.right.constValue(program)
if(rconst!=null && rconst.number<0)
errors.err("can only shift by a positive amount", expr.right.position)
} else
errors.err("right operand of bit shift must be an integer", expr.right.position)
}
}
return noModifications
@@ -251,7 +261,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(number!=null) {
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
} else if(identifier!=null && targetDt==BaseDataType.UWORD && argDt.isPassByRef) {
if(!identifier.isSubroutineParameter(program)) {
if(!identifier.isSubroutineParameter()) {
// We allow STR/ARRAY values for UWORD parameters.
// If it's an array (not STR), take the address.
if(!argDt.isString) {

View File

@@ -84,12 +84,12 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
changeSplit = SplitWish.NOSPLIT
}
else {
changeDataType = if(decl.datatype.isSplitWordArray) null else DataType.arrayFor(decl.datatype.elementType().base, true)
changeDataType = if(decl.datatype.isSplitWordArray) null else DataType.arrayFor(decl.datatype.elementType().base)
changeSplit = SplitWish.SPLIT
}
}
SplitWish.SPLIT -> {
changeDataType = if(decl.datatype.isSplitWordArray) null else DataType.arrayFor(decl.datatype.elementType().base, true)
changeDataType = if(decl.datatype.isSplitWordArray) null else DataType.arrayFor(decl.datatype.elementType().base)
}
SplitWish.NOSPLIT -> {
changeDataType = if(decl.datatype.isSplitWordArray) DataType.arrayFor(decl.datatype.elementType().base, false) else null
@@ -285,7 +285,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// replace x==1 or x==2 or x==3 with a containment check x in [1,2,3]
val valueCopies = values.sortedBy { it.number }.map { it.copy() }
val arrayType = DataType.arrayFor(elementType.base, true)
val arrayType = DataType.arrayFor(elementType.base)
val valuesArray = ArrayLiteral(InferredTypes.InferredType.known(arrayType), valueCopies.toTypedArray(), expr.position)
val containment = ContainmentCheck(needle, valuesArray, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
@@ -394,7 +394,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val index = arrayIndexedExpression.indexer.constIndex()
if(index!=null && index<0) {
val target = arrayIndexedExpression.arrayvar.targetVarDecl(program)
val target = arrayIndexedExpression.arrayvar.targetVarDecl()
val arraysize = target?.arraysize?.constIndex()
if(arraysize!=null) {
if(arraysize+index < 0) {
@@ -429,5 +429,24 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
return noModifications
}
override fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
if(addressOf.arrayIndex!=null) {
val tgt = addressOf.identifier.constValue(program)
if (tgt != null && tgt.type.isWord) {
// &constant[idx] --> constant + idx
val indexExpr = addressOf.arrayIndex!!.indexExpr
val right = if(indexExpr.inferType(program) issimpletype tgt.type)
indexExpr
else
TypecastExpression(indexExpr, tgt.type, true, indexExpr.position)
val add = BinaryExpression(tgt, "+", right, addressOf.position)
return listOf(
IAstModification.ReplaceNode(addressOf, add, parent)
)
}
}
return noModifications
}
}

View File

@@ -5,8 +5,8 @@ import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.*
internal class VerifyFunctionArgTypes(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : IAstVisitor {
@@ -31,7 +31,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
}
override fun visit(identifier: IdentifierReference) {
if(identifier.wasStringLiteral(program)) {
if(identifier.wasStringLiteral()) {
allStringRefs.add(identifier.nameInSource)
}
}

View File

@@ -77,7 +77,7 @@ main {
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)
val statements = result!!.compilerAst.entrypoint.statements
statements.size shouldBe 7
statements.size shouldBe 8
val a1 = statements[2] as Assignment
val a2 = statements[3] as Assignment
val a3 = statements[4] as Assignment

View File

@@ -8,7 +8,6 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Program
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
@@ -31,11 +30,11 @@ class TestCompilerOnCharLit: FunSpec({
val outputDir = tempdir().toPath()
fun findInitializer(vardecl: VarDecl, program: Program): Assignment? =
fun findInitializer(vardecl: VarDecl): Assignment? =
(vardecl.parent as IStatementContainer).statements
.asSequence()
.filterIsInstance<Assignment>()
.singleOrNull { it.origin== AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl(program) === vardecl }
.singleOrNull { it.origin== AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl() === vardecl }
test("testCharLitAsExtsubArg") {
@@ -79,14 +78,14 @@ class TestCompilerOnCharLit: FunSpec({
funCall.args[0] shouldBe instanceOf<IdentifierReference>()
val arg = funCall.args[0] as IdentifierReference
val decl = arg.targetVarDecl(program)!!
val decl = arg.targetVarDecl()!!
decl.type shouldBe VarDeclType.VAR
decl.datatype shouldBe DataType.UBYTE
withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null
}
val assignInitialValue = findInitializer(decl, program)!!
val assignInitialValue = findInitializer(decl)!!
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
withClue("char literal should have been replaced by ubyte literal") {
assignInitialValue.value shouldBe instanceOf<NumericLiteral>()
@@ -115,7 +114,7 @@ class TestCompilerOnCharLit: FunSpec({
// Now, both is ok for the arg: a) still the IdRef or b) replaced by numeric literal
when (val arg = funCall.args[0]) {
is IdentifierReference -> {
val decl = arg.targetVarDecl(program)!!
val decl = arg.targetVarDecl()!!
decl.type shouldBe VarDeclType.CONST
decl.datatype shouldBe DataType.UBYTE
(decl.value as NumericLiteral).number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0]

View File

@@ -36,6 +36,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
warnSymbolShadowing = false,
quietAll = true,
quietAssembler = true,
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,
@@ -156,6 +157,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"interpolation",
"kefrenbars",
"keyboardhandler",
"landscape",
"life",
"mandelbrot",
"multi-irq-old",

View File

@@ -34,7 +34,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] as IdentifierReference }
.map { it.targetVarDecl(program)!!.value as StringLiteral }
.map { it.targetVarDecl()!!.value as StringLiteral }
strLits[0].value shouldBe "main.bar"
strLits[1].value shouldBe "foo.bar"
@@ -57,7 +57,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] }
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteral
val str0 = (args[0] as IdentifierReference).targetVarDecl()!!.value as StringLiteral
str0.value shouldBe "main.bar"
str0.definingScope.name shouldBe "main"

View File

@@ -28,6 +28,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
warnSymbolShadowing = false,
quietAll = true,
quietAssembler = true,
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,

View File

@@ -21,6 +21,8 @@ class TestLaunchEmu: FunSpec({
<VARIABLESNOINIT>
</VARIABLESNOINIT>
<VARIABLESNOINITDIRTY>
</VARIABLESNOINITDIRTY>
<VARIABLESWITHINIT>
</VARIABLESWITHINIT>
<CONSTANTS>

View File

@@ -262,12 +262,13 @@ class TestMemory: FunSpec({
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.UNDEFINED)
}
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.LONG)
shouldThrow<NoSuchElementException> {
target.memorySize(DataType.arrayFor(BaseDataType.LONG), 10)
}
target.memorySize(BaseDataType.BOOL) shouldBe 1
target.memorySize(BaseDataType.BYTE) shouldBe 1
target.memorySize(BaseDataType.WORD) shouldBe 2
target.memorySize(BaseDataType.LONG) shouldBe 4
target.memorySize(BaseDataType.FLOAT) shouldBe target.FLOAT_MEM_SIZE
target.memorySize(DataType.BOOL, null) shouldBe 1
@@ -283,13 +284,14 @@ class TestMemory: FunSpec({
target.memorySize(DataType.arrayFor(BaseDataType.BOOL), 10) shouldBe 10
target.memorySize(DataType.arrayFor(BaseDataType.BYTE), 10) shouldBe 10
target.memorySize(DataType.arrayFor(BaseDataType.WORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.WORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.UWORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.FLOAT), 10) shouldBe 10*target.FLOAT_MEM_SIZE
target.memorySize(DataType.arrayFor(BaseDataType.WORD, true), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.UWORD, true), 10) shouldBe 20
target.memorySize(DataType.BOOL, 10) shouldBe 10
target.memorySize(DataType.UWORD, 10) shouldBe 20
target.memorySize(DataType.LONG, 10) shouldBe 40
target.memorySize(DataType.FLOAT, 10) shouldBe 10*target.FLOAT_MEM_SIZE
}
}

View File

@@ -37,16 +37,16 @@ class TestNumericLiteral: FunSpec({
sameValueAndType(NumericLiteral(BaseDataType.UWORD, 12345.0, dummyPos), NumericLiteral(BaseDataType.UWORD, 12345.0, dummyPos)) shouldBe true
}
test("test truncating") {
test("test truncating avoidance") {
shouldThrow<ExpressionError> {
NumericLiteral(BaseDataType.BYTE, -2.345, dummyPos)
}.message shouldContain "refused truncating"
}.message shouldContain "float value given for integer"
shouldThrow<ExpressionError> {
NumericLiteral(BaseDataType.BYTE, -2.6, dummyPos)
}.message shouldContain "refused truncating"
}.message shouldContain "float value given for integer"
shouldThrow<ExpressionError> {
NumericLiteral(BaseDataType.UWORD, 2222.345, dummyPos)
}.message shouldContain "refused truncating"
}.message shouldContain "float value given for integer"
NumericLiteral(BaseDataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
NumericLiteral(BaseDataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
NumericLiteral(BaseDataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
@@ -213,7 +213,7 @@ class TestNumericLiteral: FunSpec({
test("cast can change value") {
fun num(dt: BaseDataType, num: Double): NumericLiteral {
val n = NumericLiteral(dt, num, Position.DUMMY)
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
n.linkParents(AnonymousScope.empty())
return n
}
val cast1 = num(BaseDataType.UBYTE, 200.0).cast(BaseDataType.BYTE, false)
@@ -233,7 +233,7 @@ class TestNumericLiteral: FunSpec({
test("convert cannot change value") {
fun num(dt: BaseDataType, num: Double): NumericLiteral {
val n = NumericLiteral(dt, num, Position.DUMMY)
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
n.linkParents(AnonymousScope.empty())
return n
}
num(BaseDataType.UBYTE, 200.0).convertTypeKeepValue(BaseDataType.BYTE).isValid shouldBe false

View File

@@ -22,7 +22,9 @@ import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.vm.VmRunner
import prog8tests.helpers.*
import kotlin.io.path.readText
class TestOptimization: FunSpec({
@@ -225,7 +227,7 @@ other {
}
"""
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly = false)!!
val assignFF = result.compilerAst.entrypoint.statements.last() as Assignment
val assignFF = result.compilerAst.entrypoint.statements.dropLast(1).last() as Assignment
assignFF.isAugmentable shouldBe true
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignFF.value as BinaryExpression
@@ -252,7 +254,7 @@ other {
}
"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
result.compilerAst.entrypoint.statements.size shouldBe 7
result.compilerAst.entrypoint.statements.size shouldBe 8
val alldecls = result.compilerAst.entrypoint.allDefinedSymbols.toList()
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
}
@@ -276,12 +278,12 @@ other {
}
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
result.compilerAst.entrypoint.statements.size shouldBe 3
result.compilerAst.entrypoint.statements.size shouldBe 4
val ifstmt = result.compilerAst.entrypoint.statements[0] as IfElse
ifstmt.truepart.statements.size shouldBe 1
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
val func2 = result.compilerAst.entrypoint.statements[2] as Subroutine
func2.statements.size shouldBe 2
val func2 = result.compilerAst.entrypoint.statements.last() as Subroutine
func2.statements.size shouldBe 3
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
}
@@ -312,7 +314,7 @@ main {
}
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
result.compilerAst.entrypoint.statements.size shouldBe 0
result.compilerAst.entrypoint.statements.size shouldBe 1
result.compilerAst.entrypoint.definingScope.statements.size shouldBe 1
}
@@ -350,7 +352,7 @@ main {
z6 = z1 - 5
*/
val statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 12
statements.size shouldBe 13
val z1decl = statements[0] as VarDecl
val z1init = statements[1] as Assignment
val z2decl = statements[2] as VarDecl
@@ -395,8 +397,8 @@ main {
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val assign=stmts.last() as Assignment
stmts.size shouldBe 6
val assign=stmts[4] as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
@@ -412,8 +414,8 @@ main {
"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val assign=stmts.last() as Assignment
stmts.size shouldBe 6
val assign=stmts[4] as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
@@ -431,7 +433,7 @@ main {
"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 10
stmts.size shouldBe 11
stmts.filterIsInstance<VarDecl>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 5
}
@@ -508,7 +510,7 @@ main {
xx += 6
*/
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 3
}
@@ -537,13 +539,13 @@ main {
xx += 10
*/
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
stmts.size shouldBe 8
stmts.filterIsInstance<VarDecl>().size shouldBe 2
stmts.filterIsInstance<Assignment>().size shouldBe 5
val assignXX1 = stmts[1] as Assignment
assignXX1.target.identifier!!.nameInSource shouldBe listOf("xx")
assignXX1.value shouldBe NumericLiteral(BaseDataType.UWORD, 20.0, Position.DUMMY)
val assignXX2 = stmts.last() as Assignment
val assignXX2 = stmts[6] as Assignment
assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
val xxValue = assignXX2.value as BinaryExpression
xxValue.operator shouldBe "+"
@@ -577,7 +579,7 @@ main {
thingy++
*/
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
stmts.size shouldBe 7
val ifStmt = stmts[5] as IfElse
val containment = ifStmt.condition as ContainmentCheck
(containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
@@ -612,7 +614,7 @@ main {
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
val containment = ifStmt.condition as ContainmentCheck
(containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
@@ -634,7 +636,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
}
@@ -652,7 +654,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
}
@@ -670,7 +672,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
}
@@ -687,7 +689,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3
stmts.size shouldBe 4
}
test("repeated assignments to IO register should remain") {
@@ -828,7 +830,7 @@ main {
val errors = ErrorReporterForTests()
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false, errors = errors)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 4
st.size shouldBe 5
val xxConst = st[0] as VarDecl
xxConst.type shouldBe VarDeclType.CONST
xxConst.name shouldBe "xx"
@@ -872,7 +874,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
st.size shouldBe 9
val if1c = (st[4] as IfElse).condition as PrefixExpression
val if2c = (st[5] as IfElse).condition as PrefixExpression
val if3c = (st[6] as IfElse).condition as PrefixExpression
@@ -915,7 +917,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 12
st.size shouldBe 13
val if1 = st[4] as IfElse
val if2 = st[5] as IfElse
val if3 = st[6] as IfElse
@@ -970,7 +972,7 @@ main {
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 17
st.size shouldBe 18
val answerValue = (st[3] as Assignment).value
answerValue shouldBe NumericLiteral(BaseDataType.UWORD, 0.0, Position.DUMMY)
@@ -1019,7 +1021,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 3
st.size shouldBe 4
val ifCond1 = (st[0] as IfElse).condition as BinaryExpression
val ifCond2 = (st[1] as IfElse).condition as BinaryExpression
val ifCond3 = (st[2] as IfElse).condition as BinaryExpression
@@ -1178,4 +1180,54 @@ main {
}"""
compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
}
test("correct unused block removal for virtual target") {
val src="""
main {
sub start() {
cx16.r0++
}
}
some_block {
uword buffer = memory("arena", 2000, 0)
}
other_block {
sub redherring (uword buffer) {
%ir {{
loadm.w r99000,other_block.redherring.buffer
}}
}
}
"""
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText(), false)
}
test("correct unused block removal for c64 target") {
val src="""
main {
sub start() {
cx16.r0++
}
}
some_block {
uword buffer = memory("arena", 2000, 0)
}
other_block {
sub redherring (uword buffer) {
%asm {{
lda #<p8b_other_block.p8s_redherring.p8v_buffer
ldy #>p8b_other_block.p8s_redherring.p8v_buffer
}}
}
}"""
compileText(C64Target(), true, src, outputDir) shouldNotBe null
}
})

View File

@@ -19,11 +19,7 @@ import prog8.code.core.Position
import prog8.code.core.unescape
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.encodings.Encoder
import prog8.code.target.encodings.AtasciiEncoding
import prog8.code.target.encodings.C64osEncoding
import prog8.code.target.encodings.IsoEncoding
import prog8.code.target.encodings.PetsciiEncoding
import prog8.code.target.encodings.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import java.io.CharConversionException
@@ -227,7 +223,7 @@ class TestStringEncodings: FunSpec({
context("iso") {
test("iso accepts iso-characters") {
val result = IsoEncoding.encode("a_~ëç")
val result = IsoEncoding.encode("a_~ëç", false)
result.getOrElse { throw it }.map {it.toInt()} shouldBe listOf(97, 95, 126, 235, 231)
}
@@ -276,13 +272,13 @@ class TestStringEncodings: FunSpec({
passthrough[1] shouldBe '\u801b'
passthrough[2] shouldBe '\u8099'
passthrough[3] shouldBe '\u80ff'
var encoded = Encoder.encodeString(passthrough, Encoding.PETSCII)
var encoded = Encoder(false).encodeString(passthrough, Encoding.PETSCII)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.ATASCII)
encoded = Encoder(false).encodeString(passthrough, Encoding.ATASCII)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.SCREENCODES)
encoded = Encoder(false).encodeString(passthrough, Encoding.SCREENCODES)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.ISO)
encoded = Encoder(false).encodeString(passthrough, Encoding.ISO)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
}
@@ -413,5 +409,39 @@ class TestStringEncodings: FunSpec({
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
char2.number shouldBe 80.0
}
test("with newline conversion") {
val encoder = Encoder(true)
encoder.encodeString("\n\r", Encoding.PETSCII) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.SCREENCODES) shouldBe listOf<UByte>(141u, 141u)
encoder.encodeString("\n\r", Encoding.ATASCII) shouldBe listOf<UByte>(155u, 155u)
encoder.encodeString("\n\r", Encoding.ISO) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.ISO5) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.ISO16) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.CP437) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.KATAKANA) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.C64OS) shouldBe listOf<UByte>(13u, 13u)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.PETSCII).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.ISO).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.CP437).map { it.code } shouldBe listOf(10, 13)
}
test("without newline conversion") {
val encoder = Encoder(false)
encoder.encodeString("\n\r", Encoding.PETSCII) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.SCREENCODES) shouldBe listOf<UByte>(141u, 141u)
encoder.encodeString("\n\r", Encoding.ATASCII) shouldBe listOf<UByte>(155u, 155u)
encoder.encodeString("\n\r", Encoding.ISO) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.ISO5) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.ISO16) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.CP437) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.KATAKANA) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.C64OS) shouldBe listOf<UByte>(13u, 13u)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.PETSCII).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.ISO).map { it.code } shouldBe listOf(10, 13)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.CP437).map { it.code } shouldBe listOf(10, 13)
}
})

View File

@@ -54,6 +54,9 @@ class TestSubroutines: FunSpec({
}
asmsub asmfunc(str thing @AY) {
%asm {{
rts
}}
}
sub func(str thing) {
@@ -67,12 +70,12 @@ class TestSubroutines: FunSpec({
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.statements.isEmpty() shouldBe true
asmfunc.statements.size shouldBe 1
func.isAsmSubroutine shouldBe false
withClue("str param for subroutines should be changed into UWORD") {
asmfunc.parameters.single().type shouldBe DataType.UWORD
func.parameters.single().type shouldBe DataType.UWORD
func.statements.size shouldBe 4
func.statements.size shouldBe 5
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.UWORD
@@ -174,6 +177,9 @@ class TestSubroutines: FunSpec({
}
asmsub asmfunc(ubyte[] thing @AY) {
%asm {{
rts
}}
}
sub func(ubyte[] thing) {

Some files were not shown because too many files have changed in this diff Show More