Compare commits

...

106 Commits
v8.5 ... v8.7

Author SHA1 Message Date
e426fc0922 version 8.7 2022-11-06 22:58:39 +01:00
5d4bfffc7e float.rndseedf() now takes float seed value and is consistent for all CBM compilation targets 2022-11-06 22:53:57 +01:00
207cdaf7a4 fix kefrenbars example (use gfx2 instead of kernal routines) 2022-11-06 17:33:30 +01:00
7315b581ce added gfx2.pget(x,y) to get the pixel color value 2022-11-06 13:40:55 +01:00
38efaae7b2 ir/vm: syscall params in high base register to avoid push/pop 2022-11-06 12:52:09 +01:00
469e042216 vm: replaced prog8_lib.string_compare and others with syscalls 2022-11-04 23:12:13 +01:00
0f1a4b9d8f fixed certain type check error when passing boolean value to ubyte function parameter
fixed virtual machine string comparison syscall
2022-11-03 23:06:03 +01:00
7303c00296 vm: prog8lib.wordarray_contains() fixed 2022-11-03 22:48:47 +01:00
fc55b34d84 ir: fix asmsub multi-value return codegen 2022-11-03 22:29:41 +01:00
6f67fc0e02 ir: get rid of '_' symbol prefix 2022-11-03 21:54:53 +01:00
562d722ad5 codegen: added missing codegen for float array inplace modification 2022-11-03 20:08:46 +01:00
144c1ba3a6 ir: fix float instruction value in formatspec 2022-11-03 19:08:38 +01:00
06b032af91 refactor 2022-11-03 00:20:31 +01:00
3603140114 ir: fix unused code remover 2022-11-02 23:54:52 +01:00
e094785cbd ir: fix unused code remover 2022-11-02 23:16:51 +01:00
e7408224ac ir: remove position tracking from codechunk for now 2022-11-02 22:12:42 +01:00
e67c05c274 ir: fix asmsub contents not appearing in IR file 2022-11-02 20:50:51 +01:00
b22804efaf ir: fix inlineasm linking 2022-10-31 23:59:33 +01:00
890f55f91a fixup compiler internals diagram 2022-10-31 00:39:43 +01:00
cc5fc0b892 Merge branch 'master' into labeledchunks
# Conflicts:
#	examples/test.p8
2022-10-30 23:46:44 +01:00
5efe2b027a ir: fix chunk linkage in optimizer 2022-10-30 23:42:41 +01:00
5b6569d0f9 ir: fix overwriting chunk label 2022-10-30 19:03:02 +01:00
0eda7ac498 vm: don't crash on empty code chunks 2022-10-30 17:05:08 +01:00
a5ef353484 ir: fix memory mapped var as for loop counter 2022-10-30 14:54:47 +01:00
67a36d8d31 more robust 'return' statement checks in subroutines 2022-10-30 14:41:28 +01:00
7cc3cc3990 ir: fix non-code chunk linkage 2022-10-30 12:55:06 +01:00
dc0edc4c2b break also in for 2022-10-29 23:34:59 +02:00
71d2f091e5 Merge pull request #88 from markjreed/fix-mouse_config2
fix: don't ignore shape argument to cx16.mouse_config2
2022-10-29 23:22:14 +02:00
c2f062a391 fix: don't ignore shape argument to cx16.mouse_config2 2022-10-29 17:10:06 -04:00
224f490455 Merge branch 'master' into labeledchunks
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	examples/test.p8
2022-10-29 18:26:09 +02:00
5b35232ab4 fix "fpReg1 out of bounds" crash for vm target for in-place float array assignment. #85 2022-10-29 17:04:39 +02:00
6d6db70e42 remove type widening for bit shifts, to be consistent with other arithmetic operations. Fixes #83 2022-10-29 16:29:41 +02:00
6830e15b4e print warning when bit shifts are too large and result in 0. #83 2022-10-29 15:23:39 +02:00
3f07cad35d remove missing feature from docs 2022-10-29 14:31:40 +02:00
e951340033 BASIC, VICE, C64, zeropage spelling 2022-10-29 14:17:40 +02:00
db8912a735 Kernal spelling 2022-10-29 14:10:11 +02:00
0e297731a3 PETSCII spelling 2022-10-29 14:07:04 +02:00
f20c4f98ac Merge pull request #86 from Frosty-J/docs
Fix typos in documentation
2022-10-29 12:57:55 +02:00
05e60cc7c0 fix array type typo 2022-10-29 12:57:33 +02:00
55b4469767 Merge pull request #87 from Frosty-J/basicsafe
`%zeropage basicsafe` in Hello World
2022-10-29 12:31:28 +02:00
f15516e478 Bracket space 2022-10-29 00:25:54 +01:00
17ceadbadf %zeropage basicsafe in Hello World 2022-10-28 22:49:23 +01:00
8c25b2b316 CommanderX16 -> Commander X16 2022-10-28 22:47:14 +01:00
8b1ae404a3 Commodore-64 -> Commodore 64 2022-10-28 22:45:09 +01:00
13534cd4a9 lowlevel -> low-level 2022-10-28 22:40:36 +01:00
abfb345503 ofcourse -> of course 2022-10-28 22:39:54 +01:00
42ae935496 Various typo fixes 2022-10-28 22:39:15 +01:00
434515d957 fix: array[x] = ~array[x] no longer crashes the codegen 2022-10-27 23:56:38 +02:00
094f7803b7 fix: array[x] = -array[x] no longer crashes the codegen 2022-10-27 23:20:40 +02:00
b0c7bad391 fix: array[x] = -value no longer crashes the codegen 2022-10-27 21:58:37 +02:00
e9a4a905ef preparing to fix the array indexing compiler issue 2022-10-26 23:53:17 +02:00
7b6cd0cfbe cx16.macptr() now has additional argument in the carry flag, to reflect recent X16 kernal api change.
Also now allow bool type for status flag args and returnvalues.
2022-10-26 20:41:10 +02:00
b718b12083 ir/vm fix chunk linkage 2022-10-26 00:12:56 +02:00
cfa7258ff4 various 2022-10-25 23:18:42 +02:00
b70e0a0870 mention syntax highlighting files in the docs 2022-10-25 21:24:38 +02:00
da8eb464b8 add cx16diskio.vload_raw() to load headerless files into vram 2022-10-25 21:12:11 +02:00
8f9d1cfa30 fix regression: indexing pointer variable with word (>255) didn't work anymore since release 8.2 or so 2022-10-24 23:43:47 +02:00
585009ac5c ir: fix syscall numbers and more 2022-10-24 01:57:37 +02:00
30ee65fd14 ir: ensure that block and sub labels are also on the first chunk in said block/sub 2022-10-23 18:54:08 +02:00
76428b16f0 Merge branch 'master' into labeledchunks
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt
#	docs/source/todo.rst
#	examples/test.p8
#	virtualmachine/src/prog8/vm/VirtualMachine.kt
2022-10-23 12:19:02 +02:00
0d7b14e2d8 fix crash when assigning certain memory read to word variable. Fixes #82 2022-10-23 11:57:23 +02:00
a9d19d02b3 helpful error for programs still using the old builtin rnd() and rndw() 2022-10-22 22:36:44 +02:00
adcbe55307 replaced integer RNG with smaller and faster routine. 2022-10-22 22:01:57 +02:00
aa99a7df64 seed info 2022-10-22 17:54:24 +02:00
00afa1ce52 ir: replace RND opcode by syscalls 2022-10-22 17:20:46 +02:00
e94bf4c63c replace rnd()/rndw() builtin functions by regular routines in math module 2022-10-22 17:02:43 +02:00
ec5adffdc2 rnd()/rndf() routines can now be seeded with new rndseed()/rndseedf() routines. fixes #80 2022-10-22 13:34:22 +02:00
733c17ad3a improve docs on if syntax. fixes #81 2022-10-19 23:53:15 +02:00
53b0b562e6 fix check for routine that returns multiple values but in status bit. Fixes #79 2022-10-19 23:23:49 +02:00
fabae6e970 ir: fix handling of labeled chunks 2022-10-16 23:53:17 +02:00
a9f9c40d8a ir: fix handling of labeled chunks 2022-10-13 00:56:44 +02:00
6fc89607d3 ir: moving to labeled chunks, no more IRLabel nodes 2022-10-07 00:34:56 +02:00
2340760f53 rename 2022-10-04 22:54:14 +02:00
39d6d2857e ir: change inline binary a bit 2022-10-04 00:57:08 +02:00
7b722a0001 ir: fix count register uses 2022-10-04 00:25:55 +02:00
e7682119e0 ir: count register uses 2022-10-02 15:56:06 +02:00
af6be44676 ir: adding register usage inspections
fix compiler problems with untrimmed inlined asm, and when only a single return statement is present in a subroutine
2022-09-30 20:25:00 +02:00
5a8f97a0b6 ir: adding last missing features to be able to encode all of Prog8 2022-09-30 16:01:00 +02:00
0d4dd385b8 added '%ir' to write inline IR code, '%asm' is now only for real 6502 assembly.
(%ir is probably only used in the library modules for the virtual machine target)
2022-09-30 15:12:26 +02:00
94f0f3e966 ir: join code chunks 2022-09-30 02:47:33 +02:00
43e31765e5 kotlin 1.7.20 2022-09-29 18:41:20 +02:00
7c1bdfe713 ir: uninitialized vars remain empty, bss section classifier (unused for now as there are no segements yet) 2022-09-28 16:56:50 +02:00
9f09784b55 version 8.6.2 2022-09-27 22:45:48 +02:00
e7a3a89bfb fix windows issue 2022-09-27 22:41:48 +02:00
7ea7e63f44 use require() more often 2022-09-27 18:27:55 +02:00
1d2ce2cbeb consolidate IR line parse function 2022-09-27 18:02:57 +02:00
06cf2e0bd7 vm: fix memory slabs (bsieve example) 2022-09-27 16:32:44 +02:00
9d219ae4b9 refactor 2022-09-27 03:32:39 +02:00
71f5a6c50e remove p8virt from compiler diagram 2022-09-27 02:52:29 +02:00
90b2be2bf4 vm: new memory initialization of array vars 2022-09-27 02:43:50 +02:00
db1aa8fcbd vm: new translation of IRProgram into vm program list 2022-09-27 01:50:00 +02:00
11c000f764 moved codeGenVirtual module into virtualmachine module 2022-09-26 20:00:40 +02:00
4d6dcbd173 ir: consolidate IRCodeInstruction and Instruction 2022-09-26 19:46:44 +02:00
0da117efd2 vm: get rid of .p8virt file and cruft 2022-09-26 19:28:40 +02:00
533c368e32 make IRFileReader's file source more general 2022-09-26 14:47:28 +02:00
8883513b0e attempt to fix readthedocs.io build 2022-09-25 22:19:32 +02:00
dcc9a71455 version 8.6.1 2022-09-25 21:54:35 +02:00
1a56743bb1 fix IR repeat loop codegen when amount is 0 2022-09-25 20:48:17 +02:00
387a4b7c35 added string.lowerchar() and string.upperchar() 2022-09-25 20:20:38 +02:00
1d65d63bd9 ir: making sure all names are scoped properly. textelite now runs in vm 2022-09-25 18:02:35 +02:00
dda19c29fe vm: fix symbols to be case sensitive properly in p8virt assembler 2022-09-25 15:51:50 +02:00
ca41669f4f vm: fix scoped name in address-of inside array 2022-09-24 18:26:35 +02:00
0e1886e6bd vm: fix nested label prefixing 2022-09-24 16:00:25 +02:00
c26e116f0e vm: fix crashes when array contains pointers/strings 2022-09-24 14:42:07 +02:00
5c9c7f2c5e adding more complex vm examples 2022-09-23 14:56:06 +02:00
ca2fb6cef3 IR no longer depends on VM syscalls but has its own syscall list for the few builtin functions that still require it 2022-09-23 14:27:51 +02:00
145 changed files with 6738 additions and 4211 deletions

View File

@ -78,6 +78,9 @@ It's handy to have an emulator (or a real machine perhaps!) to run the programs
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
Example code
------------

View File

