mirror of
https://github.com/irmen/prog8.git
synced 2025-06-16 09:23:43 +00:00
Compare commits
190 Commits
Author | SHA1 | Date | |
---|---|---|---|
e426fc0922 | |||
5d4bfffc7e | |||
207cdaf7a4 | |||
7315b581ce | |||
38efaae7b2 | |||
469e042216 | |||
0f1a4b9d8f | |||
7303c00296 | |||
fc55b34d84 | |||
6f67fc0e02 | |||
562d722ad5 | |||
144c1ba3a6 | |||
06b032af91 | |||
3603140114 | |||
e094785cbd | |||
e7408224ac | |||
e67c05c274 | |||
b22804efaf | |||
890f55f91a | |||
cc5fc0b892 | |||
5efe2b027a | |||
5b6569d0f9 | |||
0eda7ac498 | |||
a5ef353484 | |||
67a36d8d31 | |||
7cc3cc3990 | |||
dc0edc4c2b | |||
71d2f091e5 | |||
c2f062a391 | |||
224f490455 | |||
5b35232ab4 | |||
6d6db70e42 | |||
6830e15b4e | |||
3f07cad35d | |||
e951340033 | |||
db8912a735 | |||
0e297731a3 | |||
f20c4f98ac | |||
05e60cc7c0 | |||
55b4469767 | |||
f15516e478 | |||
17ceadbadf | |||
8c25b2b316 | |||
8b1ae404a3 | |||
13534cd4a9 | |||
abfb345503 | |||
42ae935496 | |||
434515d957 | |||
094f7803b7 | |||
b0c7bad391 | |||
e9a4a905ef | |||
7b6cd0cfbe | |||
b718b12083 | |||
cfa7258ff4 | |||
b70e0a0870 | |||
da8eb464b8 | |||
8f9d1cfa30 | |||
585009ac5c | |||
30ee65fd14 | |||
76428b16f0 | |||
0d7b14e2d8 | |||
a9d19d02b3 | |||
adcbe55307 | |||
aa99a7df64 | |||
00afa1ce52 | |||
e94bf4c63c | |||
ec5adffdc2 | |||
733c17ad3a | |||
53b0b562e6 | |||
fabae6e970 | |||
a9f9c40d8a | |||
6fc89607d3 | |||
2340760f53 | |||
39d6d2857e | |||
7b722a0001 | |||
e7682119e0 | |||
af6be44676 | |||
5a8f97a0b6 | |||
0d4dd385b8 | |||
94f0f3e966 | |||
43e31765e5 | |||
7c1bdfe713 | |||
9f09784b55 | |||
e7a3a89bfb | |||
7ea7e63f44 | |||
1d2ce2cbeb | |||
06cf2e0bd7 | |||
9d219ae4b9 | |||
71f5a6c50e | |||
90b2be2bf4 | |||
db1aa8fcbd | |||
11c000f764 | |||
4d6dcbd173 | |||
0da117efd2 | |||
533c368e32 | |||
8883513b0e | |||
dcc9a71455 | |||
1a56743bb1 | |||
387a4b7c35 | |||
1d65d63bd9 | |||
dda19c29fe | |||
ca41669f4f | |||
0e1886e6bd | |||
c26e116f0e | |||
5c9c7f2c5e | |||
ca2fb6cef3 | |||
46dac909ef | |||
b1e4347e10 | |||
97aa91c75e | |||
4f8fb32136 | |||
e0fbce0087 | |||
fb22f78fb3 | |||
d6393cdbe5 | |||
5167fdb3f0 | |||
ab00822764 | |||
b4352ad38b | |||
d07d00fa41 | |||
11d87e4725 | |||
627ed51a1b | |||
c8f3bfa726 | |||
3091e3a1c8 | |||
2f3e7d1c27 | |||
0e831d4b92 | |||
7294ec9a3c | |||
e34bab9585 | |||
7dd14955c1 | |||
6428ced157 | |||
30a42ec1bd | |||
aacea3e9db | |||
6886b61186 | |||
0744c9fa29 | |||
502a665ffc | |||
3c315703c0 | |||
12ed07a607 | |||
101b33c381 | |||
97f4316653 | |||
b0704e86f0 | |||
a182b13e5a | |||
80b630a1e4 | |||
475efbe007 | |||
3ab5e5ac48 | |||
c6c5ff2089 | |||
176ec8ac7d | |||
dcdd4b3255 | |||
fc0a0105b3 | |||
f3960d21a8 | |||
a44d853c1b | |||
6b41734d6a | |||
c33dc0f3be | |||
bb5ffb24a8 | |||
a878c9a61d | |||
6454bf8ec4 | |||
40aa733ea7 | |||
f37a822725 | |||
f249ccd414 | |||
7ef4ddf0f3 | |||
d8e18df3a1 | |||
78d3d9d27d | |||
0aa0ec5abd | |||
b6eef3612f | |||
666d62dd7a | |||
44ee4b989f | |||
18790d867c | |||
d6b8936376 | |||
4d840c7db8 | |||
4d2b21816d | |||
2d34fdd28f | |||
68abda1219 | |||
f778f08f76 | |||
ac1bd2fb7b | |||
4b7b1379d9 | |||
e560e2ab3f | |||
1e441c2ddf | |||
93ce74eeb1 | |||
f718f4251b | |||
4644c9b621 | |||
197081f10d | |||
00b717cde8 | |||
34aa917ca4 | |||
a38ddcb364 | |||
5b9576df4e | |||
310219e5d7 | |||
a0deb463c9 | |||
90ddec2ad8 | |||
f6b03d5a78 | |||
f531daa872 | |||
046dceb5c2 | |||
dcc1f00048 | |||
05f935b598 | |||
f2d27403c5 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -19,7 +19,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<type id="Python" />
|
<type id="Python" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
3
.idea/modules.xml
generated
3
.idea/modules.xml
generated
@ -2,10 +2,10 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/codeAst/codeAst.iml" filepath="$PROJECT_DIR$/codeAst/codeAst.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||||
@ -14,6 +14,7 @@
|
|||||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
<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$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
|
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
|
@ -78,6 +78,9 @@ It's handy to have an emulator (or a real machine perhaps!) to run the programs
|
|||||||
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||||
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||||
|
|
||||||
|
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
||||||
|
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
|
||||||
|
|
||||||
|
|
||||||
Example code
|
Example code
|
||||||
------------
|
------------
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package prog8
|
package prog8.code
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By convention, the right side of an `Either` is used to hold successful values.
|
* By convention, the right side of an `Either` is used to hold successful values.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
sealed class Either<out L, out R> {
|
sealed class Either<out L, out R> {
|
||||||
|
|
@ -5,7 +5,7 @@ import prog8.code.core.*
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tree structure containing all symbol definitions in the program
|
* Tree structure containing all symbol definitions in the program
|
||||||
* (blocks, subroutines, variables (all types) and labels).
|
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||||
*/
|
*/
|
||||||
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
||||||
fun print() = printIndented(0)
|
fun print() = printIndented(0)
|
||||||
@ -54,6 +54,10 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
|||||||
vars
|
vars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
||||||
|
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
|
||||||
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +72,8 @@ enum class StNodeType {
|
|||||||
STATICVAR,
|
STATICVAR,
|
||||||
MEMVAR,
|
MEMVAR,
|
||||||
CONSTANT,
|
CONSTANT,
|
||||||
BUILTINFUNC
|
BUILTINFUNC,
|
||||||
|
MEMORYSLAB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -142,6 +147,7 @@ open class StNode(val name: String,
|
|||||||
StNodeType.LABEL -> print("(L) ")
|
StNodeType.LABEL -> print("(L) ")
|
||||||
StNodeType.STATICVAR -> print("(V) ")
|
StNodeType.STATICVAR -> print("(V) ")
|
||||||
StNodeType.MEMVAR -> print("(M) ")
|
StNodeType.MEMVAR -> print("(M) ")
|
||||||
|
StNodeType.MEMORYSLAB -> print("(MS) ")
|
||||||
StNodeType.CONSTANT -> print("(C) ")
|
StNodeType.CONSTANT -> print("(C) ")
|
||||||
StNodeType.BUILTINFUNC -> print("(F) ")
|
StNodeType.BUILTINFUNC -> print("(F) ")
|
||||||
StNodeType.ROMSUB -> print("(R) ")
|
StNodeType.ROMSUB -> print("(R) ")
|
||||||
@ -163,31 +169,41 @@ open class StNode(val name: String,
|
|||||||
|
|
||||||
class StStaticVariable(name: String,
|
class StStaticVariable(name: String,
|
||||||
val dt: DataType,
|
val dt: DataType,
|
||||||
val initialNumericValue: Double?,
|
val bss: Boolean,
|
||||||
val initialStringValue: StString?,
|
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||||
val initialArrayValue: StArray?,
|
val onetimeInitializationStringValue: StString?,
|
||||||
|
val onetimeInitializationArrayValue: StArray?,
|
||||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||||
val zpwish: ZeropageWish,
|
val zpwish: ZeropageWish,
|
||||||
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(length!=null) {
|
if(bss) {
|
||||||
require(initialNumericValue == null)
|
require(onetimeInitializationNumericValue==null)
|
||||||
if(initialArrayValue!=null)
|
require(onetimeInitializationStringValue==null)
|
||||||
require(length == initialArrayValue.size)
|
require(onetimeInitializationArrayValue.isNullOrEmpty())
|
||||||
|
} else {
|
||||||
|
require(onetimeInitializationNumericValue!=null ||
|
||||||
|
onetimeInitializationStringValue!=null ||
|
||||||
|
onetimeInitializationArrayValue!=null)
|
||||||
}
|
}
|
||||||
if(initialNumericValue!=null)
|
if(length!=null) {
|
||||||
|
require(onetimeInitializationNumericValue == null)
|
||||||
|
if(onetimeInitializationArrayValue!=null)
|
||||||
|
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||||
|
}
|
||||||
|
if(onetimeInitializationNumericValue!=null)
|
||||||
require(dt in NumericDatatypes)
|
require(dt in NumericDatatypes)
|
||||||
if(initialArrayValue!=null)
|
if(onetimeInitializationArrayValue!=null)
|
||||||
require(dt in ArrayDatatypes)
|
require(dt in ArrayDatatypes)
|
||||||
if(initialStringValue!=null) {
|
if(onetimeInitializationStringValue!=null) {
|
||||||
require(dt == DataType.STR)
|
require(dt == DataType.STR)
|
||||||
require(length == initialStringValue.first.length+1)
|
require(length == onetimeInitializationStringValue.first.length+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print("$name dt=$dt zpw=$zpwish")
|
print("$name dt=$dt zpw=$zpwish bss=$bss")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,8 +227,19 @@ class StMemVar(name: String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StMemorySlab(
|
||||||
|
name: String,
|
||||||
|
val size: UInt,
|
||||||
|
val align: UInt,
|
||||||
|
position: Position
|
||||||
|
):
|
||||||
|
StNode(name, StNodeType.MEMORYSLAB, position) {
|
||||||
|
override fun printProperties() {
|
||||||
|
print("$name size=$size align=$align")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, position: Position) :
|
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
||||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
StNode(name, StNodeType.SUBROUTINE, position) {
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print(name)
|
print(name)
|
||||||
@ -220,7 +247,11 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, position:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StRomSub(name: String, val address: UInt, parameters: List<StSubroutineParameter>, position: Position) :
|
class StRomSub(name: String,
|
||||||
|
val address: UInt,
|
||||||
|
val parameters: List<StRomSubParameter>,
|
||||||
|
val returns: List<RegisterOrStatusflag>,
|
||||||
|
position: Position) :
|
||||||
StNode(name, StNodeType.ROMSUB, position) {
|
StNode(name, StNodeType.ROMSUB, position) {
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print("$name address=${address.toHex()}")
|
print("$name address=${address.toHex()}")
|
||||||
@ -229,6 +260,7 @@ class StRomSub(name: String, val address: UInt, parameters: List<StSubroutinePar
|
|||||||
|
|
||||||
|
|
||||||
class StSubroutineParameter(val name: String, val type: DataType)
|
class StSubroutineParameter(val name: String, val type: DataType)
|
||||||
|
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||||
class StArrayElement(val number: Double?, val addressOf: List<String>?)
|
class StArrayElement(val number: Double?, val addressOf: List<String>?)
|
||||||
|
|
||||||
typealias StString = Pair<String, Encoding>
|
typealias StString = Pair<String, Encoding>
|
@ -3,7 +3,7 @@ package prog8.code.ast
|
|||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
// New (work-in-progress) simplified AST for the code generator.
|
// New simplified AST for the code generator.
|
||||||
|
|
||||||
|
|
||||||
sealed class PtNode(val position: Position) {
|
sealed class PtNode(val position: Position) {
|
||||||
@ -42,7 +42,7 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
||||||
val scopedName: List<String> by lazy {
|
val scopedName: List<String> by lazy {
|
||||||
var namedParent: PtNode = this.parent
|
var namedParent: PtNode = this.parent
|
||||||
if(namedParent is PtProgram)
|
if(namedParent is PtProgram)
|
||||||
@ -96,8 +96,13 @@ class PtBlock(name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtInlineAssembly(val assembly: String, position: Position) : PtNode(position) {
|
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||||
override fun printProperties() {}
|
override fun printProperties() {}
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
|
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12,6 +12,15 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
|||||||
init {
|
init {
|
||||||
if(type==DataType.BOOL)
|
if(type==DataType.BOOL)
|
||||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||||
|
if(type==DataType.UNDEFINED) {
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
when(this) {
|
||||||
|
is PtBuiltinFunctionCall -> { /* void function call */ }
|
||||||
|
is PtFunctionCall -> { /* void function call */ }
|
||||||
|
is PtIdentifier -> { /* non-variable identifier */ }
|
||||||
|
else -> throw IllegalArgumentException("type should be known @$position")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
@ -164,8 +173,7 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
||||||
if(operator !in setOf("+", "-", "~"))
|
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
||||||
throw IllegalArgumentException("invalid prefix operator: $operator")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
@ -8,6 +8,7 @@ class PtAsmSub(
|
|||||||
val address: UInt?,
|
val address: UInt?,
|
||||||
val clobbers: Set<CpuRegister>,
|
val clobbers: Set<CpuRegister>,
|
||||||
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
|
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
|
||||||
|
val returnTypes: List<DataType>, // TODO join with register as Pairs ?
|
||||||
val retvalRegisters: List<RegisterOrStatusflag>,
|
val retvalRegisters: List<RegisterOrStatusflag>,
|
||||||
val inline: Boolean,
|
val inline: Boolean,
|
||||||
position: Position
|
position: Position
|
||||||
@ -28,6 +29,14 @@ class PtSub(
|
|||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print(name)
|
print(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// params and return value should not be str
|
||||||
|
if(parameters.any{ it.type !in NumericDatatypes })
|
||||||
|
throw AssemblyError("non-numeric parameter")
|
||||||
|
if(returntype!=null && returntype !in NumericDatatypes)
|
||||||
|
throw AssemblyError("non-numeric returntype $returntype")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,6 +20,7 @@ class CompilationOptions(val output: OutputType,
|
|||||||
var asmQuiet: Boolean = false,
|
var asmQuiet: Boolean = false,
|
||||||
var asmListfile: Boolean = false,
|
var asmListfile: Boolean = false,
|
||||||
var experimentalCodegen: Boolean = false,
|
var experimentalCodegen: Boolean = false,
|
||||||
|
var keepIR: Boolean = false,
|
||||||
var evalStackBaseAddress: UInt? = null,
|
var evalStackBaseAddress: UInt? = null,
|
||||||
var outputDir: Path = Path(""),
|
var outputDir: Path = Path(""),
|
||||||
var symbolDefs: Map<String, String> = emptyMap()
|
var symbolDefs: Map<String, String> = emptyMap()
|
||||||
|
@ -3,11 +3,6 @@ package prog8.code.core
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
interface IMachineFloat {
|
|
||||||
fun toDouble(): Double
|
|
||||||
fun makeFloatFillAsm(): String
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class CpuType {
|
enum class CpuType {
|
||||||
CPU6502,
|
CPU6502,
|
||||||
CPU65c02,
|
CPU65c02,
|
||||||
@ -27,7 +22,7 @@ interface IMachineDefinition {
|
|||||||
val cpu: CpuType
|
val cpu: CpuType
|
||||||
|
|
||||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||||
fun getFloat(num: Number): IMachineFloat
|
fun getFloatAsmBytes(num: Number): String
|
||||||
|
|
||||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||||
|
@ -3,7 +3,7 @@ package prog8.code.core
|
|||||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
|
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
|
||||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%")
|
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ sealed class SourceCode {
|
|||||||
* [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8"
|
* [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8"
|
||||||
*/
|
*/
|
||||||
class Resource(pathString: String): SourceCode() {
|
class Resource(pathString: String): SourceCode() {
|
||||||
private val normalized = "/" + Path.of(pathString).normalize().toMutableList().joinToString("/")
|
private val normalized = "/" + Path(pathString).normalize().toMutableList().joinToString("/")
|
||||||
|
|
||||||
override val isFromResources = true
|
override val isFromResources = true
|
||||||
override val isFromFilesystem = false
|
override val isFromFilesystem = false
|
||||||
@ -125,7 +125,7 @@ sealed class SourceCode {
|
|||||||
}
|
}
|
||||||
val stream = object {}.javaClass.getResourceAsStream(normalized)
|
val stream = object {}.javaClass.getResourceAsStream(normalized)
|
||||||
text = stream!!.reader().use { it.readText() }
|
text = stream!!.reader().use { it.readText() }
|
||||||
name = Path.of(pathString).toFile().nameWithoutExtension
|
name = Path(pathString).toFile().nameWithoutExtension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,4 +117,6 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
require(size>0)
|
require(size>0)
|
||||||
return free.containsAll((address until address+size.toUInt()).toList())
|
return free.containsAll((address until address+size.toUInt()).toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract fun allocateCx16VirtualRegisters()
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
override fun getFloat(num: Number) = TODO("float from number")
|
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.output == OutputType.XEX)
|
return if (compilerOptions.output == OutputType.XEX)
|
||||||
|
@ -12,7 +12,6 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
override val SCRATCH_W1 = 0xcdu // temp storage 1 for a word $cd+$ce
|
override val SCRATCH_W1 = 0xcdu // temp storage 1 for a word $cd+$ce
|
||||||
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (options.floats && options.zeropage !in arrayOf(
|
if (options.floats && options.zeropage !in arrayOf(
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
@ -40,6 +39,14 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val distictFree = free.distinct()
|
||||||
|
free.clear()
|
||||||
|
free.addAll(distictFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allocateCx16VirtualRegisters() {
|
||||||
|
TODO("Not known if atari can put the virtual regs in ZP")
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
@ -38,6 +38,14 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val distictFree = free.distinct()
|
||||||
|
free.clear()
|
||||||
|
free.addAll(distictFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allocateCx16VirtualRegisters() {
|
||||||
|
TODO("Not known if C128 can put the virtual regs in ZP")
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package prog8.code.target.c64
|
package prog8.code.target.c64
|
||||||
|
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.*
|
||||||
import prog8.code.core.InternalCompilerException
|
|
||||||
import prog8.code.core.Zeropage
|
|
||||||
import prog8.code.core.ZeropageType
|
|
||||||
|
|
||||||
|
|
||||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
@ -59,7 +56,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||||
// add the free Zp addresses
|
// add the free Zp addresses
|
||||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||||
} else {
|
} else {
|
||||||
@ -68,6 +65,32 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val distictFree = free.distinct()
|
||||||
|
free.clear()
|
||||||
|
free.addAll(distictFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
|
||||||
|
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||||
|
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||||
|
allocateCx16VirtualRegisters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allocateCx16VirtualRegisters() {
|
||||||
|
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||||
|
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||||
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
|
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||||
|
for(reg in 0..15) {
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
|
free.remove((4+reg*2).toUInt())
|
||||||
|
free.remove((5+reg*2).toUInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,11 @@
|
|||||||
package prog8.code.target.cbm
|
package prog8.code.target.cbm
|
||||||
|
|
||||||
import prog8.code.core.IMachineFloat
|
|
||||||
import prog8.code.core.InternalCompilerException
|
import prog8.code.core.InternalCompilerException
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte): IMachineFloat {
|
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||||
@ -58,7 +57,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toDouble(): Double {
|
fun toDouble(): Double {
|
||||||
if (this == zero) return 0.0
|
if (this == zero) return 0.0
|
||||||
val exp = b0.toInt() - 128
|
val exp = b0.toInt() - 128
|
||||||
val sign = (b1.toInt() and 0x80) > 0
|
val sign = (b1.toInt() and 0x80) > 0
|
||||||
@ -67,7 +66,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
|||||||
return if (sign) -result else result
|
return if (sign) -result else result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun makeFloatFillAsm(): String {
|
fun makeFloatFillAsm(): String {
|
||||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||||
|
@ -20,7 +20,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
listOf("syslib")
|
listOf("syslib")
|
||||||
|
@ -43,19 +43,27 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val distictFree = free.distinct()
|
||||||
|
free.clear()
|
||||||
|
free.addAll(distictFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
|
||||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
allocateCx16VirtualRegisters()
|
||||||
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
}
|
||||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
}
|
||||||
for(reg in 0..15) {
|
|
||||||
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
override fun allocateCx16VirtualRegisters() {
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
for(reg in 0..15) {
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
}
|
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
|
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,9 +4,10 @@ import prog8.code.core.CompilationOptions
|
|||||||
import prog8.code.core.CpuType
|
import prog8.code.core.CpuType
|
||||||
import prog8.code.core.IMachineDefinition
|
import prog8.code.core.IMachineDefinition
|
||||||
import prog8.code.core.Zeropage
|
import prog8.code.core.Zeropage
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.isReadable
|
||||||
|
import kotlin.io.path.name
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
class VirtualMachineDefinition: IMachineDefinition {
|
class VirtualMachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override lateinit var zeropage: Zeropage // not actually used
|
override lateinit var zeropage: Zeropage // not actually used
|
||||||
|
|
||||||
override fun getFloat(num: Number) = TODO("float from number")
|
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return listOf("syslib")
|
return listOf("syslib")
|
||||||
@ -30,10 +31,18 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||||
println("\nStarting Virtual Machine...")
|
println("\nStarting Virtual Machine...")
|
||||||
// to not have external module dependencies we launch the virtual machine via reflection
|
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
|
||||||
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
||||||
val source = File("$programNameWithPath.p8virt").readText()
|
val filename = programNameWithPath.name
|
||||||
vm.runProgram(source, true)
|
if(programNameWithPath.isReadable()) {
|
||||||
|
vm.runProgram(programNameWithPath.readText())
|
||||||
|
} else {
|
||||||
|
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
|
||||||
|
if(withExt.isReadable())
|
||||||
|
vm.runProgram(withExt.readText())
|
||||||
|
else
|
||||||
|
throw NoSuchFileException(withExt.toFile(), reason="not a .p8ir file")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = false
|
override fun isIOAddress(address: UInt): Boolean = false
|
||||||
@ -44,5 +53,5 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IVirtualMachineRunner {
|
interface IVirtualMachineRunner {
|
||||||
fun runProgram(source: String, throttle: Boolean)
|
fun runProgram(irSource: CharSequence)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ compileTestKotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':codeAst')
|
|
||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="module" module-name="codeAst" />
|
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
<orderEntry type="module" module-name="compilerAst" />
|
||||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
|
@ -41,7 +41,7 @@ class AsmGen(internal val program: Program,
|
|||||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this, allocator)
|
private val expressionsAsmGen = ExpressionsAsmGen(program, this, allocator)
|
||||||
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
|
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
|
||||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, allocator)
|
private val assignmentAsmGen = AssignmentAsmGen(program, this, allocator)
|
||||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen, allocator)
|
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
|
||||||
|
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
override fun compileToAssembly(): IAssemblyProgram? {
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun out(str: String, splitlines: Boolean = true) {
|
internal fun out(str: String, splitlines: Boolean = true) {
|
||||||
val fragment = (if(splitlines && " | " in str) str.replace("|", "\n") else str).trim('\n')
|
val fragment = (if(splitlines && " | " in str) str.replace("|", "\n") else str).trim('\r', '\n')
|
||||||
if (splitlines) {
|
if (splitlines) {
|
||||||
for (line in fragment.splitToSequence('\n')) {
|
for (line in fragment.splitToSequence('\n')) {
|
||||||
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
|
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
|
||||||
@ -106,7 +106,7 @@ class AsmGen(internal val program: Program,
|
|||||||
DataType.BYTE -> listOf("cx16", "r9sL")
|
DataType.BYTE -> listOf("cx16", "r9sL")
|
||||||
DataType.UWORD -> listOf("cx16", "r9")
|
DataType.UWORD -> listOf("cx16", "r9")
|
||||||
DataType.WORD -> listOf("cx16", "r9s")
|
DataType.WORD -> listOf("cx16", "r9s")
|
||||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
|
DataType.FLOAT -> TODO("no temporary float var available")
|
||||||
else -> throw FatalAstException("invalid dt $dt")
|
else -> throw FatalAstException("invalid dt $dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,13 +311,12 @@ class AsmGen(internal val program: Program,
|
|||||||
is Subroutine -> programGen.translateSubroutine(stmt)
|
is Subroutine -> programGen.translateSubroutine(stmt)
|
||||||
is InlineAssembly -> translate(stmt)
|
is InlineAssembly -> translate(stmt)
|
||||||
is BuiltinFunctionCallStatement -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
|
is BuiltinFunctionCallStatement -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
|
||||||
is FunctionCallStatement -> functioncallAsmGen.translateFunctionCallStatement(stmt) // TODO try to remove this last usage of FunctionCallStatement node in the codegen.
|
is FunctionCallStatement -> functioncallAsmGen.translateFunctionCallStatement(stmt)
|
||||||
is Assignment -> assignmentAsmGen.translate(stmt)
|
is Assignment -> assignmentAsmGen.translate(stmt)
|
||||||
is Jump -> {
|
is Jump -> {
|
||||||
val (asmLabel, indirect) = getJumpTarget(stmt)
|
val (asmLabel, indirect) = getJumpTarget(stmt)
|
||||||
jmp(asmLabel, indirect)
|
jmp(asmLabel, indirect)
|
||||||
}
|
}
|
||||||
is GoSub -> translate(stmt)
|
|
||||||
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
|
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
|
||||||
is Label -> translate(stmt)
|
is Label -> translate(stmt)
|
||||||
is ConditionalBranch -> translate(stmt)
|
is ConditionalBranch -> translate(stmt)
|
||||||
@ -441,15 +440,9 @@ class AsmGen(internal val program: Program,
|
|||||||
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
|
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
|
||||||
functioncallAsmGen.saveXbeforeCall(functionCall)
|
functioncallAsmGen.saveXbeforeCall(functionCall)
|
||||||
|
|
||||||
internal fun saveXbeforeCall(gosub: GoSub) =
|
|
||||||
functioncallAsmGen.saveXbeforeCall(gosub)
|
|
||||||
|
|
||||||
internal fun restoreXafterCall(functionCall: IFunctionCall) =
|
internal fun restoreXafterCall(functionCall: IFunctionCall) =
|
||||||
functioncallAsmGen.restoreXafterCall(functionCall)
|
functioncallAsmGen.restoreXafterCall(functionCall)
|
||||||
|
|
||||||
internal fun restoreXafterCall(gosub: GoSub) =
|
|
||||||
functioncallAsmGen.restoreXafterCall(gosub)
|
|
||||||
|
|
||||||
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
||||||
assignmentAsmGen.translateNormalAssignment(assign)
|
assignmentAsmGen.translateNormalAssignment(assign)
|
||||||
|
|
||||||
@ -845,7 +838,7 @@ $repeatLabel lda $counterVar
|
|||||||
if(stmt.definingModule.source is SourceCode.Generated)
|
if(stmt.definingModule.source is SourceCode.Generated)
|
||||||
throw AssemblyError("%asminclude inside non-library/non-filesystem module not yet supported")
|
throw AssemblyError("%asminclude inside non-library/non-filesystem module not yet supported")
|
||||||
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
|
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
|
||||||
success = { assemblyLines.add(it.trimEnd().trimStart('\n')) },
|
success = { assemblyLines.add(it.trimEnd().trimStart('\r', '\n')) },
|
||||||
failure = { errors.err(it.toString(), stmt.position) }
|
failure = { errors.err(it.toString(), stmt.position) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -872,18 +865,6 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(gosub: GoSub) {
|
|
||||||
val tgt = gosub.identifier.targetSubroutine(program)
|
|
||||||
if(tgt!=null && tgt.isAsmSubroutine) {
|
|
||||||
// no need to rescue X , this has been taken care of already
|
|
||||||
out(" jsr ${getJumpTarget(gosub)}")
|
|
||||||
} else {
|
|
||||||
saveXbeforeCall(gosub)
|
|
||||||
out(" jsr ${getJumpTarget(gosub)}")
|
|
||||||
restoreXafterCall(gosub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getJumpTarget(jump: Jump): Pair<String, Boolean> {
|
private fun getJumpTarget(jump: Jump): Pair<String, Boolean> {
|
||||||
val ident = jump.identifier
|
val ident = jump.identifier
|
||||||
val label = jump.generatedLabel
|
val label = jump.generatedLabel
|
||||||
@ -903,8 +884,6 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getJumpTarget(gosub: GoSub): String = asmSymbolName(gosub.identifier)
|
|
||||||
|
|
||||||
private fun translate(ret: Return, withRts: Boolean=true) {
|
private fun translate(ret: Return, withRts: Boolean=true) {
|
||||||
ret.value?.let { returnvalue ->
|
ret.value?.let { returnvalue ->
|
||||||
val sub = ret.definingSubroutine!!
|
val sub = ret.definingSubroutine!!
|
||||||
@ -930,8 +909,7 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(asm: InlineAssembly) {
|
private fun translate(asm: InlineAssembly) {
|
||||||
val assembly = asm.assembly.trimEnd().trimStart('\n')
|
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
|
||||||
assemblyLines.add(assembly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
||||||
@ -2874,7 +2852,7 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, target.datatype, scope, variableAsmName = asmVariableName(target.scopedName))
|
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, this, target.datatype, scope, variableAsmName = asmVariableName(target.scopedName))
|
||||||
if (dt in ByteDatatypes) {
|
if (dt in ByteDatatypes) {
|
||||||
out(" pla")
|
out(" pla")
|
||||||
assignRegister(RegisterOrPair.A, tgt)
|
assignRegister(RegisterOrPair.A, tgt)
|
||||||
|
@ -59,7 +59,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
|||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more assembly optimizations
|
mods = optimizeSamePointerIndexing(linesByFourteen, machine, program)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO more assembly peephole optimizations
|
||||||
|
|
||||||
return numberOfOptimizations
|
return numberOfOptimizations
|
||||||
}
|
}
|
||||||
@ -320,6 +327,48 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||||
|
|
||||||
|
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
|
||||||
|
// if Y isn't modified in between we can omit the second LDY:
|
||||||
|
// ldy #0
|
||||||
|
// lda (ptr),y
|
||||||
|
// ora #3 ; <-- instruction(s) that don't modify Y
|
||||||
|
// ldy #0 ; <-- can be removed
|
||||||
|
// sta (ptr),y
|
||||||
|
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (lines in linesByFourteen) {
|
||||||
|
val first = lines[0].value.trimStart()
|
||||||
|
val second = lines[1].value.trimStart()
|
||||||
|
val third = lines[2].value.trimStart()
|
||||||
|
val fourth = lines[3].value.trimStart()
|
||||||
|
val fifth = lines[4].value.trimStart()
|
||||||
|
val sixth = lines[5].value.trimStart()
|
||||||
|
|
||||||
|
if(first.startsWith("ldy") && second.startsWith("lda") && fourth.startsWith("ldy") && fifth.startsWith("sta")) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
val fourthvalue = fourth.substring(4)
|
||||||
|
val fifthvalue = fifth.substring(4)
|
||||||
|
if("y" !in third && firstvalue==fourthvalue && secondvalue==fifthvalue && secondvalue.endsWith(",y") && fifthvalue.endsWith(",y")) {
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(first.startsWith("ldy") && second.startsWith("lda") && fifth.startsWith("ldy") && sixth.startsWith("sta")) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
val fifthvalue = fifth.substring(4)
|
||||||
|
val sixthvalue = sixth.substring(4)
|
||||||
|
if("y" !in third && "y" !in fourth && firstvalue==fifthvalue && secondvalue==sixthvalue && secondvalue.endsWith(",y") && sixthvalue.endsWith(",y")) {
|
||||||
|
mods.add(Modification(lines[4].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
|
@ -15,8 +15,7 @@ import prog8.compiler.FSignature
|
|||||||
|
|
||||||
internal class BuiltinFunctionsAsmGen(private val program: Program,
|
internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen,
|
||||||
private val assignAsmGen: AssignmentAsmGen,
|
private val assignAsmGen: AssignmentAsmGen) {
|
||||||
private val allocations: VariableAllocator) {
|
|
||||||
|
|
||||||
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||||
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single())
|
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single())
|
||||||
@ -44,7 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
|
||||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"rol" -> funcRol(fcall)
|
"rol" -> funcRol(fcall)
|
||||||
"rol2" -> funcRol2(fcall)
|
"rol2" -> funcRol2(fcall)
|
||||||
@ -309,24 +307,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||||
val name = (fcall.args[0] as StringLiteral).value
|
val name = (fcall.args[0] as StringLiteral).value
|
||||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||||
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
|
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
|
||||||
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
|
|
||||||
|
|
||||||
val existing = allocations.getMemorySlab(name)
|
|
||||||
if(existing!=null && (existing.first!=size || existing.second!=align))
|
|
||||||
throw AssemblyError("memory slab '$name' already exists with a different size or alignment at ${fcall.position}")
|
|
||||||
|
|
||||||
val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position)
|
|
||||||
slabname.linkParents(fcall)
|
slabname.linkParents(fcall)
|
||||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
||||||
val target =
|
val target =
|
||||||
if(resultToStack)
|
if(resultToStack)
|
||||||
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null)
|
||||||
else
|
else
|
||||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
|
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, asmgen)
|
||||||
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
||||||
asmgen.translateNormalAssignment(assign)
|
asmgen.translateNormalAssignment(assign)
|
||||||
allocations.allocateMemorySlab(name, size, align)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||||
@ -335,7 +325,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||||
else {
|
else {
|
||||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,7 +638,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -669,7 +659,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
|
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,29 +682,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
|
||||||
when(func.name) {
|
|
||||||
"rnd" -> {
|
|
||||||
if(resultToStack)
|
|
||||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
|
||||||
else {
|
|
||||||
asmgen.out(" jsr math.randbyte")
|
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rndw" -> {
|
|
||||||
if(resultToStack)
|
|
||||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
|
||||||
else {
|
|
||||||
asmgen.out(" jsr math.randword")
|
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("wrong func")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -865,9 +833,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
val mr0 = fcall.args[0] as? DirectMemoryRead
|
val mr0 = fcall.args[0] as? DirectMemoryRead
|
||||||
val mr1 = fcall.args[1] as? DirectMemoryRead
|
val mr1 = fcall.args[1] as? DirectMemoryRead
|
||||||
if (mr0 != null)
|
if (mr0 != null)
|
||||||
needAsave = mr0.addressExpression !is NumericLiteral && mr0.addressExpression !is IdentifierReference
|
needAsave = mr0.addressExpression !is NumericLiteral
|
||||||
if (mr1 != null)
|
if (mr1 != null)
|
||||||
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral && mr1.addressExpression !is IdentifierReference)
|
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral)
|
||||||
}
|
}
|
||||||
when(reg) {
|
when(reg) {
|
||||||
RegisterOrPair.AX -> {
|
RegisterOrPair.AX -> {
|
||||||
@ -1084,7 +1052,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname)
|
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, variableAsmName = varname)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||||
asmgen.translateNormalAssignment(assign)
|
asmgen.translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
@ -1100,7 +1068,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, asmgen)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||||
asmgen.translateNormalAssignment(assign)
|
asmgen.translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
|
@ -519,12 +519,37 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||||
if(rightVal!=null && rightVal==2) {
|
if(rightVal!=null && rightVal==2) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(expr.left)
|
||||||
when(leftDt) {
|
when (leftDt) {
|
||||||
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
|
DataType.UBYTE -> {
|
||||||
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
|
asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||||
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
}
|
||||||
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
DataType.UWORD -> {
|
||||||
else -> throw AssemblyError("wrong dt")
|
asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
bpl +
|
||||||
|
inc P8ESTACK_LO+1,x
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
+ asl a
|
||||||
|
ror P8ESTACK_LO+1,x""")
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
bpl ++
|
||||||
|
inc P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
inc P8ESTACK_HI+1,x
|
||||||
|
+ lda P8ESTACK_HI+1,x
|
||||||
|
+ asl a
|
||||||
|
ror P8ESTACK_HI+1,x
|
||||||
|
ror P8ESTACK_LO+1,x""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,10 @@ import prog8.ast.expressions.AddressOf
|
|||||||
import prog8.ast.expressions.Expression
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.ast.expressions.NumericLiteral
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.ast.statements.InlineAssembly
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.ast.statements.SubroutineParameter
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||||
@ -35,17 +38,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun saveXbeforeCall(gosub: GoSub) {
|
|
||||||
val sub = gosub.identifier.targetSubroutine(program)
|
|
||||||
if(sub?.shouldSaveX()==true) {
|
|
||||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
|
||||||
if(regSaveOnStack)
|
|
||||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
|
||||||
else
|
|
||||||
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
if(sub.shouldSaveX()) {
|
if(sub.shouldSaveX()) {
|
||||||
@ -57,22 +49,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun restoreXafterCall(gosub: GoSub) {
|
|
||||||
val sub = gosub.identifier.targetSubroutine(program)
|
|
||||||
if(sub?.shouldSaveX()==true) {
|
|
||||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
|
||||||
if(regSaveOnStack)
|
|
||||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
|
||||||
else
|
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
||||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||||
|
|
||||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
|
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
|
||||||
// Output only the code to set up the parameters and perform the actual call
|
// Output only the code to set up the parameters and perform the actual call
|
||||||
// NOTE: does NOT output the code to deal with the result values!
|
// NOTE: does NOT output the code to deal with the result values!
|
||||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||||
@ -81,11 +62,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
||||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
val subAsmName = asmgen.asmSymbolName(call.target)
|
||||||
|
|
||||||
if(!isExpression && !sub.isAsmSubroutine) {
|
|
||||||
if(!optimizeIntArgsViaRegisters(sub))
|
|
||||||
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub.isAsmSubroutine) {
|
||||||
argumentsViaRegisters(sub, call)
|
argumentsViaRegisters(sub, call)
|
||||||
if (sub.inline && asmgen.options.optimize) {
|
if (sub.inline && asmgen.options.optimize) {
|
||||||
@ -234,10 +210,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
} else {
|
} else {
|
||||||
val target: AsmAssignTarget =
|
val target: AsmAssignTarget =
|
||||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, sub, register = register)
|
||||||
else {
|
else {
|
||||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||||
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
|
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
|
||||||
}
|
}
|
||||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||||
if(value is IdentifierReference) {
|
if(value is IdentifierReference) {
|
||||||
|
@ -44,6 +44,15 @@ internal class ProgramAndVarsGen(
|
|||||||
|
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
program.allBlocks.forEach { block2asm(it) }
|
program.allBlocks.forEach { block2asm(it) }
|
||||||
|
|
||||||
|
// the global list of all floating point constants for the whole program
|
||||||
|
asmgen.out("; global float constants")
|
||||||
|
for (flt in allocator.globalFloatConsts) {
|
||||||
|
val floatFill = compTarget.machine.getFloatAsmBytes(flt.key)
|
||||||
|
val floatvalue = flt.key
|
||||||
|
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||||
|
}
|
||||||
|
|
||||||
memorySlabs()
|
memorySlabs()
|
||||||
footer()
|
footer()
|
||||||
}
|
}
|
||||||
@ -161,23 +170,15 @@ internal class ProgramAndVarsGen(
|
|||||||
private fun memorySlabs() {
|
private fun memorySlabs() {
|
||||||
asmgen.out("; memory slabs")
|
asmgen.out("; memory slabs")
|
||||||
asmgen.out("prog8_slabs\t.block")
|
asmgen.out("prog8_slabs\t.block")
|
||||||
for((name, info) in allocator.memorySlabs) {
|
for(slab in symboltable.allMemorySlabs) {
|
||||||
if(info.second>1u)
|
if(slab.align>1u)
|
||||||
asmgen.out("\t.align ${info.second.toHex()}")
|
asmgen.out("\t.align ${slab.align.toHex()}")
|
||||||
asmgen.out("$name\t.fill ${info.first}")
|
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
||||||
}
|
}
|
||||||
asmgen.out("\t.bend")
|
asmgen.out("\t.bend")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun footer() {
|
private fun footer() {
|
||||||
// the global list of all floating point constants for the whole program
|
|
||||||
asmgen.out("; global float constants")
|
|
||||||
for (flt in allocator.globalFloatConsts) {
|
|
||||||
val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
|
|
||||||
val floatvalue = flt.key
|
|
||||||
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
|
||||||
}
|
|
||||||
|
|
||||||
// program end
|
// program end
|
||||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||||
}
|
}
|
||||||
@ -215,9 +216,9 @@ internal class ProgramAndVarsGen(
|
|||||||
if(!options.dontReinitGlobals) {
|
if(!options.dontReinitGlobals) {
|
||||||
// generate subroutine to initialize block-level (global) variables
|
// generate subroutine to initialize block-level (global) variables
|
||||||
if (initializers.isNotEmpty()) {
|
if (initializers.isNotEmpty()) {
|
||||||
asmgen.out("prog8_init_vars\t.proc\n")
|
asmgen.out("prog8_init_vars\t.block\n")
|
||||||
initializers.forEach { assign -> asmgen.translate(assign) }
|
initializers.forEach { assign -> asmgen.translate(assign) }
|
||||||
asmgen.out(" rts\n .pend")
|
asmgen.out(" rts\n .bend")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,17 +270,27 @@ internal class ProgramAndVarsGen(
|
|||||||
|
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
|
|
||||||
|
val asmStartScope: String
|
||||||
|
val asmEndScope: String
|
||||||
|
if(sub.definingBlock.options().contains("force_output")) {
|
||||||
|
asmStartScope = ".block"
|
||||||
|
asmEndScope = ".bend"
|
||||||
|
} else {
|
||||||
|
asmStartScope = ".proc"
|
||||||
|
asmEndScope = ".pend"
|
||||||
|
}
|
||||||
|
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub.isAsmSubroutine) {
|
||||||
if(sub.asmAddress!=null)
|
if(sub.asmAddress!=null)
|
||||||
return // already done at the memvars section
|
return // already done at the memvars section
|
||||||
|
|
||||||
// asmsub with most likely just an inline asm in it
|
// asmsub with most likely just an inline asm in it
|
||||||
asmgen.out("${sub.name}\t.proc")
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
sub.statements.forEach { asmgen.translate(it) }
|
sub.statements.forEach { asmgen.translate(it) }
|
||||||
asmgen.out(" .pend\n")
|
asmgen.out(" $asmEndScope\n")
|
||||||
} else {
|
} else {
|
||||||
// regular subroutine
|
// regular subroutine
|
||||||
asmgen.out("${sub.name}\t.proc")
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
|
|
||||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
||||||
require(scope.type==StNodeType.SUBROUTINE)
|
require(scope.type==StNodeType.SUBROUTINE)
|
||||||
@ -308,7 +319,7 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||||
if(sub.parameters.size==1) {
|
if(sub.parameters.size==1) {
|
||||||
val dt = sub.parameters[0].type
|
val dt = sub.parameters[0].type
|
||||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
||||||
if(dt in ByteDatatypes)
|
if(dt in ByteDatatypes)
|
||||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||||
else
|
else
|
||||||
@ -316,8 +327,8 @@ internal class ProgramAndVarsGen(
|
|||||||
} else {
|
} else {
|
||||||
require(sub.parameters.size==2)
|
require(sub.parameters.size==2)
|
||||||
// 2 simple byte args, first in A, second in Y
|
// 2 simple byte args, first in A, second in Y
|
||||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
||||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
||||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||||
}
|
}
|
||||||
@ -356,7 +367,7 @@ internal class ProgramAndVarsGen(
|
|||||||
.map { it.value as StStaticVariable }
|
.map { it.value as StStaticVariable }
|
||||||
nonZpVariables2asm(variables)
|
nonZpVariables2asm(variables)
|
||||||
|
|
||||||
asmgen.out(" .pend\n")
|
asmgen.out(" $asmEndScope\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,9 +449,9 @@ internal class ProgramAndVarsGen(
|
|||||||
val result = mutableListOf<ZpStringWithInitial>()
|
val result = mutableListOf<ZpStringWithInitial>()
|
||||||
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
||||||
for (variable in vars) {
|
for (variable in vars) {
|
||||||
val svar = symboltable.lookup(variable.key) as StStaticVariable // TODO faster in flat lookup table
|
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
||||||
if(svar.initialStringValue!=null)
|
if(svar.onetimeInitializationStringValue!=null)
|
||||||
result.add(ZpStringWithInitial(variable.key, variable.value, svar.initialStringValue!!))
|
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -449,9 +460,9 @@ internal class ProgramAndVarsGen(
|
|||||||
val result = mutableListOf<ZpArrayWithInitial>()
|
val result = mutableListOf<ZpArrayWithInitial>()
|
||||||
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
||||||
for (variable in vars) {
|
for (variable in vars) {
|
||||||
val svar = symboltable.lookup(variable.key) as StStaticVariable // TODO faster in flat lookup table
|
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
||||||
if(svar.initialArrayValue!=null)
|
if(svar.onetimeInitializationArrayValue!=null)
|
||||||
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.initialArrayValue!!))
|
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -470,7 +481,7 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out("; non-zeropage variables")
|
asmgen.out("; non-zeropage variables")
|
||||||
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
|
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
|
||||||
stringvars.forEach {
|
stringvars.forEach {
|
||||||
outputStringvar(it.name, it.initialStringValue!!.second, it.initialStringValue!!.first)
|
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
|
||||||
}
|
}
|
||||||
othervars.sortedBy { it.type }.forEach {
|
othervars.sortedBy { it.type }.forEach {
|
||||||
staticVariable2asm(it)
|
staticVariable2asm(it)
|
||||||
@ -480,11 +491,11 @@ internal class ProgramAndVarsGen(
|
|||||||
private fun staticVariable2asm(variable: StStaticVariable) {
|
private fun staticVariable2asm(variable: StStaticVariable) {
|
||||||
val name = variable.name
|
val name = variable.name
|
||||||
val initialValue: Number =
|
val initialValue: Number =
|
||||||
if(variable.initialNumericValue!=null) {
|
if(variable.onetimeInitializationNumericValue!=null) {
|
||||||
if(variable.dt== DataType.FLOAT)
|
if(variable.dt== DataType.FLOAT)
|
||||||
variable.initialNumericValue!!
|
variable.onetimeInitializationNumericValue!!
|
||||||
else
|
else
|
||||||
variable.initialNumericValue!!.toInt()
|
variable.onetimeInitializationNumericValue!!.toInt()
|
||||||
} else 0
|
} else 0
|
||||||
|
|
||||||
when (variable.dt) {
|
when (variable.dt) {
|
||||||
@ -496,14 +507,14 @@ internal class ProgramAndVarsGen(
|
|||||||
if(initialValue==0) {
|
if(initialValue==0) {
|
||||||
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
||||||
} else {
|
} else {
|
||||||
val floatFill = compTarget.machine.getFloat(initialValue).makeFloatFillAsm()
|
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
||||||
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
|
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
throw AssemblyError("all string vars should have been interned into prog")
|
throw AssemblyError("all string vars should have been interned into prog")
|
||||||
}
|
}
|
||||||
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.initialArrayValue, variable.length)
|
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
||||||
else -> {
|
else -> {
|
||||||
throw AssemblyError("weird dt")
|
throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
@ -555,7 +566,7 @@ internal class ProgramAndVarsGen(
|
|||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||||
val floatFills = array.map {
|
val floatFills = array.map {
|
||||||
compTarget.machine.getFloat(it.number!!).makeFloatFillAsm()
|
compTarget.machine.getFloatAsmBytes(it.number!!)
|
||||||
}
|
}
|
||||||
asmgen.out(varname)
|
asmgen.out(varname)
|
||||||
for (f in array.zip(floatFills))
|
for (f in array.zip(floatFills))
|
||||||
|
@ -15,8 +15,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
private val zeropage = options.compTarget.machine.zeropage
|
private val zeropage = options.compTarget.machine.zeropage
|
||||||
private val memorySlabsInternal = mutableMapOf<String, Pair<UInt, UInt>>()
|
|
||||||
internal val memorySlabs: Map<String, Pair<UInt, UInt>> = memorySlabsInternal
|
|
||||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||||
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
|
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
|
||||||
|
|
||||||
@ -24,12 +22,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
allocateZeropageVariables()
|
allocateZeropageVariables()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getMemorySlab(name: String) = memorySlabsInternal[name]
|
|
||||||
|
|
||||||
internal fun allocateMemorySlab(name: String, size: UInt, align: UInt) {
|
|
||||||
memorySlabsInternal[name] = Pair(size, align)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
||||||
|
|
||||||
internal fun getFloatAsmConst(number: Double): String {
|
internal fun getFloatAsmConst(number: Double): String {
|
||||||
|
@ -29,7 +29,6 @@ internal enum class SourceStorageKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||||
private val program: Program,
|
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen,
|
||||||
val datatype: DataType,
|
val datatype: DataType,
|
||||||
val scope: Subroutine?,
|
val scope: Subroutine?,
|
||||||
@ -70,28 +69,28 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
if(reg.statusflag!=null)
|
if(reg.statusflag!=null)
|
||||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||||
else
|
else
|
||||||
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||||
}
|
}
|
||||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||||
else -> throw AssemblyError("weird target")
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget =
|
||||||
when(registers) {
|
when(registers) {
|
||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
||||||
RegisterOrPair.AX,
|
RegisterOrPair.AX,
|
||||||
RegisterOrPair.AY,
|
RegisterOrPair.AY,
|
||||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||||
RegisterOrPair.FAC1,
|
RegisterOrPair.FAC1,
|
||||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, register = registers)
|
||||||
RegisterOrPair.R0,
|
RegisterOrPair.R0,
|
||||||
RegisterOrPair.R1,
|
RegisterOrPair.R1,
|
||||||
RegisterOrPair.R2,
|
RegisterOrPair.R2,
|
||||||
@ -107,7 +106,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
RegisterOrPair.R12,
|
RegisterOrPair.R12,
|
||||||
RegisterOrPair.R13,
|
RegisterOrPair.R13,
|
||||||
RegisterOrPair.R14,
|
RegisterOrPair.R14,
|
||||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
|
|
||||||
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
||||||
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
||||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
|
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, origtarget.datatype, origtarget.scope,
|
||||||
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
||||||
} else origtarget
|
} else origtarget
|
||||||
}
|
}
|
||||||
@ -220,9 +220,25 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
|
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
|
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
|
||||||
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
|
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
|
||||||
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
|
RegisterOrPair.AX -> assignVirtualRegister(assign.target, RegisterOrPair.AX)
|
||||||
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
RegisterOrPair.AY -> assignVirtualRegister(assign.target, RegisterOrPair.AY)
|
||||||
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
|
RegisterOrPair.XY -> assignVirtualRegister(assign.target, RegisterOrPair.XY)
|
||||||
|
RegisterOrPair.R0 -> assignVirtualRegister(assign.target, RegisterOrPair.R0)
|
||||||
|
RegisterOrPair.R1 -> assignVirtualRegister(assign.target, RegisterOrPair.R1)
|
||||||
|
RegisterOrPair.R2 -> assignVirtualRegister(assign.target, RegisterOrPair.R2)
|
||||||
|
RegisterOrPair.R3 -> assignVirtualRegister(assign.target, RegisterOrPair.R3)
|
||||||
|
RegisterOrPair.R4 -> assignVirtualRegister(assign.target, RegisterOrPair.R4)
|
||||||
|
RegisterOrPair.R5 -> assignVirtualRegister(assign.target, RegisterOrPair.R5)
|
||||||
|
RegisterOrPair.R6 -> assignVirtualRegister(assign.target, RegisterOrPair.R6)
|
||||||
|
RegisterOrPair.R7 -> assignVirtualRegister(assign.target, RegisterOrPair.R7)
|
||||||
|
RegisterOrPair.R8 -> assignVirtualRegister(assign.target, RegisterOrPair.R8)
|
||||||
|
RegisterOrPair.R9 -> assignVirtualRegister(assign.target, RegisterOrPair.R9)
|
||||||
|
RegisterOrPair.R10 -> assignVirtualRegister(assign.target, RegisterOrPair.R10)
|
||||||
|
RegisterOrPair.R11 -> assignVirtualRegister(assign.target, RegisterOrPair.R11)
|
||||||
|
RegisterOrPair.R12 -> assignVirtualRegister(assign.target, RegisterOrPair.R12)
|
||||||
|
RegisterOrPair.R13 -> assignVirtualRegister(assign.target, RegisterOrPair.R13)
|
||||||
|
RegisterOrPair.R14 -> assignVirtualRegister(assign.target, RegisterOrPair.R14)
|
||||||
|
RegisterOrPair.R15 -> assignVirtualRegister(assign.target, RegisterOrPair.R15)
|
||||||
else -> {
|
else -> {
|
||||||
val sflag = returnValue.second.statusflag
|
val sflag = returnValue.second.statusflag
|
||||||
if(sflag!=null)
|
if(sflag!=null)
|
||||||
@ -271,18 +287,24 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
// first assign the value to the target then apply the operator in place on the target.
|
if(assign.target.array==null) {
|
||||||
translateNormalAssignment(AsmAssignment(
|
// First assign the value to the target then apply the operator in place on the target.
|
||||||
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
// This saves a temporary variable
|
||||||
assign.target,
|
translateNormalAssignment(
|
||||||
false, program.memsizer, assign.position
|
AsmAssignment(
|
||||||
))
|
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
||||||
val target = virtualRegsToVariables(assign.target)
|
assign.target,
|
||||||
when(value.operator) {
|
false, program.memsizer, assign.position
|
||||||
"+" -> {}
|
)
|
||||||
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
)
|
||||||
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
when (value.operator) {
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
"+" -> {}
|
||||||
|
"-" -> augmentableAsmGen.inplaceNegate(assign)
|
||||||
|
"~" -> augmentableAsmGen.inplaceInvert(assign)
|
||||||
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assignPrefixedExpressionToArrayElt(assign)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ContainmentCheck -> {
|
is ContainmentCheck -> {
|
||||||
@ -301,6 +323,39 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
|
||||||
|
require(assign.source.expression is PrefixExpression)
|
||||||
|
if(assign.source.datatype==DataType.FLOAT) {
|
||||||
|
// floatarray[x] = -value ... just use FAC1 to calculate the expression into and then store that back into the array.
|
||||||
|
assignExpressionToRegister(assign.source.expression, RegisterOrPair.FAC1, true)
|
||||||
|
assignFAC1float(assign.target)
|
||||||
|
} else {
|
||||||
|
// array[x] = -value ... use a tempvar then store that back into the array.
|
||||||
|
val tempvar = asmgen.getTempVarName(assign.target.datatype).joinToString(".")
|
||||||
|
val assignToTempvar = AsmAssignment(assign.source,
|
||||||
|
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget),
|
||||||
|
false, program.memsizer, assign.position)
|
||||||
|
asmgen.translateNormalAssignment(assignToTempvar)
|
||||||
|
when(assign.target.datatype) {
|
||||||
|
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
|
||||||
|
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
|
||||||
|
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
||||||
|
when(target.datatype) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(" lda cx16.${register.toString().lowercase()}L")
|
||||||
|
assignRegisterByte(target, CpuRegister.A)
|
||||||
|
}
|
||||||
|
in WordDatatypes -> assignRegisterpairWord(target, register)
|
||||||
|
else -> throw AssemblyError("expected byte or word")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun attemptAssignOptimizedBinexpr(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
private fun attemptAssignOptimizedBinexpr(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
||||||
if(expr.operator in ComparisonOperators) {
|
if(expr.operator in ComparisonOperators) {
|
||||||
if(expr.right.constValue(program)?.number == 0.0) {
|
if(expr.right.constValue(program)?.number == 0.0) {
|
||||||
@ -330,140 +385,288 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
if(!expr.inferType(program).isInteger)
|
if(!expr.inferType(program).isInteger)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if(expr.operator!="+" && expr.operator!="-")
|
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
|
||||||
return false
|
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
|
||||||
|
expr.left.isSimple && expr.right.isSimple) {
|
||||||
val dt = expr.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||||
val left = expr.left
|
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
||||||
val right = expr.right
|
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||||
if(dt in ByteDatatypes) {
|
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
||||||
when (right) {
|
else {
|
||||||
is IdentifierReference -> {
|
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
val symname = asmgen.asmVariableName(right)
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
||||||
if(expr.operator=="+")
|
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
asmgen.out(" clc | adc $symname")
|
when (expr.operator) {
|
||||||
else
|
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||||
asmgen.out(" sec | sbc $symname")
|
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||||
|
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||||
|
else -> throw AssemblyError("invalid operator")
|
||||||
|
}
|
||||||
assignRegisterByte(assign.target, CpuRegister.A)
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
is NumericLiteral -> {
|
return true
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
|
||||||
if(expr.operator=="+")
|
|
||||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
|
||||||
else
|
|
||||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
|
||||||
assignRegisterByte(assign.target, CpuRegister.A)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
else -> return false
|
|
||||||
}
|
}
|
||||||
} else if(dt in WordDatatypes) {
|
if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords &&
|
||||||
when (right) {
|
expr.left.isSimple && expr.right.isSimple) {
|
||||||
is AddressOf -> {
|
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
||||||
val symbol = asmgen.asmVariableName(right.identifier)
|
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||||
if(expr.operator=="+")
|
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
||||||
asmgen.out("""
|
else {
|
||||||
clc
|
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
||||||
adc #<$symbol
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
pha
|
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||||
tya
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
||||||
adc #>$symbol
|
when (expr.operator) {
|
||||||
tay
|
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
||||||
pla""")
|
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
|
||||||
else
|
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
|
||||||
asmgen.out("""
|
else -> throw AssemblyError("invalid operator")
|
||||||
sec
|
|
||||||
sbc #<$symbol
|
|
||||||
pha
|
|
||||||
tya
|
|
||||||
sbc #>$symbol
|
|
||||||
tay
|
|
||||||
pla""")
|
|
||||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val symname = asmgen.asmVariableName(right)
|
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
|
||||||
if(expr.operator=="+")
|
|
||||||
asmgen.out("""
|
|
||||||
clc
|
|
||||||
adc $symname
|
|
||||||
pha
|
|
||||||
tya
|
|
||||||
adc $symname+1
|
|
||||||
tay
|
|
||||||
pla""")
|
|
||||||
else
|
|
||||||
asmgen.out("""
|
|
||||||
sec
|
|
||||||
sbc $symname
|
|
||||||
pha
|
|
||||||
tya
|
|
||||||
sbc $symname+1
|
|
||||||
tay
|
|
||||||
pla""")
|
|
||||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
is NumericLiteral -> {
|
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
|
||||||
if(expr.operator=="+") {
|
|
||||||
asmgen.out("""
|
|
||||||
clc
|
|
||||||
adc #<${right.number.toHex()}
|
|
||||||
pha
|
|
||||||
tya
|
|
||||||
adc #>${right.number.toHex()}
|
|
||||||
tay
|
|
||||||
pla""")
|
|
||||||
} else if(expr.operator=="-") {
|
|
||||||
asmgen.out("""
|
|
||||||
sec
|
|
||||||
sbc #<${right.number.toHex()}
|
|
||||||
pha
|
|
||||||
tya
|
|
||||||
sbc #>${right.number.toHex()}
|
|
||||||
tay
|
|
||||||
pla""")
|
|
||||||
}
|
}
|
||||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
is TypecastExpression -> {
|
return true
|
||||||
val castedValue = right.expression
|
}
|
||||||
if(right.type in WordDatatypes && castedValue.inferType(program).isBytes) {
|
return false
|
||||||
if(castedValue is IdentifierReference) {
|
}
|
||||||
val castedSymname = asmgen.asmVariableName(castedValue)
|
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
if(expr.operator == "==" || expr.operator == "!=") {
|
||||||
if(expr.operator=="+")
|
// expression datatype is BOOL (ubyte) but operands can be anything
|
||||||
asmgen.out("""
|
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
|
||||||
clc
|
expr.left.isSimple && expr.right.isSimple) {
|
||||||
adc $castedSymname
|
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
bcc +
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
iny
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
||||||
|
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
if(expr.operator=="==") {
|
||||||
|
asmgen.out("""
|
||||||
|
cmp P8ZP_SCRATCH_B1
|
||||||
|
bne +
|
||||||
|
lda #1
|
||||||
|
bne ++
|
||||||
|
+ lda #0
|
||||||
+""")
|
+""")
|
||||||
else
|
} else {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sec
|
cmp P8ZP_SCRATCH_B1
|
||||||
sbc $castedSymname
|
beq +
|
||||||
bcs +
|
lda #1
|
||||||
dey
|
bne ++
|
||||||
|
+ lda #0
|
||||||
+""")
|
+""")
|
||||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
}
|
||||||
return true
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
|
return true
|
||||||
|
} else if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords &&
|
||||||
|
expr.left.isSimple && expr.right.isSimple) {
|
||||||
|
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
||||||
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
|
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||||
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
||||||
|
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
if(expr.operator=="==") {
|
||||||
|
asmgen.out("""
|
||||||
|
cmp P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
cpy P8ZP_SCRATCH_W1+1
|
||||||
|
bne +
|
||||||
|
lda #1
|
||||||
|
bne ++
|
||||||
|
+ lda #0
|
||||||
|
+""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
cmp P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
cpy P8ZP_SCRATCH_W1+1
|
||||||
|
bne +
|
||||||
|
lda #0
|
||||||
|
bne ++
|
||||||
|
+ lda #1
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
else if(expr.operator=="+" || expr.operator=="-") {
|
||||||
|
val dt = expr.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||||
|
val left = expr.left
|
||||||
|
val right = expr.right
|
||||||
|
if(dt in ByteDatatypes) {
|
||||||
|
when (right) {
|
||||||
|
is IdentifierReference -> {
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||||
|
val symname = asmgen.asmVariableName(right)
|
||||||
|
if(expr.operator=="+")
|
||||||
|
asmgen.out(" clc | adc $symname")
|
||||||
|
else
|
||||||
|
asmgen.out(" sec | sbc $symname")
|
||||||
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
is NumericLiteral -> {
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||||
|
if(expr.operator=="+")
|
||||||
|
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||||
|
else
|
||||||
|
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||||
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
} else if(dt in WordDatatypes) {
|
||||||
|
when (right) {
|
||||||
|
is AddressOf -> {
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||||
|
val symbol = asmgen.asmVariableName(right.identifier)
|
||||||
|
if(expr.operator=="+")
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc #<$symbol
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
adc #>$symbol
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc #<$symbol
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
sbc #>$symbol
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val symname = asmgen.asmVariableName(right)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||||
|
if(expr.operator=="+")
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc $symname
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
adc $symname+1
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc $symname
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
sbc $symname+1
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
is NumericLiteral -> {
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||||
|
if(expr.operator=="+") {
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc #<${right.number.toHex()}
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
adc #>${right.number.toHex()}
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
} else if(expr.operator=="-") {
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc #<${right.number.toHex()}
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
sbc #>${right.number.toHex()}
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
}
|
||||||
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
is TypecastExpression -> {
|
||||||
|
val castedValue = right.expression
|
||||||
|
if(right.type in WordDatatypes && castedValue.inferType(program).isBytes) {
|
||||||
|
if(castedValue is IdentifierReference) {
|
||||||
|
val castedSymname = asmgen.asmVariableName(castedValue)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||||
|
if(expr.operator=="+")
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc $castedSymname
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc $castedSymname
|
||||||
|
bcs +
|
||||||
|
dey
|
||||||
|
+""")
|
||||||
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> return false
|
||||||
}
|
}
|
||||||
else -> return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun assignLogicalWithSimpleRightOperandByte(target: AsmAssignTarget, left: Expression, operator: String, right: Expression) {
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A, false)
|
||||||
|
val operand = when(right) {
|
||||||
|
is NumericLiteral -> "#${right.number.toHex()}"
|
||||||
|
is IdentifierReference -> asmgen.asmSymbolName(right)
|
||||||
|
else -> throw AssemblyError("wrong right operand type")
|
||||||
|
}
|
||||||
|
when (operator) {
|
||||||
|
"&", "and" -> asmgen.out(" and $operand")
|
||||||
|
"|", "or" -> asmgen.out(" ora $operand")
|
||||||
|
"^", "xor" -> asmgen.out(" eor $operand")
|
||||||
|
else -> throw AssemblyError("invalid operator")
|
||||||
|
}
|
||||||
|
assignRegisterByte(target, CpuRegister.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignLogicalWithSimpleRightOperandWord(target: AsmAssignTarget, left: Expression, operator: String, right: Expression) {
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY, false)
|
||||||
|
when(right) {
|
||||||
|
is NumericLiteral -> {
|
||||||
|
val number = right.number.toHex()
|
||||||
|
when (operator) {
|
||||||
|
"&", "and" -> asmgen.out(" and #<$number | pha | tya | and #>$number | tay | pla")
|
||||||
|
"|", "or" -> asmgen.out(" ora #<$number | pha | tya | ora #>$number | tay | pla")
|
||||||
|
"^", "xor" -> asmgen.out(" eor #<$number | pha | tya | eor #>$number | tay | pla")
|
||||||
|
else -> throw AssemblyError("invalid operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val name = asmgen.asmSymbolName(right)
|
||||||
|
when (operator) {
|
||||||
|
"&", "and" -> asmgen.out(" and $name | pha | tya | and $name+1 | tay | pla")
|
||||||
|
"|", "or" -> asmgen.out(" ora $name | pha | tya | ora $name+1 | tay | pla")
|
||||||
|
"^", "xor" -> asmgen.out(" eor $name | pha | tya | eor $name+1 | tay | pla")
|
||||||
|
else -> throw AssemblyError("invalid operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("wrong right operand type")
|
||||||
|
}
|
||||||
|
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||||
|
}
|
||||||
|
|
||||||
private fun attemptAssignToByteCompareZero(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
private fun attemptAssignToByteCompareZero(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
||||||
when (expr.operator) {
|
when (expr.operator) {
|
||||||
"==" -> {
|
"==" -> {
|
||||||
@ -536,8 +739,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fallbackToStackEval(assign: AsmAssignment) {
|
private fun fallbackToStackEval(assign: AsmAssignment) {
|
||||||
// TODO DON'T STACK-EVAL... perhaps by using a temp var? so that it becomes augmentable assignment expression?
|
|
||||||
// or don't try to solve it here in this one case and rather rewrite the whole stack based value evaluation.
|
|
||||||
// this routine is called for assigning a binaryexpression value:
|
// this routine is called for assigning a binaryexpression value:
|
||||||
// - if it's a boolean comparison expression and the workaround isn't possible (no origTarget ast node)
|
// - if it's a boolean comparison expression and the workaround isn't possible (no origTarget ast node)
|
||||||
// - for all other binary expressions.
|
// - for all other binary expressions.
|
||||||
@ -561,7 +762,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||||
asmgen.out(" ldy #${stringVal.value.length}")
|
asmgen.out(" ldy #${stringVal.value.length}")
|
||||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||||
@ -580,14 +781,14 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||||
}
|
}
|
||||||
@ -604,7 +805,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
// use subroutine
|
// use subroutine
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||||
val stringVal = variable.value as StringLiteral
|
val stringVal = variable.value as StringLiteral
|
||||||
asmgen.out(" ldy #${stringVal.value.length}")
|
asmgen.out(" ldy #${stringVal.value.length}")
|
||||||
@ -618,7 +819,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
val arrayVal = variable.value as ArrayLiteral
|
val arrayVal = variable.value as ArrayLiteral
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||||
@ -627,7 +828,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
val arrayVal = variable.value as ArrayLiteral
|
val arrayVal = variable.value as ArrayLiteral
|
||||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||||
return
|
return
|
||||||
@ -679,7 +880,8 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
fun assignViaExprEval(addressExpression: Expression) {
|
fun assignViaExprEval(addressExpression: Expression) {
|
||||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||||
assignRegisterByte(target, CpuRegister.A)
|
asmgen.out(" ldy #0")
|
||||||
|
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (value.addressExpression) {
|
when (value.addressExpression) {
|
||||||
@ -801,6 +1003,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
} else if(valueDt in ByteDatatypes && targetDt in ByteDatatypes) {
|
} else if(valueDt in ByteDatatypes && targetDt in ByteDatatypes) {
|
||||||
// byte to byte, just assign
|
// byte to byte, just assign
|
||||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||||
|
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
|
||||||
|
// byte to word, just assign
|
||||||
|
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
|
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
|
||||||
@ -1796,18 +2001,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||||
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
|
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
|
||||||
// these will be correctly typecasted from a byte to a word value
|
// these will be correctly typecasted from a byte to a word value here
|
||||||
if(target.register !in Cx16VirtualRegisters &&
|
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
|
||||||
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
require(target.datatype in ByteDatatypes)
|
||||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
|
||||||
val parts = target.asmVarname.split('.')
|
|
||||||
if (parts.size != 2 || parts[0] != "cx16")
|
|
||||||
require(target.datatype in ByteDatatypes)
|
|
||||||
} else {
|
|
||||||
require(target.datatype in ByteDatatypes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when(target.kind) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -2579,7 +2776,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
|
|
||||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
|
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
|
||||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, asmgen)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
@ -2589,14 +2786,14 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
throw AssemblyError("can't directly assign a FLOAT expression to an integer variable $expr")
|
throw AssemblyError("can't directly assign a FLOAT expression to an integer variable $expr")
|
||||||
} else {
|
} else {
|
||||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
|
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, scope, variableAsmName = asmVarName)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
|
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
|
||||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, asmgen)
|
||||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
|
||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
|
@ -21,13 +21,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
when (val value = assign.source.expression!!) {
|
when (val value = assign.source.expression!!) {
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
// A = -A , A = +A, A = ~A, A = not A
|
// A = -A , A = +A, A = ~A, A = not A
|
||||||
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
|
||||||
val itype = value.inferType(program)
|
|
||||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
when (value.operator) {
|
when (value.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> inplaceNegate(target, type)
|
"-" -> inplaceNegate(assign)
|
||||||
"~" -> inplaceInvert(target, type)
|
"~" -> inplaceInvert(assign)
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,7 +233,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
|
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
|
||||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
|
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||||
when {
|
when {
|
||||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||||
@ -305,7 +302,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
AsmAssignTarget.fromRegisters(
|
AsmAssignTarget.fromRegisters(
|
||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
target.datatype == DataType.BYTE, null,
|
target.datatype == DataType.BYTE, null,
|
||||||
program,
|
|
||||||
asmgen
|
asmgen
|
||||||
)
|
)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
@ -317,7 +313,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
AsmAssignTarget.fromRegisters(
|
AsmAssignTarget.fromRegisters(
|
||||||
RegisterOrPair.AY,
|
RegisterOrPair.AY,
|
||||||
target.datatype == DataType.WORD, null,
|
target.datatype == DataType.WORD, null,
|
||||||
program,
|
|
||||||
asmgen
|
asmgen
|
||||||
)
|
)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
@ -329,7 +324,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
AsmAssignTarget.fromRegisters(
|
AsmAssignTarget.fromRegisters(
|
||||||
RegisterOrPair.FAC1,
|
RegisterOrPair.FAC1,
|
||||||
true, null,
|
true, null,
|
||||||
program,
|
|
||||||
asmgen
|
asmgen
|
||||||
)
|
)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
@ -1799,8 +1793,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceInvert(assign: AsmAssignment) {
|
||||||
when (dt) {
|
val target = assign.target
|
||||||
|
when (assign.target.datatype) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -1843,7 +1838,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
||||||
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
@ -1867,15 +1863,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
||||||
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invert of invalid type")
|
else -> throw AssemblyError("invert of invalid type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceNegate(assign: AsmAssignment) {
|
||||||
when (dt) {
|
val target = assign.target
|
||||||
|
when (assign.target.datatype) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -1899,9 +1897,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
|
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
||||||
else -> throw AssemblyError("no asm gen for in-place negate byte")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
@ -1958,12 +1957,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||||
else -> throw AssemblyError("no asm gen for in-place negate word")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
|
||||||
|
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
|
||||||
|
else -> throw AssemblyError("invalid float register")
|
||||||
|
}
|
||||||
|
}
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
// simply flip the sign bit in the float
|
// simply flip the sign bit in the float
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -1973,10 +1981,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
||||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target for in-place float negation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("negate of invalid type $dt")
|
else -> throw AssemblyError("negate of invalid type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +24,9 @@ compileTestKotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':codeAst')
|
|
||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
|
implementation project(':intermediate')
|
||||||
|
implementation project(':codeGenIntermediate')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
<orderEntry type="module" module-name="codeAst" />
|
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||||
|
<orderEntry type="module" module-name="intermediate" />
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -1,39 +0,0 @@
|
|||||||
package prog8.codegen.experimental
|
|
||||||
|
|
||||||
import prog8.code.SymbolTable
|
|
||||||
import prog8.code.ast.PtProgram
|
|
||||||
import prog8.code.core.CompilationOptions
|
|
||||||
import prog8.code.core.IAssemblyGenerator
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
|
||||||
import prog8.code.core.IErrorReporter
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
NOTE: The goal is to keep the dependencies as lean as possible! For now, we depend only on:
|
|
||||||
- codeAst (the 'lean' new AST and the SymbolTable)
|
|
||||||
- codeCore (various base enums and interfaces)
|
|
||||||
|
|
||||||
This *should* be enough to build a complete code generator with. But we'll see :)
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
class AsmGen(internal val program: PtProgram,
|
|
||||||
internal val symbolTable: SymbolTable,
|
|
||||||
internal val options: CompilationOptions,
|
|
||||||
internal val errors: IErrorReporter
|
|
||||||
): IAssemblyGenerator {
|
|
||||||
|
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
|
||||||
|
|
||||||
println("\n** experimental code generator **\n")
|
|
||||||
|
|
||||||
println("Writing AST into XML form...")
|
|
||||||
val xmlConv = AstToXmlConverter(program, symbolTable, options)
|
|
||||||
xmlConv.writeXml()
|
|
||||||
|
|
||||||
println("..todo: create assembly program into ${options.outputDir.toAbsolutePath()}..")
|
|
||||||
|
|
||||||
return AssemblyProgram("dummy")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package prog8.codegen.experimental
|
|
||||||
|
|
||||||
import prog8.code.core.CompilationOptions
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
|
||||||
|
|
||||||
|
|
||||||
internal class AssemblyProgram(override val name: String) : IAssemblyProgram
|
|
||||||
{
|
|
||||||
override fun assemble(options: CompilationOptions): Boolean {
|
|
||||||
println("..todo: assemble code into binary..")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,668 +0,0 @@
|
|||||||
package prog8.codegen.experimental
|
|
||||||
|
|
||||||
import prog8.code.*
|
|
||||||
import prog8.code.ast.*
|
|
||||||
import prog8.code.core.*
|
|
||||||
import javax.xml.stream.XMLOutputFactory
|
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.io.path.absolutePathString
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
NOTE: The goal is to keep the dependencies as lean as possible! For now, we depend only on:
|
|
||||||
- codeAst (the 'lean' new AST and the SymbolTable)
|
|
||||||
- codeCore (various base enums and interfaces)
|
|
||||||
|
|
||||||
This *should* be enough to build a complete code generator with. But we'll see :)
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
class AstToXmlConverter(internal val program: PtProgram,
|
|
||||||
internal val symbolTable: SymbolTable,
|
|
||||||
internal val options: CompilationOptions
|
|
||||||
) {
|
|
||||||
|
|
||||||
private lateinit var xml: IndentingXmlWriter
|
|
||||||
|
|
||||||
fun writeXml() {
|
|
||||||
val writer = (options.outputDir / Path(program.name+"-ast.xml")).toFile().printWriter()
|
|
||||||
xml = IndentingXmlWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(writer))
|
|
||||||
xml.doc()
|
|
||||||
xml.elt("program")
|
|
||||||
xml.attr("name", program.name)
|
|
||||||
xml.startChildren()
|
|
||||||
writeOptions(options)
|
|
||||||
program.children.forEach { writeNode(it) }
|
|
||||||
writeSymboltable(symbolTable)
|
|
||||||
xml.endElt()
|
|
||||||
xml.endDoc()
|
|
||||||
xml.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeSymboltable(st: SymbolTable) {
|
|
||||||
xml.elt("symboltable")
|
|
||||||
xml.startChildren()
|
|
||||||
st.flat.forEach{ (name, entry) ->
|
|
||||||
xml.elt("entry")
|
|
||||||
xml.attr("name", name.joinToString("."))
|
|
||||||
xml.attr("type", entry.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
writeStNode(entry)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeStNode(node: StNode) {
|
|
||||||
when(node.type) {
|
|
||||||
StNodeType.GLOBAL,
|
|
||||||
StNodeType.LABEL,
|
|
||||||
StNodeType.BLOCK,
|
|
||||||
StNodeType.BUILTINFUNC,
|
|
||||||
StNodeType.SUBROUTINE -> {/* no additional info*/}
|
|
||||||
StNodeType.ROMSUB -> {
|
|
||||||
node as StRomSub
|
|
||||||
xml.elt("romsub")
|
|
||||||
xml.attr("address", node.address.toString())
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
StNodeType.STATICVAR -> {
|
|
||||||
node as StStaticVariable
|
|
||||||
xml.elt("var")
|
|
||||||
xml.attr("type", node.dt.name)
|
|
||||||
xml.attr("zpwish", node.zpwish.name)
|
|
||||||
if(node.length!=null)
|
|
||||||
xml.attr("length", node.length.toString())
|
|
||||||
if(node.initialNumericValue!=null || node.initialArrayValue!=null || node.initialStringValue!=null) {
|
|
||||||
xml.startChildren()
|
|
||||||
if(node.initialNumericValue!=null) {
|
|
||||||
writeNumber(node.dt, node.initialNumericValue!!)
|
|
||||||
}
|
|
||||||
if(node.initialStringValue!=null) {
|
|
||||||
xml.writeTextNode(
|
|
||||||
"string",
|
|
||||||
listOf(Pair("encoding", node.initialStringValue!!.second.name)),
|
|
||||||
node.initialStringValue!!.first,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(node.initialArrayValue!=null) {
|
|
||||||
xml.elt("array")
|
|
||||||
xml.startChildren()
|
|
||||||
val eltDt = ArrayToElementTypes.getValue(node.dt)
|
|
||||||
node.initialArrayValue!!.forEach {
|
|
||||||
if(it.number!=null) {
|
|
||||||
writeNumber(eltDt, it.number!!)
|
|
||||||
}
|
|
||||||
if(it.addressOf!=null) {
|
|
||||||
xml.elt("addressof")
|
|
||||||
xml.attr("symbol", it.addressOf!!.joinToString("."))
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
StNodeType.MEMVAR -> {
|
|
||||||
node as StMemVar
|
|
||||||
xml.writeTextNode("memvar",
|
|
||||||
listOf(Pair("type", node.dt.name)),
|
|
||||||
node.address.toString(),
|
|
||||||
false)
|
|
||||||
}
|
|
||||||
StNodeType.CONSTANT -> {
|
|
||||||
node as StConstant
|
|
||||||
xml.writeTextNode("const",
|
|
||||||
listOf(Pair("type", node.dt.name)),
|
|
||||||
intOrDouble(node.dt, node.value).toString(),
|
|
||||||
false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeOptions(options: CompilationOptions) {
|
|
||||||
xml.elt("options")
|
|
||||||
xml.attr("target", options.compTarget.name)
|
|
||||||
xml.attr("output", options.output.name)
|
|
||||||
xml.attr("launcher", options.launcher.name)
|
|
||||||
xml.attr("zeropage", options.zeropage.name)
|
|
||||||
xml.attr("loadaddress", options.loadAddress.toString())
|
|
||||||
xml.attr("floatsenabled", options.floats.toString())
|
|
||||||
xml.attr("nosysinit", options.noSysInit.toString())
|
|
||||||
xml.attr("dontreinitglobals", options.dontReinitGlobals.toString())
|
|
||||||
xml.attr("optimize", options.optimize.toString())
|
|
||||||
if(options.evalStackBaseAddress!=null)
|
|
||||||
xml.attr("evalstackbase", options.evalStackBaseAddress!!.toString())
|
|
||||||
if(options.zpReserved.isNotEmpty()) {
|
|
||||||
xml.startChildren()
|
|
||||||
options.zpReserved.forEach {
|
|
||||||
xml.elt("zpreserved")
|
|
||||||
xml.attr("from", it.first.toString())
|
|
||||||
xml.attr("to", it.last.toString())
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(options.symbolDefs.isNotEmpty()) {
|
|
||||||
xml.startChildren()
|
|
||||||
options.symbolDefs.forEach { name, value ->
|
|
||||||
xml.elt("symboldef")
|
|
||||||
xml.attr("name", name)
|
|
||||||
xml.attr("value", value)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeNode(it: PtNode) {
|
|
||||||
when(it) {
|
|
||||||
is PtBlock -> write(it)
|
|
||||||
is PtSub -> write(it)
|
|
||||||
is PtVariable -> write(it)
|
|
||||||
is PtAssignment -> write(it)
|
|
||||||
is PtConstant -> write(it)
|
|
||||||
is PtAsmSub -> write(it)
|
|
||||||
is PtAddressOf -> write(it)
|
|
||||||
is PtArrayIndexer -> write(it)
|
|
||||||
is PtArray -> write(it)
|
|
||||||
is PtBinaryExpression -> write(it)
|
|
||||||
is PtBuiltinFunctionCall -> write(it)
|
|
||||||
is PtConditionalBranch -> write(it)
|
|
||||||
is PtContainmentCheck -> write(it)
|
|
||||||
is PtForLoop -> write(it)
|
|
||||||
is PtFunctionCall -> write(it)
|
|
||||||
is PtIdentifier -> write(it)
|
|
||||||
is PtIfElse -> write(it)
|
|
||||||
is PtInlineAssembly -> write(it)
|
|
||||||
is PtIncludeBinary -> write(it)
|
|
||||||
is PtJump -> write(it)
|
|
||||||
is PtMemoryByte -> write(it)
|
|
||||||
is PtMemMapped -> write(it)
|
|
||||||
is PtNumber -> write(it)
|
|
||||||
is PtPostIncrDecr -> write(it)
|
|
||||||
is PtPrefix -> write(it)
|
|
||||||
is PtRange -> write(it)
|
|
||||||
is PtRepeatLoop -> write(it)
|
|
||||||
is PtReturn -> write(it)
|
|
||||||
is PtString -> write(it)
|
|
||||||
is PtTypeCast -> write(it)
|
|
||||||
is PtWhen -> write(it)
|
|
||||||
is PtWhenChoice -> write(it)
|
|
||||||
is PtLabel -> write(it)
|
|
||||||
is PtNop -> {}
|
|
||||||
is PtBreakpoint -> write(it)
|
|
||||||
is PtScopeVarsDecls -> write(it)
|
|
||||||
is PtNodeGroup -> it.children.forEach { writeNode(it) }
|
|
||||||
else -> TODO("$it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(vars: PtScopeVarsDecls) {
|
|
||||||
xml.elt("vars")
|
|
||||||
xml.startChildren()
|
|
||||||
vars.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(breakPt: PtBreakpoint) {
|
|
||||||
xml.elt("breakpoint")
|
|
||||||
xml.pos(breakPt.position)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(array: PtArray) {
|
|
||||||
xml.elt("array")
|
|
||||||
xml.attr("type", array.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
array.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(prefix: PtPrefix) {
|
|
||||||
xml.elt("prefix")
|
|
||||||
xml.attr("op", prefix.operator)
|
|
||||||
xml.attr("type", prefix.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("value")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(prefix.value)
|
|
||||||
xml.endElt()
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(string: PtString) =
|
|
||||||
xml.writeTextNode("string", listOf(Pair("encoding", string.encoding.name)), string.value, false)
|
|
||||||
|
|
||||||
private fun write(rept: PtRepeatLoop) {
|
|
||||||
xml.elt("repeat")
|
|
||||||
xml.pos(rept.position)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("count")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(rept.count)
|
|
||||||
xml.endElt()
|
|
||||||
writeNode(rept.statements)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(branch: PtConditionalBranch) {
|
|
||||||
xml.elt("conditionalbranch")
|
|
||||||
xml.attr("condition", branch.condition.name)
|
|
||||||
xml.pos(branch.position)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("true")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(branch.trueScope)
|
|
||||||
xml.endElt()
|
|
||||||
if(branch.falseScope.children.isNotEmpty()) {
|
|
||||||
xml.elt("false")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(branch.falseScope)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(check: PtContainmentCheck) {
|
|
||||||
xml.elt("containment")
|
|
||||||
xml.attr("type", check.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("element")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(check.children[0])
|
|
||||||
xml.endElt()
|
|
||||||
xml.elt("iterable")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(check.children[1])
|
|
||||||
xml.endElt()
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(range: PtRange) {
|
|
||||||
xml.elt("range")
|
|
||||||
xml.attr("type", range.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("from")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(range.from)
|
|
||||||
xml.endElt()
|
|
||||||
xml.elt("to")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(range.to)
|
|
||||||
xml.endElt()
|
|
||||||
xml.elt("step")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(range.step)
|
|
||||||
xml.endElt()
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(forLoop: PtForLoop) {
|
|
||||||
xml.elt("for")
|
|
||||||
xml.attr("loopvar", strTargetName(forLoop.variable))
|
|
||||||
xml.pos(forLoop.position)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("iterable")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(forLoop.iterable)
|
|
||||||
xml.endElt()
|
|
||||||
writeNode(forLoop.statements)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(membyte: PtMemoryByte) {
|
|
||||||
xml.elt("membyte")
|
|
||||||
xml.attr("type", membyte.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("address")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(membyte.address)
|
|
||||||
xml.endElt()
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(whenStmt: PtWhen) {
|
|
||||||
xml.elt("when")
|
|
||||||
xml.pos(whenStmt.position)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("value")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(whenStmt.value)
|
|
||||||
xml.endElt()
|
|
||||||
xml.elt("choices")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(whenStmt.choices)
|
|
||||||
xml.endElt()
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(choice: PtWhenChoice) {
|
|
||||||
xml.elt("choice")
|
|
||||||
if(choice.isElse) {
|
|
||||||
xml.attr("else", "true")
|
|
||||||
xml.startChildren()
|
|
||||||
} else {
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("values")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(choice.values)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
writeNode(choice.statements)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(inlineAsm: PtInlineAssembly) {
|
|
||||||
xml.elt("assembly")
|
|
||||||
xml.pos(inlineAsm.position)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.writeTextNode("code", emptyList(), inlineAsm.assembly)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(inlineBinary: PtIncludeBinary) {
|
|
||||||
xml.elt("binary")
|
|
||||||
xml.attr("filename", inlineBinary.file.absolutePathString())
|
|
||||||
if(inlineBinary.offset!=null)
|
|
||||||
xml.attr("offset", inlineBinary.offset!!.toString())
|
|
||||||
if(inlineBinary.length!=null)
|
|
||||||
xml.attr("length", inlineBinary.length!!.toString())
|
|
||||||
xml.pos(inlineBinary.position)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(fcall: PtBuiltinFunctionCall) {
|
|
||||||
xml.elt("builtinfcall")
|
|
||||||
xml.attr("name", fcall.name)
|
|
||||||
if(fcall.void)
|
|
||||||
xml.attr("type", "VOID")
|
|
||||||
else
|
|
||||||
xml.attr("type", fcall.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
fcall.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(cast: PtTypeCast) {
|
|
||||||
xml.elt("cast")
|
|
||||||
xml.attr("type", cast.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(cast.value)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(aix: PtArrayIndexer) {
|
|
||||||
xml.elt("arrayindexed")
|
|
||||||
xml.attr("type", aix.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
write(aix.variable)
|
|
||||||
writeNode(aix.index)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(binexpr: PtBinaryExpression) {
|
|
||||||
xml.elt("binexpr")
|
|
||||||
xml.attr("op", binexpr.operator)
|
|
||||||
xml.attr("type", binexpr.type.name)
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(binexpr.left)
|
|
||||||
writeNode(binexpr.right)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(addrof: PtAddressOf) {
|
|
||||||
xml.elt("addressof")
|
|
||||||
xml.attr("symbol", strTargetName(addrof.identifier))
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(fcall: PtFunctionCall) {
|
|
||||||
xml.elt("fcall")
|
|
||||||
xml.attr("name", strTargetName(fcall))
|
|
||||||
if(fcall.void)
|
|
||||||
xml.attr("type", "VOID")
|
|
||||||
else
|
|
||||||
xml.attr("type", fcall.type.name)
|
|
||||||
xml.pos(fcall.position)
|
|
||||||
xml.startChildren()
|
|
||||||
fcall.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(number: PtNumber) = writeNumber(number.type, number.number)
|
|
||||||
|
|
||||||
private fun writeNumber(type: DataType, number: Double) =
|
|
||||||
xml.writeTextNode("number", listOf(Pair("type", type.name)), intOrDouble(type, number).toString(), false)
|
|
||||||
|
|
||||||
private fun write(symbol: PtIdentifier) {
|
|
||||||
xml.elt("symbol")
|
|
||||||
xml.attr("name", strTargetName(symbol))
|
|
||||||
xml.attr("type", symbol.type.name)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(assign: PtAssignment) {
|
|
||||||
xml.elt("assign")
|
|
||||||
xml.pos(assign.position)
|
|
||||||
xml.startChildren()
|
|
||||||
write(assign.target)
|
|
||||||
writeNode(assign.value)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(ifElse: PtIfElse) {
|
|
||||||
xml.elt("ifelse")
|
|
||||||
xml.pos(ifElse.position)
|
|
||||||
xml.startChildren()
|
|
||||||
xml.elt("condition")
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(ifElse.condition)
|
|
||||||
xml.endElt()
|
|
||||||
xml.elt("true")
|
|
||||||
xml.pos(ifElse.ifScope.position)
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(ifElse.ifScope)
|
|
||||||
xml.endElt()
|
|
||||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
|
||||||
xml.elt("false")
|
|
||||||
xml.pos(ifElse.elseScope.position)
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(ifElse.elseScope)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(ret: PtReturn) {
|
|
||||||
xml.elt("return")
|
|
||||||
if(ret.hasValue) {
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(ret.value!!)
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(incdec: PtPostIncrDecr) {
|
|
||||||
if(incdec.operator=="++") xml.elt("inc") else xml.elt("dec")
|
|
||||||
xml.startChildren()
|
|
||||||
write(incdec.target)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(label: PtLabel) {
|
|
||||||
xml.elt("label")
|
|
||||||
xml.attr("name", label.scopedName.joinToString("."))
|
|
||||||
xml.pos(label.position)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(block: PtBlock) {
|
|
||||||
xml.elt("block")
|
|
||||||
xml.attr("name", block.scopedName.joinToString("."))
|
|
||||||
if(block.address!=null)
|
|
||||||
xml.attr("address", block.address!!.toString())
|
|
||||||
xml.attr("library", block.library.toString())
|
|
||||||
xml.pos(block.position)
|
|
||||||
xml.startChildren()
|
|
||||||
block.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(memMapped: PtMemMapped) {
|
|
||||||
xml.writeTextNode("memvar",
|
|
||||||
listOf(
|
|
||||||
Pair("name", memMapped.scopedName.joinToString(".")),
|
|
||||||
Pair("type", memMapped.type.name)
|
|
||||||
),
|
|
||||||
memMapped.address.toString(),
|
|
||||||
false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(target: PtAssignTarget) {
|
|
||||||
xml.elt("target")
|
|
||||||
xml.startChildren()
|
|
||||||
if(target.identifier!=null) {
|
|
||||||
writeNode(target.identifier!!)
|
|
||||||
} else if(target.memory!=null) {
|
|
||||||
writeNode(target.memory!!)
|
|
||||||
} else if(target.array!=null) {
|
|
||||||
writeNode(target.array!!)
|
|
||||||
} else
|
|
||||||
throw InternalCompilerException("weird assign target")
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(jump: PtJump) {
|
|
||||||
xml.elt("jump")
|
|
||||||
if(jump.identifier!=null) xml.attr("symbol", strTargetName(jump.identifier!!))
|
|
||||||
else if(jump.address!=null) xml.attr("address", jump.address!!.toString())
|
|
||||||
else if(jump.generatedLabel!=null) xml.attr("label", jump.generatedLabel!!)
|
|
||||||
else
|
|
||||||
throw InternalCompilerException("weird jump target")
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(sub: PtSub) {
|
|
||||||
xml.elt("sub")
|
|
||||||
xml.attr("name", sub.scopedName.joinToString("."))
|
|
||||||
if(sub.inline)
|
|
||||||
xml.attr("inline", "true")
|
|
||||||
xml.attr("returntype", sub.returntype?.toString() ?: "VOID")
|
|
||||||
xml.pos(sub.position)
|
|
||||||
xml.startChildren()
|
|
||||||
if(sub.parameters.isNotEmpty()) {
|
|
||||||
xml.elt("parameters")
|
|
||||||
xml.startChildren()
|
|
||||||
sub.parameters.forEach { write(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
sub.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(parameter: PtSubroutineParameter, registerOrStatusflag: RegisterOrStatusflag? = null) {
|
|
||||||
xml.elt("param")
|
|
||||||
xml.attr("name", parameter.name)
|
|
||||||
xml.attr("type", parameter.type.name)
|
|
||||||
if(registerOrStatusflag?.statusflag!=null) {
|
|
||||||
xml.attr("statusflag", registerOrStatusflag.statusflag!!.toString())
|
|
||||||
}
|
|
||||||
if(registerOrStatusflag?.registerOrPair!=null){
|
|
||||||
xml.attr("registers", registerOrStatusflag.registerOrPair!!.name)
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(asmSub: PtAsmSub) {
|
|
||||||
if(asmSub.address!=null) {
|
|
||||||
xml.elt("romsub")
|
|
||||||
xml.attr("name", asmSub.scopedName.joinToString("."))
|
|
||||||
xml.attr("address", asmSub.address!!.toString())
|
|
||||||
if(asmSub.inline)
|
|
||||||
xml.attr("inline", "true")
|
|
||||||
xml.pos(asmSub.position)
|
|
||||||
xml.startChildren()
|
|
||||||
paramsEtcetera(asmSub)
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
xml.elt("asmsub")
|
|
||||||
xml.attr("name", asmSub.scopedName.joinToString("."))
|
|
||||||
if(asmSub.inline)
|
|
||||||
xml.attr("inline", "true")
|
|
||||||
xml.pos(asmSub.position)
|
|
||||||
xml.startChildren()
|
|
||||||
paramsEtcetera(asmSub)
|
|
||||||
xml.elt("code")
|
|
||||||
xml.startChildren()
|
|
||||||
asmSub.children.forEach { writeNode(it) }
|
|
||||||
xml.endElt()
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun paramsEtcetera(asmSub: PtAsmSub) {
|
|
||||||
if(asmSub.parameters.isNotEmpty()) {
|
|
||||||
xml.elt("parameters")
|
|
||||||
xml.startChildren()
|
|
||||||
asmSub.parameters.forEach { (param, reg) -> write(param, reg) }
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
if(asmSub.clobbers.isNotEmpty()) {
|
|
||||||
xml.elt("clobbers")
|
|
||||||
xml.attr("registers", asmSub.clobbers.joinToString(",") { it.name })
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
if(asmSub.retvalRegisters.isNotEmpty()) {
|
|
||||||
xml.elt("returns")
|
|
||||||
xml.startChildren()
|
|
||||||
asmSub.retvalRegisters.forEach {
|
|
||||||
xml.elt("register")
|
|
||||||
if(it.statusflag!=null)
|
|
||||||
xml.attr("statusflag", it.statusflag!!.toString())
|
|
||||||
if(it.registerOrPair!=null)
|
|
||||||
xml.attr("registers", it.registerOrPair!!.toString())
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(constant: PtConstant) {
|
|
||||||
xml.writeTextNode("const",
|
|
||||||
listOf(
|
|
||||||
Pair("name", constant.scopedName.joinToString(".")),
|
|
||||||
Pair("type", constant.type.name)
|
|
||||||
),
|
|
||||||
intOrDouble(constant.type, constant.value).toString(), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun write(variable: PtVariable) {
|
|
||||||
// the variable declaration nodes are still present in the Ast,
|
|
||||||
// but the Symboltable should be used look up their details.
|
|
||||||
xml.elt("vardecl")
|
|
||||||
xml.attr("name", variable.scopedName.joinToString("."))
|
|
||||||
xml.attr("type", variable.type.name)
|
|
||||||
if(variable.arraySize!=null)
|
|
||||||
xml.attr("arraysize", variable.arraySize.toString())
|
|
||||||
if(variable.value!=null) {
|
|
||||||
// static initialization value
|
|
||||||
xml.startChildren()
|
|
||||||
writeNode(variable.value!!)
|
|
||||||
}
|
|
||||||
xml.endElt()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun strTargetName(ident: PtIdentifier): String = ident.targetName.joinToString(".")
|
|
||||||
|
|
||||||
private fun strTargetName(call: PtFunctionCall): String = call.functionName.joinToString(".")
|
|
||||||
|
|
||||||
private fun intOrDouble(type: DataType, value: Double): Number =
|
|
||||||
if(type in IntegerDatatypes) value.toInt() else value
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package prog8.codegen.experimental
|
||||||
|
|
||||||
|
import prog8.code.SymbolTable
|
||||||
|
import prog8.code.ast.PtProgram
|
||||||
|
import prog8.code.core.CompilationOptions
|
||||||
|
import prog8.code.core.IAssemblyGenerator
|
||||||
|
import prog8.code.core.IAssemblyProgram
|
||||||
|
import prog8.code.core.IErrorReporter
|
||||||
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
|
import prog8.intermediate.IRFileWriter
|
||||||
|
|
||||||
|
class CodeGen(private val program: PtProgram,
|
||||||
|
private val symbolTable: SymbolTable,
|
||||||
|
private val options: CompilationOptions,
|
||||||
|
private val errors: IErrorReporter
|
||||||
|
): IAssemblyGenerator {
|
||||||
|
override fun compileToAssembly(): IAssemblyProgram? {
|
||||||
|
|
||||||
|
// you could write a code generator directly on the PtProgram AST,
|
||||||
|
// but you can also use the Intermediate Representation to build a codegen on:
|
||||||
|
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||||
|
val irProgram = irCodeGen.generate()
|
||||||
|
|
||||||
|
// 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 **")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -1,89 +0,0 @@
|
|||||||
package prog8.codegen.experimental
|
|
||||||
|
|
||||||
import prog8.code.core.Position
|
|
||||||
import java.util.*
|
|
||||||
import javax.xml.stream.XMLStreamWriter
|
|
||||||
|
|
||||||
class IndentingXmlWriter(val xml: XMLStreamWriter): XMLStreamWriter by xml {
|
|
||||||
private var indent = 0
|
|
||||||
private var content = Stack<Boolean>()
|
|
||||||
|
|
||||||
fun doc(version: String? = null) = if(version==null) writeStartDocument() else writeStartDocument(version)
|
|
||||||
fun endDoc() = writeEndDocument()
|
|
||||||
fun elt(name: String) = writeStartElement(name)
|
|
||||||
fun attr(name: String, value: String) = writeAttribute(name, value)
|
|
||||||
fun attrs(attributes: List<Pair<String, String>>) = attributes.forEach { writeAttribute(it.first, it.second) }
|
|
||||||
fun startChildren() {
|
|
||||||
xml.writeCharacters("\n")
|
|
||||||
content.pop()
|
|
||||||
content.push(true)
|
|
||||||
}
|
|
||||||
fun endElt(writeIndent: Boolean=true) = writeEndElement(writeIndent)
|
|
||||||
fun pos(pos: Position) = writeAttribute("src", pos.toString())
|
|
||||||
fun comment(text: String) {
|
|
||||||
writeComment(text)
|
|
||||||
writeCharacters("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeStartDocument() {
|
|
||||||
xml.writeStartDocument()
|
|
||||||
xml.writeCharacters("\n")
|
|
||||||
content.push(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeStartDocument(version: String) {
|
|
||||||
xml.writeStartDocument(version)
|
|
||||||
xml.writeCharacters("\n")
|
|
||||||
content.push(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeEndDocument() {
|
|
||||||
xml.writeEndDocument()
|
|
||||||
xml.writeCharacters("\n")
|
|
||||||
require(indent==0)
|
|
||||||
require(content.size==1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeStartElement(name: String) {
|
|
||||||
xml.writeCharacters(" ".repeat(indent))
|
|
||||||
xml.writeStartElement(name)
|
|
||||||
indent++
|
|
||||||
content.push(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeStartElement(name: String, ns: String) {
|
|
||||||
xml.writeCharacters(" ".repeat(indent))
|
|
||||||
xml.writeStartElement(name, ns)
|
|
||||||
indent++
|
|
||||||
content.push(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeEndElement(writeIndents: Boolean) {
|
|
||||||
indent--
|
|
||||||
if(content.pop() && writeIndents)
|
|
||||||
xml.writeCharacters(" ".repeat(indent))
|
|
||||||
xml.writeEndElement()
|
|
||||||
xml.writeCharacters("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeEndElement() = writeEndElement(true)
|
|
||||||
|
|
||||||
override fun writeStartElement(name: String, ns: String, p2: String) {
|
|
||||||
xml.writeCharacters(" ".repeat(indent))
|
|
||||||
xml.writeStartElement(name, ns, p2)
|
|
||||||
indent++
|
|
||||||
content.push(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeTextNode(name: String, attrs: List<Pair<String, String>>, text: String, cdata: Boolean = true) {
|
|
||||||
xml.writeCharacters(" ".repeat(indent))
|
|
||||||
xml.writeStartElement(name)
|
|
||||||
attrs.forEach { (name, value) -> xml.writeAttribute(name, value) }
|
|
||||||
if(cdata)
|
|
||||||
xml.writeCData(text)
|
|
||||||
else
|
|
||||||
xml.writeCharacters(text)
|
|
||||||
xml.writeEndElement()
|
|
||||||
xml.writeCharacters("\n")
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
id "org.jetbrains.kotlin.jvm"
|
id "org.jetbrains.kotlin.jvm"
|
||||||
id "io.kotest" version "0.3.9"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@ -25,9 +24,8 @@ compileTestKotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':codeAst')
|
|
||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
implementation project(':virtualmachine')
|
implementation project(':intermediate')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||||
@ -62,4 +60,4 @@ test {
|
|||||||
testLogging {
|
testLogging {
|
||||||
events "skipped", "failed"
|
events "skipped", "failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,15 +5,14 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
|
<orderEntry type="module" module-name="intermediate" />
|
||||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||||
<orderEntry type="module" module-name="codeAst" />
|
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
|
||||||
<orderEntry type="module" module-name="virtualmachine" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -0,0 +1,298 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.AssemblyError
|
||||||
|
import prog8.code.core.DataType
|
||||||
|
import prog8.code.core.SignedDatatypes
|
||||||
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
|
||||||
|
|
||||||
|
internal fun translate(assignment: PtAssignment): IRCodeChunks {
|
||||||
|
if(assignment.target.children.single() is PtMachineRegister)
|
||||||
|
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||||
|
|
||||||
|
return if (assignment.isInplaceAssign)
|
||||||
|
translateInplaceAssign(assignment)
|
||||||
|
else
|
||||||
|
translateRegularAssign(assignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
|
||||||
|
val ident = assignment.target.identifier
|
||||||
|
val memory = assignment.target.memory
|
||||||
|
val array = assignment.target.array
|
||||||
|
|
||||||
|
return if(ident!=null) {
|
||||||
|
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
|
||||||
|
} else if(memory != null) {
|
||||||
|
if(memory.address is PtNumber)
|
||||||
|
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
||||||
|
else
|
||||||
|
fallbackAssign(assignment)
|
||||||
|
} else if(array!=null) {
|
||||||
|
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
||||||
|
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
||||||
|
// will be optimized later and have the double assignments removed.
|
||||||
|
fallbackAssign(assignment)
|
||||||
|
} else {
|
||||||
|
fallbackAssign(assignment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignSelfInMemoryKnownAddress(
|
||||||
|
address: Int,
|
||||||
|
value: PtExpression,
|
||||||
|
origAssign: PtAssignment
|
||||||
|
): IRCodeChunks {
|
||||||
|
val vmDt = codeGen.irType(value.type)
|
||||||
|
when(value) {
|
||||||
|
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
|
||||||
|
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
|
||||||
|
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
|
||||||
|
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
||||||
|
is PtMemoryByte -> {
|
||||||
|
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
||||||
|
emptyList() // do nothing, mem=mem null assignment.
|
||||||
|
else {
|
||||||
|
// read and write a (i/o) memory location to itself.
|
||||||
|
val tempReg = codeGen.registers.nextFree()
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
|
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
||||||
|
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
||||||
|
listOf(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> return fallbackAssign(origAssign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignSelfInMemory(
|
||||||
|
symbol: String,
|
||||||
|
value: PtExpression,
|
||||||
|
origAssign: PtAssignment
|
||||||
|
): IRCodeChunks {
|
||||||
|
val vmDt = codeGen.irType(value.type)
|
||||||
|
return when(value) {
|
||||||
|
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
|
||||||
|
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
|
||||||
|
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
|
||||||
|
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
||||||
|
is PtMemoryByte -> {
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
|
val tempReg = codeGen.registers.nextFree()
|
||||||
|
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||||
|
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||||
|
listOf(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> fallbackAssign(origAssign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
|
||||||
|
if (codeGen.options.slowCodegenWarnings)
|
||||||
|
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||||
|
return translateRegularAssign(origAssign)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inplaceBinexpr(
|
||||||
|
operator: String,
|
||||||
|
operand: PtExpression,
|
||||||
|
vmDt: IRDataType,
|
||||||
|
signed: Boolean,
|
||||||
|
knownAddress: Int?,
|
||||||
|
symbol: String?,
|
||||||
|
origAssign: PtAssignment
|
||||||
|
): IRCodeChunks {
|
||||||
|
if(knownAddress!=null) {
|
||||||
|
when (operator) {
|
||||||
|
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
||||||
|
"-" -> return expressionEval.operatorMinusInplace(knownAddress, null, vmDt, operand)
|
||||||
|
"*" -> return expressionEval.operatorMultiplyInplace(knownAddress, null, vmDt, operand)
|
||||||
|
"/" -> return expressionEval.operatorDivideInplace(knownAddress, null, vmDt, signed, operand)
|
||||||
|
"|" -> return expressionEval.operatorOrInplace(knownAddress, null, vmDt, operand)
|
||||||
|
"&" -> return expressionEval.operatorAndInplace(knownAddress, null, vmDt, operand)
|
||||||
|
"^" -> return expressionEval.operatorXorInplace(knownAddress, null, vmDt, operand)
|
||||||
|
"<<" -> return expressionEval.operatorShiftLeftInplace(knownAddress, null, vmDt, operand)
|
||||||
|
">>" -> return expressionEval.operatorShiftRightInplace(knownAddress, null, vmDt, signed, operand)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
symbol!!
|
||||||
|
when (operator) {
|
||||||
|
"+" -> return expressionEval.operatorPlusInplace(null, symbol, vmDt, operand)
|
||||||
|
"-" -> return expressionEval.operatorMinusInplace(null, symbol, vmDt, operand)
|
||||||
|
"*" -> return expressionEval.operatorMultiplyInplace(null, symbol, vmDt, operand)
|
||||||
|
"/" -> return expressionEval.operatorDivideInplace(null, symbol, vmDt, signed, operand)
|
||||||
|
"|" -> return expressionEval.operatorOrInplace(null, symbol, vmDt, operand)
|
||||||
|
"&" -> return expressionEval.operatorAndInplace(null, symbol, vmDt, operand)
|
||||||
|
"^" -> return expressionEval.operatorXorInplace(null, symbol, vmDt, operand)
|
||||||
|
"<<" -> return expressionEval.operatorShiftLeftInplace(null, symbol, vmDt, operand)
|
||||||
|
">>" -> return expressionEval.operatorShiftRightInplace(null, symbol, vmDt, signed, operand)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallbackAssign(origAssign)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
|
||||||
|
val code= IRCodeChunk(null, null)
|
||||||
|
when(operator) {
|
||||||
|
"+" -> { }
|
||||||
|
"-" -> {
|
||||||
|
code += if(knownAddress!=null)
|
||||||
|
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress)
|
||||||
|
else
|
||||||
|
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
||||||
|
}
|
||||||
|
"~" -> {
|
||||||
|
val regMask = codeGen.registers.nextFree()
|
||||||
|
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
|
||||||
|
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||||
|
code += if(knownAddress!=null)
|
||||||
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
||||||
|
else
|
||||||
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird prefix operator")
|
||||||
|
}
|
||||||
|
return listOf(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
||||||
|
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||||
|
val ident = assignment.target.identifier
|
||||||
|
val memory = assignment.target.memory
|
||||||
|
val array = assignment.target.array
|
||||||
|
val vmDt = codeGen.irType(assignment.value.type)
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
var resultRegister = -1
|
||||||
|
var resultFpRegister = -1
|
||||||
|
val zero = codeGen.isZero(assignment.value)
|
||||||
|
if(!zero) {
|
||||||
|
// calculate the assignment value
|
||||||
|
if (vmDt == IRDataType.FLOAT) {
|
||||||
|
resultFpRegister = codeGen.registers.nextFreeFloat()
|
||||||
|
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
||||||
|
} else {
|
||||||
|
resultRegister = if (assignment.value is PtMachineRegister) {
|
||||||
|
(assignment.value as PtMachineRegister).register
|
||||||
|
} else {
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
result += expressionEval.translateExpression(assignment.value, reg, -1)
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ident!=null) {
|
||||||
|
val symbol = ident.targetName.joinToString(".")
|
||||||
|
val instruction = if(zero) {
|
||||||
|
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
|
||||||
|
} else {
|
||||||
|
if (vmDt == IRDataType.FLOAT)
|
||||||
|
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
||||||
|
else
|
||||||
|
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(null, null).also { it += instruction }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
else if(array!=null) {
|
||||||
|
val variable = array.variable.targetName.joinToString(".")
|
||||||
|
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
||||||
|
|
||||||
|
if(array.variable.type==DataType.UWORD) {
|
||||||
|
// indexing a pointer var instead of a real array or string
|
||||||
|
if(itemsize!=1)
|
||||||
|
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||||
|
if(array.index.type!=DataType.UBYTE)
|
||||||
|
throw AssemblyError("non-array var indexing requires bytes index")
|
||||||
|
val idxReg = codeGen.registers.nextFree()
|
||||||
|
result += expressionEval.translateExpression(array.index, idxReg, -1)
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
|
if(zero) {
|
||||||
|
// there's no STOREZIX instruction
|
||||||
|
resultRegister = codeGen.registers.nextFree()
|
||||||
|
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
||||||
|
}
|
||||||
|
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
|
||||||
|
result += code
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
val fixedIndex = constIntValue(array.index)
|
||||||
|
if(zero) {
|
||||||
|
if(fixedIndex!=null) {
|
||||||
|
val offset = fixedIndex*itemsize
|
||||||
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
|
||||||
|
result += chunk
|
||||||
|
} else {
|
||||||
|
val indexReg = codeGen.registers.nextFree()
|
||||||
|
result += loadIndexReg(array, itemsize, indexReg)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(vmDt== IRDataType.FLOAT) {
|
||||||
|
if(fixedIndex!=null) {
|
||||||
|
val offset = fixedIndex*itemsize
|
||||||
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
|
||||||
|
result += chunk
|
||||||
|
} else {
|
||||||
|
val indexReg = codeGen.registers.nextFree()
|
||||||
|
result += loadIndexReg(array, itemsize, indexReg)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(fixedIndex!=null) {
|
||||||
|
val offset = fixedIndex*itemsize
|
||||||
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
|
||||||
|
result += chunk
|
||||||
|
} else {
|
||||||
|
val indexReg = codeGen.registers.nextFree()
|
||||||
|
result += loadIndexReg(array, itemsize, indexReg)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
else if(memory!=null) {
|
||||||
|
require(vmDt== IRDataType.BYTE)
|
||||||
|
if(zero) {
|
||||||
|
if(memory.address is PtNumber) {
|
||||||
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
||||||
|
result += chunk
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(memory.address is PtNumber) {
|
||||||
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
|
||||||
|
result += chunk
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw AssemblyError("weird assigntarget")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
|
||||||
|
return if(itemsize==1) {
|
||||||
|
expressionEval.translateExpression(array.index, indexReg, -1)
|
||||||
|
} else {
|
||||||
|
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||||
|
mult.children += array.index
|
||||||
|
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||||
|
expressionEval.translateExpression(mult, indexReg, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,404 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.StStaticVariable
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.AssemblyError
|
||||||
|
import prog8.code.core.DataType
|
||||||
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
|
||||||
|
|
||||||
|
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
return when(call.name) {
|
||||||
|
"any" -> funcAny(call, resultRegister)
|
||||||
|
"all" -> funcAll(call, resultRegister)
|
||||||
|
"abs" -> funcAbs(call, resultRegister)
|
||||||
|
"cmp" -> funcCmp(call)
|
||||||
|
"sgn" -> funcSgn(call, resultRegister)
|
||||||
|
"sqrt16" -> funcSqrt16(call, resultRegister)
|
||||||
|
"pop" -> funcPop(call)
|
||||||
|
"popw" -> funcPopw(call)
|
||||||
|
"push" -> funcPush(call)
|
||||||
|
"pushw" -> funcPushw(call)
|
||||||
|
"rsave",
|
||||||
|
"rsavex",
|
||||||
|
"rrestore",
|
||||||
|
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
|
||||||
|
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||||
|
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||||
|
"msb" -> funcMsb(call, resultRegister)
|
||||||
|
"lsb" -> funcLsb(call, resultRegister)
|
||||||
|
"memory" -> funcMemory(call, resultRegister)
|
||||||
|
"peek" -> funcPeek(call, resultRegister)
|
||||||
|
"peekw" -> funcPeekW(call, resultRegister)
|
||||||
|
"poke" -> funcPoke(call)
|
||||||
|
"pokew" -> funcPokeW(call)
|
||||||
|
"pokemon" -> emptyList()
|
||||||
|
"mkword" -> funcMkword(call, resultRegister)
|
||||||
|
"sort" -> funcSort(call)
|
||||||
|
"reverse" -> funcReverse(call)
|
||||||
|
"rol" -> funcRolRor(Opcode.ROXL, call, resultRegister)
|
||||||
|
"ror" -> funcRolRor(Opcode.ROXR, call, resultRegister)
|
||||||
|
"rol2" -> funcRolRor(Opcode.ROL, call, resultRegister)
|
||||||
|
"ror2" -> funcRolRor(Opcode.ROR, call, resultRegister)
|
||||||
|
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val leftRegister = codeGen.registers.nextFree()
|
||||||
|
val rightRegister = codeGen.registers.nextFree()
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
||||||
|
result += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.CMP, codeGen.irType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
|
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||||
|
val syscall =
|
||||||
|
when (array.dt) {
|
||||||
|
DataType.ARRAY_UB,
|
||||||
|
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
|
||||||
|
DataType.ARRAY_UW,
|
||||||
|
DataType.ARRAY_W -> IMSyscall.ANY_WORD
|
||||||
|
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
|
||||||
|
else -> throw IllegalArgumentException("weird type")
|
||||||
|
}
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
|
if(resultRegister!=0)
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
|
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||||
|
val syscall =
|
||||||
|
when(array.dt) {
|
||||||
|
DataType.ARRAY_UB,
|
||||||
|
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
|
||||||
|
DataType.ARRAY_UW,
|
||||||
|
DataType.ARRAY_W -> IMSyscall.ALL_WORD
|
||||||
|
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
|
||||||
|
else -> throw IllegalArgumentException("weird type")
|
||||||
|
}
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
|
if(resultRegister!=0)
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val sourceDt = call.args.single().type
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
if(sourceDt!=DataType.UWORD) {
|
||||||
|
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||||
|
when (sourceDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = resultRegister)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
val notNegativeLabel = codeGen.createLabelName()
|
||||||
|
val compareReg = codeGen.registers.nextFree()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
||||||
|
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
|
||||||
|
it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||||
|
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
|
||||||
|
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(notNegativeLabel, null)
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
val notNegativeLabel = codeGen.createLabelName()
|
||||||
|
val compareReg = codeGen.registers.nextFree()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
||||||
|
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
|
||||||
|
it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||||
|
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(notNegativeLabel, null)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||||
|
result += assignRegisterTo(call.args.single(), reg)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||||
|
result += assignRegisterTo(call.args.single(), reg)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
val reg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
|
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||||
|
val syscall =
|
||||||
|
when(array.dt) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
|
||||||
|
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
||||||
|
else -> throw IllegalArgumentException("weird type to reverse")
|
||||||
|
}
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
|
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||||
|
val syscall =
|
||||||
|
when(array.dt) {
|
||||||
|
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
||||||
|
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
|
||||||
|
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
|
||||||
|
DataType.ARRAY_W -> IMSyscall.SORT_WORD
|
||||||
|
DataType.STR -> IMSyscall.SORT_UBYTE
|
||||||
|
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||||
|
else -> throw IllegalArgumentException("weird type to sort")
|
||||||
|
}
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val msbReg = codeGen.registers.nextFree()
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], msbReg, -1)
|
||||||
|
result += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
if(codeGen.isZero(call.args[1])) {
|
||||||
|
if (call.args[0] is PtNumber) {
|
||||||
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val valueReg = codeGen.registers.nextFree()
|
||||||
|
if (call.args[0] is PtNumber) {
|
||||||
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
if(codeGen.isZero(call.args[1])) {
|
||||||
|
if (call.args[0] is PtNumber) {
|
||||||
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val valueReg = codeGen.registers.nextFree()
|
||||||
|
if (call.args[0] is PtNumber) {
|
||||||
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
if(call.args[0] is PtNumber) {
|
||||||
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, value = address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
if(call.args[0] is PtNumber) {
|
||||||
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, value = address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val addressReg = codeGen.registers.nextFree()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val name = (call.args[0] as PtString).value
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
|
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
||||||
|
return listOf(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
return exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||||
|
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultRegister, reg2 = resultRegister)
|
||||||
|
}
|
||||||
|
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
|
val vmDt = codeGen.irType(call.args[0].type)
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
|
||||||
|
}
|
||||||
|
result += assignRegisterTo(call.args[0], resultRegister)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||||
|
val assignment = PtAssignment(target.position)
|
||||||
|
val assignTarget = PtAssignTarget(target.position)
|
||||||
|
assignTarget.children.add(target)
|
||||||
|
assignment.children.add(assignTarget)
|
||||||
|
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
result += codeGen.translateNode(assignment)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
1030
codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
Normal file
1030
codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
Normal file
File diff suppressed because it is too large
Load Diff
1166
codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
Normal file
1166
codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,272 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||||
|
fun optimize() {
|
||||||
|
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||||
|
removeEmptyChunks(sub)
|
||||||
|
joinChunks(sub)
|
||||||
|
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||||
|
// we don't optimize Inline Asm chunks here.
|
||||||
|
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
|
||||||
|
if(chunk1 is IRCodeChunk) {
|
||||||
|
do {
|
||||||
|
val indexedInstructions = chunk1.instructions.withIndex()
|
||||||
|
.map { IndexedValue(it.index, it.value) }
|
||||||
|
val changed = removeNops(chunk1, indexedInstructions)
|
||||||
|
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
||||||
|
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||||
|
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
||||||
|
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
||||||
|
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||||
|
// TODO other optimizations:
|
||||||
|
// more complex optimizations such as unused registers
|
||||||
|
} while (changed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeEmptyChunks(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
irprog.linkChunks() // re-link
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||||
|
if(sub.chunks.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 FOR NOW leave it in place. (TODO: consolidate labels into 1)
|
||||||
|
If is last chunk -> keep chunk in place because of the label.
|
||||||
|
Empty Code chunk without label ->
|
||||||
|
should not have been generated! ERROR.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
val relabelChunks = mutableListOf<Pair<Int, String>>()
|
||||||
|
val removeChunks = mutableListOf<Int>()
|
||||||
|
|
||||||
|
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||||
|
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
|
||||||
|
if(chunk.label==null) {
|
||||||
|
removeChunks += index
|
||||||
|
} else {
|
||||||
|
if (index < sub.chunks.size - 1) {
|
||||||
|
val nextchunk = sub.chunks[index + 1]
|
||||||
|
if (nextchunk.label == null) {
|
||||||
|
// can transplant label to next chunk and remove this empty one.
|
||||||
|
relabelChunks += Pair(index + 1, chunk.label!!)
|
||||||
|
removeChunks += index
|
||||||
|
} else {
|
||||||
|
if (chunk.label == nextchunk.label)
|
||||||
|
removeChunks += index
|
||||||
|
else {
|
||||||
|
// TODO: consolidate labels on same chunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relabelChunks.forEach { (index, label) ->
|
||||||
|
val chunk = IRCodeChunk(label, null)
|
||||||
|
chunk.instructions += sub.chunks[index].instructions
|
||||||
|
sub.chunks[index] = chunk
|
||||||
|
}
|
||||||
|
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun joinChunks(sub: IRSubroutine) {
|
||||||
|
// Subroutine contains a list of chunks. Some can be joined into one.
|
||||||
|
|
||||||
|
if(sub.chunks.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
||||||
|
if(chunk.label!=null)
|
||||||
|
return false
|
||||||
|
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
||||||
|
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
|
||||||
|
val lastInstruction = previous.instructions.lastOrNull()
|
||||||
|
if(lastInstruction!=null)
|
||||||
|
return lastInstruction.opcode !in OpcodesThatJump
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val chunks = mutableListOf<IRCodeChunkBase>()
|
||||||
|
chunks += sub.chunks[0]
|
||||||
|
for(ix in 1 until sub.chunks.size) {
|
||||||
|
val lastChunk = chunks.last()
|
||||||
|
if(mayJoin(lastChunk, sub.chunks[ix])) {
|
||||||
|
lastChunk.instructions += sub.chunks[ix].instructions
|
||||||
|
lastChunk.next = sub.chunks[ix].next
|
||||||
|
}
|
||||||
|
else
|
||||||
|
chunks += sub.chunks[ix]
|
||||||
|
}
|
||||||
|
sub.chunks.clear()
|
||||||
|
sub.chunks += chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
// push followed by pop to same target, or different target->replace with load
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
if(ins.opcode== Opcode.PUSH) {
|
||||||
|
if(idx < chunk.instructions.size-1) {
|
||||||
|
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
||||||
|
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
||||||
|
if(ins.reg1==insAfter.reg1) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
} else {
|
||||||
|
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
|
||||||
|
chunk.instructions.removeAt(idx+1)
|
||||||
|
}
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeDoubleSecClc(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
// double sec, clc
|
||||||
|
// sec+clc or clc+sec
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
||||||
|
if(idx < chunk.instructions.size-1) {
|
||||||
|
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
||||||
|
if(insAfter?.opcode == ins.opcode) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
val labelSymbol = ins.labelSymbol
|
||||||
|
|
||||||
|
// remove jump/branch to label immediately below (= next chunk if it has that label)
|
||||||
|
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
|
||||||
|
if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove useless RETURN
|
||||||
|
if(ins.opcode == Opcode.RETURN && idx>0) {
|
||||||
|
val previous = chunk.instructions[idx-1] as? IRInstruction
|
||||||
|
if(previous?.opcode in OpcodesThatJump) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
when (ins.opcode) {
|
||||||
|
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
||||||
|
if (ins.value == 1) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode.ADD, Opcode.SUB -> {
|
||||||
|
if (ins.value == 1) {
|
||||||
|
chunk.instructions[idx] = IRInstruction(
|
||||||
|
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
||||||
|
ins.type,
|
||||||
|
ins.reg1
|
||||||
|
)
|
||||||
|
changed = true
|
||||||
|
} else if (ins.value == 0) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode.AND -> {
|
||||||
|
if (ins.value == 0) {
|
||||||
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
||||||
|
changed = true
|
||||||
|
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode.OR -> {
|
||||||
|
if (ins.value == 0) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
|
||||||
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode.XOR -> {
|
||||||
|
if (ins.value == 0) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
if (ins.opcode == Opcode.NOP) {
|
||||||
|
changed = true
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.forEach { (idx, ins) ->
|
||||||
|
|
||||||
|
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
||||||
|
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
||||||
|
// TODO: detect multiple float ffrom/fto to the same target, only keep first
|
||||||
|
// TODO: detect multiple sequential rnd with same reg1, only keep one
|
||||||
|
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
||||||
|
// TODO: detect multiple same ands, ors; only keep first
|
||||||
|
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.core.IErrorReporter
|
||||||
|
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||||
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
||||||
|
fun optimize(): Int {
|
||||||
|
var numRemoved = removeSimpleUnlinked() + removeUnreachable()
|
||||||
|
|
||||||
|
// remove empty subs
|
||||||
|
irprog.blocks.forEach { block ->
|
||||||
|
block.subroutines.reversed().forEach { sub ->
|
||||||
|
if(sub.isEmpty()) {
|
||||||
|
if(!sub.position.file.startsWith(libraryFilePrefix))
|
||||||
|
errors.warn("unused subroutine ${sub.name}", sub.position)
|
||||||
|
block.subroutines.remove(sub)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove empty blocks
|
||||||
|
irprog.blocks.reversed().forEach { block ->
|
||||||
|
if(block.isEmpty()) {
|
||||||
|
irprog.blocks.remove(block)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnreachable(): Int {
|
||||||
|
val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first())
|
||||||
|
|
||||||
|
fun grow() {
|
||||||
|
val new = mutableSetOf<IRCodeChunkBase>()
|
||||||
|
reachable.forEach {
|
||||||
|
it.next?.let { next -> new += next }
|
||||||
|
it.instructions.forEach { instr -> instr.branchTarget?.let { target -> new += target} }
|
||||||
|
}
|
||||||
|
reachable += new
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousCount = reachable.size
|
||||||
|
while(true) {
|
||||||
|
grow()
|
||||||
|
if(reachable.size<=previousCount)
|
||||||
|
break
|
||||||
|
previousCount = reachable.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeUnlinkedChunks(reachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeSimpleUnlinked(): Int {
|
||||||
|
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
||||||
|
|
||||||
|
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||||
|
sub.chunks.forEach { chunk ->
|
||||||
|
chunk.next?.let { next -> linkedChunks += next }
|
||||||
|
chunk.instructions.forEach { it.branchTarget?.let { target -> linkedChunks += target } }
|
||||||
|
if (chunk.label == "main.start")
|
||||||
|
linkedChunks += chunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeUnlinkedChunks(linkedChunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnlinkedChunks(
|
||||||
|
linkedChunks: MutableSet<IRCodeChunkBase>
|
||||||
|
): Int {
|
||||||
|
var numRemoved = 0
|
||||||
|
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||||
|
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||||
|
if (chunk !in linkedChunks) {
|
||||||
|
if (chunk === sub.chunks[0]) {
|
||||||
|
when(chunk) {
|
||||||
|
is IRCodeChunk -> {
|
||||||
|
if (chunk.isNotEmpty()) {
|
||||||
|
// don't remove the first chunk of the sub itself because it has to have the name of the sub as label
|
||||||
|
chunk.instructions.clear()
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
|
||||||
|
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sub.chunks.removeAt(index)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.core.AssemblyError
|
||||||
|
import prog8.intermediate.SyscallRegisterBase
|
||||||
|
|
||||||
|
internal class RegisterPool {
|
||||||
|
// reserve 0,1,2 for return values of subroutine calls and syscalls
|
||||||
|
private var firstFree: Int=3
|
||||||
|
private var firstFreeFloat: Int=3
|
||||||
|
|
||||||
|
fun peekNext() = firstFree
|
||||||
|
fun peekNextFloat() = firstFreeFloat
|
||||||
|
|
||||||
|
fun nextFree(): Int {
|
||||||
|
val result = firstFree
|
||||||
|
firstFree++
|
||||||
|
if(firstFree >= SyscallRegisterBase)
|
||||||
|
throw AssemblyError("out of virtual registers (int)")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextFreeFloat(): Int {
|
||||||
|
val result = firstFreeFloat
|
||||||
|
firstFreeFloat++
|
||||||
|
if(firstFreeFloat >= SyscallRegisterBase)
|
||||||
|
throw AssemblyError("out of virtual registers (fp)")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
36
codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt
Normal file
36
codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package prog8.codegen.vm
|
||||||
|
|
||||||
|
import prog8.code.SymbolTable
|
||||||
|
import prog8.code.ast.PtProgram
|
||||||
|
import prog8.code.core.CompilationOptions
|
||||||
|
import prog8.code.core.IAssemblyGenerator
|
||||||
|
import prog8.code.core.IAssemblyProgram
|
||||||
|
import prog8.code.core.IErrorReporter
|
||||||
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
|
import prog8.intermediate.IRFileWriter
|
||||||
|
import prog8.intermediate.IRProgram
|
||||||
|
|
||||||
|
class VmCodeGen(private val program: PtProgram,
|
||||||
|
private val symbolTable: SymbolTable,
|
||||||
|
private val options: CompilationOptions,
|
||||||
|
private val errors: IErrorReporter
|
||||||
|
): IAssemblyGenerator {
|
||||||
|
override fun compileToAssembly(): IAssemblyProgram? {
|
||||||
|
|
||||||
|
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||||
|
val irProgram = irCodeGen.generate()
|
||||||
|
|
||||||
|
// no need to check options.keepIR, as the VM file format *is* the IR file.
|
||||||
|
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
|
||||||
|
|
||||||
|
override fun assemble(options: CompilationOptions): Boolean {
|
||||||
|
// the VM reads the IR file from disk.
|
||||||
|
IRFileWriter(irProgram, null).write()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package prog8tests.vm.helpers
|
import prog8.code.core.DataType
|
||||||
|
import prog8.code.core.Encoding
|
||||||
import prog8.code.core.*
|
import prog8.code.core.IMemSizer
|
||||||
|
import prog8.code.core.IStringEncoding
|
||||||
|
|
||||||
|
|
||||||
internal object DummyMemsizer : IMemSizer {
|
internal object DummyMemsizer : IMemSizer {
|
189
codeGenIntermediate/test/TestIRPeepholeOpt.kt
Normal file
189
codeGenIntermediate/test/TestIRPeepholeOpt.kt
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
import prog8.codegen.intermediate.IRPeepholeOptimizer
|
||||||
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
class TestIRPeepholeOpt: FunSpec({
|
||||||
|
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||||
|
require(chunks.first().label=="main.start")
|
||||||
|
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
||||||
|
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
||||||
|
chunks.forEach { sub += it }
|
||||||
|
block += sub
|
||||||
|
val target = VMTarget()
|
||||||
|
val options = CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.DONTUSE,
|
||||||
|
emptyList(),
|
||||||
|
floats = false,
|
||||||
|
noSysInit = true,
|
||||||
|
compTarget = target,
|
||||||
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
|
)
|
||||||
|
val prog = IRProgram("test", IRSymbolTable(null), options, target)
|
||||||
|
prog.addBlock(block)
|
||||||
|
prog.linkChunks()
|
||||||
|
prog.validate()
|
||||||
|
return prog
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
|
||||||
|
val chunk = IRCodeChunk("main.start", null)
|
||||||
|
instructions.forEach { chunk += it }
|
||||||
|
return makeIRProgram(listOf(chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }
|
||||||
|
|
||||||
|
test("remove nops") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
|
||||||
|
IRInstruction(Opcode.NOP),
|
||||||
|
IRInstruction(Opcode.NOP)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 3
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("remove jmp to label below") {
|
||||||
|
val c1 = IRCodeChunk("main.start", null)
|
||||||
|
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
|
||||||
|
val c2 = IRCodeChunk("label", null)
|
||||||
|
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
|
||||||
|
c2 += IRInstruction(Opcode.NOP) // removed
|
||||||
|
val c3 = IRCodeChunk("label2", null)
|
||||||
|
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
||||||
|
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
|
||||||
|
val c4 = IRCodeChunk("label3", null)
|
||||||
|
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
|
||||||
|
|
||||||
|
irProg.chunks().size shouldBe 4
|
||||||
|
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
irProg.chunks().size shouldBe 4
|
||||||
|
irProg.chunks()[0].label shouldBe "main.start"
|
||||||
|
irProg.chunks()[1].label shouldBe "label"
|
||||||
|
irProg.chunks()[2].label shouldBe "label2"
|
||||||
|
irProg.chunks()[3].label shouldBe "label3"
|
||||||
|
irProg.chunks()[0].isEmpty() shouldBe true
|
||||||
|
irProg.chunks()[1].isEmpty() shouldBe true
|
||||||
|
irProg.chunks()[2].isEmpty() shouldBe false
|
||||||
|
irProg.chunks()[3].isEmpty() shouldBe true
|
||||||
|
val instr = irProg.chunks().flatMap { it.instructions }
|
||||||
|
instr.size shouldBe 2
|
||||||
|
instr[0].opcode shouldBe Opcode.JUMP
|
||||||
|
instr[1].opcode shouldBe Opcode.INC
|
||||||
|
}
|
||||||
|
|
||||||
|
test("remove double sec/clc") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.SEC),
|
||||||
|
IRInstruction(Opcode.SEC),
|
||||||
|
IRInstruction(Opcode.SEC),
|
||||||
|
IRInstruction(Opcode.CLC),
|
||||||
|
IRInstruction(Opcode.CLC),
|
||||||
|
IRInstruction(Opcode.CLC)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 6
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
val instr = irProg.chunks().single().instructions
|
||||||
|
instr.size shouldBe 1
|
||||||
|
instr[0].opcode shouldBe Opcode.CLC
|
||||||
|
}
|
||||||
|
|
||||||
|
test("push followed by pop") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
|
||||||
|
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
|
||||||
|
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
|
||||||
|
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
val instr = irProg.chunks().single().instructions
|
||||||
|
instr.size shouldBe 1
|
||||||
|
instr[0].opcode shouldBe Opcode.LOADR
|
||||||
|
instr[0].reg1 shouldBe 222
|
||||||
|
instr[0].reg2 shouldBe 99
|
||||||
|
}
|
||||||
|
|
||||||
|
test("remove useless div/mul, add/sub") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 10
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("replace add/sub 1 by inc/dec") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 2
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
val instr = irProg.chunks().single().instructions
|
||||||
|
instr.size shouldBe 2
|
||||||
|
instr[0].opcode shouldBe Opcode.INC
|
||||||
|
instr[1].opcode shouldBe Opcode.DEC
|
||||||
|
}
|
||||||
|
|
||||||
|
test("remove useless and/or/xor") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
|
||||||
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
|
||||||
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
|
||||||
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
|
||||||
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 8
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("replace and/or/xor by constant number") {
|
||||||
|
val irProg = makeIRProgram(listOf(
|
||||||
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
|
||||||
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
|
||||||
|
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
|
||||||
|
))
|
||||||
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
|
opt.optimize()
|
||||||
|
val instr = irProg.chunks().single().instructions
|
||||||
|
instr.size shouldBe 4
|
||||||
|
instr[0].opcode shouldBe Opcode.LOAD
|
||||||
|
instr[1].opcode shouldBe Opcode.LOAD
|
||||||
|
instr[2].opcode shouldBe Opcode.LOAD
|
||||||
|
instr[3].opcode shouldBe Opcode.LOAD
|
||||||
|
instr[0].value shouldBe 0
|
||||||
|
instr[1].value shouldBe 0
|
||||||
|
instr[2].value shouldBe 255
|
||||||
|
instr[3].value shouldBe 65535
|
||||||
|
}
|
||||||
|
})
|
@ -1,138 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.code.core.AssemblyError
|
|
||||||
import prog8.code.core.CompilationOptions
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
|
||||||
import prog8.vm.Instruction
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.OpcodesWithAddress
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
import java.io.BufferedWriter
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.bufferedWriter
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
|
|
||||||
class AssemblyProgram(override val name: String, private val allocations: VariableAllocator
|
|
||||||
) : IAssemblyProgram {
|
|
||||||
|
|
||||||
private val globalInits = mutableListOf<VmCodeLine>()
|
|
||||||
private val blocks = mutableListOf<VmCodeChunk>()
|
|
||||||
|
|
||||||
override fun assemble(options: CompilationOptions): Boolean {
|
|
||||||
val outfile = options.outputDir / ("$name.p8virt")
|
|
||||||
println("write code to $outfile")
|
|
||||||
outfile.bufferedWriter().use { out ->
|
|
||||||
allocations.asVmMemory().forEach { (name, alloc) ->
|
|
||||||
out.write("; ${name.joinToString(".")}\n")
|
|
||||||
out.write(alloc + "\n")
|
|
||||||
}
|
|
||||||
out.write("------PROGRAM------\n")
|
|
||||||
|
|
||||||
if(!options.dontReinitGlobals) {
|
|
||||||
out.write("; global var inits\n")
|
|
||||||
globalInits.forEach { out.writeLine(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
out.write("; actual program code\n")
|
|
||||||
blocks.asSequence().flatMap { it.lines }.forEach { line->out.writeLine(line) }
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedWriter.writeLine(line: VmCodeLine) {
|
|
||||||
when(line) {
|
|
||||||
is VmCodeComment -> write("; ${line.comment}\n")
|
|
||||||
is VmCodeInstruction -> {
|
|
||||||
write(line.ins.toString() + "\n")
|
|
||||||
}
|
|
||||||
is VmCodeLabel -> write("_" + line.name.joinToString(".") + ":\n")
|
|
||||||
is VmCodeInlineAsm -> {
|
|
||||||
val asm = line.assembly.replace("""\{[a-zA-Z\d_\.]+\}""".toRegex()) { matchResult ->
|
|
||||||
val name = matchResult.value.substring(1, matchResult.value.length-1).split('.')
|
|
||||||
allocations.get(name).toString() }
|
|
||||||
write(asm+"\n")
|
|
||||||
}
|
|
||||||
is VmCodeInlineBinary -> {
|
|
||||||
write("incbin \"${line.file}\"")
|
|
||||||
if(line.offset!=null)
|
|
||||||
write(",${line.offset}")
|
|
||||||
if(line.length!=null)
|
|
||||||
write(",${line.length}")
|
|
||||||
write("\n")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("invalid vm code line")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addGlobalInits(chunk: VmCodeChunk) = globalInits.addAll(chunk.lines)
|
|
||||||
fun addBlock(block: VmCodeChunk) = blocks.add(block)
|
|
||||||
fun getBlocks(): List<VmCodeChunk> = blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class VmCodeLine
|
|
||||||
|
|
||||||
class VmCodeInstruction(
|
|
||||||
opcode: Opcode,
|
|
||||||
type: VmDataType?=null,
|
|
||||||
reg1: Int?=null, // 0-$ffff
|
|
||||||
reg2: Int?=null, // 0-$ffff
|
|
||||||
fpReg1: Int?=null, // 0-$ffff
|
|
||||||
fpReg2: Int?=null, // 0-$ffff
|
|
||||||
value: Int?=null, // 0-$ffff
|
|
||||||
fpValue: Float?=null,
|
|
||||||
labelSymbol: List<String>?=null // alternative to value for branch/call/jump labels
|
|
||||||
): VmCodeLine() {
|
|
||||||
val ins = Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value, fpValue, labelSymbol)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if(reg1!=null && (reg1<0 || reg1>65536))
|
|
||||||
throw IllegalArgumentException("reg1 out of bounds")
|
|
||||||
if(reg2!=null && (reg2<0 || reg2>65536))
|
|
||||||
throw IllegalArgumentException("reg2 out of bounds")
|
|
||||||
if(fpReg1!=null && (fpReg1<0 || fpReg1>65536))
|
|
||||||
throw IllegalArgumentException("fpReg1 out of bounds")
|
|
||||||
if(fpReg2!=null && (fpReg2<0 || fpReg2>65536))
|
|
||||||
throw IllegalArgumentException("fpReg2 out of bounds")
|
|
||||||
|
|
||||||
if(value!=null && opcode !in OpcodesWithAddress) {
|
|
||||||
when (type) {
|
|
||||||
VmDataType.BYTE -> {
|
|
||||||
if (value < -128 || value > 255)
|
|
||||||
throw IllegalArgumentException("value out of range for byte: $value")
|
|
||||||
}
|
|
||||||
VmDataType.WORD -> {
|
|
||||||
if (value < -32768 || value > 65535)
|
|
||||||
throw IllegalArgumentException("value out of range for word: $value")
|
|
||||||
}
|
|
||||||
VmDataType.FLOAT, null -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VmCodeLabel(val name: List<String>): VmCodeLine()
|
|
||||||
internal class VmCodeComment(val comment: String): VmCodeLine()
|
|
||||||
|
|
||||||
class VmCodeChunk(initial: VmCodeLine? = null) {
|
|
||||||
val lines = mutableListOf<VmCodeLine>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
if(initial!=null)
|
|
||||||
lines.add(initial)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun plusAssign(line: VmCodeLine) {
|
|
||||||
lines.add(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun plusAssign(chunk: VmCodeChunk) {
|
|
||||||
lines.addAll(chunk.lines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class VmCodeInlineAsm(asm: String): VmCodeLine() {
|
|
||||||
val assembly: String = asm.trimIndent()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class VmCodeInlineBinary(val file: Path, val offset: UInt?, val length: UInt?): VmCodeLine()
|
|
@ -1,242 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.code.ast.*
|
|
||||||
import prog8.code.core.AssemblyError
|
|
||||||
import prog8.code.core.DataType
|
|
||||||
import prog8.code.core.SignedDatatypes
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
|
|
||||||
internal class AssignmentGen(private val codeGen: CodeGen, private val expressionEval: ExpressionGen) {
|
|
||||||
|
|
||||||
internal fun translate(assignment: PtAssignment): VmCodeChunk {
|
|
||||||
if(assignment.target.children.single() is PtMachineRegister)
|
|
||||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
|
||||||
|
|
||||||
return if (assignment.isInplaceAssign)
|
|
||||||
translateInplaceAssign(assignment)
|
|
||||||
else
|
|
||||||
translateRegularAssign(assignment)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateInplaceAssign(assignment: PtAssignment): VmCodeChunk {
|
|
||||||
val ident = assignment.target.identifier
|
|
||||||
val memory = assignment.target.memory
|
|
||||||
val array = assignment.target.array
|
|
||||||
|
|
||||||
return if(ident!=null) {
|
|
||||||
val address = codeGen.allocations.get(ident.targetName)
|
|
||||||
assignSelfInMemory(address, assignment.value, assignment)
|
|
||||||
} else if(memory != null) {
|
|
||||||
if(memory.address is PtNumber)
|
|
||||||
assignSelfInMemory((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
|
||||||
else
|
|
||||||
fallbackAssign(assignment)
|
|
||||||
} else if(array!=null) {
|
|
||||||
// TODO in-place array element assignment?
|
|
||||||
fallbackAssign(assignment)
|
|
||||||
} else {
|
|
||||||
fallbackAssign(assignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assignSelfInMemory(
|
|
||||||
address: Int,
|
|
||||||
value: PtExpression,
|
|
||||||
origAssign: PtAssignment
|
|
||||||
): VmCodeChunk {
|
|
||||||
val vmDt = codeGen.vmType(value.type)
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
when(value) {
|
|
||||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
|
||||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
|
||||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address)
|
|
||||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, origAssign)
|
|
||||||
is PtMemoryByte -> {
|
|
||||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
|
||||||
code // do nothing, mem=mem null assignment.
|
|
||||||
else {
|
|
||||||
// read and write a (i/o) memory location to itself.
|
|
||||||
val tempReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
|
||||||
code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> return fallbackAssign(origAssign)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fallbackAssign(origAssign: PtAssignment): VmCodeChunk {
|
|
||||||
if (codeGen.options.slowCodegenWarnings)
|
|
||||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
|
||||||
return translateRegularAssign(origAssign)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun inplaceBinexpr(
|
|
||||||
operator: String,
|
|
||||||
operand: PtExpression,
|
|
||||||
vmDt: VmDataType,
|
|
||||||
signed: Boolean,
|
|
||||||
address: Int,
|
|
||||||
origAssign: PtAssignment
|
|
||||||
): VmCodeChunk {
|
|
||||||
when(operator) {
|
|
||||||
"+" -> return expressionEval.operatorPlusInplace(address, vmDt, operand)
|
|
||||||
"-" -> return expressionEval.operatorMinusInplace(address, vmDt, operand)
|
|
||||||
"*" -> return expressionEval.operatorMultiplyInplace(address, vmDt, operand)
|
|
||||||
"/" -> return expressionEval.operatorDivideInplace(address, vmDt, signed, operand)
|
|
||||||
"|" -> return expressionEval.operatorOrInplace(address, vmDt, operand)
|
|
||||||
"&" -> return expressionEval.operatorAndInplace(address, vmDt, operand)
|
|
||||||
"^" -> return expressionEval.operatorXorInplace(address, vmDt, operand)
|
|
||||||
"<<" -> return expressionEval.operatorShiftLeftInplace(address, vmDt, operand)
|
|
||||||
">>" -> return expressionEval.operatorShiftRightInplace(address, vmDt, signed, operand)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
return fallbackAssign(origAssign)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun inplacePrefix(operator: String, vmDt: VmDataType, address: Int): VmCodeChunk {
|
|
||||||
val code= VmCodeChunk()
|
|
||||||
when(operator) {
|
|
||||||
"+" -> { }
|
|
||||||
"-" -> {
|
|
||||||
code += VmCodeInstruction(Opcode.NEGM, vmDt, value = address)
|
|
||||||
}
|
|
||||||
"~" -> {
|
|
||||||
val regMask = codeGen.vmRegisters.nextFree()
|
|
||||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
|
||||||
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird prefix operator")
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateRegularAssign(assignment: PtAssignment): VmCodeChunk {
|
|
||||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
|
||||||
val ident = assignment.target.identifier
|
|
||||||
val memory = assignment.target.memory
|
|
||||||
val array = assignment.target.array
|
|
||||||
val vmDt = codeGen.vmType(assignment.value.type)
|
|
||||||
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
var resultRegister = -1
|
|
||||||
var resultFpRegister = -1
|
|
||||||
val zero = codeGen.isZero(assignment.value)
|
|
||||||
if(!zero) {
|
|
||||||
// calculate the assignment value
|
|
||||||
if (vmDt == VmDataType.FLOAT) {
|
|
||||||
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
|
||||||
} else {
|
|
||||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
|
||||||
(assignment.value as PtMachineRegister).register
|
|
||||||
} else {
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(assignment.value, reg, -1)
|
|
||||||
reg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(ident!=null) {
|
|
||||||
val address = codeGen.allocations.get(ident.targetName)
|
|
||||||
code += if(zero) {
|
|
||||||
VmCodeInstruction(Opcode.STOREZM, vmDt, value = address)
|
|
||||||
} else {
|
|
||||||
if (vmDt == VmDataType.FLOAT)
|
|
||||||
VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value = address)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value = address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(array!=null) {
|
|
||||||
val variable = array.variable.targetName
|
|
||||||
var variableAddr = codeGen.allocations.get(variable)
|
|
||||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
|
||||||
|
|
||||||
if(array.variable.type==DataType.UWORD) {
|
|
||||||
// indexing a pointer var instead of a real array or string
|
|
||||||
if(itemsize!=1)
|
|
||||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
|
||||||
if(array.index.type!=DataType.UBYTE)
|
|
||||||
throw AssemblyError("non-array var indexing requires bytes index")
|
|
||||||
val idxReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(array.index, idxReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, value = variableAddr)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
val fixedIndex = constIntValue(array.index)
|
|
||||||
if(zero) {
|
|
||||||
if(fixedIndex!=null) {
|
|
||||||
variableAddr += fixedIndex*itemsize
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=variableAddr)
|
|
||||||
} else {
|
|
||||||
val indexReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += loadIndexReg(array, itemsize, indexReg)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, value=variableAddr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(vmDt== VmDataType.FLOAT) {
|
|
||||||
if(fixedIndex!=null) {
|
|
||||||
variableAddr += fixedIndex*itemsize
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value=variableAddr)
|
|
||||||
} else {
|
|
||||||
val indexReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += loadIndexReg(array, itemsize, indexReg)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(fixedIndex!=null) {
|
|
||||||
variableAddr += fixedIndex*itemsize
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value=variableAddr)
|
|
||||||
} else {
|
|
||||||
val indexReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += loadIndexReg(array, itemsize, indexReg)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(memory!=null) {
|
|
||||||
require(vmDt== VmDataType.BYTE)
|
|
||||||
if(zero) {
|
|
||||||
if(memory.address is PtNumber) {
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(memory.address is PtNumber) {
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw AssemblyError("weird assigntarget")
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(itemsize==1) {
|
|
||||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
|
||||||
mult.children += array.index
|
|
||||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
|
||||||
code += expressionEval.translateExpression(mult, indexReg, -1)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,376 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.code.StStaticVariable
|
|
||||||
import prog8.code.ast.*
|
|
||||||
import prog8.code.core.AssemblyError
|
|
||||||
import prog8.code.core.DataType
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.Syscall
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
|
|
||||||
internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: ExpressionGen) {
|
|
||||||
|
|
||||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
return when(call.name) {
|
|
||||||
"any" -> funcAny(call, resultRegister)
|
|
||||||
"all" -> funcAll(call, resultRegister)
|
|
||||||
"abs" -> funcAbs(call, resultRegister)
|
|
||||||
"cmp" -> funcCmp(call)
|
|
||||||
"sgn" -> funcSgn(call, resultRegister)
|
|
||||||
"sqrt16" -> funcSqrt16(call, resultRegister)
|
|
||||||
"pop" -> funcPop(call)
|
|
||||||
"popw" -> funcPopw(call)
|
|
||||||
"push" -> funcPush(call)
|
|
||||||
"pushw" -> funcPushw(call)
|
|
||||||
"rsave",
|
|
||||||
"rsavex",
|
|
||||||
"rrestore",
|
|
||||||
"rrestorex" -> VmCodeChunk() // vm doesn't have registers to save/restore
|
|
||||||
"rnd" -> funcRnd(resultRegister)
|
|
||||||
"rndw" -> funcRndw(resultRegister)
|
|
||||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
|
||||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
|
||||||
"msb" -> funcMsb(call, resultRegister)
|
|
||||||
"lsb" -> funcLsb(call, resultRegister)
|
|
||||||
"memory" -> funcMemory(call, resultRegister)
|
|
||||||
"peek" -> funcPeek(call, resultRegister)
|
|
||||||
"peekw" -> funcPeekW(call, resultRegister)
|
|
||||||
"poke" -> funcPoke(call)
|
|
||||||
"pokew" -> funcPokeW(call)
|
|
||||||
"pokemon" -> VmCodeChunk()
|
|
||||||
"mkword" -> funcMkword(call, resultRegister)
|
|
||||||
"sort" -> funcSort(call)
|
|
||||||
"reverse" -> funcReverse(call)
|
|
||||||
"rol" -> funcRolRor(Opcode.ROXL, call, resultRegister)
|
|
||||||
"ror" -> funcRolRor(Opcode.ROXR, call, resultRegister)
|
|
||||||
"rol2" -> funcRolRor(Opcode.ROL, call, resultRegister)
|
|
||||||
"ror2" -> funcRolRor(Opcode.ROR, call, resultRegister)
|
|
||||||
else -> TODO("builtinfunc ${call.name}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcCmp(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val leftRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
val rightRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
|
||||||
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val syscall =
|
|
||||||
when (array.dt) {
|
|
||||||
DataType.ARRAY_UB,
|
|
||||||
DataType.ARRAY_B -> Syscall.ANY_BYTE
|
|
||||||
DataType.ARRAY_UW,
|
|
||||||
DataType.ARRAY_W -> Syscall.ANY_WORD
|
|
||||||
DataType.ARRAY_F -> Syscall.ANY_FLOAT
|
|
||||||
else -> throw IllegalArgumentException("weird type")
|
|
||||||
}
|
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
|
|
||||||
code += VmCodeInstruction(Opcode.SYSCALL, value = syscall.ordinal)
|
|
||||||
if (resultRegister != 0)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
|
||||||
val syscall =
|
|
||||||
when(array.dt) {
|
|
||||||
DataType.ARRAY_UB,
|
|
||||||
DataType.ARRAY_B -> Syscall.ALL_BYTE
|
|
||||||
DataType.ARRAY_UW,
|
|
||||||
DataType.ARRAY_W -> Syscall.ALL_WORD
|
|
||||||
DataType.ARRAY_F -> Syscall.ALL_FLOAT
|
|
||||||
else -> throw IllegalArgumentException("weird type")
|
|
||||||
}
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
|
||||||
code += VmCodeInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
|
||||||
if(resultRegister!=0)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val sourceDt = call.args.single().type
|
|
||||||
if(sourceDt!=DataType.UWORD) {
|
|
||||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
|
||||||
when (sourceDt) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val notNegativeLabel = codeGen.createLabelName()
|
|
||||||
val compareReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
|
|
||||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
|
||||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
|
||||||
code += VmCodeLabel(notNegativeLabel)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val notNegativeLabel = codeGen.createLabelName()
|
|
||||||
val compareReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
|
|
||||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
|
||||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
|
|
||||||
code += VmCodeLabel(notNegativeLabel)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPop(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
|
|
||||||
code += assignRegisterTo(call.args.single(), reg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPopw(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
|
|
||||||
code += assignRegisterTo(call.args.single(), reg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPush(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPushw(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcReverse(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
|
||||||
val sortSyscall =
|
|
||||||
when(array.dt) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> Syscall.REVERSE_BYTES
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> Syscall.REVERSE_WORDS
|
|
||||||
DataType.ARRAY_F -> Syscall.REVERSE_FLOATS
|
|
||||||
else -> throw IllegalArgumentException("weird type to reverse")
|
|
||||||
}
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
|
||||||
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcSort(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
|
||||||
val sortSyscall =
|
|
||||||
when(array.dt) {
|
|
||||||
DataType.ARRAY_UB -> Syscall.SORT_UBYTE
|
|
||||||
DataType.ARRAY_B -> Syscall.SORT_BYTE
|
|
||||||
DataType.ARRAY_UW -> Syscall.SORT_UWORD
|
|
||||||
DataType.ARRAY_W -> Syscall.SORT_WORD
|
|
||||||
DataType.STR -> Syscall.SORT_UBYTE
|
|
||||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
|
||||||
else -> throw IllegalArgumentException("weird type to sort")
|
|
||||||
}
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
|
||||||
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val msbReg = codeGen.vmRegisters.nextFree()
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args[0], msbReg, -1)
|
|
||||||
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPokeW(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(codeGen.isZero(call.args[1])) {
|
|
||||||
if (call.args[0] is PtNumber) {
|
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val valueReg = codeGen.vmRegisters.nextFree()
|
|
||||||
if (call.args[0] is PtNumber) {
|
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPoke(call: PtBuiltinFunctionCall): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(codeGen.isZero(call.args[1])) {
|
|
||||||
if (call.args[0] is PtNumber) {
|
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val valueReg = codeGen.vmRegisters.nextFree()
|
|
||||||
if (call.args[0] is PtNumber) {
|
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(call.args[0] is PtNumber) {
|
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(call.args[0] is PtNumber) {
|
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
|
|
||||||
} else {
|
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcRnd(resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += VmCodeInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcRndw(resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += VmCodeInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val name = (call.args[0] as PtString).value
|
|
||||||
val size = (call.args[1] as PtNumber).number.toUInt()
|
|
||||||
val align = (call.args[2] as PtNumber).number.toUInt()
|
|
||||||
val existing = codeGen.allocations.getMemorySlab(name)
|
|
||||||
val address = if(existing==null)
|
|
||||||
codeGen.allocations.allocateMemorySlab(name, size, align)
|
|
||||||
else if(existing.second!=size || existing.third!=align) {
|
|
||||||
codeGen.errors.err("memory slab '$name' already exists with a different size or alignment", call.position)
|
|
||||||
return VmCodeChunk()
|
|
||||||
}
|
|
||||||
else
|
|
||||||
existing.first
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, value=address.toInt())
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
|
||||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
|
|
||||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
|
||||||
val vmDt = codeGen.vmType(call.args[0].type)
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(opcode, vmDt, reg1=resultRegister)
|
|
||||||
code += assignRegisterTo(call.args[0], resultRegister)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assignRegisterTo(target: PtExpression, register: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val assignment = PtAssignment(target.position)
|
|
||||||
val assignTarget = PtAssignTarget(target.position)
|
|
||||||
assignTarget.children.add(target)
|
|
||||||
assignment.children.add(assignTarget)
|
|
||||||
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
|
||||||
code += codeGen.translateNode(assignment)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,830 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.code.StStaticVariable
|
|
||||||
import prog8.code.SymbolTable
|
|
||||||
import prog8.code.ast.*
|
|
||||||
import prog8.code.core.*
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
|
|
||||||
internal class VmRegisterPool {
|
|
||||||
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
|
|
||||||
private var firstFreeFloat: Int=0
|
|
||||||
|
|
||||||
fun peekNext() = firstFree
|
|
||||||
fun peekNextFloat() = firstFreeFloat
|
|
||||||
|
|
||||||
fun nextFree(): Int {
|
|
||||||
val result = firstFree
|
|
||||||
firstFree++
|
|
||||||
if(firstFree>65535)
|
|
||||||
throw AssemblyError("out of virtual registers (int)")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun nextFreeFloat(): Int {
|
|
||||||
val result = firstFreeFloat
|
|
||||||
firstFreeFloat++
|
|
||||||
if(firstFreeFloat>65535)
|
|
||||||
throw AssemblyError("out of virtual registers (fp)")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CodeGen(internal val program: PtProgram,
|
|
||||||
internal val symbolTable: SymbolTable,
|
|
||||||
internal val options: CompilationOptions,
|
|
||||||
internal val errors: IErrorReporter
|
|
||||||
): IAssemblyGenerator {
|
|
||||||
|
|
||||||
internal val allocations = VariableAllocator(symbolTable, program)
|
|
||||||
private val expressionEval = ExpressionGen(this)
|
|
||||||
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
|
||||||
private val assignmentGen = AssignmentGen(this, expressionEval)
|
|
||||||
internal val vmRegisters = VmRegisterPool()
|
|
||||||
|
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
|
||||||
val vmprog = AssemblyProgram(program.name, allocations)
|
|
||||||
|
|
||||||
if(!options.dontReinitGlobals) {
|
|
||||||
// collect global variables initializers
|
|
||||||
program.allBlocks().forEach {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> code += assignmentGen.translate(assign) }
|
|
||||||
vmprog.addGlobalInits(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.symbolDefs.isNotEmpty())
|
|
||||||
throw AssemblyError("virtual target doesn't support symbols defined on the commandline")
|
|
||||||
if(options.evalStackBaseAddress!=null)
|
|
||||||
throw AssemblyError("virtual target doesn't use eval-stack")
|
|
||||||
|
|
||||||
for (block in program.allBlocks()) {
|
|
||||||
vmprog.addBlock(translate(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.optimize) {
|
|
||||||
val optimizer = VmPeepholeOptimizer(vmprog, allocations)
|
|
||||||
optimizer.optimize()
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Vm codegen: virtual registers=${vmRegisters.peekNext()} memory usage=${allocations.freeMem}")
|
|
||||||
|
|
||||||
return vmprog
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun translateNode(node: PtNode): VmCodeChunk {
|
|
||||||
val code = when(node) {
|
|
||||||
is PtBlock -> translate(node)
|
|
||||||
is PtSub -> translate(node)
|
|
||||||
is PtScopeVarsDecls -> VmCodeChunk() // vars should be looked up via symbol table
|
|
||||||
is PtVariable -> VmCodeChunk() // var should be looked up via symbol table
|
|
||||||
is PtMemMapped -> VmCodeChunk() // memmapped var should be looked up via symbol table
|
|
||||||
is PtConstant -> VmCodeChunk() // constants have all been folded into the code
|
|
||||||
is PtAssignment -> assignmentGen.translate(node)
|
|
||||||
is PtNodeGroup -> translateGroup(node.children)
|
|
||||||
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
|
|
||||||
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
|
|
||||||
is PtNop -> VmCodeChunk()
|
|
||||||
is PtReturn -> translate(node)
|
|
||||||
is PtJump -> translate(node)
|
|
||||||
is PtWhen -> translate(node)
|
|
||||||
is PtForLoop -> translate(node)
|
|
||||||
is PtIfElse -> translate(node)
|
|
||||||
is PtPostIncrDecr -> translate(node)
|
|
||||||
is PtRepeatLoop -> translate(node)
|
|
||||||
is PtLabel -> VmCodeChunk(VmCodeLabel(node.scopedName))
|
|
||||||
is PtBreakpoint -> VmCodeChunk(VmCodeInstruction(Opcode.BREAKPOINT))
|
|
||||||
is PtConditionalBranch -> translate(node)
|
|
||||||
is PtInlineAssembly -> VmCodeChunk(VmCodeInlineAsm(node.assembly))
|
|
||||||
is PtIncludeBinary -> VmCodeChunk(VmCodeInlineBinary(node.file, node.offset, node.length))
|
|
||||||
is PtAsmSub -> TODO("asmsub not yet supported on virtual machine target ${node.position}")
|
|
||||||
is PtAddressOf,
|
|
||||||
is PtContainmentCheck,
|
|
||||||
is PtMemoryByte,
|
|
||||||
is PtProgram,
|
|
||||||
is PtArrayIndexer,
|
|
||||||
is PtBinaryExpression,
|
|
||||||
is PtIdentifier,
|
|
||||||
is PtWhenChoice,
|
|
||||||
is PtPrefix,
|
|
||||||
is PtRange,
|
|
||||||
is PtAssignTarget,
|
|
||||||
is PtTypeCast,
|
|
||||||
is PtSubroutineParameter,
|
|
||||||
is PtNumber,
|
|
||||||
is PtArray,
|
|
||||||
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
|
|
||||||
else -> TODO("missing codegen for $node")
|
|
||||||
}
|
|
||||||
if(code.lines.isNotEmpty() && node.position.line!=0)
|
|
||||||
code.lines.add(0, VmCodeComment(node.position.toString()))
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(branch: PtConditionalBranch): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val elseLabel = createLabelName()
|
|
||||||
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
|
|
||||||
code += when(branch.condition) {
|
|
||||||
BranchCondition.CS -> VmCodeInstruction(Opcode.BSTCC, labelSymbol = elseLabel)
|
|
||||||
BranchCondition.CC -> VmCodeInstruction(Opcode.BSTCS, labelSymbol = elseLabel)
|
|
||||||
BranchCondition.EQ, BranchCondition.Z -> VmCodeInstruction(Opcode.BSTNE, labelSymbol = elseLabel)
|
|
||||||
BranchCondition.NE, BranchCondition.NZ -> VmCodeInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
|
||||||
BranchCondition.MI, BranchCondition.NEG -> VmCodeInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
|
|
||||||
BranchCondition.PL, BranchCondition.POS -> VmCodeInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
|
|
||||||
BranchCondition.VC,
|
|
||||||
BranchCondition.VS -> throw AssemblyError("conditional branch ${branch.condition} not supported in vm target due to lack of cpu V flag ${branch.position}")
|
|
||||||
}
|
|
||||||
code += translateNode(branch.trueScope)
|
|
||||||
if(branch.falseScope.children.isNotEmpty()) {
|
|
||||||
val endLabel = createLabelName()
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
|
||||||
code += VmCodeLabel(elseLabel)
|
|
||||||
code += translateNode(branch.falseScope)
|
|
||||||
code += VmCodeLabel(endLabel)
|
|
||||||
} else {
|
|
||||||
code += VmCodeLabel(elseLabel)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(whenStmt: PtWhen): VmCodeChunk {
|
|
||||||
if(whenStmt.choices.children.isEmpty())
|
|
||||||
return VmCodeChunk()
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val valueReg = vmRegisters.nextFree()
|
|
||||||
val choiceReg = vmRegisters.nextFree()
|
|
||||||
val valueDt = vmType(whenStmt.value.type)
|
|
||||||
code += expressionEval.translateExpression(whenStmt.value, valueReg, -1)
|
|
||||||
val choices = whenStmt.choices.children.map {it as PtWhenChoice }
|
|
||||||
val endLabel = createLabelName()
|
|
||||||
for (choice in choices) {
|
|
||||||
if(choice.isElse) {
|
|
||||||
code += translateNode(choice.statements)
|
|
||||||
} else {
|
|
||||||
val skipLabel = createLabelName()
|
|
||||||
val values = choice.values.children.map {it as PtNumber}
|
|
||||||
if(values.size==1) {
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=values[0].number.toInt())
|
|
||||||
code += VmCodeInstruction(Opcode.BNE, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = skipLabel)
|
|
||||||
code += translateNode(choice.statements)
|
|
||||||
if(choice.statements.children.last() !is PtReturn)
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
|
||||||
} else {
|
|
||||||
val matchLabel = createLabelName()
|
|
||||||
for (value in values) {
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=value.number.toInt())
|
|
||||||
code += VmCodeInstruction(Opcode.BEQ, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = matchLabel)
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = skipLabel)
|
|
||||||
code += VmCodeLabel(matchLabel)
|
|
||||||
code += translateNode(choice.statements)
|
|
||||||
if(choice.statements.children.last() !is PtReturn)
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
|
||||||
}
|
|
||||||
code += VmCodeLabel(skipLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code += VmCodeLabel(endLabel)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(forLoop: PtForLoop): VmCodeChunk {
|
|
||||||
val loopvar = symbolTable.lookup(forLoop.variable.targetName) as StStaticVariable
|
|
||||||
val iterable = forLoop.iterable
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
when(iterable) {
|
|
||||||
is PtRange -> {
|
|
||||||
if(iterable.from is PtNumber && iterable.to is PtNumber)
|
|
||||||
code += translateForInConstantRange(forLoop, loopvar)
|
|
||||||
else
|
|
||||||
code += translateForInNonConstantRange(forLoop, loopvar)
|
|
||||||
}
|
|
||||||
is PtIdentifier -> {
|
|
||||||
val arrayAddress = allocations.get(iterable.targetName)
|
|
||||||
val iterableVar = symbolTable.lookup(iterable.targetName) as StStaticVariable
|
|
||||||
val loopvarAddress = allocations.get(loopvar.scopedName)
|
|
||||||
val indexReg = vmRegisters.nextFree()
|
|
||||||
val tmpReg = vmRegisters.nextFree()
|
|
||||||
val loopLabel = createLabelName()
|
|
||||||
val endLabel = createLabelName()
|
|
||||||
if(iterableVar.dt==DataType.STR) {
|
|
||||||
// iterate over a zero-terminated string
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
|
||||||
code += VmCodeLabel(loopLabel)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADX, VmDataType.BYTE, reg1=tmpReg, reg2=indexReg, value = arrayAddress)
|
|
||||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=tmpReg, labelSymbol = endLabel)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1=tmpReg, value = loopvarAddress)
|
|
||||||
code += translateNode(forLoop.statements)
|
|
||||||
code += VmCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=indexReg)
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = loopLabel)
|
|
||||||
code += VmCodeLabel(endLabel)
|
|
||||||
} else {
|
|
||||||
// iterate over array
|
|
||||||
val elementDt = ArrayToElementTypes.getValue(iterable.type)
|
|
||||||
val elementSize = program.memsizer.memorySize(elementDt)
|
|
||||||
val lengthBytes = iterableVar.length!! * elementSize
|
|
||||||
if(lengthBytes<256) {
|
|
||||||
val lengthReg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=lengthReg, value=lengthBytes)
|
|
||||||
code += VmCodeLabel(loopLabel)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=tmpReg, reg2=indexReg, value=arrayAddress)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=tmpReg, value = loopvarAddress)
|
|
||||||
code += translateNode(forLoop.statements)
|
|
||||||
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
|
|
||||||
code += VmCodeInstruction(Opcode.BNE, VmDataType.BYTE, reg1=indexReg, reg2=lengthReg, labelSymbol = loopLabel)
|
|
||||||
} else if(lengthBytes==256) {
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
|
||||||
code += VmCodeLabel(loopLabel)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=tmpReg, reg2=indexReg, value=arrayAddress)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=tmpReg, value = loopvarAddress)
|
|
||||||
code += translateNode(forLoop.statements)
|
|
||||||
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
|
|
||||||
code += VmCodeInstruction(Opcode.BNZ, VmDataType.BYTE, reg1=indexReg, labelSymbol = loopLabel)
|
|
||||||
} else {
|
|
||||||
throw AssemblyError("iterator length should never exceed 256")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird for iterable")
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateForInNonConstantRange(forLoop: PtForLoop, loopvar: StStaticVariable): VmCodeChunk {
|
|
||||||
val iterable = forLoop.iterable as PtRange
|
|
||||||
val step = iterable.step.number.toInt()
|
|
||||||
if (step==0)
|
|
||||||
throw AssemblyError("step 0")
|
|
||||||
val indexReg = vmRegisters.nextFree()
|
|
||||||
val endvalueReg = vmRegisters.nextFree()
|
|
||||||
val loopvarAddress = allocations.get(loopvar.scopedName)
|
|
||||||
val loopvarDt = vmType(loopvar.dt)
|
|
||||||
val loopLabel = createLabelName()
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
|
|
||||||
code += expressionEval.translateExpression(iterable.to, endvalueReg, -1)
|
|
||||||
code += expressionEval.translateExpression(iterable.from, indexReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
|
|
||||||
code += VmCodeLabel(loopLabel)
|
|
||||||
code += translateNode(forLoop.statements)
|
|
||||||
if(step<3) {
|
|
||||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
|
||||||
} else {
|
|
||||||
// TODO WHY THID DISTINCTION?
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
|
||||||
code += addConstReg(loopvarDt, indexReg, step)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
|
||||||
}
|
|
||||||
val branchOpcode = if(loopvar.dt in SignedDatatypes) Opcode.BLES else Opcode.BLE
|
|
||||||
code += VmCodeInstruction(branchOpcode, loopvarDt, reg1=indexReg, reg2=endvalueReg, labelSymbol=loopLabel)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StStaticVariable): VmCodeChunk {
|
|
||||||
val loopLabel = createLabelName()
|
|
||||||
val loopvarAddress = allocations.get(loopvar.scopedName)
|
|
||||||
val indexReg = vmRegisters.nextFree()
|
|
||||||
val loopvarDt = vmType(loopvar.dt)
|
|
||||||
val iterable = forLoop.iterable as PtRange
|
|
||||||
val step = iterable.step.number.toInt()
|
|
||||||
val rangeStart = (iterable.from as PtNumber).number.toInt()
|
|
||||||
val rangeEndUntyped = (iterable.to as PtNumber).number.toInt() + step
|
|
||||||
if(step==0)
|
|
||||||
throw AssemblyError("step 0")
|
|
||||||
if(step>0 && rangeEndUntyped<rangeStart || step<0 && rangeEndUntyped>rangeStart)
|
|
||||||
throw AssemblyError("empty range")
|
|
||||||
val rangeEndWrapped = if(loopvarDt==VmDataType.BYTE) rangeEndUntyped and 255 else rangeEndUntyped and 65535
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val endvalueReg: Int
|
|
||||||
if(rangeEndWrapped!=0) {
|
|
||||||
endvalueReg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1 = endvalueReg, value = rangeEndWrapped)
|
|
||||||
} else {
|
|
||||||
endvalueReg = -1 // not used
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1=indexReg, value=rangeStart)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
|
|
||||||
code += VmCodeLabel(loopLabel)
|
|
||||||
code += translateNode(forLoop.statements)
|
|
||||||
if(step<3) {
|
|
||||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
|
||||||
} else {
|
|
||||||
// TODO WHY THIS DISTICTION ?
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
|
||||||
code += addConstReg(loopvarDt, indexReg, step)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
|
||||||
}
|
|
||||||
code += if(rangeEndWrapped==0) {
|
|
||||||
VmCodeInstruction(Opcode.BNZ, loopvarDt, reg1 = indexReg, labelSymbol = loopLabel)
|
|
||||||
} else {
|
|
||||||
VmCodeInstruction(Opcode.BNE, loopvarDt, reg1 = indexReg, reg2 = endvalueReg, labelSymbol = loopLabel)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addConstReg(dt: VmDataType, reg: Int, value: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
when(value) {
|
|
||||||
0 -> { /* do nothing */ }
|
|
||||||
1 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.INC, dt, reg1=reg)
|
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.INC, dt, reg1=reg)
|
|
||||||
code += VmCodeInstruction(Opcode.INC, dt, reg1=reg)
|
|
||||||
}
|
|
||||||
-1 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
|
|
||||||
}
|
|
||||||
-2 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
|
|
||||||
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
code += if(value>0) {
|
|
||||||
VmCodeInstruction(Opcode.ADD, dt, reg1 = reg, value=value)
|
|
||||||
} else {
|
|
||||||
VmCodeInstruction(Opcode.SUB, dt, reg1 = reg, value=-value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addConstMem(dt: VmDataType, address: UInt, value: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
when(value) {
|
|
||||||
0 -> { /* do nothing */ }
|
|
||||||
1 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.INCM, dt, value=address.toInt())
|
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.INCM, dt, value=address.toInt())
|
|
||||||
code += VmCodeInstruction(Opcode.INCM, dt, value=address.toInt())
|
|
||||||
}
|
|
||||||
-1 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.DECM, dt, value=address.toInt())
|
|
||||||
}
|
|
||||||
-2 -> {
|
|
||||||
code += VmCodeInstruction(Opcode.DECM, dt, value=address.toInt())
|
|
||||||
code += VmCodeInstruction(Opcode.DECM, dt, value=address.toInt())
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val valueReg = vmRegisters.nextFree()
|
|
||||||
val operandReg = vmRegisters.nextFree()
|
|
||||||
if(value>0) {
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=value)
|
|
||||||
code += VmCodeInstruction(Opcode.ADDR, dt, reg1 = valueReg, reg2 = operandReg) // TODO USE ADDM?
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=-value)
|
|
||||||
code += VmCodeInstruction(Opcode.SUBR, dt, reg1 = valueReg, reg2 = operandReg) // TODO USE ADDM?
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1f)
|
|
||||||
return code
|
|
||||||
code += if(factor==0f) {
|
|
||||||
VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = 0f)
|
|
||||||
} else {
|
|
||||||
VmCodeInstruction(Opcode.MUL, VmDataType.FLOAT, fpReg1 = fpReg, fpValue=factor)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun multiplyByConstFloatInplace(address: Int, factor: Float): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1f)
|
|
||||||
return code
|
|
||||||
if(factor==0f) {
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.FLOAT, value = address)
|
|
||||||
} else {
|
|
||||||
val factorReg = vmRegisters.nextFreeFloat()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
|
||||||
code += VmCodeInstruction(Opcode.MULM, VmDataType.FLOAT, fpReg1 = factorReg, value = address)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
|
|
||||||
|
|
||||||
internal fun multiplyByConst(dt: VmDataType, reg: Int, factor: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1)
|
|
||||||
return code
|
|
||||||
val pow2 = powersOfTwo.indexOf(factor)
|
|
||||||
if(pow2==1) {
|
|
||||||
// just shift 1 bit
|
|
||||||
code += VmCodeInstruction(Opcode.LSL, dt, reg1=reg)
|
|
||||||
}
|
|
||||||
else if(pow2>=1) {
|
|
||||||
// just shift multiple bits
|
|
||||||
val pow2reg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
||||||
code += VmCodeInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
|
|
||||||
} else {
|
|
||||||
code += if (factor == 0) {
|
|
||||||
VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
|
|
||||||
} else {
|
|
||||||
VmCodeInstruction(Opcode.MUL, dt, reg1=reg, value=factor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun multiplyByConstInplace(dt: VmDataType, address: Int, factor: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1)
|
|
||||||
return code
|
|
||||||
val pow2 = powersOfTwo.indexOf(factor)
|
|
||||||
if(pow2==1) {
|
|
||||||
// just shift 1 bit
|
|
||||||
code += VmCodeInstruction(Opcode.LSLM, dt, value = address)
|
|
||||||
}
|
|
||||||
else if(pow2>=1) {
|
|
||||||
// just shift multiple bits
|
|
||||||
val pow2reg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
||||||
code += VmCodeInstruction(Opcode.LSLNM, dt, reg1=pow2reg, value=address)
|
|
||||||
} else {
|
|
||||||
if (factor == 0) {
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, dt, value=address)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val factorReg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value = factor)
|
|
||||||
code += VmCodeInstruction(Opcode.MULM, dt, reg1=factorReg, value = address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun divideByConstFloat(fpReg: Int, factor: Float): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1f)
|
|
||||||
return code
|
|
||||||
code += if(factor==0f) {
|
|
||||||
VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = Float.MAX_VALUE)
|
|
||||||
} else {
|
|
||||||
VmCodeInstruction(Opcode.DIVS, VmDataType.FLOAT, fpReg1 = fpReg, fpValue=factor)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun divideByConstFloatInplace(address: Int, factor: Float): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1f)
|
|
||||||
return code
|
|
||||||
if(factor==0f) {
|
|
||||||
val maxvalueReg = vmRegisters.nextFreeFloat()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = maxvalueReg, fpValue = Float.MAX_VALUE)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.FLOAT, fpReg1 = maxvalueReg, value=address)
|
|
||||||
} else {
|
|
||||||
val factorReg = vmRegisters.nextFreeFloat()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
|
||||||
code += VmCodeInstruction(Opcode.DIVSM, VmDataType.FLOAT, fpReg1 = factorReg, value=address)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun divideByConst(dt: VmDataType, reg: Int, factor: Int, signed: Boolean): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1)
|
|
||||||
return code
|
|
||||||
val pow2 = powersOfTwo.indexOf(factor)
|
|
||||||
if(pow2==1) {
|
|
||||||
// just shift 1 bit
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.ASR, dt, reg1=reg)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.LSR, dt, reg1=reg)
|
|
||||||
}
|
|
||||||
else if(pow2>=1) {
|
|
||||||
// just shift multiple bits
|
|
||||||
val pow2reg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
|
|
||||||
} else {
|
|
||||||
code += if (factor == 0) {
|
|
||||||
VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
|
||||||
} else {
|
|
||||||
if(signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVS, dt, reg1=reg, value=factor)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIV, dt, reg1=reg, value=factor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun divideByConstInplace(dt: VmDataType, address: Int, factor: Int, signed: Boolean): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(factor==1)
|
|
||||||
return code
|
|
||||||
val pow2 = powersOfTwo.indexOf(factor)
|
|
||||||
if(pow2==1) {
|
|
||||||
// just shift 1 bit
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.ASRM, dt, value=address)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.LSRM, dt, value=address)
|
|
||||||
}
|
|
||||||
else if(pow2>=1) {
|
|
||||||
// just shift multiple bits
|
|
||||||
val pow2reg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.ASRNM, dt, reg1=pow2reg, value=address)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.LSRNM, dt, reg1=pow2reg, value=address)
|
|
||||||
} else {
|
|
||||||
if (factor == 0) {
|
|
||||||
val reg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=reg, value=address)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val factorReg = vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVSM, dt, reg1=factorReg, value=address)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIVM, dt, reg1=factorReg, value=address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(ifElse: PtIfElse): VmCodeChunk {
|
|
||||||
if(ifElse.condition.operator !in ComparisonOperators)
|
|
||||||
throw AssemblyError("if condition should only be a binary comparison expression")
|
|
||||||
|
|
||||||
val signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
|
||||||
val vmDt = vmType(ifElse.condition.left.type)
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
|
|
||||||
fun translateNonZeroComparison(): VmCodeChunk {
|
|
||||||
val elseBranch = when(ifElse.condition.operator) {
|
|
||||||
"==" -> Opcode.BNE
|
|
||||||
"!=" -> Opcode.BEQ
|
|
||||||
"<" -> if(signed) Opcode.BGES else Opcode.BGE
|
|
||||||
">" -> if(signed) Opcode.BLES else Opcode.BLE
|
|
||||||
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
|
|
||||||
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
|
|
||||||
else -> throw AssemblyError("invalid comparison operator")
|
|
||||||
}
|
|
||||||
|
|
||||||
val leftReg = vmRegisters.nextFree()
|
|
||||||
val rightReg = vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
|
||||||
code += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
|
|
||||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
|
||||||
// if and else parts
|
|
||||||
val elseLabel = createLabelName()
|
|
||||||
val afterIfLabel = createLabelName()
|
|
||||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, reg2=rightReg, labelSymbol = elseLabel)
|
|
||||||
code += translateNode(ifElse.ifScope)
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = afterIfLabel)
|
|
||||||
code += VmCodeLabel(elseLabel)
|
|
||||||
code += translateNode(ifElse.elseScope)
|
|
||||||
code += VmCodeLabel(afterIfLabel)
|
|
||||||
} else {
|
|
||||||
// only if part
|
|
||||||
val afterIfLabel = createLabelName()
|
|
||||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, reg2=rightReg, labelSymbol = afterIfLabel)
|
|
||||||
code += translateNode(ifElse.ifScope)
|
|
||||||
code += VmCodeLabel(afterIfLabel)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateZeroComparison(): VmCodeChunk {
|
|
||||||
fun equalOrNotEqualZero(elseBranch: Opcode): VmCodeChunk {
|
|
||||||
val leftReg = vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
|
||||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
|
||||||
// if and else parts
|
|
||||||
val elseLabel = createLabelName()
|
|
||||||
val afterIfLabel = createLabelName()
|
|
||||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, labelSymbol = elseLabel)
|
|
||||||
code += translateNode(ifElse.ifScope)
|
|
||||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = afterIfLabel)
|
|
||||||
code += VmCodeLabel(elseLabel)
|
|
||||||
code += translateNode(ifElse.elseScope)
|
|
||||||
code += VmCodeLabel(afterIfLabel)
|
|
||||||
} else {
|
|
||||||
// only if part
|
|
||||||
val afterIfLabel = createLabelName()
|
|
||||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, labelSymbol = afterIfLabel)
|
|
||||||
code += translateNode(ifElse.ifScope)
|
|
||||||
code += VmCodeLabel(afterIfLabel)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (ifElse.condition.operator) {
|
|
||||||
"==" -> {
|
|
||||||
// if X==0 ... so we just branch on left expr is Not-zero.
|
|
||||||
equalOrNotEqualZero(Opcode.BNZ)
|
|
||||||
}
|
|
||||||
"!=" -> {
|
|
||||||
// if X!=0 ... so we just branch on left expr is Zero.
|
|
||||||
equalOrNotEqualZero(Opcode.BZ)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// another comparison against 0, just use regular codegen for this.
|
|
||||||
translateNonZeroComparison()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return if(constValue(ifElse.condition.right)==0.0)
|
|
||||||
translateZeroComparison()
|
|
||||||
else
|
|
||||||
translateNonZeroComparison()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun translate(postIncrDecr: PtPostIncrDecr): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val operationMem: Opcode
|
|
||||||
val operationRegister: Opcode
|
|
||||||
when(postIncrDecr.operator) {
|
|
||||||
"++" -> {
|
|
||||||
operationMem = Opcode.INCM
|
|
||||||
operationRegister = Opcode.INC
|
|
||||||
}
|
|
||||||
"--" -> {
|
|
||||||
operationMem = Opcode.DECM
|
|
||||||
operationRegister = Opcode.DEC
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird operator")
|
|
||||||
}
|
|
||||||
val ident = postIncrDecr.target.identifier
|
|
||||||
val memory = postIncrDecr.target.memory
|
|
||||||
val array = postIncrDecr.target.array
|
|
||||||
val vmDt = vmType(postIncrDecr.target.type)
|
|
||||||
if(ident!=null) {
|
|
||||||
val address = allocations.get(ident.targetName)
|
|
||||||
code += VmCodeInstruction(operationMem, vmDt, value = address)
|
|
||||||
} else if(memory!=null) {
|
|
||||||
if(memory.address is PtNumber) {
|
|
||||||
val address = (memory.address as PtNumber).number.toInt()
|
|
||||||
code += VmCodeInstruction(operationMem, vmDt, value = address)
|
|
||||||
} else {
|
|
||||||
val incReg = vmRegisters.nextFree()
|
|
||||||
val addressReg = vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADI, vmDt, reg1 = incReg, reg2 = addressReg)
|
|
||||||
code += VmCodeInstruction(operationRegister, vmDt, reg1 = incReg)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1 = incReg, reg2 = addressReg)
|
|
||||||
}
|
|
||||||
} else if (array!=null) {
|
|
||||||
val variable = array.variable.targetName
|
|
||||||
var variableAddr = allocations.get(variable)
|
|
||||||
val itemsize = program.memsizer.memorySize(array.type)
|
|
||||||
val fixedIndex = constIntValue(array.index)
|
|
||||||
if(fixedIndex!=null) {
|
|
||||||
variableAddr += fixedIndex*itemsize
|
|
||||||
code += VmCodeInstruction(operationMem, vmDt, value=variableAddr)
|
|
||||||
} else {
|
|
||||||
val incReg = vmRegisters.nextFree()
|
|
||||||
val indexReg = vmRegisters.nextFree()
|
|
||||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=incReg, reg2=indexReg, value=variableAddr)
|
|
||||||
code += VmCodeInstruction(operationRegister, vmDt, reg1=incReg)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1=incReg, reg2=indexReg, value=variableAddr)
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
throw AssemblyError("weird assigntarget")
|
|
||||||
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(repeat: PtRepeatLoop): VmCodeChunk {
|
|
||||||
when (constIntValue(repeat.count)) {
|
|
||||||
0 -> return VmCodeChunk()
|
|
||||||
1 -> return translateGroup(repeat.children)
|
|
||||||
256 -> {
|
|
||||||
// 256 iterations can still be done with just a byte counter if you set it to zero as starting value.
|
|
||||||
repeat.children[0] = PtNumber(DataType.UBYTE, 0.0, repeat.count.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val counterReg = vmRegisters.nextFree()
|
|
||||||
val vmDt = vmType(repeat.count.type)
|
|
||||||
code += expressionEval.translateExpression(repeat.count, counterReg, -1)
|
|
||||||
val repeatLabel = createLabelName()
|
|
||||||
code += VmCodeLabel(repeatLabel)
|
|
||||||
code += translateNode(repeat.statements)
|
|
||||||
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=counterReg)
|
|
||||||
code += VmCodeInstruction(Opcode.BNZ, vmDt, reg1=counterReg, labelSymbol = repeatLabel)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(jump: PtJump): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(jump.address!=null)
|
|
||||||
throw AssemblyError("cannot jump to memory location in the vm target")
|
|
||||||
code += if(jump.generatedLabel!=null)
|
|
||||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf(jump.generatedLabel!!))
|
|
||||||
else if(jump.identifier!=null)
|
|
||||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.targetName)
|
|
||||||
else
|
|
||||||
throw AssemblyError("weird jump")
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateGroup(group: List<PtNode>): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
group.forEach { code += translateNode(it) }
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(ret: PtReturn): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val value = ret.value
|
|
||||||
if(value!=null) {
|
|
||||||
// Call Convention: return value is always returned in r0 (or fr0 if float)
|
|
||||||
code += if(value.type==DataType.FLOAT)
|
|
||||||
expressionEval.translateExpression(value, -1, 0)
|
|
||||||
else
|
|
||||||
expressionEval.translateExpression(value, 0, -1)
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(Opcode.RETURN)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(sub: PtSub): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += VmCodeComment("SUB: ${sub.scopedName} -> ${sub.returntype}")
|
|
||||||
code += VmCodeLabel(sub.scopedName)
|
|
||||||
for (child in sub.children) {
|
|
||||||
code += translateNode(child)
|
|
||||||
}
|
|
||||||
code += VmCodeComment("SUB-END '${sub.name}'")
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(block: PtBlock): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += VmCodeComment("BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
|
|
||||||
for (child in block.children) {
|
|
||||||
if(child !is PtAssignment) // global variable initialization is done elsewhere
|
|
||||||
code += translateNode(child)
|
|
||||||
}
|
|
||||||
code += VmCodeComment("BLOCK-END '${block.name}'")
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun vmType(type: DataType): VmDataType {
|
|
||||||
return when(type) {
|
|
||||||
DataType.BOOL,
|
|
||||||
DataType.UBYTE,
|
|
||||||
DataType.BYTE -> VmDataType.BYTE
|
|
||||||
DataType.UWORD,
|
|
||||||
DataType.WORD -> VmDataType.WORD
|
|
||||||
DataType.FLOAT -> VmDataType.FLOAT
|
|
||||||
in PassByReferenceDatatypes -> VmDataType.WORD
|
|
||||||
else -> throw AssemblyError("no vm datatype for $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var labelSequenceNumber = 0
|
|
||||||
internal fun createLabelName(): List<String> {
|
|
||||||
labelSequenceNumber++
|
|
||||||
return listOf("prog8_label_gen_$labelSequenceNumber")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk =
|
|
||||||
builtinFuncGen.translate(call, resultRegister)
|
|
||||||
|
|
||||||
internal fun isZero(expression: PtExpression): Boolean = expression is PtNumber && expression.number==0.0
|
|
||||||
|
|
||||||
internal fun isOne(expression: PtExpression): Boolean = expression is PtNumber && expression.number==1.0
|
|
||||||
}
|
|
||||||
|
|
@ -1,856 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.code.StStaticVariable
|
|
||||||
import prog8.code.StSub
|
|
||||||
import prog8.code.ast.*
|
|
||||||
import prog8.code.core.*
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
|
|
||||||
|
|
||||||
internal class ExpressionGen(private val codeGen: CodeGen) {
|
|
||||||
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
require(codeGen.vmRegisters.peekNext() > resultRegister)
|
|
||||||
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
|
|
||||||
when (expr) {
|
|
||||||
is PtMachineRegister -> {
|
|
||||||
if(resultRegister!=expr.register) {
|
|
||||||
val vmDt = codeGen.vmType(expr.type)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, vmDt, reg1=resultRegister, reg2=expr.register)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is PtNumber -> {
|
|
||||||
val vmDt = codeGen.vmType(expr.type)
|
|
||||||
code += if(vmDt==VmDataType.FLOAT)
|
|
||||||
VmCodeInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, fpValue = expr.number.toFloat())
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=expr.number.toInt())
|
|
||||||
}
|
|
||||||
is PtIdentifier -> {
|
|
||||||
val vmDt = codeGen.vmType(expr.type)
|
|
||||||
val mem = codeGen.allocations.get(expr.targetName)
|
|
||||||
code += if (expr.type in PassByValueDatatypes) {
|
|
||||||
if(vmDt==VmDataType.FLOAT)
|
|
||||||
VmCodeInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, value = mem)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, value = mem)
|
|
||||||
} else {
|
|
||||||
// for strings and arrays etc., load the *address* of the value instead
|
|
||||||
VmCodeInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, value = mem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is PtAddressOf -> {
|
|
||||||
val vmDt = codeGen.vmType(expr.type)
|
|
||||||
val mem = codeGen.allocations.get(expr.identifier.targetName)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=mem)
|
|
||||||
}
|
|
||||||
is PtMemoryByte -> {
|
|
||||||
if(expr.address is PtNumber) {
|
|
||||||
val address = (expr.address as PtNumber).number.toInt()
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1=resultRegister, value = address)
|
|
||||||
} else {
|
|
||||||
val addressRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(expr.address, addressRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1=resultRegister, reg2=addressRegister)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is PtTypeCast -> code += translate(expr, resultRegister, resultFpRegister)
|
|
||||||
is PtPrefix -> code += translate(expr, resultRegister)
|
|
||||||
is PtArrayIndexer -> code += translate(expr, resultRegister, resultFpRegister)
|
|
||||||
is PtBinaryExpression -> code += translate(expr, resultRegister, resultFpRegister)
|
|
||||||
is PtBuiltinFunctionCall -> code += codeGen.translateBuiltinFunc(expr, resultRegister)
|
|
||||||
is PtFunctionCall -> code += translate(expr, resultRegister, resultFpRegister)
|
|
||||||
is PtContainmentCheck -> code += translate(expr, resultRegister, resultFpRegister)
|
|
||||||
is PtRange,
|
|
||||||
is PtArray,
|
|
||||||
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
|
|
||||||
else -> throw AssemblyError("weird expression")
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(check: PtContainmentCheck, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += translateExpression(check.element, resultRegister, -1) // load the element to check in resultRegister
|
|
||||||
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.targetName) as StStaticVariable
|
|
||||||
when(iterable.dt) {
|
|
||||||
DataType.STR -> {
|
|
||||||
val call = PtFunctionCall(listOf("prog8_lib", "string_contains"), false, DataType.UBYTE, check.position)
|
|
||||||
call.children.add(check.element)
|
|
||||||
call.children.add(check.iterable)
|
|
||||||
code += translate(call, resultRegister, resultFpRegister)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
val call = PtFunctionCall(listOf("prog8_lib", "bytearray_contains"), false, DataType.UBYTE, check.position)
|
|
||||||
call.children.add(check.element)
|
|
||||||
call.children.add(check.iterable)
|
|
||||||
call.children.add(PtNumber(DataType.UBYTE, iterable.length!!.toDouble(), iterable.position))
|
|
||||||
code += translate(call, resultRegister, resultFpRegister)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
val call = PtFunctionCall(listOf("prog8_lib", "wordarray_contains"), false, DataType.UBYTE, check.position)
|
|
||||||
call.children.add(check.element)
|
|
||||||
call.children.add(check.iterable)
|
|
||||||
call.children.add(PtNumber(DataType.UBYTE, iterable.length!!.toDouble(), iterable.position))
|
|
||||||
code += translate(call, resultRegister, resultFpRegister)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
|
|
||||||
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.targetName}")
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(arrayIx: PtArrayIndexer, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type)
|
|
||||||
val vmDt = codeGen.vmType(arrayIx.type)
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val idxReg = codeGen.vmRegisters.nextFree()
|
|
||||||
val arrayLocation = codeGen.allocations.get(arrayIx.variable.targetName)
|
|
||||||
|
|
||||||
if(arrayIx.variable.type==DataType.UWORD) {
|
|
||||||
// indexing a pointer var instead of a real array or string
|
|
||||||
if(eltSize!=1)
|
|
||||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
|
||||||
if(arrayIx.index.type!=DataType.UBYTE)
|
|
||||||
throw AssemblyError("non-array var indexing requires bytes index")
|
|
||||||
code += translateExpression(arrayIx.index, idxReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADIX, vmDt, reg1=resultRegister, reg2=idxReg, value = arrayLocation)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
if(arrayIx.index is PtNumber) {
|
|
||||||
// optimized code when index is known - just calculate the memory address here
|
|
||||||
val memOffset = (arrayIx.index as PtNumber).number.toInt() * eltSize
|
|
||||||
if(vmDt==VmDataType.FLOAT)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.FLOAT, fpReg1=resultFpRegister, value=arrayLocation+memOffset)
|
|
||||||
else
|
|
||||||
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=arrayLocation+memOffset)
|
|
||||||
} else {
|
|
||||||
code += translateExpression(arrayIx.index, idxReg, -1)
|
|
||||||
if(eltSize>1)
|
|
||||||
code += codeGen.multiplyByConst(VmDataType.BYTE, idxReg, eltSize)
|
|
||||||
if(vmDt==VmDataType.FLOAT)
|
|
||||||
code += VmCodeInstruction(Opcode.LOADX, VmDataType.FLOAT, fpReg1 = resultFpRegister, reg1=idxReg, value = arrayLocation)
|
|
||||||
else
|
|
||||||
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=idxReg, value = arrayLocation)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(expr: PtPrefix, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
code += translateExpression(expr.value, resultRegister, -1)
|
|
||||||
val vmDt = codeGen.vmType(expr.type)
|
|
||||||
when(expr.operator) {
|
|
||||||
"+" -> { }
|
|
||||||
"-" -> {
|
|
||||||
code += VmCodeInstruction(Opcode.NEG, vmDt, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
"~" -> {
|
|
||||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
|
||||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, value=mask)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird prefix operator")
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(cast: PtTypeCast, predefinedResultRegister: Int, predefinedResultFpRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(cast.type==cast.value.type)
|
|
||||||
return code
|
|
||||||
val actualResultFpReg = if(predefinedResultFpRegister>=0) predefinedResultFpRegister else codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
val actualResultReg = if(predefinedResultRegister>=0) predefinedResultRegister else codeGen.vmRegisters.nextFree()
|
|
||||||
if(cast.value.type==DataType.FLOAT) {
|
|
||||||
// a cast from float to integer, so evaluate the value into a float register first
|
|
||||||
code += translateExpression(cast.value, -1, actualResultFpReg)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
code += translateExpression(cast.value, actualResultReg, -1)
|
|
||||||
when(cast.type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when(cast.value.type) {
|
|
||||||
DataType.BYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
|
|
||||||
DataType.FLOAT -> code += VmCodeInstruction(Opcode.FTOUB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
else -> throw AssemblyError("weird cast value type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
when(cast.value.type) {
|
|
||||||
DataType.UBYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
|
|
||||||
DataType.FLOAT -> code += VmCodeInstruction(Opcode.FTOSB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
else -> throw AssemblyError("weird cast value type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when(cast.value.type) {
|
|
||||||
DataType.BYTE -> {
|
|
||||||
// byte -> uword: sign extend
|
|
||||||
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = actualResultReg)
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
// ubyte -> uword: sign extend
|
|
||||||
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = actualResultReg)
|
|
||||||
}
|
|
||||||
DataType.WORD -> { }
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
code += VmCodeInstruction(Opcode.FTOUW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird cast value type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
when(cast.value.type) {
|
|
||||||
DataType.BYTE -> {
|
|
||||||
// byte -> word: sign extend
|
|
||||||
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = actualResultReg)
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
// byte -> word: sign extend
|
|
||||||
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = actualResultReg)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> { }
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
code += VmCodeInstruction(Opcode.FTOSW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird cast value type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
code += when(cast.value.type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
VmCodeInstruction(Opcode.FFROMUB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
VmCodeInstruction(Opcode.FFROMSB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
VmCodeInstruction(Opcode.FFROMUW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
VmCodeInstruction(Opcode.FFROMSW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird cast value type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird cast type")
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translate(binExpr: PtBinaryExpression, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val vmDt = codeGen.vmType(binExpr.left.type)
|
|
||||||
val signed = binExpr.left.type in SignedDatatypes
|
|
||||||
return when(binExpr.operator) {
|
|
||||||
"+" -> operatorPlus(binExpr, vmDt, resultRegister, resultFpRegister)
|
|
||||||
"-" -> operatorMinus(binExpr, vmDt, resultRegister, resultFpRegister)
|
|
||||||
"*" -> operatorMultiply(binExpr, vmDt, resultRegister, resultFpRegister)
|
|
||||||
"/" -> operatorDivide(binExpr, vmDt, resultRegister, resultFpRegister, signed)
|
|
||||||
"%" -> operatorModulo(binExpr, vmDt, resultRegister)
|
|
||||||
"|" -> operatorOr(binExpr, vmDt, resultRegister)
|
|
||||||
"&" -> operatorAnd(binExpr, vmDt, resultRegister)
|
|
||||||
"^" -> operatorXor(binExpr, vmDt, resultRegister)
|
|
||||||
"<<" -> operatorShiftLeft(binExpr, vmDt, resultRegister)
|
|
||||||
">>" -> operatorShiftRight(binExpr, vmDt, resultRegister, signed)
|
|
||||||
"==" -> operatorEquals(binExpr, vmDt, resultRegister, false)
|
|
||||||
"!=" -> operatorEquals(binExpr, vmDt, resultRegister, true)
|
|
||||||
"<" -> operatorLessThan(binExpr, vmDt, resultRegister, signed, false)
|
|
||||||
">" -> operatorGreaterThan(binExpr, vmDt, resultRegister, signed, false)
|
|
||||||
"<=" -> operatorLessThan(binExpr, vmDt, resultRegister, signed, true)
|
|
||||||
">=" -> operatorGreaterThan(binExpr, vmDt, resultRegister, signed, true)
|
|
||||||
else -> throw AssemblyError("weird operator ${binExpr.operator}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorGreaterThan(
|
|
||||||
binExpr: PtBinaryExpression,
|
|
||||||
vmDt: VmDataType,
|
|
||||||
resultRegister: Int,
|
|
||||||
signed: Boolean,
|
|
||||||
greaterEquals: Boolean
|
|
||||||
): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, -1, leftFpReg)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
|
||||||
val ins = if (signed) {
|
|
||||||
if (greaterEquals) Opcode.SGES else Opcode.SGTS
|
|
||||||
} else {
|
|
||||||
if (greaterEquals) Opcode.SGE else Opcode.SGT
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
|
|
||||||
} else {
|
|
||||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
|
||||||
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
|
|
||||||
comparisonCall.children.add(binExpr.left)
|
|
||||||
comparisonCall.children.add(binExpr.right)
|
|
||||||
code += translate(comparisonCall, resultRegister, -1)
|
|
||||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
|
||||||
code += if(greaterEquals)
|
|
||||||
VmCodeInstruction(Opcode.SGES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.SGTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
val ins = if (signed) {
|
|
||||||
if (greaterEquals) Opcode.SGES else Opcode.SGTS
|
|
||||||
} else {
|
|
||||||
if (greaterEquals) Opcode.SGE else Opcode.SGT
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorLessThan(
|
|
||||||
binExpr: PtBinaryExpression,
|
|
||||||
vmDt: VmDataType,
|
|
||||||
resultRegister: Int,
|
|
||||||
signed: Boolean,
|
|
||||||
lessEquals: Boolean
|
|
||||||
): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, -1, leftFpReg)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
|
||||||
val ins = if (signed) {
|
|
||||||
if (lessEquals) Opcode.SLES else Opcode.SLTS
|
|
||||||
} else {
|
|
||||||
if (lessEquals) Opcode.SLE else Opcode.SLT
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
|
|
||||||
} else {
|
|
||||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
|
||||||
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
|
|
||||||
comparisonCall.children.add(binExpr.left)
|
|
||||||
comparisonCall.children.add(binExpr.right)
|
|
||||||
code += translate(comparisonCall, resultRegister, -1)
|
|
||||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
|
||||||
code += if(lessEquals)
|
|
||||||
VmCodeInstruction(Opcode.SLES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.SLTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
val ins = if (signed) {
|
|
||||||
if (lessEquals) Opcode.SLES else Opcode.SLTS
|
|
||||||
} else {
|
|
||||||
if (lessEquals) Opcode.SLE else Opcode.SLT
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorEquals(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, notEquals: Boolean): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(binExpr.left, -1, leftFpReg)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightFpReg)
|
|
||||||
if (notEquals) {
|
|
||||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
|
||||||
} else {
|
|
||||||
val label = codeGen.createLabelName()
|
|
||||||
val valueReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=1)
|
|
||||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=valueReg, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=valueReg, labelSymbol = label)
|
|
||||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=0)
|
|
||||||
code += VmCodeLabel(label)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
|
||||||
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
|
|
||||||
comparisonCall.children.add(binExpr.left)
|
|
||||||
comparisonCall.children.add(binExpr.right)
|
|
||||||
code += translate(comparisonCall, resultRegister, -1)
|
|
||||||
if(!notEquals)
|
|
||||||
code += VmCodeInstruction(Opcode.INV, vmDt, reg1=resultRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, value=1)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
|
|
||||||
code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorShiftRight(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, signed: Boolean): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(codeGen.isOne(binExpr.right)) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
val opc = if (signed) Opcode.ASR else Opcode.LSR
|
|
||||||
code += VmCodeInstruction(opc, vmDt, reg1 = resultRegister)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
val opc = if (signed) Opcode.ASRN else Opcode.LSRN
|
|
||||||
code += VmCodeInstruction(opc, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorShiftRightInplace(address: Int, vmDt: VmDataType, signed: Boolean, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(codeGen.isOne(operand)) {
|
|
||||||
val opc = if (signed) Opcode.ASRM else Opcode.LSRM
|
|
||||||
code += VmCodeInstruction(opc, vmDt, value=address)
|
|
||||||
} else {
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
val opc = if (signed) Opcode.ASRNM else Opcode.LSRNM
|
|
||||||
code += VmCodeInstruction(opc, vmDt, reg1 = operandReg, value=address)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorShiftLeft(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(codeGen.isOne(binExpr.right)){
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LSL, vmDt, reg1=resultRegister)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LSLN, vmDt, reg1=resultRegister, rightResultReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorShiftLeftInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(codeGen.isOne(operand)){
|
|
||||||
code += VmCodeInstruction(Opcode.LSLM, vmDt, value=address)
|
|
||||||
} else {
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.LSLNM, vmDt, reg1=operandReg, value=address)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorXor(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.XORR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorXorInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=operandReg, value = address)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ANDR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorAndInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ANDM, vmDt, reg1=operandReg, value=address)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.OR, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ORR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorOrInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ORM, vmDt, reg1=operandReg, value = address)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
|
||||||
if(vmDt==VmDataType.FLOAT)
|
|
||||||
throw IllegalArgumentException("floating-point modulo not supported")
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.MOD, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.MODR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorDivide(binExpr: PtBinaryExpression,
|
|
||||||
vmDt: VmDataType,
|
|
||||||
resultRegister: Int,
|
|
||||||
resultFpRegister: Int,
|
|
||||||
signed: Boolean): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val constFactorRight = binExpr.right as? PtNumber
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
val factor = constFactorRight.number.toFloat()
|
|
||||||
code += codeGen.divideByConstFloat(resultFpRegister, factor)
|
|
||||||
} else {
|
|
||||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVSR, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIVR, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
val factor = constFactorRight.number.toInt()
|
|
||||||
code += codeGen.divideByConst(vmDt, resultRegister, factor, signed)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += if (signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVS, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIV, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += if (signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVSR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIVR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorDivideInplace(address: Int, vmDt: VmDataType, signed: Boolean, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val constFactorRight = operand as? PtNumber
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
|
||||||
val factor = constFactorRight.number.toFloat()
|
|
||||||
code += codeGen.divideByConstFloatInplace(address, factor)
|
|
||||||
} else {
|
|
||||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(operand, -1, operandFpReg)
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVSM, vmDt, fpReg1 = operandFpReg, value=address)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIVM, vmDt, fpReg1 = operandFpReg, value=address)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
|
||||||
val factor = constFactorRight.number.toInt()
|
|
||||||
code += codeGen.divideByConstInplace(vmDt, address, factor, signed)
|
|
||||||
} else {
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += if(signed)
|
|
||||||
VmCodeInstruction(Opcode.DIVSM, vmDt, reg1=operandReg, value = address)
|
|
||||||
else
|
|
||||||
VmCodeInstruction(Opcode.DIVM, vmDt, reg1=operandReg, value = address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val constFactorLeft = binExpr.left as? PtNumber
|
|
||||||
val constFactorRight = binExpr.right as? PtNumber
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if(constFactorLeft!=null) {
|
|
||||||
code += translateExpression(binExpr.right, -1, resultFpRegister)
|
|
||||||
val factor = constFactorLeft.number.toFloat()
|
|
||||||
code += codeGen.multiplyByConstFloat(resultFpRegister, factor)
|
|
||||||
} else if(constFactorRight!=null) {
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
val factor = constFactorRight.number.toFloat()
|
|
||||||
code += codeGen.multiplyByConstFloat(resultFpRegister, factor)
|
|
||||||
} else {
|
|
||||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.MULR, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(constFactorLeft!=null && constFactorLeft.type!=DataType.FLOAT) {
|
|
||||||
code += translateExpression(binExpr.right, resultRegister, -1)
|
|
||||||
val factor = constFactorLeft.number.toInt()
|
|
||||||
code += codeGen.multiplyByConst(vmDt, resultRegister, factor)
|
|
||||||
} else if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
val factor = constFactorRight.number.toInt()
|
|
||||||
code += codeGen.multiplyByConst(vmDt, resultRegister, factor)
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.MULR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorMultiplyInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
val constFactorRight = operand as? PtNumber
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if(constFactorRight!=null) {
|
|
||||||
val factor = constFactorRight.number.toFloat()
|
|
||||||
code += codeGen.multiplyByConstFloatInplace(address, factor)
|
|
||||||
} else {
|
|
||||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(operand, -1, operandFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.MULM, vmDt, fpReg1 = operandFpReg, value = address)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
|
||||||
val factor = constFactorRight.number.toInt()
|
|
||||||
code += codeGen.multiplyByConstInplace(vmDt, address, factor)
|
|
||||||
} else {
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.MULM, vmDt, reg1=operandReg, value = address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorMinus(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if((binExpr.right as? PtNumber)?.number==1.0) {
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.DEC, vmDt, fpReg1 = resultFpRegister)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.SUB, vmDt, fpReg1 = resultFpRegister, fpValue = (binExpr.right as PtNumber).number.toFloat())
|
|
||||||
} else {
|
|
||||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.SUBR, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if((binExpr.right as? PtNumber)?.number==1.0) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.SUB, vmDt, reg1 = resultRegister, value = (binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.SUBR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorMinusInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if((operand as? PtNumber)?.number==1.0) {
|
|
||||||
code += VmCodeInstruction(Opcode.DECM, vmDt, value=address)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(operand, -1, operandFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.SUBM, vmDt, fpReg1=operandFpReg, value=address)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if((operand as? PtNumber)?.number==1.0) {
|
|
||||||
code += VmCodeInstruction(Opcode.DECM, vmDt, value=address)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.SUBM, vmDt, reg1=operandReg, value = address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun operatorPlus(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if((binExpr.left as? PtNumber)?.number==1.0) {
|
|
||||||
code += translateExpression(binExpr.right, -1, resultFpRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
|
|
||||||
}
|
|
||||||
else if((binExpr.right as? PtNumber)?.number==1.0) {
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += VmCodeInstruction(Opcode.ADD, vmDt, fpReg1 = resultFpRegister, fpValue = (binExpr.right as PtNumber).number.toFloat())
|
|
||||||
} else {
|
|
||||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
|
||||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.ADDR, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if((binExpr.left as? PtNumber)?.number==1.0) {
|
|
||||||
code += translateExpression(binExpr.right, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
else if((binExpr.right as? PtNumber)?.number==1.0) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(binExpr.right is PtNumber) {
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ADD, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
|
||||||
} else {
|
|
||||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
|
||||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ADDR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun operatorPlusInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
if(vmDt==VmDataType.FLOAT) {
|
|
||||||
if((operand as? PtNumber)?.number==1.0) {
|
|
||||||
code += VmCodeInstruction(Opcode.INCM, vmDt, value = address)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(operand, -1, operandFpReg)
|
|
||||||
code += VmCodeInstruction(Opcode.ADDM, vmDt, fpReg1=operandFpReg, value=address)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if((operand as? PtNumber)?.number==1.0) {
|
|
||||||
code += VmCodeInstruction(Opcode.INCM, vmDt, value = address)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val operandReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(operand, operandReg, -1)
|
|
||||||
code += VmCodeInstruction(Opcode.ADDM, vmDt, reg1=operandReg, value=address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translate(fcall: PtFunctionCall, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
|
||||||
val subroutine = codeGen.symbolTable.flat.getValue(fcall.functionName) as StSub
|
|
||||||
val code = VmCodeChunk()
|
|
||||||
for ((arg, parameter) in fcall.args.zip(subroutine.parameters)) {
|
|
||||||
val paramDt = codeGen.vmType(parameter.type)
|
|
||||||
if(codeGen.isZero(arg)) {
|
|
||||||
if (paramDt == VmDataType.FLOAT) {
|
|
||||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
|
||||||
} else {
|
|
||||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (paramDt == VmDataType.FLOAT) {
|
|
||||||
val argFpReg = codeGen.vmRegisters.nextFreeFloat()
|
|
||||||
code += translateExpression(arg, -1, argFpReg)
|
|
||||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, fpReg1 = argFpReg, value = mem)
|
|
||||||
} else {
|
|
||||||
val argReg = codeGen.vmRegisters.nextFree()
|
|
||||||
code += translateExpression(arg, argReg, -1)
|
|
||||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
|
||||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, reg1 = argReg, value = mem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code += VmCodeInstruction(Opcode.CALL, labelSymbol=fcall.functionName)
|
|
||||||
if(fcall.type==DataType.FLOAT) {
|
|
||||||
if (!fcall.void && resultFpRegister != 0) {
|
|
||||||
// Call convention: result value is in fr0, so put it in the required register instead.
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.FLOAT, fpReg1 = resultFpRegister, fpReg2 = 0)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!fcall.void && resultRegister != 0) {
|
|
||||||
// Call convention: result value is in r0, so put it in the required register instead.
|
|
||||||
code += VmCodeInstruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1 = resultRegister, reg2 = 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.code.SymbolTable
|
|
||||||
import prog8.code.ast.PtProgram
|
|
||||||
import prog8.code.core.*
|
|
||||||
|
|
||||||
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram) {
|
|
||||||
|
|
||||||
private val allocations = mutableMapOf<List<String>, Int>()
|
|
||||||
private var freeMemoryStart: Int
|
|
||||||
|
|
||||||
val freeMem: Int
|
|
||||||
get() = freeMemoryStart
|
|
||||||
|
|
||||||
init {
|
|
||||||
var nextLocation = 0
|
|
||||||
for (variable in st.allVariables) {
|
|
||||||
val memsize =
|
|
||||||
when (variable.dt) {
|
|
||||||
DataType.STR -> variable.initialStringValue!!.first.length + 1 // include the zero byte
|
|
||||||
in NumericDatatypes -> program.memsizer.memorySize(variable.dt)
|
|
||||||
in ArrayDatatypes -> program.memsizer.memorySize(variable.dt, variable.length!!)
|
|
||||||
else -> throw InternalCompilerException("weird dt")
|
|
||||||
}
|
|
||||||
|
|
||||||
allocations[variable.scopedName] = nextLocation
|
|
||||||
nextLocation += memsize
|
|
||||||
}
|
|
||||||
for (memvar in st.allMemMappedVariables) {
|
|
||||||
// TODO virtual machine doesn't have memory mapped variables, so treat them as regular allocated variables for now
|
|
||||||
val memsize =
|
|
||||||
when (memvar.dt) {
|
|
||||||
in NumericDatatypes -> program.memsizer.memorySize(memvar.dt)
|
|
||||||
in ArrayDatatypes -> program.memsizer.memorySize(memvar.dt, memvar.length!!)
|
|
||||||
else -> throw InternalCompilerException("weird dt")
|
|
||||||
}
|
|
||||||
|
|
||||||
allocations[memvar.scopedName] = nextLocation
|
|
||||||
nextLocation += memsize
|
|
||||||
}
|
|
||||||
|
|
||||||
freeMemoryStart = nextLocation
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(name: List<String>) = allocations.getValue(name)
|
|
||||||
|
|
||||||
fun asVmMemory(): List<Pair<List<String>, String>> {
|
|
||||||
val mm = mutableListOf<Pair<List<String>, String>>()
|
|
||||||
for (variable in st.allVariables) {
|
|
||||||
val location = allocations.getValue(variable.scopedName)
|
|
||||||
val typeStr = when(variable.dt) {
|
|
||||||
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
|
|
||||||
DataType.BYTE, DataType.ARRAY_B -> "byte"
|
|
||||||
DataType.UWORD, DataType.ARRAY_UW -> "uword"
|
|
||||||
DataType.WORD, DataType.ARRAY_W -> "word"
|
|
||||||
DataType.FLOAT, DataType.ARRAY_F -> "float"
|
|
||||||
else -> throw InternalCompilerException("weird dt")
|
|
||||||
}
|
|
||||||
val value = when(variable.dt) {
|
|
||||||
DataType.FLOAT -> (variable.initialNumericValue ?: 0.0).toString()
|
|
||||||
in NumericDatatypes -> (variable.initialNumericValue ?: 0).toHex()
|
|
||||||
DataType.STR -> {
|
|
||||||
val encoded = program.encoding.encodeString(variable.initialStringValue!!.first, variable.initialStringValue!!.second)
|
|
||||||
encoded.joinToString(",") { it.toInt().toHex() } + ",0"
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
if(variable.initialArrayValue!=null) {
|
|
||||||
variable.initialArrayValue!!.joinToString(",") { it.number!!.toString() }
|
|
||||||
} else {
|
|
||||||
(1..variable.length!!).joinToString(",") { "0" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in ArrayDatatypes -> {
|
|
||||||
if(variable.initialArrayValue!==null) {
|
|
||||||
variable.initialArrayValue!!.joinToString(",") { it.number!!.toHex() }
|
|
||||||
} else {
|
|
||||||
(1..variable.length!!).joinToString(",") { "0" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw InternalCompilerException("weird dt")
|
|
||||||
}
|
|
||||||
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
|
|
||||||
}
|
|
||||||
return mm
|
|
||||||
}
|
|
||||||
|
|
||||||
private val memorySlabsInternal = mutableMapOf<String, Triple<UInt, UInt, UInt>>()
|
|
||||||
internal val memorySlabs: Map<String, Triple<UInt, UInt, UInt>> = memorySlabsInternal
|
|
||||||
|
|
||||||
fun allocateMemorySlab(name: String, size: UInt, align: UInt): UInt {
|
|
||||||
val address =
|
|
||||||
if(align==0u || align==1u)
|
|
||||||
freeMemoryStart.toUInt()
|
|
||||||
else
|
|
||||||
(freeMemoryStart.toUInt() + align-1u) and (0xffffffffu xor (align-1u))
|
|
||||||
|
|
||||||
memorySlabsInternal[name] = Triple(address, size, align)
|
|
||||||
freeMemoryStart = (address + size).toInt()
|
|
||||||
return address
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMemorySlab(name: String): Triple<UInt, UInt, UInt>? = memorySlabsInternal[name]
|
|
||||||
}
|
|
@ -1,189 +0,0 @@
|
|||||||
package prog8.codegen.virtual
|
|
||||||
|
|
||||||
import prog8.vm.Instruction
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
|
|
||||||
internal class VmOptimizerException(msg: String): Exception(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private val allocations: VariableAllocator) {
|
|
||||||
fun optimize() {
|
|
||||||
vmprog.getBlocks().forEach { block ->
|
|
||||||
do {
|
|
||||||
val indexedInstructions = block.lines.withIndex()
|
|
||||||
.filter { it.value is VmCodeInstruction }
|
|
||||||
.map { IndexedValue(it.index, (it.value as VmCodeInstruction).ins) }
|
|
||||||
val changed = removeNops(block, indexedInstructions)
|
|
||||||
|| removeDoubleLoadsAndStores(block, indexedInstructions) // TODO not yet implemented
|
|
||||||
|| removeUselessArithmetic(block, indexedInstructions)
|
|
||||||
|| removeWeirdBranches(block, indexedInstructions)
|
|
||||||
|| removeDoubleSecClc(block, indexedInstructions)
|
|
||||||
|| cleanupPushPop(block, indexedInstructions)
|
|
||||||
// TODO other optimizations:
|
|
||||||
// more complex optimizations such as unused registers
|
|
||||||
} while(changed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cleanupPushPop(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
|
||||||
// push followed by pop to same target, or different target->replace with load
|
|
||||||
var changed = false
|
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
||||||
if(ins.opcode==Opcode.PUSH) {
|
|
||||||
if(idx < block.lines.size-1) {
|
|
||||||
val insAfter = block.lines[idx+1] as? VmCodeInstruction
|
|
||||||
if(insAfter!=null && insAfter.ins.opcode ==Opcode.POP) {
|
|
||||||
if(ins.reg1==insAfter.ins.reg1) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
} else {
|
|
||||||
block.lines[idx] = VmCodeInstruction(Opcode.LOADR, ins.type, reg1=insAfter.ins.reg1, reg2=ins.reg1)
|
|
||||||
block.lines.removeAt(idx+1)
|
|
||||||
}
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun removeDoubleSecClc(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
|
||||||
// double sec, clc
|
|
||||||
// sec+clc or clc+sec
|
|
||||||
var changed = false
|
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
||||||
if(ins.opcode==Opcode.SEC || ins.opcode==Opcode.CLC) {
|
|
||||||
if(idx < block.lines.size-1) {
|
|
||||||
val insAfter = block.lines[idx+1] as? VmCodeInstruction
|
|
||||||
if(insAfter?.ins?.opcode == ins.opcode) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
else if(ins.opcode==Opcode.SEC && insAfter?.ins?.opcode==Opcode.CLC) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
else if(ins.opcode==Opcode.CLC && insAfter?.ins?.opcode==Opcode.SEC) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeWeirdBranches(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
|
||||||
// jump/branch to label immediately below
|
|
||||||
var changed = false
|
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
||||||
if(ins.opcode==Opcode.JUMP && ins.labelSymbol!=null) {
|
|
||||||
// if jumping to label immediately following this
|
|
||||||
if(idx < block.lines.size-1) {
|
|
||||||
val label = block.lines[idx+1] as? VmCodeLabel
|
|
||||||
if(label?.name == ins.labelSymbol) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeUselessArithmetic(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
|
||||||
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
|
|
||||||
var changed = false
|
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
||||||
when (ins.opcode) {
|
|
||||||
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
|
||||||
if (ins.value == 1) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.ADD, Opcode.SUB -> {
|
|
||||||
if (ins.value == 1) {
|
|
||||||
block.lines[idx] = VmCodeInstruction(
|
|
||||||
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
|
||||||
ins.type,
|
|
||||||
ins.reg1
|
|
||||||
)
|
|
||||||
changed = true
|
|
||||||
} else if (ins.value == 0) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.AND -> {
|
|
||||||
if (ins.value == 0) {
|
|
||||||
block.lines[idx] = VmCodeInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
|
||||||
changed = true
|
|
||||||
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.OR -> {
|
|
||||||
if (ins.value == 0) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) {
|
|
||||||
block.lines[idx] = VmCodeInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.XOR -> {
|
|
||||||
if (ins.value == 0) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeNops(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
|
||||||
var changed = false
|
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
||||||
if (ins.opcode == Opcode.NOP) {
|
|
||||||
changed = true
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeDoubleLoadsAndStores(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
|
||||||
var changed = false
|
|
||||||
indexedInstructions.forEach { (idx, ins) ->
|
|
||||||
|
|
||||||
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
|
||||||
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
|
||||||
// TODO: detect multiple float ffrom/fto to the same target, only keep first
|
|
||||||
// TODO: detect multiple sequential rnd with same reg1, only keep one
|
|
||||||
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
|
||||||
// TODO: detect multiple same ands, ors; only keep first
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface ICodeChange { // TODO not used? remove?
|
|
||||||
fun perform(block: VmCodeChunk)
|
|
||||||
|
|
||||||
class Remove(val idx: Int): ICodeChange {
|
|
||||||
override fun perform(block: VmCodeChunk) {
|
|
||||||
block.lines.removeAt(idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
package prog8tests.vm
|
|
||||||
|
|
||||||
import io.kotest.assertions.fail
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import prog8.code.SymbolTable
|
|
||||||
import prog8.code.ast.PtProgram
|
|
||||||
import prog8.codegen.virtual.*
|
|
||||||
import prog8.vm.Opcode
|
|
||||||
import prog8.vm.VmDataType
|
|
||||||
import prog8tests.vm.helpers.DummyMemsizer
|
|
||||||
import prog8tests.vm.helpers.DummyStringEncoder
|
|
||||||
|
|
||||||
class TestVmPeepholeOpt: FunSpec({
|
|
||||||
fun makeVmProgram(lines: List<VmCodeLine>): Pair<AssemblyProgram, VariableAllocator> {
|
|
||||||
val st = SymbolTable()
|
|
||||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
|
||||||
val allocations = VariableAllocator(st, program)
|
|
||||||
val asm = AssemblyProgram("test", allocations)
|
|
||||||
val block = VmCodeChunk()
|
|
||||||
for(line in lines)
|
|
||||||
block += line
|
|
||||||
asm.addBlock(block)
|
|
||||||
return Pair(asm, allocations)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AssemblyProgram.lines(): List<VmCodeLine> = this.getBlocks().flatMap { it.lines }
|
|
||||||
|
|
||||||
test("remove nops") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("dummy")),
|
|
||||||
VmCodeInstruction(Opcode.NOP),
|
|
||||||
VmCodeInstruction(Opcode.NOP)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 3
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
asm.lines().size shouldBe 1
|
|
||||||
}
|
|
||||||
|
|
||||||
test("remove jmp to label below") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label")), // removed
|
|
||||||
VmCodeLabel(listOf("label")),
|
|
||||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label2")), // removed
|
|
||||||
VmCodeInstruction(Opcode.NOP), // removed
|
|
||||||
VmCodeLabel(listOf("label2")),
|
|
||||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label3")),
|
|
||||||
VmCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
|
|
||||||
VmCodeLabel(listOf("label3"))
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 8
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 5
|
|
||||||
(lines[0] as VmCodeLabel).name shouldBe listOf("label")
|
|
||||||
(lines[1] as VmCodeLabel).name shouldBe listOf("label2")
|
|
||||||
(lines[2] as VmCodeInstruction).ins.opcode shouldBe Opcode.JUMP
|
|
||||||
(lines[3] as VmCodeInstruction).ins.opcode shouldBe Opcode.INC
|
|
||||||
(lines[4] as VmCodeLabel).name shouldBe listOf("label3")
|
|
||||||
}
|
|
||||||
|
|
||||||
test("remove double sec/clc") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.SEC),
|
|
||||||
VmCodeInstruction(Opcode.SEC),
|
|
||||||
VmCodeInstruction(Opcode.SEC),
|
|
||||||
VmCodeInstruction(Opcode.CLC),
|
|
||||||
VmCodeInstruction(Opcode.CLC),
|
|
||||||
VmCodeInstruction(Opcode.CLC)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 6
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 1
|
|
||||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.CLC
|
|
||||||
}
|
|
||||||
|
|
||||||
test("push followed by pop") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
|
|
||||||
VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
|
|
||||||
VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
|
|
||||||
VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 4
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 1
|
|
||||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOADR
|
|
||||||
(lines[0] as VmCodeInstruction).ins.reg1 shouldBe 222
|
|
||||||
(lines[0] as VmCodeInstruction).ins.reg2 shouldBe 99
|
|
||||||
}
|
|
||||||
|
|
||||||
test("remove useless div/mul, add/sub") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
|
|
||||||
VmCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
|
|
||||||
VmCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
|
|
||||||
VmCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
|
|
||||||
VmCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
|
|
||||||
VmCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
|
|
||||||
VmCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
|
|
||||||
VmCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
|
|
||||||
VmCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
|
|
||||||
VmCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 10
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 4
|
|
||||||
}
|
|
||||||
|
|
||||||
test("replace add/sub 1 by inc/dec") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
|
|
||||||
VmCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 2
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 2
|
|
||||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.INC
|
|
||||||
(lines[1] as VmCodeInstruction).ins.opcode shouldBe Opcode.DEC
|
|
||||||
}
|
|
||||||
|
|
||||||
test("remove useless and/or/xor") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
|
|
||||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
|
|
||||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
|
|
||||||
VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
|
|
||||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
|
|
||||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
|
|
||||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
|
|
||||||
VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 8
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 4
|
|
||||||
}
|
|
||||||
|
|
||||||
test("replace and/or/xor by constant number") {
|
|
||||||
val(asm, allocations) = makeVmProgram(listOf(
|
|
||||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
|
|
||||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
|
|
||||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
|
|
||||||
VmCodeInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
|
|
||||||
))
|
|
||||||
asm.lines().size shouldBe 4
|
|
||||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
|
||||||
opt.optimize()
|
|
||||||
val lines = asm.lines()
|
|
||||||
lines.size shouldBe 4
|
|
||||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
|
||||||
(lines[1] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
|
||||||
(lines[2] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
|
||||||
(lines[3] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
|
||||||
(lines[0] as VmCodeInstruction).ins.value shouldBe 0
|
|
||||||
(lines[1] as VmCodeInstruction).ins.value shouldBe 0
|
|
||||||
(lines[2] as VmCodeInstruction).ins.value shouldBe 255
|
|
||||||
(lines[3] as VmCodeInstruction).ins.value shouldBe 65535
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,15 +1,18 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.base.UndefinedSymbolError
|
import prog8.ast.base.UndefinedSymbolError
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.maySwapOperandOrder
|
||||||
|
import prog8.ast.statements.ForLoop
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.ast.statements.VarDeclType
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.AssociativeOperators
|
import prog8.code.core.AssociativeOperators
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.compiler.BuiltinFunctions
|
|
||||||
|
|
||||||
|
|
||||||
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||||
|
@ -13,13 +13,16 @@ import prog8.ast.statements.Jump
|
|||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.log2
|
import kotlin.math.log2
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||||
|
|
||||||
class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
class ExpressionSimplifier(private val program: Program,
|
||||||
|
private val errors: IErrorReporter,
|
||||||
|
private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||||
|
|
||||||
@ -74,6 +77,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(compTarget.name!=VMTarget.NAME) {
|
||||||
|
val booleanCondition = ifElse.condition as? BinaryExpression
|
||||||
|
if(booleanCondition!=null && booleanCondition.operator=="&") {
|
||||||
|
// special optimization of WORD & $ff00 -> just and the msb of WORD with $ff
|
||||||
|
val rightNum = booleanCondition.right as? NumericLiteral
|
||||||
|
if(rightNum!=null && rightNum.type==DataType.UWORD && (rightNum.number.toInt() and 0x00ff)==0) {
|
||||||
|
val msb = BuiltinFunctionCall(IdentifierReference(listOf("msb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
|
||||||
|
val bytevalue = NumericLiteral(DataType.UBYTE, (rightNum.number.toInt() shr 8).toDouble(), booleanCondition.right.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(booleanCondition.left, msb, booleanCondition),
|
||||||
|
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +353,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(functionCallExpr.target.nameInSource == listOf("mkword")) {
|
||||||
|
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
|
||||||
|
// just cast the lsb to uword
|
||||||
|
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,19 +496,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
in powersOfTwo -> {
|
in powersOfTwo -> {
|
||||||
if (leftDt in IntegerDatatypes) {
|
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
|
||||||
// divided by a power of two => shift right
|
// Unsigned number divided by a power of two => shift right
|
||||||
|
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
|
||||||
|
// so we leave that as is and let the code generator deal with it.
|
||||||
val numshifts = log2(cv).toInt()
|
val numshifts = log2(cv).toInt()
|
||||||
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in negativePowersOfTwo -> {
|
|
||||||
if (leftDt in IntegerDatatypes) {
|
|
||||||
// divided by a negative power of two => negate, then shift right
|
|
||||||
val numshifts = log2(-cv).toInt()
|
|
||||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftDt == DataType.UBYTE) {
|
if (leftDt == DataType.UBYTE) {
|
||||||
@ -539,9 +561,10 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
}
|
}
|
||||||
in negativePowersOfTwo -> {
|
in negativePowersOfTwo -> {
|
||||||
if (leftValue.inferType(program).isInteger) {
|
if (leftValue.inferType(program).isInteger) {
|
||||||
// times a negative power of two => negate, then shift left
|
// times a negative power of two => negate, then shift
|
||||||
val numshifts = log2(-cv).toInt()
|
val numshifts = log2(-cv).toInt()
|
||||||
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
val negation = PrefixExpression("-", expr2.left, expr.position)
|
||||||
|
return BinaryExpression(negation, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -565,13 +588,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
if (amount >= 16) {
|
if (amount >= 16) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||||
} else if (amount > 8) {
|
}
|
||||||
|
else if(amount==8) {
|
||||||
|
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
|
||||||
|
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(DataType.UBYTE, 0.0, expr.position)), expr.position)
|
||||||
|
}
|
||||||
|
else if (amount > 8) {
|
||||||
|
// same as above but with residual shifts.
|
||||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
|
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
|
||||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||||
@ -597,6 +629,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
when (idt.getOr(DataType.UNDEFINED)) {
|
when (idt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral.optimalInteger(0, expr.position)
|
return NumericLiteral.optimalInteger(0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -608,14 +641,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
|||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if (amount >= 16) {
|
if (amount >= 16) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral.optimalInteger(0, expr.position)
|
return NumericLiteral.optimalInteger(0, expr.position)
|
||||||
}
|
}
|
||||||
|
else if(amount==8) {
|
||||||
|
// shift right by 8 bits is just a byte operation: msb(X) as uword
|
||||||
|
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
|
||||||
|
}
|
||||||
else if (amount > 8) {
|
else if (amount > 8) {
|
||||||
|
// same as above but with residual shifts.
|
||||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
|
// bit-shifting a signed value shouldn't be allowed by the compiler but here we go...
|
||||||
if (amount > 16) {
|
if (amount > 16) {
|
||||||
expr.right = NumericLiteral.optimalInteger(16, expr.right.position)
|
expr.right = NumericLiteral.optimalInteger(16, expr.right.position)
|
||||||
return null
|
return null
|
||||||
|
@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
|
|||||||
return inliner.applyModifications()
|
return inliner.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Program.simplifyExpressions() : Int {
|
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
|
||||||
val opti = ExpressionSimplifier(this)
|
val opti = ExpressionSimplifier(this, errors, target)
|
||||||
opti.visit(this)
|
opti.visit(this)
|
||||||
return opti.applyModifications()
|
return opti.applyModifications()
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
is Return -> {
|
is Return -> {
|
||||||
if(stmt.value is NumericLiteral)
|
if(stmt.value is NumericLiteral)
|
||||||
true
|
true
|
||||||
|
else if(stmt.value==null)
|
||||||
|
true
|
||||||
else if (stmt.value is IdentifierReference) {
|
else if (stmt.value is IdentifierReference) {
|
||||||
makeFullyScoped(stmt.value as IdentifierReference)
|
makeFullyScoped(stmt.value as IdentifierReference)
|
||||||
true
|
true
|
||||||
@ -106,7 +108,7 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
} else
|
} else
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
is Jump, is GoSub -> true
|
is Jump -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,15 +172,6 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
return super.before(program)
|
return super.before(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
|
||||||
val sub = gosub.identifier.targetStatement(program) as? Subroutine
|
|
||||||
return if(sub==null)
|
|
||||||
noModifications
|
|
||||||
else
|
|
||||||
possibleInlineFcallStmt(sub, gosub, parent)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
||||||
if(sub.inline && sub.parameters.isEmpty()) {
|
if(sub.inline && sub.parameters.isEmpty()) {
|
||||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))
|
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))
|
||||||
|
@ -73,31 +73,6 @@ class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
|
|
||||||
val subroutine = functionCallStatement.target.targetSubroutine(program)
|
|
||||||
if(subroutine!=null) {
|
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
|
||||||
if(first is Return)
|
|
||||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
|
||||||
}
|
|
||||||
|
|
||||||
if(compTarget.name!=VMTarget.NAME) {
|
|
||||||
// see if we can optimize any complex argument expressions to be just a simple variable
|
|
||||||
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
|
|
||||||
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
|
|
||||||
val arg = functionCallStatement.args[0]
|
|
||||||
if(!arg.isSimple && arg !is IFunctionCall) {
|
|
||||||
val name = getTempRegisterName(arg.inferType(program))
|
|
||||||
val tempvar = IdentifierReference(name, functionCallStatement.position)
|
|
||||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
|
|
||||||
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,11 +97,13 @@ class StatementOptimizer(private val program: Program,
|
|||||||
if(constvalue!=null) {
|
if(constvalue!=null) {
|
||||||
return if(constvalue.asBooleanValue){
|
return if(constvalue.asBooleanValue){
|
||||||
// always true -> keep only if-part
|
// always true -> keep only if-part
|
||||||
errors.warn("condition is always true", ifElse.condition.position)
|
if(!ifElse.definingModule.isLibrary)
|
||||||
|
errors.warn("condition is always true", ifElse.condition.position)
|
||||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
||||||
} else {
|
} else {
|
||||||
// always false -> keep only else-part
|
// always false -> keep only else-part
|
||||||
errors.warn("condition is always false", ifElse.condition.position)
|
if(!ifElse.definingModule.isLibrary)
|
||||||
|
errors.warn("condition is always false", ifElse.condition.position)
|
||||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,25 +221,6 @@ class StatementOptimizer(private val program: Program,
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// NOTE: do NOT remove a jump to the next statement, because this will lead to wrong code when this occurs at the end of a subroutine
|
|
||||||
// if we want to optimize this away, it can be done later at code generation time.
|
|
||||||
|
|
||||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
|
||||||
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
|
|
||||||
val subroutineParams = gosub.identifier.targetSubroutine(program)?.parameters
|
|
||||||
if(subroutineParams!=null && subroutineParams.isEmpty()) {
|
|
||||||
val returnstmt = gosub.nextSibling() as? Return
|
|
||||||
if(returnstmt!=null && returnstmt.value==null) {
|
|
||||||
return listOf(
|
|
||||||
IAstModification.Remove(returnstmt, parent as IStatementContainer),
|
|
||||||
IAstModification.ReplaceNode(gosub, Jump(null, gosub.identifier, null, gosub.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
@ -335,9 +293,9 @@ class StatementOptimizer(private val program: Program,
|
|||||||
val bexpr=assignment.value as? BinaryExpression
|
val bexpr=assignment.value as? BinaryExpression
|
||||||
if(bexpr!=null) {
|
if(bexpr!=null) {
|
||||||
val rightCv = bexpr.right.constValue(program)?.number
|
val rightCv = bexpr.right.constValue(program)?.number
|
||||||
if(bexpr.operator=="-" && rightCv==null) {
|
if(bexpr.operator=="-" && rightCv==null && targetIDt.isInteger) {
|
||||||
if(bexpr.right isSameAs assignment.target) {
|
if(bexpr.right.isSimple && bexpr.right isSameAs assignment.target) {
|
||||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation, for integers)
|
||||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
@ -429,31 +387,22 @@ class StatementOptimizer(private val program: Program,
|
|||||||
if(compTarget.name==VMTarget.NAME)
|
if(compTarget.name==VMTarget.NAME)
|
||||||
return noModifications
|
return noModifications
|
||||||
|
|
||||||
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
|
|
||||||
val subr = returnStmt.definingSubroutine!!
|
|
||||||
val returnDt = subr.returntypes.single()
|
|
||||||
if (returnDt in IntegerDatatypes) {
|
|
||||||
// first assign to intermediary variable, then return that
|
|
||||||
val (returnVarName, _) = program.getTempVar(returnDt)
|
|
||||||
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
|
|
||||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
|
||||||
val assign = Assignment(tgt, value, AssignmentOrigin.OPTIMIZER, returnStmt.position)
|
|
||||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
|
||||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO decision when to use intermediary variable to calculate returnvalue seems a bit arbitrary...
|
|
||||||
val returnvalue = returnStmt.value
|
val returnvalue = returnStmt.value
|
||||||
if (returnvalue!=null) {
|
if (returnvalue!=null) {
|
||||||
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
|
val dt = returnvalue.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
val mod = returnViaIntermediaryVar(returnvalue)
|
if(dt!=DataType.UNDEFINED) {
|
||||||
if(mod!=null)
|
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
|
||||||
return mod
|
// first assign to intermediary variable, then return that
|
||||||
|
val (returnVarName, _) = program.getTempVar(dt)
|
||||||
|
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
|
||||||
|
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||||
|
val assign = Assignment(tgt, returnvalue, AssignmentOrigin.OPTIMIZER, returnStmt.position)
|
||||||
|
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||||
|
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,13 +27,13 @@ compileTestKotlin {
|
|||||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':codeAst')
|
|
||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
implementation project(':codeOptimizers')
|
implementation project(':codeOptimizers')
|
||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
implementation project(':codeGenCpu6502')
|
implementation project(':codeGenCpu6502')
|
||||||
implementation project(':codeGenVirtual')
|
implementation project(':codeGenIntermediate')
|
||||||
implementation project(':codeGenExperimental')
|
implementation project(':codeGenExperimental')
|
||||||
|
implementation project(':virtualmachine')
|
||||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
<orderEntry type="module" module-name="codeAst" />
|
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
<orderEntry type="module" module-name="compilerAst" />
|
||||||
<orderEntry type="module" module-name="codeOptimizers" />
|
<orderEntry type="module" module-name="codeOptimizers" />
|
||||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||||
<orderEntry type="module" module-name="codeGenVirtual" />
|
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||||
|
<orderEntry type="module" module-name="virtualmachine" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -56,7 +56,8 @@ romsub $af4b = ROUND() clobbers(A,X,Y) ; round fac1
|
|||||||
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||||
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||||
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
|
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
|
romsub $af57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||||
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||||
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||||
@ -151,6 +152,17 @@ asmsub FREADUY (ubyte value @Y) {
|
|||||||
|
|
||||||
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
|
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
|
||||||
|
|
||||||
|
sub rndf() -> float {
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr RND_0
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
%asminclude "library:c128/floats.asm"
|
%asminclude "library:c128/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm"
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
|
@ -171,6 +171,18 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|||||||
|
|
||||||
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
||||||
|
|
||||||
|
sub rndf() -> float {
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr FREADSA
|
||||||
|
jsr RND ; rng into fac1
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
%asminclude "library:c64/floats.asm"
|
%asminclude "library:c64/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm"
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ hline_zero2
|
|||||||
; for efficiency of internal algorithms here is the internal plot routine
|
; for efficiency of internal algorithms here is the internal plot routine
|
||||||
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
|
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
|
||||||
|
|
||||||
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
uword @zp internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||||
|
|
||||||
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||||
%asm {{
|
%asm {{
|
||||||
|
@ -37,14 +37,19 @@ cx16diskio {
|
|||||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
; -- like the basic command VLOAD "filename",device,bank,address
|
; -- like the basic command VLOAD "filename",device,bank,address
|
||||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||||
|
; the file has to have the usual 2 byte header (which will be skipped)
|
||||||
%asm {{
|
%asm {{
|
||||||
; -- load a file into video ram
|
clc
|
||||||
|
internal_vload:
|
||||||
phx
|
phx
|
||||||
pha
|
pha
|
||||||
tya
|
tya
|
||||||
tax
|
tax
|
||||||
lda #1
|
bcc +
|
||||||
ldy #0
|
ldy #%00000010 ; headerless load mode
|
||||||
|
bne ++
|
||||||
|
+ ldy #0 ; normal load mode
|
||||||
|
+ lda #1
|
||||||
jsr c64.SETLFS
|
jsr c64.SETLFS
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
ldy cx16.r0+1
|
ldy cx16.r0+1
|
||||||
@ -71,6 +76,15 @@ cx16diskio {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
|
; -- like the basic command BVLOAD "filename",device,bank,address
|
||||||
|
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||||
|
; the file is read fully including the first two bytes.
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
jmp vload.internal_vload
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
; replacement function that makes use of fast block read capability of the X16
|
; replacement function that makes use of fast block read capability of the X16
|
||||||
; use this in place of regular diskio.f_read()
|
; use this in place of regular diskio.f_read()
|
||||||
@ -97,7 +111,7 @@ cx16diskio {
|
|||||||
size = 255
|
size = 255
|
||||||
if num_bytes<size
|
if num_bytes<size
|
||||||
size = num_bytes
|
size = num_bytes
|
||||||
size = cx16.macptr(lsb(size), bufferpointer)
|
size = cx16.macptr(lsb(size), bufferpointer, false)
|
||||||
if_cs
|
if_cs
|
||||||
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
||||||
diskio.list_blocks += size
|
diskio.list_blocks += size
|
||||||
|
@ -57,7 +57,8 @@ romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
|
|||||||
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||||
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||||
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
|
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
|
||||||
|
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||||
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||||
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||||
@ -147,19 +148,21 @@ asmsub FREADUY (ubyte value @Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub RND() clobbers(A,X,Y) {
|
|
||||||
%asm {{
|
|
||||||
lda #0
|
|
||||||
php
|
|
||||||
jsr cx16.entropy_get
|
|
||||||
plp
|
|
||||||
jmp RND_0
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
||||||
|
|
||||||
|
sub rndf() -> float {
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda #1
|
||||||
|
jsr RND_0
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
%asminclude "library:c64/floats.asm"
|
%asminclude "library:c64/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm"
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -366,7 +366,7 @@ _done
|
|||||||
position2(x,y,true)
|
position2(x,y,true)
|
||||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||||
color &= 3
|
color &= 3
|
||||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
color <<= gfx2.plot.shift4c[lsb(x) & 3] ; TODO with lookup table
|
||||||
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||||
repeat lheight {
|
repeat lheight {
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -561,7 +561,11 @@ _done
|
|||||||
and #1
|
and #1
|
||||||
}}
|
}}
|
||||||
if_nz {
|
if_nz {
|
||||||
cx16.r0L = lsb(x) & 7 ; xbits
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
x /= 8
|
x /= 8
|
||||||
x += y*(320/8)
|
x += y*(320/8)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -571,7 +575,7 @@ _done
|
|||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
lda x
|
lda x
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
ldy cx16.r0L ; xbits
|
ply ; xbits
|
||||||
lda bits,y
|
lda bits,y
|
||||||
ldy color
|
ldy color
|
||||||
beq +
|
beq +
|
||||||
@ -608,7 +612,11 @@ _done
|
|||||||
and #1
|
and #1
|
||||||
}}
|
}}
|
||||||
if_nz {
|
if_nz {
|
||||||
cx16.r0L = lsb(x) & 7 ; xbits
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
x /= 8
|
x /= 8
|
||||||
x += y*(640/8)
|
x += y*(640/8)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -618,7 +626,7 @@ _done
|
|||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
lda x
|
lda x
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
ldy cx16.r0L ; xbits
|
ply ; xbits
|
||||||
lda bits,y
|
lda bits,y
|
||||||
ldy color
|
ldy color
|
||||||
beq +
|
beq +
|
||||||
@ -635,7 +643,7 @@ _done
|
|||||||
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
cx16.r2L = lsb(x) & 3 ; xbits
|
cx16.r2L = lsb(x) & 3 ; xbits
|
||||||
color &= 3
|
color &= 3
|
||||||
color <<= shift4c[cx16.r2L]
|
color <<= shift4c[cx16.r2L] ; TODO with lookup table
|
||||||
%asm {{
|
%asm {{
|
||||||
stz cx16.VERA_CTRL
|
stz cx16.VERA_CTRL
|
||||||
lda cx16.r1L
|
lda cx16.r1L
|
||||||
@ -654,6 +662,93 @@ _done
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub pget(uword @zp x, uword y) -> ubyte {
|
||||||
|
when active_mode {
|
||||||
|
1 -> {
|
||||||
|
; lores monochrome
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
|
x /= 8
|
||||||
|
x += y*(320/8)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
lda x+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda x
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ply ; xbits
|
||||||
|
lda plot.bits,y
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
; TODO mode 2 and 3
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda cx16.r1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
; hires monochrome
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
|
x /= 8
|
||||||
|
x += y*(640/8)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
lda x+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda x
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ply ; xbits
|
||||||
|
lda plot.bits,y
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; hires 4c
|
||||||
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda cx16.r1L
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0H
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda cx16.r0L
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.r0L
|
||||||
|
}}
|
||||||
|
cx16.r1L = lsb(x) & 3
|
||||||
|
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] ; TODO with lookup table
|
||||||
|
return cx16.r0L & 3
|
||||||
|
}
|
||||||
|
else -> return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub position(uword @zp x, uword y) {
|
sub position(uword @zp x, uword y) {
|
||||||
ubyte bank
|
ubyte bank
|
||||||
when active_mode {
|
when active_mode {
|
||||||
@ -844,25 +939,28 @@ _done
|
|||||||
}
|
}
|
||||||
6 -> {
|
6 -> {
|
||||||
; hires 4c
|
; hires 4c
|
||||||
|
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
|
||||||
|
cx16.r11L = color
|
||||||
|
cx16.r8 = y
|
||||||
while @(sctextptr) {
|
while @(sctextptr) {
|
||||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
repeat 8 {
|
repeat 8 {
|
||||||
; TODO rewrite this inner loop partly in assembly
|
; TODO rewrite this inner loop partly in assembly
|
||||||
; requires expanding the charbits to 2-bits per pixel (based on color)
|
; requires expanding the charbits to 2-bits per pixel (based on color)
|
||||||
; also it's way more efficient to draw whole horizontal spans instead of per-character
|
; also it's way more efficient to draw whole horizontal spans instead of per-character
|
||||||
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
|
cx16.r9L = cx16.vpeek(charset_bank, chardataptr) ; get the 8 horizontal character bits
|
||||||
|
cx16.r7 = x
|
||||||
repeat 8 {
|
repeat 8 {
|
||||||
charbits <<= 1
|
cx16.r9L <<= 1
|
||||||
if_cs
|
if_cs
|
||||||
plot(x, y, color)
|
plot(cx16.r7, cx16.r8, cx16.r11L)
|
||||||
x++
|
cx16.r7++
|
||||||
}
|
}
|
||||||
x-=8
|
|
||||||
chardataptr++
|
chardataptr++
|
||||||
y++
|
cx16.r8++
|
||||||
}
|
}
|
||||||
x+=8
|
x+=8
|
||||||
y-=8
|
cx16.r8-=8
|
||||||
sctextptr++
|
sctextptr++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub set_rgb(uword palette_words_ptr, uword num_colors) {
|
sub set_rgb(uword palette_words_ptr, uword num_colors) {
|
||||||
; 1 word per color entry (in little endian format as layed out in video memory, so $gb0r)
|
; 1 word per color entry (in little endian format as layed out in video memory, so $gb;$0r)
|
||||||
vera_palette_ptr = $fa00
|
vera_palette_ptr = $fa00
|
||||||
repeat num_colors*2 {
|
repeat num_colors*2 {
|
||||||
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
|
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
|
||||||
|
@ -15,6 +15,12 @@ psg {
|
|||||||
const ubyte RIGHT = %10000000
|
const ubyte RIGHT = %10000000
|
||||||
|
|
||||||
sub voice(ubyte voice_num, ubyte channel, ubyte volume, ubyte waveform, ubyte pulsewidth) {
|
sub voice(ubyte voice_num, ubyte channel, ubyte volume, ubyte waveform, ubyte pulsewidth) {
|
||||||
|
; -- Enables a 'voice' on the PSG.
|
||||||
|
; voice_num = 0-15, the voice number.
|
||||||
|
; channel = either LEFT or RIGHT or (LEFT|RIGHT). Specifies the stereo channel(s) to use.
|
||||||
|
; volume = 0-63, the starting volume for the voice
|
||||||
|
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
|
||||||
|
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
|
||||||
envelope_states[voice_num] = 255
|
envelope_states[voice_num] = 255
|
||||||
cx16.r0 = $f9c2 + voice_num * 4
|
cx16.r0 = $f9c2 + voice_num * 4
|
||||||
cx16.VERA_CTRL = 0
|
cx16.VERA_CTRL = 0
|
||||||
@ -30,12 +36,15 @@ psg {
|
|||||||
|
|
||||||
; sub freq_hz(ubyte voice_num, float hertz) {
|
; sub freq_hz(ubyte voice_num, float hertz) {
|
||||||
; ; this would rely on floating point math to convert hertz to vera frequency
|
; ; this would rely on floating point math to convert hertz to vera frequency
|
||||||
; ; TODO should be replaced by integer math maybe with a lookup table?
|
; ; could be replaced by integer math maybe with a lookup table?
|
||||||
; uword vera_freq = (hertz / 0.3725290298461914) as uword
|
; uword vera_freq = (hertz / 0.3725290298461914) as uword
|
||||||
; freq(voice_num, vera_freq)
|
; freq(voice_num, vera_freq)
|
||||||
; }
|
; }
|
||||||
|
|
||||||
sub freq(ubyte voice_num, uword vera_freq) {
|
sub freq(ubyte voice_num, uword vera_freq) {
|
||||||
|
; -- Changes the frequency of the voice's sound.
|
||||||
|
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
|
||||||
|
; (https://github.com/commanderx16/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
|
||||||
cx16.r0 = $f9c0 + voice_num * 4
|
cx16.r0 = $f9c0 + voice_num * 4
|
||||||
cx16.VERA_CTRL = 0
|
cx16.VERA_CTRL = 0
|
||||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||||
@ -47,6 +56,8 @@ psg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub volume(ubyte voice_num, ubyte vol) {
|
sub volume(ubyte voice_num, ubyte vol) {
|
||||||
|
; -- Modifies the volume of this voice.
|
||||||
|
; voice_num = 0-15, vol = 0-63 where 0=silent, 63=loudest.
|
||||||
cx16.r0 = $f9c2 + voice_num * 4
|
cx16.r0 = $f9c2 + voice_num * 4
|
||||||
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | vol)
|
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | vol)
|
||||||
envelope_volumes[voice_num] = mkword(vol, 0)
|
envelope_volumes[voice_num] = mkword(vol, 0)
|
||||||
@ -54,11 +65,18 @@ psg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub pulse_width(ubyte voice_num, ubyte pw) {
|
sub pulse_width(ubyte voice_num, ubyte pw) {
|
||||||
|
; -- Modifies the pulse width of this voice (when waveform=PULSE)
|
||||||
|
; voice_num = 0-15, pw = 0-63 where 0=narrow, 63=50%cycle so square wave.
|
||||||
cx16.r0 = $f9c3 + voice_num * 4
|
cx16.r0 = $f9c3 + voice_num * 4
|
||||||
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | pw)
|
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
sub envelope(ubyte voice_num, ubyte maxvolume, ubyte attack, ubyte sustain, ubyte release) {
|
sub envelope(ubyte voice_num, ubyte maxvolume, ubyte attack, ubyte sustain, ubyte release) {
|
||||||
|
; -- Enables AttackSustainRelease volume envelope for a voice.
|
||||||
|
; Note: this requires setting up envelopes_irq() as well, read its description.
|
||||||
|
; voice_num = 0-15 maxvolume = 0-63
|
||||||
|
; attack, sustain, release = 0-255 that determine the speed of the A/D/R.
|
||||||
|
; TODO describe how the speeds are calculated. For now, experiment. Higher values means *slower* enveloping.
|
||||||
envelope_states[voice_num] = 255
|
envelope_states[voice_num] = 255
|
||||||
envelope_attacks[voice_num] = attack
|
envelope_attacks[voice_num] = attack
|
||||||
envelope_sustains[voice_num] = sustain
|
envelope_sustains[voice_num] = sustain
|
||||||
@ -73,6 +91,7 @@ psg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub silent() {
|
sub silent() {
|
||||||
|
; -- Shut down all PSG voices.
|
||||||
for cx16.r1L in 0 to 15 {
|
for cx16.r1L in 0 to 15 {
|
||||||
envelope_states[cx16.r1L] = 255
|
envelope_states[cx16.r1L] = 255
|
||||||
envelope_volumes[cx16.r1L] = 0
|
envelope_volumes[cx16.r1L] = 0
|
||||||
|
@ -98,7 +98,7 @@ cx16 {
|
|||||||
&uword ISTOP = $0328
|
&uword ISTOP = $0328
|
||||||
&uword IGETIN = $032a
|
&uword IGETIN = $032a
|
||||||
&uword ICLALL = $032c
|
&uword ICLALL = $032c
|
||||||
&uword KEYHDL = $032e ; keyboard scan code handler
|
&uword KEYHDL = $032e ; keyboard scan code handler see examples/cx16/keyboardhandler.p8
|
||||||
&uword ILOAD = $0330
|
&uword ILOAD = $0330
|
||||||
&uword ISAVE = $0332
|
&uword ISAVE = $0332
|
||||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||||
@ -306,7 +306,7 @@ romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
|||||||
romsub $ff5f = screen_mode(ubyte mode @A, ubyte getCurrent @Pc) clobbers(A, X, Y) -> ubyte @Pc
|
romsub $ff5f = screen_mode(ubyte mode @A, ubyte getCurrent @Pc) clobbers(A, X, Y) -> ubyte @Pc
|
||||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||||
romsub $ff6e = jsrfar()
|
romsub $ff6e = jsrfar() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||||
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||||
@ -363,12 +363,13 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
|||||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||||
|
|
||||||
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY) clobbers(A) -> ubyte @Pc, uword @XY
|
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||||
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||||
|
|
||||||
; keyboard, mouse, joystick
|
; keyboard, mouse, joystick
|
||||||
|
; note: also see the kbdbuf_clear() helper routine below!
|
||||||
romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
|
romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
|
||||||
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||||
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||||
@ -380,12 +381,22 @@ romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
|||||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||||
|
|
||||||
|
asmsub kbdbuf_clear() {
|
||||||
|
; -- convenience helper routine to clear the keyboard buffer
|
||||||
|
%asm {{
|
||||||
|
- jsr c64.GETIN
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
||||||
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
||||||
%asm {{
|
%asm {{
|
||||||
|
pha ; save shape
|
||||||
sec
|
sec
|
||||||
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
|
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
|
||||||
lda #1
|
pla ; get shape back
|
||||||
jmp cx16.mouse_config
|
jmp cx16.mouse_config
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ diskio {
|
|||||||
|
|
||||||
c64.SETNAM(1, "$")
|
c64.SETNAM(1, "$")
|
||||||
c64.SETLFS(13, drivenumber, 0)
|
c64.SETLFS(13, drivenumber, 0)
|
||||||
|
ubyte status = 1
|
||||||
void c64.OPEN() ; open 13,8,0,"$"
|
void c64.OPEN() ; open 13,8,0,"$"
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
@ -23,8 +24,13 @@ diskio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
; while not key pressed / EOF encountered, read data.
|
; while not key pressed / EOF encountered, read data.
|
||||||
ubyte status = c64.READST()
|
status = c64.READST()
|
||||||
while not status {
|
if status!=0 {
|
||||||
|
status = 1
|
||||||
|
goto io_error
|
||||||
|
}
|
||||||
|
|
||||||
|
while status==0 {
|
||||||
ubyte low = c64.CHRIN()
|
ubyte low = c64.CHRIN()
|
||||||
ubyte high = c64.CHRIN()
|
ubyte high = c64.CHRIN()
|
||||||
txt.print_uw(mkword(high, low))
|
txt.print_uw(mkword(high, low))
|
||||||
@ -43,9 +49,9 @@ diskio {
|
|||||||
if c64.STOP2()
|
if c64.STOP2()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
status = c64.READST()
|
||||||
|
|
||||||
io_error:
|
io_error:
|
||||||
status = c64.READST()
|
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(13)
|
c64.CLOSE(13)
|
||||||
|
|
||||||
@ -59,6 +65,42 @@ io_error:
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub diskname(ubyte drivenumber) -> uword {
|
||||||
|
; -- Returns pointer to disk name string or 0 if failure.
|
||||||
|
|
||||||
|
c64.SETNAM(1, "$")
|
||||||
|
c64.SETLFS(13, drivenumber, 0)
|
||||||
|
ubyte okay = false
|
||||||
|
void c64.OPEN() ; open 13,8,0,"$"
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
void c64.CHKIN(13) ; use #13 as input channel
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
repeat 6 {
|
||||||
|
void c64.CHRIN() ; skip the 6 prologue bytes
|
||||||
|
}
|
||||||
|
if c64.READST()!=0
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
; while not key pressed / EOF encountered, read data.
|
||||||
|
cx16.r0 = &list_filename
|
||||||
|
repeat {
|
||||||
|
@(cx16.r0) = c64.CHRIN()
|
||||||
|
if @(cx16.r0)==0
|
||||||
|
break
|
||||||
|
cx16.r0++
|
||||||
|
}
|
||||||
|
okay = true
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
|
c64.CLOSE(13)
|
||||||
|
if okay
|
||||||
|
return &list_filename
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
; internal variables for the iterative file lister / loader
|
; internal variables for the iterative file lister / loader
|
||||||
bool list_skip_disk_name
|
bool list_skip_disk_name
|
||||||
@ -67,26 +109,25 @@ io_error:
|
|||||||
bool iteration_in_progress = false
|
bool iteration_in_progress = false
|
||||||
ubyte @zp first_byte
|
ubyte @zp first_byte
|
||||||
bool have_first_byte
|
bool have_first_byte
|
||||||
str list_filename = "?" * 32
|
str list_filename = "?" * 50
|
||||||
|
|
||||||
|
|
||||||
; ----- get a list of files (uses iteration functions internally) -----
|
; ----- get a list of files (uses iteration functions internally) -----
|
||||||
|
|
||||||
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
||||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
|
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files.
|
||||||
const uword names_buf_size = 800
|
const uword filenames_buf_size = 800
|
||||||
uword names_buffer = memory("filenames", names_buf_size, 0)
|
uword filenames_buffer = memory("filenames", filenames_buf_size, 0)
|
||||||
uword buffer_start = names_buffer
|
uword buffer_start = filenames_buffer
|
||||||
ubyte files_found = 0
|
ubyte files_found = 0
|
||||||
if lf_start_list(drivenumber, pattern_ptr) {
|
if lf_start_list(drivenumber, pattern_ptr) {
|
||||||
while lf_next_entry() {
|
while lf_next_entry() {
|
||||||
@(name_ptrs) = lsb(names_buffer)
|
@(name_ptrs) = lsb(filenames_buffer)
|
||||||
name_ptrs++
|
name_ptrs++
|
||||||
@(name_ptrs) = msb(names_buffer)
|
@(name_ptrs) = msb(filenames_buffer)
|
||||||
name_ptrs++
|
name_ptrs++
|
||||||
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
|
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
||||||
files_found++
|
files_found++
|
||||||
if names_buffer - buffer_start > names_buf_size-18
|
if filenames_buffer - buffer_start > filenames_buf_size-18
|
||||||
break
|
break
|
||||||
if files_found == max_names
|
if files_found == max_names
|
||||||
break
|
break
|
||||||
@ -384,8 +425,8 @@ _end rts
|
|||||||
|
|
||||||
sub status(ubyte drivenumber) -> uword {
|
sub status(ubyte drivenumber) -> uword {
|
||||||
; -- retrieve the disk drive's current status message
|
; -- retrieve the disk drive's current status message
|
||||||
uword messageptr = &filename
|
uword messageptr = &list_filename
|
||||||
c64.SETNAM(0, filename)
|
c64.SETNAM(0, list_filename)
|
||||||
c64.SETLFS(15, drivenumber, 15)
|
c64.SETLFS(15, drivenumber, 15)
|
||||||
void c64.OPEN() ; open 15,8,15
|
void c64.OPEN() ; open 15,8,15
|
||||||
if_cs
|
if_cs
|
||||||
@ -406,10 +447,10 @@ _end rts
|
|||||||
done:
|
done:
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(15)
|
c64.CLOSE(15)
|
||||||
return filename
|
return list_filename
|
||||||
|
|
||||||
io_error:
|
io_error:
|
||||||
filename = "?disk error"
|
list_filename = "?disk error"
|
||||||
goto done
|
goto done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,10 +552,10 @@ io_error:
|
|||||||
|
|
||||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||||
; -- delete a file on the drive
|
; -- delete a file on the drive
|
||||||
filename[0] = 's'
|
list_filename[0] = 's'
|
||||||
filename[1] = ':'
|
list_filename[1] = ':'
|
||||||
ubyte flen = string.copy(filenameptr, &filename+2)
|
ubyte flen = string.copy(filenameptr, &list_filename+2)
|
||||||
c64.SETNAM(flen+2, filename)
|
c64.SETNAM(flen+2, list_filename)
|
||||||
c64.SETLFS(1, drivenumber, 15)
|
c64.SETLFS(1, drivenumber, 15)
|
||||||
void c64.OPEN()
|
void c64.OPEN()
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
@ -523,17 +564,24 @@ io_error:
|
|||||||
|
|
||||||
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||||
; -- rename a file on the drive
|
; -- rename a file on the drive
|
||||||
filename[0] = 'r'
|
list_filename[0] = 'r'
|
||||||
filename[1] = ':'
|
list_filename[1] = ':'
|
||||||
ubyte flen_new = string.copy(newfileptr, &filename+2)
|
ubyte flen_new = string.copy(newfileptr, &list_filename+2)
|
||||||
filename[flen_new+2] = '='
|
list_filename[flen_new+2] = '='
|
||||||
ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
|
ubyte flen_old = string.copy(oldfileptr, &list_filename+3+flen_new)
|
||||||
c64.SETNAM(3+flen_new+flen_old, filename)
|
c64.SETNAM(3+flen_new+flen_old, list_filename)
|
||||||
c64.SETLFS(1, drivenumber, 15)
|
c64.SETLFS(1, drivenumber, 15)
|
||||||
void c64.OPEN()
|
void c64.OPEN()
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(1)
|
c64.CLOSE(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
str filename = "0:??????????????????????????????????????"
|
sub send_command(ubyte drivenumber, uword commandptr) {
|
||||||
|
; -- send a dos command to the drive
|
||||||
|
c64.SETNAM(string.length(commandptr), commandptr)
|
||||||
|
c64.SETLFS(15, drivenumber, 15)
|
||||||
|
void c64.OPEN()
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(15)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,13 +221,18 @@ sub ceil(float value) -> float {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rndf() -> float {
|
sub rndseedf(float seed) {
|
||||||
|
if seed>0
|
||||||
|
seed = -seed ; make sure fp seed is always negative
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
stx P8ZP_SCRATCH_REG
|
stx floats_store_reg
|
||||||
lda #1
|
lda #<seed
|
||||||
jsr FREADSA
|
ldy #>seed
|
||||||
jsr RND ; rng into fac1
|
jsr MOVFM ; load float into fac1
|
||||||
ldx P8ZP_SCRATCH_REG
|
lda #-1
|
||||||
|
jsr floats.RND
|
||||||
|
ldx floats_store_reg
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -229,60 +229,32 @@ _divisor .word 0
|
|||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
randseed .proc
|
|
||||||
; -- reset the random seeds for the byte and word random generators
|
|
||||||
; arguments: uword seed in A/Y clobbers A
|
|
||||||
; (default starting values are: A=$2c Y=$9e)
|
|
||||||
sta randword._seed
|
|
||||||
sty randword._seed+1
|
|
||||||
clc
|
|
||||||
adc #14
|
|
||||||
sta randbyte._seed
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
randbyte .proc
|
|
||||||
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
|
||||||
jmp randword
|
|
||||||
.pend
|
|
||||||
|
|
||||||
randword .proc
|
randword .proc
|
||||||
; -- 16 bit pseudo random number generator into AY
|
; -- 16 bit pseudo random number generator into AY
|
||||||
|
; default seed = $00c2 $1137
|
||||||
; rand64k ;Factors of 65535: 3 5 17 257
|
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
|
||||||
lda sr1+1
|
inc x1
|
||||||
asl a
|
clc
|
||||||
asl a
|
x1=*+1
|
||||||
eor sr1+1
|
lda #$00 ;x1
|
||||||
asl a
|
c1=*+1
|
||||||
eor sr1+1
|
eor #$c2 ;c1
|
||||||
asl a
|
a1=*+1
|
||||||
asl a
|
eor #$11 ;a1
|
||||||
eor sr1+1
|
sta a1
|
||||||
asl a
|
b1=*+1
|
||||||
rol sr1 ;shift this left, "random" bit comes from low
|
adc #$37 ;b1
|
||||||
rol sr1+1
|
sta b1
|
||||||
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
|
lsr a
|
||||||
lda sr2+1
|
eor a1
|
||||||
asl a
|
adc c1
|
||||||
eor sr2+1
|
sta c1
|
||||||
asl a
|
ldy b1
|
||||||
asl a
|
|
||||||
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
|
|
||||||
rol sr2+1
|
|
||||||
lda sr1+1 ;can be left out
|
|
||||||
eor sr2+1 ;if you dont use
|
|
||||||
tay ;y as suggested
|
|
||||||
lda sr1 ;mix up lowbytes of SR1
|
|
||||||
eor sr2 ;and SR2 to combine both
|
|
||||||
rts
|
rts
|
||||||
|
|
||||||
sr1 .word $a55a
|
|
||||||
sr2 .word $7653
|
|
||||||
|
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
randbyte = randword ; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||||
|
|
||||||
|
|
||||||
; ----------- optimized multiplications (stack) : ---------
|
; ----------- optimized multiplications (stack) : ---------
|
||||||
stack_mul_byte_3 .proc
|
stack_mul_byte_3 .proc
|
||||||
|
@ -37,60 +37,6 @@ _sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub sin16(ubyte angle @A) -> word @AY {
|
|
||||||
%asm {{
|
|
||||||
tay
|
|
||||||
lda _sinecos8lo,y
|
|
||||||
pha
|
|
||||||
lda _sinecos8hi,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
_ := trunc(32767.0 * sin(range(256+64) * rad(360.0/256.0)))
|
|
||||||
_sinecos8lo .byte <_
|
|
||||||
_sinecos8hi .byte >_
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub cos16(ubyte angle @A) -> word @AY {
|
|
||||||
%asm {{
|
|
||||||
tay
|
|
||||||
lda sin16._sinecos8lo+64,y
|
|
||||||
pha
|
|
||||||
lda sin16._sinecos8hi+64,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub sin16u(ubyte angle @A) -> uword @AY {
|
|
||||||
%asm {{
|
|
||||||
tay
|
|
||||||
lda _sinecos8ulo,y
|
|
||||||
pha
|
|
||||||
lda _sinecos8uhi,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
_ := trunc(32768.0 + 32767.5 * sin(range(256+64) * rad(360.0/256.0)))
|
|
||||||
_sinecos8ulo .byte <_
|
|
||||||
_sinecos8uhi .byte >_
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub cos16u(ubyte angle @A) -> uword @AY {
|
|
||||||
%asm {{
|
|
||||||
tay
|
|
||||||
lda sin16u._sinecos8ulo+64,y
|
|
||||||
pha
|
|
||||||
lda sin16u._sinecos8uhi+64,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub sinr8u(ubyte radians @A) clobbers(Y) -> ubyte @A {
|
asmsub sinr8u(ubyte radians @A) clobbers(Y) -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
tay
|
tay
|
||||||
@ -125,57 +71,28 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub sinr16(ubyte radians @A) -> word @AY {
|
asmsub rnd() -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
tay
|
jmp math.randbyte
|
||||||
lda _sinecosR8lo,y
|
|
||||||
pha
|
|
||||||
lda _sinecosR8hi,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
|
|
||||||
_sinecosR8lo .byte <_
|
|
||||||
_sinecosR8hi .byte >_
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub cosr16(ubyte radians @A) -> word @AY {
|
asmsub rndw() -> uword @AY {
|
||||||
%asm {{
|
%asm {{
|
||||||
tay
|
jmp math.randword
|
||||||
lda sinr16._sinecosR8lo+45,y
|
|
||||||
pha
|
|
||||||
lda sinr16._sinecosR8hi+45,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub sinr16u(ubyte radians @A) -> uword @AY {
|
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
|
||||||
|
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
|
||||||
%asm {{
|
%asm {{
|
||||||
tay
|
sta math.randword.x1
|
||||||
lda _sinecosR8ulo,y
|
sty math.randword.c1
|
||||||
pha
|
lda cx16.r0L
|
||||||
lda _sinecosR8uhi,y
|
sta math.randword.a1
|
||||||
tay
|
lda cx16.r0H
|
||||||
pla
|
sta math.randword.b1
|
||||||
rts
|
rts
|
||||||
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
|
|
||||||
_sinecosR8ulo .byte <_
|
|
||||||
_sinecosR8uhi .byte >_
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub cosr16u(ubyte radians @A) -> uword @AY {
|
|
||||||
%asm {{
|
|
||||||
tay
|
|
||||||
lda sinr16u._sinecosR8ulo+45,y
|
|
||||||
pha
|
|
||||||
lda sinr16u._sinecosR8uhi+45,y
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
rts
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,24 +244,6 @@ func_sqrt16_into_A .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
func_rnd_stack .proc
|
|
||||||
; -- put a random ubyte on the estack
|
|
||||||
jsr math.randbyte
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rndw_stack .proc
|
|
||||||
; -- put a random uword on the estack
|
|
||||||
jsr math.randword
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
tya
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
func_sort_ub .proc
|
func_sort_ub .proc
|
||||||
; 8bit unsigned sort
|
; 8bit unsigned sort
|
||||||
|
@ -129,7 +129,7 @@ _startloop dey
|
|||||||
|
|
||||||
asmsub find(uword string @R0, ubyte character @A) -> ubyte @A, ubyte @Pc {
|
asmsub find(uword string @R0, ubyte character @A) -> ubyte @A, ubyte @Pc {
|
||||||
; Locates the first position of the given character in the string,
|
; 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.
|
; returns Carry set if found + index in A, or A=0 + Carry clear if not found.
|
||||||
%asm {{
|
%asm {{
|
||||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
@ -144,7 +144,8 @@ _startloop dey
|
|||||||
beq _found
|
beq _found
|
||||||
iny
|
iny
|
||||||
bne -
|
bne -
|
||||||
_notfound clc
|
_notfound lda #0
|
||||||
|
clc
|
||||||
rts
|
rts
|
||||||
_found tya
|
_found tya
|
||||||
sec
|
sec
|
||||||
@ -223,6 +224,49 @@ _done rts
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub lowerchar(ubyte char @A) -> ubyte @A {
|
||||||
|
%asm {{
|
||||||
|
and #$7f
|
||||||
|
cmp #97
|
||||||
|
bcc +
|
||||||
|
cmp #123
|
||||||
|
bcs +
|
||||||
|
and #%11011111
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub upperchar(ubyte char @A) -> ubyte @A {
|
||||||
|
%asm {{
|
||||||
|
cmp #65
|
||||||
|
bcc +
|
||||||
|
cmp #91
|
||||||
|
bcs +
|
||||||
|
ora #%00100000
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub startswith(str st, str prefix) -> bool {
|
||||||
|
ubyte prefix_len = length(prefix)
|
||||||
|
ubyte str_len = length(st)
|
||||||
|
if prefix_len > str_len
|
||||||
|
return false
|
||||||
|
cx16.r9L = st[prefix_len]
|
||||||
|
st[prefix_len] = 0
|
||||||
|
cx16.r9H = compare(st, prefix) as ubyte
|
||||||
|
st[prefix_len] = cx16.r9L
|
||||||
|
return cx16.r9H==0
|
||||||
|
}
|
||||||
|
|
||||||
|
sub endswith(str st, str suffix) -> bool {
|
||||||
|
ubyte suffix_len = length(suffix)
|
||||||
|
ubyte str_len = length(st)
|
||||||
|
if suffix_len > str_len
|
||||||
|
return false
|
||||||
|
return compare(st + str_len - suffix_len, suffix) == 0
|
||||||
|
}
|
||||||
|
|
||||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
; pattern matching of a string.
|
; pattern matching of a string.
|
||||||
@ -293,5 +337,4 @@ fail clc ; yes, no match found, return with c=0
|
|||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -195,8 +195,8 @@ sub str2uword(str string) -> uword {
|
|||||||
; -- returns the unsigned word value of the string number argument in AY
|
; -- returns the unsigned word value of the string number argument in AY
|
||||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
; (any non-digit character will terminate the number string that is parsed)
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0, {conv.str2uword.string}
|
loadm.w r65500,conv.str2uword.string
|
||||||
syscall 11
|
syscall 11
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
@ -206,8 +206,8 @@ sub str2word(str string) -> word {
|
|||||||
; -- returns the signed word value of the string number argument in AY
|
; -- returns the signed word value of the string number argument in AY
|
||||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||||
; (any non-digit character will terminate the number string that is parsed)
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0, {conv.str2word.string}
|
loadm.w r65500,conv.str2word.string
|
||||||
syscall 12
|
syscall 12
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
|
@ -9,82 +9,82 @@ floats {
|
|||||||
|
|
||||||
sub print_f(float value) {
|
sub print_f(float value) {
|
||||||
; ---- prints the floating point value (without a newline).
|
; ---- prints the floating point value (without a newline).
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.print_f.value}
|
loadm.f fr65500,floats.print_f.value
|
||||||
syscall 25
|
syscall 25
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub pow(float value, float power) -> float {
|
sub pow(float value, float power) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.pow.value}
|
loadm.f fr0,floats.pow.value
|
||||||
loadm.f fr1,{floats.pow.power}
|
loadm.f fr1,floats.pow.power
|
||||||
fpow.f fr0,fr1
|
fpow.f fr0,fr1
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub fabs(float value) -> float {
|
sub fabs(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.fabs.value}
|
loadm.f fr0,floats.fabs.value
|
||||||
fabs.f fr0,fr0
|
fabs.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sin(float angle) -> float {
|
sub sin(float angle) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.sin.angle}
|
loadm.f fr0,floats.sin.angle
|
||||||
fsin.f fr0,fr0
|
fsin.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cos(float angle) -> float {
|
sub cos(float angle) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.cos.angle}
|
loadm.f fr0,floats.cos.angle
|
||||||
fcos.f fr0,fr0
|
fcos.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub tan(float value) -> float {
|
sub tan(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.tan.value}
|
loadm.f fr0,floats.tan.value
|
||||||
ftan.f fr0,fr0
|
ftan.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub atan(float value) -> float {
|
sub atan(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.atan.value}
|
loadm.f fr0,floats.atan.value
|
||||||
fatan.f fr0,fr0
|
fatan.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub ln(float value) -> float {
|
sub ln(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.ln.value}
|
loadm.f fr0,floats.ln.value
|
||||||
fln.f fr0,fr0
|
fln.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub log2(float value) -> float {
|
sub log2(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.log2.value}
|
loadm.f fr0,floats.log2.value
|
||||||
flog.f fr0,fr0
|
flog.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sqrt(float value) -> float {
|
sub sqrt(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.sqrt.value}
|
loadm.f fr0,floats.sqrt.value
|
||||||
fsqrt.f fr0,fr0
|
sqrt.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -100,16 +100,16 @@ sub deg(float angle) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub round(float value) -> float {
|
sub round(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.round.value}
|
loadm.f fr0,floats.round.value
|
||||||
fround.f fr0,fr0
|
fround.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub floor(float value) -> float {
|
sub floor(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.floor.value}
|
loadm.f fr0,floats.floor.value
|
||||||
ffloor.f fr0,fr0
|
ffloor.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
@ -117,17 +117,25 @@ sub floor(float value) -> float {
|
|||||||
|
|
||||||
sub ceil(float value) -> float {
|
sub ceil(float value) -> float {
|
||||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,{floats.ceil.value}
|
loadm.f fr0,floats.ceil.value
|
||||||
fceil.f fr0,fr0
|
fceil.f fr0,fr0
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rndf() -> float {
|
sub rndf() -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
rnd.f fr0
|
syscall 35
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub rndseedf(float seed) {
|
||||||
|
%ir {{
|
||||||
|
loadm.f fr65500,floats.rndseedf.seed
|
||||||
|
syscall 32
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ math {
|
|||||||
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
|
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
|
||||||
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
|
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
|
||||||
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
|
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
|
||||||
return sintab[angle]
|
return sintab[angle]
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cos8u(ubyte angle) -> ubyte {
|
sub cos8u(ubyte angle) -> ubyte {
|
||||||
@ -40,31 +40,53 @@ math {
|
|||||||
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
|
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
|
||||||
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
|
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
|
||||||
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff ]
|
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff ]
|
||||||
return costab[angle]
|
return costab[angle]
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sin8(ubyte angle) -> byte {
|
sub sin8(ubyte angle) -> byte {
|
||||||
return 42 ; TODO
|
ubyte[256] sintab = [
|
||||||
|
$00, $03, $06, $09,
|
||||||
|
$0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d, $30, $33, $36, $39,
|
||||||
|
$3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57, $59, $5b, $5e, $60,
|
||||||
|
$62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74, $75, $76, $77, $78,
|
||||||
|
$79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e, $7f, $7e, $7e, $7e,
|
||||||
|
$7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76, $75, $74, $72, $71,
|
||||||
|
$70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b, $59, $57, $55, $52,
|
||||||
|
$50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33, $30, $2d, $2a, $27,
|
||||||
|
$24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03, $00, $fd, $fa, $f7,
|
||||||
|
$f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3, $d0, $cd, $ca, $c7,
|
||||||
|
$c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9, $a7, $a5, $a2, $a0,
|
||||||
|
$9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c, $8b, $8a, $89, $88,
|
||||||
|
$87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82, $81, $82, $82, $82,
|
||||||
|
$82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8e, $8f,
|
||||||
|
$90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5, $a7, $a9, $ab, $ae,
|
||||||
|
$b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd, $d0, $d3, $d6, $d9,
|
||||||
|
$dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd
|
||||||
|
]
|
||||||
|
return sintab[angle] as byte
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cos8(ubyte angle) -> byte {
|
sub cos8(ubyte angle) -> byte {
|
||||||
return 42 ; TODO
|
ubyte[256] costab = [
|
||||||
}
|
$7f, $7e, $7e, $7e,
|
||||||
|
$7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76, $75, $74, $72, $71,
|
||||||
sub sin16(ubyte angle) -> word {
|
$70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b, $59, $57, $55, $52,
|
||||||
return 4242 ; TODO
|
$50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33, $30, $2d, $2a, $27,
|
||||||
}
|
$24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03, $00, $fd, $fa, $f7,
|
||||||
|
$f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3, $d0, $cd, $ca, $c7,
|
||||||
sub cos16(ubyte angle) -> word {
|
$c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9, $a7, $a5, $a2, $a0,
|
||||||
return 4242 ; TODO
|
$9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c, $8b, $8a, $89, $88,
|
||||||
}
|
$87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82, $81, $82, $82, $82,
|
||||||
|
$82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8e, $8f,
|
||||||
sub sin16u(ubyte angle) -> uword {
|
$90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5, $a7, $a9, $ab, $ae,
|
||||||
return 4242 ; TODO
|
$b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd, $d0, $d3, $d6, $d9,
|
||||||
}
|
$dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd, $00, $03, $06, $09,
|
||||||
|
$0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d, $30, $33, $36, $39,
|
||||||
sub cos16u(ubyte angle) -> uword {
|
$3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57, $59, $5b, $5e, $60,
|
||||||
return 4242 ; TODO
|
$62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74, $75, $76, $77, $78,
|
||||||
|
$79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e
|
||||||
|
]
|
||||||
|
return costab[angle] as byte
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sinr8u(ubyte radians) -> ubyte {
|
sub sinr8u(ubyte radians) -> ubyte {
|
||||||
@ -102,26 +124,62 @@ math {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub sinr8(ubyte radians) -> byte {
|
sub sinr8(ubyte radians) -> byte {
|
||||||
return 42 ; TODO
|
ubyte[180] sintab = [
|
||||||
|
$00, $04, $08, $0d,
|
||||||
|
$11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f, $43, $47, $4a, $4e,
|
||||||
|
$51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70, $72, $74, $75, $77,
|
||||||
|
$78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7f, $7e, $7e, $7e, $7d, $7d, $7c,
|
||||||
|
$7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66, $64, $61, $5e, $5b,
|
||||||
|
$58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f, $2b, $27, $23, $1e,
|
||||||
|
$1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea, $e6, $e2, $dd, $d9,
|
||||||
|
$d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac, $a8, $a5, $a2, $9f,
|
||||||
|
$9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86, $85, $84, $83, $83,
|
||||||
|
$82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86, $88, $89, $8b, $8c,
|
||||||
|
$8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac, $af, $b2, $b6, $b9,
|
||||||
|
$bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea, $ef, $f3, $f8, $fc
|
||||||
|
]
|
||||||
|
return sintab[radians] as byte
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cosr8(ubyte radians) -> byte {
|
sub cosr8(ubyte radians) -> byte {
|
||||||
return 42 ; TODO
|
ubyte[180] costab = [
|
||||||
|
$7f, $7e, $7e, $7e, $7d, $7d, $7c,
|
||||||
|
$7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66, $64, $61, $5e, $5b,
|
||||||
|
$58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f, $2b, $27, $23, $1e,
|
||||||
|
$1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea, $e6, $e2, $dd, $d9,
|
||||||
|
$d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac, $a8, $a5, $a2, $9f,
|
||||||
|
$9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86, $85, $84, $83, $83,
|
||||||
|
$82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86, $88, $89, $8b, $8c,
|
||||||
|
$8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac, $af, $b2, $b6, $b9,
|
||||||
|
$bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea, $ef, $f3, $f8, $fc,
|
||||||
|
$00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f,
|
||||||
|
$43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70,
|
||||||
|
$72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e
|
||||||
|
]
|
||||||
|
return costab[radians] as byte
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sinr16(ubyte radians) -> word {
|
sub rnd() -> ubyte {
|
||||||
return 4242 ; TODO
|
%ir {{
|
||||||
|
syscall 33
|
||||||
|
return
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cosr16(ubyte radians) -> word {
|
sub rndw() -> uword {
|
||||||
return 4242 ; TODO
|
%ir {{
|
||||||
|
syscall 34
|
||||||
|
return
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sinr16u(ubyte radians) -> uword {
|
sub rndseed(uword seed1, uword seed2) {
|
||||||
return 4242 ; TODO
|
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
|
||||||
}
|
%ir {{
|
||||||
|
loadm.w r65500,math.rndseed.seed1
|
||||||
sub cosr16u(ubyte radians) -> uword {
|
loadm.w r65501,math.rndseed.seed2
|
||||||
return 4242 ; TODO
|
syscall 31
|
||||||
|
return
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,6 @@
|
|||||||
; Internal library routines - always included by the compiler
|
; Internal library routines - always included by the compiler
|
||||||
|
|
||||||
%import textio
|
|
||||||
|
|
||||||
prog8_lib {
|
prog8_lib {
|
||||||
%option force_output
|
%option force_output
|
||||||
|
|
||||||
sub string_contains(ubyte needle, str haystack) -> ubyte {
|
|
||||||
repeat {
|
|
||||||
if @(haystack)==0
|
|
||||||
return false
|
|
||||||
if @(haystack)==needle
|
|
||||||
return true
|
|
||||||
haystack++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub bytearray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
|
|
||||||
haystack_ptr--
|
|
||||||
while num_elements {
|
|
||||||
if haystack_ptr[num_elements]==needle
|
|
||||||
return true
|
|
||||||
num_elements--
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
sub wordarray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
|
|
||||||
haystack_ptr += (num_elements-1) * 2
|
|
||||||
while num_elements {
|
|
||||||
if peekw(haystack_ptr)==needle
|
|
||||||
return true
|
|
||||||
haystack_ptr -= 2
|
|
||||||
num_elements--
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
sub string_compare(str st1, str st2) -> byte {
|
|
||||||
; Compares two strings for sorting.
|
|
||||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
|
||||||
; Note that you can also directly compare strings and string values with eachother using
|
|
||||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
|
||||||
%asm {{
|
|
||||||
loadm.w r0, {prog8_lib.string_compare.st1}
|
|
||||||
loadm.w r1, {prog8_lib.string_compare.st2}
|
|
||||||
syscall 29
|
|
||||||
return
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,12 @@ string {
|
|||||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||||
; Note that you can also directly compare strings and string values with eachother using
|
; Note that you can also directly compare strings and string values with eachother using
|
||||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||||
return prog8_lib.string_compare(st1, st2)
|
%ir {{
|
||||||
|
loadm.w r65500,string.compare.st1
|
||||||
|
loadm.w r65501,string.compare.st2
|
||||||
|
syscall 29
|
||||||
|
return
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub lower(str st) -> ubyte {
|
sub lower(str st) -> ubyte {
|
||||||
@ -113,4 +118,36 @@ string {
|
|||||||
ix++
|
ix++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub lowerchar(ubyte char) -> ubyte {
|
||||||
|
if char >= 'A' and char <= 'Z'
|
||||||
|
char |= %00100000
|
||||||
|
return char
|
||||||
|
}
|
||||||
|
|
||||||
|
sub upperchar(ubyte char) -> ubyte {
|
||||||
|
if char >= 'a' and char <= 'z'
|
||||||
|
char &= %11011111
|
||||||
|
return char
|
||||||
|
}
|
||||||
|
|
||||||
|
sub startswith(str st, str prefix) -> bool {
|
||||||
|
ubyte prefix_len = length(prefix)
|
||||||
|
ubyte str_len = length(st)
|
||||||
|
if prefix_len > str_len
|
||||||
|
return false
|
||||||
|
cx16.r9L = st[prefix_len]
|
||||||
|
st[prefix_len] = 0
|
||||||
|
cx16.r9H = compare(st, prefix) as ubyte
|
||||||
|
st[prefix_len] = cx16.r9L
|
||||||
|
return cx16.r9H==0
|
||||||
|
}
|
||||||
|
|
||||||
|
sub endswith(str st, str suffix) -> bool {
|
||||||
|
ubyte suffix_len = length(suffix)
|
||||||
|
ubyte str_len = length(st)
|
||||||
|
if suffix_len > str_len
|
||||||
|
return false
|
||||||
|
return compare(st + str_len - suffix_len, suffix) == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,22 +7,22 @@ sys {
|
|||||||
|
|
||||||
sub reset_system() {
|
sub reset_system() {
|
||||||
; Soft-reset the system back to initial power-on Basic prompt.
|
; Soft-reset the system back to initial power-on Basic prompt.
|
||||||
%asm {{
|
%ir {{
|
||||||
syscall 0
|
syscall 0
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub wait(uword jiffies) {
|
sub wait(uword jiffies) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0, {sys.wait.jiffies}
|
loadm.w r65500,sys.wait.jiffies
|
||||||
syscall 13
|
syscall 13
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub waitvsync() {
|
sub waitvsync() {
|
||||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||||
%asm {{
|
%ir {{
|
||||||
syscall 14
|
syscall 14
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -61,45 +61,52 @@ sys {
|
|||||||
|
|
||||||
sub exit(ubyte returnvalue) {
|
sub exit(ubyte returnvalue) {
|
||||||
; -- immediately exit the program with a return code in the A register
|
; -- immediately exit the program with a return code in the A register
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0,{sys.exit.returnvalue}
|
loadm.b r65500,sys.exit.returnvalue
|
||||||
syscall 1
|
syscall 1
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub set_carry() {
|
sub set_carry() {
|
||||||
%asm {{
|
%ir {{
|
||||||
sec
|
sec
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub clear_carry() {
|
sub clear_carry() {
|
||||||
%asm {{
|
%ir {{
|
||||||
clc
|
clc
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub gfx_enable(ubyte mode) {
|
sub gfx_enable(ubyte mode) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0, {sys.gfx_enable.mode}
|
loadm.b r65500,sys.gfx_enable.mode
|
||||||
syscall 8
|
syscall 8
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub gfx_clear(ubyte color) {
|
||||||
|
%ir {{
|
||||||
|
loadm.b r65500,sys.gfx_clear.color
|
||||||
|
syscall 9
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
sub gfx_plot(uword xx, uword yy, ubyte color) {
|
sub gfx_plot(uword xx, uword yy, ubyte color) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0, {sys.gfx_plot.xx}
|
loadm.w r65500,sys.gfx_plot.xx
|
||||||
loadm.w r1, {sys.gfx_plot.yy}
|
loadm.w r65501,sys.gfx_plot.yy
|
||||||
loadm.b r2, {sys.gfx_plot.color}
|
loadm.b r65502,sys.gfx_plot.color
|
||||||
syscall 10
|
syscall 10
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
|
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0, {sys.gfx_getpixel.xx}
|
loadm.w r65500,sys.gfx_getpixel.xx
|
||||||
loadm.w r1, {sys.gfx_getpixel.yy}
|
loadm.w r65501,sys.gfx_getpixel.yy
|
||||||
syscall 30
|
syscall 30
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
@ -110,105 +117,105 @@ cx16 {
|
|||||||
|
|
||||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||||
; they are simulated on the VirtualMachine as well but their location in memory is different
|
; they are simulated on the VirtualMachine as well but their location in memory is different
|
||||||
&uword r0 = $0002
|
&uword r0 = $ff02
|
||||||
&uword r1 = $0004
|
&uword r1 = $ff04
|
||||||
&uword r2 = $0006
|
&uword r2 = $ff06
|
||||||
&uword r3 = $0008
|
&uword r3 = $ff08
|
||||||
&uword r4 = $000a
|
&uword r4 = $ff0a
|
||||||
&uword r5 = $000c
|
&uword r5 = $ff0c
|
||||||
&uword r6 = $000e
|
&uword r6 = $ff0e
|
||||||
&uword r7 = $0010
|
&uword r7 = $ff10
|
||||||
&uword r8 = $0012
|
&uword r8 = $ff12
|
||||||
&uword r9 = $0014
|
&uword r9 = $ff14
|
||||||
&uword r10 = $0016
|
&uword r10 = $ff16
|
||||||
&uword r11 = $0018
|
&uword r11 = $ff18
|
||||||
&uword r12 = $001a
|
&uword r12 = $ff1a
|
||||||
&uword r13 = $001c
|
&uword r13 = $ff1c
|
||||||
&uword r14 = $001e
|
&uword r14 = $ff1e
|
||||||
&uword r15 = $0020
|
&uword r15 = $ff20
|
||||||
|
|
||||||
&word r0s = $0002
|
&word r0s = $ff02
|
||||||
&word r1s = $0004
|
&word r1s = $ff04
|
||||||
&word r2s = $0006
|
&word r2s = $ff06
|
||||||
&word r3s = $0008
|
&word r3s = $ff08
|
||||||
&word r4s = $000a
|
&word r4s = $ff0a
|
||||||
&word r5s = $000c
|
&word r5s = $ff0c
|
||||||
&word r6s = $000e
|
&word r6s = $ff0e
|
||||||
&word r7s = $0010
|
&word r7s = $ff10
|
||||||
&word r8s = $0012
|
&word r8s = $ff12
|
||||||
&word r9s = $0014
|
&word r9s = $ff14
|
||||||
&word r10s = $0016
|
&word r10s = $ff16
|
||||||
&word r11s = $0018
|
&word r11s = $ff18
|
||||||
&word r12s = $001a
|
&word r12s = $ff1a
|
||||||
&word r13s = $001c
|
&word r13s = $ff1c
|
||||||
&word r14s = $001e
|
&word r14s = $ff1e
|
||||||
&word r15s = $0020
|
&word r15s = $ff20
|
||||||
|
|
||||||
&ubyte r0L = $0002
|
&ubyte r0L = $ff02
|
||||||
&ubyte r1L = $0004
|
&ubyte r1L = $ff04
|
||||||
&ubyte r2L = $0006
|
&ubyte r2L = $ff06
|
||||||
&ubyte r3L = $0008
|
&ubyte r3L = $ff08
|
||||||
&ubyte r4L = $000a
|
&ubyte r4L = $ff0a
|
||||||
&ubyte r5L = $000c
|
&ubyte r5L = $ff0c
|
||||||
&ubyte r6L = $000e
|
&ubyte r6L = $ff0e
|
||||||
&ubyte r7L = $0010
|
&ubyte r7L = $ff10
|
||||||
&ubyte r8L = $0012
|
&ubyte r8L = $ff12
|
||||||
&ubyte r9L = $0014
|
&ubyte r9L = $ff14
|
||||||
&ubyte r10L = $0016
|
&ubyte r10L = $ff16
|
||||||
&ubyte r11L = $0018
|
&ubyte r11L = $ff18
|
||||||
&ubyte r12L = $001a
|
&ubyte r12L = $ff1a
|
||||||
&ubyte r13L = $001c
|
&ubyte r13L = $ff1c
|
||||||
&ubyte r14L = $001e
|
&ubyte r14L = $ff1e
|
||||||
&ubyte r15L = $0020
|
&ubyte r15L = $ff20
|
||||||
|
|
||||||
&ubyte r0H = $0003
|
&ubyte r0H = $ff03
|
||||||
&ubyte r1H = $0005
|
&ubyte r1H = $ff05
|
||||||
&ubyte r2H = $0007
|
&ubyte r2H = $ff07
|
||||||
&ubyte r3H = $0009
|
&ubyte r3H = $ff09
|
||||||
&ubyte r4H = $000b
|
&ubyte r4H = $ff0b
|
||||||
&ubyte r5H = $000d
|
&ubyte r5H = $ff0d
|
||||||
&ubyte r6H = $000f
|
&ubyte r6H = $ff0f
|
||||||
&ubyte r7H = $0011
|
&ubyte r7H = $ff11
|
||||||
&ubyte r8H = $0013
|
&ubyte r8H = $ff13
|
||||||
&ubyte r9H = $0015
|
&ubyte r9H = $ff15
|
||||||
&ubyte r10H = $0017
|
&ubyte r10H = $ff17
|
||||||
&ubyte r11H = $0019
|
&ubyte r11H = $ff19
|
||||||
&ubyte r12H = $001b
|
&ubyte r12H = $ff1b
|
||||||
&ubyte r13H = $001d
|
&ubyte r13H = $ff1d
|
||||||
&ubyte r14H = $001f
|
&ubyte r14H = $ff1f
|
||||||
&ubyte r15H = $0021
|
&ubyte r15H = $ff21
|
||||||
|
|
||||||
&byte r0sL = $0002
|
&byte r0sL = $ff02
|
||||||
&byte r1sL = $0004
|
&byte r1sL = $ff04
|
||||||
&byte r2sL = $0006
|
&byte r2sL = $ff06
|
||||||
&byte r3sL = $0008
|
&byte r3sL = $ff08
|
||||||
&byte r4sL = $000a
|
&byte r4sL = $ff0a
|
||||||
&byte r5sL = $000c
|
&byte r5sL = $ff0c
|
||||||
&byte r6sL = $000e
|
&byte r6sL = $ff0e
|
||||||
&byte r7sL = $0010
|
&byte r7sL = $ff10
|
||||||
&byte r8sL = $0012
|
&byte r8sL = $ff12
|
||||||
&byte r9sL = $0014
|
&byte r9sL = $ff14
|
||||||
&byte r10sL = $0016
|
&byte r10sL = $ff16
|
||||||
&byte r11sL = $0018
|
&byte r11sL = $ff18
|
||||||
&byte r12sL = $001a
|
&byte r12sL = $ff1a
|
||||||
&byte r13sL = $001c
|
&byte r13sL = $ff1c
|
||||||
&byte r14sL = $001e
|
&byte r14sL = $ff1e
|
||||||
&byte r15sL = $0020
|
&byte r15sL = $ff20
|
||||||
|
|
||||||
&byte r0sH = $0003
|
&byte r0sH = $ff03
|
||||||
&byte r1sH = $0005
|
&byte r1sH = $ff05
|
||||||
&byte r2sH = $0007
|
&byte r2sH = $ff07
|
||||||
&byte r3sH = $0009
|
&byte r3sH = $ff09
|
||||||
&byte r4sH = $000b
|
&byte r4sH = $ff0b
|
||||||
&byte r5sH = $000d
|
&byte r5sH = $ff0d
|
||||||
&byte r6sH = $000f
|
&byte r6sH = $ff0f
|
||||||
&byte r7sH = $0011
|
&byte r7sH = $ff11
|
||||||
&byte r8sH = $0013
|
&byte r8sH = $ff13
|
||||||
&byte r9sH = $0015
|
&byte r9sH = $ff15
|
||||||
&byte r10sH = $0017
|
&byte r10sH = $ff17
|
||||||
&byte r11sH = $0019
|
&byte r11sH = $ff19
|
||||||
&byte r12sH = $001b
|
&byte r12sH = $ff1b
|
||||||
&byte r13sH = $001d
|
&byte r13sH = $ff1d
|
||||||
&byte r14sH = $001f
|
&byte r14sH = $ff1f
|
||||||
&byte r15sH = $0021
|
&byte r15sH = $ff21
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ txt {
|
|||||||
|
|
||||||
sub clear_screen() {
|
sub clear_screen() {
|
||||||
str @shared sequence = "\x1b[2J\x1B[H"
|
str @shared sequence = "\x1b[2J\x1B[H"
|
||||||
%asm {{
|
%ir {{
|
||||||
load.w r0, {txt.clear_screen.sequence}
|
load.w r65500,txt.clear_screen.sequence
|
||||||
syscall 3
|
syscall 3
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -29,15 +29,15 @@ sub uppercase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub chrout(ubyte char) {
|
sub chrout(ubyte char) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0, {txt.chrout.char}
|
loadm.b r65500,txt.chrout.char
|
||||||
syscall 2
|
syscall 2
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print (str text) {
|
sub print (str text) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0, {txt.print.text}
|
loadm.w r65500,txt.print.text
|
||||||
syscall 3
|
syscall 3
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -113,11 +113,26 @@ sub print_w (word value) {
|
|||||||
sub input_chars (uword buffer) -> ubyte {
|
sub input_chars (uword buffer) -> ubyte {
|
||||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
||||||
; It assumes the keyboard is selected as I/O channel!
|
; It assumes the keyboard is selected as I/O channel!
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,{txt.input_chars.buffer}
|
loadm.w r65500,txt.input_chars.buffer
|
||||||
syscall 6
|
syscall 6
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub plot (ubyte col, ubyte row) {
|
||||||
|
; use ANSI escape sequence to position the cursor
|
||||||
|
txt.chrout(27)
|
||||||
|
txt.chrout('[')
|
||||||
|
txt.print_ub(row)
|
||||||
|
txt.chrout(';')
|
||||||
|
txt.print_ub(col)
|
||||||
|
txt.chrout('H')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setchr (ubyte col, ubyte row, ubyte char) {
|
||||||
|
plot(col, row)
|
||||||
|
txt.chrout(char)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
8.3.1
|
8.7
|
||||||
|
@ -12,9 +12,10 @@ import prog8.compiler.compileProgram
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.nio.file.StandardWatchEventKinds
|
import java.nio.file.StandardWatchEventKinds
|
||||||
|
import java.nio.file.WatchKey
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import kotlin.io.path.Path
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
@ -34,23 +35,24 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
|
|||||||
|
|
||||||
private fun compileMain(args: Array<String>): Boolean {
|
private fun compileMain(args: Array<String>): Boolean {
|
||||||
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
||||||
|
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
|
||||||
|
val symbolDefs by cli.option(ArgType.String, fullName = "D", description = "define assembly symbol(s) with -D SYMBOL=VALUE").multiple()
|
||||||
|
val evalStackAddrString by cli.option(ArgType.String, fullName = "esa", description = "override the eval-stack base address (must be page aligned)")
|
||||||
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||||
|
val keepIR by cli.option(ArgType.Boolean, fullName = "keepIR", description = "keep the IR code file (for targets that use it)")
|
||||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||||
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
|
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
|
||||||
|
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||||
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
|
||||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
|
||||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||||
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
|
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
|
||||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
|
|
||||||
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 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 startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a p8-virt listing in the VM instead")
|
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
|
||||||
val symbolDefs by cli.option(ArgType.String, fullName = "D", description = "define assembly symbol(s) with -D SYMBOL=VALUE").multiple()
|
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
|
||||||
val evalStackAddrString by cli.option(ArgType.String, fullName = "esa", description = "override the eval-stack base address (must be page aligned)")
|
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 moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -125,6 +127,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
quietAssembler == true,
|
quietAssembler == true,
|
||||||
asmListfile == true,
|
asmListfile == true,
|
||||||
experimentalCodegen == true,
|
experimentalCodegen == true,
|
||||||
|
keepIR == true,
|
||||||
compilationTarget,
|
compilationTarget,
|
||||||
evalStackAddr,
|
evalStackAddr,
|
||||||
processedSymbols,
|
processedSymbols,
|
||||||
@ -143,21 +146,31 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
for (importedFile in allImportedFiles) {
|
for (importedFile in allImportedFiles) {
|
||||||
print(" ")
|
print(" ")
|
||||||
println(importedFile)
|
println(importedFile)
|
||||||
val watchDir = importedFile.parent ?: Path.of("")
|
val watchDir = importedFile.parent ?: Path("")
|
||||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||||
}
|
}
|
||||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||||
|
|
||||||
|
fun determineRecompilationNeeded(event: WatchKey): Boolean {
|
||||||
|
if(event.isValid) {
|
||||||
|
for (changed in event.pollEvents()) {
|
||||||
|
if (changed.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||||
|
val changedPath = changed.context() as Path
|
||||||
|
if (allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
||||||
|
println(" change detected: $changedPath")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var recompile=false
|
var recompile=false
|
||||||
while(!recompile) {
|
while(!recompile) {
|
||||||
val event = watchservice.take()
|
val event = watchservice.take()
|
||||||
for (changed in event.pollEvents()) {
|
Thread.sleep(50) // avoid multiple events on same file
|
||||||
val changedPath = changed.context() as Path
|
recompile = determineRecompilationNeeded(event)
|
||||||
if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
|
||||||
println(" change detected: $changedPath")
|
|
||||||
recompile = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event.reset()
|
event.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,6 +192,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
quietAssembler == true,
|
quietAssembler == true,
|
||||||
asmListfile == true,
|
asmListfile == true,
|
||||||
experimentalCodegen == true,
|
experimentalCodegen == true,
|
||||||
|
keepIR == true,
|
||||||
compilationTarget,
|
compilationTarget,
|
||||||
evalStackAddr,
|
evalStackAddr,
|
||||||
processedSymbols,
|
processedSymbols,
|
||||||
@ -234,13 +248,9 @@ private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runVm(listingFilename: String): Boolean {
|
fun runVm(irFilename: String): Boolean {
|
||||||
val name =
|
val irFile = Path(irFilename)
|
||||||
if(listingFilename.endsWith(".p8virt"))
|
|
||||||
listingFilename.substring(0, listingFilename.length-7)
|
|
||||||
else
|
|
||||||
listingFilename
|
|
||||||
val vmdef = VirtualMachineDefinition()
|
val vmdef = VirtualMachineDefinition()
|
||||||
vmdef.launchEmulator(0, Paths.get(name))
|
vmdef.launchEmulator(0, irFile)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import prog8.ast.walk.IAstVisitor
|
|||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.*
|
import prog8.code.target.*
|
||||||
|
import prog8.codegen.vm.VmCodeGen
|
||||||
import prog8.compiler.astprocessing.*
|
import prog8.compiler.astprocessing.*
|
||||||
import prog8.optimizer.*
|
import prog8.optimizer.*
|
||||||
import prog8.parser.ParseError
|
import prog8.parser.ParseError
|
||||||
@ -37,6 +38,7 @@ class CompilerArguments(val filepath: Path,
|
|||||||
val quietAssembler: Boolean,
|
val quietAssembler: Boolean,
|
||||||
val asmListfile: Boolean,
|
val asmListfile: Boolean,
|
||||||
val experimentalCodegen: Boolean,
|
val experimentalCodegen: Boolean,
|
||||||
|
val keepIR: Boolean,
|
||||||
val compilationTarget: String,
|
val compilationTarget: String,
|
||||||
val evalStackBaseAddress: UInt?,
|
val evalStackBaseAddress: UInt?,
|
||||||
val symbolDefs: Map<String, String>,
|
val symbolDefs: Map<String, String>,
|
||||||
@ -79,6 +81,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
asmQuiet = args.quietAssembler
|
asmQuiet = args.quietAssembler
|
||||||
asmListfile = args.asmListfile
|
asmListfile = args.asmListfile
|
||||||
experimentalCodegen = args.experimentalCodegen
|
experimentalCodegen = args.experimentalCodegen
|
||||||
|
keepIR = args.keepIR
|
||||||
evalStackBaseAddress = args.evalStackBaseAddress
|
evalStackBaseAddress = args.evalStackBaseAddress
|
||||||
outputDir = args.outputDir.normalize()
|
outputDir = args.outputDir.normalize()
|
||||||
symbolDefs = args.symbolDefs
|
symbolDefs = args.symbolDefs
|
||||||
@ -328,7 +331,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
|||||||
|
|
||||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
println("Analyzing code...")
|
println("Analyzing code...")
|
||||||
program.preprocessAst(errors, compilerOptions.compTarget)
|
program.preprocessAst(errors, compilerOptions)
|
||||||
program.checkIdentifiers(errors, compilerOptions)
|
program.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
|
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
|
||||||
@ -339,7 +342,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
|||||||
errors.report()
|
errors.report()
|
||||||
program.reorderStatements(errors, compilerOptions)
|
program.reorderStatements(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
program.changeNotExpression(errors)
|
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
program.addTypecasts(errors, compilerOptions)
|
program.addTypecasts(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
@ -358,7 +361,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
|||||||
remover.applyModifications()
|
remover.applyModifications()
|
||||||
while (true) {
|
while (true) {
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
val optsDone1 = program.simplifyExpressions()
|
val optsDone1 = program.simplifyExpressions(errors, compTarget)
|
||||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
||||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||||
val optsDone4 = program.inlineSubroutines()
|
val optsDone4 = program.inlineSubroutines()
|
||||||
@ -391,7 +394,7 @@ private fun createAssemblyAndAssemble(program: Program,
|
|||||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
val symbolTable = SymbolTableMaker().makeFrom(program)
|
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
|
||||||
|
|
||||||
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
||||||
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
||||||
@ -445,18 +448,15 @@ internal fun asmGeneratorFor(program: Program,
|
|||||||
options: CompilationOptions): IAssemblyGenerator
|
options: CompilationOptions): IAssemblyGenerator
|
||||||
{
|
{
|
||||||
if(options.experimentalCodegen) {
|
if(options.experimentalCodegen) {
|
||||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) {
|
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||||
// TODO for now, use the new Intermediary Ast for this experimental codegen:
|
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
|
||||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
|
||||||
return prog8.codegen.experimental.AsmGen(intermediateAst, symbolTable, options, errors)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||||
// TODO rewrite 6502 codegen on new Intermediary Ast
|
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
|
||||||
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
|
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
|
||||||
if (options.compTarget.name == VMTarget.NAME) {
|
if (options.compTarget.name == VMTarget.NAME) {
|
||||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||||
return prog8.codegen.virtual.CodeGen(intermediateAst, symbolTable, options, errors)
|
return VmCodeGen(intermediateAst, symbolTable, options, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.VMTarget
|
|
||||||
import prog8.compiler.BuiltinFunctions
|
import prog8.compiler.BuiltinFunctions
|
||||||
import prog8.compiler.InplaceModifyingBuiltinFunctions
|
import prog8.compiler.InplaceModifyingBuiltinFunctions
|
||||||
import prog8.compiler.builtinFunctionReturnType
|
import prog8.compiler.builtinFunctionReturnType
|
||||||
@ -202,16 +201,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(jump)
|
super.visit(jump)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(gosub: GoSub) {
|
|
||||||
val targetStatement = checkFunctionOrLabelExists(gosub.identifier, gosub)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
if(targetStatement is BuiltinFunctionPlaceholder)
|
|
||||||
errors.err("can't gosub to a builtin function", gosub.position)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.visit(gosub)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
override fun visit(block: Block) {
|
||||||
val addr = block.address
|
val addr = block.address
|
||||||
if(addr!=null && addr>65535u) {
|
if(addr!=null && addr>65535u) {
|
||||||
@ -262,19 +251,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(inlineAssembly: InlineAssembly) {
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
val assembly = inlineAssembly.assembly
|
if(inlineAssembly.hasReturnOrRts(compilerOptions.compTarget))
|
||||||
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
count++
|
||||||
if (" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
|
||||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
|
|
||||||
)
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
if(" return" in assembly || "\treturn" in assembly
|
|
||||||
|| " jump" in assembly || "\tjump" in assembly
|
|
||||||
|| " jumpi" in assembly || "\tjumpi" in assembly
|
|
||||||
)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +291,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
if (subroutine.returntypes.isNotEmpty()) {
|
if (subroutine.returntypes.isNotEmpty()) {
|
||||||
// for asm subroutines with an address, no statement check is possible.
|
// 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 'goto' in it (or rts/jmp/bra in case of %asm)")
|
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or the assembler equivalent in case of %asm)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,32 +305,32 @@ internal class AstChecker(private val program: Program,
|
|||||||
err("number of return registers is not the isSameAs as number of return values")
|
err("number of return registers is not the isSameAs as number of return values")
|
||||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||||
if(param.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
if(param.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||||
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
|
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE && param.first.type != DataType.BOOL)
|
||||||
err("parameter '${param.first.name}' should be (u)byte")
|
err("parameter '${param.first.name}' should be (u)byte or bool")
|
||||||
}
|
}
|
||||||
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||||
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes)
|
||||||
err("parameter '${param.first.name}' should be (u)word (an address) or str")
|
err("parameter '${param.first.name}' should be (u)word (an address) or str")
|
||||||
}
|
}
|
||||||
else if(param.second.statusflag!=null) {
|
else if(param.second.statusflag!=null) {
|
||||||
if (param.first.type != DataType.UBYTE)
|
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
|
||||||
err("parameter '${param.first.name}' should be ubyte")
|
err("parameter '${param.first.name}' should be bool or ubyte")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||||
if(pair.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
if(pair.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
|
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE && pair.first != DataType.BOOL)
|
||||||
err("return value #${index + 1} should be (u)byte")
|
err("return type #${index + 1} should be (u)byte")
|
||||||
}
|
}
|
||||||
else if(pair.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes)
|
||||||
err("return value #${index + 1} should be (u)word/address")
|
err("return type #${index + 1} should be (u)word/address")
|
||||||
}
|
}
|
||||||
else if(pair.second.statusflag!=null) {
|
else if(pair.second.statusflag!=null) {
|
||||||
if (pair.first != DataType.UBYTE)
|
if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
|
||||||
err("return value #${index + 1} should be ubyte")
|
err("return type #${index + 1} should be bool or ubyte")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,7 +608,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
err("memory address must be valid integer 0..\$ffff")
|
err("memory address must be valid integer 0..\$ffff")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?")
|
err("value of memory mapped variable can only be a constant, maybe use an address pointer type instead?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1018,7 +996,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
// It's not (yet) possible to handle these multiple return values because assignments
|
// It's not (yet) possible to handle these multiple return values because assignments
|
||||||
// are only to a single unique target at the same time.
|
// are only to a single unique target at the same time.
|
||||||
// EXCEPTION:
|
// EXCEPTION:
|
||||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||||
// it *is* possible to handle them by just actually assigning the register value and
|
// it *is* possible to handle them by just actually assigning the register value and
|
||||||
// dealing with the status bit as just being that, the status bit after the call.
|
// dealing with the status bit as just being that, the status bit after the call.
|
||||||
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||||
|
@ -6,11 +6,12 @@ import prog8.ast.expressions.CharLiteral
|
|||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.ast.expressions.NumericLiteral
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.ast.statements.InlineAssembly
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.statements.VarDeclOrigin
|
import prog8.ast.statements.VarDeclOrigin
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.compiler.printProgram
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
@ -48,8 +49,8 @@ internal fun Program.reorderStatements(errors: IErrorReporter, options: Compilat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.changeNotExpression(errors: IErrorReporter) {
|
internal fun Program.changeNotExpressionAndIfComparisonExpr(errors: IErrorReporter, target: ICompilationTarget) {
|
||||||
val changer = NotExpressionChanger(this, errors)
|
val changer = NotExpressionAndIfComparisonExprChanger(this, errors, target)
|
||||||
changer.visit(this)
|
changer.visit(this)
|
||||||
while(errors.noErrors() && changer.applyModifications()>0) {
|
while(errors.noErrors() && changer.applyModifications()>0) {
|
||||||
changer.visit(this)
|
changer.visit(this)
|
||||||
@ -92,8 +93,8 @@ internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter) {
|
|||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.preprocessAst(errors: IErrorReporter, target: ICompilationTarget) {
|
internal fun Program.preprocessAst(errors: IErrorReporter, options: CompilationOptions) {
|
||||||
val transforms = AstPreprocessor(this, errors, target)
|
val transforms = AstPreprocessor(this, errors, options)
|
||||||
transforms.visit(this)
|
transforms.visit(this)
|
||||||
var mods = transforms.applyModifications()
|
var mods = transforms.applyModifications()
|
||||||
while(mods>0)
|
while(mods>0)
|
||||||
@ -166,3 +167,10 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
|
||||||
|
return statements
|
||||||
|
.asSequence()
|
||||||
|
.filterIsInstance<InlineAssembly>()
|
||||||
|
.any { it.hasReturnOrRts(compTarget) }
|
||||||
|
}
|
@ -150,6 +150,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||||
|
|
||||||
private fun visitFunctionCall(call: IFunctionCall) {
|
private fun visitFunctionCall(call: IFunctionCall) {
|
||||||
|
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
|
||||||
|
val target = call.target.targetStatement(program)
|
||||||
|
if(target==null) {
|
||||||
|
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
when (val target = call.target.targetStatement(program)) {
|
when (val target = call.target.targetStatement(program)) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
val expectedNumberOfArgs: Int = target.parameters.size
|
val expectedNumberOfArgs: Int = target.parameters.size
|
||||||
|
@ -18,9 +18,8 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
|||||||
|
|
||||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(parent !is VarDecl) {
|
if(parent !is VarDecl) {
|
||||||
// TODO move this / remove this, and make the codegen better instead.
|
if(options.compTarget.name == VMTarget.NAME)
|
||||||
// If the expression is pointervar[idx] where pointervar is uword and not a real array,
|
return noModifications // vm codegen deals correctly with all cases
|
||||||
// replace it by a @(pointervar+idx) expression.
|
|
||||||
// Don't replace the initializer value in a vardecl - this will be moved to a separate
|
// Don't replace the initializer value in a vardecl - this will be moved to a separate
|
||||||
// assignment statement soon in after(VarDecl)
|
// assignment statement soon in after(VarDecl)
|
||||||
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
|
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
|
||||||
@ -29,15 +28,16 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(options.compTarget.name== VMTarget.NAME)
|
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
|
||||||
return noModifications // vm codegen deals correctly with all cases
|
// into account the case where the index value is a word type.
|
||||||
|
// The replacement here is to fix missing cases in the 6502 codegen.
|
||||||
|
// TODO make the 6502 codegen better so this work around can be removed
|
||||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||||
if(parent is AssignTarget) {
|
if(parent is AssignTarget) {
|
||||||
val assignment = parent.parent as? Assignment
|
val assignment = parent.parent as? Assignment
|
||||||
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
|
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
|
||||||
// ONLY for a constant assignment, or direct variable assignment, the codegen contains correct optimized code.
|
// the codegen contains correct optimized code ONLY for a constant assignment, or direct variable assignment.
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.
|
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.
|
||||||
|
@ -8,39 +8,53 @@ import prog8.ast.statements.*
|
|||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.Cx16Target
|
import prog8.code.target.Cx16Target
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
|
||||||
|
|
||||||
class AstPreprocessor(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
|
class AstPreprocessor(val program: Program,
|
||||||
|
val errors: IErrorReporter,
|
||||||
|
val options: CompilationOptions) : AstWalker() {
|
||||||
|
|
||||||
override fun before(program: Program): Iterable<IAstModification> {
|
override fun before(program: Program): Iterable<IAstModification> {
|
||||||
if(compTarget.name!=Cx16Target.NAME) {
|
if(options.compTarget.name==C64Target.NAME) {
|
||||||
// reset the address of the virtual registers to be inside the evaluation stack.
|
if(options.zeropage==ZeropageType.KERNALSAFE || options.zeropage==ZeropageType.FULL) {
|
||||||
// (we don't do this on CommanderX16 itself as the registers have a fixed location in Zeropage there)
|
// there is enough space in the zero page to put the cx16 virtual registers there.
|
||||||
val cx16block = program.allBlocks.single { it.name=="cx16" }
|
// unfortunately, can't be the same address as CommanderX16.
|
||||||
val memVars = cx16block.statements
|
relocateCx16VirtualRegisters(program, 0x0004u)
|
||||||
.filterIsInstance<VarDecl>()
|
|
||||||
.associateBy { it.name }
|
|
||||||
|
|
||||||
val estack = compTarget.machine.ESTACK_HI
|
|
||||||
for(regnum in 0u..15u) {
|
|
||||||
val rX = memVars.getValue("r$regnum")
|
|
||||||
val rXL = memVars.getValue("r${regnum}L")
|
|
||||||
val rXH = memVars.getValue("r${regnum}H")
|
|
||||||
val rXs = memVars.getValue("r${regnum}s")
|
|
||||||
val rXsL = memVars.getValue("r${regnum}sL")
|
|
||||||
val rXsH = memVars.getValue("r${regnum}sH")
|
|
||||||
setAddress(rX, estack + 2u*regnum)
|
|
||||||
setAddress(rXL, estack + 2u*regnum)
|
|
||||||
setAddress(rXH, estack + 2u*regnum +1u)
|
|
||||||
setAddress(rXs, estack + 2u*regnum)
|
|
||||||
setAddress(rXsL, estack + 2u*regnum)
|
|
||||||
setAddress(rXsH, estack + 2u*regnum + 1u)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(options.compTarget.name !in setOf(Cx16Target.NAME, VMTarget.NAME)) {
|
||||||
|
relocateCx16VirtualRegisters(program, options.compTarget.machine.ESTACK_HI)
|
||||||
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun relocateCx16VirtualRegisters(program: Program, baseAddress: UInt) {
|
||||||
|
// reset the address of the virtual registers to be inside the evaluation stack.
|
||||||
|
// (we don't do this on CommanderX16 itself as the registers have a fixed location in Zeropage there)
|
||||||
|
val cx16block = program.allBlocks.single { it.name == "cx16" }
|
||||||
|
val memVars = cx16block.statements
|
||||||
|
.filterIsInstance<VarDecl>()
|
||||||
|
.associateBy { it.name }
|
||||||
|
|
||||||
|
for (regnum in 0u..15u) {
|
||||||
|
val rX = memVars.getValue("r$regnum")
|
||||||
|
val rXL = memVars.getValue("r${regnum}L")
|
||||||
|
val rXH = memVars.getValue("r${regnum}H")
|
||||||
|
val rXs = memVars.getValue("r${regnum}s")
|
||||||
|
val rXsL = memVars.getValue("r${regnum}sL")
|
||||||
|
val rXsH = memVars.getValue("r${regnum}sH")
|
||||||
|
setAddress(rX, baseAddress + 2u * regnum)
|
||||||
|
setAddress(rXL, baseAddress + 2u * regnum)
|
||||||
|
setAddress(rXH, baseAddress + 2u * regnum + 1u)
|
||||||
|
setAddress(rXs, baseAddress + 2u * regnum)
|
||||||
|
setAddress(rXsL, baseAddress + 2u * regnum)
|
||||||
|
setAddress(rXsH, baseAddress + 2u * regnum + 1u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setAddress(vardecl: VarDecl, address: UInt) {
|
private fun setAddress(vardecl: VarDecl, address: UInt) {
|
||||||
val oldAddr = vardecl.value as NumericLiteral
|
val oldAddr = vardecl.value as NumericLiteral
|
||||||
vardecl.value = NumericLiteral(oldAddr.type, address.toDouble(), oldAddr.position)
|
vardecl.value = NumericLiteral(oldAddr.type, address.toDouble(), oldAddr.position)
|
||||||
@ -48,13 +62,13 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
|
|||||||
|
|
||||||
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||||
if(char.encoding== Encoding.DEFAULT)
|
if(char.encoding== Encoding.DEFAULT)
|
||||||
char.encoding = compTarget.defaultEncoding
|
char.encoding = options.compTarget.defaultEncoding
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||||
if(string.encoding==Encoding.DEFAULT)
|
if(string.encoding==Encoding.DEFAULT)
|
||||||
string.encoding = compTarget.defaultEncoding
|
string.encoding = options.compTarget.defaultEncoding
|
||||||
return super.before(string, parent)
|
return super.before(string, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.IStatementContainer
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.getTempVar
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
@ -132,15 +135,20 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
val mods = mutableListOf<IAstModification>()
|
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.
|
// 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 some other situations.
|
// and if an assembly block doesn't contain a rts/rti.
|
||||||
if (!subroutine.isAsmSubroutine) {
|
if (!subroutine.isAsmSubroutine) {
|
||||||
if(subroutine.statements.isEmpty() ||
|
if(subroutine.isEmpty()) {
|
||||||
(subroutine.amountOfRtsInAsm() == 0
|
|
||||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
|
||||||
&& subroutine.statements.last() !is Subroutine
|
|
||||||
&& subroutine.statements.last() !is Return)) {
|
|
||||||
val returnStmt = Return(null, subroutine.position)
|
val returnStmt = Return(null, subroutine.position)
|
||||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
} else {
|
||||||
|
val last = subroutine.statements.last()
|
||||||
|
if((last !is InlineAssembly || !last.hasReturnOrRts(options.compTarget)) && last !is Return) {
|
||||||
|
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
|
||||||
|
if(lastStatement !is Return) {
|
||||||
|
val returnStmt = Return(null, subroutine.position)
|
||||||
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,10 +169,21 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!subroutine.inline || !options.optimize) {
|
if (!subroutine.inline || !options.optimize) {
|
||||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && subroutine.amountOfRtsInAsm() == 0) {
|
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
|
||||||
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
||||||
// (non-asm routines get a Return statement as needed, above)
|
// (non-asm routines get a Return statement as needed, above)
|
||||||
mods += IAstModification.InsertLast(InlineAssembly(" rts\n", Position.DUMMY), subroutine)
|
mods += if(options.compTarget.name==VMTarget.NAME)
|
||||||
|
IAstModification.InsertLast(InlineAssembly(" return\n", true, Position.DUMMY), subroutine)
|
||||||
|
else
|
||||||
|
IAstModification.InsertLast(InlineAssembly(" rts\n", false, Position.DUMMY), subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subroutine.isNotEmpty() && subroutine.statements.last() is Return) {
|
||||||
|
// maybe the last return can be removed because there is a fall-through prevention above it
|
||||||
|
val lastStatementBefore = subroutine.statements.reversed().drop(1).firstOrNull { it !is Subroutine }
|
||||||
|
if(lastStatementBefore is Return) {
|
||||||
|
mods += IAstModification.Remove(subroutine.statements.last(), subroutine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,88 +234,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
(binExpr.right as? NumericLiteral)?.number!=0.0)
|
(binExpr.right as? NumericLiteral)?.number!=0.0)
|
||||||
throw InternalCompilerException("0==X should have been swapped to if X==0")
|
throw InternalCompilerException("0==X should have been swapped to if X==0")
|
||||||
|
|
||||||
// simplify the conditional expression, introduce simple assignments if required.
|
return noModifications
|
||||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
|
||||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
|
||||||
val simplify = simplifyConditionalExpression(binExpr)
|
|
||||||
val modifications = mutableListOf<IAstModification>()
|
|
||||||
if(simplify.rightVarAssignment!=null) {
|
|
||||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
|
||||||
modifications += IAstModification.InsertBefore(
|
|
||||||
ifElse,
|
|
||||||
simplify.rightVarAssignment,
|
|
||||||
parent as IStatementContainer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(simplify.leftVarAssignment!=null) {
|
|
||||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
|
||||||
modifications += IAstModification.InsertBefore(
|
|
||||||
ifElse,
|
|
||||||
simplify.leftVarAssignment,
|
|
||||||
parent as IStatementContainer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifications
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CondExprSimplificationResult(
|
|
||||||
val leftVarAssignment: Assignment?,
|
|
||||||
val leftOperandReplacement: Expression?,
|
|
||||||
val rightVarAssignment: Assignment?,
|
|
||||||
val rightOperandReplacement: Expression?
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
|
|
||||||
|
|
||||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
|
||||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
|
||||||
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
|
||||||
|
|
||||||
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimization for Vm target
|
|
||||||
return CondExprSimplificationResult(null, null, null, null)
|
|
||||||
|
|
||||||
var leftAssignment: Assignment? = null
|
|
||||||
var leftOperandReplacement: Expression? = null
|
|
||||||
var rightAssignment: Assignment? = null
|
|
||||||
var rightOperandReplacement: Expression? = null
|
|
||||||
|
|
||||||
val separateLeftExpr = !expr.left.isSimple
|
|
||||||
&& expr.left !is IFunctionCall
|
|
||||||
&& expr.left !is ContainmentCheck
|
|
||||||
val separateRightExpr = !expr.right.isSimple
|
|
||||||
&& expr.right !is IFunctionCall
|
|
||||||
&& expr.right !is ContainmentCheck
|
|
||||||
val leftDt = expr.left.inferType(program)
|
|
||||||
val rightDt = expr.right.inferType(program)
|
|
||||||
|
|
||||||
if(!leftDt.isInteger || !rightDt.isInteger) {
|
|
||||||
// we can't reasonably simplify non-integer expressions
|
|
||||||
return CondExprSimplificationResult(null, null, null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(separateLeftExpr) {
|
|
||||||
val name = getTempRegisterName(leftDt)
|
|
||||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
|
||||||
leftAssignment = Assignment(
|
|
||||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
|
||||||
expr.left,
|
|
||||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(separateRightExpr) {
|
|
||||||
val (tempVarName, _) = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
|
|
||||||
rightOperandReplacement = IdentifierReference(tempVarName, expr.position)
|
|
||||||
rightAssignment = Assignment(
|
|
||||||
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
|
|
||||||
expr.right,
|
|
||||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return CondExprSimplificationResult(
|
|
||||||
leftAssignment, leftOperandReplacement,
|
|
||||||
rightAssignment, rightOperandReplacement
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user