Compare commits

..
31 Commits
Author SHA1 Message Date
Irmen de Jong f874942075 version 10.1 2024-02-04 16:22:43 +01:00
Irmen de Jong 2cadb546d5 optimized in-place memory/pointervar operations some more 2024-02-04 15:17:24 +01:00
Irmen de Jong 344a1b9eb8 cx16: added cx16.get_program_args() and cx16.set_program_args() 2024-02-04 11:35:13 +01:00
Irmen de Jong 3c77f8a020 IR: optimize pointer access 2024-02-04 07:51:50 +01:00
Irmen de Jong 8e00408e3e optimize pointer indexing a bit 2024-02-03 23:24:33 +01:00
Irmen de Jong abcdfd8e28 optimize postincrdecr on memory a bit 2024-02-03 22:33:27 +01:00
Irmen de Jong b0f5b6925d getting rid of pointer[idx] in ast, instead always use @(ptr+idx) 2024-02-03 19:22:04 +01:00
Irmen de Jong ef79d0c43e fix temp variable name clash resulting in faulty code in certain common subexpression optimization 2024-02-03 19:20:28 +01:00
Irmen de Jong 78b4288005 avoid git's line ending translation for this test... 2024-02-03 14:57:25 +01:00
Irmen de Jong 680f5d21ee normalize DOS line endings to just \n, fixes source lines in asm for \r\n source files 2024-02-03 12:53:21 +01:00
Irmen de Jong c71aa0895f get rid of certain redundant !=0 comparisons in logical expressions 2024-02-01 21:50:01 +01:00
Irmen de Jong 9f8e61789a fix 65c02 codegen for certain in-place byte bitwise invert 2024-01-31 23:55:43 +01:00
Irmen de Jong 932035cdc5 flag "returning a statement" as a syntax error 2024-01-31 21:36:39 +01:00
Irmen de Jong ef198f1493 fix for certain invalid expression evaluation code.
it no longer reuses the same temporary variable all the time.

todo
2024-01-31 21:18:21 +01:00
Irmen de Jong 48ef856c0b identified wrong expr eval code - still need solution. Crash for now. 2024-01-30 23:00:50 +01:00
Irmen de Jong 9aea2b22c4 doc improvement, fixes #120 2024-01-30 19:07:18 +01:00
Irmen de Jong e0055bc431 IR: working on inplace +/- 2024-01-29 22:08:47 +01:00
Irmen de Jong 9553248ed6 IR: integrate inplace assignment ops 2024-01-28 22:33:16 +01:00
Irmen de Jong 39d2194d8f IR: implemented inplace prefix op on split array
VM: NEG instructions also set N and Z flags
2024-01-28 22:33:13 +01:00
Irmen de Jong 0800033b47 fixed split-word array in-place element +/- (other operators not yet...) 2024-01-28 22:30:45 +01:00
Irmen de Jong 64d8943b7d fix error when trying to post-inc/decrement a character in a string 2024-01-28 16:47:55 +01:00
Irmen de Jong 444e97b00b todo 2024-01-27 12:31:36 +01:00
Irmen de Jong 1816bda7ea extra check 2024-01-26 00:12:31 +01:00
Irmen de Jong d4a2031c07 fix certain assignment data type mismatch crash 2024-01-25 21:14:20 +01:00
Irmen de Jong 8cf0b6cf51 IR: optimize code for ==0 and !=0 augmented assigns 2024-01-25 00:30:47 +01:00
Irmen de Jong f2010bf7a5 IR: better code for array element assignments (w.i.p.) 2024-01-23 00:56:06 +01:00
Irmen de Jong 8f56a7fe69 IR: use INV instead of XOR for bitwise invert 2024-01-22 22:47:54 +01:00
Irmen de Jong 64c132ee0a changed -breakinstr option so that you now specify the exact instruction to use for a %breakpoint.
also fixed a IR issue with x=not x.
2024-01-22 22:01:47 +01:00
Irmen de Jong 84a7e86fe3 clarify progend() 2024-01-22 18:57:25 +01:00
Irmen de Jong a8c09d6144 removed a problematic bitshift replacement in the const evaluator 2024-01-21 23:05:51 +01:00
Irmen de Jong 87c46ba730 check boolean array size mismatch.
check for weird string assignment.
check for X16 problematic cpu instructions rmb, smb, bbr, bbs.
tweak number node equality wrt bool type
2024-01-21 19:15:22 +01:00
46 changed files with 1688 additions and 761 deletions

No files matched your search