@ -1,8 +1,7 @@
package prog8
package prog8.code
/**
* By convention, the right side of an `Either` is used to hold successful values.
*
*/
sealed class Either<out L, out R> {

View File

@ -169,6 +169,7 @@ open class StNode(val name: String,
class StStaticVariable(name: String,
val dt: DataType,
val bss: Boolean,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: StString?,
val onetimeInitializationArrayValue: StArray?,
@ -177,10 +178,19 @@ class StStaticVariable(name: String,
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
init {
if(bss) {
require(onetimeInitializationNumericValue==null)
require(onetimeInitializationStringValue==null)
require(onetimeInitializationArrayValue.isNullOrEmpty())
} else {
require(onetimeInitializationNumericValue!=null ||
onetimeInitializationStringValue!=null ||
onetimeInitializationArrayValue!=null)
}
if(length!=null) {
require(onetimeInitializationNumericValue == null)
if(onetimeInitializationArrayValue!=null)
require(length == onetimeInitializationArrayValue.size)
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
}
if(onetimeInitializationNumericValue!=null)
require(dt in NumericDatatypes)
@ -193,7 +203,7 @@ class StStaticVariable(name: String,
}
override fun printProperties() {
print("$name dt=$dt zpw=$zpwish")
print("$name dt=$dt zpw=$zpwish bss=$bss")
}
}
@ -221,12 +231,11 @@ class StMemorySlab(
name: String,
val size: UInt,
val align: UInt,
val allocatedAddress: UInt? = null, // this is used (for now) in the VM code generator. TODO remove this once no longer used
position: Position
):
StNode(name, StNodeType.MEMORYSLAB, position) {
override fun printProperties() {
print("$name size=$size align=$align address=$allocatedAddress")
print("$name size=$size align=$align")
}
}

View File

@ -42,7 +42,7 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
}
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) {
sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
val scopedName: List<String> by lazy {
var namedParent: PtNode = this.parent
if(namedParent is PtProgram)
@ -96,8 +96,13 @@ class PtBlock(name: String,
}
class PtInlineAssembly(val assembly: String, position: Position) : PtNode(position) {
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
override fun printProperties() {}
init {
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
}
}

View File

@ -173,8 +173,7 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
init {
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
if(operator !in setOf("+", "-", "~"))
throw IllegalArgumentException("invalid prefix operator: $operator")
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
}
override fun printProperties() {

View File

@ -29,6 +29,14 @@ class PtSub(
override fun printProperties() {
print(name)
}
init {
// params and return value should not be str
if(parameters.any{ it.type !in NumericDatatypes })
throw AssemblyError("non-numeric parameter")
if(returntype!=null && returntype !in NumericDatatypes)
throw AssemblyError("non-numeric returntype $returntype")
}
}

View File

@ -106,7 +106,7 @@ sealed class SourceCode {
* [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8"
*/
class Resource(pathString: String): SourceCode() {
private val normalized = "/" + Path.of(pathString).normalize().toMutableList().joinToString("/")
private val normalized = "/" + Path(pathString).normalize().toMutableList().joinToString("/")
override val isFromResources = true
override val isFromFilesystem = false
@ -125,7 +125,7 @@ sealed class SourceCode {
}
val stream = object {}.javaClass.getResourceAsStream(normalized)
text = stream!!.reader().use { it.readText() }
name = Path.of(pathString).toFile().nameWithoutExtension
name = Path(pathString).toFile().nameWithoutExtension
}
}

View File

@ -4,12 +4,11 @@ import prog8.code.core.CompilationOptions
import prog8.code.core.CpuType
import prog8.code.core.IMachineDefinition
import prog8.code.core.Zeropage
import java.io.File
import java.nio.file.Path
import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VirtualMachineDefinition: IMachineDefinition {
override val cpu = CpuType.VIRTUAL
@ -32,17 +31,18 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
println("\nStarting Virtual Machine...")
// to not have external module dependencies we launch the virtual machine via reflection
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val filename = programNameWithPath.name
if(filename.endsWith(".p8virt")) {
vm.runProgram(programNameWithPath.readText(), true)
} else if(File("$filename.p8virt").isFile) {
val source = File("$filename.p8virt").readText()
vm.runProgram(source, true)
if(programNameWithPath.isReadable()) {
vm.runProgram(programNameWithPath.readText())
} else {
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText())
else
throw NoSuchFileException(withExt.toFile(), reason="not a .p8ir file")
}
else
throw IllegalArgumentException("vm can only run .p8virt or .p8ir files")
}
override fun isIOAddress(address: UInt): Boolean = false
@ -53,5 +53,5 @@ class VirtualMachineDefinition: IMachineDefinition {
}
interface IVirtualMachineRunner {
fun runProgram(source: String, throttle: Boolean)
fun runProgram(irSource: CharSequence)
}

View File

@ -8,7 +8,6 @@ import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.StMemorySlab
import prog8.code.SymbolTable
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@ -42,7 +41,7 @@ class AsmGen(internal val program: Program,
private val expressionsAsmGen = ExpressionsAsmGen(program, this, allocator)
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
private val assignmentAsmGen = AssignmentAsmGen(program, this, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
override fun compileToAssembly(): IAssemblyProgram? {
@ -78,7 +77,7 @@ class AsmGen(internal val program: Program,
}
internal fun out(str: String, splitlines: Boolean = true) {
val fragment = (if(splitlines && " | " in str) str.replace("|", "\n") else str).trim('\n')
val fragment = (if(splitlines && " | " in str) str.replace("|", "\n") else str).trim('\r', '\n')
if (splitlines) {
for (line in fragment.splitToSequence('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
@ -107,7 +106,7 @@ class AsmGen(internal val program: Program,
DataType.BYTE -> listOf("cx16", "r9sL")
DataType.UWORD -> listOf("cx16", "r9")
DataType.WORD -> listOf("cx16", "r9s")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
DataType.FLOAT -> TODO("no temporary float var available")
else -> throw FatalAstException("invalid dt $dt")
}
}
@ -839,7 +838,7 @@ $repeatLabel lda $counterVar
if(stmt.definingModule.source is SourceCode.Generated)
throw AssemblyError("%asminclude inside non-library/non-filesystem module not yet supported")
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
success = { assemblyLines.add(it.trimEnd().trimStart('\n')) },
success = { assemblyLines.add(it.trimEnd().trimStart('\r', '\n')) },
failure = { errors.err(it.toString(), stmt.position) }
)
}
@ -910,8 +909,7 @@ $repeatLabel lda $counterVar
}
private fun translate(asm: InlineAssembly) {
val assembly = asm.assembly.trimEnd().trimStart('\n')
assemblyLines.add(assembly)
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
}
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
@ -2897,12 +2895,6 @@ $repeatLabel lda $counterVar
else
extra
}
fun addMemorySlab(name: String, size: UInt, align: UInt, position: Position): String {
val prefixedName = "prog8_memoryslab_$name"
symbolTable.add(StMemorySlab(prefixedName, size, align, null, position))
return prefixedName
}
}

View File

@ -15,8 +15,7 @@ import prog8.compiler.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val assignAsmGen: AssignmentAsmGen,
private val allocations: VariableAllocator) {
private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single())
@ -44,7 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
@ -309,10 +307,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
val prefixedName = asmgen.addMemorySlab(name, size, align, fcall.position)
val slabname = IdentifierReference(listOf("prog8_slabs", prefixedName), fcall.position)
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
slabname.linkParents(fcall)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
val target =
@ -691,28 +686,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
when(func.name) {
"rnd" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rnd_stack")
else {
asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
}
}
"rndw" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rndw_stack")
else {
asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
}
}
else -> throw AssemblyError("wrong func")
}
}
private fun funcPokeW(fcall: IFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteral -> {

View File

@ -53,7 +53,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!

View File

@ -287,18 +287,24 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
if(assign.target.array==null) {
// First assign the value to the target then apply the operator in place on the target.
// This saves a temporary variable
translateNormalAssignment(
AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
)
)
when (value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign)
"~" -> augmentableAsmGen.inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
} else {
assignPrefixedExpressionToArrayElt(assign)
}
}
is ContainmentCheck -> {
@ -317,6 +323,28 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
internal fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
require(assign.source.expression is PrefixExpression)
if(assign.source.datatype==DataType.FLOAT) {
// floatarray[x] = -value ... just use FAC1 to calculate the expression into and then store that back into the array.
assignExpressionToRegister(assign.source.expression, RegisterOrPair.FAC1, true)
assignFAC1float(assign.target)
} else {
// array[x] = -value ... use a tempvar then store that back into the array.
val tempvar = asmgen.getTempVarName(assign.target.datatype).joinToString(".")
val assignToTempvar = AsmAssignment(assign.source,
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget),
false, program.memsizer, assign.position)
asmgen.translateNormalAssignment(assignToTempvar)
when(assign.target.datatype) {
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
else -> throw AssemblyError("weird dt")
}
}
}
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
when(target.datatype) {
in ByteDatatypes -> {
@ -852,7 +880,8 @@ internal class AssignmentAsmGen(private val program: Program,
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(target, CpuRegister.A)
asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
when (value.addressExpression) {
@ -1972,18 +2001,10 @@ internal class AssignmentAsmGen(private val program: Program,
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
// these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
if(target.kind== TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes)
} else {
require(target.datatype in ByteDatatypes)
}
}
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
// these will be correctly typecasted from a byte to a word value here
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
require(target.datatype in ByteDatatypes)
when(target.kind) {
TargetStorageKind.VARIABLE -> {

View File

@ -21,13 +21,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
val itype = value.inferType(program)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(target, type)
"~" -> inplaceInvert(target, type)
"-" -> inplaceNegate(assign)
"~" -> inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
}
@ -1796,8 +1793,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) {
internal fun inplaceInvert(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1840,7 +1838,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.UWORD -> {
@ -1864,15 +1863,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
when (dt) {
internal fun inplaceNegate(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1896,9 +1897,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
else -> throw AssemblyError("no asm gen for in-place negate byte")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.WORD -> {
@ -1955,12 +1957,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
else -> throw AssemblyError("no asm gen for in-place negate word")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.FLOAT -> {
when (target.kind) {
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
else -> throw AssemblyError("invalid float register")
}
}
TargetStorageKind.VARIABLE -> {
// simply flip the sign bit in the float
asmgen.out("""
@ -1970,10 +1981,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target for in-place float negation")
}
}
else -> throw AssemblyError("negate of invalid type $dt")
else -> throw AssemblyError("negate of invalid type")
}
}

View File

@ -22,7 +22,7 @@ class CodeGen(private val program: PtProgram,
val irProgram = irCodeGen.generate()
// this stub only writes the IR program to disk but doesn't generate anything else.
IRFileWriter(irProgram).writeFile()
IRFileWriter(irProgram, null).write()
println("** experimental codegen stub: no assembly generated **")
return null

View File

@ -26,7 +26,6 @@ compileTestKotlin {
dependencies {
implementation project(':codeCore')
implementation project(':intermediate')
implementation project(':virtualmachine')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"

View File

@ -12,7 +12,6 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component>

View File

@ -3,16 +3,12 @@ package prog8.codegen.intermediate
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SignedDatatypes
import prog8.intermediate.IRCodeChunk
import prog8.intermediate.IRCodeInstruction
import prog8.intermediate.Opcode
import prog8.intermediate.VmDataType
import prog8.intermediate.*
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
internal fun translate(assignment: PtAssignment): IRCodeChunk {
internal fun translate(assignment: PtAssignment): IRCodeChunks {
if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
@ -22,7 +18,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
translateRegularAssign(assignment)
}
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunk {
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
@ -48,23 +44,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
address: Int,
value: PtExpression,
origAssign: PtAssignment
): IRCodeChunk {
val vmDt = codeGen.vmType(value.type)
val code = IRCodeChunk(origAssign.position)
): IRCodeChunks {
val vmDt = codeGen.irType(value.type)
when(value) {
is PtIdentifier -> return code // do nothing, x=x null assignment.
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null, value.position)
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
is PtMemoryByte -> {
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
code // do nothing, mem=mem null assignment.
emptyList() // do nothing, mem=mem null assignment.
else {
// read and write a (i/o) memory location to itself.
val tempReg = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
code += IRCodeInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
code
val tempReg = codeGen.registers.nextFree()
val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
listOf(code)
}
}
else -> return fallbackAssign(origAssign)
@ -75,25 +71,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
symbol: String,
value: PtExpression,
origAssign: PtAssignment
): IRCodeChunk {
val vmDt = codeGen.vmType(value.type)
val code = IRCodeChunk(origAssign.position)
when(value) {
is PtIdentifier -> return code // do nothing, x=x null assignment.
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, null, symbol, value.position)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
): IRCodeChunks {
val vmDt = codeGen.irType(value.type)
return when(value) {
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
is PtMemoryByte -> {
val tempReg = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
code += IRCodeInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
return code
val code = IRCodeChunk(null, null)
val tempReg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
listOf(code)
}
else -> return fallbackAssign(origAssign)
else -> fallbackAssign(origAssign)
}
}
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunk {
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
if (codeGen.options.slowCodegenWarnings)
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
return translateRegularAssign(origAssign)
@ -102,12 +99,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun inplaceBinexpr(
operator: String,
operand: PtExpression,
vmDt: VmDataType,
vmDt: IRDataType,
signed: Boolean,
knownAddress: Int?,
symbol: String?,
origAssign: PtAssignment
): IRCodeChunk {
): IRCodeChunks {
if(knownAddress!=null) {
when (operator) {
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
@ -139,66 +136,67 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return fallbackAssign(origAssign)
}
private fun inplacePrefix(operator: String, vmDt: VmDataType, knownAddress: Int?, addressSymbol: String?, position: Position): IRCodeChunk {
val code= IRCodeChunk(position)
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
val code= IRCodeChunk(null, null)
when(operator) {
"+" -> { }
"-" -> {
code += if(knownAddress!=null)
IRCodeInstruction(Opcode.NEGM, vmDt, value = knownAddress)
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress)
else
IRCodeInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
}
"~" -> {
val regMask = codeGen.vmRegisters.nextFree()
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
code += IRCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
val regMask = codeGen.registers.nextFree()
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
code += if(knownAddress!=null)
IRCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
else
IRCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
}
else -> throw AssemblyError("weird prefix operator")
}
return code
return listOf(code)
}
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunk {
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
val vmDt = codeGen.vmType(assignment.value.type)
val code = IRCodeChunk(assignment.position)
val vmDt = codeGen.irType(assignment.value.type)
val result = mutableListOf<IRCodeChunkBase>()
var resultRegister = -1
var resultFpRegister = -1
val zero = codeGen.isZero(assignment.value)
if(!zero) {
// calculate the assignment value
if (vmDt == VmDataType.FLOAT) {
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
if (vmDt == IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.nextFreeFloat()
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
} else {
resultRegister = if (assignment.value is PtMachineRegister) {
(assignment.value as PtMachineRegister).register
} else {
val reg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(assignment.value, reg, -1)
val reg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(assignment.value, reg, -1)
reg
}
}
}
if(ident!=null) {
val symbol = ident.targetName.joinToString(".")
code += if(zero) {
IRCodeInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
} else {
if (vmDt == VmDataType.FLOAT)
IRCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
if (vmDt == IRDataType.FLOAT)
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
else
IRCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
}
result += IRCodeChunk(null, null).also { it += instruction }
return result
}
else if(array!=null) {
val variable = array.variable.targetName.joinToString(".")
@ -210,85 +208,91 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
throw AssemblyError("non-array var indexing requires bytes dt")
if(array.index.type!=DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index")
val idxReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(array.index, idxReg, -1)
val idxReg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(array.index, idxReg, -1)
val code = IRCodeChunk(null, null)
if(zero) {
// there's no STOREZIX instruction
resultRegister = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
resultRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
}
code += IRCodeInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
return code
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
result += code
return result
}
val fixedIndex = constIntValue(array.index)
if(zero) {
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
code += IRCodeInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset")
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position)
code += IRCodeInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable)
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
}
} else {
if(vmDt== VmDataType.FLOAT) {
if(vmDt== IRDataType.FLOAT) {
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
code += IRCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset")
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position)
code += IRCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
}
} else {
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
code += IRCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset")
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position)
code += IRCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
}
}
}
return result
}
else if(memory!=null) {
require(vmDt== VmDataType.BYTE)
require(vmDt== IRDataType.BYTE)
if(zero) {
if(memory.address is PtNumber) {
code += IRCodeInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += IRCodeInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
val addressReg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(memory.address, addressReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
}
} else {
if(memory.address is PtNumber) {
code += IRCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += IRCodeInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
val addressReg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(memory.address, addressReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
}
}
return result
}
else
throw AssemblyError("weird assigntarget")
return code
}
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int, position: Position): IRCodeChunk {
val code = IRCodeChunk(position)
if(itemsize==1) {
code += expressionEval.translateExpression(array.index, indexReg, -1)
}
else {
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
return if(itemsize==1) {
expressionEval.translateExpression(array.index, indexReg, -1)
} else {
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
code += expressionEval.translateExpression(mult, indexReg, -1)
expressionEval.translateExpression(mult, indexReg, -1)
}
return code
}
}

View File

@ -4,14 +4,12 @@ import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.intermediate.*
import prog8.vm.Syscall
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
return when(call.name) {
"any" -> funcAny(call, resultRegister)
"all" -> funcAll(call, resultRegister)
@ -26,9 +24,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"rsave",
"rsavex",
"rrestore",
"rrestorex" -> IRCodeChunk(call.position) // vm doesn't have registers to save/restore
"rnd" -> funcRnd(resultRegister, call.position)
"rndw" -> funcRndw(resultRegister, call.position)
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"msb" -> funcMsb(call, resultRegister)
@ -38,7 +34,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"peekw" -> funcPeekW(call, resultRegister)
"poke" -> funcPoke(call)
"pokew" -> funcPokeW(call)
"pokemon" -> IRCodeChunk(call.position)
"pokemon" -> emptyList()
"mkword" -> funcMkword(call, resultRegister)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
@ -50,320 +46,359 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val leftRegister = codeGen.vmRegisters.nextFree()
val rightRegister = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
code += IRCodeInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
return code
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
val leftRegister = codeGen.registers.nextFree()
val rightRegister = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], leftRegister, -1)
result += exprGen.translateExpression(call.args[1], rightRegister, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, codeGen.irType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
}
return result
}
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val code = IRCodeChunk(call.position)
val syscall =
when (array.dt) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> Syscall.ANY_BYTE
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> Syscall.ANY_WORD
DataType.ARRAY_F -> Syscall.ANY_FLOAT
DataType.ARRAY_W -> IMSyscall.ANY_WORD
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type")
}
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
code += IRCodeInstruction(Opcode.SYSCALL, value = syscall.ordinal)
if (resultRegister != 0)
code += IRCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
if(resultRegister!=0)
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
}
return result
}
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val syscall =
when(array.dt) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> Syscall.ALL_BYTE
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> Syscall.ALL_WORD
DataType.ARRAY_F -> Syscall.ALL_FLOAT
DataType.ARRAY_W -> IMSyscall.ALL_WORD
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
else -> throw IllegalArgumentException("weird type")
}
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += IRCodeInstruction(Opcode.SYSCALL, value=syscall.ordinal)
if(resultRegister!=0)
code += IRCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
if(resultRegister!=0)
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
}
return result
}
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val sourceDt = call.args.single().type
val result = mutableListOf<IRCodeChunkBase>()
if(sourceDt!=DataType.UWORD) {
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
when (sourceDt) {
DataType.UBYTE -> {
code += IRCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = resultRegister)
}
}
DataType.BYTE -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
code += IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
code += IRCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
code += IRCodeInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
code += IRCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
code += IRCodeLabel(notNegativeLabel)
val compareReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
}
result += IRCodeChunk(notNegativeLabel, null)
}
DataType.WORD -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
code += IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
code += IRCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
code += IRCodeInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
code += IRCodeLabel(notNegativeLabel)
val compareReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
}
result += IRCodeChunk(notNegativeLabel, null)
}
else -> throw AssemblyError("weird type")
}
}
return code
return result
}
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRCodeInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
return code
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val reg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
}
return result
}
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRCodeInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
return code
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val reg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
}
return result
}
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
code += assignRegisterTo(call.args.single(), reg)
return code
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(null, null)
val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
val result = mutableListOf<IRCodeChunkBase>(code)
result += assignRegisterTo(call.args.single(), reg)
return result
}
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += IRCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
code += assignRegisterTo(call.args.single(), reg)
return code
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(null, null)
val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
val result = mutableListOf<IRCodeChunkBase>(code)
result += assignRegisterTo(call.args.single(), reg)
return result
}
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
return code
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val reg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
}
return result
}
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
return code
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val reg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = reg)
}
return result
}
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunk {
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val sortSyscall =
val syscall =
when(array.dt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> Syscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> Syscall.REVERSE_WORDS
DataType.ARRAY_F -> Syscall.REVERSE_FLOATS
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
else -> throw IllegalArgumentException("weird type to reverse")
}
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += IRCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
}
return result
}
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunk {
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val sortSyscall =
val syscall =
when(array.dt) {
DataType.ARRAY_UB -> Syscall.SORT_UBYTE
DataType.ARRAY_B -> Syscall.SORT_BYTE
DataType.ARRAY_UW -> Syscall.SORT_UWORD
DataType.ARRAY_W -> Syscall.SORT_WORD
DataType.STR -> Syscall.SORT_UBYTE
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
DataType.ARRAY_W -> IMSyscall.SORT_WORD
DataType.STR -> IMSyscall.SORT_UBYTE
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
else -> throw IllegalArgumentException("weird type to sort")
}
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += IRCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
}
return result
}
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val msbReg = codeGen.vmRegisters.nextFree()
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], msbReg, -1)
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
code += IRCodeInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
return code
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val msbReg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], msbReg, -1)
result += exprGen.translateExpression(call.args[1], resultRegister, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
}
return result
}
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += IRCodeInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += IRCodeInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
}
}
} else {
val valueReg = codeGen.vmRegisters.nextFree()
val valueReg = codeGen.registers.nextFree()
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRCodeInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRCodeInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueReg, reg2 = addressReg)
}
}
}
return code
return result
}
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += IRCodeInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += IRCodeInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
}
}
} else {
val valueReg = codeGen.vmRegisters.nextFree()
val valueReg = codeGen.registers.nextFree()
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRCodeInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
}
}
}
return code
return result
}
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += IRCodeInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += IRCodeInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
}
}
return code
return result
}
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += IRCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += IRCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
}
}
return code
return result
}
private fun funcRnd(resultRegister: Int, position: Position): IRCodeChunk {
val code = IRCodeChunk(position)
code += IRCodeInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
return code
}
private fun funcRndw(resultRegister: Int, position: Position): IRCodeChunk {
val code = IRCodeChunk(position)
code += IRCodeInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
return code
}
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val name = (call.args[0] as PtString).value
val size = (call.args[1] as PtNumber).number.toUInt()
val align = (call.args[2] as PtNumber).number.toUInt()
val label = codeGen.addMemorySlab(name, size, align, call.position)
val code = IRCodeChunk(call.position)
code += IRCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, labelSymbol = label)
return code
val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return listOf(code)
}
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
return exprGen.translateExpression(call.args.single(), resultRegister, -1)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return code
}
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
code += IRCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultRegister, reg2 = resultRegister)
}
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return code
return result
}
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val vmDt = codeGen.vmType(call.args[0].type)
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
code += IRCodeInstruction(opcode, vmDt, reg1=resultRegister)
code += assignRegisterTo(call.args[0], resultRegister)
return code
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val vmDt = codeGen.irType(call.args[0].type)
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
}
result += assignRegisterTo(call.args[0], resultRegister)
return result
}
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunk {
val code = IRCodeChunk(target.position)
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtMachineRegister(register, target.type, target.position))
code += codeGen.translateNode(assignment)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += codeGen.translateNode(assignment)
return result
}
}

View File

@ -5,41 +5,128 @@ import prog8.intermediate.*
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
fun optimize() {
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.forEach { chunk ->
removeEmptyChunks(sub)
joinChunks(sub)
sub.chunks.withIndex().forEach { (index, chunk1) ->
// we don't optimize Inline Asm chunks here.
if(chunk is IRCodeChunk) {
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
if(chunk1 is IRCodeChunk) {
do {
val indexedInstructions = chunk.lines.withIndex()
.filter { it.value is IRCodeInstruction }
.map { IndexedValue(it.index, (it.value as IRCodeInstruction).ins) }
val changed = removeNops(chunk, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk, indexedInstructions) // TODO not yet implemented
|| removeUselessArithmetic(chunk, indexedInstructions)
|| removeWeirdBranches(chunk, indexedInstructions)
|| removeDoubleSecClc(chunk, indexedInstructions)
|| cleanupPushPop(chunk, indexedInstructions)
val indexedInstructions = chunk1.instructions.withIndex()
.map { IndexedValue(it.index, it.value) }
val changed = removeNops(chunk1, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|| removeUselessArithmetic(chunk1, indexedInstructions)
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
// TODO other optimizations:
// more complex optimizations such as unused registers
} while (changed)
}
}
removeEmptyChunks(sub)
}
irprog.linkChunks() // re-link
}
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun removeEmptyChunks(sub: IRSubroutine) {
if(sub.chunks.isEmpty())
return
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: consolidate labels into 1)
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
*/
val relabelChunks = mutableListOf<Pair<Int, String>>()
val removeChunks = mutableListOf<Int>()
sub.chunks.withIndex().forEach { (index, chunk) ->
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
if(chunk.label==null) {
removeChunks += index
} else {
if (index < sub.chunks.size - 1) {
val nextchunk = sub.chunks[index + 1]
if (nextchunk.label == null) {
// can transplant label to next chunk and remove this empty one.
relabelChunks += Pair(index + 1, chunk.label!!)
removeChunks += index
} else {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
// TODO: consolidate labels on same chunk
}
}
}
}
}
}
relabelChunks.forEach { (index, label) ->
val chunk = IRCodeChunk(label, null)
chunk.instructions += sub.chunks[index].instructions
sub.chunks[index] = chunk
}
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
}
private fun joinChunks(sub: IRSubroutine) {
// Subroutine contains a list of chunks. Some can be joined into one.
if(sub.chunks.isEmpty())
return
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
if(chunk.label!=null)
return false
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
val lastInstruction = previous.instructions.lastOrNull()
if(lastInstruction!=null)
return lastInstruction.opcode !in OpcodesThatJump
return true
}
return false
}
val chunks = mutableListOf<IRCodeChunkBase>()
chunks += sub.chunks[0]
for(ix in 1 until sub.chunks.size) {
val lastChunk = chunks.last()
if(mayJoin(lastChunk, sub.chunks[ix])) {
lastChunk.instructions += sub.chunks[ix].instructions
lastChunk.next = sub.chunks[ix].next
}
else
chunks += sub.chunks[ix]
}
sub.chunks.clear()
sub.chunks += chunks
}
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// push followed by pop to same target, or different target->replace with load
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.PUSH) {
if(idx < chunk.lines.size-1) {
val insAfter = chunk.lines[idx+1] as? IRCodeInstruction
if(insAfter!=null && insAfter.ins.opcode == Opcode.POP) {
if(ins.reg1==insAfter.ins.reg1) {
chunk.lines.removeAt(idx)
chunk.lines.removeAt(idx)
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
if(ins.reg1==insAfter.reg1) {
chunk.instructions.removeAt(idx)
chunk.instructions.removeAt(idx)
} else {
chunk.lines[idx] = IRCodeInstruction(Opcode.LOADR, ins.type, reg1=insAfter.ins.reg1, reg2=ins.reg1)
chunk.lines.removeAt(idx+1)
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
chunk.instructions.removeAt(idx+1)
}
changed = true
}
@ -49,24 +136,24 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeDoubleSecClc(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun removeDoubleSecClc(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// double sec, clc
// sec+clc or clc+sec
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
if(idx < chunk.lines.size-1) {
val insAfter = chunk.lines[idx+1] as? IRCodeInstruction
if(insAfter?.ins?.opcode == ins.opcode) {
chunk.lines.removeAt(idx)
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter?.opcode == ins.opcode) {
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.SEC && insAfter?.ins?.opcode== Opcode.CLC) {
chunk.lines.removeAt(idx)
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.CLC && insAfter?.ins?.opcode== Opcode.SEC) {
chunk.lines.removeAt(idx)
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -75,73 +162,78 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeWeirdBranches(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
// jump/branch to label immediately below
private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
val labelSymbol = ins.labelSymbol
// remove jump/branch to label immediately below (= next chunk if it has that label)
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
// if jumping to label immediately following this
if(idx < chunk.lines.size-1) {
val label = chunk.lines[idx+1] as? IRCodeLabel
if(labelSymbol.size==1 && label?.name == labelSymbol[0]) {
chunk.lines.removeAt(idx)
changed = true
}
if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) {
chunk.instructions.removeAt(idx)
changed = true
}
}
// remove useless RETURN
if(ins.opcode == Opcode.RETURN && idx>0) {
val previous = chunk.instructions[idx-1] as? IRInstruction
if(previous?.opcode in OpcodesThatJump) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
return changed
}
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
if (ins.value == 1) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.ADD, Opcode.SUB -> {
if (ins.value == 1) {
chunk.lines[idx] = IRCodeInstruction(
chunk.instructions[idx] = IRInstruction(
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
ins.type,
ins.reg1
)
changed = true
} else if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.AND -> {
if (ins.value == 0) {
chunk.lines[idx] = IRCodeInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
changed = true
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) {
chunk.lines.removeAt(idx)
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
chunk.instructions.removeAt(idx)
changed = true
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) {
chunk.lines.removeAt(idx)
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.OR -> {
if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) {
chunk.lines[idx] = IRCodeInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
changed = true
}
}
Opcode.XOR -> {
if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -151,18 +243,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.NOP) {
changed = true
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
}
}
return changed
}
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.forEach { (idx, ins) ->

View File

@ -0,0 +1,103 @@
package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
import prog8.intermediate.*
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
fun optimize(): Int {
var numRemoved = removeSimpleUnlinked() + removeUnreachable()
// remove empty subs
irprog.blocks.forEach { block ->
block.subroutines.reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(libraryFilePrefix))
errors.warn("unused subroutine ${sub.name}", sub.position)
block.subroutines.remove(sub)
numRemoved++
}
}
}
// remove empty blocks
irprog.blocks.reversed().forEach { block ->
if(block.isEmpty()) {
irprog.blocks.remove(block)
numRemoved++
}
}
return numRemoved
}
private fun removeUnreachable(): Int {
val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first())
fun grow() {
val new = mutableSetOf<IRCodeChunkBase>()
reachable.forEach {
it.next?.let { next -> new += next }
it.instructions.forEach { instr -> instr.branchTarget?.let { target -> new += target} }
}
reachable += new
}
var previousCount = reachable.size
while(true) {
grow()
if(reachable.size<=previousCount)
break
previousCount = reachable.size
}
return removeUnlinkedChunks(reachable)
}
private fun removeSimpleUnlinked(): Int {
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach { it.branchTarget?.let { target -> linkedChunks += target } }
if (chunk.label == "main.start")
linkedChunks += chunk
}
}
return removeUnlinkedChunks(linkedChunks)
}
private fun removeUnlinkedChunks(
linkedChunks: MutableSet<IRCodeChunkBase>
): Int {
var numRemoved = 0
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
if (chunk !in linkedChunks) {
if (chunk === sub.chunks[0]) {
when(chunk) {
is IRCodeChunk -> {
if (chunk.isNotEmpty()) {
// don't remove the first chunk of the sub itself because it has to have the name of the sub as label
chunk.instructions.clear()
numRemoved++
}
}
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
numRemoved++
}
}
} else {
sub.chunks.removeAt(index)
numRemoved++
}
}
}
}
return numRemoved
}
}

View File

@ -1,10 +1,12 @@
package prog8.codegen.intermediate
import prog8.code.core.AssemblyError
import prog8.intermediate.SyscallRegisterBase
internal class RegisterPool {
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
private var firstFreeFloat: Int=0
// reserve 0,1,2 for return values of subroutine calls and syscalls
private var firstFree: Int=3
private var firstFreeFloat: Int=3
fun peekNext() = firstFree
fun peekNextFloat() = firstFreeFloat
@ -12,7 +14,7 @@ internal class RegisterPool {
fun nextFree(): Int {
val result = firstFree
firstFree++
if(firstFree>65535)
if(firstFree >= SyscallRegisterBase)
throw AssemblyError("out of virtual registers (int)")
return result
}
@ -20,7 +22,7 @@ internal class RegisterPool {
fun nextFreeFloat(): Int {
val result = firstFreeFloat
firstFreeFloat++
if(firstFreeFloat>65535)
if(firstFreeFloat >= SyscallRegisterBase)
throw AssemblyError("out of virtual registers (fp)")
return result
}

View File

@ -1,4 +1,4 @@
package prog8.codegen.virtual
package prog8.codegen.vm
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
@ -7,9 +7,8 @@ import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileReader
import prog8.intermediate.IRFileWriter
import java.nio.file.Path
import prog8.intermediate.IRProgram
class VmCodeGen(private val program: PtProgram,
private val symbolTable: SymbolTable,
@ -21,20 +20,17 @@ class VmCodeGen(private val program: PtProgram,
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate()
return if(options.keepIR) {
//create IR file on disk and read it back.
IRFileWriter(irProgram).writeFile()
val irProgram2 = IRFileReader(options.outputDir, irProgram.name).readFile()
VmAssemblyProgram(irProgram2.name, irProgram2)
} else {
VmAssemblyProgram(irProgram.name, irProgram)
}
// no need to check options.keepIR, as the VM file format *is* the IR file.
return VmAssemblyProgram(irProgram.name, irProgram)
}
}
companion object {
fun compileIR(listingFilename: String): IAssemblyProgram {
val irProgram = IRFileReader(Path.of(""), listingFilename).readFile()
return VmAssemblyProgram(irProgram.name, irProgram)
}
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
override fun assemble(options: CompilationOptions): Boolean {
// the VM reads the IR file from disk.
IRFileWriter(irProgram, null).write()
return true
}
}
}

View File

@ -1,21 +1,17 @@
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTable
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.codegen.intermediate.IRPeepholeOptimizer
import prog8.intermediate.*
class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(lines: List<IRCodeLine>): IRProgram {
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
require(chunks.first().label=="main.start")
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
val chunk = IRCodeChunk(Position.DUMMY)
for(line in lines)
chunk += line
sub += chunk
chunks.forEach { sub += it }
block += sub
val st = SymbolTable()
val target = VMTarget()
val options = CompilationOptions(
OutputType.RAW,
@ -27,153 +23,167 @@ class TestIRPeepholeOpt: FunSpec({
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
val prog = IRProgram("test", st, options, target)
val prog = IRProgram("test", IRSymbolTable(null), options, target)
prog.addBlock(block)
prog.linkChunks()
prog.validate()
return prog
}
fun IRProgram.lines(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.lines }
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
val chunk = IRCodeChunk("main.start", null)
instructions.forEach { chunk += it }
return makeIRProgram(listOf(chunk))
}
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }
test("remove nops") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.JUMP, labelSymbol = "dummy"),
IRCodeInstruction(Opcode.NOP),
IRCodeInstruction(Opcode.NOP)
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
IRInstruction(Opcode.NOP),
IRInstruction(Opcode.NOP)
))
irProg.lines().size shouldBe 3
irProg.chunks().single().instructions.size shouldBe 3
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
irProg.lines().size shouldBe 1
irProg.chunks().single().instructions.size shouldBe 1
}
test("remove jmp to label below") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.JUMP, labelSymbol = "label"), // removed
IRCodeLabel("label"),
IRCodeInstruction(Opcode.JUMP, labelSymbol = "label2"), // removed
IRCodeInstruction(Opcode.NOP), // removed
IRCodeLabel("label2"),
IRCodeInstruction(Opcode.JUMP, labelSymbol = "label3"),
IRCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
IRCodeLabel("label3")
))
irProg.lines().size shouldBe 8
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
val c2 = IRCodeChunk("label", null)
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
c2 += IRInstruction(Opcode.NOP) // removed
val c3 = IRCodeChunk("label2", null)
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
val c4 = IRCodeChunk("label3", null)
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
irProg.chunks().size shouldBe 4
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 5
(lines[0] as IRCodeLabel).name shouldBe "label"
(lines[1] as IRCodeLabel).name shouldBe "label2"
(lines[2] as IRCodeInstruction).ins.opcode shouldBe Opcode.JUMP
(lines[3] as IRCodeInstruction).ins.opcode shouldBe Opcode.INC
(lines[4] as IRCodeLabel).name shouldBe "label3"
irProg.chunks().size shouldBe 4
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label"
irProg.chunks()[2].label shouldBe "label2"
irProg.chunks()[3].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe true
irProg.chunks()[2].isEmpty() shouldBe false
irProg.chunks()[3].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP
instr[1].opcode shouldBe Opcode.INC
}
test("remove double sec/clc") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.SEC),
IRCodeInstruction(Opcode.SEC),
IRCodeInstruction(Opcode.SEC),
IRCodeInstruction(Opcode.CLC),
IRCodeInstruction(Opcode.CLC),
IRCodeInstruction(Opcode.CLC)
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC)
))
irProg.lines().size shouldBe 6
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 1
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.CLC
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.CLC
}
test("push followed by pop") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
IRCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
IRCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
IRCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
))
irProg.lines().size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 1
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOADR
(lines[0] as IRCodeInstruction).ins.reg1 shouldBe 222
(lines[0] as IRCodeInstruction).ins.reg2 shouldBe 99
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.LOADR
instr[0].reg1 shouldBe 222
instr[0].reg2 shouldBe 99
}
test("remove useless div/mul, add/sub") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
IRCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
IRCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
IRCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
IRCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
IRCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
IRCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
IRCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
IRCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
IRCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
))
irProg.lines().size shouldBe 10
irProg.chunks().single().instructions.size shouldBe 10
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
}
test("replace add/sub 1 by inc/dec") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
IRCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
))
irProg.lines().size shouldBe 2
irProg.chunks().single().instructions.size shouldBe 2
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 2
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.INC
(lines[1] as IRCodeInstruction).ins.opcode shouldBe Opcode.DEC
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.INC
instr[1].opcode shouldBe Opcode.DEC
}
test("remove useless and/or/xor") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
IRCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
IRCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
IRCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
IRCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
))
irProg.lines().size shouldBe 8
irProg.chunks().single().instructions.size shouldBe 8
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
}
test("replace and/or/xor by constant number") {
val irProg = makeIRProgram(listOf(
IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
IRCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
IRCodeInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
))
irProg.lines().size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 4
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
(lines[1] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
(lines[2] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
(lines[3] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
(lines[0] as IRCodeInstruction).ins.value shouldBe 0
(lines[1] as IRCodeInstruction).ins.value shouldBe 0
(lines[2] as IRCodeInstruction).ins.value shouldBe 255
(lines[3] as IRCodeInstruction).ins.value shouldBe 65535
val instr = irProg.chunks().single().instructions
instr.size shouldBe 4
instr[0].opcode shouldBe Opcode.LOAD
instr[1].opcode shouldBe Opcode.LOAD
instr[2].opcode shouldBe Opcode.LOAD
instr[3].opcode shouldBe Opcode.LOAD
instr[0].value shouldBe 0
instr[1].value shouldBe 0
instr[2].value shouldBe 255
instr[3].value shouldBe 65535
}
})

View File

@ -1,65 +0,0 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.9"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':codeCore')
implementation project(':intermediate')
implementation project(':codeGenIntermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDir "${project.projectDir}/test"
}
}
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="module" module-name="codeGenIntermediate" />
</component>
</module>

View File

@ -1,103 +0,0 @@
package prog8.codegen.virtual
import prog8.code.core.AssemblyError
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.intermediate.*
import java.io.BufferedWriter
import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
override fun assemble(dummyOptions: CompilationOptions): Boolean {
val outfile = irProgram.options.outputDir / ("$name.p8virt")
println("write code to $outfile")
// at last, allocate the variables in memory.
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
outfile.bufferedWriter().use { out ->
allocations.asVmMemory().forEach { (name, alloc) ->
out.write("var ${name.joinToString(".")} $alloc\n")
}
out.write("------PROGRAM------\n")
if(!irProgram.options.dontReinitGlobals) {
out.write("; global var inits\n")
irProgram.globalInits.forEach { out.writeLine(it) }
}
irProgram.blocks.firstOrNull()?.let {
if(it.subroutines.any { it.name=="main.start" }) {
// there is a "main.start" entrypoint, jump to it
out.writeLine(IRCodeInstruction(Opcode.JUMP, labelSymbol = "main.start"))
}
}
out.write("; actual program code\n")
irProgram.blocks.forEach { block ->
if(block.address!=null)
TODO("blocks can't have a load address for vm")
out.write("; BLOCK ${block.name} ${block.position}\n")
block.inlineAssembly.forEach { asm ->
out.write("; ASM ${asm.position}\n")
out.write(asm.assembly)
out.write("\n")
}
block.subroutines.forEach { sub ->
out.write("; SUB ${sub.name} ${sub.position}\n")
out.write("_${sub.name}:\n")
sub.chunks.forEach { chunk ->
if(chunk is IRInlineAsmChunk) {
out.write("; ASM ${chunk.position}\n")
out.write(processInlinedAsm(chunk.assembly, allocations))
out.write("\n")
} else {
chunk.lines.forEach { out.writeLine(it) }
}
}
out.write("; END SUB ${sub.name}\n")
}
block.asmSubroutines.forEach { sub ->
out.write("; ASMSUB ${sub.name} ${sub.position}\n")
out.write("_${sub.name}:\n")
out.write(processInlinedAsm(sub.assembly, allocations))
out.write("\n; END ASMSUB ${sub.name}\n")
}
out.write("; END BLOCK ${block.name}\n")
}
}
return true
}
private fun processInlinedAsm(asm: String, allocations: VmVariableAllocator): String {
// TODO do we have to replace variable names by their allocated address???
return asm
}
}
private fun BufferedWriter.writeLine(line: IRCodeLine) {
when(line) {
is IRCodeComment -> {
write("; ${line.comment}\n")
}
is IRCodeInstruction -> {
write(line.ins.toString() + "\n")
}
is IRCodeInlineBinary -> {
write("!binary ")
line.data.withIndex().forEach {(index, byte) ->
write(byte.toString(16).padStart(2,'0'))
if(index and 63 == 63 && index<line.data.size-1)
write("\n!binary ")
}
write("\n")
}
is IRCodeLabel -> {
write("_${line.name}:\n")
}
else -> throw AssemblyError("invalid IR code line")
}
}

View File

@ -1,92 +0,0 @@
package prog8.codegen.virtual
import prog8.code.SymbolTable
import prog8.code.core.*
import prog8.intermediate.getTypeString
internal class VmVariableAllocator(val st: SymbolTable, val encoding: IStringEncoding, memsizer: IMemSizer) {
internal val allocations = mutableMapOf<List<String>, Int>()
private var freeMemoryStart: Int
val freeMem: Int
get() = freeMemoryStart
init {
var nextLocation = 0
for (variable in st.allVariables) {
val memsize =
when (variable.dt) {
DataType.STR -> variable.onetimeInitializationStringValue!!.first.length + 1 // include the zero byte
in NumericDatatypes -> memsizer.memorySize(variable.dt)
in ArrayDatatypes -> memsizer.memorySize(variable.dt, variable.length!!)
else -> throw InternalCompilerException("weird dt")
}
allocations[variable.scopedName] = nextLocation
nextLocation += memsize
}
for(slab in st.allMemorySlabs) {
// we ignore the alignment for the VM.
allocations[slab.scopedName] = nextLocation
nextLocation += slab.size.toInt()
}
freeMemoryStart = nextLocation
}
fun asVmMemory(): List<Pair<List<String>, String>> {
val mm = mutableListOf<Pair<List<String>, String>>()
// normal variables
for (variable in st.allVariables) {
val location = allocations.getValue(variable.scopedName)
val value = when(variable.dt) {
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: 0.0).toString()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue ?: 0).toHex()
DataType.STR -> {
val encoded = encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
encoded.joinToString(",") { it.toInt().toHex() }
}
DataType.ARRAY_F -> {
if(variable.onetimeInitializationArrayValue!=null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toString() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
in ArrayDatatypes -> {
if(variable.onetimeInitializationArrayValue!==null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toHex() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
else -> throw InternalCompilerException("weird dt")
}
mm.add(Pair(variable.scopedName, "@$location ${getTypeString(variable)} $value"))
}
// memory mapped variables
for (variable in st.allMemMappedVariables) {
val value = when(variable.dt) {
DataType.FLOAT -> "0.0"
in NumericDatatypes -> "0"
DataType.ARRAY_F -> (1..variable.length!!).joinToString(",") { "0.0" }
in ArrayDatatypes -> (1..variable.length!!).joinToString(",") { "0" }
else -> throw InternalCompilerException("weird dt for mem mapped var")
}
mm.add(Pair(variable.scopedName, "@${variable.address} ${getTypeString(variable)} $value"))
}
// memory slabs.
for(slab in st.allMemorySlabs) {
val address = allocations.getValue(slab.scopedName)
mm.add(Pair(slab.scopedName, "@$address ubyte[${slab.size}] 0"))
}
return mm
}
}

View File

@ -20,7 +20,9 @@ import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
class ExpressionSimplifier(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@ -586,11 +588,13 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
else if(amount==8) {
@ -625,6 +629,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (idt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
}
}
@ -636,6 +641,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
}
DataType.UWORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
}
else if(amount==8) {

View File

@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, target)
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, errors, target)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() {
is Return -> {
if(stmt.value is NumericLiteral)
true
else if(stmt.value==null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true

View File

@ -97,11 +97,13 @@ class StatementOptimizer(private val program: Program,
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifElse.condition.position)
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifElse.condition.position)
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
}
}
@ -291,9 +293,9 @@ class StatementOptimizer(private val program: Program,
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number
if(bexpr.operator=="-" && rightCv==null) {
if(bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
if(bexpr.operator=="-" && rightCv==null && targetIDt.isInteger) {
if(bexpr.right.isSimple && bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation, for integers)
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
return listOf(

View File

@ -31,8 +31,9 @@ dependencies {
implementation project(':codeOptimizers')
implementation project(':compilerAst')
implementation project(':codeGenCpu6502')
implementation project(':codeGenVirtual')
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation 'org.antlr:antlr4-runtime:4.10.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"

View File

@ -21,6 +21,7 @@
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenVirtual" />
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
</component>
</module>

View File

@ -56,7 +56,8 @@ romsub $af4b = ROUND() clobbers(A,X,Y) ; round fac1
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $af57 = RND() clobbers(A,X,Y) ; alias for RND_0
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
@ -151,6 +152,17 @@ asmsub FREADUY (ubyte value @Y) {
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
sub rndf() -> float {
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr RND_0
ldx P8ZP_SCRATCH_REG
rts
}}
}
%asminclude "library:c128/floats.asm"
%asminclude "library:c64/floats_funcs.asm"

View File

@ -171,6 +171,18 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
sub rndf() -> float {
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
rts
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"

View File

@ -37,14 +37,19 @@ cx16diskio {
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
%asm {{
; -- load a file into video ram
clc
internal_vload:
phx
pha
tya
tax
lda #1
ldy #0
bcc +
ldy #%00000010 ; headerless load mode
bne ++
+ ldy #0 ; normal load mode
+ lda #1
jsr c64.SETLFS
lda cx16.r0
ldy cx16.r0+1
@ -71,6 +76,15 @@ cx16diskio {
}}
}
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command BVLOAD "filename",device,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
%asm {{
sec
jmp vload.internal_vload
}}
}
; replacement function that makes use of fast block read capability of the X16
; use this in place of regular diskio.f_read()
@ -97,7 +111,7 @@ cx16diskio {
size = 255
if num_bytes<size
size = num_bytes
size = cx16.macptr(lsb(size), bufferpointer)
size = cx16.macptr(lsb(size), bufferpointer, false)
if_cs
goto byte_read_loop ; macptr block read not supported, do fallback loop
diskio.list_blocks += size

View File

@ -57,7 +57,8 @@ romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
@ -147,19 +148,21 @@ asmsub FREADUY (ubyte value @Y) {
}}
}
asmsub RND() clobbers(A,X,Y) {
%asm {{
lda #0
php
jsr cx16.entropy_get
plp
jmp RND_0
}}
}
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
sub rndf() -> float {
%asm {{
phx
lda #1
jsr RND_0
plx
rts
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}

View File

@ -366,7 +366,7 @@ _done
position2(x,y,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
color <<= gfx2.plot.shift4c[lsb(x) & 3] ; TODO with lookup table
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat lheight {
%asm {{
@ -561,7 +561,11 @@ _done
and #1
}}
if_nz {
cx16.r0L = lsb(x) & 7 ; xbits
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(320/8)
%asm {{
@ -571,7 +575,7 @@ _done
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
ply ; xbits
lda bits,y
ldy color
beq +
@ -608,7 +612,11 @@ _done
and #1
}}
if_nz {
cx16.r0L = lsb(x) & 7 ; xbits
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(640/8)
%asm {{
@ -618,7 +626,7 @@ _done
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
ply ; xbits
lda bits,y
ldy color
beq +
@ -635,7 +643,7 @@ _done
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits
color &= 3
color <<= shift4c[cx16.r2L]
color <<= shift4c[cx16.r2L] ; TODO with lookup table
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
@ -654,6 +662,93 @@ _done
}
}
sub pget(uword @zp x, uword y) -> ubyte {
when active_mode {
1 -> {
; lores monochrome
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.bits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
; TODO mode 2 and 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
}}
}
5 -> {
; hires monochrome
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.bits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
6 -> {
; hires 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
sta cx16.r0L
}}
cx16.r1L = lsb(x) & 3
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] ; TODO with lookup table
return cx16.r0L & 3
}
else -> return 0
}
}
sub position(uword @zp x, uword y) {
ubyte bank
when active_mode {

View File

@ -26,7 +26,7 @@ palette {
}
sub set_rgb(uword palette_words_ptr, uword num_colors) {
; 1 word per color entry (in little endian format as layed out in video memory, so $gb0r)
; 1 word per color entry (in little endian format as layed out in video memory, so $gb;$0r)
vera_palette_ptr = $fa00
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))

View File

@ -363,7 +363,7 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY) clobbers(A) -> ubyte @Pc, uword @XY
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
@ -393,9 +393,10 @@ asmsub kbdbuf_clear() {
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
%asm {{
pha ; save shape
sec
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
lda #1
pla ; get shape back
jmp cx16.mouse_config
}}
}

View File

@ -221,13 +221,18 @@ sub ceil(float value) -> float {
}}
}
sub rndf() -> float {
sub rndseedf(float seed) {
if seed>0
seed = -seed ; make sure fp seed is always negative
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
stx floats_store_reg
lda #<seed
ldy #>seed
jsr MOVFM ; load float into fac1
lda #-1
jsr floats.RND
ldx floats_store_reg
rts
}}
}

View File

@ -229,60 +229,32 @@ _divisor .word 0
.pend
randseed .proc
; -- reset the random seeds for the byte and word random generators
; arguments: uword seed in A/Y clobbers A
; (default starting values are: A=$2c Y=$9e)
sta randword._seed
sty randword._seed+1
clc
adc #14
sta randbyte._seed
rts
.pend
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
.pend
randword .proc
; -- 16 bit pseudo random number generator into AY
; rand64k ;Factors of 65535: 3 5 17 257
lda sr1+1
asl a
asl a
eor sr1+1
asl a
eor sr1+1
asl a
asl a
eor sr1+1
asl a
rol sr1 ;shift this left, "random" bit comes from low
rol sr1+1
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
lda sr2+1
asl a
eor sr2+1
asl a
asl a
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
rol sr2+1
lda sr1+1 ;can be left out
eor sr2+1 ;if you dont use
tay ;y as suggested
lda sr1 ;mix up lowbytes of SR1
eor sr2 ;and SR2 to combine both
; default seed = $00c2 $1137
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
inc x1
clc
x1=*+1
lda #$00 ;x1
c1=*+1
eor #$c2 ;c1
a1=*+1
eor #$11 ;a1
sta a1
b1=*+1
adc #$37 ;b1
sta b1
lsr a
eor a1
adc c1
sta c1
ldy b1
rts
sr1 .word $a55a
sr2 .word $7653
.pend
randbyte = randword ; -- 8 bit pseudo random number generator into A (by just reusing randword)
; ----------- optimized multiplications (stack) : ---------
stack_mul_byte_3 .proc

View File

@ -71,4 +71,28 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
asmsub rnd() -> ubyte @A {
%asm {{
jmp math.randbyte
}}
}
asmsub rndw() -> uword @AY {
%asm {{
jmp math.randword
}}
}
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
%asm {{
sta math.randword.x1
sty math.randword.c1
lda cx16.r0L
sta math.randword.a1
lda cx16.r0H
sta math.randword.b1
rts
}}
}
}

View File

@ -244,24 +244,6 @@ func_sqrt16_into_A .proc
rts
.pend
func_rnd_stack .proc
; -- put a random ubyte on the estack
jsr math.randbyte
sta P8ESTACK_LO,x
dex
rts
.pend
func_rndw_stack .proc
; -- put a random uword on the estack
jsr math.randword
sta P8ESTACK_LO,x
tya
sta P8ESTACK_HI,x
dex
rts
.pend
func_sort_ub .proc
; 8bit unsigned sort

View File

@ -224,6 +224,29 @@ _done rts
}}
}
asmsub lowerchar(ubyte char @A) -> ubyte @A {
%asm {{
and #$7f
cmp #97
bcc +
cmp #123
bcs +
and #%11011111
+ rts
}}
}
asmsub upperchar(ubyte char @A) -> ubyte @A {
%asm {{
cmp #65
bcc +
cmp #91
bcs +
ora #%00100000
+ rts
}}
}
sub startswith(str st, str prefix) -> bool {
ubyte prefix_len = length(prefix)
ubyte str_len = length(st)

View File

@ -195,8 +195,8 @@ sub str2uword(str string) -> uword {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
loadm.w r0,conv.str2uword.string
%ir {{
loadm.w r65500,conv.str2uword.string
syscall 11
return
}}
@ -206,8 +206,8 @@ sub str2word(str string) -> word {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
loadm.w r0,conv.str2word.string
%ir {{
loadm.w r65500,conv.str2word.string
syscall 12
return
}}

View File

@ -9,15 +9,15 @@ floats {
sub print_f(float value) {
; ---- prints the floating point value (without a newline).
%asm {{
loadm.f fr0,floats.print_f.value
%ir {{
loadm.f fr65500,floats.print_f.value
syscall 25
return
}}
}
sub pow(float value, float power) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.pow.value
loadm.f fr1,floats.pow.power
fpow.f fr0,fr1
@ -26,7 +26,7 @@ sub pow(float value, float power) -> float {
}
sub fabs(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.fabs.value
fabs.f fr0,fr0
return
@ -34,7 +34,7 @@ sub fabs(float value) -> float {
}
sub sin(float angle) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.sin.angle
fsin.f fr0,fr0
return
@ -42,7 +42,7 @@ sub sin(float angle) -> float {
}
sub cos(float angle) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.cos.angle
fcos.f fr0,fr0
return
@ -50,7 +50,7 @@ sub cos(float angle) -> float {
}
sub tan(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.tan.value
ftan.f fr0,fr0
return
@ -58,7 +58,7 @@ sub tan(float value) -> float {
}
sub atan(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.atan.value
fatan.f fr0,fr0
return
@ -66,7 +66,7 @@ sub atan(float value) -> float {
}
sub ln(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.ln.value
fln.f fr0,fr0
return
@ -74,7 +74,7 @@ sub ln(float value) -> float {
}
sub log2(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.log2.value
flog.f fr0,fr0
return
@ -82,7 +82,7 @@ sub log2(float value) -> float {
}
sub sqrt(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.sqrt.value
sqrt.f fr0,fr0
return
@ -100,7 +100,7 @@ sub deg(float angle) -> float {
}
sub round(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.round.value
fround.f fr0,fr0
return
@ -108,7 +108,7 @@ sub round(float value) -> float {
}
sub floor(float value) -> float {
%asm {{
%ir {{
loadm.f fr0,floats.floor.value
ffloor.f fr0,fr0
return
@ -117,7 +117,7 @@ sub floor(float value) -> float {
sub ceil(float value) -> float {
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
%asm {{
%ir {{
loadm.f fr0,floats.ceil.value
fceil.f fr0,fr0
return
@ -125,9 +125,17 @@ sub ceil(float value) -> float {
}
sub rndf() -> float {
%asm {{
rnd.f fr0
%ir {{
syscall 35
return
}}
}
sub rndseedf(float seed) {
%ir {{
loadm.f fr65500,floats.rndseedf.seed
syscall 32
}}
}
}

View File

@ -159,4 +159,27 @@ math {
return costab[radians] as byte
}
sub rnd() -> ubyte {
%ir {{
syscall 33
return
}}
}
sub rndw() -> uword {
%ir {{
syscall 34
return
}}
}
sub rndseed(uword seed1, uword seed2) {
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
%ir {{
loadm.w r65500,math.rndseed.seed1
loadm.w r65501,math.rndseed.seed2
syscall 31
return
}}
}
}

View File

@ -1,53 +1,6 @@
; Internal library routines - always included by the compiler
%import textio
prog8_lib {
%option force_output
sub string_contains(ubyte needle, str haystack) -> ubyte {
repeat {
if @(haystack)==0
return false
if @(haystack)==needle
return true
haystack++
}
}
sub bytearray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
haystack_ptr--
while num_elements {
if haystack_ptr[num_elements]==needle
return true
num_elements--
}
return false
}
sub wordarray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
haystack_ptr += (num_elements-1) * 2
while num_elements {
if peekw(haystack_ptr)==needle
return true
haystack_ptr -= 2
num_elements--
}
return false
}
sub string_compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
loadm.w r0,prog8_lib.string_compare.st1
loadm.w r1,prog8_lib.string_compare.st2
syscall 29
return
}}
}
}

View File

@ -83,7 +83,12 @@ string {
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
return prog8_lib.string_compare(st1, st2)
%ir {{
loadm.w r65500,string.compare.st1
loadm.w r65501,string.compare.st2
syscall 29
return
}}
}
sub lower(str st) -> ubyte {
@ -114,6 +119,18 @@ string {
}
}
sub lowerchar(ubyte char) -> ubyte {
if char >= 'A' and char <= 'Z'
char |= %00100000
return char
}
sub upperchar(ubyte char) -> ubyte {
if char >= 'a' and char <= 'z'
char &= %11011111
return char
}
sub startswith(str st, str prefix) -> bool {
ubyte prefix_len = length(prefix)
ubyte str_len = length(st)

View File

@ -7,22 +7,22 @@ sys {
sub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
%ir {{
syscall 0
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
%asm {{
loadm.w r0,sys.wait.jiffies
%ir {{
loadm.w r65500,sys.wait.jiffies
syscall 13
}}
}
sub waitvsync() {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
%asm {{
%ir {{
syscall 14
}}
}
@ -61,52 +61,52 @@ sys {
sub exit(ubyte returnvalue) {
; -- immediately exit the program with a return code in the A register
%asm {{
loadm.b r0,sys.exit.returnvalue
%ir {{
loadm.b r65500,sys.exit.returnvalue
syscall 1
}}
}
sub set_carry() {
%asm {{
%ir {{
sec
}}
}
sub clear_carry() {
%asm {{
%ir {{
clc
}}
}
sub gfx_enable(ubyte mode) {
%asm {{
loadm.b r0,sys.gfx_enable.mode
%ir {{
loadm.b r65500,sys.gfx_enable.mode
syscall 8
}}
}
sub gfx_clear(ubyte color) {
%asm {{
loadm.b r0,sys.gfx_clear.color
%ir {{
loadm.b r65500,sys.gfx_clear.color
syscall 9
}}
}
sub gfx_plot(uword xx, uword yy, ubyte color) {
%asm {{
loadm.w r0,sys.gfx_plot.xx
loadm.w r1,sys.gfx_plot.yy
loadm.b r2,sys.gfx_plot.color
%ir {{
loadm.w r65500,sys.gfx_plot.xx
loadm.w r65501,sys.gfx_plot.yy
loadm.b r65502,sys.gfx_plot.color
syscall 10
}}
}
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
%asm {{
loadm.w r0,sys.gfx_getpixel.xx
loadm.w r1,sys.gfx_getpixel.yy
%ir {{
loadm.w r65500,sys.gfx_getpixel.xx
loadm.w r65501,sys.gfx_getpixel.yy
syscall 30
return
}}

View File

@ -6,8 +6,8 @@ txt {
sub clear_screen() {
str @shared sequence = "\x1b[2J\x1B[H"
%asm {{
load.w r0,txt.clear_screen.sequence
%ir {{
load.w r65500,txt.clear_screen.sequence
syscall 3
}}
}
@ -29,15 +29,15 @@ sub uppercase() {
}
sub chrout(ubyte char) {
%asm {{
loadm.b r0,txt.chrout.char
%ir {{
loadm.b r65500,txt.chrout.char
syscall 2
}}
}
sub print (str text) {
%asm {{
loadm.w r0,txt.print.text
%ir {{
loadm.w r65500,txt.print.text
syscall 3
}}
}
@ -113,11 +113,26 @@ sub print_w (word value) {
sub input_chars (uword buffer) -> ubyte {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
loadm.w r0,txt.input_chars.buffer
%ir {{
loadm.w r65500,txt.input_chars.buffer
syscall 6
return
}}
}
sub plot (ubyte col, ubyte row) {
; use ANSI escape sequence to position the cursor
txt.chrout(27)
txt.chrout('[')
txt.print_ub(row)
txt.chrout(';')
txt.print_ub(col)
txt.chrout('H')
}
sub setchr (ubyte col, ubyte row, ubyte char) {
plot(col, row)
txt.chrout(char)
}
}

View File

@ -1 +1 @@
8.5
8.7

View File

@ -2,16 +2,20 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.code.core.*
import prog8.code.core.CbmPrgLauncherType
import prog8.code.core.toHex
import prog8.code.target.*
import prog8.code.target.virtual.VirtualMachineDefinition
import prog8.codegen.virtual.VmCodeGen
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import java.io.File
import java.nio.file.*
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
import java.nio.file.WatchKey
import java.time.LocalDateTime
import kotlin.io.path.Path
import kotlin.system.exitProcess
@ -47,7 +51,7 @@ private fun compileMain(args: Array<String>): Boolean {
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a p8-virt or p8-ir listing in the VM instead")
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)")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
@ -142,7 +146,7 @@ private fun compileMain(args: Array<String>): Boolean {
for (importedFile in allImportedFiles) {
print(" ")
println(importedFile)
val watchDir = importedFile.parent ?: Path.of("")
val watchDir = importedFile.parent ?: Path("")
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
@ -244,28 +248,9 @@ private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
return result
}
fun runVm(listingFilename: String): Boolean {
if(listingFilename.endsWith(".p8ir")) {
val withoutSuffix = listingFilename.substring(0, listingFilename.length-5)
val compiled = VmCodeGen.compileIR(withoutSuffix)
if (!compiled.assemble(CompilationOptions( // these are just dummy options, the actual options are inside the .p8ir file itself:
OutputType.PRG,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
floats = true,
noSysInit = true,
compTarget = VMTarget(),
loadAddress = VMTarget().machine.PROGRAM_LOAD_ADDRESS
))
) {
return false
}
val vmdef = VirtualMachineDefinition()
vmdef.launchEmulator(0, Paths.get(withoutSuffix))
return true
}
fun runVm(irFilename: String): Boolean {
val irFile = Path(irFilename)
val vmdef = VirtualMachineDefinition()
vmdef.launchEmulator(0, Paths.get(listingFilename))
vmdef.launchEmulator(0, irFile)
return true
}

View File

@ -14,6 +14,7 @@ import prog8.ast.walk.IAstVisitor
import prog8.code.SymbolTable
import prog8.code.core.*
import prog8.code.target.*
import prog8.codegen.vm.VmCodeGen
import prog8.compiler.astprocessing.*
import prog8.optimizer.*
import prog8.parser.ParseError
@ -360,7 +361,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(compTarget)
val optsDone1 = program.simplifyExpressions(errors, compTarget)
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
val optsDone4 = program.inlineSubroutines()
@ -393,7 +394,7 @@ private fun createAssemblyAndAssemble(program: Program,
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
val symbolTable = SymbolTableMaker().makeFrom(program)
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
@ -455,7 +456,7 @@ internal fun asmGeneratorFor(program: Program,
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
if (options.compTarget.name == VMTarget.NAME) {
val intermediateAst = IntermediateAstMaker(program).transform()
return prog8.codegen.virtual.VmCodeGen(intermediateAst, symbolTable, options, errors)
return VmCodeGen(intermediateAst, symbolTable, options, errors)
}
}

View File

@ -7,7 +7,6 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
import prog8.compiler.InplaceModifyingBuiltinFunctions
import prog8.compiler.builtinFunctionReturnType
@ -252,16 +251,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(inlineAssembly: InlineAssembly) {
val assembly = inlineAssembly.assembly
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
if (" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
)
count++
} else {
if(" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly)
count++
}
if(inlineAssembly.hasReturnOrRts(compilerOptions.compTarget))
count++
}
}
@ -319,12 +310,12 @@ internal class AstChecker(private val program: Program,
}
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes)
err("parameter '${param.first.name}' should be (u)word (an address) or str")
}
else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE)
err("parameter '${param.first.name}' should be ubyte")
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
err("parameter '${param.first.name}' should be bool or ubyte")
}
}
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
@ -334,12 +325,12 @@ internal class AstChecker(private val program: Program,
}
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes)
err("return type #${index + 1} should be (u)word/address")
}
else if(pair.second.statusflag!=null) {
if (pair.first != DataType.UBYTE)
err("return type #${index + 1} should be ubyte")
if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
err("return type #${index + 1} should be bool or ubyte")
}
}
@ -1005,7 +996,7 @@ internal class AstChecker(private val program: Program,
// It's not (yet) possible to handle these multiple return values because assignments
// are only to a single unique target at the same time.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit,
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }

View File

@ -12,7 +12,6 @@ import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@ -170,15 +169,8 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
}
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
val instructions =
if(compTarget.name == VMTarget.NAME)
listOf(" return", "\treturn", " jump", "\tjump")
else
listOf(" rti", "\trti", " rts", "\trts", " jmp", "\tjmp", " bra", "\tbra")
return statements
.asSequence()
.filterIsInstance<InlineAssembly>()
.any {
instructions.any { instr->instr in it.assembly }
}
.any { it.hasReturnOrRts(compTarget) }
}

View File

@ -150,6 +150,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
private fun visitFunctionCall(call: IFunctionCall) {
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
val target = call.target.targetStatement(program)
if(target==null) {
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
return
}
}
when (val target = call.target.targetStatement(program)) {
is Subroutine -> {
val expectedNumberOfArgs: Int = target.parameters.size

View File

@ -18,9 +18,8 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(parent !is VarDecl) {
// TODO move this / remove this, and make the codegen better instead.
// If the expression is pointervar[idx] where pointervar is uword and not a real array,
// replace it by a @(pointervar+idx) expression.
if(options.compTarget.name == VMTarget.NAME)
return noModifications // vm codegen deals correctly with all cases
// Don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl)
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
@ -29,15 +28,16 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
}
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(options.compTarget.name==VMTarget.NAME)
return noModifications // vm codegen deals correctly with all cases
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
// into account the case where the index value is a word type.
// The replacement here is to fix missing cases in the 6502 codegen.
// TODO make the 6502 codegen better so this work around can be removed
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
if(parent is AssignTarget) {
val assignment = parent.parent as? Assignment
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
// ONLY for a constant assignment, or direct variable assignment, the codegen contains correct optimized code.
// the codegen contains correct optimized code ONLY for a constant assignment, or direct variable assignment.
return noModifications
}
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.

View File

@ -135,15 +135,20 @@ internal class BeforeAsmAstChanger(val program: Program,
val mods = mutableListOf<IAstModification>()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
// and if an assembly block doesn't contain a rts/rti.
if (!subroutine.isAsmSubroutine) {
if(subroutine.statements.isEmpty() ||
(!subroutine.hasRtsInAsm(options.compTarget)
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine
&& subroutine.statements.last() !is Return)) {
if(subroutine.isEmpty()) {
val returnStmt = Return(null, subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
} else {
val last = subroutine.statements.last()
if((last !is InlineAssembly || !last.hasReturnOrRts(options.compTarget)) && last !is Return) {
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
if(lastStatement !is Return) {
val returnStmt = Return(null, subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
}
}
@ -167,8 +172,18 @@ internal class BeforeAsmAstChanger(val program: Program,
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
// make sure the NOT INLINED asm subroutine actually has a rts at the end
// (non-asm routines get a Return statement as needed, above)
val instruction = if(options.compTarget.name==VMTarget.NAME) " return\n" else " rts\n"
mods += IAstModification.InsertLast(InlineAssembly(instruction, Position.DUMMY), subroutine)
mods += if(options.compTarget.name==VMTarget.NAME)
IAstModification.InsertLast(InlineAssembly(" return\n", true, Position.DUMMY), subroutine)
else
IAstModification.InsertLast(InlineAssembly(" rts\n", false, Position.DUMMY), subroutine)
}
}
if(subroutine.isNotEmpty() && subroutine.statements.last() is Return) {
// maybe the last return can be removed because there is a fall-through prevention above it
val lastStatementBefore = subroutine.statements.reversed().drop(1).firstOrNull { it !is Subroutine }
if(lastStatementBefore is Return) {
mods += IAstModification.Remove(subroutine.statements.last(), subroutine)
}
}

View File

@ -129,4 +129,18 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="<<" || expr.operator==">>") {
val shifts = expr.right.constValue(program)
if(shifts!=null) {
val dt = expr.left.inferType(program)
if(dt.istype(DataType.UBYTE) && shifts.number>=8.0)
errors.warn("shift always results in 0", expr.position)
if(dt.istype(DataType.UWORD) && shifts.number>=16.0)
errors.warn("shift always results in 0", expr.position)
}
}
return noModifications
}
}

View File

@ -10,8 +10,7 @@ import prog8.code.core.IErrorReporter
import prog8.code.core.Position
internal class CodeDesugarer(val program: Program,
private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@ -23,6 +22,7 @@ internal class CodeDesugarer(val program: Program,
// - replace while and do-until loops by just jumps.
// - replace peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
@ -135,4 +135,32 @@ _after:
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
// 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.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))
}
}
}
return noModifications
}
}

View File

@ -197,7 +197,7 @@ class IntermediateAstMaker(val program: Program) {
"%asminclude" -> {
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
val assembly = result.getOrElse { throw it }
PtInlineAssembly(assembly, directive.position)
PtInlineAssembly(assembly.trimEnd().trimStart('\r', '\n'), false, directive.position)
}
else -> {
// other directives don't output any code (but could end up in option flags somewhere else)
@ -251,8 +251,10 @@ class IntermediateAstMaker(val program: Program) {
return ifelse
}
private fun transform(srcNode: InlineAssembly): PtInlineAssembly =
PtInlineAssembly(srcNode.assembly, srcNode.position)
private fun transform(srcNode: InlineAssembly): PtInlineAssembly {
val assembly = srcNode.assembly.trimEnd().trimStart('\r', '\n')
return PtInlineAssembly(assembly, srcNode.isIR, srcNode.position)
}
private fun transform(srcJump: Jump): PtJump {
val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null
@ -306,13 +308,26 @@ class IntermediateAstMaker(val program: Program) {
sub.parameters.forEach { it.first.parent=sub }
if(srcSub.asmAddress==null) {
var combinedAsm = ""
for (asm in srcSub.statements)
combinedAsm += (asm as InlineAssembly).assembly + "\n"
if(combinedAsm.isNotEmpty())
sub.add(PtInlineAssembly(combinedAsm, srcSub.statements[0].position))
else
sub.add(PtInlineAssembly("", srcSub.position))
var combinedTrueAsm = ""
var combinedIrAsm = ""
for (asm in srcSub.statements) {
asm as InlineAssembly
if(asm.isIR)
combinedIrAsm += asm.assembly + "\n"
else
combinedTrueAsm += asm.assembly + "\n"
}
if(combinedTrueAsm.isNotEmpty()) {
combinedTrueAsm = combinedTrueAsm.trimEnd().trimStart('\r', '\n')
sub.add(PtInlineAssembly(combinedTrueAsm, false, srcSub.statements[0].position))
}
if(combinedIrAsm.isNotEmpty()) {
combinedIrAsm = combinedIrAsm.trimEnd().trimStart('\r', '\n')
sub.add(PtInlineAssembly(combinedIrAsm, true, srcSub.statements[0].position))
}
if(combinedIrAsm.isEmpty() && combinedTrueAsm.isEmpty())
sub.add(PtInlineAssembly("", true, srcSub.position))
}
return sub
@ -320,9 +335,12 @@ class IntermediateAstMaker(val program: Program) {
private fun transformSub(srcSub: Subroutine): PtSub {
val (vardecls, statements) = srcSub.statements.partition { it is VarDecl }
var returntype = srcSub.returntypes.singleOrNull()
if(returntype==DataType.STR)
returntype=DataType.UWORD // if a sub returns 'str', replace with uword. Intermediate AST and I.R. don't contain 'str' datatype anymore.
val sub = PtSub(srcSub.name,
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) },
srcSub.returntypes.singleOrNull(),
returntype,
srcSub.inline,
srcSub.position)
sub.parameters.forEach { it.parent=sub }

View File

@ -1,13 +1,11 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.compiler.BuiltinFunctions
internal class StatementReorderer(val program: Program,
val errors: IErrorReporter,
@ -195,54 +193,6 @@ internal class StatementReorderer(val program: Program,
&& maySwapOperandOrder(expr))
return listOf(IAstModification.SwapOperands(expr))
// when using a simple bit shift and assigning it to a variable of a different type,
// try to make the bit shifting 'wide enough' to fall into the variable's type.
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
if(expr.operator=="<<" || expr.operator==">>") {
val leftDt = expr.left.inferType(program)
when (parent) {
is Assignment -> {
val targetDt = parent.target.inferType(program)
if(leftDt != targetDt) {
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is VarDecl -> {
if(leftDt isnot parent.datatype) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is IFunctionCall -> {
val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(callee.name)
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
if(leftDt isAssignableTo type) {
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
}
else -> throw FatalAstException("weird callee")
}
}
else -> return noModifications
}
}
return noModifications
}

View File

@ -7,6 +7,8 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.*
import prog8.code.core.ArrayDatatypes
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Position
import java.util.*
@ -14,10 +16,12 @@ internal class SymbolTableMaker: IAstVisitor {
private val st = SymbolTable()
private val scopestack = Stack<StNode>()
private var dontReinitGlobals = false
fun makeFrom(program: Program): SymbolTable {
fun makeFrom(program: Program, options: CompilationOptions): SymbolTable {
scopestack.clear()
st.children.clear()
dontReinitGlobals = options.dontReinitGlobals
this.visit(program)
program.builtinFunctions.names.forEach {
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
@ -38,7 +42,7 @@ internal class SymbolTableMaker: IAstVisitor {
override fun visit(subroutine: Subroutine) {
if(subroutine.asmAddress!=null) {
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmParameterRegisters, subroutine.position)
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmReturnvaluesRegisters, subroutine.position)
scopestack.peek().add(node)
// st.origAstLinks[subroutine] = node
} else {
@ -57,7 +61,7 @@ internal class SymbolTableMaker: IAstVisitor {
val node =
when(decl.type) {
VarDeclType.VAR -> {
val initialNumeric = (decl.value as? NumericLiteral)?.number
var initialNumeric = (decl.value as? NumericLiteral)?.number
val initialStringLit = decl.value as? StringLiteral
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
val initialArrayLit = decl.value as? ArrayLiteral
@ -71,7 +75,15 @@ internal class SymbolTableMaker: IAstVisitor {
initialStringLit.value.length+1 // include the terminating 0-byte
else
null
StStaticVariable(decl.name, decl.datatype, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
val bss = if(decl.datatype==DataType.STR)
false
else if(decl.isArray)
initialArray.isNullOrEmpty()
else {
if(dontReinitGlobals) initialNumeric = initialNumeric ?: 0.0
initialNumeric == null
}
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
}
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
VarDeclType.MEMORY -> {
@ -104,4 +116,16 @@ internal class SymbolTableMaker: IAstVisitor {
scopestack.peek().add(node)
// st.origAstLinks[label] = node
}
override fun visit(bfc: BuiltinFunctionCall) {
if(bfc.name=="memory") {
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
val name = (bfc.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (bfc.args[1] as NumericLiteral).number.toUInt()
val align = (bfc.args[2] as NumericLiteral).number.toUInt()
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, bfc.position))
}
super.visit(bfc)
}
}

View File

@ -5,6 +5,7 @@ import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.ByteDatatypes
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
@ -82,7 +83,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(mismatch>=0) {
val actual = argtypes[mismatch]
val expected = consideredParamTypes[mismatch]
return if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
return if(actual==DataType.BOOL && expected in ByteDatatypes)
null // a bool is just 1 or 0.
else if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
null // specifying a 1 or 0 as a BOOL is okay
else
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
@ -91,6 +94,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(target.asmReturnvaluesRegisters.size>1) {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if (call !is FunctionCallStatement) {
val checkParent =
@ -99,7 +106,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
else
parent
if (checkParent !is Assignment && checkParent !is VarDecl) {
return Pair("can't use subroutine call that returns multiple return values here", call.position)
val (returnRegisters, _) = target.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.size>1) {
return Pair("can't use subroutine call that returns multiple return values here", call.position)
}
}
}
}

View File

@ -44,18 +44,18 @@ class TestBuiltinFunctions: FunSpec({
conv.returns.reg shouldBe RegisterOrPair.A
}
test("not-pure func with fixed type") {
val func = BuiltinFunctions.getValue("rnd")
func.name shouldBe "rnd"
func.parameters.size shouldBe 0
test("not-pure func with varying result value type") {
val func = BuiltinFunctions.getValue("cmp")
func.name shouldBe "cmp"
func.parameters.size shouldBe 2
func.pure shouldBe false
func.returnType shouldBe DataType.UBYTE
func.returnType shouldBe null
val conv = func.callConvention(emptyList())
conv.params.size shouldBe 0
conv.returns.dt shouldBe DataType.UBYTE
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UWORD))
conv.params.size shouldBe 2
conv.returns.dt shouldBe null
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.A
conv.returns.reg shouldBe null
}
test("func without return type") {

View File

@ -3,18 +3,15 @@ package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldNotBe
import prog8.code.core.ICompilationTarget
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.*
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8tests.helpers.assumeDirectory
import prog8tests.helpers.cartesianProduct
import prog8tests.helpers.outputDir
import prog8tests.helpers.workingDir
import prog8tests.helpers.*
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
import kotlin.io.path.readText
/**
@ -47,8 +44,11 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
val searchIn = mutableListOf(examplesDir)
if (target is Cx16Target) {
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
when (target) {
is Cx16Target -> searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
is VMTarget -> searchIn.add(0, assumeDirectory(examplesDir, "vm"))
is C128Target -> searchIn.add(0, assumeDirectory(examplesDir, "c128"))
is AtariTarget -> searchIn.add(0, assumeDirectory(examplesDir, "atari"))
}
val filepath = searchIn.asSequence()
.map { it.resolve("$source.p8") }
@ -168,3 +168,24 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
}
}
})
class TestCompilerOnExamplesVirtual: FunSpec({
val onlyVirtual = listOf(
"bouncegfx",
"bsieve",
"pixelshader",
"sincos",
"textelite"
)
onlyVirtual.forEach {
val target = VMTarget()
val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) {
val src = filepath.readText()
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null
compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null
}
}
})

View File

@ -87,7 +87,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
val sourcedirs = listOf("${fixturesDir}")
val sourcedirs = listOf("$fixturesDir")
compileFile(filepath.fileName, sourcedirs) shouldNotBe null
}

View File

@ -0,0 +1,40 @@
package prog8tests
import io.kotest.core.spec.style.FunSpec
import prog8.code.target.VMTarget
import kotlin.io.path.deleteExisting
import kotlin.io.path.writeText
class TestLaunchEmu: FunSpec({
test("test launch virtualmachine via target") {
val target = VMTarget()
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
tmpfile.writeText("""<PROGRAM NAME=test>
<OPTIONS>
</OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<VARIABLES>
</VARIABLES>
<MEMORYMAPPEDVARIABLES>
</MEMORYMAPPEDVARIABLES>
<MEMORYSLABS>
</MEMORYSLABS>
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]>
</BLOCK>
</PROGRAM>
""")
target.machine.launchEmulator(0, tmpfile)
tmpfile.deleteExisting()
}
})

View File

@ -258,8 +258,8 @@ class TestSubroutines: FunSpec({
sub start() {
label()
label(1)
void rnd()
void rnd(1)
void cmp(22,44)
void cmp(11)
}
}
"""

View File

@ -78,10 +78,10 @@ private fun makeSt(): SymbolTable {
block1.add(sub12)
block1.add(StConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY))
block1.add(StConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY))
sub11.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub11.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub12.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub12.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub11.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub11.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub12.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub12.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
val block2 = StNode("block2", StNodeType.BLOCK, Position.DUMMY)
val sub21 = StNode("sub1", StNodeType.SUBROUTINE, Position.DUMMY)

View File

@ -14,7 +14,6 @@ import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@ -31,7 +30,7 @@ class TestTypecasts: FunSpec({
}
}"""
val errors = ErrorReporterForTests()
val result = compileText(VMTarget(), false, text, writeAssembly = false, errors=errors)
val result = compileText(C64Target(), false, text, writeAssembly = false, errors=errors)
result shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "type mismatch, was: FLOAT expected one of: [UBYTE, BYTE, UWORD, WORD]"
@ -627,9 +626,9 @@ main {
ubyte @shared wordNr2 = (interlaced >= ${'$'}33) + (interlaced >= ${'$'}66) + (interlaced >= ${'$'}99) + (interlaced >= ${'$'}CC)
}
}"""
val result = compileText(VMTarget(), false, text, writeAssembly = true)!!
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 14
stmts.size shouldBeGreaterThan 10
}
test("word to byte casts") {
@ -920,4 +919,19 @@ main {
}"""
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
}
test("memory reads byte into word variable") {
val text = """
main {
sub start() {
uword @shared ww
uword address = $1000
ww = @(address+100)
ww = @(address+1000)
cx16.r0 = @(address+100)
cx16.r0 = @(address+1000)
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
})

View File

@ -2,6 +2,7 @@
%import floats
%import string
%import syslib
%import math
%import test_stack
%zeropage basicsafe
@ -281,17 +282,17 @@ main {
txt.nl()
ub = rnd()
ub = math.rnd()
txt.print_ub(ub)
txt.nl()
ub = zero+rnd()*1+zero
ub = zero+math.rnd()*1+zero
txt.print_ub(ub)
txt.nl()
uw = rndw()
uw = math.rndw()
txt.print_uw(uw)
txt.nl()
uw = zero+rndw()*1+zero
uw = zero+math.rndw()*1+zero
txt.print_uw(uw)
txt.nl()

View File

@ -10,14 +10,13 @@ import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.VarDecl
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.compiler.printProgram
import prog8tests.helpers.compileText
class TestVarious: FunSpec({
test("symbol names in inline assembly blocks") {
val names1 = InlineAssembly("""
""", Position.DUMMY).names
""", false, Position.DUMMY).names
names1 shouldBe emptySet()
val names2 = InlineAssembly("""
@ -29,7 +28,7 @@ label2:
; also not these
;; ...or these
// valid words 123456
""", Position.DUMMY).names
""", false, Position.DUMMY).names
names2 shouldBe setOf("label", "lda", "sta", "ea", "value", "label2", "othervalue", "valid", "words")
}
@ -100,7 +99,6 @@ main {
}
}"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
printProgram(result.program)
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 6
val name1 = stmts[0] as VarDecl
@ -114,5 +112,22 @@ main {
(name2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xx1xx2"
(rept2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xyzxyzxyzxyz"
}
test("pointervariable indexing allowed with >255") {
val src="""
main {
sub start() {
uword pointer = ${'$'}2000
@(pointer+${'$'}1000) = 123
ubyte @shared ub = @(pointer+${'$'}1000)
pointer[${'$'}1000] = 99
ub = pointer[${'$'}1000]
uword index = ${'$'}1000
pointer[index] = 55
ub = pointer[index]
}
}"""
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
})

View File

@ -0,0 +1,93 @@
package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldNotBe
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.compileText
class TestArrayInplaceAssign: FunSpec({
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
val text = """
main {
sub start() {
byte[5] array
byte bb
array[1] = -bb
}
}
"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (integer types)") {
val text = """
main {
byte[10] foo
ubyte[10] foou
word[10] foow
uword[10] foowu
sub start() {
foo[1] = 42
foo[1] = -foo[1]
foow[1] = 4242
foow[1] = -foow[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (float type) vm target") {
val text = """
%import floats
main {
float[10] flt
sub start() {
flt[1] = 42.42
flt[1] = -flt[1]
}
}"""
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
// TODO implement this in 6502 codegen and re-enable test
xtest("array in-place negation (float type) 6502 target") {
val text = """
%import floats
main {
float[10] flt
sub start() {
flt[1] = 42.42
flt[1] = -flt[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place invert") {
val text = """
main {
ubyte[10] foo
uword[10] foow
sub start() {
foo[1] = 42
foo[1] = ~foo[1]
foow[1] = 4242
foow[1] = ~foow[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
})

View File

@ -71,7 +71,7 @@ class TestAsmGenSymbols: StringSpec({
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
options.compTarget.machine.zeropage = C64Zeropage(options)
val st = SymbolTableMaker().makeFrom(program)
val st = SymbolTableMaker().makeFrom(program, options)
return AsmGen(program, st, options, errors)
}

View File

@ -1,15 +1,10 @@
package prog8tests.helpers
import prog8.ast.Program
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.c64.C64Zeropage
import prog8.codegen.cpu6502.AsmGen
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.astprocessing.SymbolTableMaker
import prog8.compiler.compileProgram
import prog8.compiler.determineProgramLoadAddress
import java.nio.file.Path
import kotlin.io.path.name
@ -22,6 +17,7 @@ internal fun compileFile(
outputDir: Path = prog8tests.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
keepIR: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult? {
val filepath = fileDir.resolve(fileName)
@ -36,7 +32,7 @@ internal fun compileFile(
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
keepIR = false,
keepIR = keepIR,
platform.name,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -57,30 +53,12 @@ internal fun compileText(
sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
keepIR: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult? {
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
filePath.toFile().writeText(sourceText)
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
}
internal fun generateAssembly(
program: Program,
options: CompilationOptions? = null
): IAssemblyProgram? {
val coptions = options ?: CompilationOptions(OutputType.RAW, CbmPrgLauncherType.BASIC, ZeropageType.DONTUSE, emptyList(),
floats = true,
noSysInit = true,
compTarget = C64Target(),
loadAddress = 0u, outputDir = outputDir)
coptions.compTarget.machine.zeropage = C64Zeropage(coptions)
val st = SymbolTableMaker().makeFrom(program)
val errors = ErrorReporterForTests()
determineProgramLoadAddress(program, coptions, errors)
errors.report()
val asmgen = AsmGen(program, st, coptions, errors)
errors.report()
return asmgen.compileToAssembly()
return compileFile(platform, optimize, filePath.parent, filePath.name,
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr, keepIR=keepIR)
}

View File

@ -57,7 +57,7 @@ class PathsHelpersTests: FunSpec({
test("on existing directory") {
shouldThrow<java.lang.AssertionError> {
assumeNotExists("${fixturesDir}")
assumeNotExists("$fixturesDir")
}
}
}
@ -157,13 +157,13 @@ class PathsHelpersTests: FunSpec({
context("WithStringAndStringArgs") {
test("on non-existing path") {
shouldThrow<AssertionError> {
assumeDirectory("${fixturesDir}", "i_do_not_exist")
assumeDirectory("$fixturesDir", "i_do_not_exist")
}
}
test("on existing file") {
shouldThrow<AssertionError> {
assumeDirectory("${fixturesDir}", "ast_simple_main.p8")
assumeDirectory("$fixturesDir", "ast_simple_main.p8")
}
}
@ -178,13 +178,13 @@ class PathsHelpersTests: FunSpec({
context("WithStringAndPathArgs") {
test("on non-existing path") {
shouldThrow<AssertionError> {
assumeDirectory("${fixturesDir}", Path("i_do_not_exist"))
assumeDirectory("$fixturesDir", Path("i_do_not_exist"))
}
}
test("on existing file") {
shouldThrow<AssertionError> {
assumeDirectory("${fixturesDir}", Path("ast_simple_main.p8"))
assumeDirectory("$fixturesDir", Path("ast_simple_main.p8"))
}
}
@ -240,7 +240,7 @@ class PathsHelpersTests: FunSpec({
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile("${fixturesDir}")
assumeReadableFile("$fixturesDir")
}
}
}
@ -289,7 +289,7 @@ class PathsHelpersTests: FunSpec({
context("WithStringAndStringArgs") {
test("on non-existing path") {
shouldThrow<java.lang.AssertionError> {
assumeReadableFile("${fixturesDir}", "i_do_not_exist")
assumeReadableFile("$fixturesDir", "i_do_not_exist")
}
}
@ -301,7 +301,7 @@ class PathsHelpersTests: FunSpec({
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile("${fixturesDir}", "..")
assumeReadableFile("$fixturesDir", "..")
}
}
}

View File

@ -0,0 +1,245 @@
package prog8tests.vm
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.expressions.BuiltinFunctionCall
import prog8.ast.statements.Assignment
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.vm.VmRunner
import prog8tests.helpers.compileText
import kotlin.io.path.readText
class TestCompilerVirtual: FunSpec({
test("compile virtual: any all sort reverse builtin funcs") {
val src = """
main {
sub start() {
uword[] words = [1111,2222,0,4444,3333]
ubyte result = all(words)
result++
result = any(words)
result++
sort(words)
reverse(words)
}
}"""
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
}
test("compile virtual: array with pointers") {
val src = """
main {
sub start() {
str localstr = "hello"
ubyte[] otherarray = [1,2,3]
uword[] words = [1111,2222,"three",&localstr,&otherarray]
uword @shared zz = &words
ubyte result = 2222 in words
zz = words[2]
zz++
zz = words[3]
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
}
test("compile virtual: str args and return type") {
val src = """
main {
sub start() {
sub testsub(str s1) -> str {
return "result"
}
uword result = testsub("arg")
}
}"""
val target = VMTarget()
var result = compileText(target, false, src, writeAssembly = true)!!
var virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
result = compileText(target, true, src, writeAssembly = true)!!
virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
}
test("compile virtual: nested labels") {
val src = """
main {
sub start() {
uword i
uword k
repeat {
mylabel0:
goto mylabel0
}
while cx16.r0 {
mylabel1:
goto mylabel1
}
do {
mylabel2:
goto mylabel2
} until cx16.r0
repeat cx16.r0 {
mylabel3:
goto mylabel3
}
for cx16.r0L in 0 to 2 {
mylabel4:
goto mylabel4
}
for cx16.r0L in cx16.r1L to cx16.r2L {
mylabel5:
goto mylabel5
}
mylabel_outside:
for i in 0 to 10 {
mylabel_inside:
if i==100 {
goto mylabel_outside
goto mylabel_inside
}
while k <= 10 {
k++
}
do {
k--
} until k==0
for k in 0 to 5 {
i++
}
repeat 10 {
k++
}
}
}
}"""
val target1 = C64Target()
compileText(target1, false, src, writeAssembly = false) shouldNotBe null
val target = VMTarget()
compileText(target, false, src, writeAssembly = true) shouldNotBe null
}
test("case sensitive symbols") {
val src = """
main {
sub start() {
ubyte bytevar = 11 ; var at 0
ubyte byteVAR = 22 ; var at 1
ubyte ByteVar = 33 ; var at 2
ubyte @shared total = bytevar+byteVAR+ByteVar ; var at 3
goto skipLABEL
SkipLabel:
return
skipLABEL:
bytevar = 42
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
vm.memory.getUB(0) shouldBe 42u
vm.memory.getUB(3) shouldBe 66u
}
}
test("memory slabs") {
val src = """
main {
sub start() {
uword slab1 = memory("slab1", 2000, 64)
slab1[10]=42
slab1[11]=43
ubyte @shared value1 = slab1[10] ; var at 2
ubyte @shared value2 = slab1[11] ; var at 3
}
}"""
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val start = result.program.entrypoint
start.statements.size shouldBe 9
((start.statements[1] as Assignment).value as BuiltinFunctionCall).name shouldBe "memory"
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
vm.memory.getUB(2) shouldBe 42u
vm.memory.getUB(3) shouldBe 43u
}
}
test("memory mapped var as for loop counter") {
val src = """
main {
sub start() {
for cx16.r0 in 0 to 10 {
cx16.r1++
}
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
vm.stepCount shouldBe 49
}
}
test("asmsub for virtual target") {
val src = """
main {
sub start() {
void test(42)
}
asmsub test(ubyte xx @A) -> ubyte @Y {
%asm {{
lda #99
tay
rts
return
}}
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
val exc = shouldThrow<Exception> {
VmRunner().runProgram(virtfile.readText())
}
exc.message shouldContain("does not support non-IR asmsubs")
}
})

View File

@ -8,7 +8,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.parser.Prog8ANTLRParser
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
@ -20,7 +20,7 @@ private data class NumericLiteralNode(val number: Double, val datatype: DataType
private fun ParserRuleContext.toPosition() : Position {
val pathString = start.inputStream.sourceName
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
val path = Path.of(pathString)
val path = Path(pathString)
if(path.isRegularFile()) {
SourceCode.relative(path).toString()
} else {
@ -40,6 +40,7 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
it.inlineir()!=null -> it.inlineir().toAst()
it.labeldef()!=null -> it.labeldef().toAst()
else -> throw FatalAstException("weird block node $it")
}
@ -125,6 +126,9 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
val ir = inlineir()?.toAst()
if(ir!=null) return ir
val branchstmt = branch_stmt()?.toAst()
if(branchstmt!=null) return branchstmt
@ -252,7 +256,12 @@ private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
val text = INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), toPosition())
return InlineAssembly(text.substring(2, text.length-2), false, toPosition())
}
private fun Prog8ANTLRParser.InlineirContext.toAst(): InlineAssembly {
val text = INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), true, toPosition())
}
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {

View File

@ -7,6 +7,7 @@ import prog8.ast.expressions.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.VMTarget
interface INamedStatement {
@ -616,19 +617,28 @@ class FunctionCallStatement(override var target: IdentifierReference,
override fun toString() = "FunctionCallStatement(target=$target, pos=$position)"
}
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
class InlineAssembly(val assembly: String, val isIR: Boolean, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun copy() = InlineAssembly(assembly, position)
override fun copy() = InlineAssembly(assembly, isIR, position)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun hasReturnOrRts(target: ICompilationTarget): Boolean {
return if(target.name!= VMTarget.NAME) {
" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
} else {
" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly || " jumpa" in assembly || "\tjumpa" in assembly
}
}
val names: Set<String> by lazy {
// A cache of all the words (identifiers) present in this block of assembly code

View File

@ -119,8 +119,6 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("rsavex" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("rrestorex" , false, emptyList(), null),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),

View File

@ -1,2 +1,2 @@
sphinx>=4.0
sphinx_rtd_theme==1.0.0
sphinx>=4.4.0, !=5.2.0.post0 # https://github.com/sphinx-doc/sphinx/issues/10860
sphinx_rtd_theme>=1.0.0

View File

@ -68,7 +68,7 @@ It contains all of the program's code and data and has a certain file format tha
allows it to be loaded directly on the target system. Prog8 currently has no built-in
support for programs that exceed 64 Kb of memory, nor for multi-part loaders.
For the Commodore-64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
For the Commodore 64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
This way the user can load it as any other program and simply RUN it to start. (This is a regular ".prg" program).
Prog8 can create those, but it is also possible to output plain binary programs
that can be loaded into memory anywhere.
@ -95,7 +95,7 @@ For normal use the compiler can be invoked with the command:
By default, assembly code is generated and written to ``sourcefile.asm``.
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ assembler tool
that creastes the final runnable program.
that creates the final runnable program.
Command line options
@ -138,7 +138,7 @@ One or more .p8 module files
``-noreinit``
Don't create code to reinitialize the global (block level) variables on every run of the program.
Also means that all such variables are no longer placed in the zero page.
Also means that all such variables are no longer placed in the zeropage.
Sometimes the program will be a lot shorter when using this, but sometimes the opposite happens.
When using this option, it is no longer be possible to run the program correctly more than once!
*Experimental feature*: still has some problems!
@ -182,7 +182,7 @@ One or more .p8 module files
``-esa <address>``
Override the base address of the evaluation stack. Has to be page-aligned.
You can specify an integer or hexadecimal address.
When not compiling for the CommanderX16 target, the location of the 16 virtual registers cx16.r0..r15
When not compiling for the Commander X16 target, the location of the 16 virtual registers cx16.r0..r15
is changed accordingly (to keep them in the same memory space as the evaluation stack).
@ -210,26 +210,26 @@ the ``srcdirs`` command line option.
.. _debugging:
Debugging (with Vice)
Debugging (with VICE)
---------------------
There's support for using the monitor and debugging capabilities of the rather excellent
`Vice emulator <http://vice-emu.sourceforge.net/>`_.
`VICE emulator <http://vice-emu.sourceforge.net/>`_.
The ``%breakpoint`` directive (see :ref:`directives`) in the source code instructs the compiler to put
a *breakpoint* at that position. Some systems use a BRK instruction for this, but
this will usually halt the machine altogether instead of just suspending execution.
Prog8 issues a NOP instruction instead and creates a 'virtual' breakpoint at this position.
All breakpoints are then written to a file called "programname.vice-mon-list",
which is meant to be used by the Vice emulator.
It contains a series of commands for Vice's monitor, including source labels and the breakpoint settings.
which is meant to be used by the VICE emulator.
It contains a series of commands for VICE's monitor, including source labels and the breakpoint settings.
If you use the emulator autostart feature of the compiler, it will take care of this for you.
If you launch Vice manually, you'll have to use a command line option to load this file:
If you launch VICE manually, you'll have to use a command line option to load this file:
``$ x64 -moncommands programname.vice-mon-list``
Vice will then use the label names in memory disassembly, and will activate any breakpoints as well.
If your running program hits one of the breakpoints, Vice will halt execution and drop you into the monitor.
VICE will then use the label names in memory disassembly, and will activate any breakpoints as well.
If your running program hits one of the breakpoints, VICE will halt execution and drop you into the monitor.
Troubleshooting

View File

@ -15,7 +15,7 @@ This is a compiled programming language targeting the 8-bit
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
while still being low level enough to create high performance programs.
You can compile programs for various machines with this CPU:
@ -69,12 +69,12 @@ Language features
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
- Strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest petscii equivalents.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Many built-in functions, such as ``sin``, ``cos``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
- If you only use standard kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
- If you only use standard Kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
Code example
@ -83,6 +83,7 @@ Code example
Here is a hello world program::
%import textio
%zeropage basicsafe
main {
sub start() {
@ -139,11 +140,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
when compiled an ran on a C-64 you get this:
when compiled an ran on a C64 you get this:
.. image:: _static/primes_example.png
:align: center
:alt: result when run on C-64
:alt: result when run on C64
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
@ -169,19 +170,23 @@ Required additional tools
It's very easy to compile yourself.
A recent precompiled .exe (only for Windows) can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
It's possible to use older versions, but it is very likely that the automatic Vice breakpoints won't work with them.
It's possible to use older versions, but it is very likely that the automatic VICE breakpoints won't work with them.
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK in their packages repository instead.
For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
In C64 mode, the compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the CommanderX16 instead, there's a choice of the official `x16emu <https://github.com/commanderx16/x16-emulator>`_
Finally: an **emulator** (or a real machine of course) to test and run your programs on.
In C64 mode, the compiler assumes the presence of the `VICE emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the Commander X16 instead, there's a choice of the official `x16emu <https://github.com/commanderx16/x16-emulator>`_
and the unofficial `box16 <https://github.com/indigodarkwolf/box16>`_ (you can select which one you want to launch
using the ``-emu`` or ``-emu2`` command line options)
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
Look in the `syntax-files <https://github.com/irmen/prog8/tree/master/syntax-files>`_ directory in the github repository to find them.
.. toctree::
:maxdepth: 2

View File

@ -29,7 +29,7 @@ of these library modules automatically as required.
syslib
------
The "system library" for your target machine. It contains many system-specific definitions such
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
as ROM/Kernal subroutine definitions, memory location constants, and utility subroutines.
Depending on the compilation target, other routines may also be available in here specific to that target.
Best is to check the source code of the correct syslib module.
@ -45,8 +45,8 @@ sys (part of syslib)
system when the program is running.
The following return values are currently defined:
- 16 = compiled for CommanderX16 with 65C02 CPU
- 64 = compiled for Commodore-64 with 6502/6510 CPU
- 16 = compiled for Commander X16 with 65C02 CPU
- 64 = compiled for Commodore 64 with 6502/6510 CPU
``exit(returncode)``
Immediately stops the program and exits it, with the returncode in the A register.
@ -105,7 +105,7 @@ sys (part of syslib)
note: a more accurate way to do this is by using a raster irq handler instead.
``reset_system()``
Soft-reset the system back to initial power-on Basic prompt.
Soft-reset the system back to initial power-on BASIC prompt.
(called automatically by Prog8 when the main subroutine returns and the program is not using basicsafe zeropage option)
@ -140,7 +140,7 @@ Provides several routines that deal with disk drive I/O, such as:
- delete and rename files on the disk
- send arbitrary CbmDos command to disk drive
On the Commander X16 it tries to use that machine's fast kernal loading routines if possible.
On the Commander X16 it tries to use that machine's fast Kernal loading routines if possible.
string
@ -150,7 +150,7 @@ Provides string manipulation routines.
``length(str) -> ubyte length``
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
Don't confuse this with ``len`` and ``sizeof``!
``left(source, length, target)``
Copies the left side of the source string of the given length to target string.
@ -176,8 +176,8 @@ Provides string manipulation routines.
and the index in the string. Or 0+carry bit clear if the character was not found.
``compare(string1, string2) -> ubyte result``
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
Returns -1, 0 or 1 depending on whether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with each other
using ``==``, ``<`` etcetera (it will use string.compare for you under water automatically).
``copy(from, to) -> ubyte length``
@ -186,10 +186,16 @@ Provides string manipulation routines.
but this function is useful if you're dealing with addresses for instance.
``lower(string)``
Lowercases the petscii-string in place.
Lowercases the PETSCII-string in place.
``upper(string)``
Uppercases the petscii-string in place.
Uppercases the PETSCII-string in place.
``lowerchar(char)``
Returns lowercased character.
``upperchar(char)``
Returns uppercased character.
``startswith(string, prefix) -> bool``
Returns true if string starts with prefix, otherwise false
@ -204,7 +210,7 @@ Provides string manipulation routines.
floats
------
Provides definitions for the ROM/kernal subroutines and utility routines dealing with floating
Provides definitions for the ROM/Kernal subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers,
``fabs`` to get the absolute value of a floating point number, and a dozen or so floating point
math routines.
@ -251,7 +257,10 @@ tan(x)
Tangent.
rndf()
returns a pseudo-random float between 0.0 and 1.0
returns the next random float between 0.0 and 1.0 from the Pseudo RNG sequence.
rndseedf(seed)
Sets a new seed for the float pseudo-RNG sequence. Use a negative non-zero number as seed value.
graphics
@ -270,12 +279,22 @@ Use the ``gfx2`` library if you want full-screen graphics or non-monochrome draw
math
----
Low level math routines. You should not normally have to bother with this directly.
The compiler needs it to implement most of the math operations in your programs.
Low-level integer math routines (which you usually don't have to bother with directly, but they are used by the compiler internally).
Pseudo-Random number generators (byte and word).
Various 8-bit integer trig functions that use lookup tables to quickly calculate sine and cosines.
Usually a custom lookup table is the way to go if your application needs these,
but perhaps the provided ones can be of service too.
However there's a bunch of 8-bit integer trig functions in here too that use lookup tables
to quickly calculate sine and cosines. Usually a custom lookup table is the way to go if your
application needs this, but perhaps the provided ones can be of service too:
rnd()
Returns next random byte 0-255 from the pseudo-RNG sequence.
rndw()
Returns next random word 0-65535 from the pseudo-RNG sequence.
rndseed(uword seed1, uword seed2)
Sets a new seed for the pseudo-RNG sequence (both rnd and rndw). The seed consists of two words.
Do not use zeros for the seed!
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
@ -314,7 +333,7 @@ and allows you to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
Low-level language support. You should not normally have to bother with this directly.
The compiler needs it for various built-in system routines.
@ -324,7 +343,7 @@ Full-screen multicolor bitmap graphics routines, available on the Cx16 machine o
- multiple full-screen resolutions: 640 * 480 monochrome, and 320 * 240 monochrome and 256 colors
- clearing screen, switching screen mode, also back to text mode is possible.
- drawing individual pixels
- drawing and reading individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
- drawing text inside the bitmap
- in monochrome mode, it's possible to use a stippled drawing pattern to simulate a shade of gray.
@ -333,15 +352,16 @@ Full-screen multicolor bitmap graphics routines, available on the Cx16 machine o
palette (cx16 only)
--------------------
Available for the Cx16 target. Various routines to set the display color palette.
There are also a few better looking Commodore-64 color palettes available here,
There are also a few better looking Commodore 64 color palettes available here,
because the Commander X16's default colors for this (the first 16 colors) are too saturated
and are quite different than how they looked on a VIC-II chip in a C-64.
and are quite different than how they looked on a VIC-II chip in a C64.
cx16diskio (cx16 only)
-----------------------
Available for the Cx16 target. Contains extensions to the load and load_raw routines from the regular
diskio module, to deal with loading of potentially large files in to banked ram (HiRam).
Routines to directly load data into video ram are also present (vload and vload_raw).
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
to 16 bits, 64Kb)

View File

@ -17,15 +17,15 @@ CPU
Memory Map
----------
Zero page
zeropage
=========
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the Zero page that are free to use at all times.
#. Provide list of any additional free Zero page locations for a normal running system (basic + kernal enabled)
#. Provide list of any additional free Zero page locations when basic is off, but floating point routines should still work
#. Provide list of any additional free Zero page locations when only the kernal remains enabled
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the zeropage that are free to use at all times.
#. Provide list of any additional free zeropage locations for a normal running system (BASIC + Kernal enabled)
#. Provide list of any additional free zeropage locations when BASIC is off, but floating point routines should still work
#. Provide list of any additional free zeropage locations when only the Kernal remains enabled
Only the three 16-bit pointers are absolutely required to be able to use prog8 on the system.
But more known available Zero page locations mean smaller and faster programs.
But more known available zeropage locations mean smaller and faster programs.
RAM, ROM, I/O
@ -40,7 +40,7 @@ RAM, ROM, I/O
Character encodings
-------------------
#. if not Petscii or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. if not PETSCII or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. provide alternate character encodings (if any)
#. what are the system's standard character screen dimensions?
#. is there a screen character matrix directly accessible in Ram? What's it address? Same for color attributes if any.
@ -50,7 +50,7 @@ ROM routines
------------
#. provide a list of the core ROM routines on the system, with names, addresses, and call signatures.
Ideally there are at least some routines to manipulate the screen and get some user input(clear, print text, print numbers, input strings from the keyboard)
Ideally there are at least some routines to manipulate the screen and get some user input (clear, print text, print numbers, input strings from the keyboard)
Routines to initialize the system to a sane state and to do a warm reset are useful too.
The more the merrier.
@ -74,7 +74,7 @@ the new target system.
There are several other support libraries that you may want to port (``diskio``, ``graphics`` to name a few).
Also ofcourse if there are unique things available on the new target system, don't hesitate to provide
Also of course if there are unique things available on the new target system, don't hesitate to provide
extensions to the ``syslib`` or perhaps a new special custom library altogether.

Binary file not shown.

View File

@ -1,20 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" width="215.9mm" height="210mm" viewBox="0 0 21590 21000" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<svg version="1.2" width="215.9mm" height="235mm" viewBox="0 0 21590 23500" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs class="ClipPathGroup">
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
<rect x="0" y="0" width="21590" height="21000"/>
<rect x="0" y="0" width="21590" height="23500"/>
</clipPath>
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
<rect x="21" y="21" width="21547" height="20958"/>
<rect x="21" y="23" width="21547" height="23453"/>
</clipPath>
</defs>
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
<font-face font-family="Bitstream Vera Sans Mono embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1879" descent="476"/>
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
<glyph unicode="v" horiz-adv-x="1060" d="M 100,1120 L 291,1120 616,180 942,1120 1133,1120 735,0 498,0 100,1120 Z"/>
<glyph unicode="t" horiz-adv-x="927" d="M 614,1438 L 614,1120 1032,1120 1032,977 614,977 614,369 C 614,286 630,229 661,196 692,163 747,147 825,147 L 1032,147 1032,0 807,0 C 669,0 572,28 515,83 458,138 430,234 430,369 L 430,977 131,977 131,1120 430,1120 430,1438 614,1438 Z"/>
<glyph unicode="r" horiz-adv-x="821" d="M 1155,889 C 1116,920 1076,942 1035,956 994,970 950,977 901,977 786,977 699,941 638,869 577,797 547,693 547,557 L 547,0 362,0 362,1120 547,1120 547,901 C 578,980 625,1041 689,1084 752,1126 828,1147 915,1147 960,1147 1003,1141 1042,1130 1081,1119 1119,1101 1155,1077 L 1155,889 Z"/>
<glyph unicode="p" horiz-adv-x="927" d="M 375,141 L 375,-426 190,-426 190,1120 375,1120 375,977 C 406,1032 447,1075 498,1104 549,1133 607,1147 674,1147 809,1147 916,1095 993,990 1070,885 1108,740 1108,555 1108,373 1069,230 992,127 915,23 809,-29 674,-29 606,-29 547,-14 496,15 445,44 404,86 375,141 Z M 915,559 C 915,702 893,809 848,882 803,955 736,991 647,991 558,991 490,955 444,882 398,809 375,701 375,559 375,418 398,310 444,237 490,164 558,127 647,127 736,127 803,163 848,236 893,309 915,416 915,559 Z"/>
<glyph unicode="o" horiz-adv-x="980" d="M 616,991 C 523,991 452,955 404,882 356,809 332,702 332,559 332,417 356,310 404,237 452,164 523,127 616,127 710,127 781,164 829,237 877,310 901,417 901,559 901,702 877,809 829,882 781,955 710,991 616,991 Z M 616,1147 C 771,1147 890,1097 973,996 1055,895 1096,750 1096,559 1096,368 1055,222 973,122 891,21 772,-29 616,-29 461,-29 342,21 260,122 178,222 137,368 137,559 137,750 178,895 260,996 342,1097 461,1147 616,1147 Z"/>
@ -63,6 +61,7 @@
<glyph unicode="I" horiz-adv-x="636" d="M 78,0 L 78,86 104,86 C 127,86 149,88 170,91 190,94 208,101 223,112 238,122 250,137 259,156 268,175 272,201 272,233 L 272,1229 C 272,1261 268,1287 259,1306 250,1325 238,1340 223,1351 208,1361 190,1368 170,1371 149,1374 127,1376 104,1376 L 78,1376 78,1462 674,1462 674,1376 647,1376 C 624,1376 603,1374 582,1371 561,1368 544,1361 529,1351 514,1340 502,1325 493,1306 484,1287 479,1261 479,1229 L 479,233 C 479,201 484,175 493,156 502,137 514,122 529,112 544,101 561,94 582,91 603,88 624,86 647,86 L 674,86 674,0 78,0 Z"/>
<glyph unicode="G" horiz-adv-x="1324" d="M 821,-20 C 702,-20 599,-2 511,34 422,70 349,121 291,187 232,253 189,332 160,425 131,518 117,620 117,733 117,844 132,945 163,1037 193,1129 238,1208 299,1274 359,1340 434,1391 523,1428 612,1465 716,1483 834,1483 910,1483 976,1477 1033,1466 1089,1454 1136,1438 1174,1417 1211,1396 1239,1372 1258,1344 1277,1316 1286,1286 1286,1253 1286,1231 1281,1211 1271,1194 1261,1176 1248,1161 1231,1149 1214,1136 1194,1127 1171,1120 1148,1113 1123,1110 1096,1110 1096,1142 1091,1174 1082,1205 1073,1236 1057,1265 1036,1290 1015,1315 987,1335 952,1351 917,1366 875,1374 825,1374 738,1374 665,1360 604,1332 543,1303 493,1262 454,1208 415,1153 387,1086 370,1007 353,927 344,836 344,733 344,631 353,540 371,461 389,382 418,315 459,261 500,207 552,166 617,138 682,110 761,96 854,96 893,96 932,98 969,102 1006,106 1038,112 1067,121 L 1067,451 C 1067,481 1063,505 1054,524 1045,542 1032,556 1017,566 1002,575 984,582 964,585 943,588 922,590 899,590 L 891,590 891,676 1423,676 1423,590 1415,590 C 1396,590 1377,588 1360,585 1343,582 1328,575 1315,565 1302,554 1292,539 1285,520 1278,500 1274,474 1274,442 L 1274,74 C 1205,42 1135,18 1062,3 989,-12 909,-20 821,-20 Z"/>
<glyph unicode="C" horiz-adv-x="1086" d="M 774,1483 C 844,1483 905,1477 957,1466 1008,1454 1051,1438 1086,1417 1120,1396 1146,1372 1163,1344 1180,1316 1188,1286 1188,1253 1188,1231 1184,1211 1175,1194 1166,1176 1153,1161 1137,1149 1121,1136 1102,1127 1081,1120 1059,1113 1035,1110 1010,1110 1010,1142 1006,1174 998,1205 989,1236 976,1265 957,1290 938,1315 913,1335 883,1351 852,1366 815,1374 770,1374 693,1374 627,1360 573,1332 519,1303 475,1262 441,1208 407,1153 382,1086 367,1007 352,927 344,836 344,733 344,642 352,558 368,482 384,406 409,341 444,287 479,232 523,190 577,160 630,130 695,115 770,115 820,115 864,120 903,130 941,139 975,152 1004,168 1033,184 1059,203 1082,224 1104,245 1124,266 1141,289 1152,282 1162,272 1169,259 1176,246 1180,230 1180,209 1180,183 1171,157 1154,130 1136,103 1109,78 1073,56 1037,34 991,16 936,2 881,-13 815,-20 739,-20 637,-20 547,-2 470,34 393,70 328,121 276,187 223,253 184,332 157,425 130,518 117,620 117,733 117,844 131,945 159,1037 187,1129 229,1208 284,1274 339,1340 407,1391 489,1428 571,1465 666,1483 774,1483 Z"/>
<glyph unicode="B" horiz-adv-x="1165" d="M 1153,1096 C 1153,1050 1147,1010 1134,975 1121,940 1103,910 1080,884 1057,858 1031,836 1000,819 969,801 935,786 899,774 L 899,766 C 946,758 988,744 1027,725 1066,706 1099,681 1127,650 1154,619 1176,582 1191,540 1206,497 1214,449 1214,395 1214,263 1171,164 1084,99 997,33 865,0 688,0 L 78,0 78,86 104,86 C 127,86 149,88 170,91 190,94 208,101 223,112 238,122 250,137 259,156 268,175 272,201 272,233 L 272,1237 C 272,1267 268,1291 259,1310 250,1328 237,1342 222,1352 207,1362 189,1369 169,1372 148,1375 127,1376 104,1376 L 78,1376 78,1462 627,1462 C 804,1462 936,1433 1023,1374 1110,1315 1153,1222 1153,1096 Z M 479,102 L 678,102 C 736,102 785,107 825,118 865,129 898,146 923,170 948,193 967,224 978,262 989,300 995,346 995,401 995,454 990,501 980,540 970,579 953,612 929,638 904,664 872,683 832,696 792,709 742,715 682,715 L 479,715 479,102 Z M 479,817 L 621,817 C 681,817 731,822 771,833 811,844 843,860 867,883 891,905 908,934 919,969 929,1004 934,1047 934,1096 934,1146 928,1188 917,1222 906,1255 887,1282 862,1303 837,1324 804,1338 764,1347 723,1356 674,1360 616,1360 L 479,1360 479,817 Z"/>
<glyph unicode="A" horiz-adv-x="1456" d="M 414,489 L 336,274 C 330,258 325,242 322,227 319,211 317,197 317,186 317,151 328,126 351,110 373,94 407,86 453,86 L 500,86 500,0 0,0 0,86 39,86 C 59,86 76,88 90,93 104,97 117,105 128,117 139,129 150,145 161,166 171,187 182,213 195,246 L 649,1462 809,1462 1272,195 C 1280,174 1288,156 1297,142 1305,128 1315,117 1326,109 1337,100 1350,94 1365,91 1380,88 1397,86 1417,86 L 1444,86 1444,0 881,0 881,86 928,86 C 1010,86 1051,119 1051,184 1051,195 1050,207 1047,219 1044,231 1039,245 1034,260 L 952,489 414,489 Z M 788,950 C 767,1011 747,1068 730,1121 712,1174 697,1225 686,1274 681,1249 676,1226 670,1203 663,1180 656,1156 649,1132 642,1108 633,1083 624,1057 615,1030 604,1001 592,969 L 453,592 915,592 788,950 Z"/>
<glyph unicode="8" horiz-adv-x="980" d="M 94,367 C 94,416 102,460 118,497 133,534 155,567 183,596 211,625 244,651 282,674 320,697 361,720 406,741 367,763 332,787 299,813 266,838 238,866 215,897 192,928 174,961 161,998 148,1034 141,1073 141,1116 141,1163 149,1209 166,1253 183,1297 209,1336 244,1370 279,1404 325,1431 382,1452 438,1473 505,1483 584,1483 648,1483 705,1474 755,1457 805,1439 847,1414 882,1383 916,1352 942,1315 960,1272 978,1229 987,1181 987,1130 987,1085 980,1046 967,1012 954,978 935,948 911,921 887,894 858,869 824,848 790,826 752,805 711,784 762,760 808,734 850,707 891,680 927,650 957,618 987,586 1010,551 1027,514 1043,477 1051,436 1051,391 1051,326 1039,269 1016,218 993,167 960,123 917,88 874,53 823,26 762,8 701,-11 634,-20 559,-20 482,-20 415,-10 357,10 299,30 251,58 212,93 173,128 143,169 124,216 104,263 94,313 94,367 Z M 569,74 C 614,74 654,81 691,95 727,108 758,127 784,151 809,175 829,204 843,237 857,270 864,307 864,346 864,381 858,413 845,443 832,472 812,501 783,529 754,556 717,583 672,610 626,636 570,663 504,692 439,657 388,612 350,557 312,502 293,435 293,358 293,315 299,277 311,242 322,207 340,178 363,153 386,128 414,108 449,95 484,81 524,74 569,74 Z M 805,1135 C 805,1166 801,1197 793,1227 784,1257 771,1284 753,1308 734,1331 710,1350 681,1365 651,1379 614,1386 571,1386 533,1386 499,1380 470,1368 440,1355 415,1338 395,1317 375,1295 360,1269 350,1239 339,1209 334,1176 334,1141 334,1104 340,1071 352,1042 363,1013 381,986 404,962 427,938 455,916 490,895 525,874 565,852 610,831 649,850 681,870 706,891 731,911 750,933 765,958 780,982 790,1009 796,1038 802,1067 805,1099 805,1135 Z"/>
<glyph unicode="6" horiz-adv-x="953" d="M 659,1384 C 556,1384 479,1334 426,1233 373,1132 343,981 336,782 353,796 371,810 392,823 412,836 434,847 458,857 482,866 508,874 537,880 565,886 596,889 629,889 692,889 748,879 799,860 850,841 893,813 929,777 964,741 992,697 1011,645 1030,593 1040,534 1040,469 1040,397 1030,331 1011,271 991,211 962,160 925,117 887,74 841,40 787,16 732,-8 670,-20 600,-20 532,-20 469,-6 411,23 353,51 303,96 261,157 218,218 185,296 161,392 137,487 125,602 125,737 125,802 130,865 140,927 149,989 164,1048 183,1103 202,1158 227,1209 256,1256 285,1303 320,1343 360,1377 399,1410 444,1436 494,1455 544,1474 599,1483 659,1483 716,1483 766,1477 809,1465 851,1453 886,1437 914,1417 942,1397 963,1374 977,1348 990,1322 997,1295 997,1268 997,1229 984,1198 957,1177 930,1156 891,1145 840,1145 840,1178 837,1210 831,1239 824,1268 814,1293 799,1315 784,1336 766,1353 743,1366 720,1378 692,1384 659,1384 Z M 588,784 C 561,784 536,781 511,774 486,767 463,758 442,747 420,736 400,723 382,709 364,694 348,680 334,666 335,563 343,476 356,403 369,330 387,271 410,225 433,179 462,145 495,124 528,103 565,92 606,92 679,92 735,121 774,178 812,235 831,325 831,449 831,566 810,651 769,704 727,757 667,784 588,784 Z"/>
@ -120,7 +119,7 @@
</font>
</defs>
<defs class="TextShapeIndex">
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31 id32 id33 id34 id35 id36 id37 id38 id39 id40 id41 id42 id43 id44 id45"/>
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31 id32 id33 id34 id35 id36 id37 id38 id39 id40 id41 id42 id43 id44 id45 id46 id47 id48 id49"/>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
@ -177,7 +176,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="3500" width="5611" height="1213"/>
<path fill="rgb(142,134,174)" stroke="none" d="M 4509,4685 L 1731,4685 1731,3527 7287,3527 7287,4685 4509,4685 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,4685 L 1731,4685 1731,3527 7287,3527 7287,4685 4509,4685 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="2619" y="4277"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Read module.p8</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2600" y="4277"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Read module.p8</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
@ -185,7 +184,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="5583" width="5611" height="1212"/>
<path fill="rgb(142,134,174)" stroke="none" d="M 4509,6767 L 1731,6767 1731,5610 7287,5610 7287,6767 4509,6767 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,6767 L 1731,6767 1731,5610 7287,5610 7287,6767 4509,6767 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="3459" y="6360"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Tokenize</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="3451" y="6360"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Tokenize</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
@ -193,7 +192,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="9750" width="5611" height="1213"/>
<path fill="rgb(114,159,207)" stroke="none" d="M 4509,10935 L 1731,10935 1731,9777 7287,9777 7287,10935 4509,10935 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,10935 L 1731,10935 1731,9777 7287,9777 7287,10935 4509,10935 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="2774" y="10527"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Convert to AST</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2761" y="10527"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Convert to AST</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
@ -201,7 +200,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="13916" width="5611" height="1444"/>
<path fill="rgb(114,159,207)" stroke="none" d="M 4509,15332 L 1731,15332 1731,13943 7287,13943 7287,15332 4509,15332 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,15332 L 1731,15332 1731,13943 7287,13943 7287,15332 4509,15332 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="2285" y="14523"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Semantic and Type </tspan></tspan><tspan class="TextPosition" x="3468" y="15095"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">checking</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2263" y="14519"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Semantic and Type </tspan></tspan><tspan class="TextPosition" x="3459" y="15099"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">checking</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
@ -209,7 +208,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="11833" width="5611" height="1212"/>
<path fill="rgb(114,159,207)" stroke="none" d="M 4509,13017 L 1731,13017 1731,11860 7287,11860 7287,13017 4509,13017 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,13017 L 1731,13017 1731,11860 7287,11860 7287,13017 4509,13017 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="2716" y="12610"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Preprocess AST</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2697" y="12610"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Preprocess AST</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -260,7 +259,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="5269" y="16315" width="3758" height="1212"/>
<path fill="rgb(114,159,207)" stroke="none" d="M 7148,17499 L 5296,17499 5296,16342 8999,16342 8999,17499 7148,17499 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 7148,17499 L 5296,17499 5296,16342 8999,16342 8999,17499 7148,17499 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="6083" y="17092"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Optimize</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="6072" y="17092"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Optimize</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
@ -268,7 +267,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="18315" width="5611" height="1213"/>
<path fill="rgb(114,159,207)" stroke="none" d="M 4509,19500 L 1731,19500 1731,18342 7287,18342 7287,19500 4509,19500 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,19500 L 1731,19500 1731,18342 7287,18342 7287,19500 4509,19500 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="2625" y="19092"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Postprocess AST</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2604" y="19092"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Postprocess AST</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -290,7 +289,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="1704" y="7665" width="5611" height="1213"/>
<path fill="rgb(142,134,174)" stroke="none" d="M 4509,8850 L 1731,8850 1731,7692 7287,7692 7287,8850 4509,8850 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 4509,8850 L 1731,8850 1731,7692 7287,7692 7287,8850 4509,8850 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="3866" y="8442"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Parse</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="3857" y="8442"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Parse</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -311,7 +310,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="13167" y="3306" width="5611" height="1213"/>
<path fill="rgb(129,172,166)" stroke="none" d="M 15972,4491 L 13194,4491 13194,3333 18750,3333 18750,4491 15972,4491 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 15972,4491 L 13194,4491 13194,3333 18750,3333 18750,4491 15972,4491 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="14190" y="3797"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Select CodeGen</tspan></tspan><tspan class="TextPosition" x="14869" y="4369"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">for target</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="14169" y="3793"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Select CodeGen</tspan></tspan><tspan class="TextPosition" x="14861" y="4373"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">for target</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -333,7 +332,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="14723" y="5621" width="4305" height="1907"/>
<path fill="rgb(129,172,166)" stroke="none" d="M 16875,7500 L 14750,7500 14750,5648 19000,5648 19000,7500 16875,7500 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 16875,7500 L 14750,7500 14750,5648 19000,5648 19000,7500 16875,7500 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="16236" y="6173"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Make </tspan></tspan><tspan class="TextPosition" x="15343" y="6745"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Intermediate </tspan></tspan><tspan class="TextPosition" x="16422" y="7317"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">AST</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="16230" y="6165"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Make </tspan></tspan><tspan class="TextPosition" x="15330" y="6745"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Intermediate </tspan></tspan><tspan class="TextPosition" x="16420" y="7325"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">AST</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -348,7 +347,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="14723" y="8472" width="4305" height="1444"/>
<path fill="rgb(129,172,166)" stroke="none" d="M 16875,9888 L 14750,9888 14750,8499 19000,8499 19000,9888 16875,9888 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 16875,9888 L 14750,9888 14750,8499 19000,8499 19000,9888 16875,9888 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="15434" y="9365"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Generate IR </tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="15419" y="9365"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Generate IR </tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -363,7 +362,7 @@
<rect class="BoundingBox" stroke="none" fill="none" x="14723" y="10972" width="4305" height="1444"/>
<path fill="rgb(129,172,166)" stroke="none" d="M 16875,12388 L 14750,12388 14750,10999 19000,10999 19000,12388 16875,12388 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 16875,12388 L 14750,12388 14750,10999 19000,10999 19000,12388 16875,12388 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="15495" y="11865"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Optimize IR</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="15482" y="11865"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Optimize IR</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
@ -375,17 +374,17 @@
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id31">
<rect class="BoundingBox" stroke="none" fill="none" x="13157" y="14223" width="3620" height="1305"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 14967,15500 L 13184,15500 13184,14250 16749,14250 16749,15500 14967,15500 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 14967,15500 L 13184,15500 13184,14250 16749,14250 16749,15500 14967,15500 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="13904" y="14760"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Generate </tspan></tspan><tspan class="TextPosition" x="13836" y="15332"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">6502 Asm</tspan></tspan></tspan></text>
<rect class="BoundingBox" stroke="none" fill="none" x="13157" y="14223" width="3620" height="1457"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 14967,15652 L 13184,15652 13184,14250 16749,14250 16749,15652 14967,15652 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 14967,15652 L 13184,15652 13184,14250 16749,14250 16749,15652 14967,15652 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="13891" y="14832"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Generate </tspan></tspan><tspan class="TextPosition" x="13832" y="15412"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">6502 Asm</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id32">
<rect class="BoundingBox" stroke="none" fill="none" x="12223" y="4464" width="3777" height="10552"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 15972,4491 L 15972,5019 12250,5019 12250,14875 12925,14875"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 12906,14736 L 13185,14875 12906,15015 12906,14736 Z"/>
<rect class="BoundingBox" stroke="none" fill="none" x="12223" y="4464" width="3777" height="10628"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 15972,4491 L 15972,5019 12250,5019 12250,14951 12925,14951"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 12906,14812 L 13185,14951 12906,15091 12906,14812 Z"/>
</g>
</g>
<g class="TextShape">
@ -399,13 +398,13 @@
<rect class="BoundingBox" stroke="none" fill="none" x="13167" y="16269" width="3611" height="1509"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 14972,17750 L 13194,17750 13194,16296 16750,16296 16750,17750 14972,17750 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 14972,17750 L 13194,17750 13194,16296 16750,16296 16750,17750 14972,17750 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="13857" y="16908"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Assemble </tspan></tspan><tspan class="TextPosition" x="14083" y="17480"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">(64tass)</tspan></tspan></tspan></text>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="13844" y="16904"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Assemble </tspan></tspan><tspan class="TextPosition" x="14073" y="17484"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">(64tass)</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id35">
<rect class="BoundingBox" stroke="none" fill="none" x="14832" y="15473" width="281" height="824"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 14967,15500 L 14967,15898 14972,15898 14972,16036"/>
<rect class="BoundingBox" stroke="none" fill="none" x="14832" y="15625" width="281" height="672"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 14967,15652 L 14967,15974 14972,15974 14972,16036"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 15112,16017 L 14972,16296 14833,16017 15112,16017 Z"/>
</g>
</g>
@ -439,49 +438,79 @@
<path fill="rgb(52,101,164)" stroke="none" d="M 15108,18332 L 14968,18611 14829,18332 15108,18332 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id40">
<rect class="BoundingBox" stroke="none" fill="none" x="17473" y="14222" width="3305" height="1212"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 19125,15406 L 17500,15406 17500,14249 20750,14249 20750,15406 19125,15406 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 19125,15406 L 17500,15406 17500,14249 20750,14249 20750,15406 19125,15406 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="493px" font-weight="400"><tspan class="TextPosition" x="18063" y="14713"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Generate </tspan></tspan><tspan class="TextPosition" x="18122" y="15285"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">VM code</tspan></tspan></tspan></text>
<rect class="BoundingBox" stroke="none" fill="none" x="18809" y="17375" width="281" height="1028"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 18869,17402 L 18869,17902 18949,17902 18949,18142"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 19089,18123 L 18949,18402 18810,18123 19089,18123 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id41">
<rect class="BoundingBox" stroke="none" fill="none" x="17595" y="16498" width="3157" height="1393"/>
<path fill="rgb(255,245,206)" stroke="none" d="M 17596,16499 L 20750,16499 20750,17616 C 19501,17607 19543,17835 18416,17889 18013,17856 17866,17831 17596,17793 L 17596,16499 Z"/>
<path fill="none" stroke="rgb(52,101,164)" d="M 17596,16499 L 20750,16499 20750,17616 C 19501,17607 19543,17835 18416,17889 18013,17856 17866,17831 17596,17793 L 17596,16499 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Bitstream Vera Sans Mono, monospace" font-size="370px" font-weight="400"><tspan class="TextPosition" x="17852" y="17185"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">hello.p8virt</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id42">
<rect class="BoundingBox" stroke="none" fill="none" x="19034" y="15380" width="281" height="1121"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 19125,15407 L 19125,15966 19174,15966 19174,16240"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 19314,16221 L 19174,16500 19035,16221 19314,16221 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id43">
<rect class="BoundingBox" stroke="none" fill="none" x="14827" y="12362" width="2076" height="1889"/>
<path fill="none" stroke="rgb(183,179,202)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 16875,12389 L 16875,13319 14967,13319 14967,13990"/>
<path fill="rgb(183,179,202)" stroke="none" d="M 15107,13971 L 14967,14250 14828,13971 15107,13971 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id44">
<rect class="BoundingBox" stroke="none" fill="none" x="16848" y="12362" width="2418" height="1889"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 16875,12389 L 16875,13319 19125,13319 19125,13990"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 19265,13971 L 19125,14250 18986,13971 19265,13971 Z"/>
<g id="id42">
<rect class="BoundingBox" stroke="none" fill="none" x="16848" y="12362" width="2195" height="1652"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 16875,12389 L 16875,13213 18902,13213 18902,13753"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 19042,13734 L 18902,14013 18763,13734 19042,13734 Z"/>
</g>
</g>
<g class="TextShape">
<g id="id45">
<g id="id43">
<rect class="BoundingBox" stroke="none" fill="none" x="13473" y="13084" width="2778" height="1167"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Liberation Serif, serif" font-size="388px" font-style="italic" font-weight="400"><tspan class="TextPosition" x="13723" y="13556"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">(future)</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id44">
<rect class="BoundingBox" stroke="none" fill="none" x="17125" y="18375" width="3647" height="1806"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 17729,20153 L 17729,20153 C 17628,20153 17528,20113 17441,20036 17353,19959 17280,19848 17229,19715 17179,19582 17152,19431 17152,19278 L 17152,19278 C 17152,19124 17179,18973 17229,18840 17280,18707 17353,18596 17441,18519 17528,18442 17628,18402 17729,18402 L 20166,18402 20167,18402 C 20268,18402 20368,18442 20455,18519 20543,18596 20616,18707 20667,18840 20717,18973 20744,19124 20744,19278 L 20744,19278 20744,19278 C 20744,19431 20717,19582 20667,19715 20616,19848 20543,19959 20455,20036 20368,20113 20268,20153 20167,20153 L 17729,20153 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 17729,20153 L 17729,20153 C 17628,20153 17528,20113 17441,20036 17353,19959 17280,19848 17229,19715 17179,19582 17152,19431 17152,19278 L 17152,19278 C 17152,19124 17179,18973 17229,18840 17280,18707 17353,18596 17441,18519 17528,18442 17628,18402 17729,18402 L 20166,18402 20167,18402 C 20268,18402 20368,18442 20455,18519 20543,18596 20616,18707 20667,18840 20717,18973 20744,19124 20744,19278 L 20744,19278 20744,19278 C 20744,19431 20717,19582 20667,19715 20616,19848 20543,19959 20455,20036 20368,20113 20268,20153 20167,20153 L 17729,20153 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="18277" y="18927"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Run in </tspan></tspan><tspan class="TextPosition" x="18266" y="19423"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">virtual </tspan></tspan><tspan class="TextPosition" x="18074" y="19919"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">machine</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id45">
<rect class="BoundingBox" stroke="none" fill="none" x="17217" y="15973" width="3305" height="1457"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 18869,17402 L 17244,17402 17244,16000 20494,16000 20494,17402 18869,17402 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 18869,17402 L 17244,17402 17244,16000 20494,16000 20494,17402 18869,17402 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="17781" y="16582"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Build VM </tspan></tspan><tspan class="TextPosition" x="17836" y="17162"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">program</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id46">
<rect class="BoundingBox" stroke="none" fill="none" x="17307" y="14011" width="3189" height="1393"/>
<path fill="rgb(255,245,206)" stroke="none" d="M 17308,14012 L 20494,14012 20494,15129 C 19232,15120 19275,15348 18136,15402 17729,15369 17580,15344 17308,15306 L 17308,14012 Z"/>
<path fill="none" stroke="rgb(52,101,164)" d="M 17308,14012 L 20494,14012 20494,15129 C 19232,15120 19275,15348 18136,15402 17729,15369 17580,15344 17308,15306 L 17308,14012 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Bitstream Vera Sans Mono, monospace" font-size="370px" font-weight="400"><tspan class="TextPosition" x="17801" y="14698"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">hello.p8ir</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id47">
<rect class="BoundingBox" stroke="none" fill="none" x="18729" y="15293" width="281" height="708"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 18902,15320 L 18902,15688 18869,15688 18869,15740"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 19009,15721 L 18869,16000 18730,15721 19009,15721 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id48">
<rect class="BoundingBox" stroke="none" fill="none" x="13125" y="20625" width="3647" height="1806"/>
<path fill="rgb(255,166,166)" stroke="none" d="M 13729,22403 L 13729,22403 C 13628,22403 13528,22363 13441,22286 13353,22209 13280,22098 13229,21965 13179,21832 13152,21681 13152,21528 L 13152,21528 C 13152,21374 13179,21223 13229,21090 13280,20957 13353,20846 13441,20769 13528,20692 13628,20652 13729,20652 L 16166,20652 16167,20652 C 16268,20652 16368,20692 16455,20769 16543,20846 16616,20957 16667,21090 16717,21223 16744,21374 16744,21528 L 16744,21528 16744,21528 C 16744,21681 16717,21832 16667,21965 16616,22098 16543,22209 16455,22286 16368,22363 16268,22403 16167,22403 L 13729,22403 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 13729,22403 L 13729,22403 C 13628,22403 13528,22363 13441,22286 13353,22209 13280,22098 13229,21965 13179,21832 13152,21681 13152,21528 L 13152,21528 C 13152,21374 13179,21223 13229,21090 13280,20957 13353,20846 13441,20769 13528,20692 13628,20652 13729,20652 L 16166,20652 16167,20652 C 16268,20652 16368,20692 16455,20769 16543,20846 16616,20957 16667,21090 16717,21223 16744,21374 16744,21528 L 16744,21528 16744,21528 C 16744,21681 16717,21832 16667,21965 16616,22098 16543,22209 16455,22286 16368,22363 16268,22403 16167,22403 L 13729,22403 Z"/>
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="14222" y="21177"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Run on </tspan></tspan><tspan class="TextPosition" x="14353" y="21673"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">target </tspan></tspan><tspan class="TextPosition" x="14074" y="22169"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">machine</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id49">
<rect class="BoundingBox" stroke="none" fill="none" x="14809" y="19891" width="281" height="762"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 14968,19918 L 14968,20313 14949,20313 14949,20392"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 15089,20373 L 14949,20652 14810,20373 15089,20373 Z"/>
</g>
</g>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -107,10 +107,6 @@ The name of a block must be unique in your entire program.
Be careful when importing other modules; blocks in your own code cannot have
the same name as a block defined in an imported module or library.
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not fully parsed either.
The address can be used to place a block at a specific location in memory.
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
the previous block in memory).
@ -201,12 +197,12 @@ Values will usually be part of an expression or assignment statement::
*putting a variable in zeropage:*
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
when selecting variables to put into zero page (but no guarantees). If there are enough free locations in the zeropage,
when selecting variables to put into zeropage (but no guarantees). If there are enough free locations in the zeropage,
it will try to fill it with as much other variables as possible (before they will be put in regular memory pages).
Use ``@requirezp`` tag to *force* the variable into zeropage, but if there is no more free space the compilation will fail.
It's possible to put strings, arrays and floats into zeropage too, however because Zp space is really scarce
this is not advised as they will eat up the available space very quickly. It's best to only put byte or word
variables in Zeropage.
variables in zeropage.
Example::
@ -230,7 +226,7 @@ Integers
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
in hexadecimal and in binary notation.
A single character in single quotes such as ``'a'`` is translated into a byte integer,
which is the Petscii value for that character.
which is the PETSCII value for that character.
Unsigned integers are in the range 0-255 for unsigned byte types, and 0-65535 for unsigned word types.
The signed integers integers are in the range -128..127 for bytes,
@ -260,9 +256,9 @@ Floating point numbers
^^^^^^^^^^^^^^^^^^^^^^
Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines,
and currently all floating point operations are specific to the Commodore-64.
This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
and currently all floating point operations are specific to the Commodore 64.
This is because routines in the C64 BASIC and Kernal ROMs are used for that.
So floating point operations will only work if the C64 BASIC ROM (and Kernal ROM)
are banked in.
Also your code needs to import the ``floats`` library to enable floating point support
@ -286,7 +282,7 @@ Here are some examples of arrays::
byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
ubyte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
uword[] others = [names, array] ; array of pointers/addresses to other arrays
@ -341,7 +337,7 @@ Strings (without encoding prefix) will be encoded (translated from ASCII/UTF-8)
Alternative encodings can be specified with a ``encodingname:`` prefix to the string or character literal.
The following encodings are currently recognised:
- ``petscii`` Petscii, the default encoding on CBM machines (c64, c128, cx16)
- ``petscii`` PETSCII, the default encoding on CBM machines (c64, c128, cx16)
- ``sc`` CBM-screencodes aka 'poke' codes (c64, c128, cx16)
- ``iso`` iso-8859-15 text (supported on cx16)
@ -353,7 +349,7 @@ It can be correctly displayed on the screen only if a iso-8859-15 charset has be
You can concatenate two string literals using '+', which can be useful to
split long strings over separate lines. But remember that the length
of the total string still cannot exceed 255 characaters.
of the total string still cannot exceed 255 characters.
A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value.
And a new string value can be assigned to another string, but no bounds check is done
so be sure the destination string is large enough to contain the new value (it is overwritten in memory)::
@ -370,7 +366,7 @@ as newlines, quote characters themselves, and so on. The ones used most often ar
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
read the syntax reference on strings.
Using the ``in`` operator you can easily check if a characater is present in a string,
Using the ``in`` operator you can easily check if a character is present in a string,
example: ``if '@' in email_address {....}`` (however this gives no clue about the location
in the string where the character is present, if you need that, use the ``string.find()``
library function instead)
@ -412,10 +408,10 @@ for the constant itself). This is only valid for the simple numeric types (byte,
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
rather than being newly allocated. The initial value (mandatory) must be a valid
memory address. Reading the variable will read the given data type from the
address you specified, and setting the varible will directly modify that memory location(s)::
address you specified, and setting the variable will directly modify that memory location(s)::
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
&word SCREENCOLORS = $d020 ; a 16-bit word at the address $d020-$d021
.. _pointervars_programming:
@ -452,9 +448,9 @@ Many type conversions are possible by just writing ``as <type>`` at the end of a
f = 56.777
ub = f as ubyte ; ub will be 56
Sometimes it is a straight 'type cast' where the value is simply interpreted as being of the other type,
sometimes an actual value conversion is done to convert it into the targe type.
Try to avoid type conversions as much as possible.
Sometimes it is a straight reinterpretation of the given value as being of the other type,
sometimes an actual value conversion is done to convert it into the other type.
Try to avoid those type conversions as much as possible.
Initial values across multiple runs of the program
@ -511,19 +507,29 @@ Conditional Execution
if statements
^^^^^^^^^^^^^
Conditional execution means that the flow of execution changes based on certiain conditions,
Conditional execution means that the flow of execution changes based on certain conditions,
rather than having fixed gotos or subroutine calls::
if aa>4 goto overflow
if xx==5 {
yy = 99
zz = 42
} else {
aa = 3
bb = 9
}
if xx==3 yy = 4
if xx==3 yy = 4 else aa = 2
if xx==5
yy = 42
else if xx==6
yy = 43
else
yy = 44
if xx==5 {
yy = 99
} else {
aa = 3
}
if aa>4 goto some_label
if xx==3 yy = 4
if xx==3 yy = 4 else aa = 2
Conditional jumps (``if condition goto label``) are compiled using 6502's branching instructions (such as ``bne`` and ``bcc``) so
@ -557,7 +563,7 @@ So ``if_cc goto target`` will directly translate into the single CPU instruction
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
This is not always the case after a function call or other operations!
If in doubt, check the generated assembly code!
.. note::
@ -633,7 +639,7 @@ If possible, the expression is parsed and evaluated by the compiler itself at co
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
Expressions can contain procedure and function calls.
There are various built-in functions such as sin(), cos() that can be used in expressions (see :ref:`builtinfunctions`).
You can also reference idendifiers defined elsewhere in your code.
You can also reference identifiers defined elsewhere in your code.
Read the :ref:`syntaxreference` chapter for all details on the available operators and kinds of expressions you can write.
@ -670,7 +676,7 @@ Logical expressions are expressions that calculate a boolean result: true or fal
logical expressions will compile more efficiently than when you're using regular integer type operands
(because these have to be converted to 0 or 1 every time)
You can use parentheses to group parts of an expresion to change the precedence.
You can use parentheses to group parts of an expression to change the precedence.
Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions
within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
and ``(true or false) and false`` is false instead of true.
@ -835,24 +841,18 @@ pokemon(address, value)
Doesn't have anything to do with a certain video game.
push(value)
pushes a byte value on the CPU hardware stack. Lowlevel function that should normally not be used.
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
pushw(value)
pushes a 16-bit word value on the CPU hardware stack. Lowlevel function that should normally not be used.
pushes a 16-bit word value on the CPU hardware stack. Low-level function that should normally not be used.
pop(variable)
pops a byte value off the CPU hardware stack into the given variable. Only variables can be used.
Lowlevel function that should normally not be used.
Low-level function that should normally not be used.
popw(value)
pops a 16-bit word value off the CPU hardware stack into the given variable. Only variables can be used.
Lowlevel function that should normally not be used.
rnd()
returns a pseudo-random byte from 0..255
rndw()
returns a pseudo-random word from 0..65535
Low-level function that should normally not be used.
rol(x)
Rotate the bits in x (byte or word) one position to the left.
@ -885,7 +885,7 @@ ror2(x)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
memory(name, size, alignment)
@ -903,7 +903,7 @@ memory(name, size, alignment)
You can only treat it as a pointer or use it in inline assembly.
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
Calls an assembly routine in another ram-bank on the Commander X16 (using the ``jsrfar`` routine)
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify
any address in system ram (why this can be useful is explained at the end of this paragraph)
The third argument can be used to designate the memory address
@ -917,7 +917,7 @@ callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
This is not very efficient though, so maybe you should write a small piece of inline assembly for this instead.
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another rom-bank on the CommanderX16
Calls an assembly routine in another rom-bank on the Commander X16
The banked ROM is located in the address range $C000-$FFFF (16 kilobyte).
There are 32 banks (0 to 31).
The third argument can be used to designate the memory address
@ -931,7 +931,7 @@ callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
syscall(callnr), syscall1(callnr, arg), syscall2(callnr, arg1, arg2), syscall3(callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub kernal routines at
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.

View File

@ -8,7 +8,7 @@ Module file
-----------
This is a file with the ``.p8`` suffix, containing *directives* and *code blocks*, described below.
The file is a text file wich can also contain:
The file is a text file which can also contain:
Lines, whitespace, indentation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -48,28 +48,28 @@ Directives
Global setting, selects the program launcher stub to use.
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
- type ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
- type ``basic`` : add a tiny C64 BASIC program, with a SYS statement calling into the machine code
- type ``none`` : no launcher logic is added at all
.. data:: %zeropage <style>
Level: module.
Global setting, select ZeroPage handling style. Defaults to ``kernalsafe``.
Global setting, select zeropage handling style. Defaults to ``kernalsafe``.
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
and don't change anything else. This allows full use of Kernal ROM routines (but not BASIC routines),
including default IRQs during normal system operation.
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernal). No clean exit is possible.
are required to perform floating point operations (from the BASIC Kernal). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
touch change anything else. This allows full use of BASIC and Kernal ROM routines including default IRQs
during normal system operation.
When the program exits, it simply returns to the BASIC ready prompt.
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and Kernal ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
@ -84,9 +84,9 @@ Directives
16 virtual registers cx16.r0...cx16.r15 from the Commander X16 into the zeropage as well
(but not on the same locations). They are relocated automatically by the compiler.
The other options need those locations for other things so those virtual registers have
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zero page
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zeropage
variables or pointers etc. will be a lot slower in those cases!
On the CommanderX16 the registers are always in zeropage. On other targets, for now, they
On the Commander X16 the registers are always in zeropage. On other targets, for now, they
are always outside of the zeropage.
.. data:: %zpreserved <fromaddress>,<toaddress>
@ -100,8 +100,8 @@ Directives
Level: module.
Global setting, set the program's start memory address. It's usually fixed at ``$0801`` because the
default launcher type is a CBM-basic program. But you have to specify this address yourself when
you don't use a CBM-basic launcher.
default launcher type is a CBM-BASIC program. But you have to specify this address yourself when
you don't use a CBM-BASIC launcher.
.. data:: %import <name>
@ -119,7 +119,7 @@ Directives
Sets special compiler options.
- ``enable_floats`` (module level) tells the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
to deal with floating point numbers (by using various subroutines from the Commodore 64 Kernal).
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
importing the ``floats`` library is required anyway and that will enable it for you automatically.
- ``no_sysinit`` (module level) which cause the resulting program to *not* include
@ -150,7 +150,7 @@ Directives
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all. Symbols defined in the included assembly can not be referenced
from prog8 code. However they can be referenced from other assembly code if properly prefixed.
You can ofcourse use a label in your prog8 code just before the %asminclude directive, and reference
You can of course use a label in your prog8 code just before the %asminclude directive, and reference
that particular label to get to (the start of) the included assembly.
Be careful: you risk symbol redefinitions or duplications if you include a piece of
assembly into a prog8 block that already defines symbols itself.
@ -225,7 +225,7 @@ and after that, a combination of letters, numbers, or underscores. Examples of v
Code blocks
-----------
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
A named block of actual program code. It defines a *scope* (also known as 'namespace') and
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
<blockname> [<address>] {
@ -270,7 +270,7 @@ 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 intial value yourself, zero will be used.
or an expression. If you don't provide an initial value yourself, zero will be used.
You can 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.
@ -295,7 +295,7 @@ Various examples::
byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
uword @requirezp zpaddr = $3000 ; we require this variable in Zeropage
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
word @shared asmvar ; variable is used in assembly code but not elsewhere
@ -329,7 +329,7 @@ type identifier type storage size example var declara
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``bool[]`` boolean array depends on value ``bool[] myvar = [true, false, true]`` note: consider using bit flags in a byte or word instead to save space
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
``str`` string (petscii) varies ``str myvar = "hello."``
``str`` string (PETSCII) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
@ -342,7 +342,7 @@ value is given, the array size in the declaration can be omitted.
Note that ``%`` is also the remainder operator so be careful: if you want to take the remainder
of something with an operand starting with 1 or 0, you'll have to add a space in between.
**character values:** you can use a single character in quotes like this ``'a'`` for the Petscii byte value of that character.
**character values:** you can use a single character in quotes like this ``'a'`` for the PETSCII byte value of that character.
**``byte`` versus ``word`` values:**
@ -461,7 +461,7 @@ There are several escape sequences available to put special characters into your
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
- String literals can contain many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳".
- String literals can contain many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳".
Characters like ^, _, \\, {, } and | (that have no direct PETSCII counterpart) are still accepted and converted to the closest PETSCII equivalents. (Make sure you save the source file in UTF-8 encoding if you use this.)
@ -505,7 +505,7 @@ logical: ``not`` ``and`` ``or`` ``xor``
the ``bool`` variable type instead, where this conversion doesn't need to occur.
.. note::
Unlike most other programming languages, there is no short-cirquit or McCarthy-evaluation
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
all operands from these logical expressions, even when one of them already determines the outcome!
@ -580,7 +580,7 @@ Multiple return values
^^^^^^^^^^^^^^^^^^^^^^
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernal ROM) can return more than one return value.
(referencing a routine in Kernal ROM) can return more than one return value.
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
@ -648,21 +648,22 @@ the statement body of such a subroutine should consist of just an inline assembl
The ``@ <register>`` part is required for rom and assembly-subroutines, as it specifies for the compiler
what cpu registers should take the routine's arguments. You can use the regular set of registers
(A, X, Y), the special 16-bit register pairs to take word values (AX, AY and XY) and even a processor status
(A, X, Y), special 16-bit register pairs to take word values (AX, AY and XY) and even a processor status
flag such as Carry (Pc).
It is not possible to use floating point arguments or return values in an asmsub.
.. note::
Asmsubs can also be tagged as ``inline asmsub`` to make trivial pieces of assembly inserted
directly instead of a call to them. Note that it is literal copy-paste of code that is done,
so make sure the assembly is actually written to behave like such - which probably means you
don't want a ``rts`` or ``jmp`` or ``bra`` in it!
.. note::
The 'virtual' 16-bit registers from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
This means you don't have to set them up manually before calling a subroutine that takes
one or more parameters in those 'registers'. You can just list the arguments directly.
*This also works on the Commodore-64!* (however they are not as efficient there because they're not in zeropage)
*This also works on the Commodore 64!* (however they are not as efficient there because they're not in zeropage)
In prog8 and assembly code these 'registers' are directly accessible too via
``cx16.r0`` .. ``cx16.r15`` (these are memory mapped uword values),
``cx16.r0s`` .. ``cx16.r15s`` (these are memory mapped word values),
@ -715,7 +716,8 @@ And this is a loop over the values of the array ``fibonacci_numbers``::
uword number
for number in fibonacci_numbers {
; do something with number
; do something with number...
break ; break out of the loop early
}
@ -765,7 +767,7 @@ Conditional Execution and Jumps
Unconditional jump: goto
^^^^^^^^^^^^^^^^^^^^^^^^
To jump to another part of the program, you use a ``goto`` statement with an addres or the name
To jump to another part of the program, you use a ``goto`` statement with an address or the name
of a label or subroutine::
goto $c000 ; address
@ -783,31 +785,35 @@ Note: to do an indirect *JSR* to a routine with a varying address, you can use t
(which is not very efficient) or you have to write a small piece of inline assembly.
Conditional execution
^^^^^^^^^^^^^^^^^^^^^
if statements
^^^^^^^^^^^^^
With the 'if' / 'else' statement you can execute code depending on the value of a condition::
if <expression> <statements> [else <statements> ]
where <statements> can be just a single statement for instance just a ``goto``, or it can be a block such as this::
If <statements> is just a single statement, for instance just a ``goto`` or a single assignment,
it's possible to just write the statement without any curly braces.
However if <statements> is a block of multiple statements, you'll have to enclose it in curly braces::
if <expression> {
<statements>
} else if <expression> {
<statements>
} else {
<alternative statements>
<statements>
}
**Special status register branch form:**
There is a special form of the if-statement that immediately translates into one of the 6502's branching instructions.
It is almost the same as the regular if-statement but it lacks a contional expression part, because the if-statement
It is almost the same as the regular if-statement but it lacks a conditional expression part, because the if-statement
itself defines on what status register bit it should branch on::
if_XX <statements> [else <statements> ]
where <statements> can be just a single statement for instance just a ``goto``, or it can be a block such as this::
where <statements> can be just a single statement or a block again::
if_XX {
<statements>
@ -822,7 +828,7 @@ It can also be one of the four aliases that are easier to read: ``if_z``, ``if_n
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
This is not always the case after a function call or other operations!
If in doubt, check the generated assembly code!

View File

@ -20,7 +20,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar
This chapter explains some relevant system details of the c64 and cx16 machines.
.. hint::
If you only use standard kernal and prog8 library routines,
If you only use standard Kernal and prog8 library routines,
it is often possible to compile the *exact same program* for
different machines (just change the compilation target flag)!
@ -39,7 +39,7 @@ This is a hard limit: there is no built-in support for RAM expansions or bank sw
====================== ================== ========
memory area type note
====================== ================== ========
``$00``--``$ff`` ZeroPage contains many sensitive system variables
``$00``--``$ff`` zeropage contains many sensitive system variables
``$100``--``$1ff`` Hardware stack used by the CPU, normally not accessed directly
``$0200``--``$ffff`` Free RAM or ROM free to use memory area, often a mix of RAM and ROM
====================== ================== ========
@ -64,9 +64,9 @@ reserved address in use for
================== =======================
The actual machine will often have many other special addresses as well,
For example, the Commodore-64 has:
For example, the Commodore 64 has:
- ROMs installed in the machine: BASIC, kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
- ROMs installed in the machine: BASIC, Kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
- memory-mapped I/O registers, for the video and sound chips, and the CIA's. Occupying ``$d000``--``$dfff``.
- RAM areas that are used for screen graphics and sprite data: usually at ``$0400``--``$07ff``.
@ -75,18 +75,18 @@ Prog8 programs can access all of those special memory locations but it will have
.. _zeropage:
ZeroPage ("ZP")
Zeropage ("ZP")
---------------
The ZeroPage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
The zeropage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
they take less clock cycles to access and need fewer instruction bytes than accessing other memory locations outside of the ZP.
Theoretically they can all be used in a program, with the follwoing limitations:
Theoretically they can all be used in a program, with the following limitations:
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use
- most other addresses will already be in use by the machine's operating system or kernal,
- most other addresses will already be in use by the machine's operating system or Kernal,
and overwriting them will probably crash the machine. It is possible to use all of these
yourself, but only if the program takes over the entire system (and seizes control from the regular kernal).
This means it can no longer use (most) BASIC and kernal routines from ROM.
yourself, but only if the program takes over the entire system (and seizes control from the regular Kernal).
This means it can no longer use (most) BASIC and Kernal routines from ROM.
- it's more convenient and safe to let the compiler allocate these addresses for you and just
use symbolic names in the program code.
@ -95,19 +95,19 @@ It will use the free ZP addresses to place its ZP variables in,
until they're all used up. If instructed to output a program that takes over the entire
machine, (almost) all of the ZP addresses are suddenly available and will be used.
**ZeroPage handling is configurable:**
**zeropage handling is configurable:**
There's a global program directive to specify the way the compiler
treats the ZP for the program. The default is to be reasonably restrictive to use the
part of the ZP that is not used by the C64's kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
part of the ZP that is not used by the C64's Kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or Kernal).
If you want, it's also possible to be more restrictive and stay clear of the addresses used by BASIC routines too.
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
IRQs and the ZeroPage
IRQs and the zeropage
^^^^^^^^^^^^^^^^^^^^^
The normal IRQ routine in the C-64's kernal will read and write several addresses in the ZP
The normal IRQ routine in the C64's Kernal will read and write several addresses in the ZP
(such as the system's software jiffy clock which sits in ``$a0 - $a2``):
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
@ -144,7 +144,7 @@ IRQ Handling
Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler (for clean separation, it is advised to define it inside its own block).
There are a few library routines available to make setting up C-64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
There are a few library routines available to make setting up C64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
For the C64 these routines are::
@ -155,7 +155,7 @@ For the C64 these routines are::
And for the Commander X16::
cx16.set_irq(uword handler_address, boolean useKernal) ; vsync irq
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables Kernal irq handler! sys.wait() won't work anymore
cx16.restore_irq() ; set everything back to the systems default irq handler
@ -163,5 +163,5 @@ The Commander X16 provides two additional routines that should be used *in your
cx16.push_vera_context()
; ... do your work that uses vera here...
cx15.pop_vera_context()
cx16.pop_vera_context()

View File

@ -7,7 +7,7 @@ All variables are static in memory
All variables are allocated statically, there is no concept of dynamic heap or stack frames.
Essentially all variables are global (but scoped) and can be accessed and modified anywhere,
but care should be taken ofcourse to avoid unexpected side effects.
but care should be taken of course to avoid unexpected side effects.
Especially when you're dealing with interrupts or re-entrant routines: don't modify variables
that you not own or else you will break stuff.
@ -24,7 +24,7 @@ directly into the target variable, register, or memory location.
The software stack is implemented as follows:
- 2 pages of memory are allocated for this, exact locations vary per machine target.
For the C-64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
For the C64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
For the Commander X16 they are set at $0400 and $0500 (so $0400-$05ff are reserved).
This default location can be overridden using the `-esa` command line option.
- these are the high and low bytes of the values on the stack (it's a 'split 16 bit word stack')
@ -42,7 +42,7 @@ Calling a subroutine requires three steps:
#. preparing the arguments (if any) and passing them to the routine
#. calling the routine
#. preparig the return value (if any) and returning that from the call.
#. preparing the return value (if any) and returning that from the call.
Calling the routine is just a simple JSR instruction, but the other two work like this:
@ -51,7 +51,7 @@ Calling the routine is just a simple JSR instruction, but the other two work lik
``asmsub`` routines
^^^^^^^^^^^^^^^^^^^
These are usually declarations of kernal (ROM) routines or low-level assembly only routines,
These are usually declarations of Kernal (ROM) routines or low-level assembly only routines,
that have their arguments solely passed into specific registers.
Sometimes even via a processor status flag such as the Carry flag.
Return values also via designated registers.
@ -73,7 +73,7 @@ regular subroutines
- the return value is passed back to the caller via cpu register(s):
Byte values will be put in ``A`` .
Word values will be put in ``A`` + ``Y`` register pair.
Float values will be put in the ``FAC1`` float 'register' (Basic allocated this somewhere in ram).
Float values will be put in the ``FAC1`` float 'register' (BASIC allocated this somewhere in ram).
Calls to builtin functions are treated in a special way:
@ -83,7 +83,7 @@ Some builtin functions have a fully custom implementation.
The compiler will warn about routines that are called and that return a value, if you're not
doing something with that returnvalue. This can be on purpuse if you're simply not interested in it.
doing something with that returnvalue. This can be on purpose if you're simply not interested in it.
Use the ``void`` keyword in front of the subroutine call to get rid of the warning in that case.
@ -92,7 +92,7 @@ The 6502 CPU's X-register: off-limits
Prog8 uses the cpu's X-register as a pointer in its internal expression evaluation stack.
When only writing code in Prog8, this is taken care of behind the scenes for you by the compiler.
However when you are including or linking with assembly routines or kernal/ROM calls that *do*
However when you are including or linking with assembly routines or Kernal/ROM calls that *do*
use the X register (either clobbering it internally, or using it as a parameter, or return value register),
those calls will destroy Prog8's stack pointer and this will result in invalid calculations.
@ -138,8 +138,8 @@ Some notes and references into the compiler's source code modules:
Most notably, node type information is now baked in. (``codeCore`` module)
#. An *Intermediate Representation* has been defined that is generated from the intermediate AST. This IR
is more or less a machine code language for a virtual machine - and indeed this is what the built-in
prog8 VM will execute if you use the 'virtual' compilaton target and use ``-emu`` to launch the VM.
(``intermediate`` and ``codeGenIntermediate`` modules, and ``codeGenVirtual`` and ``virtualmachine`` module for the VM related stuff)
prog8 VM will execute if you use the 'virtual' compilation target and use ``-emu`` to launch the VM.
(``intermediate`` and ``codeGenIntermediate`` modules, and ``virtualmachine`` module for the VM related stuff)
#. Currently the 6502 ASM code generator still works directly on the *Compiler AST*. A future version
should replace this by working on the IR code, and should be much smaller and simpler.
(``codeGenCpu6502`` module)

View File

@ -3,6 +3,9 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- ir: register allocation per data type a specific allocation, so we are certain when a reg is used it's just for one specific datatype
- ir: write addresses as hex into p8ir file
...
@ -17,12 +20,12 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- vm/ir: all(), any(), reverse() and sort() still depend on a VM Syscall. Get rid of this. (maybe use a IR 'builtin' function?)
- vm/ir: put variables and arrays in BSS section (unless -noreinit is specified)
- vm: Jumps go to a code block rather than a specific address(label) -> also helps future dead code elimination?
- vm: the above means that every label introduces a new code block. This eliminates the use of actual labels altogether.
- vm: add more optimizations in IRPeepholeOptimizer
- vm: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us)
- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code
- ir: mechanism to determine for chunks which registers are getting input values from "outside"
- ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk)
- ir: peephole opt: renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
- ir peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out!)
- ir: add more optimizations in IRPeepholeOptimizer
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.
this is documented behavior to now loop around but it's too easy to forget about!
Lot of work because of so many special cases in ForLoopsAsmgen.....
@ -51,18 +54,15 @@ Libraries:
- fix the problems in atari target, and flesh out its libraries.
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- optimize several inner loops in gfx2 even further?
- add modes 3 and perhaps even 2 to gfx2 (16 color and 4 color)?
- add a flood fill routine to gfx2?
- add modes 3 and perhaps even 2 to gfx2 (lores 16 color and 4 color)?
- add a flood fill (span fill/scanline fill) routine to gfx2?
Expressions:
- rethink the whole "isAugmentable" business. Because the way this is determined, should always also be exactly mirrorred in the AugmentableAssignmentAsmGen or you'll get a crash at code gen time.
note: the new Ast doesn't need this any more so maybe we can get rid of it altogether in the old AST - but it's still used for something in the UnusedCodeRemover.
- can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer
- rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables'?
"Three address code" was mentioned. https://en.wikipedia.org/wiki/Three-address_code
these variables have to be unique for each subroutine because they could otherwise be interfered with from irq routines etc.
- rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code
that, for instance, uses a fixed number of predetermined value 'variables'?
The VM IL solves this already (by using unlimited registers) but that still lacks a translation to 6502.
- this removes the need for the BinExprSplitter? (which is problematic and very limited now)
and perhaps the assignment splitting in BeforeAsmAstChanger too

View File

@ -1,5 +1,6 @@
%import syslib
%import textio
%import math
%import test_stack
%zeropage basicsafe
@ -41,7 +42,7 @@ main {
active_height--
upwards = false
} else {
target_height = 8 + rnd() % 16
target_height = 8 + math.rnd() % 16
if upwards
mountain = 233
else
@ -56,7 +57,7 @@ main {
txt.scroll_left(true)
; float the balloon
if rnd() & %10000
if math.rnd() & %10000
c64.SPXY[1] ++
else
c64.SPXY[1] --
@ -70,10 +71,10 @@ main {
txt.setcc(39, yy, 160, 8) ; draw mountain
}
yy = rnd()
yy = math.rnd()
if yy > 100 {
; draw a star
txt.setcc(39, yy % (active_height-1), '.', rnd())
txt.setcc(39, yy % (active_height-1), '.', math.rnd())
}
if yy > 200 {
@ -84,7 +85,7 @@ main {
tree = 88
else if yy & %00100000 != 0
tree = 65
if rnd() > 130
if math.rnd() > 130
treecolor = 13
txt.setcc(39, active_height, tree, treecolor)
}

View File

@ -1,4 +1,5 @@
%import textio
%import math
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
@ -22,11 +23,11 @@ main {
; Setup Starting Ball Positions
ubyte lp
for lp in 0 to ballCount-1 {
BX[lp] = rnd() % txt.DEFAULT_WIDTH
BY[lp] = rnd() % txt.DEFAULT_HEIGHT
BC[lp] = rnd() & 15
DX[lp] = rnd() & 1
DY[lp] = rnd() & 1
BX[lp] = math.rnd() % txt.DEFAULT_WIDTH
BY[lp] = math.rnd() % txt.DEFAULT_HEIGHT
BC[lp] = math.rnd() & 15
DX[lp] = math.rnd() & 1
DY[lp] = math.rnd() & 1
}
; display balls

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