Compare commits

..
44 Commits
Author SHA1 Message Date
Irmen de Jong 914f19be86 version 8.9 2023-02-12 17:38:13 +01:00
Irmen de Jong d0b18dec8e shuffle variable sorting around to attempt smaller compiled programs 2023-02-12 17:34:33 +01:00
Irmen de Jong 549c598f51 variables sorted in asm 2023-02-11 14:35:56 +01:00
Irmen de Jong 7b59bc8d12 avoid division by zero if host fs hyperload is used which loads instantly 2023-02-08 01:37:49 +01:00
Irmen de Jong 79d0fb0b52 cx16.numbanks() now returns a word because the result can be >255 2023-02-08 00:51:34 +01:00
Irmen de Jong edf56d34f8 doc about no conditional compilation, fixes #93
also added a note to MEMTOP about 0 result
2023-02-06 23:36:19 +01:00
Irmen de Jong 6733253826 added printer for Pt Ast tree 2023-02-05 16:42:06 +01:00
Irmen de Jong 86210c4513 clarification 2023-02-01 20:58:40 +01:00
Irmen de Jong e08da659e5 got rid of PtScopeVarsDecls node, just insert variable nodes directly 2023-01-29 13:25:15 +01:00
Irmen de Jong e9ec310d8a upgrade to kotlin 1.8.0 2023-01-27 22:14:10 +01:00
Irmen de Jong 19a2791c65 vm target can't use asmsub at all, give better error for that 2023-01-26 01:38:13 +01:00
Irmen de Jong f1a7d5ecf7 docs 2023-01-26 00:37:30 +01:00
Irmen de Jong 8b05abb80d proper error when attempting to refer to parameters of asmsub by name 2023-01-25 23:41:08 +01:00
Irmen de Jong 9a2df072cc tiny correction 2023-01-24 22:48:44 +01:00
Irmen de Jong 224278e07a correct openjdk-11 sdk setting in project files instead of just 11 2023-01-24 01:49:38 +01:00
Irmen de Jong 74b69e191e restructure keyboardhandler example due to X register bug, discussed in #94 2023-01-24 01:30:57 +01:00
Irmen de Jong 8cda8a727c update vtui example to vtui 1.0 2023-01-24 01:00:21 +01:00
Irmen de Jong 4403e4ed62 optimize node renames 2023-01-22 18:26:37 +01:00
Irmen de Jong 6ee270d9d8 make name a var in new ast to allow cheap renames 2023-01-22 17:10:04 +01:00
Irmen de Jong 44fa309d20 tweak action 2023-01-21 15:29:11 +01:00
Irmen de Jong 58d88f3dd4 github action and update tool docs 2023-01-21 14:47:32 +01:00
Irmen de Jong e980c23177 github action 2023-01-21 14:25:17 +01:00
Irmen de Jong 75224321bb github action 2023-01-21 14:19:01 +01:00
Irmen de Jong 801af05b20 github action 2023-01-21 14:02:08 +01:00
Irmen de Jong 7611dbbddc fix action 2023-01-21 13:47:09 +01:00
Irmen de Jong 6d40ca15bc github action 2023-01-21 13:39:30 +01:00
Irmen de Jong 32c1c19224 tweak sys.wait() routines on various targets
add warning to docs about FP usage in IRQ
2023-01-20 03:29:10 +01:00
Irmen de Jong bbf6357222 remove workaround for black cursor at boot as this was recently fixed in the kernal rom. 2023-01-17 23:27:27 +01:00
Irmen de Jong dc16629c24 todo 2023-01-04 23:57:59 +01:00
Irmen de Jong 3718b9d768 less joins 2023-01-02 02:10:38 +01:00
Irmen de Jong c25eb088ec redo 8e730ef93d to avoid larger code generated 2023-01-01 23:43:33 +01:00
Irmen de Jong 3feb3e52f8 optimizing scoped names in zeropage 2022-12-31 03:57:51 +01:00
Irmen de Jong 8e730ef93d optimizing scoped names more and fix scoping of identifier names in arrays (pointers) in SymbolTable 2022-12-31 03:20:20 +01:00
Irmen de Jong e0913a39ab optimizing 2022-12-30 18:50:45 +01:00
Irmen de Jong 7a27fbc001 add params for future changes 2022-12-30 17:43:55 +01:00
Irmen de Jong ee0dbdad35 don't reshuffle 'start' routine to the top. Fixes zsound examples. 2022-12-30 17:12:01 +01:00
Irmen de Jong 9225f88f89 diskio comments 2022-12-30 15:49:53 +01:00
Irmen de Jong a04839dd6b vm: add property for custom breakpoint handler 2022-12-30 15:10:13 +01:00
Irmen de Jong 002006517a rewrite bool=bool^1 into bool=not bool 2022-12-29 19:42:38 +01:00
Irmen de Jong f5b202d438 fix ast type error in float cast to bool 2022-12-28 22:18:21 +01:00
Irmen de Jong a7df094ff4 don't allow ~ on booleans, also introduce SZ and SNZ instructions in IR to complete the conditional-set instruction list. 2022-12-28 21:19:38 +01:00
Irmen de Jong 1e6fa77633 ir: 4 new instructions to branch on signed <0, >0, <=0, >=0 2022-12-28 13:14:20 +01:00
Irmen de Jong eb4cff202c removed redundant branch opcodes in IR: BLT(S), BLE(S). Just use swapped BGT(S), BGE(S). 2022-12-28 12:41:05 +01:00
Irmen de Jong 7ee777f405 vm/ir: for loop is now correctly skipped if loopvar>endvar
this is different still in the 6502 codegen, where it wraps around $00!
2022-12-27 18:12:41 +01:00
59 changed files with 1076 additions and 589 deletions

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
name: Build and Test the Prog8 compiler
on:
push:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install 64tass
run: sudo apt-get update -y && sudo apt-get install -y 64tass
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 11
distribution: adopt
- name: Build and test with Gradle
run: ./gradlew build shadowJar --no-daemon
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v3
with:
name: prog8-compiler-jar-zipped
path: compiler/build/libs/*-all.jar
+1 -1
View File
@@ -19,7 +19,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+35 -32
View File
@@ -12,8 +12,9 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
* The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes)
*/
val flat: Map<List<String>, StNode> by lazy {
val result = mutableMapOf<List<String>, StNode>()
val flat: Map<String, StNode> by lazy {
val result = mutableMapOf<String, StNode>()
fun flatten(node: StNode) {
result[node.scopedName] = node
node.children.values.forEach { flatten(it) }
@@ -54,7 +55,7 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
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: String) = flat[scopedName]
}
@@ -81,38 +82,18 @@ open class StNode(val name: String,
lateinit var parent: StNode
val scopedName: List<String> by lazy {
if(type== StNodeType.GLOBAL)
emptyList()
else
parent.scopedName + name
}
val scopedName: String by lazy { scopedNameList.joinToString(".") }
fun lookup(name: String) =
lookupUnqualified(name)
open fun lookup(scopedName: List<String>) =
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
fun lookupOrElse(name: String, default: () -> StNode) =
lookupUnqualified(name) ?: default()
fun lookupOrElse(scopedName: List<String>, default: () -> StNode) =
lookup(scopedName) ?: default()
open fun lookup(scopedName: String) =
lookup(scopedName.split('.'))
private fun lookupQualified(scopedName: List<String>): StNode? {
// a scoped name refers to a name in another namespace, and always stars from the root.
var node = this
while(node.type!= StNodeType.GLOBAL)
node = node.parent
fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
lookupUnscoped(name) ?: default()
for(name in scopedName) {
if(name in node.children)
node = node.children.getValue(name)
else
return null
}
return node
}
fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
lookup(scopedName.split('.')) ?: default()
private fun lookupUnqualified(name: String): StNode? {
fun lookupUnscoped(name: String): StNode? {
// first consider the builtin functions
var globalscope = this
while(globalscope.type!= StNodeType.GLOBAL)
@@ -138,6 +119,28 @@ open class StNode(val name: String,
children[child.name] = child
child.parent = this
}
private val scopedNameList: List<String> by lazy {
if(type== StNodeType.GLOBAL)
emptyList()
else
parent.scopedNameList + name
}
private fun lookup(scopedName: List<String>): StNode? {
// a scoped name refers to a name in another namespace, and always stars from the root.
var node = this
while(node.type!= StNodeType.GLOBAL)
node = node.parent
for(name in scopedName) {
if(name in node.children)
node = node.children.getValue(name)
else
return null
}
return node
}
}
class StStaticVariable(name: String,
@@ -215,7 +218,7 @@ class StRomSub(name: String,
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 addressOfSymbol: String?)
typealias StString = Pair<String, Encoding>
typealias StArray = List<StArrayElement>
+9 -11
View File
@@ -1,6 +1,8 @@
package prog8.code.ast
import prog8.code.core.*
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.core.Position
import java.nio.file.Path
// New simplified AST for the code generator.
@@ -42,15 +44,17 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
}
sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
val scopedName: List<String> by lazy {
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
// Note that as an exception, the 'name' is not read-only
// but a var. This is to allow for cheap node renames.
val scopedName: String by lazy {
var namedParent: PtNode = this.parent
if(namedParent is PtProgram)
listOf(name)
name
else {
while (namedParent !is PtNamedNode)
namedParent = namedParent.parent
namedParent.scopedName + name
namedParent.scopedName + "." + name
}
}
}
@@ -130,12 +134,6 @@ class PtNop(position: Position): PtNode(position) {
}
class PtScopeVarsDecls(position: Position): PtNode(position) {
override fun printProperties() {}
}
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc.)
inline fun <reified T> findParentNode(node: PtNode): T? {
@@ -33,7 +33,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
is PtNumber -> other is PtNumber && other.type==type && other.number==number
@@ -108,7 +108,7 @@ class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, posit
}
class PtFunctionCall(val functionName: List<String>,
class PtFunctionCall(val name: String,
val void: Boolean,
type: DataType,
position: Position) : PtExpression(type, position) {
@@ -120,14 +120,14 @@ class PtFunctionCall(val functionName: List<String>,
val args: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {
print("${functionName.joinToString(".")} void=$void")
print("$name void=$void")
}
}
class PtIdentifier(val ref: List<String>, val targetName: List<String>, type: DataType, position: Position) : PtExpression(type, position) {
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) {
override fun printProperties() {
print("$ref --> $targetName $type")
print("$name $type")
}
}
+153
View File
@@ -0,0 +1,153 @@
package prog8.code.ast
import prog8.code.core.*
/**
* Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root),
* passing it as a String to the specified receiver function.
*/
fun printAst(root: PtNode, output: (text: String) -> Unit) {
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String {
return when(node) {
is PtAssignTarget -> ""
is PtAssignment -> "<assign>"
is PtBreakpoint -> "%breakpoint"
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
is PtAddressOf -> "&"
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
val str = if(node.void) "void " else ""
str + node.name + "()"
}
is PtContainmentCheck -> "in"
is PtFunctionCall -> {
val str = if(node.void) "void " else ""
str + node.name + "()"
}
is PtIdentifier -> "${node.name} ${type(node.type)}"
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
is PtMemoryByte -> "@()"
is PtNumber -> "${node.number.toHex()} ${type(node.type)}"
is PtPrefix -> node.operator
is PtRange -> "<range>"
is PtString -> "\"${node.value.escape()}\""
is PtTypeCast -> "as ${node.type.name.lowercase()}"
is PtForLoop -> "for"
is PtIfElse -> "ifelse"
is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}"
is PtInlineAssembly -> {
if(node.isIR)
"%ir {{ ...${node.assembly.length} characters... }}"
else
"%asm {{ ...${node.assembly.length} characters... }}"
}
is PtJump -> {
if(node.identifier!=null)
"goto ${node.identifier.name}"
else if(node.address!=null)
"goto ${node.address.toHex()}"
else if(node.generatedLabel!=null)
"goto ${node.generatedLabel}"
else
"???"
}
is PtAsmSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returnTypes.isEmpty()) "" else (if (node.returnTypes.size == 1) "-> ${node.returnTypes[0].name.lowercase()}" else "-> ${node.returnTypes.map { it.name.lowercase() }}")
val str = if (node.inline) "inline " else ""
if(node.address==null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
} else {
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
}
}
is PtBlock -> {
val addr = if(node.address==null) "" else "@${node.address?.toHex()}"
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
"\nblock '${node.name}' $addr $align"
}
is PtConstant -> {
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
"const ${node.type.name.lowercase()} ${node.name} = $value"
}
is PtLabel -> "${node.name}:"
is PtMemMapped -> {
if(node.type in ArrayDatatypes) {
val arraysize = if(node.arraySize==null) "" else node.arraySize.toString()
val eltType = ArrayToElementTypes.getValue(node.type)
"&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}"
} else {
"&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}"
}
}
is PtSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
var str = if(node.inline) "inline " else ""
str += "sub ${node.name}($params) "
if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}"
str
}
is PtVariable -> {
val str = if(node.arraySize!=null) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
}
else if(node.type in ArrayDatatypes) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[] ${node.name}"
}
else
"${node.type.name.lowercase()} ${node.name}"
if(node.value!=null)
str + " = " + txt(node.value!!)
else
str
}
is PtNodeGroup -> "<group>"
is PtNop -> "nop"
is PtPostIncrDecr -> "<post> ${node.operator}"
is PtProgram -> "PROGRAM ${node.name}"
is PtRepeatLoop -> "repeat"
is PtReturn -> "return"
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
is PtWhen -> "when"
is PtWhenChoice -> {
if(node.isElse)
"else"
else
"->"
}
else -> throw InternalCompilerException("unrecognised ast node $node")
}
}
if(root is PtProgram) {
output(txt(root))
root.children.forEach {
walkAst(it) { node, depth ->
val txt = txt(node)
if(txt.isNotEmpty())
output(" ".repeat(depth) + txt(node))
}
}
} else {
walkAst(root) { node, depth ->
val txt = txt(node)
if(txt.isNotEmpty())
output(" ".repeat(depth) + txt(node))
}
}
}
fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) {
fun recurse(node: PtNode, depth: Int) {
act(node, depth)
node.children.forEach { recurse(it, depth+1) }
}
recurse(root, 0)
}
+3 -3
View File
@@ -66,7 +66,7 @@ class PtAssignment(position: Position) : PtNode(position) {
}
false
}
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.name==source.name
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
@@ -74,7 +74,7 @@ class PtAssignment(position: Position) : PtNode(position) {
is PtPrefix -> {
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
(target is PtIdentifier && (source.value as? PtIdentifier)?.name==target.name)
}
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
is PtBinaryExpression ->
@@ -202,7 +202,7 @@ class PtConstant(name: String, val type: DataType, val value: Double, position:
}
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) {
class PtMemMapped(name: String, val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("&$type $name = ${address.toHex()}")
}
@@ -100,12 +100,12 @@ class AsmGen(internal val program: Program,
fun asmSymbolName(identifier: IdentifierReference) = asmSymbolName(identifier.nameInSource)
fun asmVariableName(identifier: IdentifierReference) = asmVariableName(identifier.nameInSource)
internal fun getTempVarName(dt: DataType): List<String> {
internal fun getTempVarName(dt: DataType): String {
return when(dt) {
DataType.UBYTE -> listOf("cx16", "r9L")
DataType.BYTE -> listOf("cx16", "r9sL")
DataType.UWORD -> listOf("cx16", "r9")
DataType.WORD -> listOf("cx16", "r9s")
DataType.UBYTE -> "cx16.r9L"
DataType.BYTE -> "cx16.r9sL"
DataType.UWORD -> "cx16.r9"
DataType.WORD -> "cx16.r9s"
DataType.FLOAT -> TODO("no temporary float var available")
else -> throw FatalAstException("invalid dt $dt")
}
@@ -450,7 +450,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) {
val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
val decl = program.toplevelModule.lookup(identifier.split('.')) as? VarDecl
if(decl!=null) {
when(decl.type){
VarDeclType.VAR -> null
@@ -124,7 +124,7 @@ internal class AssemblyProgram(
breakpoints.add("break \$" + match.groupValues[1])
}
val num = breakpoints.size
breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(0, "; breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
@@ -223,7 +223,7 @@ internal class ProgramAndVarsGen(
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
private fun createBlockVariables(block: Block) {
val scope = symboltable.lookupOrElse(block.name) { throw AssemblyError("lookup") }
val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.BLOCK)
val varsInBlock = getVars(scope)
@@ -242,7 +242,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
@@ -286,7 +286,7 @@ internal class ProgramAndVarsGen(
// regular subroutine
asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
val scope = symboltable.lookupOrElse(sub.scopedName.joinToString(".")) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
@@ -357,7 +357,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
@@ -428,13 +428,13 @@ internal class ProgramAndVarsGen(
}
private class ZpStringWithInitial(
val name: List<String>,
val name: String,
val alloc: MemoryAllocator.VarAllocation,
val value: Pair<String, Encoding>
)
private class ZpArrayWithInitial(
val name: List<String>,
val name: String,
val alloc: MemoryAllocator.VarAllocation,
val value: StArray
)
@@ -443,9 +443,10 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpStringWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
for (variable in vars) {
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
val scopedName = variable.key.joinToString(".")
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!))
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
}
return result
}
@@ -454,15 +455,17 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpArrayWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
for (variable in vars) {
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
val scopedName = variable.key.joinToString(".")
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!))
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
}
return result
}
private fun zeropagevars2asm(varNames: Set<List<String>>) {
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }
private fun zeropagevars2asm(varNames: Set<String>) {
val namesLists = varNames.map { it.split('.') }.toSet()
val zpVariables = allocator.zeropageVars.filter { it.key in namesLists }.toList().sortedBy { it.second.address }
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
@@ -473,7 +476,7 @@ internal class ProgramAndVarsGen(
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
asmgen.out("")
asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
val (stringvars, othervars) = variables.sortedBy { it.name }.partition { it.dt==DataType.STR }
stringvars.forEach {
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
}
@@ -579,10 +582,10 @@ internal class ProgramAndVarsGen(
}
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
memvars.forEach {
memvars.sortedBy { it.address }.forEach {
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.forEach {
consts.sortedBy { it.name }.forEach {
if(it.dt==DataType.FLOAT)
asmgen.out(" ${it.name} = ${it.value}")
else
@@ -620,8 +623,8 @@ internal class ProgramAndVarsGen(
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
else if(it.addressOf!=null) {
asmgen.asmSymbolName(it.addressOf!!)
else if(it.addressOfSymbol!=null) {
asmgen.asmSymbolName(it.addressOfSymbol!!)
}
else
throw AssemblyError("weird array elt")
@@ -22,7 +22,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
allocateZeropageVariables()
}
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropageVars
internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number]
@@ -56,7 +56,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedName.split('.'),
variable.dt,
variable.length,
variable.position,
@@ -75,7 +75,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedName.split('.'),
variable.dt,
variable.length,
variable.position,
@@ -88,13 +88,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
// try to allocate any other interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
if(errors.noErrors()) {
for (variable in varsDontCare.sortedBy { it.scopedName.size }) {
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
for (variable in sortedList) {
if(variable.dt in IntegerDatatypes) {
if(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.scopedName.split('.'),
variable.dt,
variable.length,
variable.position,
@@ -124,6 +125,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
}
}
collect(st)
return vars
return vars.sortedBy { it.dt }
}
}
@@ -294,6 +294,7 @@ internal class AssignmentAsmGen(private val program: Program,
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign, true)
"~" -> augmentableAsmGen.inplaceInvert(assign)
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
} else {
@@ -324,7 +325,7 @@ internal class AssignmentAsmGen(private val program: Program,
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 tempvar = asmgen.getTempVarName(assign.target.datatype)
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)
@@ -24,7 +24,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val array = assignment.target.array
return if(ident!=null) {
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
assignSelfInMemory(ident.name, assignment.value, assignment)
} else if(memory != null) {
if(memory.address is PtNumber)
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
@@ -186,20 +186,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
if(ident!=null) {
val symbol = ident.targetName.joinToString(".")
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = ident.name)
} else {
if (vmDt == IRDataType.FLOAT)
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = ident.name)
else
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = ident.name)
}
result += IRCodeChunk(null, null).also { it += instruction }
return result
}
else if(array!=null) {
val variable = array.variable.targetName.joinToString(".")
val variable = array.variable.name
val itemsize = codeGen.program.memsizer.memorySize(array.type)
if(array.variable.type==DataType.UWORD) {
@@ -60,7 +60,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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 array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when (array.dt) {
DataType.ARRAY_UB,
@@ -83,7 +83,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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 array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(array.dt) {
DataType.ARRAY_UB,
@@ -204,7 +204,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(array.dt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
@@ -223,7 +223,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(array.dt) {
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
@@ -39,22 +39,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is PtIdentifier -> {
val vmDt = codeGen.irType(expr.type)
val symbol = expr.targetName.joinToString(".")
val code = IRCodeChunk(null, null)
code += if (expr.type in PassByValueDatatypes) {
if(vmDt==IRDataType.FLOAT)
IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = expr.name)
else
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
} else {
// for strings and arrays etc., load the *address* of the value instead
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol)
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
}
listOf(code)
}
is PtAddressOf -> {
val vmDt = codeGen.irType(expr.type)
val symbol = expr.identifier.targetName.joinToString(".")
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
@@ -72,27 +71,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
result
}
is PtTypeCast -> {
translate(expr, resultRegister, resultFpRegister)
}
is PtPrefix -> {
translate(expr, resultRegister, resultFpRegister)
}
is PtArrayIndexer -> {
translate(expr, resultRegister, resultFpRegister)
}
is PtBinaryExpression -> {
translate(expr, resultRegister, resultFpRegister)
}
is PtBuiltinFunctionCall -> {
codeGen.translateBuiltinFunc(expr, resultRegister)
}
is PtFunctionCall -> {
translate(expr, resultRegister, resultFpRegister)
}
is PtContainmentCheck -> {
translate(expr, resultRegister, resultFpRegister)
}
is PtTypeCast -> translate(expr, resultRegister, resultFpRegister)
is PtPrefix -> translate(expr, resultRegister, resultFpRegister)
is PtArrayIndexer -> translate(expr, resultRegister, resultFpRegister)
is PtBinaryExpression -> translate(expr, resultRegister, resultFpRegister)
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr, resultRegister)
is PtFunctionCall -> translate(expr, resultRegister, resultFpRegister)
is PtContainmentCheck -> translate(expr, resultRegister, resultFpRegister)
is PtRange,
is PtArray,
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
@@ -103,7 +88,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(check: PtContainmentCheck, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
result += translateExpression(check.element, resultRegister, -1) // load the element to check in resultRegister
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.targetName) as StStaticVariable
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable
when(iterable.dt) {
DataType.STR -> {
result += translateExpression(check.element, SyscallRegisterBase, -1)
@@ -135,7 +120,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.targetName}")
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}")
}
return result
}
@@ -145,7 +130,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val vmDt = codeGen.irType(arrayIx.type)
val result = mutableListOf<IRCodeChunkBase>()
val idxReg = codeGen.registers.nextFree()
val arrayVarSymbol = arrayIx.variable.targetName.joinToString(".")
val arrayVarSymbol = arrayIx.variable.name
if(arrayIx.variable.type==DataType.UWORD) {
// indexing a pointer var instead of a real array or string
@@ -430,11 +415,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.AND, vmDt, reg1 = resultRegister, value = 1)
}
} else {
val rightResultReg = codeGen.registers.nextFree()
result += translateExpression(binExpr.left, resultRegister, -1)
result += translateExpression(binExpr.right, rightResultReg, -1)
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg), null)
if(constValue(binExpr.right)==0.0) {
result += translateExpression(binExpr.left, resultRegister, -1)
val opcode = if (notEquals) Opcode.SNZ else Opcode.SZ
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = resultRegister), null)
} else {
val rightResultReg = codeGen.registers.nextFree()
result += translateExpression(binExpr.left, resultRegister, -1)
result += translateExpression(binExpr.right, rightResultReg, -1)
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg), null)
}
}
}
return result
@@ -940,12 +931,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
fun translate(fcall: PtFunctionCall, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.functionName)) {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
val paramDt = codeGen.irType(parameter.type)
val symbol = (fcall.functionName + parameter.name).joinToString(".")
val symbol = "${fcall.name}.${parameter.name}"
if(codeGen.isZero(arg)) {
addInstr(result, IRInstruction(Opcode.STOREZM, paramDt, labelSymbol = symbol), null)
} else {
@@ -960,7 +951,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
}
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol=fcall.functionName.joinToString(".")), null)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol=fcall.name), null)
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.
@@ -24,8 +24,9 @@ class IRCodeGen(
internal val registers = RegisterPool()
fun generate(): IRProgram {
flattenLabelNames()
flattenNestedSubroutines()
makeAllNodenamesScoped()
moveAllNestedSubroutinesToBlockScope()
verifyNameScoping(program, symbolTable)
val irProg = IRProgram(program.name, IRSymbolTable(symbolTable), options, program.encoding)
@@ -71,6 +72,39 @@ class IRCodeGen(
return irProg
}
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
fun verifyPtNode(node: PtNode) {
when (node) {
is PtBuiltinFunctionCall -> require('.' !in node.name) { "builtin function call name should not be scoped: ${node.name}" }
is PtFunctionCall -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtIdentifier -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtAsmSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtBlock -> require('.' !in node.name) { "block name should not be scoped: ${node.name}" }
is PtConstant -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtLabel -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtMemMapped -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}" }
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
else -> { /* node has no name */
}
}
node.children.forEach { verifyPtNode(it) }
}
fun verifyStNode(node: StNode) {
require('.' !in node.name) { "st node name should not be scoped: ${node.name}"}
node.children.forEach {
require(it.key==it.value.name)
verifyStNode(it.value)
}
}
verifyPtNode(program)
verifyStNode(symbolTable)
}
private fun ensureFirstChunkLabels(irProg: IRProgram) {
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
@@ -131,7 +165,7 @@ class IRCodeGen(
symbol = symbolExpr
index = 0u
}
val target = symbolTable.flat[symbol.split('.')]
val target = symbolTable.flat[symbol]
if (target is StMemVar) {
replacements.add(Triple(chunk, idx, target.address+index))
}
@@ -155,108 +189,51 @@ class IRCodeGen(
}
}
private fun flattenLabelNames() {
val renameLabels = mutableListOf<Pair<PtNode, PtLabel>>()
fun flattenRecurse(node: PtNode) {
private fun makeAllNodenamesScoped() {
val renames = mutableListOf<Pair<PtNamedNode, String>>()
fun recurse(node: PtNode) {
node.children.forEach {
if (it is PtLabel)
renameLabels += Pair(it.parent, it)
else
flattenRecurse(it)
if(it is PtNamedNode)
renames.add(it to it.scopedName)
recurse(it)
}
}
flattenRecurse(program)
renameLabels.forEach { (parent, label) ->
val renamedLabel = PtLabel(label.scopedName.joinToString("."), label.position)
val idx = parent.children.indexOf(label)
parent.children.removeAt(idx)
parent.children.add(idx, renamedLabel)
}
recurse(program)
renames.forEach { it.first.name = it.second }
}
private fun flattenNestedSubroutines() {
// this moves all nested subroutines up to the block scope.
// also changes the name to be the fully scoped one, so it becomes unique at the top level.
val flattenedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
private fun moveAllNestedSubroutinesToBlockScope() {
val movedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
val removedSubs = mutableListOf<Pair<PtSub, PtSub>>()
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
asmsub.address,
asmsub.clobbers,
asmsub.parameters,
asmsub.returnTypes,
asmsub.retvalRegisters,
asmsub.inline,
asmsub.position)
asmsub.children.forEach { flattened.add(it) }
flattenedAsmSubs += Pair(block, flattened)
removalsAsmSubs += Pair(parentSub, asmsub)
fun moveToBlock(block: PtBlock, parent: PtSub, asmsub: PtAsmSub) {
block.add(asmsub)
parent.children.remove(asmsub)
}
fun flattenNestedSub(block: PtBlock, parentSub: PtSub, sub: PtSub) {
sub.children.filterIsInstance<PtSub>().forEach { subsub->flattenNestedSub(block, sub, subsub) }
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsmSub(block, sub, asmsubsub) }
val flattened = PtSub(sub.scopedName.joinToString("."),
sub.parameters,
sub.returntype,
sub.inline,
sub.position)
sub.children.forEach { if(it !is PtSub) flattened.add(it) }
flattenedSubs += Pair(block, flattened)
removalsSubs += Pair(parentSub, sub)
fun moveToBlock(block: PtBlock, parent: PtSub, sub: PtSub) {
sub.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, sub, subsub) }
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, sub, asmsubsub) }
movedSubs += Pair(block, sub)
removedSubs += Pair(parent, sub)
}
program.allBlocks().forEach { block ->
block.children.forEach {
if(it is PtSub) {
if (it is PtSub) {
// Only regular subroutines can have nested subroutines.
it.children.filterIsInstance<PtSub>().forEach { subsub->flattenNestedSub(block, it, subsub)}
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsmSub(block, it, asmsubsub)}
renameSubs += Pair(block, it)
it.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, it, subsub) }
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, it, asmsubsub) }
}
if(it is PtAsmSub)
renameAsmSubs += Pair(block, it)
}
}
removalsSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
removalsAsmSubs.forEach { (parent, asmsub) -> parent.children.remove(asmsub) }
flattenedSubs.forEach { (block, sub) -> block.add(sub) }
flattenedAsmSubs.forEach { (block, asmsub) -> block.add(asmsub) }
renameSubs.forEach { (parent, sub) ->
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
sub.children.forEach { renamedSub.add(it) }
val subindex = parent.children.indexOf(sub)
parent.children[subindex] = renamedSub // keep the order of nodes the same
}
renameAsmSubs.forEach { (parent, sub) ->
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
sub.address,
sub.clobbers,
sub.parameters,
sub.returnTypes,
sub.retvalRegisters,
sub.inline,
sub.position)
if(sub.children.isNotEmpty())
renamedSub.add(sub.children.single())
val subindex = parent.children.indexOf(sub)
parent.children[subindex] = renamedSub // keep the order of nodes the same
}
removedSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
movedSubs.forEach { (block, sub) -> block.add(sub) }
}
internal fun translateNode(node: PtNode): IRCodeChunks {
val chunks = when(node) {
is PtScopeVarsDecls -> emptyList() // vars should be looked up via symbol table
is PtVariable -> emptyList() // var should be looked up via symbol table
is PtMemMapped -> emptyList() // memmapped var should be looked up via symbol table
is PtConstant -> emptyList() // constants have all been folded into the code
@@ -338,7 +315,7 @@ class IRCodeGen(
}
addInstr(result, branchIns, null)
} else {
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.targetName.joinToString(".")
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.name
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
@@ -454,7 +431,7 @@ class IRCodeGen(
}
private fun translate(forLoop: PtForLoop): IRCodeChunks {
val loopvar = symbolTable.lookup(forLoop.variable.targetName)!!
val loopvar = symbolTable.lookup(forLoop.variable.name)!!
val iterable = forLoop.iterable
val result = mutableListOf<IRCodeChunkBase>()
when(iterable) {
@@ -465,9 +442,9 @@ class IRCodeGen(
result += translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
val symbol = iterable.targetName.joinToString(".")
val iterableVar = symbolTable.lookup(iterable.targetName) as StStaticVariable
val loopvarSymbol = loopvar.scopedName.joinToString(".")
val iterableVar = symbolTable.lookup(iterable.name) as StStaticVariable
require(forLoop.variable.name == loopvar.scopedName)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.nextFree()
val tmpReg = registers.nextFree()
val loopLabel = createLabelName()
@@ -476,7 +453,7 @@ class IRCodeGen(
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0), null)
val chunk = IRCodeChunk(loopLabel, null)
chunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpReg, reg2=indexReg, labelSymbol = symbol)
chunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpReg, reg2=indexReg, labelSymbol = iterable.name)
chunk += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=tmpReg, labelSymbol = endLabel)
chunk += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=tmpReg, labelSymbol = loopvarSymbol)
result += chunk
@@ -498,7 +475,7 @@ class IRCodeGen(
chunk += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, value=lengthBytes)
result += chunk
val chunk2 = IRCodeChunk(loopLabel, null)
chunk2 += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=symbol)
chunk2 += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
chunk2 += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
result += chunk2
result += translateNode(forLoop.statements)
@@ -507,7 +484,7 @@ class IRCodeGen(
} else if(lengthBytes==256) {
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0), null)
val chunk = IRCodeChunk(loopLabel, null)
chunk += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=symbol)
chunk += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
chunk += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
result += chunk
result += translateNode(forLoop.statements)
@@ -530,7 +507,8 @@ class IRCodeGen(
throw AssemblyError("step 0")
val indexReg = registers.nextFree()
val endvalueReg = registers.nextFree()
val loopvarSymbol = loopvar.scopedName.joinToString(".")
require(forLoop.variable.name == loopvar.scopedName)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
is StStaticVariable -> loopvar.dt
@@ -542,18 +520,27 @@ class IRCodeGen(
result += expressionEval.translateExpression(iterable.to, endvalueReg, -1)
result += expressionEval.translateExpression(iterable.from, indexReg, -1)
val labelAfterFor = createLabelName()
val greaterOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGTS else Opcode.BGT
addInstr(result, IRInstruction(greaterOpcode, loopvarDtIr, indexReg, endvalueReg, labelSymbol=labelAfterFor), null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=indexReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol), null)
val branchOpcode = if(loopvarDt in SignedDatatypes) Opcode.BLES else Opcode.BLE
addInstr(result, IRInstruction(branchOpcode, loopvarDtIr, reg1=indexReg, reg2=endvalueReg, labelSymbol=loopLabel), null)
// if endvalue >= index, iterate loop
val branchOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGES else Opcode.BGE
addInstr(result, IRInstruction(branchOpcode, loopvarDtIr, reg1=endvalueReg, reg2=indexReg, labelSymbol=loopLabel), null)
result += IRCodeChunk(labelAfterFor, null)
return result
}
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
val loopLabel = createLabelName()
val loopvarSymbol = loopvar.scopedName.joinToString(".")
require(forLoop.variable.name == loopvar.scopedName)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.nextFree()
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@@ -900,56 +887,117 @@ class IRCodeGen(
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 signed = ifElse.condition.left.type in SignedDatatypes
val irDt = irType(ifElse.condition.left.type)
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
if(goto!=null && ifElse.elseScope.children.isEmpty()) {
// special case the form: if <condition> goto <place>
val result = mutableListOf<IRCodeChunkBase>()
val leftReg = registers.nextFree()
val rightReg = registers.nextFree()
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
result += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
val opcode = when(ifElse.condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> Opcode.BLT
">" -> Opcode.BGT
"<=" -> Opcode.BLE
">=" -> Opcode.BGE
val leftRegNum = registers.nextFree()
val rightRegNum = registers.nextFree()
result += expressionEval.translateExpression(ifElse.condition.left, leftRegNum, -1)
result += expressionEval.translateExpression(ifElse.condition.right, rightRegNum, -1)
val opcode: Opcode
val firstReg: Int
val secondReg: Int
when(ifElse.condition.operator) {
"==" -> {
opcode = Opcode.BEQ
firstReg = leftRegNum
secondReg = rightRegNum
}
"!=" -> {
opcode = Opcode.BNE
firstReg = leftRegNum
secondReg = rightRegNum
}
"<" -> {
// swapped '>'
opcode = if(signed) Opcode.BGTS else Opcode.BGT
firstReg = rightRegNum
secondReg = leftRegNum
}
">" -> {
opcode = if(signed) Opcode.BGTS else Opcode.BGT
firstReg = leftRegNum
secondReg = rightRegNum
}
"<=" -> {
// swapped '>='
opcode = if(signed) Opcode.BGES else Opcode.BGE
firstReg = rightRegNum
secondReg = leftRegNum
}
">=" -> {
opcode = if(signed) Opcode.BGES else Opcode.BGE
firstReg = leftRegNum
secondReg = rightRegNum
}
else -> throw AssemblyError("invalid comparison operator")
}
if(goto.address!=null)
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, value = goto.address?.toInt()), null)
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, value = goto.address?.toInt()), null)
else if(goto.generatedLabel!=null)
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.generatedLabel), null)
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.identifier!!.targetName.joinToString(".")), null)
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, labelSymbol = goto.identifier!!.name), null)
return result
}
fun translateNonZeroComparison(): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
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
val leftRegNum = registers.nextFree()
val rightRegNum = registers.nextFree()
result += expressionEval.translateExpression(ifElse.condition.left, leftRegNum, -1)
result += expressionEval.translateExpression(ifElse.condition.right, rightRegNum, -1)
val elseBranchOpcode: Opcode
val elseBranchFirstReg: Int
val elseBranchSecondReg: Int
when(ifElse.condition.operator) {
"==" -> {
elseBranchOpcode = Opcode.BNE
elseBranchFirstReg = leftRegNum
elseBranchSecondReg = rightRegNum
}
"!=" -> {
elseBranchOpcode = Opcode.BEQ
elseBranchFirstReg = leftRegNum
elseBranchSecondReg = rightRegNum
}
"<" -> {
// else part when left >= right
elseBranchOpcode = if(signed) Opcode.BGES else Opcode.BGE
elseBranchFirstReg = leftRegNum
elseBranchSecondReg = rightRegNum
}
">" -> {
// else part when left <= right --> right >= left
elseBranchOpcode = if(signed) Opcode.BGES else Opcode.BGE
elseBranchFirstReg = rightRegNum
elseBranchSecondReg = leftRegNum
}
"<=" -> {
// else part when left > right
elseBranchOpcode = if(signed) Opcode.BGTS else Opcode.BGT
elseBranchFirstReg = leftRegNum
elseBranchSecondReg = rightRegNum
}
">=" -> {
// else part when left < right --> right > left
elseBranchOpcode = if(signed) Opcode.BGTS else Opcode.BGT
elseBranchFirstReg = rightRegNum
elseBranchSecondReg = leftRegNum
}
else -> throw AssemblyError("invalid comparison operator")
}
val leftReg = registers.nextFree()
val rightReg = registers.nextFree()
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
result += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
if(ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = elseLabel), null)
addInstr(result, IRInstruction(elseBranchOpcode, irDt, reg1=elseBranchFirstReg, reg2=elseBranchSecondReg, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -957,7 +1005,7 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = afterIfLabel), null)
addInstr(result, IRInstruction(elseBranchOpcode, irDt, reg1=elseBranchFirstReg, reg2=elseBranchSecondReg, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -989,18 +1037,13 @@ class IRCodeGen(
}
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()
}
"==" -> equalOrNotEqualZero(Opcode.BNZ)
"!=" -> equalOrNotEqualZero(Opcode.BZ)
"<" -> if(signed) equalOrNotEqualZero(Opcode.BGEZS) else throw AssemblyError("unsigned < 0 shouldn't occur in codegen")
">" -> if(signed) equalOrNotEqualZero(Opcode.BLEZS) else throw AssemblyError("unsigned > 0 shouldn't occur in codegen")
"<=" -> if(signed) equalOrNotEqualZero(Opcode.BGZS) else throw AssemblyError("unsigned <= 0 shouldn't occur in codegen")
">=" -> if(signed) equalOrNotEqualZero(Opcode.BLZS) else throw AssemblyError("unsigned >= 0 shouldn't occur in codegen")
else -> throw AssemblyError("weird operator")
}
}
@@ -1030,7 +1073,7 @@ class IRCodeGen(
val irDt = irType(postIncrDecr.target.type)
val result = mutableListOf<IRCodeChunkBase>()
if(ident!=null) {
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = ident.targetName.joinToString(".")), null)
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = ident.name), null)
} else if(memory!=null) {
if(memory.address is PtNumber) {
val address = (memory.address as PtNumber).number.toInt()
@@ -1046,7 +1089,7 @@ class IRCodeGen(
result += chunk
}
} else if (array!=null) {
val variable = array.variable.targetName.joinToString(".")
val variable = array.variable.name
val itemsize = program.memsizer.memorySize(array.type)
val fixedIndex = constIntValue(array.index)
if(fixedIndex!=null) {
@@ -1102,7 +1145,7 @@ class IRCodeGen(
if (jump.generatedLabel != null)
IRInstruction(Opcode.JUMP, labelSymbol = jump.generatedLabel!!)
else if (jump.identifier != null)
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.targetName.joinToString("."))
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
else
throw AssemblyError("weird jump")
}
@@ -1136,7 +1179,7 @@ class IRCodeGen(
when(child) {
is PtNop -> { /* nothing */ }
is PtAssignment -> { /* global variable initialization is done elsewhere */ }
is PtScopeVarsDecls -> { /* vars should be looked up via symbol table */ }
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
for (subchild in child.children) {
@@ -1184,9 +1227,9 @@ class IRCodeGen(
private fun translate(parameters: List<PtSubroutineParameter>) =
parameters.map {
val flattenedName = (it.definingSub()!!.scopedName + it.name)
val flattenedName = it.definingSub()!!.name + "." + it.name
val orig = symbolTable.flat.getValue(flattenedName) as StStaticVariable
IRSubroutine.IRParam(flattenedName.joinToString("."), orig.dt)
IRSubroutine.IRParam(flattenedName, orig.dt)
}
private fun translate(alignment: PtBlock.BlockAlignment): IRBlock.BlockAlignment {
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -231,6 +231,18 @@ class ExpressionSimplifier(private val program: Program,
}
}
if(leftDt==DataType.BOOL) {
// optimize boolean constant comparisons
// if(expr.operator=="==" && rightVal?.number==0.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
// if(expr.operator=="!=" && rightVal?.number==1.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
if(expr.operator=="==" && rightVal?.number==1.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(expr.operator=="!=" && rightVal?.number==0.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
// simplify when a term is constant and directly determines the outcome
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
+25 -8
View File
@@ -561,15 +561,32 @@ sys {
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
%asm {{
stx P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda c64.TIME_LO
sta P8ZP_SCRATCH_B1
- lda c64.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
jmp _loop
}}
}
asmsub waitvsync() clobbers(A) {
+25 -8
View File
@@ -527,15 +527,32 @@ sys {
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
%asm {{
stx P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda c64.TIME_LO
sta P8ZP_SCRATCH_B1
- lda c64.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
jmp _loop
}}
}
asmsub waitvsync() clobbers(A) {
+9 -6
View File
@@ -5,6 +5,7 @@
cx16diskio {
; Same as diskio.load() but with additional bank parameter to select the Ram bank to load into.
; Use kernal LOAD routine to load the given program file in memory.
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
@@ -14,9 +15,10 @@ cx16diskio {
; You can use the load_size() function to calcuate the size of the file that was loaded.
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
cx16.rambank(bank)
return diskio.load(drivenumber, filenameptr, address_override)
return diskio.internal_load_routine(drivenumber, filenameptr, address_override, false)
}
; Same as diskio.load_raw() but with additional bank parameter to select the Ram bank to load into.
; Use kernal LOAD routine to load the given file in memory.
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
; The load address is mandatory. Returns the number of bytes loaded.
@@ -24,9 +26,9 @@ cx16diskio {
; or alternatively make sure to reset the correct ram bank yourself after the load!
; Returns the end load address+1 if successful or 0 if a load error occurred.
; You can use the load_size() function to calcuate the size of the file that was loaded.
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
cx16.rambank(bank)
return diskio.load_headerless_cx16(drivenumber, filenameptr, address, true)
return diskio.internal_load_routine(drivenumber, filenameptr, address_override, true)
}
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
@@ -87,8 +89,9 @@ internal_vload:
}}
}
; replacement function that makes use of fast block read capability of the X16
; use this in place of regular diskio.f_read()
; Replacement function that makes use of fast block read capability of the X16,
; and can wrap over multiple ram banks while reading.
; Use this in place of regular diskio.f_read() on X16.
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
@@ -147,7 +150,7 @@ m_in_buffer sta $ffff
}
; replacement function that makes use of fast block read capability of the X16
; use this in place of regular diskio.f_read_all()
; use this in place of regular diskio.f_read_all() on X16
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
if not diskio.iteration_in_progress
+32 -13
View File
@@ -460,13 +460,23 @@ inline asmsub getrambank() -> ubyte @A {
}}
}
asmsub numbanks() -> ubyte @A {
asmsub numbanks() -> uword @AY {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
; Note that the number of such banks can be bigger than 255 so a word is returned, but mostly
; the A register could suffice as the lsb.
; The maximum number of banks is 256 = 2 Megabytes of banked Ram aka Hiram. (Y=1 and A=0 in this case).
; MEMTOP itself reports 0 in this case which we change into 256 for convenience.
; It reporting 0 doesn't mean 'zero banks', instead it means 256 banks (=2Mb banked RAM),
; as there is no X16 without at least 1 page of banked RAM.
%asm {{
phx
sec
jsr c64.MEMTOP
plx
ldy #0
cmp #0
bne +
iny
+ plx
rts
}}
}
@@ -638,9 +648,6 @@ asmsub init_system() {
and #%11111000
ora P8ZP_SCRATCH_REG
sta VERA_DC_VIDEO ; keep old output mode
ldy #0
clc
jsr c64.PLOT ; force a call to PLOT to avoid autostart black square issue, also see textio.fix_autostart_square()
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg
@@ -911,17 +918,29 @@ sys {
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: regular system vsync irq handler must be running, and no nother irqs
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
%asm {{
- wai ; wait for irq (assume it was vsync)
cmp #0
phx
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
dey
+ dec a
bne -
cpy #0
bne -
plx
rts
+ jsr c64.RDTIM
sta P8ZP_SCRATCH_B1
- jsr c64.RDTIM
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
bra _loop
}}
}
-20
View File
@@ -21,26 +21,6 @@ sub home() {
txt.chrout(19)
}
asmsub fix_autostart_square() {
; Here's a possible work around for weird issue that prints a black character after first call to c64.PLOT()
; if you're also using c64.CINT() yourself. The default prog8 program initialization (which calls CINT) already performs this workaround.
; The problem occurs when a program is autostarded in the emulator with -run -prg test.prg,
; or when the program is saved as AUTOBOOT.X16 and loaded on boot like that.
%asm {{
phx
sec
jsr c64.PLOT
clc
jsr c64.PLOT
lda #' '
jsr c64.CHROUT ; overwrite the black square
clc
jsr c64.PLOT ; cursor back to original position
plx
rts
}}
}
sub nl() {
txt.chrout('\n')
}
+8 -5
View File
@@ -282,6 +282,7 @@ close_end:
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT
; automatically load into subsequent banks if it reaches a bank boundary!
; Consider using cx16diskio.f_read() on X16.
if not iteration_in_progress or not num_bytes
return 0
@@ -316,6 +317,7 @@ m_in_buffer sta $ffff
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; Note: Consider using cx16diskio.f_read_all() on X16!
if not iteration_in_progress
return 0
@@ -475,9 +477,9 @@ io_error:
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
; Consider using cx16diskio.load() instead.
; Also consider using cx16diskio.load() instead on the Commander X16.
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
return load_headerless_cx16(drivenumber, filenameptr, address_override, false)
return internal_load_routine(drivenumber, filenameptr, address_override, false)
}
; Use kernal LOAD routine to load the given file in memory.
@@ -488,10 +490,10 @@ io_error:
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
; Consider using cx16diskio.load_raw() instead on the Commander X16.
; Also consider using cx16diskio.load_raw() instead on the Commander X16.
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
if sys.target==16 ; are we on commander X16?
return load_headerless_cx16(drivenumber, filenameptr, address, true)
return internal_load_routine(drivenumber, filenameptr, address, true)
; fallback to reading the 2 header bytes separately
if not f_open(drivenumber, filenameptr)
return 0
@@ -506,7 +508,8 @@ io_error:
; Internal routine, only to be used on Commander X16 platform if headerless=true,
; because this routine uses kernal support for that to load headerless files.
sub load_headerless_cx16(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
; On C64 it will always be called with headerless=false.
sub internal_load_routine(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
cx16.r1 = 0
+1 -1
View File
@@ -1 +1 @@
8.8.1
8.9
+3 -3
View File
@@ -392,7 +392,7 @@ private fun createAssemblyAndAssemble(program: Program,
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
val symbolTable = SymbolTableMaker(program, compilerOptions).make()
// 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.
@@ -446,14 +446,14 @@ internal fun asmGeneratorFor(program: Program,
options: CompilationOptions): IAssemblyGenerator
{
if(options.experimentalCodegen) {
val intermediateAst = IntermediateAstMaker(program).transform()
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
if (options.compTarget.name == VMTarget.NAME) {
val intermediateAst = IntermediateAstMaker(program).transform()
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return VmCodeGen(intermediateAst, symbolTable, options, errors)
}
}
@@ -61,6 +61,14 @@ internal class AstChecker(private val program: Program,
}
}
override fun visit(identifier: IdentifierReference) {
val targetParam = identifier.targetVarDecl(program)?.subroutineParameter
if(targetParam!=null) {
if((targetParam.parent as Subroutine).isAsmSubroutine)
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
}
}
override fun visit(returnStmt: Return) {
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
if(expectedReturnValues.size>1) {
@@ -300,6 +308,8 @@ internal class AstChecker(private val program: Program,
err("subroutines can only be defined in the scope of a block or within another subroutine")
if(subroutine.isAsmSubroutine) {
if(compilerOptions.compTarget.name==VMTarget.NAME)
err("cannot use asmsub for vm target, use regular subs")
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
err("number of asm parameter registers is not the isSameAs as number of parameters")
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
@@ -835,6 +845,8 @@ internal class AstChecker(private val program: Program,
else if(expr.operator == "~") {
if(dt !in IntegerDatatypes)
errors.err("can only use bitwise invert on integer types", expr.position)
if(dt==DataType.BOOL)
errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position)
}
super.visit(expr)
}
@@ -2,6 +2,7 @@ package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
@@ -14,7 +15,8 @@ internal class BoolRemover(val program: Program) : AstWalker() {
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.type == DataType.BOOL) {
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position)
val valueDt = typecast.expression.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(valueDt, 0.0, typecast.position), typecast.position)
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
}
return noModifications
@@ -8,9 +8,10 @@ import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.compiler.BuiltinFunctions
import prog8.compiler.builtinFunctionReturnType
@@ -22,7 +23,7 @@ import kotlin.io.path.isRegularFile
/**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/
class IntermediateAstMaker(val program: Program) {
class IntermediateAstMaker(private val program: Program, private val symbolTable: SymbolTable, private val options: CompilationOptions) {
fun transform(): PtProgram {
val ptProgram = PtProgram(
program.name,
@@ -109,19 +110,9 @@ class IntermediateAstMaker(val program: Program) {
return target
}
private fun targetOf(identifier: IdentifierReference): Pair<List<String>, DataType> {
val target=identifier.targetStatement(program)!! as INamedStatement
val targetname = if(target.name in program.builtinFunctions.names)
listOf("<builtin>", target.name)
else
target.scopedName
val type = identifier.inferType(program).getOr(DataType.UNDEFINED)
return Pair(targetname, type)
}
private fun transform(identifier: IdentifierReference): PtIdentifier {
val (target, type) = targetOf(identifier)
return PtIdentifier(identifier.nameInSource, target, type, identifier.position)
val (target, type) = identifier.targetNameAndType(program)
return PtIdentifier(target, type, identifier.position)
}
private fun transform(srcBlock: Block): PtBlock {
@@ -140,16 +131,16 @@ class IntermediateAstMaker(val program: Program) {
}
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
if(vardecls.isNotEmpty()) block.add(makeScopeVarsDecls(vardecls, srcBlock.position))
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements)
block.add(transformStatement(stmt))
return block
}
private fun makeScopeVarsDecls(vardecls: List<Statement>, position: Position): PtNode {
val decls = PtScopeVarsDecls(position)
private fun makeScopeVarsDecls(vardecls: Iterable<Statement>): Iterable<PtNamedNode> {
val decls = mutableListOf<PtNamedNode>()
vardecls.forEach {
decls.add(transformStatement(it as VarDecl))
decls.add(transformStatement(it as VarDecl) as PtNamedNode)
}
return decls
}
@@ -218,7 +209,7 @@ class IntermediateAstMaker(val program: Program) {
}
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
val (target, type) = targetOf(srcCall.target)
val (target, type) = srcCall.target.targetNameAndType(program)
val call = PtFunctionCall(target,true, type, srcCall.position)
for (arg in srcCall.args)
call.add(transformExpression(arg))
@@ -226,7 +217,7 @@ class IntermediateAstMaker(val program: Program) {
}
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = targetOf(srcCall.target)
val (target, _) = srcCall.target.targetNameAndType(program)
val type = srcCall.inferType(program).getOrElse {
throw FatalAstException("unknown dt $srcCall")
}
@@ -344,8 +335,7 @@ class IntermediateAstMaker(val program: Program) {
srcSub.inline,
srcSub.position)
sub.parameters.forEach { it.parent=sub }
if(vardecls.isNotEmpty()) sub.add(makeScopeVarsDecls(vardecls, sub.position))
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
for (statement in statements)
sub.add(transformStatement(statement))
@@ -359,7 +349,7 @@ class IntermediateAstMaker(val program: Program) {
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
}
@@ -40,6 +40,13 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
}
}
}
if(expr.operator=="^" && expr.left.inferType(program) istype DataType.BOOL && expr.right.constValue(program)?.number == 1.0) {
// boolean ^ 1 --> not boolean
val notExpr = PrefixExpression("not", expr.left, expr.position)
return listOf(IAstModification.ReplaceNode(expr, notExpr, parent))
}
return noModifications
}
@@ -127,15 +127,6 @@ internal class StatementReorderer(val program: Program,
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
val modifications = mutableListOf<IAstModification>()
val subs = subroutine.statements.filterIsInstance<Subroutine>()
@@ -12,13 +12,13 @@ import prog8.code.core.DataType
import prog8.code.core.Position
import java.util.*
internal class SymbolTableMaker: IAstVisitor {
internal class SymbolTableMaker(private val program: Program, private val options: CompilationOptions): IAstVisitor {
private val st = SymbolTable()
private val scopestack = Stack<StNode>()
private var dontReinitGlobals = false
fun makeFrom(program: Program, options: CompilationOptions): SymbolTable {
fun make(): SymbolTable {
scopestack.clear()
st.children.clear()
dontReinitGlobals = options.dontReinitGlobals
@@ -27,6 +27,7 @@ internal class SymbolTableMaker: IAstVisitor {
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
st.add(node)
}
require(scopestack.isEmpty())
return st
}
@@ -103,8 +104,14 @@ internal class SymbolTableMaker: IAstVisitor {
return null
return arrayLit.value.map {
when(it){
is AddressOf -> StArrayElement(null, it.identifier.nameInSource)
is IdentifierReference -> StArrayElement(null, it.nameInSource)
is AddressOf -> {
val scopedName = it.identifier.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is IdentifierReference -> {
val scopedName = it.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is NumericLiteral -> StArrayElement(it.number, null)
else -> throw FatalAstException("weird element dt in array literal")
}
+1 -1
View File
@@ -112,7 +112,7 @@ class TestCallgraph: FunSpec({
"""
val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program)
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
empties.size shouldBe 3
empties[0].position.line shouldBe 4
+19 -18
View File
@@ -12,7 +12,7 @@ import prog8.code.core.ZeropageWish
class TestSymbolTable: FunSpec({
test("empty symboltable") {
val st = SymbolTable()
st.scopedName shouldBe emptyList()
st.scopedName shouldBe ""
st.name shouldBe ""
st.type shouldBe StNodeType.GLOBAL
st.children shouldBe mutableMapOf()
@@ -21,50 +21,51 @@ class TestSymbolTable: FunSpec({
test("symboltable flatten") {
val st = makeSt()
st.flat[listOf("zzzzz")] shouldBe null
st.flat.getValue(listOf("msb")).type shouldBe StNodeType.BUILTINFUNC
st.flat.getValue(listOf("block2")).type shouldBe StNodeType.BLOCK
st.flat.getValue(listOf("block2", "sub2", "subsub", "label")).type shouldBe StNodeType.LABEL
st.flat[listOf("block2", "sub2", "subsub", "label", "zzzz")] shouldBe null
st.flat["zzzzz"] shouldBe null
st.flat.getValue("msb").type shouldBe StNodeType.BUILTINFUNC
st.flat.getValue("block2").type shouldBe StNodeType.BLOCK
st.flat.getValue("block2.sub2.subsub.label").type shouldBe StNodeType.LABEL
st.flat["block2.sub2.subsub.label.zzzz"] shouldBe null
}
test("symboltable global lookups") {
val st = makeSt()
st.lookupUnscoped("undefined") shouldBe null
st.lookup("undefined") shouldBe null
st.lookup(listOf("undefined")) shouldBe null
var default = st.lookupOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) }
st.lookup("undefined.undefined") shouldBe null
var default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) }
default.name shouldBe "default"
default = st.lookupOrElse(listOf("undefined")) { StNode("default", StNodeType.LABEL, Position.DUMMY) }
default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) }
default.name shouldBe "default"
val msbFunc = st.lookupOrElse("msb") { fail("msb must be found") }
val msbFunc = st.lookupUnscopedOrElse("msb") { fail("msb must be found") }
msbFunc.type shouldBe StNodeType.BUILTINFUNC
val variable = st.lookupOrElse(listOf("block1", "sub2", "v2")) { fail("v2 must be found") }
val variable = st.lookupOrElse("block1.sub2.v2") { fail("v2 must be found") }
variable.type shouldBe StNodeType.STATICVAR
}
test("symboltable nested lookups") {
val st = makeSt()
val sub1 = st.lookupOrElse(listOf("block1", "sub1")) { fail("should find sub1") }
val sub1 = st.lookupOrElse("block1.sub1") { fail("should find sub1") }
sub1.name shouldBe "sub1"
sub1.scopedName shouldBe listOf("block1", "sub1")
sub1.scopedName shouldBe "block1.sub1"
sub1.type shouldBe StNodeType.SUBROUTINE
sub1.children.size shouldBe 2
val v1 = sub1.lookupOrElse("v1") { fail("v1 must be found") } as StStaticVariable
val v1 = sub1.lookupUnscopedOrElse("v1") { fail("v1 must be found") } as StStaticVariable
v1.type shouldBe StNodeType.STATICVAR
v1.name shouldBe "v1"
v1.dt shouldBe DataType.BYTE
val blockc = sub1.lookupOrElse("blockc") { fail("blockc") } as StConstant
val blockc = sub1.lookupUnscopedOrElse("blockc") { fail("blockc") } as StConstant
blockc.type shouldBe StNodeType.CONSTANT
blockc.value shouldBe 999.0
val subsub = st.lookupOrElse(listOf("block2", "sub2", "subsub")) { fail("should find subsub") }
subsub.lookup("blockc") shouldBe null
subsub.lookup("label") shouldNotBe null
val subsub = st.lookupOrElse("block2.sub2.subsub") { fail("should find subsub") }
subsub.lookupUnscoped("blockc") shouldBe null
subsub.lookupUnscoped("label") shouldNotBe null
}
})
+16 -16
View File
@@ -368,16 +368,16 @@ main {
txt.print("const inv 255: ")
txt.print_ub(~ 0)
txt.nl()
bvalue = 129
txt.print("bitwise inv 126: ")
bvalue = ~ bvalue
txt.print_ub(bvalue)
txt.nl()
bvalue = 0
txt.print("bitwise inv 255: ")
bvalue = ~ bvalue
txt.print_ub(bvalue)
txt.nl()
; bvalue = 129
; txt.print("bitwise inv 126: ")
; bvalue = ~ bvalue
; txt.print_ub(bvalue)
; txt.nl()
; bvalue = 0
; txt.print("bitwise inv 255: ")
; bvalue = ~ bvalue
; txt.print_ub(bvalue)
; txt.nl()
txt.print("bitwise or 14: ")
txt.print_ub(ub1 | ub2 | ub3 | ub4)
@@ -397,12 +397,12 @@ main {
txt.print("bitwise xor 6: ")
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ 8)
txt.nl()
txt.print("bitwise not 247: ")
txt.print_ub(~ub3)
txt.nl()
txt.print("bitwise not 255: ")
txt.print_ub(~ub4)
txt.nl()
;txt.print("bitwise not 247: ")
;txt.print_ub(~ub3)
;txt.nl()
;txt.print("bitwise not 255: ")
;txt.print_ub(~ub4)
;txt.nl()
txt.print("not 0: ")
bvalue = 3 * (ub4 | not (ub3 | ub3 | ub3))
+26 -14
View File
@@ -5,9 +5,10 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import prog8.code.ast.*
import prog8.code.core.DataType
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.compiler.astprocessing.IntermediateAstMaker
import prog8.compiler.astprocessing.SymbolTableMaker
import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({
@@ -25,37 +26,48 @@ class TestIntermediateAst: FunSpec({
}
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val ast = IntermediateAstMaker(result.program).transform()
val target = C64Target()
val options = CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
floats = false,
noSysInit = true,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
val result = compileText(target, false, text, writeAssembly = false)!!
val st = SymbolTableMaker(result.program, options).make()
val ast = IntermediateAstMaker(result.program, st, options).transform()
ast.name shouldBe result.program.name
ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found")
entry.children.size shouldBe 4
entry.children.size shouldBe 5
entry.name shouldBe "start"
entry.scopedName shouldBe listOf("main", "start")
entry.scopedName shouldBe "main.start"
val blocks = ast.allBlocks().toList()
blocks.size shouldBeGreaterThan 1
blocks[0].name shouldBe "main"
blocks[0].scopedName shouldBe listOf("main")
blocks[0].scopedName shouldBe "main"
val vars = entry.children[0] as PtScopeVarsDecls
val ccInit = entry.children[1] as PtAssignment
ccInit.target.identifier?.targetName shouldBe listOf("main","start","cc")
val ccInit = entry.children[2] as PtAssignment
ccInit.target.identifier?.name shouldBe "main.start.cc"
(ccInit.value as PtNumber).number shouldBe 0.0
val ccdecl = vars.children[0] as PtVariable
val ccdecl = entry.children[0] as PtVariable
ccdecl.name shouldBe "cc"
ccdecl.scopedName shouldBe listOf("main", "start", "cc")
ccdecl.scopedName shouldBe "main.start.cc"
ccdecl.type shouldBe DataType.UBYTE
val arraydecl = vars.children[1] as PtVariable
val arraydecl = entry.children[1] as PtVariable
arraydecl.name shouldBe "array"
arraydecl.type shouldBe DataType.ARRAY_UB
val containmentCast = (entry.children[2] as PtAssignment).value as PtTypeCast
val containmentCast = (entry.children[3] as PtAssignment).value as PtTypeCast
containmentCast.type shouldBe DataType.UBYTE
val containment = containmentCast.value as PtContainmentCheck
(containment.element as PtNumber).number shouldBe 11.0
val fcall = (entry.children[3] as PtAssignment).value as PtFunctionCall
val fcall = (entry.children[4] as PtAssignment).value as PtFunctionCall
fcall.void shouldBe false
fcall.type shouldBe DataType.UBYTE
ast.print()
@@ -72,7 +72,7 @@ class TestAsmGenSymbols: StringSpec({
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
options.compTarget.machine.zeropage = C64Zeropage(options)
val st = SymbolTableMaker().makeFrom(program, options)
val st = SymbolTableMaker(program, options).make()
return AsmGen(program, st, options, errors)
}
+64 -2
View File
@@ -14,6 +14,7 @@ import prog8.intermediate.IRFileReader
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
import prog8.vm.VmRunner
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
@@ -218,7 +219,7 @@ main {
}
}
test("asmsub for virtual target") {
test("asmsub for virtual target not supported") {
val src = """
main {
sub start() {
@@ -230,6 +231,28 @@ main {
lda #99
tay
rts
}}
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val errors = ErrorReporterForTests()
compileText(target, false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "cannot use asmsub for vm target"
}
test("asmsub for virtual target not supported even with IR") {
val src = """
main {
sub start() {
void test(42)
}
asmsub test(ubyte xx @A) -> ubyte @Y {
%ir {{
return
}}
}
@@ -237,13 +260,52 @@ main {
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val errors = ErrorReporterForTests()
compileText(target, false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "cannot use asmsub for vm target"
}
test("inline asm for virtual target should be IR") {
val src = """
main {
sub start() {
%asm {{
lda #99
tay
rts
}}
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
val exc = shouldThrow<Exception> {
VmRunner().runProgram(virtfile.readText())
}
exc.message shouldContain("does not support non-IR asmsubs")
exc.message shouldContain("does not support real inlined assembly")
}
test("inline asm for virtual target with IR is accepted") {
val src = """
main {
sub start() {
%ir {{
return
}}
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
}
test("addresses from labels/subroutines not yet supported in VM") {
@@ -914,6 +914,16 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine
fun targetNameAndType(program: Program): Pair<String, DataType> {
val target=targetStatement(program)!! as INamedStatement
val targetname: String = if(target.name in program.builtinFunctions.names)
"<builtin>.${target.name}"
else
target.scopedName.joinToString(".")
val type = inferType(program).getOr(DataType.UNDEFINED)
return Pair(targetname, type)
}
override fun linkParents(parent: Node) {
this.parent = parent
}
+10 -5
View File
@@ -123,7 +123,7 @@ One or more .p8 module files
Auto-starts target system emulator after successful compilation.
emu2 starts the alternative emulator if available.
The compiled program and the symbol and breakpoint lists
(for the machine code monitor) are immediately loaded into the emulator..
(for the machine code monitor) are immediately loaded into the emulator (if it supports them)
``-out <directory>``
sets directory location for output files instead of current directory
@@ -202,13 +202,15 @@ You can create multiple source files yourself to modularize your large programs
multiple module files. You can also create "library" modules this way with handy routines,
that can be shared among programs. By importing those module files, you can use them in other modules.
It is possible to tell the compiler where it should look for these files, by using
the ``srcdirs`` command line option.
the ``srcdirs`` command line option. This can also be a lo-fi way to use different source files
for different compilation targets if you wish. Which is useful as currently the compiler
doesn't have conditional compilation like #ifdef/#endif in C.
.. _debugging:
Debugging (with VICE)
---------------------
Debugging (with VICE or Box16)
------------------------------
There's support for using the monitor and debugging capabilities of the rather excellent
`VICE emulator <http://vice-emu.sourceforge.net/>`_.
@@ -218,7 +220,7 @@ a *breakpoint* at that position. Some systems use a BRK instruction for this, bu
this will usually halt the machine altogether instead of just suspending execution.
Prog8 issues a NOP instruction instead and creates a 'virtual' breakpoint at this position.
All breakpoints are then written to a file called "programname.vice-mon-list",
which is meant to be used by the VICE emulator.
which is meant to be used by the VICE and Box16 emulators.
It contains a series of commands for VICE's monitor, including source labels and the breakpoint settings.
If you use the emulator autostart feature of the compiler, it will take care of this for you.
If you launch VICE manually, you'll have to use a command line option to load this file:
@@ -228,6 +230,9 @@ If you launch VICE manually, you'll have to use a command line option to load th
VICE will then use the label names in memory disassembly, and will activate any breakpoints as well.
If your running program hits one of the breakpoints, VICE will halt execution and drop you into the monitor.
Box16 is the alternative emulator for the Commander X16 and it also includes debugging facilities
that support these symbol and breakpoint lists.
Troubleshooting
---------------
+4 -3
View File
@@ -170,11 +170,12 @@ Required additional tools
It's very easy to compile yourself.
A recent precompiled .exe (only for Windows) can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
It's possible to use older versions, but it is very likely that the automatic VICE breakpoints won't work with them.
If you are on a Debian based Linux, there's a "64tass" package in the repositories. It's a bit old but seems to work.
It's possible to use these older versions of 64tass, but it is likely that the automatic VICE breakpoints won't work with them though.
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK in their packages repository instead.
For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK or similar in their packages repository instead.
For Windows it's possible to get that as well; check out `Adoptium <https://adoptium.net/temurin/releases/?version=11>`_ .
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
Finally: an **emulator** (or a real machine of course) to test and run your programs on.
+8 -4
View File
@@ -31,12 +31,14 @@ syslib
The "system library" for your target machine. It contains many system-specific definitions such
as ROM/Kernal subroutine definitions, memory location constants, and utility subroutines.
Depending on the compilation target, other routines may also be available in here specific to that target.
Best is to check the source code of the correct syslib module.
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
to write programs that work on both targets without modifications.
This module is usually imported automatically and can provide definitions in the ``sys``, ``c64``, ``cx16``, ``c128``, ``atari`` blocks
depending on the chosen compilation target. Read the `syslib source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib>`_ for the correct compilation target to see exactly what is there.
sys (part of syslib)
--------------------
``target``
@@ -91,13 +93,15 @@ sys (part of syslib)
``wait(uword jiffies)``
wait approximately the given number of jiffies (1/60th seconds)
note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
Note: the regular system irq handler has run for this to work as it depends on the system jiffy clock.
If this is is not possible (for instance because your program is running its own irq handler logic *and* no longer calls
the kernal's handler routine), you'll have to write your own wait routine instead.
``waitvsync()``
busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
can be used to avoid screen flicker/tearing when updating screen contents.
note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
note for cx16: the system irq handler has to be active for this to work (this is not required on c64)
note for cx16: the regular system irq handler has to run for this to work (this is not required on C64 and C128)
``waitrastborder()`` (c64/c128 targets only)
busy wait till the raster position has reached the bottom screen border (approximately)
+4 -4
View File
@@ -638,7 +638,7 @@ In most places where a number or other value is expected, you can use just the n
If possible, the expression is parsed and evaluated by the compiler itself at compile time, and the (constant) resulting value is used in its place.
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
Expressions can contain procedure and function calls.
There are various built-in functions such as sin(), cos() that can be used in expressions (see :ref:`builtinfunctions`).
There are various built-in functions that can be used in expressions (see :ref:`builtinfunctions`).
You can also reference identifiers defined elsewhere in your code.
Read the :ref:`syntaxreference` chapter for all details on the available operators and kinds of expressions you can write.
@@ -760,7 +760,8 @@ Math
^^^^
abs(x)
Absolute value of an integer. For floating point numbers, use ``floats.fabs()`` instead.
Absolute value of an integer. Value returned is an unsigned word.
For floating point numbers, use ``floats.fabs()`` instead.
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
@@ -837,8 +838,7 @@ pokew(address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
pokemon(address, value)
Attempts to write a byte to a ROM at a location in machine language monitor bank.
Doesn't have anything to do with a certain video game.
Doesn't do anything useful. Also doesn't have anything to do with a certain video game.
push(value)
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
+11 -1
View File
@@ -159,9 +159,19 @@ And for the Commander X16::
cx16.restore_irq() ; set everything back to the systems default irq handler
The Commander X16 provides two additional routines that should be used *in your IRQ handler routine* if it uses the Vera registers::
The Commander X16 syslib provides two additional routines that should be used *in your IRQ handler routine* if it uses the Vera registers.
They take care of saving and restoring the Vera state of the interrupted main program, otherwise the IRQ handler's manipulation
will corrupt any Vera operations that were going on in the main program. The routines are::
cx16.push_vera_context()
; ... do your work that uses vera here...
cx16.pop_vera_context()
.. caution::
It is advised to not use floating point calculations inside IRQ handler routines.
Beside them being very slow, there are intricate requirements such as having the
correct ROM bank enabled to be able to successfully call them (and making sure the correct
ROM bank is reset at the end of the handler), and the possibility
of corrupting variables and floating point calculations that are being executed
in the interrupted main program. These memory locations should be backed up
and restored at the end of the handler, further increasing its execution time...
+23 -13
View File
@@ -1,16 +1,25 @@
TODO
====
For next release
^^^^^^^^^^^^^^^^
- make sure bool value is always 0 or 1 (all casts should convert), then:
- rewrite bool=bool^1 into bool=not bool
- should solve: bool bb = not bb -> larger code than bool bb ^= 1
- should solve: bool xx = ~xx -> larger code than xx ^= 1
For next minor release
^^^^^^^^^^^^^^^^^^^^^^
...
For 9.0 major changes
^^^^^^^^^^^^^^^^^^^^^
- SEGMENTS.
- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class)
- block "golden" treated specially: every var in here will be allocated in the Golden ram area
- that block can only contain variables.
- the variables can NOT have initialization values, they will all be set to zero on startup (simple memset)
- just initialize them yourself in start() if you need a non-zero value
- OR.... do all this automatically if 'golden' is enabled as a compiler option? So compiler allocates in ZP first, then Golden Ram, then regular ram
- OR.... make all this more generic and use some %segment option to create real segments for 64tass?
- (need separate step in codegen and IR to write the "golden" variables)
- rewrite 6502 codegen on Pt* ast and symboltable, instead of CompilerAst nodes. (work in codegen-on-new-ast branch)
- optimize "dotted string" comments again.
Need help with
^^^^^^^^^^^^^^
@@ -30,13 +39,14 @@ Compiler:
- ir: mechanism to determine for chunks which registers are getting input values from "outside"
- ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk)
- ir: peephole opt: renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
- ir peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out!)
- ir: peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out! and don't renumber registers above SyscallRegisterBase!)
- ir: add more optimizations in IRPeepholeOptimizer
- ir: for expressions with array indexes that occur multiple times, can we avoid loading them into new virtualregs everytime and just reuse a single virtualreg as indexer?
- vm: somehow be able to load a label address as value? (VmProgramLoader)
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.
this is documented behavior to now loop around but it's too easy to forget about!
Lot of work because of so many special cases in ForLoopsAsmgen.....
How is it for the vm target? -> just 2 special cases in CodeGen.
- 6502 codegen: see if we can let for loops skip the loop if startvar>endvar, without adding a lot of code size/duplicating the loop condition.
It is documented behavior to now loop 'around' $00 but it's too easy to forget about!
Lot of work because of so many special cases in ForLoopsAsmgen..... (vm codegen already behaves like this)
- ir: can we determine for the loop variable in forloops if it could be kept in a (virtual) register instead of a real variable? Need to be able to check if the variable is used by another statement beside just the for loop.
- createAssemblyAndAssemble(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
but probably better to rewrite the 6502 codegen on top of the new Ast.
- generate WASM to eventually run prog8 on a browser canvas?
+4
View File
@@ -10,6 +10,10 @@ main {
const ubyte REPEATS = 2
sub print_speed(uword jiffies) {
if jiffies==0 {
txt.print("\n 0 jiffies measured, speed is extremely high\n")
return
}
float speed = 65536.0 * REPEATS / (jiffies as float / 60.0)
txt.nl()
txt.print_uw(jiffies)
+49 -20
View File
@@ -10,56 +10,85 @@ main {
sys.set_irqd()
uword old_keyhdl = cx16.KEYHDL
cx16.KEYHDL = &main.keyboard_scancode_handler.asm_shim
cx16.KEYHDL = &keyboard_scancode_handler
sys.clear_irqd()
bool escape_pressed
while not escape_pressed {
; just sit here
handle_keyboard_event()
}
sys.set_irqd()
cx16.KEYHDL = old_keyhdl
sys.clear_irqd()
}
sub keyboard_scancode_handler(ubyte prefix, ubyte scancode, bool updown) -> bool {
txt.print_ubhex(prefix, true)
; Keyboard handler communication variables.
; these need to be in block scope instead of in a subroutine,
; so that they won't get overwritten with initialization values every time.
; The assembly keyboard handler will set these, prog8 will read them.
bool @shared keyhdl_event ; is there a keyboard event to handle?
ubyte @shared keyhdl_prefix
ubyte @shared keyhdl_scancode
ubyte @shared keyhdl_updown
sub handle_keyboard_event() {
; Potentially handle keyboard event.
; Note that we do this from the program's main loop instead of
; the actual keyboard handler routine itself.
; The reason for this is documented below in the handler assembly routine.
if not keyhdl_event
return
keyhdl_event = false
txt.print_ubhex(keyhdl_prefix, true)
txt.chrout(':')
txt.print_ubhex(scancode, true)
txt.print_ubhex(keyhdl_scancode, true)
txt.spc()
if updown
if keyhdl_updown
txt.chrout('u')
else
txt.chrout('d')
txt.nl()
if prefix==0 and scancode==119 and updown {
if keyhdl_prefix==0 and keyhdl_scancode==119 and keyhdl_updown {
; escape was pressed! exit back to basic
main.start.escape_pressed = true
}
return true ; true = consume key event, false = continue processing it
}
asmsub keyboard_scancode_handler() {
; NOTE that the keyboard handler is an asm subroutine.
; Unfortunately is it not possible to use prog8 code or calls here,
; because the X register gets overwritten here (to store the prefix byte)
; and prog8 uses the X register internally (for the evaluation stack).
; So it is unsafe to call prog8 code from here because the evaluation stack pointer
; will be invalid which produces undefined results.
; So, instead, we store the various keyboard event bytes and signal
; the main prog8 program that a keyboard event has occurred.
; It then processes it independently from the assembly code here.
;
; Unfortunately this also means you cannot decide from that prog8 code
; if the keyboard press should be consumed/ignored or put into the keyboard queue
; (this is controlled by returning 0 or 1 in register A here)
asm_shim:
%asm {{
php
pha
phx
stz updown
stz keyhdl_updown
bcc +
inc updown
+ stx prefix
sta scancode
jsr keyboard_scancode_handler
beq +
inc keyhdl_updown
+ stx keyhdl_prefix
sta keyhdl_scancode
lda #1
sta keyhdl_event
; we can do additional stuff here and decide if we want to
; consume the key event or not (A=0 or A!=0)
plx
pla
lda #0 ;By setting A=0 we will consume this key event
lda #0 ;By setting A=0 we will remove this key event for now
tax
plp
rts
+ plx
pla ; leave A untouched, continue processing
plp
rts
}}
}
}
Binary file not shown.
Binary file not shown.
+5 -2
View File
@@ -110,10 +110,10 @@ char_loop:
vtui $1000 {
%asmbinary "VTUI0.9.BIN", 2 ; skip the 2 dummy load address bytes
%asmbinary "VTUI1.0.BIN", 2 ; skip the 2 dummy load address bytes
; NOTE: base address $1000 here must be the same as the block's memory address, for obvious reasons!
; The routines below are for VTUI 0.8
; The routines below are for VTUI 1.0
romsub $1000 = initialize() clobbers(A, X, Y)
romsub $1002 = screen_set(ubyte mode @A) clobbers(A, X, Y)
romsub $1005 = set_bank(ubyte bank @Pc) clobbers(A)
@@ -133,6 +133,9 @@ vtui $1000 {
romsub $102f = save_rect(ubyte ramtype @A, ubyte vbank @Pc, uword address @R0, ubyte width @R1, ubyte height @R2) clobbers(A, X, Y)
romsub $1032 = rest_rect(ubyte ramtype @A, ubyte vbank @Pc, uword address @R0, ubyte width @R1, ubyte height @R2) clobbers(A, X, Y)
romsub $1035 = input_str(uword buffer @R0, ubyte buflen @Y, ubyte colors @X) clobbers (A) -> ubyte @Y
romsub $1038 = get_bank() clobbers (A) -> ubyte @Pc
romsub $103b = get_stride() -> ubyte @A
romsub $103e = get_decr() clobbers (A) -> ubyte @Pc
; -- helper function to do string length counting for you internally, and turn the convertchars flag into a boolean again
asmsub print_str2(str txtstring @R0, ubyte colors @X, ubyte convertchars @Pc) clobbers(A, Y) {
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
kotlinVersion=1.7.21
kotlinVersion=1.8.0
@@ -221,7 +221,7 @@ class IRFileReader {
bss = false
initArray = value.split(',').map {
if (it.startsWith('@'))
StArrayElement(null, it.drop(1).split('.'))
StArrayElement(null, it.drop(1))
else
StArrayElement(parseIRValue(it).toDouble(), null)
}
@@ -169,7 +169,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
if(it.number!=null)
it.number!!.toInt().toHex()
else
"@${it.addressOf!!.joinToString(".")}"
"@${it.addressOfSymbol}"
}
} else {
"" // array will be zero'd out at program start
@@ -74,26 +74,29 @@ bstvc address - branch to location if Status bit Overf
bstvs address - branch to location if Status bit Overflow is not set
bz reg1, address - branch to location if reg1 is zero
bnz reg1, address - branch to location if reg1 is not zero
bgzs reg1, address - branch to location if reg1 > 0 (signed)
bgezs reg1, address - branch to location if reg1 >= 0 (signed)
blzs reg1, address - branch to location if reg1 < 0 (signed)
blezs reg1, address - branch to location if reg1 <= 0 (signed)
beq reg1, reg2, address - jump to location in program given by location, if reg1 == reg2
bne reg1, reg2, address - jump to location in program given by location, if reg1 != reg2
blt reg1, reg2, address - jump to location in program given by location, if reg1 < reg2 (unsigned)
blts reg1, reg2, address - jump to location in program given by location, if reg1 < reg2 (signed)
ble reg1, reg2, address - jump to location in program given by location, if reg1 <= reg2 (unsigned)
bles reg1, reg2, address - jump to location in program given by location, if reg1 <= reg2 (signed)
bgt reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (unsigned)
bgts reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (signed)
bge reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (unsigned)
bges reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (signed)
seq reg1, reg2 - set reg=1 if reg1 == reg2, otherwise set reg1=0
sne reg1, reg2 - set reg=1 if reg1 != reg2, otherwise set reg1=0
slt reg1, reg2 - set reg=1 if reg1 < reg2 (unsigned), otherwise set reg1=0
slts reg1, reg2 - set reg=1 if reg1 < reg2 (signed), otherwise set reg1=0
sle reg1, reg2 - set reg=1 if reg1 <= reg2 (unsigned), otherwise set reg1=0
sles reg1, reg2 - set reg=1 if reg1 <= reg2 (signed), otherwise set reg1=0
sgt reg1, reg2 - set reg=1 if reg1 > reg2 (unsigned), otherwise set reg1=0
sgts reg1, reg2 - set reg=1 if reg1 > reg2 (signed), otherwise set reg1=0
sge reg1, reg2 - set reg=1 if reg1 >= reg2 (unsigned), otherwise set reg1=0
sges reg1, reg2 - set reg=1 if reg1 >= reg2 (signed), otherwise set reg1=0
( NOTE: there are no blt/ble instructions because these are equivalent to bgt/bge with the operands swapped around.)
sz reg1, reg2 - set reg1=1 if reg2==0, otherwise set reg1=0
snz reg1, reg2 - set reg1=1 if reg2!=0, otherwise set reg1=0
seq reg1, reg2 - set reg1=1 if reg1 == reg2, otherwise set reg1=0
sne reg1, reg2 - set reg1=1 if reg1 != reg2, otherwise set reg1=0
slt reg1, reg2 - set reg1=1 if reg1 < reg2 (unsigned), otherwise set reg1=0
slts reg1, reg2 - set reg1=1 if reg1 < reg2 (signed), otherwise set reg1=0
sle reg1, reg2 - set reg1=1 if reg1 <= reg2 (unsigned), otherwise set reg1=0
sles reg1, reg2 - set reg1=1 if reg1 <= reg2 (signed), otherwise set reg1=0
sgt reg1, reg2 - set reg1=1 if reg1 > reg2 (unsigned), otherwise set reg1=0
sgts reg1, reg2 - set reg1=1 if reg1 > reg2 (signed), otherwise set reg1=0
sge reg1, reg2 - set reg1=1 if reg1 >= reg2 (unsigned), otherwise set reg1=0
sges reg1, reg2 - set reg1=1 if reg1 >= reg2 (signed), otherwise set reg1=0
ARITHMETIC
@@ -242,16 +245,18 @@ enum class Opcode {
BSTVS,
BZ,
BNZ,
BGZS,
BGEZS,
BLZS,
BLEZS,
BEQ,
BNE,
BLT,
BLTS,
BGT,
BGTS,
BLE,
BLES,
BGE,
BGES,
SZ,
SNZ,
SEQ,
SNE,
SLT,
@@ -377,14 +382,14 @@ val OpcodesThatBranch = setOf(
Opcode.BSTVS,
Opcode.BZ,
Opcode.BNZ,
Opcode.BGZS,
Opcode.BGEZS,
Opcode.BLZS,
Opcode.BLEZS,
Opcode.BEQ,
Opcode.BNE,
Opcode.BLT,
Opcode.BLTS,
Opcode.BGT,
Opcode.BGTS,
Opcode.BLE,
Opcode.BLES,
Opcode.BGE,
Opcode.BGES
)
@@ -417,14 +422,14 @@ val OpcodesWithMemoryAddressAsValue = setOf(
Opcode.BSTVS,
Opcode.BZ,
Opcode.BNZ,
Opcode.BGZS,
Opcode.BGEZS,
Opcode.BLZS,
Opcode.BLEZS,
Opcode.BEQ,
Opcode.BNE,
Opcode.BLT,
Opcode.BLTS,
Opcode.BGT,
Opcode.BGTS,
Opcode.BLE,
Opcode.BLES,
Opcode.BGE,
Opcode.BGES,
Opcode.INCM,
@@ -558,16 +563,18 @@ val instructionFormats = mutableMapOf(
Opcode.BSTVS to InstructionFormat.from("N,<v"),
Opcode.BZ to InstructionFormat.from("BW,<r1,<v"),
Opcode.BNZ to InstructionFormat.from("BW,<r1,<v"),
Opcode.BGZS to InstructionFormat.from("BW,<r1,<v"),
Opcode.BGEZS to InstructionFormat.from("BW,<r1,<v"),
Opcode.BLZS to InstructionFormat.from("BW,<r1,<v"),
Opcode.BLEZS to InstructionFormat.from("BW,<r1,<v"),
Opcode.BEQ to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BNE to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BLT to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BLTS to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BGT to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BGTS to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BLE to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BLES to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BGE to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.BGES to InstructionFormat.from("BW,<r1,<r2,<v"),
Opcode.SZ to InstructionFormat.from("BW,>r1,<r2"),
Opcode.SNZ to InstructionFormat.from("BW,>r1,<r2"),
Opcode.SEQ to InstructionFormat.from("BW,<>r1,<r2"),
Opcode.SNE to InstructionFormat.from("BW,<>r1,<r2"),
Opcode.SLT to InstructionFormat.from("BW,<>r1,<r2"),
@@ -734,8 +741,8 @@ data class IRInstruction(
fpReg1direction = format.fpReg1
fpReg2direction = format.fpReg2
if(opcode in setOf(Opcode.BEQ, Opcode.BNE, Opcode.BLT, Opcode.BLTS,
Opcode.BGT, Opcode.BGTS, Opcode.BLE, Opcode.BLES,
if(opcode in setOf(Opcode.BEQ, Opcode.BNE,
Opcode.BGT, Opcode.BGTS,
Opcode.BGE, Opcode.BGES,
Opcode.SEQ, Opcode.SNE, Opcode.SLT, Opcode.SLTS,
Opcode.SGT, Opcode.SGTS, Opcode.SLE, Opcode.SLES,
@@ -27,9 +27,9 @@ class IRSymbolTable(sourceSt: SymbolTable?) {
allVariables().forEach {variable ->
variable.onetimeInitializationArrayValue?.let {
it.forEach { arrayElt ->
if(arrayElt.addressOf!=null) {
require(arrayElt.addressOf!!.size > 1) {
"pointer var in array should be properly scoped: ${arrayElt.addressOf!!} in ${variable.name}"
if(arrayElt.addressOfSymbol!=null) {
require(arrayElt.addressOfSymbol!!.contains('.')) {
"pointer var in array should be properly scoped: ${arrayElt.addressOfSymbol} in ${variable.name}"
}
}
}
@@ -61,8 +61,8 @@ class IRSymbolTable(sourceSt: SymbolTable?) {
return null
val newArray = mutableListOf<StArrayElement>()
array.forEach {
if(it.addressOf!=null) {
val target = variable.lookup(it.addressOf!!)!!
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!)!!
newArray.add(StArrayElement(null, target.scopedName))
} else {
newArray.add(it)
@@ -70,7 +70,7 @@ class IRSymbolTable(sourceSt: SymbolTable?) {
}
return newArray
}
scopedName = variable.scopedName.joinToString(".")
scopedName = variable.scopedName
varToadd = StStaticVariable(scopedName, variable.dt, variable.bss,
variable.onetimeInitializationNumericValue,
variable.onetimeInitializationStringValue,
@@ -91,7 +91,7 @@ class IRSymbolTable(sourceSt: SymbolTable?) {
scopedName = variable.name
varToadd = variable
} else {
scopedName = variable.scopedName.joinToString(".")
scopedName = variable.scopedName
varToadd = StMemVar(scopedName, variable.dt, variable.address, variable.length, variable.position)
}
table[scopedName] = varToadd
+101 -38
View File
@@ -36,6 +36,7 @@ class VirtualMachine(irProgram: IRProgram) {
val registers = Registers()
val callStack = Stack<Pair<IRCodeChunk, Int>>()
val valueStack = Stack<UByte>() // max 128 entries
var breakpointHandler: ((pcChunk: IRCodeChunk, pcIndex: Int) -> Unit)? = null // can set custom breakpoint handler
var pcChunk: IRCodeChunk
var pcIndex = 0
var stepCount = 0
@@ -176,16 +177,18 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.BSTVC, Opcode.BSTVS -> TODO("overflow status flag not yet supported in VM (BSTVC,BSTVS)")
Opcode.BZ -> InsBZ(ins)
Opcode.BNZ -> InsBNZ(ins)
Opcode.BGZS -> InsBGZS(ins)
Opcode.BGEZS -> InsBGEZS(ins)
Opcode.BLZS -> InsBLZS(ins)
Opcode.BLEZS -> InsBLEZS(ins)
Opcode.BEQ -> InsBEQ(ins)
Opcode.BNE -> InsBNE(ins)
Opcode.BLT -> InsBLTU(ins)
Opcode.BLTS -> InsBLTS(ins)
Opcode.BGT -> InsBGTU(ins)
Opcode.BGTS -> InsBGTS(ins)
Opcode.BLE -> InsBLEU(ins)
Opcode.BLES -> InsBLES(ins)
Opcode.BGE -> InsBGEU(ins)
Opcode.BGES -> InsBGES(ins)
Opcode.SZ -> InsSZ(ins)
Opcode.SNZ -> InsSNZ(ins)
Opcode.SEQ -> InsSEQ(ins)
Opcode.SNE -> InsSNE(ins)
Opcode.SLT -> InsSLT(ins)
@@ -346,7 +349,10 @@ class VirtualMachine(irProgram: IRProgram) {
private fun InsBREAKPOINT() {
nextPc()
throw BreakpointException(pcChunk, pcIndex)
if(breakpointHandler!=null)
breakpointHandler?.invoke(pcChunk, pcIndex)
else
throw BreakpointException(pcChunk, pcIndex)
}
private fun InsLOADCPU(i: IRInstruction) {
@@ -660,6 +666,78 @@ class VirtualMachine(irProgram: IRProgram) {
}
}
private fun InsBGZS(i: IRInstruction) {
when(i.type!!) {
IRDataType.BYTE -> {
if(registers.getSB(i.reg1!!)>0)
branchTo(i)
else
nextPc()
}
IRDataType.WORD -> {
if(registers.getSW(i.reg1!!)>0)
branchTo(i)
else
nextPc()
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
}
private fun InsBGEZS(i: IRInstruction) {
when(i.type!!) {
IRDataType.BYTE -> {
if(registers.getSB(i.reg1!!)>=0)
branchTo(i)
else
nextPc()
}
IRDataType.WORD -> {
if(registers.getSW(i.reg1!!)>=0)
branchTo(i)
else
nextPc()
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
}
private fun InsBLZS(i: IRInstruction) {
when(i.type!!) {
IRDataType.BYTE -> {
if(registers.getSB(i.reg1!!)<0)
branchTo(i)
else
nextPc()
}
IRDataType.WORD -> {
if(registers.getSW(i.reg1!!)<0)
branchTo(i)
else
nextPc()
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
}
private fun InsBLEZS(i: IRInstruction) {
when(i.type!!) {
IRDataType.BYTE -> {
if(registers.getSB(i.reg1!!)<=0)
branchTo(i)
else
nextPc()
}
IRDataType.WORD -> {
if(registers.getSW(i.reg1!!)<=0)
branchTo(i)
else
nextPc()
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
}
private fun InsBEQ(i: IRInstruction) {
val (left: Int, right: Int) = getBranchOperands(i)
if(left==right)
@@ -676,22 +754,6 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun InsBLTU(i: IRInstruction) {
val (left: UInt, right: UInt) = getBranchOperandsU(i)
if(left<right)
branchTo(i)
else
nextPc()
}
private fun InsBLTS(i: IRInstruction) {
val (left: Int, right: Int) = getBranchOperands(i)
if(left<right)
branchTo(i)
else
nextPc()
}
private fun InsBGTU(i: IRInstruction) {
val (left: UInt, right: UInt) = getBranchOperandsU(i)
if(left>right)
@@ -709,23 +771,6 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun InsBLEU(i: IRInstruction) {
val (left: UInt, right: UInt) = getBranchOperandsU(i)
if(left<=right)
branchTo(i)
else
nextPc()
}
private fun InsBLES(i: IRInstruction) {
val (left: Int, right: Int) = getBranchOperands(i)
if(left<=right)
branchTo(i)
else
nextPc()
}
private fun InsBGEU(i: IRInstruction) {
val (left: UInt, right: UInt) = getBranchOperandsU(i)
if(left>=right)
@@ -743,6 +788,20 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun InsSZ(i: IRInstruction) {
val (_: Int, right: Int) = getSetOnConditionOperands(i)
val value = if(right==0) 1 else 0
setResultReg(i.reg1!!, value, i.type!!)
nextPc()
}
private fun InsSNZ(i: IRInstruction) {
val (_: Int, right: Int) = getSetOnConditionOperands(i)
val value = if(right!=0) 1 else 0
setResultReg(i.reg1!!, value, i.type!!)
nextPc()
}
private fun InsSEQ(i: IRInstruction) {
val (left: Int, right: Int) = getSetOnConditionOperands(i)
val value = if(left==right) 1 else 0
@@ -2162,6 +2221,10 @@ class VmRunner: IVirtualMachineRunner {
fun runAndTestProgram(irSource: String, test: (VirtualMachine) -> Unit) {
val irProgram = IRFileReader().read(irSource)
val vm = VirtualMachine(irProgram)
// vm.breakpointHandler = { pcChunk, pcIndex ->
// println("UNHANDLED BREAKPOINT")
// println(" IN CHUNK: $pcChunk(${pcChunk.label}) INDEX: $pcIndex = INSTR ${pcChunk.instructions[pcIndex]}")
// }
vm.run()
test(vm)
}
+4 -11
View File
@@ -38,14 +38,7 @@ class VmProgramLoader {
block.children.forEach { child ->
when(child) {
is IRAsmSubroutine -> {
if(!child.asmChunk.isIR)
throw IRParseException("vm currently does not support non-IR asmsubs: ${child.label}")
else {
val replacement = addAssemblyToProgram(child.asmChunk, programChunks, variableAddresses)
chunkReplacements += replacement
}
}
is IRAsmSubroutine -> throw IRParseException("vm does not support asmsubs (use normal sub): ${child.label}")
is IRCodeChunk -> programChunks += child
is IRInlineAsmChunk -> {
val replacement = addAssemblyToProgram(child, programChunks, variableAddresses)
@@ -284,10 +277,10 @@ class VmProgramLoader {
}
DataType.ARRAY_UW -> {
for(elt in it) {
if(elt.addressOf!=null) {
val name = elt.addressOf!!.joinToString(".")
if(elt.addressOfSymbol!=null) {
val name = elt.addressOfSymbol!!
val symbolAddress = symbolAddresses[name]
?: throw IRParseException("vm cannot yet load a label address as a value: ${name}") // TODO
?: throw IRParseException("vm cannot yet load a label address as a value: $name") // TODO
memory.setUW(addr, symbolAddress.toUShort())
} else {
memory.setUW(addr, elt.number!!.toInt().toUShort())
+3 -21
View File
@@ -2,7 +2,6 @@ import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.intermediate.*
@@ -84,7 +83,7 @@ class TestVm: FunSpec( {
}
}
test("non-IR asmsub not supported in vm") {
test("asmsub not supported in vm even with IR") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRAsmSubroutine(
@@ -93,33 +92,16 @@ class TestVm: FunSpec( {
emptySet(),
emptyList(),
emptyList(),
IRInlineAsmChunk("main.asmstart", "inlined asm here", false, null),
IRInlineAsmChunk("main.asmstart", "return", false, null),
Position.DUMMY
)
block += startSub
program.addBlock(block)
shouldThrowWithMessage<IRParseException>("vm currently does not support non-IR asmsubs: main.asmstart") {
shouldThrowWithMessage<IRParseException>("vm does not support asmsubs (use normal sub): main.asmstart") {
VirtualMachine(program)
}
}
test("IR asmsub ok in vm") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRAsmSubroutine(
"main.start",
0x2000u,
emptySet(),
emptyList(),
emptyList(),
IRInlineAsmChunk("main.start", "return", true, null),
Position.DUMMY
)
block += startSub
program.addBlock(block)
VirtualMachine(program).run()
}
test("vmrunner") {
val runner = VmRunner()
val irSource="""<?xml version="1.0" encoding="utf-8"?>