@@ -138,16 +138,16 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
val variable: PtIdentifier
get() = children[0] as PtIdentifier
get() {
require((children[0] as? PtIdentifier)?.type in ArrayDatatypes+DataType.STR) // TODO remove
return children[0] as PtIdentifier
}
val index: PtExpression
get() = children[1] as PtExpression
val splitWords: Boolean
get() = variable.type in SplitWordArrayTypes
val usesPointerVariable: Boolean
get() = variable.type==DataType.UWORD
init {
require(elementType in NumericDatatypes)
}
@@ -250,7 +250,10 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtNumber)
return false
return number==other.number
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
return number==other.number
else
return type==other.type && number==other.number
}
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
@@ -25,7 +25,7 @@ class CompilationOptions(val output: OutputType,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var breakpointCpuInstruction: Boolean = false,
var breakpointCpuInstruction: String? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
) {
+6 -3
View File
@@ -69,7 +69,8 @@ sealed class SourceCode {
* Turn a plain String into a [SourceCode] object.
* [origin] will be something like `string:44c56085`.
*/
class Text(override val text: String): SourceCode() {
class Text(origText: String): SourceCode() {
override val text = origText.replace("\\R".toRegex(), "\n") // normalize line endings
override val isFromResources = false
override val isFromFilesystem = false
override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}"
@@ -95,7 +96,8 @@ sealed class SourceCode {
val normalized = path.normalize()
origin = relative(normalized).toString()
try {
text = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
val contents = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
text = contents.replace("\\R".toRegex(), "\n") // normalize line endings
name = normalized.toFile().nameWithoutExtension
} catch (nfx: java.nio.file.NoSuchFileException) {
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
@@ -127,7 +129,8 @@ sealed class SourceCode {
)
}
val stream = object {}.javaClass.getResourceAsStream(normalized)
text = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
val contents = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
text = contents.replace("\\R".toRegex(), "\n") // normalize line endings
name = Path(pathString).toFile().nameWithoutExtension
}
}
@@ -22,6 +22,8 @@ private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
}
private var tempVarCounter = 0
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
fun extractableSubExpr(expr: PtExpression): Boolean {
@@ -74,7 +76,8 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
val containerScopedName = findScopeName(stmtContainer)
val tempvarName = "subexprvar_line${binexpr.position.line}_${binexpr.hashCode().toUInt()}"
tempVarCounter++
val tempvarName = "prog8_subexprvar_$tempVarCounter"
// TODO: some tempvars could be reused, if they are from different lines
val datatype = occurrence1.type
@@ -95,7 +98,7 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
tempassign.parent = stmtContainer
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.DONTCARE, null, null, binexpr.position)
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.NOT_IN_ZEROPAGE, null, null, binexpr.position)
stmtContainer.add(0, tempvar)
tempvar.parent = stmtContainer
@@ -7,6 +7,7 @@ import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.assignment.*
import java.util.*
import kotlin.io.path.Path
@@ -223,7 +224,7 @@ class AsmGen6502Internal (
internal val loopEndLabels = ArrayDeque<String>()
private val zeropage = options.compTarget.machine.zeropage
private val allocator = VariableAllocator(symbolTable, options, errors)
private val assemblyLines = mutableListOf<String>()
private val assembly = mutableListOf<String>()
private val breakpointLabels = mutableListOf<String>()
private val forloopsAsmGen = ForLoopsAsmGen(this, zeropage)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
@@ -235,7 +236,7 @@ class AsmGen6502Internal (
fun compileToAssembly(): IAssemblyProgram? {
assemblyLines.clear()
assembly.clear()
loopEndLabels.clear()
println("Generating assembly code... ")
@@ -243,15 +244,22 @@ class AsmGen6502Internal (
if(errors.noErrors()) {
val output = options.outputDir.resolve("${program.name}.asm")
val asmLines = assembly.asSequence().flatMapTo(mutableListOf()) { it.split('\n') }
if(options.compTarget.name==Cx16Target.NAME) {
scanInvalid65816instructions(asmLines)
if(!errors.noErrors()) {
errors.report()
return null
}
}
if(options.optimize) {
val separateLines = assemblyLines.flatMapTo(mutableListOf()) { it.split('\n') }
assemblyLines.clear()
while(optimizeAssembly(separateLines, options.compTarget.machine, symbolTable)>0) {
while(optimizeAssembly(asmLines, options.compTarget.machine, symbolTable)>0) {
// optimize the assembly source code
}
output.writeLines(separateLines)
output.writeLines(asmLines)
} else {
output.writeLines(assemblyLines)
// write the unmodified code
output.writeLines(assembly)
}
return AssemblyProgram(program.name, options.outputDir, options.compTarget)
} else {
@@ -260,6 +268,22 @@ class AsmGen6502Internal (
}
}
private fun scanInvalid65816instructions(asmLines: MutableList<String>) {
// The CommanderX16 ships with a WDC 65C02 CPU or a WDC 65816 CPU
// The latter is compatible with the 65C02 except for 4 instructions: RMB, SMB, BBS, BBR.
// We cannot set a different 6502 CPU target for the 64tass assembler, because we still need to support the STP and WAI instructions...
// so we have to scan for these instructions ourselves.
val invalid = Regex("""\s*((rmb\s|smb\s|bbs\s|bbr\s)|(rmb[0-7]|smb[0-7]|bbs[0-7]|bbr[0-7]))""", RegexOption.IGNORE_CASE)
for((index, line) in asmLines.withIndex()) {
if(line.length>=4 && invalid.matchesAt(line, 0)) {
errors.err(
"invalid assembly instruction used (not compatible with the 65816 CPU): ${line.trim()}",
Position("<output-assemblycode>", index, 1, 1)
)
}
}
}
internal fun isTargetCpu(cpu: CpuType) = options.compTarget.machine.cpu == cpu
private var lastSourceLineNumber: Int = -1
@@ -283,9 +307,9 @@ class AsmGen6502Internal (
for (line in fragment.splitToSequence('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
assembly.add(trimmed)
}
} else assemblyLines.add(fragment)
} else assembly.add(fragment)
}
fun asmSymbolName(regs: RegisterOrPair): String =
@@ -332,15 +356,20 @@ class AsmGen6502Internal (
name
}
internal val tempVarsCounters = mutableMapOf(
DataType.BYTE to 0,
DataType.UBYTE to 0,
DataType.WORD to 0,
DataType.UWORD to 0,
DataType.FLOAT to 0
)
internal fun buildTempVarName(dt: DataType, counter: Int): String = "prog8_tmpvar_${dt.toString().lowercase()}_$counter"
internal fun getTempVarName(dt: DataType): String {
return when(dt) {
DataType.UBYTE -> "cx16.r9L"
DataType.BYTE -> "cx16.r9sL"
DataType.UWORD -> "cx16.r9"
DataType.WORD -> "cx16.r9s"
DataType.FLOAT -> "floats.floats_temp_var"
else -> throw AssemblyError("invalid dt")
}
tempVarsCounters[dt] = tempVarsCounters.getValue(dt)+1
return buildTempVarName(dt, tempVarsCounters.getValue(dt))
}
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
@@ -1056,7 +1085,7 @@ $repeatLabel""")
if(asm.isIR)
throw AssemblyError("%asm containing IR code cannot be translated to 6502 assembly")
else
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
assembly.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
}
private fun translate(incbin: PtIncludeBinary) {
@@ -1078,14 +1107,8 @@ $repeatLabel""")
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out(label)
if(options.breakpointCpuInstruction) {
val instruction =
when(options.compTarget.machine.cpu) {
CpuType.CPU6502 -> "brk"
CpuType.CPU65c02 -> "stp"
else -> throw AssemblyError("invalid cpu type")
}
out(" $instruction")
if(options.breakpointCpuInstruction!=null) {
out(" ${options.breakpointCpuInstruction}")
}
}
@@ -1169,7 +1192,7 @@ $repeatLabel""")
return null
}
internal fun tryOptimizedPointerAccessWithA(addressExpr: PtExpression, operator: String, write: Boolean): Boolean {
internal fun tryOptimizedPointerAccessWithA(addressExpr: PtBinaryExpression, write: Boolean): Boolean {
// optimize pointer,indexregister if possible
fun evalBytevalueWillClobberA(expr: PtExpression): Boolean {
@@ -1185,7 +1208,7 @@ $repeatLabel""")
}
}
if(operator=="+") {
if(addressExpr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? PtIdentifier
@@ -2896,7 +2919,7 @@ $repeatLabel""")
}
is PtBinaryExpression -> {
val addrExpr = expr.address as PtBinaryExpression
if(!tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
if(!tryOptimizedPointerAccessWithA(addrExpr, false)) {
assignViaExprEval()
}
}
@@ -677,7 +677,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtArrayIndexer -> {
val indexer = fcall.args[0] as PtArrayIndexer
require(!indexer.usesPointerVariable)
val elementSize: Int
val msbAdd: Int
if(indexer.splitWords) {
@@ -846,8 +845,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
else -> {
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
asmgen.assignExpressionTo(fcall.args[1],
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, tempvar, null, null, null, null))
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
pha
@@ -1,9 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtNumber
import prog8.code.ast.PtPostIncrDecr
import prog8.code.ast.PtProgram
import prog8.code.ast.*
import prog8.code.core.*
@@ -37,6 +34,55 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
}
}
targetMemory!=null -> {
fun incDecViaExprEval() {
asmgen.assignExpressionToRegister(targetMemory.address, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
fun tryOptimizedPointerIncDec(address: PtBinaryExpression): Boolean {
if(address.operator=="+") {
val offset = address.right.asConstInteger()
if(offset!=null && offset<256) {
// we have @(ptr + 255) ++ , or @(ptr+255)--
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out("""
sta (+) + 1
sty (+) + 2
ldx #$offset""")
if(incr)
asmgen.out("+\tinc ${'$'}ffff,x\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff,x\t; modified")
return true
} else if(address.right.type in ByteDatatypes) {
// we have @(ptr + bytevar) ++ , or @(ptr+bytevar)--
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.X, false)
if(incr)
asmgen.out("+\tinc ${'$'}ffff,x\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff,x\t; modified")
return true
} else if((address.right as? PtTypeCast)?.value?.type in ByteDatatypes) {
// we have @(ptr + bytevar) ++ , or @(ptr+bytevar)--
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.assignExpressionToRegister((address.right as PtTypeCast).value, RegisterOrPair.X, false)
if(incr)
asmgen.out("+\tinc ${'$'}ffff,x\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff,x\t; modified")
return true
}
}
return false
}
when (val addressExpr = targetMemory.address) {
is PtNumber -> {
val what = addressExpr.number.toHex()
@@ -50,13 +96,13 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
is PtBinaryExpression -> {
if(!tryOptimizedPointerIncDec(addressExpr)) {
incDecViaExprEval()
}
}
else -> {
asmgen.assignExpressionToRegister(addressExpr, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
incDecViaExprEval()
}
}
}
@@ -91,22 +137,7 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> {
if(targetArrayIdx.usesPointerVariable) {
asmgen.out("""
lda $asmArrayvarname
clc
adc #$indexValue
sta (+) +1
lda $asmArrayvarname+1
adc #0
sta (+) +2""")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
} else {
asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
}
asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
}
in WordDatatypes -> {
if(incr)
@@ -130,22 +161,7 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
when(elementDt) {
in ByteDatatypes -> {
if(targetArrayIdx.usesPointerVariable) {
asmgen.out("""
txa
clc
adc $asmArrayvarname
sta (+) +1
lda $asmArrayvarname+1
adc #0
sta (+) +2""")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
} else {
asmgen.out(if (incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
asmgen.out(if (incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
@@ -49,6 +49,7 @@ internal class ProgramAndVarsGen(
}
memorySlabs()
tempVars()
footer()
}
}
@@ -165,6 +166,26 @@ internal class ProgramAndVarsGen(
}
}
private fun tempVars() {
asmgen.out("; expression temp vars\n .section BSS")
for((dt, count) in asmgen.tempVarsCounters) {
if(count>0) {
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
DataType.BYTE -> asmgen.out("$name .char ?")
DataType.UBYTE -> asmgen.out("$name .byte ?")
DataType.WORD -> asmgen.out("$name .sint ?")
DataType.UWORD -> asmgen.out("$name .word ?")
DataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
}
}
}
asmgen.out(" .send BSS")
}
private fun footer() {
var relocateBssVars = false
var relocateBssSlabs = false
@@ -64,32 +64,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val value = assign.source.array!!
val elementDt = assign.source.datatype
val arrayVarName = asmgen.asmVariableName(value.variable)
if(value.usesPointerVariable) {
if(elementDt !in ByteDatatypes)
throw AssemblyError("non-array var indexing requires bytes dt")
if(value.type != DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index")
if(asmgen.isTargetCpu(CpuType.CPU65c02) && (value.index as? PtNumber)?.number==0.0) {
if (asmgen.isZpVar(value.variable)) {
asmgen.out(" lda ($arrayVarName)")
} else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
asmgen.out(" lda (P8ZP_SCRATCH_W1)")
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if (asmgen.isZpVar(value.variable)) {
asmgen.out(" lda ($arrayVarName),y")
} else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
}
}
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, false)
return
}
val constIndex = value.index.asConstInteger()
if(value.splitWords) {
@@ -170,7 +144,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtBinaryExpression -> {
val addrExpr = value.address as PtBinaryExpression
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
assignRegisterByte(assign.target, CpuRegister.A, false, true)
} else {
assignViaExprEval(value.address)
@@ -798,34 +772,37 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return true
}
else -> {
val leftMemByte = expr.left as? PtMemoryByte
val rightMemByte = expr.right as? PtMemoryByte
val leftArrayIndexer = expr.left as? PtArrayIndexer
val rightArrayIndexer = expr.right as? PtArrayIndexer
if(rightArrayIndexer!=null && rightArrayIndexer.type in ByteDatatypes && left.type in ByteDatatypes) {
// special optimization for bytevalue +/- bytearr[y] : no need to use a tempvar, just use adc array,y or sbc array,y or adc (ptr),y / sbc (ptr),y
if(expr.operator=="+" && leftArrayIndexer!=null && leftArrayIndexer.type in ByteDatatypes && right.type in ByteDatatypes) {
// special optimization for bytearray[y] + bytevalue : no need to use a tempvar, just use adc array,y
assignExpressionToRegister(right, RegisterOrPair.A, right.type==DataType.BYTE)
if(!leftArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(leftArrayIndexer.index, RegisterOrPair.Y, false)
if(!leftArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(leftArrayIndexer.variable)
asmgen.out(" clc | adc $arrayvarname,y")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
} else if(rightArrayIndexer!=null && rightArrayIndexer.type in ByteDatatypes && left.type in ByteDatatypes) {
// special optimization for bytevalue +/- bytearray[y] : no need to use a tempvar, just use adc array,y or sbc array,y
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(rightArrayIndexer.index, RegisterOrPair.Y, false)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pla")
if(rightArrayIndexer.usesPointerVariable && !asmgen.isZpVar(rightArrayIndexer.variable)) {
asmgen.out("""
ldx ${rightArrayIndexer.variable.name}
stx P8ZP_SCRATCH_W1
ldx ${rightArrayIndexer.variable.name}+1
stx P8ZP_SCRATCH_W1+1""")
if (expr.operator == "+")
asmgen.out(" clc | adc (P8ZP_SCRATCH_W1),y")
else
asmgen.out(" sec | sbc (P8ZP_SCRATCH_W1),y")
} else {
val arrayvarname = if (rightArrayIndexer.usesPointerVariable)
"(${rightArrayIndexer.variable.name})"
else
asmgen.asmSymbolName(rightArrayIndexer.variable)
if (expr.operator == "+")
asmgen.out(" clc | adc $arrayvarname,y")
else
asmgen.out(" sec | sbc $arrayvarname,y")
}
val arrayvarname = asmgen.asmSymbolName(rightArrayIndexer.variable)
if (expr.operator == "+")
asmgen.out(" clc | adc $arrayvarname,y")
else
asmgen.out(" sec | sbc $arrayvarname,y")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
} else if(expr.operator=="+" && leftMemByte!=null && right.type in ByteDatatypes && optimizedPointerIndexPlusMinusByteIntoA(right, "+", leftMemByte)) {
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
return true
} else if(rightMemByte!=null && left.type in ByteDatatypes && optimizedPointerIndexPlusMinusByteIntoA(left, expr.operator, rightMemByte)) {
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
return true
} else {
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
if(directIntoY(right)) {
@@ -1010,6 +987,47 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return false
}
private fun optimizedPointerIndexPlusMinusByteIntoA(value: PtExpression, operator: String, mem: PtMemoryByte): Boolean {
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
val address = mem.address as? PtBinaryExpression
if(address is PtBinaryExpression) {
val ptrVar = address.left as? PtIdentifier
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar)
val constOffset = address.right.asConstInteger()
if (constOffset != null && constOffset < 256) {
// we have value + @(zpptr + 255), or value - @(zpptr+255)
asmgen.out(" ldy #$constOffset")
if (operator == "+")
asmgen.out(" clc | adc ($pointername),y")
else
asmgen.out(" sec | sbc ($pointername),y")
} else if (address.right.type in ByteDatatypes) {
// we have @(ptr + bytevar) ++ , or @(ptr+bytevar)--
asmgen.out(" pha")
assignExpressionToRegister(address.right, RegisterOrPair.Y, false)
asmgen.out(" pla")
if (operator == "+")
asmgen.out(" clc | adc ($pointername),y")
else
asmgen.out(" sec | sbc ($pointername),y")
} else if ((address.right as? PtTypeCast)?.value?.type in ByteDatatypes) {
// we have @(ptr + bytevar as uword) ++ , or @(ptr+bytevar as uword)--
asmgen.out(" pha")
assignExpressionToRegister((address.right as PtTypeCast).value, RegisterOrPair.Y, false)
asmgen.out(" pla")
if (operator == "+")
asmgen.out(" clc | adc ($pointername),y")
else
asmgen.out(" sec | sbc ($pointername),y")
}
return true
}
}
return false
}
private fun optimizedBitwiseExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if (expr.right.isSimple()) {
@@ -2034,7 +2052,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtBinaryExpression -> {
val addrExpr = value.address as PtBinaryExpression
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
} else {
@@ -2836,21 +2854,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(assignsIndexedPointerVar(target)) {
if (target.constArrayIndexValue==0u) {
asmgen.out(" lda $sourceName")
asmgen.storeAIntoPointerVar(target.origAstTarget!!.array!!.variable)
} else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
if (asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" lda $sourceName | sta (${target.asmVarname}),y")
} else {
asmgen.out(" lda ${target.asmVarname} | sta P8ZP_SCRATCH_W2 | lda ${target.asmVarname}+1 | sta P8ZP_SCRATCH_W2+1")
asmgen.out(" lda $sourceName | sta (P8ZP_SCRATCH_W2),y")
}
}
return
}
if(target.array!!.splitWords)
TODO("assign into split words ${target.position}")
if (target.constArrayIndexValue!=null) {
@@ -3256,87 +3259,29 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(target.array!!.splitWords)
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
if(assignsIndexedPointerVar(target)) {
// assign byte to a byte array via an uword pointervariable instead of a regular array variable
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy #${target.constArrayIndexValue} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy #${target.constArrayIndexValue}
sta (P8ZP_SCRATCH_W1),y""")
}
// assign regular array indexing
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as? PtIdentifier
if(indexVar!=null) {
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy ${asmgen.asmVariableName(indexVar)}
sta (P8ZP_SCRATCH_W1),y""")
}
} else {
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.out(" pla")
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldx ${target.asmVarname}
stx P8ZP_SCRATCH_W1
ldx ${target.asmVarname}+1
stx P8ZP_SCRATCH_W1+1
sta (P8ZP_SCRATCH_W1),y""")
}
}
asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
return
} else {
// assign regular array indexing
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as? PtIdentifier
if(indexVar!=null) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
} else {
require(target.array.index.type in ByteDatatypes)
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.out(" pla | sta ${target.asmVarname},y")
}
val indexVar = target.array.index as? PtIdentifier
if(indexVar!=null) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
} else {
require(target.array.index.type in ByteDatatypes)
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.out(" pla | sta ${target.asmVarname},y")
}
}
}
@@ -3632,21 +3577,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(assignsIndexedPointerVar(target)) {
if (target.constArrayIndexValue==0u) {
asmgen.out(" lda #0")
asmgen.storeAIntoPointerVar(target.origAstTarget!!.array!!.variable)
} else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
if (asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" lda #0 | sta (${target.asmVarname}),y")
} else {
asmgen.out(" lda ${target.asmVarname} | sta P8ZP_SCRATCH_W2 | lda ${target.asmVarname}+1 | sta P8ZP_SCRATCH_W2+1")
asmgen.out(" lda #0 | sta (P8ZP_SCRATCH_W2),y")
}
}
return
}
if(target.array!!.splitWords)
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
if (target.constArrayIndexValue!=null) {
@@ -3686,21 +3616,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(assignsIndexedPointerVar(target)) {
if (target.constArrayIndexValue==0u) {
asmgen.out(" lda #${byte.toHex()}")
asmgen.storeAIntoPointerVar(target.origAstTarget!!.array!!.variable)
} else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
if (asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" lda #${byte.toHex()} | sta (${target.asmVarname}),y")
} else {
asmgen.out(" lda ${target.asmVarname} | sta P8ZP_SCRATCH_W2 | lda ${target.asmVarname}+1 | sta P8ZP_SCRATCH_W2+1")
asmgen.out(" lda #${byte.toHex()} | sta (P8ZP_SCRATCH_W2),y")
}
}
return
}
if(target.array!!.splitWords)
TODO("assign into split words ${target.position}")
if (target.constArrayIndexValue!=null) {
@@ -3732,9 +3647,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignsIndexedPointerVar(target: AsmAssignTarget): Boolean =
target.origAstTarget?.array?.variable?.type==DataType.UWORD
internal fun assignConstantFloat(target: AsmAssignTarget, float: Double) {
if (float == 0.0) {
// optimized case for float zero
@@ -4007,7 +3919,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
}
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, addressExpr.operator, true))
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
storeViaExprEval()
}
else -> storeViaExprEval()
@@ -4061,9 +3973,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sta $addr""")
}
is PtIdentifier -> {
val sourceName = asmgen.loadByteFromPointerIntoA(memory.address as PtIdentifier)
asmgen.loadByteFromPointerIntoA(memory.address as PtIdentifier)
asmgen.out(" eor #255")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoPointerVar(memory.address as PtIdentifier)
}
else -> {
asmgen.assignExpressionToVariable(memory.address, "P8ZP_SCRATCH_W2", DataType.UWORD)
@@ -163,7 +163,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
else -> {
asmgen.assignExpressionTo(memory.address, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, memory.definingISub(), target.position, register = RegisterOrPair.AY))
if(memory.address is PtBinaryExpression && tryOptimizedMemoryInplace(memory.address as PtBinaryExpression, operator, value))
return
asmgen.assignExpressionToRegister(memory.address, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.saveRegisterStack(CpuRegister.Y, true)
asmgen.out(" jsr prog8_lib.read_byte_from_address_in_AY_into_A")
@@ -191,12 +193,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
val tempVar = asmgen.getTempVarName(DataType.UBYTE)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value.expression)
inplacemodificationByteVariableWithValue(tempVar, DataType.UBYTE, operator, value.expression)
else
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value.expression!!)
asmgen.out(" ldx P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue(tempVar, DataType.UBYTE, operator, value.expression!!)
asmgen.out(" ldx $tempVar")
}
}
asmgen.restoreRegisterStack(CpuRegister.Y, false)
@@ -208,10 +211,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
TargetStorageKind.ARRAY -> {
val indexNum = target.array!!.index as? PtNumber
if (indexNum!=null) {
val targetVarName = if(target.array.splitWords)
"${target.asmVarname} + ${indexNum.number.toInt()}"
else
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
val index = indexNum.number.toInt()
if(target.array.splitWords) {
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationSplitWordWithLiteralval(target.asmVarname, index, operator, value.number!!.number.toInt())
else -> {
// TODO: more optimized code for VARIABLE, REGISTER, MEMORY, ARRAY, EXPRESSION in the case of split-word arrays
val scope = target.origAstTarget?.definingSub()
val regTarget = AsmAssignTarget.fromRegisters(RegisterOrPair.R0, false, target.position, scope, asmgen)
val assignToReg = AsmAssignment(value, regTarget, program.memsizer, target.position)
assignmentAsmGen.translateNormalAssignment(assignToReg, scope)
inplacemodificationSplitWordWithR0(target.asmVarname, index, operator)
}
}
return
}
// normal array
val targetVarName = "${target.asmVarname} + ${index*program.memsizer.memorySize(target.datatype)}"
when (target.datatype) {
in ByteDatatypes -> {
when(value.kind) {
@@ -312,13 +328,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
val tempVar = asmgen.getTempVarName(DataType.UBYTE)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
inplacemodificationByteVariableWithValue(tempVar, target.datatype, operator, value.expression)
else
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
inplacemodificationByteVariableWithValue(tempVar, target.datatype, operator, value.expression!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
asmgen.out(" lda $tempVar")
}
}
asmgen.out(" sta ${target.array.variable.name},y")
@@ -386,12 +403,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
val tempVar = asmgen.getTempVarName(DataType.UWORD)
asmgen.out(" sta $tempVar | stx $tempVar+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
else
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression!!, block)
asmgen.out(" lda $tempVar | ldx $tempVar+1")
}
}
asmgen.restoreRegisterStack(CpuRegister.Y, true)
@@ -458,6 +476,133 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun tryOptimizedMemoryInplace(address: PtBinaryExpression, operator: String, value: AsmAssignSource): Boolean {
if(value.datatype !in ByteDatatypes || operator !in "|&^+-")
return false
val rightTc = address.right as? PtTypeCast
val constOffset = (address.right as? PtNumber)?.number?.toInt()
if(address.operator=="+" && (address.right.type in ByteDatatypes || (rightTc!=null && rightTc.value.type in ByteDatatypes) || (constOffset!=null && constOffset<256)) ) {
if(rightTc!=null)
asmgen.assignExpressionToRegister(rightTc.value, RegisterOrPair.A, false)
else if(constOffset!=null)
asmgen.out(" lda #${constOffset}")
else
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.A, false)
asmgen.out(" pha") // offset on stack
val zpPointerVarName: String
if(address.left is PtIdentifier && asmgen.isZpVar(address.left as PtIdentifier)) {
zpPointerVarName = (address.left as PtIdentifier).name
} else {
zpPointerVarName = "P8ZP_SCRATCH_W2"
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out(" sta $zpPointerVarName | sty $zpPointerVarName+1")
}
// calculate value into A
val assignValue = AsmAssignment(value,
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE,
address.definingISub(), Position.DUMMY, register = RegisterOrPair.A),
program.memsizer, Position.DUMMY)
assignmentAsmGen.translateNormalAssignment(assignValue, address.definingISub())
asmgen.restoreRegisterStack(CpuRegister.Y, false) // offset into Y
when(operator) {
"|" -> asmgen.out(" ora ($zpPointerVarName),y")
"&" -> asmgen.out(" and ($zpPointerVarName),y")
"^" -> asmgen.out(" eor ($zpPointerVarName),y")
"+" -> asmgen.out(" clc | adc ($zpPointerVarName),y")
"-" -> asmgen.out(" sta P8ZP_SCRATCH_REG | lda ($zpPointerVarName),y | sec | sbc P8ZP_SCRATCH_REG")
else -> throw AssemblyError("invalid op")
}
asmgen.out(" sta ($zpPointerVarName),y")
return true
}
return false
}
private fun inplacemodificationSplitWordWithR0(arrayVar: String, index: Int, operator: String) {
when (operator) {
"+" -> {
asmgen.out("""
lda ${arrayVar}_lsb+$index
clc
adc cx16.r0L
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
adc cx16.r0H
sta ${arrayVar}_msb+$index""")
}
"-" -> {
asmgen.out("""
lda ${arrayVar}_lsb+$index
sec
sbc cx16.r0L
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
sbc cx16.r0H
sta ${arrayVar}_msb+$index""")
}
else -> TODO("in-place modify split-words array value for operator $operator")
}
}
private fun inplacemodificationSplitWordWithLiteralval(arrayVar: String, index: Int, operator: String, value: Int) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
when (operator) {
"+" -> {
when {
value==0 -> {}
value in 1..0xff -> asmgen.out("""
lda ${arrayVar}_lsb+$index
clc
adc #$value
sta ${arrayVar}_lsb+$index
bcc +
inc ${arrayVar}_msb+$index
+""")
value==0x0100 -> asmgen.out(" inc ${arrayVar}_msb+$index")
value==0x0200 -> asmgen.out(" inc ${arrayVar}_msb+$index | inc ${arrayVar}_msb+$index")
value==0x0300 -> asmgen.out(" inc ${arrayVar}_msb+$index | inc ${arrayVar}_msb+$index | inc ${arrayVar}_msb+$index")
value==0x0400 -> asmgen.out(" inc ${arrayVar}_msb+$index | inc ${arrayVar}_msb+$index | inc ${arrayVar}_msb+$index | inc ${arrayVar}_msb+$index")
value and 255==0 -> asmgen.out(" lda ${arrayVar}_msb+$index | clc | adc #>$value | sta ${arrayVar}_msb+$index")
else -> asmgen.out("""
lda ${arrayVar}_lsb+$index
clc
adc #<$value
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
adc #>$value
sta ${arrayVar}_msb+$index""")
}
}
"-" -> {
when {
value==0 -> {}
value in 1..0xff -> asmgen.out("""
lda ${arrayVar}_lsb+$index
sec
sbc #$value
sta ${arrayVar}_lsb+$index
bcs +
dec ${arrayVar}_msb+$index
+""")
value==0x0100 -> asmgen.out(" dec ${arrayVar}_msb+$index")
value==0x0200 -> asmgen.out(" dec ${arrayVar}_msb+$index | dec ${arrayVar}_msb+$index")
value==0x0300 -> asmgen.out(" dec ${arrayVar}_msb+$index | dec ${arrayVar}_msb+$index | dec ${arrayVar}_msb+$index")
value==0x0400 -> asmgen.out(" dec ${arrayVar}_msb+$index | dec ${arrayVar}_msb+$index | dec ${arrayVar}_msb+$index | dec ${arrayVar}_msb+$index")
value and 255==0 -> asmgen.out(" lda ${arrayVar}_msb+$index | sec | sbc #>$value | sta ${arrayVar}_msb+$index")
else -> asmgen.out("""
lda ${arrayVar}_lsb+$index
sec
sbc #<$value
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
sbc #>$value
sta ${arrayVar}_msb+$index""")
}
}
else -> TODO("in-place modify split-words array value for operator $operator")
}
}
private fun inplacemodificationRegisterAXwithVariable(operator: String, variable: String, varDt: DataType): Boolean {
when(operator) {
"+" -> {
@@ -14,5 +14,6 @@
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
@@ -1,12 +1,13 @@
package prog8.codegen.intermediate
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.getOrElse
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PrefixOperators
import prog8.code.core.SignedDatatypes
import prog8.code.core.*
import prog8.intermediate.*
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
internal fun translate(assignment: PtAssignment): IRCodeChunks {
@@ -22,95 +23,57 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(augAssign.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
val ident = augAssign.target.identifier
val memory = augAssign.target.memory
val array = augAssign.target.array
val target = augAssign.target
val targetDt = irType(target.type)
val memTarget = target.memory
val constAddress = (memTarget?.address as? PtNumber)?.number?.toInt()
val symbol = target.identifier?.name
val array = target.array
val value = augAssign.value
val signed = target.type in SignedDatatypes
val result: Result<IRCodeChunks, NotImplementedError> = when(augAssign.operator) {
"+=" -> expressionEval.operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"-=" -> expressionEval.operatorMinusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value)
"/=" -> expressionEval.operatorDivideInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"|=" -> expressionEval.operatorOrInplace(symbol, array, constAddress, memTarget, targetDt, value)
"or=" -> expressionEval.operatorLogicalOrInplace(symbol, array, constAddress, memTarget, targetDt, value)
"&=" -> expressionEval.operatorAndInplace(symbol, array, constAddress, memTarget, targetDt, value)
"and=" -> expressionEval.operatorLogicalAndInplace(symbol, array, constAddress, memTarget, targetDt, value)
"^=", "xor=" -> expressionEval.operatorXorInplace(symbol, array, constAddress, memTarget, targetDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(symbol, array, constAddress, memTarget, targetDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"%=" -> expressionEval.operatorModuloInplace(symbol, array, constAddress, memTarget, targetDt, value)
"==" -> expressionEval.operatorEqualsInplace(symbol, array, constAddress, memTarget, targetDt, value)
"!=" -> expressionEval.operatorNotEqualsInplace(symbol, array, constAddress, memTarget, targetDt, value)
"<" -> expressionEval.operatorLessInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
">" -> expressionEval.operatorGreaterInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"<=" -> expressionEval.operatorLessEqualInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
">=" -> expressionEval.operatorGreaterEqualInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
in PrefixOperators -> inplacePrefix(augAssign.operator, symbol, array, constAddress, memTarget, targetDt)
val chunks = if(ident!=null) {
assignVarAugmented(ident.name, augAssign)
} else if(memory != null) {
if(memory.address is PtNumber)
assignMemoryAugmented((memory.address as PtNumber).number.toInt(), augAssign)
else
fallbackAssign(augAssign)
} else if(array!=null) {
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
// will be optimized later and have the double assignments removed.
fallbackAssign(augAssign)
} else {
fallbackAssign(augAssign)
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
val chunks = result.getOrElse { fallbackAssign(augAssign) }
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(augAssign.position)
return chunks
}
private fun assignMemoryAugmented(
address: Int,
assignment: PtAugmentedAssign
): IRCodeChunks {
val value = assignment.value
val targetDt = irType(assignment.target.type)
val signed = assignment.target.type in SignedDatatypes
return when(assignment.operator) {
"+=" -> expressionEval.operatorPlusInplace(address, null, targetDt, value)
"-=" -> expressionEval.operatorMinusInplace(address, null, targetDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(address, null, targetDt, value)
"/=" -> expressionEval.operatorDivideInplace(address, null, targetDt, signed, value)
"|=" -> expressionEval.operatorOrInplace(address, null, targetDt, value)
"&=" -> expressionEval.operatorAndInplace(address, null, targetDt, value)
"^=" -> expressionEval.operatorXorInplace(address, null, targetDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(address, null, targetDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(address, null, targetDt, signed, value)
"%=" -> expressionEval.operatorModuloInplace(address, null, targetDt, value)
"==" -> expressionEval.operatorEqualsInplace(address, null, targetDt, value)
"!=" -> expressionEval.operatorNotEqualsInplace(address, null, targetDt, value)
"<" -> expressionEval.operatorLessInplace(address, null, targetDt, signed, value)
">" -> expressionEval.operatorGreaterInplace(address, null, targetDt, signed, value)
"<=" -> expressionEval.operatorLessEqualInplace(address, null, targetDt, signed, value)
">=" -> expressionEval.operatorGreaterEqualInplace(address, null, targetDt, signed, value)
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, address, null)
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
}
}
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
val value = assignment.value
val signed = assignment.target.type in SignedDatatypes
val targetDt = irType(assignment.target.type)
return when(assignment.operator) {
"+=" -> expressionEval.operatorPlusInplace(null, symbol, targetDt, value)
"-=" -> expressionEval.operatorMinusInplace(null, symbol, targetDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(null, symbol, targetDt, value)
"/=" -> expressionEval.operatorDivideInplace(null, symbol, targetDt, signed, value)
"|=" -> expressionEval.operatorOrInplace(null, symbol, targetDt, value)
"or=" -> expressionEval.operatorLogicalOrInplace(null, symbol, targetDt, value)
"&=" -> expressionEval.operatorAndInplace(null, symbol, targetDt, value)
"and=" -> expressionEval.operatorLogicalAndInplace(null, symbol, targetDt, value)
"^=", "xor=" -> expressionEval.operatorXorInplace(null, symbol, targetDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, targetDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, targetDt, signed, value)
"%=" -> expressionEval.operatorModuloInplace(null, symbol, targetDt, value)
"==" -> expressionEval.operatorEqualsInplace(null, symbol, targetDt, value)
"!=" -> expressionEval.operatorNotEqualsInplace(null, symbol, targetDt, value)
"<" -> expressionEval.operatorLessInplace(null, symbol, targetDt, signed, value)
">" -> expressionEval.operatorGreaterInplace(null, symbol, targetDt, signed, value)
"<=" -> expressionEval.operatorLessEqualInplace(null, symbol, targetDt, signed, value)
">=" -> expressionEval.operatorGreaterEqualInplace(null, symbol, targetDt, signed, value)
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, null, symbol)
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
}
}
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
val value: PtExpression
if(origAssign.operator in PrefixOperators) {
value = PtPrefix(origAssign.operator, origAssign.target.type, origAssign.value.position)
value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
value.add(origAssign.value)
} else {
require(origAssign.operator.endsWith('='))
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.target.type, origAssign.value.position)
val operator = when(origAssign.operator) {
in ComparisonOperators -> origAssign.operator
else -> {
require(origAssign.operator.endsWith('='))
origAssign.operator.dropLast(1)
}
}
value = PtBinaryExpression(operator, origAssign.value.type, origAssign.value.position)
val left: PtExpression = origAssign.target.children.single() as PtExpression
value.add(left)
value.add(origAssign.value)
@@ -121,28 +84,161 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return translateRegularAssign(normalAssign)
}
private fun inplacePrefix(operator: String, vmDt: IRDataType, address: Int?, symbol: String?): IRCodeChunks {
val code= IRCodeChunk(null, null)
private fun inplacePrefix(operator: String, symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType): Result<IRCodeChunks, NotImplementedError> {
if(operator=="+")
return Ok(emptyList())
if(array!=null)
return Ok(inplacePrefixArray(operator, array))
val result = mutableListOf<IRCodeChunkBase>()
if(constAddress==null && memory!=null) {
val register = codeGen.registers.nextFree()
val tr = expressionEval.translateExpression(memory.address)
addToResult(result, tr, tr.resultReg, -1)
when(operator) {
"-" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, vmDt, reg1=register, reg2=tr.resultReg)
it += IRInstruction(Opcode.NEG, vmDt, reg1=register)
it += IRInstruction(Opcode.STOREI, vmDt, reg1=register, reg2=tr.resultReg)
}
}
"~" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, vmDt, reg1=register, reg2=tr.resultReg)
it += IRInstruction(Opcode.INV, vmDt, reg1=register)
it += IRInstruction(Opcode.STOREI, vmDt, reg1=register, reg2=tr.resultReg)
}
}
}
} else {
when (operator) {
"-" -> addInstr(result, IRInstruction(Opcode.NEGM, vmDt, address = constAddress, labelSymbol = symbol), null)
"~" -> addInstr(result, IRInstruction(Opcode.INVM, vmDt, address = constAddress, labelSymbol = symbol), null)
// TODO: in boolean branch, how is 'not' handled here?
else -> throw AssemblyError("weird prefix operator")
}
}
return Ok(result)
}
private fun inplacePrefixArray(operator: String, array: PtArrayIndexer): IRCodeChunks {
val eltSize = codeGen.program.memsizer.memorySize(array.type)
val result = mutableListOf<IRCodeChunkBase>()
val vmDt = irType(array.type)
val constIndex = array.index.asConstInteger()
fun loadIndex(): Int {
val tr = expressionEval.translateExpression(array.index)
addToResult(result, tr, tr.resultReg, -1)
if(!array.splitWords && eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
return tr.resultReg
}
if(array.splitWords) {
// handle split LSB/MSB arrays
when(operator) {
"+" -> { }
"-" -> {
val skipCarryLabel = codeGen.createLabelName()
if(constIndex!=null) {
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = array.variable.name+"_lsb", symbolOffset = constIndex), null)
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel), null)
addInstr(result, IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = array.variable.name+"_msb", symbolOffset = constIndex), null)
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = array.variable.name+"_msb", symbolOffset = constIndex), skipCarryLabel)
} else {
val indexReg = loadIndex()
val registerLsb = codeGen.registers.nextFree()
val registerMsb = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = registerLsb, reg2 = indexReg, labelSymbol = array.variable.name+"_lsb")
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1 = registerLsb)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = registerLsb, reg2 = indexReg, labelSymbol = array.variable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = registerMsb, reg2 = indexReg, labelSymbol = array.variable.name+"_msb")
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1 = registerMsb)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = registerLsb, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel)
it += IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1 = registerMsb)
}
result += IRCodeChunk(skipCarryLabel, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = registerMsb, reg2 = indexReg, labelSymbol = array.variable.name+"_msb")
}
}
}
"~" -> {
if(constIndex!=null) {
addInstr(result, IRInstruction(Opcode.INVM, IRDataType.BYTE, labelSymbol = array.variable.name+"_lsb", symbolOffset = constIndex), null)
addInstr(result, IRInstruction(Opcode.INVM, IRDataType.BYTE, labelSymbol = array.variable.name+"_msb", symbolOffset = constIndex), null)
} else {
val indexReg = loadIndex()
val register = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name+"_lsb")
it += IRInstruction(Opcode.INV, IRDataType.BYTE, reg1 = register)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name+"_msb")
it += IRInstruction(Opcode.INV, IRDataType.BYTE, reg1 = register)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name+"_msb")
}
}
}
else -> throw AssemblyError("weird prefix operator")
}
return result
}
// normal array.
when(operator) {
"+" -> { }
"-" -> {
code += if(address!=null)
IRInstruction(Opcode.NEGM, vmDt, address = address)
else
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = symbol)
if(constIndex!=null) {
addInstr(result, IRInstruction(Opcode.NEGM, vmDt, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize), null)
} else {
val indexReg = loadIndex()
val register = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name)
it += IRInstruction(Opcode.NEG, vmDt, reg1 = register)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name)
}
}
}
"~" -> {
val regMask = codeGen.registers.nextFree()
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, immediate = mask)
code += if(address!=null)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, address = address)
else
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = symbol)
if(constIndex!=null) {
addInstr(result, IRInstruction(Opcode.INVM, vmDt, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize), null)
} else {
val indexReg = loadIndex()
val register = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name)
it += IRInstruction(Opcode.INV, vmDt, reg1 = register)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name)
}
}
}
"not" -> {
// TODO: in boolean branch, is 'not' handled ok like this?
val register = codeGen.registers.nextFree()
if(constIndex!=null) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, vmDt, reg1=register, immediate = 1)
it += IRInstruction(Opcode.XORM, vmDt, reg1=register, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
}
} else {
val indexReg = loadIndex()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name)
it += IRInstruction(Opcode.XOR, vmDt, reg1 = register, immediate = 1)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = array.variable.name)
}
}
}
else -> throw AssemblyError("weird prefix operator")
}
return listOf(code)
return result
}
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
@@ -206,25 +302,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val variable = targetArray.variable.name
val itemsize = codeGen.program.memsizer.memorySize(targetArray.type)
if(targetArray.usesPointerVariable) {
if(itemsize!=1)
throw AssemblyError("non-array var indexing requires bytes dt")
if(targetArray.index.type!=DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index")
val tr = expressionEval.translateExpression(targetArray.index)
val idxReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
val code = IRCodeChunk(null, null)
if(zero) {
// there's no STOREZIX instruction
valueRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, targetDt, reg1=valueRegister, immediate = 0)
}
code += IRInstruction(Opcode.STOREIX, targetDt, reg1=valueRegister, reg2=idxReg, labelSymbol = variable)
result += code
return result
}
val fixedIndex = constIntValue(targetArray.index)
val arrayLength = codeGen.symbolTable.getLength(targetArray.variable.name)
if(zero) {
@@ -306,18 +383,47 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg) }
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
}
}
} else {
if(targetMemory.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=(targetMemory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg) }
val constAddress = targetMemory.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
return result
}
val ptrWithOffset = targetMemory.address as? PtBinaryExpression
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
if((ptrWithOffset.right as? PtNumber)?.number?.toInt() in 0..255) {
// STOREIX only works with byte index.
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = ptrWithOffset.right.asConstInteger())
it += IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=offsetReg, labelSymbol = ptrName)
}
return result
}
}
val offsetTypecast = ptrWithOffset?.right as? PtTypeCast
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier
&& (ptrWithOffset.right.type in ByteDatatypes || offsetTypecast?.value?.type in ByteDatatypes)) {
// STOREIX only works with byte index.
val tr = if(offsetTypecast?.value?.type in ByteDatatypes)
expressionEval.translateExpression(offsetTypecast!!.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return result
}
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
return result
}
return result
@@ -705,7 +705,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
is PtArrayIndexer -> {
require(!target.usesPointerVariable)
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
File diff suppressed because it is too large. Load diff
@@ -475,8 +475,8 @@ class IRCodeGen(
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
val concatReg = registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
}
@@ -1462,7 +1462,6 @@ class IRCodeGen(
else -> throw AssemblyError("weird operator")
}
} else {
val arrayLength = symbolTable.getLength(array.variable.name)
val indexTr = expressionEval.translateExpression(array.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val incReg = registers.nextFree()
@@ -1470,28 +1469,28 @@ class IRCodeGen(
when(postIncrDecr.operator) {
"++" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=incReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.BSTNE, labelSymbol = skipLabel)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_msb")
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=incReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_msb")
}
result += IRCodeChunk(skipLabel, null)
}
"--" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.BSTNE, labelSymbol = skipLabel)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_msb")
it += IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=incReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_msb")
}
result += IRCodeChunk(skipLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=incReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=incReg, reg2=indexTr.resultReg, labelSymbol = "${variable}_lsb")
}
}
else -> throw AssemblyError("weird operator")
@@ -1517,38 +1516,60 @@ class IRCodeGen(
if(ident!=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()
addInstr(result, IRInstruction(operationMem, irDt, address = address), null)
} else {
val tr = expressionEval.translateExpression(memory.address)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val constAddress = memory.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(operationMem, irDt, address = constAddress.number.toInt()), null)
return result
}
val ptrWithOffset = memory.address as? PtBinaryExpression
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
if((ptrWithOffset.right as? PtNumber)?.number?.toInt() in 0..255) {
// LOADIX only works with byte index.
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val offsetReg = registers.nextFree()
val incReg = registers.nextFree()
it += IRInstruction(Opcode.LOADI, irDt, reg1 = incReg, reg2 = tr.resultReg)
it += IRInstruction(operationRegister, irDt, reg1 = incReg)
it += IRInstruction(Opcode.STOREI, irDt, reg1 = incReg, reg2 = tr.resultReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = ptrWithOffset.right.asConstInteger())
it += IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=incReg, reg2=offsetReg, labelSymbol = ptrName)
it += IRInstruction(operationRegister, irDt, reg1 = incReg)
it += IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=incReg, reg2=offsetReg, labelSymbol = ptrName)
}
return result
}
}
val offsetTypecast = ptrWithOffset?.right as? PtTypeCast
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier
&& (ptrWithOffset.right.type in ByteDatatypes || offsetTypecast?.value?.type in ByteDatatypes)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type in ByteDatatypes)
expressionEval.translateExpression(offsetTypecast!!.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
result += IRCodeChunk(null, null).also {
val incReg = registers.nextFree()
it += IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=incReg, reg2=tr.resultReg, labelSymbol = ptrName)
it += IRInstruction(operationRegister, irDt, reg1 = incReg)
it += IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=incReg, reg2=tr.resultReg, labelSymbol = ptrName)
}
return result
}
val tr = expressionEval.translateExpression(memory.address)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val incReg = registers.nextFree()
it += IRInstruction(Opcode.LOADI, irDt, reg1 = incReg, reg2 = tr.resultReg)
it += IRInstruction(operationRegister, irDt, reg1 = incReg)
it += IRInstruction(Opcode.STOREI, irDt, reg1 = incReg, reg2 = tr.resultReg)
}
return result
} else if (array!=null) {
val variable = array.variable.name
val itemsize = program.memsizer.memorySize(array.type)
val fixedIndex = constIntValue(array.index)
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
val indexReg = registers.nextFree()
val dataReg = registers.nextFree()
if(array.usesPointerVariable) {
// we don't have an indirect dec/inc so do it via an intermediate register
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = offset)
it += IRInstruction(Opcode.LOADIX, irDt, reg1 = dataReg, reg2 = indexReg, labelSymbol = variable)
it += IRInstruction(operationRegister, irDt, reg1=dataReg)
it += IRInstruction(Opcode.STOREIX, irDt, reg1 = dataReg, reg2 = indexReg, labelSymbol = variable)
}
} else {
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = variable, symbolOffset = offset), null)
}
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = variable, symbolOffset = fixedIndex*itemsize), null)
} else {
val indexTr = expressionEval.translateExpression(array.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
@@ -1556,15 +1577,9 @@ class IRCodeGen(
result += multiplyByConst(IRDataType.BYTE, indexTr.resultReg, itemsize)
result += IRCodeChunk(null, null).also {
val incReg = registers.nextFree()
if(array.usesPointerVariable) {
it += IRInstruction(Opcode.LOADIX, irDt, reg1=incReg, reg2=indexTr.resultReg, labelSymbol=variable)
it += IRInstruction(operationRegister, irDt, reg1=incReg)
it += IRInstruction(Opcode.STOREIX, irDt, reg1=incReg, reg2=indexTr.resultReg, labelSymbol=variable)
} else {
it += IRInstruction(Opcode.LOADX, irDt, reg1=incReg, reg2=indexTr.resultReg, labelSymbol=variable)
it += IRInstruction(operationRegister, irDt, reg1=incReg)
it += IRInstruction(Opcode.STOREX, irDt, reg1=incReg, reg2=indexTr.resultReg, labelSymbol=variable)
}
it += IRInstruction(Opcode.LOADX, irDt, reg1=incReg, reg2=indexTr.resultReg, labelSymbol=variable)
it += IRInstruction(operationRegister, irDt, reg1=incReg)
it += IRInstruction(Opcode.STOREX, irDt, reg1=incReg, reg2=indexTr.resultReg, labelSymbol=variable)
}
}
} else
@@ -4,10 +4,7 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.ForLoop
import prog8.ast.statements.RepeatLoop
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclType
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
@@ -35,6 +32,19 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return noModifications
}
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
val casted = numLiteral.cast(iDt.getOr(DataType.UNDEFINED))
if(casted.isValid) {
return listOf(IAstModification.ReplaceNode(numLiteral, casted.value!!, parent))
}
}
}
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
val result = containment.constValue(program)
if(result!=null)
@@ -251,48 +261,6 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
}
if(rightconst!=null && (expr.operator=="<<" || expr.operator==">>")) {
val dt = expr.left.inferType(program)
if(dt.isBytes && rightconst.number>=8) {
if(dt.istype(DataType.UBYTE)) {
val zeroUB = NumericLiteral(DataType.UBYTE, 0.0, expr.position)
modifications.add(IAstModification.ReplaceNode(expr, zeroUB, parent))
} else {
if(leftconst!=null) {
val zeroB = NumericLiteral(DataType.BYTE, 0.0, expr.position)
val minusoneB = NumericLiteral(DataType.BYTE, -1.0, expr.position)
if(leftconst.number<0.0) {
if(expr.operator=="<<")
modifications.add(IAstModification.ReplaceNode(expr, zeroB, parent))
else
modifications.add(IAstModification.ReplaceNode(expr, minusoneB, parent))
} else {
modifications.add(IAstModification.ReplaceNode(expr, zeroB, parent))
}
}
}
}
else if(dt.isWords && rightconst.number>=16) {
if(dt.istype(DataType.UWORD)) {
val zeroUW = NumericLiteral(DataType.UWORD, 0.0, expr.position)
modifications.add(IAstModification.ReplaceNode(expr, zeroUW, parent))
} else {
if(leftconst!=null) {
val zeroW = NumericLiteral(DataType.WORD, 0.0, expr.position)
val minusoneW = NumericLiteral(DataType.WORD, -1.0, expr.position)
if(leftconst.number<0.0) {
if(expr.operator=="<<")
modifications.add(IAstModification.ReplaceNode(expr, zeroW, parent))
else
modifications.add(IAstModification.ReplaceNode(expr, minusoneW, parent))
} else {
modifications.add(IAstModification.ReplaceNode(expr, zeroW, parent))
}
}
}
}
}
return modifications
}
@@ -354,14 +322,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(stepLiteral!=null) {
val stepCast = stepLiteral.cast(targetDt)
if(stepCast.isValid)
stepCast.valueOrZero()
stepCast.value!!
else
range.step
} else {
range.step
}
return RangeExpression(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
return RangeExpression(fromCast.value!!, toCast.value!!, newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@@ -420,7 +388,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
return listOf(IAstModification.ReplaceNode(numval, cast.value!!, decl))
}
}
}
@@ -40,7 +40,7 @@ class VarConstantValueTypeAdjuster(
declConstValue.linkParents(decl)
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.value!!, decl))
}
}
} catch (x: UndefinedSymbolError) {
@@ -32,8 +32,8 @@ class ExpressionSimplifier(private val program: Program,
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
if (newLiteral.isValid && newLiteral.value!! !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.value!!, parent)
}
}
+41
View File
@@ -1172,6 +1172,47 @@ sub search_x16edit() -> ubyte {
}}
}
sub set_program_args(uword args_ptr, ubyte args_size) {
; Set the inter-program arguments.
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2007%20-%20Memory%20Map.md#bank-0
sys.push(getrambank())
rambank(0)
sys.memcopy(args_ptr, $bf00, args_size)
if args_size<255
@($bf00+args_size) = 0
rambank(sys.pop())
}
asmsub get_program_args(uword buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
; Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2007%20-%20Memory%20Map.md#bank-0
%asm {{
lda #0
rol a
sta P8ZP_SCRATCH_REG
lda $00
pha
stz $00
stz P8ZP_SCRATCH_W1
lda #$bf
sta P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (cx16.r0),y
beq +
_continue iny
cpy cx16.r1L ; max size?
bne -
beq ++
+ lda P8ZP_SCRATCH_REG ; binary?
bne _continue
+ pla
sta $00
}}
}
}
sys {
+3 -3
View File
@@ -52,7 +52,7 @@ private fun compileMain(args: Array<String>): Boolean {
val splitWordArrays by cli.option(ArgType.Boolean, fullName = "splitarrays", description = "treat all word arrays as tagged with @split to make them lsb/msb split in memory")
val printAst1 by cli.option(ArgType.Boolean, fullName = "printast1", description = "print out the compiler AST")
val printAst2 by cli.option(ArgType.Boolean, fullName = "printast2", description = "print out the intermediate AST that is used for code generation")
val breakpointCpuInstruction by cli.option(ArgType.Boolean, fullName = "breakinstr", description = "also use a CPU instruction for %breakpoint")
val breakpointCpuInstruction by cli.option(ArgType.Choice(listOf("brk", "stp"), { it }), fullName = "breakinstr", description = "the CPU instruction to use as well for %breakpoint")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
@@ -158,7 +158,7 @@ private fun compileMain(args: Array<String>): Boolean {
slabsGolden == true,
compilationTarget!!,
splitWordArrays == true,
breakpointCpuInstruction = false,
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
processedSymbols,
@@ -236,7 +236,7 @@ private fun compileMain(args: Array<String>): Boolean {
slabsGolden == true,
compilationTarget!!,
splitWordArrays == true,
breakpointCpuInstruction == true,
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
processedSymbols,
+1 -1
View File
@@ -44,7 +44,7 @@ class CompilerArguments(val filepath: Path,
val slabsGolden: Boolean,
val compilationTarget: String,
val splitWordArrays: Boolean,
val breakpointCpuInstruction: Boolean,
val breakpointCpuInstruction: String?,
val printAst1: Boolean,
val printAst2: Boolean,
val symbolDefs: Map<String, String>,
@@ -140,6 +140,18 @@ internal class AstChecker(private val program: Program,
}
}
}
val statements = (returnStmt.parent as IStatementContainer).statements
val myIndex = statements.indexOf(returnStmt)
if(myIndex>=0 && myIndex<statements.size-1) {
val stmtAfterReturn = statements[myIndex+1]
if(stmtAfterReturn.position.line==returnStmt.position.line) {
// this error is not generated by the parser, unfortunately.
// it parses "return somestatement" as: "return" "somestatement" (and this then only results in a warning about unreachable code).
errors.err("a statement is not a return value", stmtAfterReturn.position)
}
}
super.visit(returnStmt)
}
@@ -1328,7 +1340,7 @@ internal class AstChecker(private val program: Program,
if(target !is VarDecl || target.type== VarDeclType.CONST) {
errors.err("can only increment or decrement a variable", postIncrDecr.position)
} else if(target.datatype !in NumericDatatypes) {
errors.err("can only increment or decrement a byte/float/word variable", postIncrDecr.position)
errors.err("cannot increment/decrement this", postIncrDecr.position)
}
}
} else if(postIncrDecr.target.arrayindexed != null) {
@@ -1339,8 +1351,8 @@ internal class AstChecker(private val program: Program,
}
else {
val dt = (target as VarDecl).datatype
if(dt !in NumericDatatypes && dt !in ArrayDatatypes)
errors.err("can only increment or decrement a byte/float/word", postIncrDecr.position)
if(dt !in NumericDatatypes && dt !in ArrayDatatypes && dt!=DataType.STR)
errors.err("cannot increment/decrement this", postIncrDecr.position)
}
}
// else if(postIncrDecr.target.memoryAddress != null) { } // a memory location can always be ++/--
@@ -1538,6 +1550,25 @@ internal class AstChecker(private val program: Program,
when (targetDt) {
DataType.STR -> return err("string value expected")
DataType.ARRAY_BOOL -> {
// value may be either a single byte, or a byte arraysize (of all constant values)\
if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt))
return false
val arraySpecSize = arrayspec.constIndex()
val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize>256)
return err("boolean array length must be 1-256")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid boolean array size, must be 1-256")
}
return err("invalid boolean array initialization value ${value.type}, expected $targetDt")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type istype targetDt) {
@@ -1660,15 +1691,15 @@ internal class AstChecker(private val program: Program,
val array = value.value.map {
when (it) {
is NumericLiteral -> it.number.toInt()
is AddressOf -> it.identifier.hashCode() and 0xffff
is IdentifierReference -> it.hashCode() and 0xffff
is AddressOf -> it.identifier.nameInSource.hashCode() and 0xffff
is IdentifierReference -> it.nameInSource.hashCode() and 0xffff
is TypecastExpression -> {
val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type)
if(cast==null || !cast.isValid)
-9999999
else
cast.valueOrZero().number.toInt()
cast.value!!.number.toInt()
}
else -> -9999999
}
@@ -1687,6 +1718,9 @@ internal class AstChecker(private val program: Program,
DataType.ARRAY_W, DataType.ARRAY_W_SPLIT -> {
correct = array.all { it in -32768..32767 }
}
DataType.ARRAY_BOOL -> {
correct = array.all { it==0 || it==1 }
}
DataType.ARRAY_F -> correct = true
else -> throw FatalAstException("invalid array type $type")
}
@@ -1730,6 +1764,9 @@ internal class AstChecker(private val program: Program,
if((sourceDatatype== DataType.UWORD || sourceDatatype== DataType.WORD) && (targetDatatype== DataType.UBYTE || targetDatatype== DataType.BYTE)) {
errors.err("cannot assign word to byte, use msb() or lsb()?", position)
}
else if(sourceDatatype in IterableDatatypes && targetDatatype in ByteDatatypes) {
errors.err("cannot assign string or array to a byte", position)
}
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else {
@@ -195,25 +195,19 @@ _after:
// replace pointervar[word] by @(pointervar+word) to avoid the
// "array indexing is limited to byte size 0..255" error for pointervariables.
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val indexerDt = indexExpr.inferType(program)
if(indexerDt.isWords) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype==DataType.UWORD) {
val add: Expression =
if(indexExpr.constValue(program)?.number==0.0)
arrayIndexedExpression.arrayvar.copy()
else
BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype==DataType.UWORD) {
val wordIndex = TypecastExpression(indexExpr, DataType.UWORD, true, indexExpr.position)
val address = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", wordIndex, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array
val memread = DirectMemoryRead(address, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
return noModifications
@@ -551,8 +551,10 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
}
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val arrayVarType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(arrayVarType, srcArr.position)
if(srcArr.arrayvar.targetVarDecl(program)!!.datatype !in ArrayDatatypes + DataType.STR)
throw FatalAstException("array indexing can only be used on array or string variables ${srcArr.position}")
val eltType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(eltType, srcArr.position)
array.add(transform(srcArr.arrayvar))
array.add(transformExpression(srcArr.indexer.indexExpr))
return array
@@ -196,7 +196,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
if((assignment.value as? BinaryExpression)?.operator in ComparisonOperators) {
if((assignment.value as? BinaryExpression)?.operator in ComparisonOperators && targettype in IntegerDatatypes) {
// special case, treat a boolean comparison result as the same type as the target value to avoid needless casts later
return noModifications
}
@@ -207,7 +207,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
fun castLiteral(cvalue2: NumericLiteral): List<IAstModification.ReplaceNode> {
val cast = cvalue2.cast(targettype)
return if(cast.isValid)
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
listOf(IAstModification.ReplaceNode(assignment.value, cast.value!!, assignment))
else
emptyList()
}
@@ -314,7 +314,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val modifications = mutableListOf<IAstModification>()
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.valueOrZero()
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.value
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
else
@@ -328,7 +328,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val modifications = mutableListOf<IAstModification>()
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.valueOrZero()
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.value
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
else
@@ -349,9 +349,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
return noModifications
if (returnValue is NumericLiteral) {
val cast = returnValue.cast(subroutine.returntypes.single())
val cast = returnValue.cast(subReturnType)
if(cast.isValid)
returnStmt.value = cast.valueOrZero()
returnStmt.value = cast.value
} else {
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
@@ -402,9 +402,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val castedValue = expressionToCast.cast(requiredType)
if (castedValue.isValid) {
val signOriginal = sign(expressionToCast.number)
val signCasted = sign(castedValue.valueOrZero().number)
val signCasted = sign(castedValue.value!!.number)
if(signOriginal==signCasted) {
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.value!!, parent)
}
return
}
@@ -50,7 +50,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
if(typecast.expression is NumericLiteral) {
val value = (typecast.expression as NumericLiteral).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
return listOf(IAstModification.ReplaceNode(typecast, value.value!!, parent))
}
val sourceDt = typecast.expression.inferType(program)
@@ -66,9 +66,9 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
// if the expression is a comparison expression, or a logical expression, it produces the
// correct 'boolean' byte result so the cast can be removed.
// correct 'boolean' byte result so the cast can be removed. Only if target is Integer.
val binExpr = typecast.expression as? BinaryExpression
if(binExpr!=null && binExpr.operator in ComparisonOperators + LogicalOperators) {
if(binExpr!=null && binExpr.operator in ComparisonOperators + LogicalOperators && typecast.type in IntegerDatatypesNoBool) {
return listOf(IAstModification.ReplaceNode(typecast, binExpr, parent))
}
@@ -151,6 +151,27 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
}
}
if(expr.operator in LogicalOperators) {
// remove redundant !=0 comparisons from logical expressions such as: a!=0 xor b --> a xor b (only for byte operands!)
val leftExpr = expr.left as? BinaryExpression
if(leftExpr != null &&
leftExpr.operator == "!=" &&
!leftExpr.left.isSimple &&
leftExpr.left.inferType(program).isBytes &&
leftExpr.right.constValue(program)?.number == 0.0) {
return listOf(IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr))
}
val rightExpr = expr.right as? BinaryExpression
if(rightExpr != null &&
rightExpr.operator == "!=" &&
!rightExpr.left.isSimple &&
rightExpr.left.inferType(program).isBytes &&
rightExpr.right.constValue(program)?.number == 0.0) {
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
}
}
return noModifications
}
+1 -1
View File
@@ -38,7 +38,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
slabsGolden = false,
compilationTarget = target.name,
splitWordArrays = false,
breakpointCpuInstruction = false,
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
symbolDefs = emptyMap(),
+1 -1
View File
@@ -36,7 +36,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
slabsGolden = false,
compilationTarget = Cx16Target.NAME,
splitWordArrays = false,
breakpointCpuInstruction = false,
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
symbolDefs = emptyMap(),
+55
View File
@@ -817,4 +817,59 @@ main {
if5.condition shouldBe instanceOf<BinaryExpression>()
if6.condition shouldBe instanceOf<BinaryExpression>()
}
test("funky bitshifts") {
val src="""
main {
sub start() {
const uword one = 1
const uword two = 2
uword @shared answer = one * two >> 8
funcw(one * two >> 8)
const uword uw1 = 99
const uword uw2 = 22
uword @shared answer2 = uw1 * uw2 >> 8 ; optimized into msb(uw1*uw2) as uword
funcw(uw1 * uw2 >> 8)
uword @shared uw3 = 99
uword @shared uw4 = 22
uword @shared answer3 = uw3 * uw4 >> 8 ; optimized into msb(uw1*uw2) as uword
funcw(uw3 * uw4 >> 8)
}
sub funcw(uword ww) {
cx16.r0++
}
}"""
val result = compileText(Cx16Target(), true, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 17
val answerValue = (st[3] as Assignment).value
answerValue shouldBe NumericLiteral(DataType.UWORD, 0.0, Position.DUMMY)
val funcarg1 = (st[4] as FunctionCallStatement).args.single()
funcarg1 shouldBe NumericLiteral(DataType.UWORD, 0.0, Position.DUMMY)
val answer2Value = (st[8] as Assignment).value
answer2Value shouldBe NumericLiteral(DataType.UWORD, 8.0, Position.DUMMY)
val funcarg2 = (st[9] as FunctionCallStatement).args.single()
funcarg2 shouldBe NumericLiteral(DataType.UWORD, 8.0, Position.DUMMY)
val answer3ValueTc = (st[15] as Assignment).value as TypecastExpression
answer3ValueTc.type shouldBe DataType.UWORD
val answer3Value = answer3ValueTc.expression as FunctionCallExpression
answer3Value.target.nameInSource shouldBe listOf("msb")
answer3Value.args.single() shouldBe instanceOf<BinaryExpression>()
val funcarg3tc = (st[16] as FunctionCallStatement).args.single() as TypecastExpression
funcarg3tc.type shouldBe DataType.UWORD
val funcarg3 = funcarg3tc.expression as FunctionCallExpression
funcarg3.target.nameInSource shouldBe listOf("msb")
funcarg3.args.single() shouldBe instanceOf<BinaryExpression>()
}
})
+20
View File
@@ -1088,4 +1088,24 @@ main {
errors.errors[3] shouldContain "overflow"
}
test("type fitting of const assignment values") {
val src="""
main {
sub start() {
&ubyte mapped = 8000
mapped = 6144 >> 9
ubyte @shared ubb = 6144 >> 9
bool @shared bb = 6144
}
}"""
compileText(C64Target(), true, src, writeAssembly = false) shouldNotBe null
val src2="""
main {
sub start() {
ubyte @shared ubb = 6144 >> 2 ; should still be type error
}
}"""
compileText(C64Target(), true, src2, writeAssembly = false) shouldBe null
}
})
+18
View File
@@ -174,4 +174,22 @@ main {
compileText(Cx16Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("return with a statement instead of a value is a syntax error") {
val src="""
main {
sub invalid() {
return cx16.r0++
}
sub start() {
invalid()
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "statement"
}
})
+21 -4
View File
@@ -3,13 +3,11 @@ package prog8tests.ast
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.core.SourceCode
import prog8.code.core.SourceCode.Companion.LIBRARYFILEPREFIX
import prog8tests.helpers.assumeNotExists
import prog8tests.helpers.assumeReadableFile
import prog8tests.helpers.fixturesDir
import prog8tests.helpers.resourcesDir
import prog8tests.helpers.*
import kotlin.io.path.Path
@@ -29,6 +27,14 @@ class TestSourceCode: AnnotationSpec() {
src.toString().startsWith("prog8.code.core.SourceCode") shouldBe true
}
@Test
fun testFromStringDOSLineEndings() {
val text = "main {\r\nline2\r\nline3\r\n}\r\n"
val src = SourceCode.Text(text)
src.text shouldNotBe text // because normalized line endings!
src.text.split('\r', '\n').size shouldBe 5
}
@Test
fun testFromPathWithNonExistingPath() {
val filename = "i_do_not_exist.p8"
@@ -60,6 +66,17 @@ class TestSourceCode: AnnotationSpec() {
src.isFromFilesystem shouldBe true
}
@Test
fun testFromPathWithExistingPathDOSLineEndings() {
val text = "main {\r\nline2\r\nline3\r\n}\r"
val filePath = outputDir.resolve("on_the_fly_test_" + text.hashCode().toUInt().toString(16) + ".p8")
filePath.toFile().writeText(text)
val path = assumeReadableFile(fixturesDir, filePath)
val src = SourceCode.File(path)
src.text shouldNotBe path.toFile().readText() // should be normalized!
src.text.split('\r', '\n').size shouldBe 5
}
@Test
fun testFromPathWithExistingNonNormalizedPath() {
val filename = "ast_simple_main.p8"
@@ -58,6 +58,21 @@ main {
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("array init size mismatch error") {
val text="""
main {
sub start() {
ubyte[10] uba = [1,2,3]
bool[10] bba = [true, false, true]
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "size mismatch"
errors.errors[1] shouldContain "size mismatch"
}
test("invalid && operator") {
val text="""
main {
+1 -1
View File
@@ -38,7 +38,7 @@ internal fun compileFile(
outputDir = outputDir,
errors = errors ?: ErrorReporterForTests(),
splitWordArrays = false,
breakpointCpuInstruction = false,
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false
)
-3
View File
@@ -288,9 +288,6 @@ mylabel:
test("nesting with overlapping names is ok (doesn't work for 64tass)") {
val src="""
%import textio
%zeropage basicsafe
main {
sub start() {
main()
@@ -366,7 +366,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
val cv = expression.constValue(program) ?: return null
val cast = cv.cast(type)
return if(cast.isValid) {
val newval = cast.valueOrZero()
val newval = cast.value!!
newval.linkParents(parent)
return newval
}
@@ -558,21 +558,19 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteral)
return false
return number==other.number
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
return number==other.number
else
return type==other.type && number==other.number
}
operator fun compareTo(other: NumericLiteral): Int = number.compareTo(other.number)
class ValueAfterCast(val isValid: Boolean, val whyFailed: String?, private val value: NumericLiteral?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
fun linkParent(parent: Node) {
value?.linkParents(parent)
}
}
data class ValueAfterCast(val isValid: Boolean, val whyFailed: String?, val value: NumericLiteral?)
fun cast(targettype: DataType): ValueAfterCast {
val result = internalCast(targettype)
result.linkParent(this.parent)
result.value?.linkParents(this.parent)
return result
}
@@ -867,7 +865,7 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
val castArray = value.map {
val cast = (it as NumericLiteral).cast(elementType)
if(cast.isValid)
cast.valueOrZero() as Expression
cast.value!! as Expression
else
return null // abort
}.toTypedArray()
@@ -876,12 +874,12 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
else if(elementType in WordDatatypes && value.all { it is NumericLiteral || it is AddressOf || it is IdentifierReference}) {
val castArray = value.map {
when(it) {
is AddressOf -> it as Expression
is IdentifierReference -> it as Expression
is AddressOf -> it
is IdentifierReference -> it
is NumericLiteral -> {
val numcast = it.cast(elementType)
if(numcast.isValid)
numcast.valueOrZero() as Expression
numcast.value!!
else
return null // abort
}
+6 -5
View File
@@ -175,12 +175,13 @@ One or more .p8 module files
``-check``
Quickly check the program for errors. No output will be produced.
``-breakinstr``
Also output a CPU instruction for a ``%breakpoint``, as well as the entry in the vice monitor list file.
``-breakinstr <instruction>``
Also output the specified CPU instruction for a ``%breakpoint``, as well as the entry in the vice monitor list file.
This can be useful on emulators/systems that don't parse the breakpoint information in the list file,
such as the X16Emu emulator for the Commander X16. Prog8 then uses a STP instruction (65c02) to trigger a
breakpoint in the debugger (if enabled with -debug on the emulator). On a 6502 CPU, this option
will output a BRK instruction instead.
such as the X16Emu emulator for the Commander X16.
Useful instructions to consider are ``brk`` and ``stp``.
For example for the Commander X16 emulator, ``stp`` is useful because it can actually tyrigger
a breakpoint halt in the debugger when this is enabled by running the emulator with -debug.
``-expericodegen``
Use experimental code generation backend (*incomplete*).
+12 -3
View File
@@ -102,7 +102,8 @@ sys (part of syslib)
the irqd status bit if it was cleared before entering).
``progend ()``
Returns the last address of the program in memory + 1.
Returns the last address of the program in memory + 1. This means: the memory address directly after all the program code and variables,
including the uninitialized ones ("BSS" variables) and the uninitialized memory blocks reserved by the `memory()` function.
Can be used to load dynamic data after the program, instead of hardcoding something.
``wait (uword jiffies)``
@@ -525,9 +526,13 @@ The compiler needs it for various built-in system routines.
cx16
----
This is available on *all targets*, it is always imported as part of syslib.
On the Commander X16 this module contains a whole bunch of things specific to that machine.
On the Commander X16 this module contains a *whole bunch* of things specific to that machine.
It's way too much to include here, you have to study the
`source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/syslib.p8>`_
to see what is there.
On the other targets, it only contains the definition of the 16 memory mapped virtual registers
(cx16.r0 - cx16.r15) and the following two utility routines:
(cx16.r0 - cx16.r15) and the following utility routines:
``save_virtual_registers()``
save the values of all 16 virtual registers r0 - r15 in a buffer. Might be useful in an IRQ handler to avoid clobbering them.
@@ -535,6 +540,10 @@ On the other targets, it only contains the definition of the 16 memory mapped vi
``restore_virtual_registers()``
restore the values of all 16 virtual registers r0 - r15 from the buffer. Might be useful in an IRQ handler to avoid clobbering them.
``cpu_is_65816()``
Returns true if the CPU in the computer is a 65816, false otherwise (6502 cpu).
bmx (cx16 only)
----------------
Routines to load and save "BMX" files, the CommanderX16 bitmap file format.
+2 -1
View File
@@ -232,7 +232,8 @@ Integers
^^^^^^^^
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
in hexadecimal and in binary notation. You can use underscores to group digits to make long numbers more readable.
in hexadecimal and in binary notation. There is no octal notation.
You can use underscores to group digits to make long numbers more readable.
A single character in single quotes such as ``'a'`` is translated into a byte integer,
which is the PETSCII value for that character.
+13 -12
View File
@@ -329,22 +329,21 @@ Variable declarations
Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You can give them an initial value as well. That value can be a simple literal value,
or an expression. If you don't provide an initial value yourself, zero will be used.
Add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage (but no guarantees). If the ZP is full,
the variable will be allocated in normal memory elsewhere.
Add a ``@requirezp`` tag to force the variable in zeropage, but if the ZP is full,
the compilation will fail.
Add a ``@shared`` shared-tag, to tell the compiler that the variable is shared
with some assembly code and that it should not be optimized away if not used elsewhere.
For (u)word arrays, add ``@split`` to make the array layout in memory as 2 split arrays
one with the LSBs one with the MSBs of the word values.
The syntax for variable declarations is::
<datatype> [ @tag ] <variable name> [ = <initial value> ]
The syntax is::
Here are the tags you can add to a variable:
<datatype> [ @type-tag ] <variable name> [ = <initial value> ]
========== ======
Tag Effect
========== ======
@zp prioritize the variable for putting it into Zero page. No guarantees; if ZP is full the variable will be placed in another memory location.
@requirezp force the variable into Zero page. If ZP is full, compilation will fail.
@shared means the variable is shared with some assembly code and that it cannot be optimized away if not used elsewhere.
@split (only valid on (u)word arrays) Makes the array to be placed in memory as 2 separate byte arrays; one with the LSBs one with the MSBs of the word values. May improve performance.
========== ======
where type-tag is one of the tags mentioned earlier.
For boolean and numeric variables, you can actually declare them in one go by listing the names in a comma separated list.
Type tags, and the optional initialization value, are applied equally to all variables in such a list.
@@ -404,6 +403,8 @@ type identifier type storage size example var declara
**arrays:** you can split an array initializer list over several lines if you want. When an initialization
value is given, the array size in the declaration can be omitted.
**numbers:** unless prefixed for hex or binary as described below, all numbers are decimal numbers. There is no octal notation.
**hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac``
**binary numbers:** you can use a percent prefix to write binary numbers: ``%10010011``
+4 -1
View File
@@ -1,6 +1,8 @@
TODO
====
(after merge in boolean): move all "OperatorXinplace" from expressionGen to AssignmentGen, see if we can get rid of the Result return type.
...
@@ -41,7 +43,6 @@ Compiler:
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
- generate WASM to eventually run prog8 on a browser canvas? Use binaryen toolkit and/or my binaryen kotlin library?
- add Vic20 target?
- split words arrays all()
- split words arrays sort()
@@ -62,6 +63,7 @@ Optimizations:
those checks should probably be removed, or be made permanent
- optimizeCommonSubExpressions: currently only looks in expressions on a single line, could search across multiple expressions
STRUCTS again?
--------------
@@ -78,6 +80,7 @@ What if we were to re-introduce Structs in prog8? Some thoughts:
Other language/syntax features to think about
---------------------------------------------
- remove ++/-- (just use Pythonesque x+=1) OR make ++/-- into a postfix expression (but then we need a prefix variant of them too?) maybe via a new builtin function like postincr__w() etc?
- support for assigning multiple return values from romsub/asmsub to multiple variables.
- add (rom/ram)bank support to romsub. A call will then automatically switch banks, use callfar and something else when in banked ram.
challenges: how to not make this too X16 specific? How does the compiler know what bank to switch (ram/rom)?
+107
View File
@@ -1,8 +1,115 @@
%import textio
%import floats
%import math
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
; ubyte[10] az
ubyte @shared offset=4
uword @shared az = $4000
ubyte @shared value = 22
az[20] |= 99
az[21] &= 99
az[22] ^= 99
az[23] += 99
az[24] -= 99
; az[2000] |= 99
; az[value] |= 99
; az[cx16.r0] |= 99
; cx16.r0L = az[200]
; cx16.r1L = az[2000]
; cx16.r0L = az[value]
; cx16.r0H = value*4 + az[value]
; cx16.r0L = az[value] + value*4
;
; @($4004) = 99
; az[4]--
; @(az + offset)--
; txt.print_ub(@($4004))
; txt.nl()
; az[4]++
; @(az+offset)++
; txt.print_ub(@($4004))
; txt.nl()
; cx16.r0L = az[4] + value*5
; cx16.r1L = value*5 + az[4]
; ubyte @shared xx
; ubyte[3] ubarr
; uword[3] @split uwarr
; byte[3] sbarr
; bool[3] barr
; float[3] flarr
; bool @shared bb
; uword ptr = &ubarr
;
; ptr[1]++
; ptr[1]++
; ptr[1]--
; txt.print_ub(ubarr[1])
; txt.nl()
; ptr[1]+=4
; ptr[1]-=3
; txt.print_ub(ubarr[1])
; txt.nl()
; sbarr[1] = sbarr[1] == 0
; sbarr[1] = sbarr[1] != 0
; sbarr[1] = sbarr[1] < 0
; sbarr[1] = sbarr[1] <= 0
; sbarr[1] = sbarr[1] > 0
; sbarr[1] = sbarr[1] >= 0
;
; xx = 1
;
; sbarr[xx] = sbarr[xx] == 0
; sbarr[xx] = sbarr[xx] != 0
; sbarr[xx] = sbarr[xx] < 0
; sbarr[xx] = sbarr[xx] <= 0
; sbarr[xx] = sbarr[xx] > 0
; sbarr[xx] = sbarr[xx] >= 0
; sbarr[1] = sbarr[1] == 2
; sbarr[1] = sbarr[1] != 2
; sbarr[1] = sbarr[1] < 2
; sbarr[1] = sbarr[1] <= 2
; sbarr[1] = sbarr[1] > 2
; sbarr[1] = sbarr[1] >= 2
; xx = 1
; sbarr[xx] = sbarr[xx] == 2
; sbarr[xx] = sbarr[xx] != 2
; sbarr[xx] = sbarr[xx] < 2
; sbarr[xx] = sbarr[xx] <= 2
; sbarr[xx] = sbarr[xx] > 2
; sbarr[xx] = sbarr[xx] >= 2
; ubarr[1] = ubarr[1] == 2
; ubarr[1] = ubarr[1] < 2
; ubarr[1] = ubarr[1] <= 2
; ubarr[1] = ubarr[1] > 3
; ubarr[1] = ubarr[1] >= 3
; barr[1] = barr[0] and barr[2]
; barr[1] = barr[0] or barr[2]
; barr[1] = barr[0] xor barr[2]
; barr[1] = not barr[0]
;
; ubarr[1] = 999
; ubarr[1] = ubarr[1]==999
; txt.print_uw(ubarr[1])
;
; barr[1] = barr[1] and bb
; barr[1] = barr[1] or bb
; barr[1] = barr[1] xor bb
;
; bb = bb and barr[1]
; bb = bb or barr[1]
; bb = bb xor barr[1]
; bb = not bb
}
}
+1 -1
View File
@@ -5,4 +5,4 @@ org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
kotlinVersion=1.9.22
version=10.0
version=10.1
@@ -42,7 +42,7 @@ class RequestParser : Take {
asmListfile = false,
experimentalCodegen = false,
splitWordArrays = false,
breakpointCpuInstruction = false,
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
varsHighBank = null,
@@ -16,7 +16,7 @@ Program to execute is not stored in the system memory, it's just a separate list
Value stack, max 128 entries of 1 byte each.
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,
LOAD instructions DO affect the Z and N flags.
INC/DEC instructions DO affect the Z and N flags,
INC/DEC/NEG instructions DO affect the Z and N flags,
other instructions only affect Z an N flags if the value in a result register is written.
See OpcodesThatSetStatusbits
@@ -444,6 +444,8 @@ val OpcodesThatSetStatusbitsButNotCarry = arrayOf(
Opcode.LOADX,
Opcode.LOADIX,
Opcode.LOADR,
Opcode.NEG,
Opcode.NEGM,
Opcode.INC,
Opcode.INCM,
Opcode.DEC,
@@ -456,7 +458,7 @@ val OpcodesThatSetStatusbitsButNotCarry = arrayOf(
Opcode.OR,
Opcode.XORM,
Opcode.XORR,
Opcode.XOR
Opcode.XOR,
)
val OpcodesThatDependOnCarry = arrayOf(
+20 -4
View File
@@ -974,8 +974,16 @@ class VirtualMachine(irProgram: IRProgram) {
private fun InsNEG(i: IRInstruction) {
when(i.type!!) {
IRDataType.BYTE -> registers.setUB(i.reg1!!, (-registers.getUB(i.reg1!!).toInt()).toUByte())
IRDataType.WORD -> registers.setUW(i.reg1!!, (-registers.getUW(i.reg1!!).toInt()).toUShort())
IRDataType.BYTE -> {
val value = -registers.getUB(i.reg1!!).toInt()
registers.setUB(i.reg1!!, value.toUByte())
statusbitsNZ(value, IRDataType.BYTE)
}
IRDataType.WORD -> {
val value = -registers.getUW(i.reg1!!).toInt()
registers.setUW(i.reg1!!, value.toUShort())
statusbitsNZ(value, IRDataType.WORD)
}
IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, -registers.getFloat(i.fpReg1!!))
}
nextPc()
@@ -984,8 +992,16 @@ class VirtualMachine(irProgram: IRProgram) {
private fun InsNEGM(i: IRInstruction) {
val address = i.address!!
when(i.type!!) {
IRDataType.BYTE -> memory.setUB(address, (-memory.getUB(address).toInt()).toUByte())
IRDataType.WORD -> memory.setUW(address, (-memory.getUW(address).toInt()).toUShort())
IRDataType.BYTE -> {
val value = -memory.getUB(address).toInt()
memory.setUB(address, value.toUByte())
statusbitsNZ(value, IRDataType.BYTE)
}
IRDataType.WORD -> {
val value = -memory.getUW(address).toInt()
memory.setUW(address, value.toUShort())
statusbitsNZ(value, IRDataType.WORD)
}
IRDataType.FLOAT -> memory.setFloat(address, -memory.getFloat(address))
}
nextPc()