mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
914f19be86 | ||
|
|
d0b18dec8e | ||
|
|
549c598f51 | ||
|
|
7b59bc8d12 | ||
|
|
79d0fb0b52 | ||
|
|
edf56d34f8 | ||
|
|
6733253826 | ||
|
|
86210c4513 | ||
|
|
e08da659e5 | ||
|
|
e9ec310d8a | ||
|
|
19a2791c65 | ||
|
|
f1a7d5ecf7 | ||
|
|
8b05abb80d | ||
|
|
9a2df072cc | ||
|
|
224278e07a | ||
|
|
74b69e191e | ||
|
|
8cda8a727c | ||
|
|
4403e4ed62 | ||
|
|
6ee270d9d8 | ||
|
|
44fa309d20 | ||
|
|
58d88f3dd4 | ||
|
|
e980c23177 | ||
|
|
75224321bb | ||
|
|
801af05b20 | ||
|
|
7611dbbddc | ||
|
|
6d40ca15bc | ||
|
|
32c1c19224 | ||
|
|
bbf6357222 | ||
|
|
dc16629c24 | ||
|
|
3718b9d768 | ||
|
|
c25eb088ec | ||
|
|
3feb3e52f8 | ||
|
|
8e730ef93d | ||
|
|
e0913a39ab | ||
|
|
7a27fbc001 | ||
|
|
ee0dbdad35 | ||
|
|
9225f88f89 | ||
|
|
a04839dd6b | ||
|
|
002006517a | ||
|
|
f5b202d438 | ||
|
|
a7df094ff4 | ||
|
|
1e6fa77633 | ||
|
|
eb4cff202c | ||
|
|
7ee777f405 |
No files matched your search
@@ -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
|
||||
Generated
+1
-1
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
8.8.1
|
||||
8.9
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
---------------
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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?
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"?>
|
||||
|
||||
Reference in new issue
Block a user