Compare commits

...

85 Commits
v8.9 ... v8.10

Author SHA1 Message Date
8acd94fc89 avoid work 2023-03-05 12:32:58 +01:00
1436480eab added a few more comparison expression optimizations 2023-03-04 16:01:40 +01:00
448d176c24 fix vm crash on empty string 2023-03-04 15:35:54 +01:00
fd269453a4 todos 2023-03-04 14:14:01 +01:00
b3b380964c remove searchParameter() from lookups
it shouldn't be needed to look up subroutine parameters by scoped name
2023-03-04 13:24:33 +01:00
6e9025ebf2 cx16 fix irq statusbit handling and kefrenbars example 2023-03-03 21:58:08 +01:00
3922691b3c limit to 48828 hz sample rate (vera max) 2023-03-03 18:04:21 +01:00
0545b77cf4 ask for filename 2023-03-03 17:24:16 +01:00
6b3f39fa1a oops 2023-03-03 17:17:19 +01:00
3114ab87dc add 8 bit sample width support 2023-03-03 17:12:44 +01:00
00bc99cc7b added cx16/stream-wav example, refactor pcmaudio code 2023-03-03 14:18:13 +01:00
540b3ae2f4 tweak BinaryExpression splitting 2023-02-28 21:45:38 +01:00
dbfe4140e1 improved import search paths 2023-02-28 20:08:11 +01:00
d3675ec254 gone, deprecated 2023-02-27 23:41:22 +01:00
ded2483fc0 cx16 startup code now properly turns off mouse cursor 2023-02-27 23:35:42 +01:00
e62ea388e0 tweak cx16 adpcm example 2023-02-24 01:38:03 +01:00
f20356e9be cx16.callfar signature has been changed to be easier to use 2023-02-23 23:06:20 +01:00
d282a2d846 remove cx16.callrom() just use callfar 2023-02-23 23:02:56 +01:00
4641ac46e7 extra question in porting guide for high ram 2023-02-22 22:56:43 +01:00
ba9268a09e added -varshigh compiler option to move BSS section.
Documented BSS a bit in the manual.
2023-02-22 22:44:29 +01:00
fb9902c536 avoid const fold loop on const bool thing=true
fixes #97
2023-02-22 21:27:08 +01:00
5318ba6c6e shrink evalstack from 2 to 1 page
c64=$cf00-$cfff, x16: $0700-$07ff
2023-02-21 22:52:04 +01:00
fd5ebef488 cx16 startup code now also selects ram bank 1 2023-02-21 21:53:32 +01:00
d9e4f39ddc memset BSS section to zero all at once, less individual var=0 assigns 2023-02-21 00:26:21 +01:00
435b9d8973 get rid of 'noreinit' option for now, because it resulted in unreliable code 2023-02-20 23:29:16 +01:00
0ea70ba656 fix proper initialization of zeropagevars with 'noreinit' 2023-02-20 23:05:27 +01:00
92a07b87d2 clearer 2023-02-20 02:32:36 +01:00
c3c82282ba reinitGlobals option is clearer than the inverse 2023-02-19 19:09:29 +01:00
adc15c24ef introduce bss segments 2023-02-19 18:12:37 +01:00
dddf9a9396 remove explicit 'bss' from St var, changed to 'uninitialized' 2023-02-19 16:50:06 +01:00
9ca6860ffa tweak 2023-02-19 15:08:16 +01:00
f7dd388954 remove unsupported floats.FTOSWRDAY routine. Fixes #96 2023-02-17 18:05:46 +01:00
6012839f0e todo 2023-02-16 23:06:09 +01:00
8e9cbab053 todo 2023-02-16 22:53:16 +01:00
aaf375a57b move some utility methods into Pt Ast nodes itself 2023-02-16 22:45:35 +01:00
3cce985f03 check float bits 2023-02-16 22:22:12 +01:00
c59df6ec20 optimize isZpVar 2023-02-16 00:41:20 +01:00
5c3f41f64d reintroduce explicit PtAugmentedAssign ast node 2023-02-15 22:54:32 +01:00
cf3523f49f Merge branch 'codegen-on-new-ast' 2023-02-14 22:48:11 +01:00
db794752cb fix ast error on inline sub 2023-02-14 22:37:33 +01:00
bceaebe856 fix crash on sort/reverse unused arrays
fixes #95
2023-02-14 00:26:29 +01:00
3916de2921 attempt to clarify docs of cx16.numbanks() 2023-02-13 23:45:53 +01:00
9e0f8c1a97 remove avg() from syntax defs, it doesn't exist anymore 2023-02-13 22:31:06 +01:00
0cbc56b82e remove unused ast print func 2023-02-13 00:19:48 +01:00
b95608f68a new common ICodeGeneratorBackend interface for all code generator classes 2023-02-12 23:52:54 +01:00
b6e5dbd06c optimized away VarDecl.subroutineParameter 2023-02-12 23:19:35 +01:00
f09bcf3fcf Merge branch 'master' into codegen-on-new-ast 2023-02-12 17:36:18 +01:00
75d486b124 fix variable node casting 2023-02-12 17:04:58 +01:00
4914609485 local varnames and fix uninitialized parents 2023-02-12 16:00:58 +01:00
75bd66326a fix variable zpwish 2023-02-11 15:18:57 +01:00
8f904f75bb Merge branch 'master' into codegen-on-new-ast 2023-02-11 14:40:23 +01:00
ed68d604d6 fix break as indirect jump
fix subroutine param scoped name
2023-02-11 01:21:27 +01:00
f83752f43b update compiler internals diagram 2023-02-09 23:15:19 +01:00
86c22636eb Merge branch 'master' into codegen-on-new-ast 2023-02-09 23:05:54 +01:00
30d20a453b tweak SymbolTable and fix its unittest 2023-02-09 22:58:21 +01:00
fe29d8a23f tweak codegen of inline sub 2023-02-09 21:59:09 +01:00
694d088160 some cleanups about asmsub return registers and types 2023-02-09 03:19:57 +01:00
6aabbffc62 some cleanups 2023-02-09 02:34:18 +01:00
623329fb33 fix 2023-02-05 17:08:24 +01:00
9f0074eef9 Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	codeCore/src/prog8/code/ast/AstStatements.kt
2023-02-05 16:44:30 +01:00
f117805129 order 2023-02-05 12:36:32 +01:00
c75b1581d2 lookup via new ST 2023-02-05 01:15:23 +01:00
109e118aba fix sub return register 2023-02-03 21:16:44 +01:00
201b77d5b6 boolean vs byte cast fixing, and pointervar error 2023-02-02 00:57:20 +01:00
a5ca08f33d fix popCpuStack to load values into asmsub register params 2023-02-01 22:00:37 +01:00
988a3e4446 group the three Pt nodes that represent a variable in the p8 source under single interface IPtVariable 2023-01-31 23:29:15 +01:00
0f5cd22bb7 more codegen fixes 2023-01-31 22:57:26 +01:00
2f5bed36b3 remove bool to ubyte typecasts 2023-01-31 01:25:44 +01:00
5b6534bb28 fix symbol lookup in new ast and minor codegen errors 2023-01-31 00:18:21 +01:00
e31e5b2477 got rid of PtScopeVarsDecls 2023-01-29 13:49:27 +01:00
07d5fafe2e Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
2023-01-29 13:34:00 +01:00
8a4979f44c vm target 'zeropage' more robust 2023-01-29 12:47:12 +01:00
e67464325f fix missing symboltable entries for asmgen 2023-01-28 00:00:23 +01:00
94c9b0d23b Merge branch 'master' into codegen-on-new-ast 2023-01-27 22:14:57 +01:00
c78d1e3c39 implemented Pt findTarget and siblings 2023-01-27 01:51:21 +01:00
e94319145f test 2023-01-26 01:41:44 +01:00
3f3b01b5f6 Merge branch 'master' into codegen-on-new-ast 2023-01-26 01:40:30 +01:00
4e8ccf0ef3 Merge branch 'master' into codegen-on-new-ast 2023-01-26 00:38:54 +01:00
48c9349ce9 working on codegen fixes 2023-01-25 01:57:25 +01:00
117d848466 consolidate builtin function definitions into codeCore 2023-01-25 00:23:00 +01:00
99c62aab36 Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	examples/test.p8
2023-01-24 01:51:20 +01:00
a3c0c7c96f Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
#	examples/test.p8
2023-01-22 18:30:37 +01:00
9b209823f6 simple test 2023-01-22 17:10:52 +01:00
b2cb125bd4 more 6502 codegen on new Pt-AST. 2023-01-22 17:10:52 +01:00
5e8f767642 6502 codegen on new Pt-AST. 2023-01-22 17:10:52 +01:00
152 changed files with 4855 additions and 4196 deletions

View File

@ -1,35 +0,0 @@
#### Just a few remarks upfront:
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
#### Problems with `compilerAst`:
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
#### Steps to take, in conceptual (!) order:
(note: all these steps have been implemented, rejected or otherwise solved now.)
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
- from the local file system (use case: user programs)
- from resources (prog8lib)
- from plain strings (for testing)
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
- plus, optionally, method's for registering/unregistering a listener with the parser
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
6. move `IStringEncoding` to module `compiler`
7. same with `ModuleImporter`, then rewrite that (addressing #46)
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
9. re-think `IStringEncoding` to address #38

View File

@ -1,5 +1,7 @@
package prog8.code package prog8.code
import prog8.code.ast.PtNode
import prog8.code.ast.PtProgram
import prog8.code.core.* import prog8.code.core.*
@ -7,7 +9,7 @@ import prog8.code.core.*
* Tree structure containing all symbol definitions in the program * Tree structure containing all symbol definitions in the program
* (blocks, subroutines, variables (all types), memoryslabs, and labels). * (blocks, subroutines, variables (all types), memoryslabs, and labels).
*/ */
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) { class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) {
/** /**
* The table as a flat mapping of scoped names to the StNode. * The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes) * This gives the fastest lookup possible (no need to traverse tree nodes)
@ -52,7 +54,17 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
} }
val allMemorySlabs: Collection<StMemorySlab> by lazy { val allMemorySlabs: Collection<StMemorySlab> by lazy {
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null } val vars = mutableListOf<StMemorySlab>()
fun collect(node: StNode) {
for(child in node.children) {
if(child.value.type== StNodeType.MEMORYSLAB)
vars.add(child.value as StMemorySlab)
else
collect(child.value)
}
}
collect(this)
vars
} }
override fun lookup(scopedName: String) = flat[scopedName] override fun lookup(scopedName: String) = flat[scopedName]
@ -76,7 +88,7 @@ enum class StNodeType {
open class StNode(val name: String, open class StNode(val name: String,
val type: StNodeType, val type: StNodeType,
val position: Position, val astNode: PtNode,
val children: MutableMap<String, StNode> = mutableMapOf() val children: MutableMap<String, StNode> = mutableMapOf()
) { ) {
@ -145,33 +157,31 @@ open class StNode(val name: String,
class StStaticVariable(name: String, class StStaticVariable(name: String,
val dt: DataType, val dt: DataType,
val bss: Boolean,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: StString?, val onetimeInitializationStringValue: StString?,
val onetimeInitializationArrayValue: StArray?, val onetimeInitializationArrayValue: StArray?,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
val zpwish: ZeropageWish, val zpwish: ZeropageWish, // used in the variable allocator
position: Position) : StNode(name, StNodeType.STATICVAR, position) { astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
init { init {
if(bss) {
require(onetimeInitializationNumericValue==null)
require(onetimeInitializationStringValue==null)
require(onetimeInitializationArrayValue.isNullOrEmpty())
} else {
require(onetimeInitializationNumericValue!=null ||
onetimeInitializationStringValue!=null ||
onetimeInitializationArrayValue!=null)
}
if(length!=null) { if(length!=null) {
require(onetimeInitializationNumericValue == null) require(onetimeInitializationNumericValue == null)
if(onetimeInitializationArrayValue!=null) if(onetimeInitializationArrayValue!=null)
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length) require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
} }
if(onetimeInitializationNumericValue!=null) if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes) require(dt in NumericDatatypes)
if(onetimeInitializationArrayValue!=null) require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
}
if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes) require(dt in ArrayDatatypes)
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
}
if(onetimeInitializationStringValue!=null) { if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR) require(dt == DataType.STR)
require(length == onetimeInitializationStringValue.first.length+1) require(length == onetimeInitializationStringValue.first.length+1)
@ -180,8 +190,8 @@ class StStaticVariable(name: String,
} }
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) : class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
StNode(name, StNodeType.CONSTANT, position) { StNode(name, StNodeType.CONSTANT, astNode) {
} }
@ -189,31 +199,31 @@ class StMemVar(name: String,
val dt: DataType, val dt: DataType,
val address: UInt, val address: UInt,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
position: Position) : astNode: PtNode) :
StNode(name, StNodeType.MEMVAR, position) { StNode(name, StNodeType.MEMVAR, astNode) {
} }
class StMemorySlab( class StMemorySlab(
name: String, name: String,
val size: UInt, val size: UInt,
val align: UInt, val align: UInt,
position: Position astNode: PtNode
): ):
StNode(name, StNodeType.MEMORYSLAB, position) { StNode(name, StNodeType.MEMORYSLAB, astNode) {
} }
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) : class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
StNode(name, StNodeType.SUBROUTINE, position) { StNode(name, StNodeType.SUBROUTINE, astNode) {
} }
class StRomSub(name: String, class StRomSub(name: String,
val address: UInt, val address: UInt,
val parameters: List<StRomSubParameter>, val parameters: List<StRomSubParameter>,
val returns: List<RegisterOrStatusflag>, val returns: List<StRomSubParameter>,
position: Position) : astNode: PtNode) :
StNode(name, StNodeType.ROMSUB, position) { StNode(name, StNodeType.ROMSUB, astNode)
}
class StSubroutineParameter(val name: String, val type: DataType) class StSubroutineParameter(val name: String, val type: DataType)

View File

@ -0,0 +1,207 @@
package prog8.code
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import java.util.*
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
fun make(): SymbolTable {
val st = SymbolTable(program)
BuiltinFunctions.forEach {
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
}
val scopestack = Stack<StNode>()
scopestack.push(st)
program.children.forEach {
addToSt(it, scopestack)
}
require(scopestack.size==1)
if(options.compTarget.name != VMTarget.NAME) {
listOf(
PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_B1, null, Position.DUMMY),
PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_REG, null, Position.DUMMY),
PtMemMapped("P8ZP_SCRATCH_W1", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W1, null, Position.DUMMY),
PtMemMapped("P8ZP_SCRATCH_W2", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W2, null, Position.DUMMY),
PtMemMapped("P8ESTACK_LO", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_LO, 256u, Position.DUMMY),
PtMemMapped("P8ESTACK_HI", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_HI, 256u, Position.DUMMY)
).forEach {
it.parent = program
st.add(StMemVar(it.name, it.type, it.address, null, it))
}
}
return st
}
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
val stNode = when(node) {
is PtAsmSub -> {
if(node.address==null) {
val params = node.parameters.map { StSubroutineParameter(it.second.name, it.second.type) }
StSub(node.name, params, node.returns.singleOrNull()?.second, node)
} else {
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
StRomSub(node.name, node.address, parameters, returns, node)
}
}
is PtBlock -> {
StNode(node.name, StNodeType.BLOCK, node)
}
is PtConstant -> {
StConstant(node.name, node.type, node.value, node)
}
is PtLabel -> {
StNode(node.name, StNodeType.LABEL, node)
}
is PtMemMapped -> {
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
}
is PtSub -> {
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
StSub(node.name, params, node.returntype, node)
}
is PtVariable -> {
val initialNumeric: Double?
val initialString: StString?
val initialArray: StArray?
val numElements: Int?
val value = node.value
if(value!=null) {
val number = (value as? PtNumber)?.number
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
when (value) {
is PtString -> {
initialString = StString(value.value, value.encoding)
initialArray = null
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
val array = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialString = null
numElements = array.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
initialString = null
initialArray = null
numElements = node.arraySize?.toInt()
}
}
} else {
initialNumeric = null
initialArray = null
initialString = null
numElements = node.arraySize?.toInt()
}
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
}
is PtBuiltinFunctionCall -> {
if(node.name=="memory") {
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
require(node.name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val slabname = (node.args[0] as PtString).value
val size = (node.args[1] as PtNumber).number.toUInt()
val align = (node.args[2] as PtNumber).number.toUInt()
// don't add memory slabs in nested scope, just put them in the top level of the ST
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
}
null
}
else -> null // node is not present in the ST
}
if(stNode!=null) {
scope.peek().add(stNode)
scope.push(stNode)
}
node.children.forEach {
addToSt(it, scope)
}
if(stNode!=null)
scope.pop()
}
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
return value.children.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier.name)
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
else -> throw AssemblyError("invalid array element $it")
}
}
}
}
// override fun visit(decl: VarDecl) {
// val node =
// when(decl.type) {
// VarDeclType.VAR -> {
// var initialNumeric = (decl.value as? NumericLiteral)?.number
// if(initialNumeric==0.0)
// initialNumeric=null // variable will go into BSS and this will be set to 0
// val initialStringLit = decl.value as? StringLiteral
// val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
// val initialArrayLit = decl.value as? ArrayLiteral
// val initialArray = makeInitialArray(initialArrayLit)
// if(decl.isArray && decl.datatype !in ArrayDatatypes)
// throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
// val numElements =
// if(decl.isArray)
// decl.arraysize!!.constIndex()
// else if(initialStringLit!=null)
// initialStringLit.value.length+1 // include the terminating 0-byte
// else
// null
// val bss = if(decl.datatype==DataType.STR)
// false
// else if(decl.isArray)
// initialArray.isNullOrEmpty()
// else
// initialNumeric == null
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
// StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, astNode, decl.position)
// }
// VarDeclType.CONST -> {
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
// StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, astNode, decl.position)
// }
// VarDeclType.MEMORY -> {
// val numElements =
// if(decl.datatype in ArrayDatatypes)
// decl.arraysize!!.constIndex()
// else null
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
// StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, astNode, decl.position)
// }
// }
// scopestack.peek().add(node)
// // st.origAstLinks[decl] = node
// }
//
// private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
// if(arrayLit==null)
// return null
// return arrayLit.value.map {
// when(it){
// is AddressOf -> {
// val scopedName = it.identifier.targetNameAndType(program).first
// StArrayElement(null, scopedName)
// }
// is IdentifierReference -> {
// val scopedName = it.targetNameAndType(program).first
// StArrayElement(null, scopedName)
// }
// is NumericLiteral -> StArrayElement(it.number, null)
// else -> throw FatalAstException("weird element dt in array literal")
// }
// }.toList()
// }
//

View File

@ -3,6 +3,7 @@ package prog8.code.ast
import prog8.code.core.IMemSizer import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding import prog8.code.core.IStringEncoding
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.core.SourceCode
import java.nio.file.Path import java.nio.file.Path
// New simplified AST for the code generator. // New simplified AST for the code generator.
@ -13,16 +14,6 @@ sealed class PtNode(val position: Position) {
val children = mutableListOf<PtNode>() val children = mutableListOf<PtNode>()
lateinit var parent: PtNode lateinit var parent: PtNode
fun printIndented(indent: Int) {
print(" ".repeat(indent))
print("${this.javaClass.simpleName} ")
printProperties()
println()
children.forEach { it.printIndented(indent+1) }
}
abstract fun printProperties()
fun add(child: PtNode) { fun add(child: PtNode) {
children.add(child) children.add(child)
child.parent = this child.parent = this
@ -36,12 +27,11 @@ sealed class PtNode(val position: Position) {
fun definingBlock() = findParentNode<PtBlock>(this) fun definingBlock() = findParentNode<PtBlock>(this)
fun definingSub() = findParentNode<PtSub>(this) fun definingSub() = findParentNode<PtSub>(this)
fun definingAsmSub() = findParentNode<PtAsmSub>(this) fun definingAsmSub() = findParentNode<PtAsmSub>(this)
fun definingISub() = findParentNode<IPtSubroutine>(this)
} }
class PtNodeGroup : PtNode(Position.DUMMY) { class PtNodeGroup : PtNode(Position.DUMMY)
override fun printProperties() {}
}
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) { sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
@ -65,10 +55,6 @@ class PtProgram(
val memsizer: IMemSizer, val memsizer: IMemSizer,
val encoding: IStringEncoding val encoding: IStringEncoding
) : PtNode(Position.DUMMY) { ) : PtNode(Position.DUMMY) {
fun print() = printIndented(0)
override fun printProperties() {
print("'$name'")
}
// fun allModuleDirectives(): Sequence<PtDirective> = // fun allModuleDirectives(): Sequence<PtDirective> =
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct() // children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
@ -86,12 +72,9 @@ class PtBlock(name: String,
val library: Boolean, val library: Boolean,
val forceOutput: Boolean, val forceOutput: Boolean,
val alignment: BlockAlignment, val alignment: BlockAlignment,
val source: SourceCode, // taken from the module the block is defined in.
position: Position position: Position
) : PtNamedNode(name, position) { ) : PtNamedNode(name, position) {
override fun printProperties() {
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
}
enum class BlockAlignment { enum class BlockAlignment {
NONE, NONE,
WORD, WORD,
@ -101,8 +84,6 @@ class PtBlock(name: String,
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) { class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
override fun printProperties() {}
init { init {
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" } require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" } require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
@ -110,28 +91,16 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
} }
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) { class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
override fun printProperties() {
print(name)
}
}
class PtBreakpoint(position: Position): PtNode(position) { class PtBreakpoint(position: Position): PtNode(position)
override fun printProperties() {}
}
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) { class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
override fun printProperties() {
print("filename=$file offset=$offset length=$length")
}
}
class PtNop(position: Position): PtNode(position) { class PtNop(position: Position): PtNode(position)
override fun printProperties() {}
}
// find the parent node of a specific type or interface // find the parent node of a specific type or interface

View File

@ -1,9 +1,8 @@
package prog8.code.ast package prog8.code.ast
import prog8.code.core.DataType import prog8.code.core.*
import prog8.code.core.Encoding
import prog8.code.core.Position
import java.util.* import java.util.*
import kotlin.math.abs
import kotlin.math.round import kotlin.math.round
@ -23,10 +22,6 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
} }
} }
override fun printProperties() {
print(type)
}
infix fun isSameAs(other: PtExpression): Boolean { infix fun isSameAs(other: PtExpression): Boolean {
return when(this) { return when(this) {
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
@ -43,6 +38,69 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
else -> false else -> false
} }
} }
infix fun isSameAs(target: PtAssignTarget): Boolean {
return when {
target.memory != null && this is PtMemoryByte-> {
target.memory!!.address isSameAs this.address
}
target.identifier != null && this is PtIdentifier -> {
this.name == target.identifier!!.name
}
target.array != null && this is PtArrayIndexer -> {
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
}
else -> false
}
}
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
fun isSimple(): Boolean {
return when(this) {
is PtAddressOf -> true
is PtArray -> true
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
is PtBinaryExpression -> false
is PtBuiltinFunctionCall -> name in arrayOf("msb", "lsb", "peek", "peekw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
is PtContainmentCheck -> false
is PtFunctionCall -> false
is PtIdentifier -> true
is PtMachineRegister -> true
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
is PtNumber -> true
is PtPrefix -> value.isSimple()
is PtRange -> true
is PtString -> true
is PtTypeCast -> value.isSimple()
}
}
/*
fun clone(): PtExpression {
fun withClonedChildrenFrom(orig: PtExpression, clone: PtExpression): PtExpression {
orig.children.forEach { clone.add((it as PtExpression).clone()) }
return clone
}
when(this) {
is PtAddressOf -> return withClonedChildrenFrom(this, PtAddressOf(position))
is PtArray -> return withClonedChildrenFrom(this, PtArray(type, position))
is PtArrayIndexer -> return withClonedChildrenFrom(this, PtArrayIndexer(type, position))
is PtBinaryExpression -> return withClonedChildrenFrom(this, PtBinaryExpression(operator, type, position))
is PtBuiltinFunctionCall -> return withClonedChildrenFrom(this, PtBuiltinFunctionCall(name, void, hasNoSideEffects, type, position))
is PtContainmentCheck -> return withClonedChildrenFrom(this, PtContainmentCheck(position))
is PtFunctionCall -> return withClonedChildrenFrom(this, PtFunctionCall(name, void, type, position))
is PtIdentifier -> return withClonedChildrenFrom(this, PtIdentifier(name, type, position))
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
is PtTypeCast -> return withClonedChildrenFrom(this, PtTypeCast(type, position))
}
}
*/
} }
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) { class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
@ -51,11 +109,15 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
} }
class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, position) { class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
val variable: PtIdentifier val variable: PtIdentifier
get() = children[0] as PtIdentifier get() = children[0] as PtIdentifier
val index: PtExpression val index: PtExpression
get() = children[1] as PtExpression get() = children[1] as PtExpression
init {
require(elementType in NumericDatatypes)
}
} }
@ -66,6 +128,9 @@ class PtArray(type: DataType, position: Position): PtExpression(type, position)
return false return false
return type==other.type && children == other.children return type==other.type && children == other.children
} }
val size: Int
get() = children.size
} }
@ -81,9 +146,6 @@ class PtBuiltinFunctionCall(val name: String,
val args: List<PtExpression> val args: List<PtExpression>
get() = children.map { it as PtExpression } get() = children.map { it as PtExpression }
override fun printProperties() {
print("$name void=$void noSideFx=$hasNoSideEffects")
}
} }
@ -93,10 +155,6 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
get() = children[0] as PtExpression get() = children[0] as PtExpression
val right: PtExpression val right: PtExpression
get() = children[1] as PtExpression get() = children[1] as PtExpression
override fun printProperties() {
print("$operator -> $type")
}
} }
@ -119,28 +177,25 @@ class PtFunctionCall(val name: String,
val args: List<PtExpression> val args: List<PtExpression>
get() = children.map { it as PtExpression } get() = children.map { it as PtExpression }
override fun printProperties() {
print("$name void=$void")
}
} }
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) { class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position)
override fun printProperties() {
print("$name $type")
}
}
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) { class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
val address: PtExpression val address: PtExpression
get() = children.single() as PtExpression get() = children.single() as PtExpression
override fun printProperties() {}
} }
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) { class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
companion object {
fun fromBoolean(bool: Boolean, position: Position): PtNumber =
PtNumber(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
}
init { init {
if(type==DataType.BOOL) if(type==DataType.BOOL)
throw IllegalArgumentException("bool should have become ubyte @$position") throw IllegalArgumentException("bool should have become ubyte @$position")
@ -151,10 +206,6 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
} }
} }
override fun printProperties() {
print("$number ($type)")
}
override fun hashCode(): Int = Objects.hash(type, number) override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -175,10 +226,6 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0 // note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" } require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
} }
override fun printProperties() {
print(operator)
}
} }
@ -190,15 +237,36 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
val step: PtNumber val step: PtNumber
get() = children[2] as PtNumber get() = children[2] as PtNumber
override fun printProperties() {} fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
val fromLv = from as? PtNumber
val toLv = to as? PtNumber
val stepLv = step as? PtNumber
if(fromLv==null || toLv==null || stepLv==null)
return null
val fromVal = fromLv.number.toInt()
val toVal = toLv.number.toInt()
val stepVal = stepLv.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
} }
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) { class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
override fun printProperties() {
print("$encoding:\"$value\"")
}
override fun hashCode(): Int = Objects.hash(value, encoding) override fun hashCode(): Int = Objects.hash(value, encoding)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if(other==null || other !is PtString) if(other==null || other !is PtString)
@ -214,12 +282,8 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
} }
// special node that isn't created from compiling user code, but used internally // special node that isn't created from compiling user code, but used internally in the Intermediate Code
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) { class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
override fun printProperties() {
print("reg=$register $type")
}
}
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null

View File

@ -10,8 +10,9 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
fun type(dt: DataType) = "!${dt.name.lowercase()}!" fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String { fun txt(node: PtNode): String {
return when(node) { return when(node) {
is PtAssignTarget -> "" is PtAssignTarget -> "<target>"
is PtAssignment -> "<assign>" is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint" is PtBreakpoint -> "%breakpoint"
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}" is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
is PtAddressOf -> "&" is PtAddressOf -> "&"
@ -30,7 +31,10 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
is PtIdentifier -> "${node.name} ${type(node.type)}" is PtIdentifier -> "${node.name} ${type(node.type)}"
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}" is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
is PtMemoryByte -> "@()" is PtMemoryByte -> "@()"
is PtNumber -> "${node.number.toHex()} ${type(node.type)}" is PtNumber -> {
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
"$numstr ${type(node.type)}"
}
is PtPrefix -> node.operator is PtPrefix -> node.operator
is PtRange -> "<range>" is PtRange -> "<range>"
is PtString -> "\"${node.value.escape()}\"" is PtString -> "\"${node.value.escape()}\""
@ -57,7 +61,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
is PtAsmSub -> { is PtAsmSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..." val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}" val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returnTypes.isEmpty()) "" else (if (node.returnTypes.size == 1) "-> ${node.returnTypes[0].name.lowercase()}" else "-> ${node.returnTypes.map { it.name.lowercase() }}") val returns = if (node.returns.isEmpty()) "" else (if (node.returns.size == 1) "-> ${node.returns[0].second.name.lowercase()}" else "-> ${node.returns.map { it.second.name.lowercase() }}")
val str = if (node.inline) "inline " else "" val str = if (node.inline) "inline " else ""
if(node.address==null) { if(node.address==null) {
str + "asmsub ${node.name}($params) $clobbers $returns" str + "asmsub ${node.name}($params) $clobbers $returns"
@ -66,7 +70,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
} }
} }
is PtBlock -> { is PtBlock -> {
val addr = if(node.address==null) "" else "@${node.address?.toHex()}" val addr = if(node.address==null) "" else "@${node.address.toHex()}"
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}" val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
"\nblock '${node.name}' $addr $align" "\nblock '${node.name}' $addr $align"
} }
@ -86,8 +90,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
} }
is PtSub -> { is PtSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..." val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
var str = if(node.inline) "inline " else "" var str = "sub ${node.name}($params) "
str += "sub ${node.name}($params) "
if(node.returntype!=null) if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}" str += "-> ${node.returntype.name.lowercase()}"
str str
@ -104,7 +107,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
else else
"${node.type.name.lowercase()} ${node.name}" "${node.type.name.lowercase()} ${node.name}"
if(node.value!=null) if(node.value!=null)
str + " = " + txt(node.value!!) str + " = " + txt(node.value)
else else
str str
} }
@ -135,6 +138,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
output(" ".repeat(depth) + txt(node)) output(" ".repeat(depth) + txt(node))
} }
} }
println()
} else { } else {
walkAst(root) { node, depth -> walkAst(root) { node, depth ->
val txt = txt(node) val txt = txt(node)

View File

@ -3,48 +3,39 @@ package prog8.code.ast
import prog8.code.core.* import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
}
class PtAsmSub( class PtAsmSub(
name: String, name: String,
val address: UInt?, val address: UInt?,
val clobbers: Set<CpuRegister>, val clobbers: Set<CpuRegister>,
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>, val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
val returnTypes: List<DataType>, // TODO join with register as Pairs ? val returns: List<Pair<RegisterOrStatusflag, DataType>>,
val retvalRegisters: List<RegisterOrStatusflag>,
val inline: Boolean, val inline: Boolean,
position: Position position: Position
) : PtNamedNode(name, position) { ) : PtNamedNode(name, position), IPtSubroutine
override fun printProperties() {
print("$name inline=$inline")
}
}
class PtSub( class PtSub(
name: String, name: String,
val parameters: List<PtSubroutineParameter>, val parameters: List<PtSubroutineParameter>,
val returntype: DataType?, val returntype: DataType?,
val inline: Boolean,
position: Position position: Position
) : PtNamedNode(name, position) { ) : PtNamedNode(name, position), IPtSubroutine {
override fun printProperties() {
print(name)
}
init { init {
// params and return value should not be str // params and return value should not be str
if(parameters.any{ it.type !in NumericDatatypes }) if(parameters.any{ it.type !in NumericDatatypes })
throw AssemblyError("non-numeric parameter") throw AssemblyError("non-numeric parameter")
if(returntype!=null && returntype !in NumericDatatypes) if(returntype!=null && returntype !in NumericDatatypes)
throw AssemblyError("non-numeric returntype $returntype") throw AssemblyError("non-numeric returntype $returntype")
parameters.forEach { it.parent=this }
} }
} }
class PtSubroutineParameter(val name: String, val type: DataType, position: Position): PtNode(position) { class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
override fun printProperties() {
print("$type $name")
}
}
class PtAssignment(position: Position) : PtNode(position) { class PtAssignment(position: Position) : PtNode(position) {
@ -52,38 +43,22 @@ class PtAssignment(position: Position) : PtNode(position) {
get() = children[0] as PtAssignTarget get() = children[0] as PtAssignTarget
val value: PtExpression val value: PtExpression
get() = children[1] as PtExpression get() = children[1] as PtExpression
}
override fun printProperties() { }
val isInplaceAssign: Boolean by lazy { class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position) {
val target = target.children.single() as PtExpression val target: PtAssignTarget
when(val source = value) { get() = children[0] as PtAssignTarget
is PtArrayIndexer -> { val value: PtExpression
if(target is PtArrayIndexer && source.type==target.type) { get() = children[1] as PtExpression
if(target.variable isSameAs source.variable) { init {
target.index isSameAs source.index require(operator.endsWith('=') || operator in PrefixOperators) {
} "invalid augmented assign operator $operator"
}
false
}
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.name==source.name
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
is PtPrefix -> {
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
||
(target is PtIdentifier && (source.value as? PtIdentifier)?.name==target.name)
}
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
is PtBinaryExpression ->
target isSameAs source.left
else -> false
} }
} }
} }
class PtAssignTarget(position: Position) : PtNode(position) { class PtAssignTarget(position: Position) : PtNode(position) {
val identifier: PtIdentifier? val identifier: PtIdentifier?
get() = children.single() as? PtIdentifier get() = children.single() as? PtIdentifier
@ -102,7 +77,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
} }
} }
override fun printProperties() {} infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
} }
@ -111,10 +86,6 @@ class PtConditionalBranch(val condition: BranchCondition, position: Position) :
get() = children[0] as PtNodeGroup get() = children[0] as PtNodeGroup
val falseScope: PtNodeGroup val falseScope: PtNodeGroup
get() = children[1] as PtNodeGroup get() = children[1] as PtNodeGroup
override fun printProperties() {
print(condition)
}
} }
@ -125,8 +96,6 @@ class PtForLoop(position: Position) : PtNode(position) {
get() = children[1] as PtExpression get() = children[1] as PtExpression
val statements: PtNodeGroup val statements: PtNodeGroup
get() = children[2] as PtNodeGroup get() = children[2] as PtNodeGroup
override fun printProperties() {}
} }
@ -137,8 +106,6 @@ class PtIfElse(position: Position) : PtNode(position) {
get() = children[1] as PtNodeGroup get() = children[1] as PtNodeGroup
val elseScope: PtNodeGroup val elseScope: PtNodeGroup
get() = children[2] as PtNodeGroup get() = children[2] as PtNodeGroup
override fun printProperties() {}
} }
@ -146,10 +113,8 @@ class PtJump(val identifier: PtIdentifier?,
val address: UInt?, val address: UInt?,
val generatedLabel: String?, val generatedLabel: String?,
position: Position) : PtNode(position) { position: Position) : PtNode(position) {
override fun printProperties() { init {
identifier?.printProperties() identifier?.let {it.parent = this }
if(address!=null) print(address.toHex())
if(generatedLabel!=null) print(generatedLabel)
} }
} }
@ -157,10 +122,6 @@ class PtJump(val identifier: PtIdentifier?,
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) { class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
val target: PtAssignTarget val target: PtAssignTarget
get() = children.single() as PtAssignTarget get() = children.single() as PtAssignTarget
override fun printProperties() {
print(operator)
}
} }
@ -169,8 +130,6 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
get() = children[0] as PtExpression get() = children[0] as PtExpression
val statements: PtNodeGroup val statements: PtNodeGroup
get() = children[1] as PtNodeGroup get() = children[1] as PtNodeGroup
override fun printProperties() {}
} }
@ -183,30 +142,26 @@ class PtReturn(position: Position) : PtNode(position) {
else else
null null
} }
override fun printProperties() {}
} }
class PtVariable(name: String, val type: DataType, var value: PtExpression?, var arraySize: UInt?, position: Position) : PtNamedNode(name, position) { sealed interface IPtVariable {
override fun printProperties() { val name: String
print("$type $name") val type: DataType
}
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
init {
value?.let {it.parent=this}
} }
} }
class PtConstant(name: String, val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position) { class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
override fun printProperties() {
print("$type $name = $value")
}
}
class PtMemMapped(name: String, val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position) { class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
override fun printProperties() {
print("&$type $name = ${address.toHex()}")
}
}
class PtWhen(position: Position) : PtNode(position) { class PtWhen(position: Position) : PtNode(position) {
@ -214,8 +169,6 @@ class PtWhen(position: Position) : PtNode(position) {
get() = children[0] as PtExpression get() = children[0] as PtExpression
val choices: PtNodeGroup val choices: PtNodeGroup
get() = children[1] as PtNodeGroup get() = children[1] as PtNodeGroup
override fun printProperties() {}
} }
@ -224,5 +177,4 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
get() = children[0] as PtNodeGroup get() = children[0] as PtNodeGroup
val statements: PtNodeGroup val statements: PtNodeGroup
get() = children[1] as PtNodeGroup get() = children[1] as PtNodeGroup
override fun printProperties() {}
} }

View File

@ -0,0 +1,110 @@
package prog8.code.core
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
override fun toString(): String {
val paramConvs = params.mapIndexed { index, it ->
when {
it.reg!=null -> "$index:${it.reg}"
it.variable -> "$index:variable"
else -> "$index:???"
}
}
val returnConv =
when {
returns.reg!=null -> returns.reg.toString()
returns.floatFac1 -> "floatFAC1"
else -> "<no returnvalue>"
}
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
}
}
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
class FSignature(val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val returnType: DataType?) {
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns: ReturnConvention = when (returnType) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(returnType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
null -> ReturnConvention(null, null, false)
else -> {
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(paramType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
else -> ReturnConvention(paramType, null, false)
}
}
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// one parameter goes via register/registerpair
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
CallConvention(listOf(paramConv), returns)
}
else -> {
// multiple parameters go via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
CallConvention(paramConvs, returns)
}
}
}
}
val BuiltinFunctions: Map<String, FSignature> = mapOf(
// this set of function have no return value and operate in-place:
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
"abs" to FSignature(true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD),
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
// normal functions follow:
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
"sqrt16" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
"rsave" to FSignature(false, emptyList(), null),
"rsavex" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"rrestorex" to FSignature(false, emptyList(), null),
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")

View File

@ -16,11 +16,15 @@ class CompilationOptions(val output: OutputType,
var slowCodegenWarnings: Boolean = false, var slowCodegenWarnings: Boolean = false,
var optimize: Boolean = false, var optimize: Boolean = false,
var optimizeFloatExpressions: Boolean = false, var optimizeFloatExpressions: Boolean = false,
var dontReinitGlobals: Boolean = false,
var asmQuiet: Boolean = false, var asmQuiet: Boolean = false,
var asmListfile: Boolean = false, var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false, var experimentalCodegen: Boolean = false,
var varsHigh: Boolean = false,
var evalStackBaseAddress: UInt? = null, var evalStackBaseAddress: UInt? = null,
var outputDir: Path = Path(""), var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap() var symbolDefs: Map<String, String> = emptyMap()
) ) {
init {
compTarget.machine.initializeMemoryAreas(this)
}
}

View File

@ -1,12 +0,0 @@
package prog8.code.core
interface IAssemblyGenerator {
fun compileToAssembly(): IAssemblyProgram?
}
interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions): Boolean
}
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"

View File

@ -0,0 +1,19 @@
package prog8.code.core
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
interface ICodeGeneratorBackend {
fun generate(program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter): IAssemblyProgram?
}
interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions): Boolean
}
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"

View File

@ -7,5 +7,5 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val defaultEncoding: Encoding val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte> override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
} }

View File

@ -16,6 +16,8 @@ interface IMachineDefinition {
var ESTACK_LO: UInt var ESTACK_LO: UInt
var ESTACK_HI: UInt var ESTACK_HI: UInt
val PROGRAM_LOAD_ADDRESS : UInt val PROGRAM_LOAD_ADDRESS : UInt
val BSSHIGHRAM_START: UInt
val BSSHIGHRAM_END: UInt
val cpu: CpuType val cpu: CpuType
var zeropage: Zeropage var zeropage: Zeropage

View File

@ -10,5 +10,5 @@ enum class Encoding(val prefix: String) {
interface IStringEncoding { interface IStringEncoding {
fun encodeString(str: String, encoding: Encoding): List<UByte> fun encodeString(str: String, encoding: Encoding): List<UByte>
fun decodeString(bytes: List<UByte>, encoding: Encoding): String fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
} }

View File

@ -11,7 +11,7 @@ class MemAllocationError(message: String) : Exception(message)
abstract class MemoryAllocator(protected val options: CompilationOptions) { abstract class MemoryAllocator(protected val options: CompilationOptions) {
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int) data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
abstract fun allocate(name: List<String>, abstract fun allocate(name: String,
datatype: DataType, datatype: DataType,
numElements: Int?, numElements: Int?,
position: Position?, position: Position?,
@ -29,7 +29,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
// the variables allocated into Zeropage. // the variables allocated into Zeropage.
// name (scoped) ==> pair of address to (Datatype + bytesize) // name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>() val allocatedVariables = mutableMapOf<String, VarAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations. val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
@ -51,7 +51,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
return free.windowed(2).any { it[0] == it[1] - 1u } return free.windowed(2).any { it[0] == it[1] - 1u }
} }
override fun allocate(name: List<String>, override fun allocate(name: String,
datatype: DataType, datatype: DataType,
numElements: Int?, numElements: Int?,
position: Position?, position: Position?,
@ -107,7 +107,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
private fun reserve(range: UIntRange) = free.removeAll(range) private fun reserve(range: UIntRange) = free.removeAll(range)
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt { private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String): UInt {
require(size>=0) require(size>=0)
free.removeAll(address until address+size.toUInt()) free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) { if(name.isNotEmpty()) {
@ -135,7 +135,7 @@ class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAlloc
private var nextLocation: UInt = region.first private var nextLocation: UInt = region.first
override fun allocate( override fun allocate(
name: List<String>, name: String,
datatype: DataType, datatype: DataType,
numElements: Int?, numElements: Int?,
position: Position?, position: Position?,

View File

@ -5,6 +5,7 @@ val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not") val LogicalOperators = setOf("and", "or", "xor", "not")
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor") val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val BitwiseOperators = setOf("&", "|", "^", "~") val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators // val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
fun invertedComparisonOperator(operator: String) = fun invertedComparisonOperator(operator: String) =

View File

@ -23,7 +23,7 @@ object Encoder: IStringEncoding {
success = { it } success = { it }
) )
} }
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String { override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
val decoded = when(encoding) { val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true) Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true) Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)

View File

@ -13,9 +13,12 @@ class AtariMachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = 6 override val FLOAT_MEM_SIZE = 6
override val PROGRAM_LOAD_ADDRESS = 0x2000u override val PROGRAM_LOAD_ADDRESS = 0x2000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive // TODO
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive // TODO
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam override lateinit var golden: GoldenRam

View File

@ -14,9 +14,13 @@ class C128MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x1c01u override val PROGRAM_LOAD_ADDRESS = 0x1c01u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam override lateinit var golden: GoldenRam

View File

@ -15,9 +15,13 @@ class C64MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0801u override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive override var ESTACK_LO = 0xcf00u // $cf00-$cf7f inclusive
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive override var ESTACK_HI = 0xcf80u // $cf80-$cfff inclusive
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = ESTACK_LO
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam override lateinit var golden: GoldenRam

View File

@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02). // The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) { for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15 allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt()) free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt()) free.remove((5+reg*2).toUInt())
} }

View File

@ -208,7 +208,7 @@ object AtasciiEncoding {
return Ok(mapped) return Ok(mapped)
} }
fun decode(bytes: List<UByte>): Result<String, CharConversionException> { fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString("")) return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
} }
} }

View File

@ -27,7 +27,7 @@ object IsoEncoding {
} }
} }
fun decode(bytes: List<UByte>): Result<String, CharConversionException> { fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return try { return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset)) Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) { } catch (ce: CharConversionException) {

View File

@ -14,9 +14,13 @@ class CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0801u override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive override var ESTACK_LO = 0x0700u // $0700-$077f inclusive
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive override var ESTACK_HI = 0x0780u // $0780-$07ff inclusive
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
override val BSSHIGHRAM_END = 0xc000u // rom starts here.
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam override lateinit var golden: GoldenRam

View File

@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well. // However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) { for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15 allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
} }
} }
} }

View File

@ -17,10 +17,18 @@ class VirtualMachineDefinition: IMachineDefinition {
override var ESTACK_LO = 0u // not actually used override var ESTACK_LO = 0u // not actually used
override var ESTACK_HI = 0u // not actually used override var ESTACK_HI = 0u // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number") override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toFloat().toBits().toUInt()
val hexStr = bits.toString(16).padStart(8, '0')
val parts = hexStr.chunked(2).map { "\$" + it }
return parts.joinToString(", ")
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> { override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return listOf("syslib") return listOf("syslib")
@ -44,9 +52,24 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = false override fun isIOAddress(address: UInt): Boolean = false
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {} override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = VirtualZeropage(compilerOptions)
}
} }
interface IVirtualMachineRunner { interface IVirtualMachineRunner {
fun runProgram(irSource: String) fun runProgram(irSource: String)
} }
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ }
}

View File

@ -3,6 +3,7 @@ plugins {
id 'java' id 'java'
id 'application' id 'application'
id "org.jetbrains.kotlin.jvm" id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.9"
} }
java { java {
@ -25,11 +26,11 @@ compileTestKotlin {
dependencies { dependencies {
implementation project(':codeCore') implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect" // implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16" implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
} }
sourceSets { sourceSets {
@ -41,6 +42,22 @@ sourceSets {
srcDirs = ["${project.projectDir}/res"] srcDirs = ["${project.projectDir}/res"]
} }
} }
test {
java {
srcDir "${project.projectDir}/test"
}
}
} }
// note: there are no unit tests in this module! 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

@ -4,13 +4,15 @@
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" /> <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" /> <orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" /> <orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component> </component>
</module> </module>

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,15 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.Program import prog8.code.StConstant
import prog8.ast.expressions.NumericLiteral import prog8.code.StMemVar
import prog8.ast.statements.VarDecl import prog8.code.SymbolTable
import prog8.ast.statements.VarDeclType
import prog8.code.core.IMachineDefinition import prog8.code.core.IMachineDefinition
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations // note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int { internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int {
var numberOfOptimizations = 0 var numberOfOptimizations = 0
@ -37,7 +36,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++ numberOfOptimizations++
} }
mods = optimizeStoreLoadSame(linesByFour, machine, program) mods = optimizeStoreLoadSame(linesByFour, machine, symbolTable)
if(mods.isNotEmpty()) { if(mods.isNotEmpty()) {
apply(mods, lines) apply(mods, lines)
linesByFour = getLinesBy(lines, 4) linesByFour = getLinesBy(lines, 4)
@ -52,14 +51,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
} }
var linesByFourteen = getLinesBy(lines, 14) var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, program) mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) { if(mods.isNotEmpty()) {
apply(mods, lines) apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14) linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++ numberOfOptimizations++
} }
mods = optimizeSamePointerIndexing(linesByFourteen, machine, program) mods = optimizeSamePointerIndexing(linesByFourteen)
if(mods.isNotEmpty()) { if(mods.isNotEmpty()) {
apply(mods, lines) apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14) linesByFourteen = getLinesBy(lines, 14)
@ -129,7 +128,11 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
return mods return mods
} }
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> { private fun optimizeSameAssignments(
linesByFourteen: List<List<IndexedValue<String>>>,
machine: IMachineDefinition,
symbolTable: SymbolTable
): List<Modification> {
// Optimize sequential assignments of the same value to various targets (bytes, words, floats) // Optimize sequential assignments of the same value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check... // the float one is the one that requires 2*7=14 lines of code to check...
@ -154,8 +157,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val fourthvalue = sixth.substring(4) val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) { if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines) // lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
val address1 = getAddressArg(first, program) val address1 = getAddressArg(first, symbolTable)
val address2 = getAddressArg(second, program) val address2 = getAddressArg(second, symbolTable)
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) { if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
mods.add(Modification(lines[4].index, true, null)) mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null)) mods.add(Modification(lines[5].index, true, null))
@ -168,7 +171,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val secondvalue = third.substring(4) val secondvalue = third.substring(4)
if(firstvalue==secondvalue) { if(firstvalue==secondvalue) {
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line) // lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
val address = getAddressArg(first, program) val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) if(address==null || !machine.isIOAddress(address))
mods.add(Modification(lines[2].index, true, null)) mods.add(Modification(lines[2].index, true, null))
} }
@ -251,7 +254,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val thirdvalue = third.substring(4) val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4) val fourthvalue = fourth.substring(4)
if(firstvalue==thirdvalue && secondvalue == fourthvalue) { if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
val address = getAddressArg(first, program) val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) { if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true overlappingMods = true
mods.add(Modification(lines[2].index, true, null)) mods.add(Modification(lines[2].index, true, null))
@ -275,7 +278,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4) val firstvalue = first.substring(4)
val thirdvalue = third.substring(4) val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue) { if(firstvalue==thirdvalue) {
val address = getAddressArg(first, program) val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) { if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true overlappingMods = true
mods.add(Modification(lines[2].index, true, null)) mods.add(Modification(lines[2].index, true, null))
@ -295,7 +298,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val secondvalue = second.substring(4) val secondvalue = second.substring(4)
val thirdvalue = third.substring(4) val thirdvalue = third.substring(4)
if(firstvalue==secondvalue && firstvalue==thirdvalue) { if(firstvalue==secondvalue && firstvalue==thirdvalue) {
val address = getAddressArg(first, program) val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) { if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true overlappingMods = true
val reg2 = second[2] val reg2 = second[2]
@ -314,7 +317,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4) val firstvalue = first.substring(4)
val secondvalue = second.substring(4) val secondvalue = second.substring(4)
if(firstvalue==secondvalue) { if(firstvalue==secondvalue) {
val address = getAddressArg(first, program) val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) { if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true overlappingMods = true
mods.add(Modification(lines[0].index, true, null)) mods.add(Modification(lines[0].index, true, null))
@ -327,7 +330,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
return mods return mods
} }
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> { private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y // Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
// if Y isn't modified in between we can omit the second LDY: // if Y isn't modified in between we can omit the second LDY:
@ -369,7 +372,11 @@ private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<
return mods return mods
} }
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> { private fun optimizeStoreLoadSame(
linesByFour: List<List<IndexedValue<String>>>,
machine: IMachineDefinition,
symbolTable: SymbolTable
): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated // sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for (lines in linesByFour) { for (lines in linesByFour) {
@ -397,7 +404,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
} }
else { else {
// no branch instruction follows, we can remove the load instruction // no branch instruction follows, we can remove the load instruction
val address = getAddressArg(lines[2].value, program) val address = getAddressArg(lines[2].value, symbolTable)
address==null || !machine.isIOAddress(address) address==null || !machine.isIOAddress(address)
} }
@ -439,7 +446,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""") private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
private fun getAddressArg(line: String, program: Program): UInt? { private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
// try to get the constant value address, could return null if it's a symbol instead
val loadArg = line.trimStart().substring(3).trim() val loadArg = line.trimStart().substring(3).trim()
return when { return when {
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16) loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
@ -450,15 +458,11 @@ private fun getAddressArg(line: String, program: Program): UInt? {
val identMatch = identifierRegex.find(loadArg) val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) { if(identMatch!=null) {
val identifier = identMatch.value val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split('.')) as? VarDecl when (val symbol = symbolTable.flat[identifier]) {
if(decl!=null) { is StConstant -> symbol.value.toUInt()
when(decl.type){ is StMemVar -> symbol.address
VarDeclType.VAR -> null else -> null
VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
}
} }
else null
} else null } else null
} }
else -> loadArg.substring(1).toUIntOrNull() else -> loadArg.substring(1).toUIntOrNull()

View File

@ -1,15 +1,12 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.expressions.ArrayIndexedExpression import prog8.code.ast.*
import prog8.ast.expressions.BuiltinFunctionCall
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
import prog8.code.core.Cx16VirtualRegisters import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> { fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
val order = mutableListOf<Int>() val order = mutableListOf<Int>()
// order is: // order is:
// 1) cx16 virtual word registers, // 1) cx16 virtual word registers,
@ -17,43 +14,45 @@ fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
// 3) single CPU registers (X last), except A, // 3) single CPU registers (X last), except A,
// 4) CPU Carry status flag // 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value) // 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex() val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters } val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY) val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters } val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A } val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null } val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
cx16regs.forEach { order += it.index } cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index } pairedRegs.forEach { order += it.index }
regsWithoutA.forEach { regsWithoutA.forEach {
if(it.value.second.registerOrPair != RegisterOrPair.X) if(it.value.first.registerOrPair != RegisterOrPair.X)
order += it.index order += it.index
} }
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index} regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
rest.forEach { order += it.index } rest.forEach { order += it.index }
regA.forEach { order += it.index } regA.forEach { order += it.index }
require(order.size==sub.parameters.size) require(order.size==sub.parameters.size)
return order return order
} }
fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>, fun asmsub6502ArgsHaveRegisterClobberRisk(
paramRegisters: List<RegisterOrStatusflag>): Boolean { args: List<PtExpression>,
fun isClobberRisk(expr: Expression): Boolean { params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
): Boolean {
fun isClobberRisk(expr: PtExpression): Boolean {
when (expr) { when (expr) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
return paramRegisters.any { return params.any {
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY) it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
} }
} }
is BuiltinFunctionCall -> { is PtBuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb") if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0]) return isClobberRisk(expr.args[0])
if (expr.name == "mkword") if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1]) return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple return !expr.isSimple()
} }
else -> return !expr.isSimple else -> return !expr.isSimple()
} }
} }

View File

@ -3,7 +3,6 @@ package prog8.codegen.cpu6502
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError import com.github.michaelbull.result.mapError
import prog8.ast.generatedLabelPrefix
import prog8.code.core.* import prog8.code.core.*
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
@ -63,7 +62,7 @@ internal class AssemblyProgram(
"atari" -> { "atari" -> {
// Atari800XL .xex generation. // Atari800XL .xex generation.
// TODO are these options okay? // TODO are these options okay for atari?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror", "-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--no-monitor" "--no-monitor"
@ -104,7 +103,7 @@ internal class AssemblyProgram(
} }
private fun removeGeneratedLabelsFromMonlist() { private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""") val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
val lines = viceMonListFile.toFile().readLines() val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use { viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) { for (line in lines) {

View File

@ -1,49 +1,39 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall import prog8.code.ast.*
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.code.core.* import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.* import prog8.codegen.cpu6502.assignment.*
import prog8.compiler.BuiltinFunctions
import prog8.compiler.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen, private val asmgen: AsmGen6502Internal,
private val assignAsmGen: AssignmentAsmGen) { private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) { internal fun translateFunctioncallExpression(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single()) return translateFunctioncall(fcall, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
} }
internal fun translateFunctioncallStatement(fcall: BuiltinFunctionCallStatement) { internal fun translateFunctioncallStatement(fcall: PtBuiltinFunctionCall) {
val func = BuiltinFunctions.getValue(fcall.name) translateFunctioncall(fcall, discardResult = true, resultToStack = false, resultRegister = null)
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
} }
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun translateFunctioncall(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
if (discardResult && func.pure) if (discardResult && fcall.hasNoSideEffects)
return // can just ignore the whole function call altogether return null // can just ignore the whole function call altogether
if(discardResult && resultToStack) if(discardResult && resultToStack)
throw AssemblyError("cannot both discard the result AND put it onto stack") throw AssemblyError("cannot both discard the result AND put it onto stack")
val sscope = (fcall as Node).definingSubroutine val sscope = fcall.definingISub()
when (func.name) { when (fcall.name) {
"msb" -> funcMsb(fcall, resultToStack, resultRegister) "msb" -> funcMsb(fcall, resultToStack, resultRegister)
"lsb" -> funcLsb(fcall, resultToStack, resultRegister) "lsb" -> funcLsb(fcall, resultToStack, resultRegister)
"mkword" -> funcMkword(fcall, resultToStack, resultRegister) "mkword" -> funcMkword(fcall, resultToStack, resultRegister)
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope) "abs" -> funcAbs(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope) "any", "all" -> funcAnyAll(fcall, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope) "sgn" -> funcSgn(fcall, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope) "sqrt16" -> funcSqrt16(fcall, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall) "rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall) "rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall) "ror" -> funcRor(fcall)
@ -59,16 +49,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0]) "push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0]) "pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
"pop" -> { "pop" -> {
require(fcall.args[0] is IdentifierReference) { require(fcall.args[0] is PtIdentifier) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}" "attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
} }
asmgen.popCpuStack(DataType.UBYTE, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine) val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
val target = symbol!!.astNode as IPtVariable
asmgen.popCpuStack(DataType.UBYTE, target, fcall.definingISub())
} }
"popw" -> { "popw" -> {
require(fcall.args[0] is IdentifierReference) { require(fcall.args[0] is PtIdentifier) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}" "attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
} }
asmgen.popCpuStack(DataType.UWORD, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine) val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
val target = symbol!!.astNode as IPtVariable
asmgen.popCpuStack(DataType.UWORD, target, fcall.definingISub())
} }
"rsave" -> funcRsave() "rsave" -> funcRsave()
"rsavex" -> funcRsaveX() "rsavex" -> funcRsaveX()
@ -76,9 +70,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"rrestorex" -> funcRrestoreX() "rrestorex" -> funcRrestoreX()
"cmp" -> funcCmp(fcall) "cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall) "callfar" -> funcCallFar(fcall)
"callrom" -> funcCallRom(fcall) else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
} }
return BuiltinFunctions.getValue(fcall.name).returnType
} }
private fun funcRsave() { private fun funcRsave() {
@ -132,147 +127,60 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1") asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
} }
private fun funcCallFar(fcall: IFunctionCall) { private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
if(asmgen.options.compTarget.name != "cx16") if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time") throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt() asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
val address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0 asmgen.out(" sta (++)+0")
val argAddrArg = fcall.args[2] asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
if(bank==null) asmgen.out(" sta (+)+0 | sty (+)+1")
throw AssemblyError("callfar (jsrfar) bank has to be a constant") asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
if(fcall.args[1].constValue(program) == null) { asmgen.out("""
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false) jsr cx16.jsrfar
asmgen.out(" sta (+)+0 | sty (+)+1 ; store jsrfar address word") + .word 0
} + .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
jsr cx16.jsrfar
+ .word ${address.toHex()}
.byte ${bank.toHex()}""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callfar done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr cx16.jsrfar
+ .word ${address.toHex()}
.byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
}
is NumericLiteral -> {
asmgen.out("""
lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar
+ .word ${address.toHex()}
.byte ${bank.toHex()}
sta ${argAddrArg.number.toHex()}""")
}
else -> throw AssemblyError("callfar only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
} }
private fun funcCallRom(fcall: IFunctionCall) { private fun funcCmp(fcall: PtBuiltinFunctionCall) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callrom only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callrom requires constant arguments")
if(address !in 0xc000..0xffff)
throw AssemblyError("callrom done on address outside of cx16 banked rom")
if(bank>=32)
throw AssemblyError("callrom bank must be <32")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
jsr ${address.toHex()}
pla
sta $01""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callrom done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr ${address.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}
pla
sta $01""")
}
is NumericLiteral -> {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${argAddrArg.number.toHex()}
jsr ${address.toHex()}
sta ${argAddrArg.number.toHex()}
pla
sta $01""")
}
else -> throw AssemblyError("callrom only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
}
private fun funcCmp(fcall: IFunctionCall) {
val arg1 = fcall.args[0] val arg1 = fcall.args[0]
val arg2 = fcall.args[1] val arg2 = fcall.args[1]
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") } if(arg1.type in ByteDatatypes) {
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") } if(arg2.type in ByteDatatypes) {
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) {
when (arg2) { when (arg2) {
is IdentifierReference -> { is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}") asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
} }
is NumericLiteral -> { is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number.toInt()}") asmgen.out(" cmp #${arg2.number.toInt()}")
} }
is DirectMemoryRead -> { is PtMemoryByte -> {
if(arg2.addressExpression is NumericLiteral) { if(arg2.address is PtNumber) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}") asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
} else { } else {
if(arg1.isSimple) { if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1") asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else { } else {
asmgen.pushCpuStack(DataType.UBYTE, arg1) asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1") asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
} }
} }
} }
else -> { else -> {
if(arg1.isSimple) { if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1") asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else { } else {
asmgen.pushCpuStack(DataType.UBYTE, arg1) asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1") asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
} }
} }
@ -280,10 +188,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} else } else
throw AssemblyError("args for cmp() should have same dt") throw AssemblyError("args for cmp() should have same dt")
} else { } else {
// dt1 is a word // arg1 is a word
if(dt2 in WordDatatypes) { if(arg2.type in WordDatatypes) {
when (arg2) { when (arg2) {
is IdentifierReference -> { is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
cpy ${asmgen.asmVariableName(arg2)}+1 cpy ${asmgen.asmVariableName(arg2)}+1
@ -291,7 +199,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
cmp ${asmgen.asmVariableName(arg2)} cmp ${asmgen.asmVariableName(arg2)}
+""") +""")
} }
is NumericLiteral -> { is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
cpy #>${arg2.number.toInt()} cpy #>${arg2.number.toInt()}
@ -300,8 +208,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""") +""")
} }
else -> { else -> {
if(arg1.isSimple) { if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, fcall.definingISub())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
cpy P8ZP_SCRATCH_W1+1 cpy P8ZP_SCRATCH_W1+1
@ -310,7 +218,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""") +""")
} else { } else {
asmgen.pushCpuStack(DataType.UWORD, arg1) asmgen.pushCpuStack(DataType.UWORD, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, fcall.definingISub())
asmgen.restoreRegisterStack(CpuRegister.Y, false) asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false) asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out(""" asmgen.out("""
@ -326,25 +234,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcMemory(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is BuiltinFunctionCall) if(discardResult)
throw AssemblyError("should not discard result of memory allocation at $fcall") throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as StringLiteral).value val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"} require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position) val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
slabname.linkParents(fcall) val addressOf = PtAddressOf(fcall.position)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position)) addressOf.add(slabname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
val target = val target =
if(resultToStack) if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null) AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null)
else else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, asmgen) AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position) val assign = AsmAssignment(src, target, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { private fun funcSqrt16(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall, scope)
if(resultToStack) if(resultToStack)
asmgen.out(" jsr prog8_lib.func_sqrt16_stack") asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else { else {
@ -353,13 +263,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcReverse(fcall: IFunctionCall) { private fun funcReverse(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() val variable = fcall.args.single()
if (variable is IdentifierReference) { if (variable is PtIdentifier) {
val decl = variable.targetVarDecl(program)!! val symbol = asmgen.symbolTable.lookup(variable.name)
val decl = symbol!!.astNode as IPtVariable
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("cannot reverse a constant")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
}
val varName = asmgen.asmVariableName(variable) val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex() when (decl.type) {
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
@ -392,13 +307,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcSort(fcall: IFunctionCall) { private fun funcSort(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() val variable = fcall.args.single()
if (variable is IdentifierReference) { if (variable is PtIdentifier) {
val decl = variable.targetVarDecl(program)!! val symbol = asmgen.symbolTable.lookup(variable.name)
val decl = symbol!!.astNode as IPtVariable
val varName = asmgen.asmVariableName(variable) val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex() val numElements = when(decl) {
when (decl.datatype) { is PtConstant -> throw AssemblyError("cannot sort a constant")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
}
when (decl.type) {
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
@ -406,7 +326,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
lda #$numElements""") lda #$numElements""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b") asmgen.out(if (decl.type == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
} }
DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out(""" asmgen.out("""
@ -415,7 +335,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
lda #$numElements""") lda #$numElements""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w") asmgen.out(if (decl.type == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
} }
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported") DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -424,26 +344,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("weird type") throw AssemblyError("weird type")
} }
private fun funcRor2(fcall: IFunctionCall) { private fun funcRor2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single() val what = fcall.args.single()
val dt = what.inferType(program) when (what.type) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'b') translateRolRorArrayArgs(what.variable, what, "ror2", 'b')
asmgen.out(" jsr prog8_lib.ror2_array_ub") asmgen.out(" jsr prog8_lib.ror2_array_ub")
} }
is DirectMemoryRead -> { is PtMemoryByte -> {
if (what.addressExpression is NumericLiteral) { if (what.address is PtNumber) {
val number = (what.addressExpression as NumericLiteral).number val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}") asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.ror2_mem_ub") asmgen.out(" jsr prog8_lib.ror2_mem_ub")
} }
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable") asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
} }
@ -452,11 +371,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
DataType.UWORD -> { DataType.UWORD -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'w') translateRolRorArrayArgs(what.variable, what, "ror2", 'w')
asmgen.out(" jsr prog8_lib.ror2_array_uw") asmgen.out(" jsr prog8_lib.ror2_array_uw")
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ") asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
} }
@ -467,26 +386,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcRor(fcall: IFunctionCall) { private fun funcRor(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single() val what = fcall.args.single()
val dt = what.inferType(program) when (what.type) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'b') translateRolRorArrayArgs(what.variable, what, "ror", 'b')
asmgen.out(" jsr prog8_lib.ror_array_ub") asmgen.out(" jsr prog8_lib.ror_array_ub")
} }
is DirectMemoryRead -> { is PtMemoryByte -> {
if (what.addressExpression is NumericLiteral) { if (what.address is PtNumber) {
val number = (what.addressExpression as NumericLiteral).number val number = (what.address as PtNumber).number
asmgen.out(" ror ${number.toHex()}") asmgen.out(" ror ${number.toHex()}")
} else { } else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression) val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) { if(ptrAndIndex!=null) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X) asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY) asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out(""" asmgen.out("""
@ -495,7 +413,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+ ror ${'$'}ffff,x ; modified""") + ror ${'$'}ffff,x ; modified""")
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
sta (+) + 1 sta (+) + 1
sty (+) + 2 sty (+) + 2
@ -503,7 +421,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable") asmgen.out(" ror $variable")
} }
@ -512,11 +430,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
DataType.UWORD -> { DataType.UWORD -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'w') translateRolRorArrayArgs(what.variable, what, "ror", 'w')
asmgen.out(" jsr prog8_lib.ror_array_uw") asmgen.out(" jsr prog8_lib.ror_array_uw")
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable+1 | ror $variable") asmgen.out(" ror $variable+1 | ror $variable")
} }
@ -527,26 +445,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcRol2(fcall: IFunctionCall) { private fun funcRol2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single() val what = fcall.args.single()
val dt = what.inferType(program) when (what.type) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'b') translateRolRorArrayArgs(what.variable, what, "rol2", 'b')
asmgen.out(" jsr prog8_lib.rol2_array_ub") asmgen.out(" jsr prog8_lib.rol2_array_ub")
} }
is DirectMemoryRead -> { is PtMemoryByte -> {
if (what.addressExpression is NumericLiteral) { if (what.address is PtNumber) {
val number = (what.addressExpression as NumericLiteral).number val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}") asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.rol2_mem_ub") asmgen.out(" jsr prog8_lib.rol2_mem_ub")
} }
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable") asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
} }
@ -555,11 +472,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
DataType.UWORD -> { DataType.UWORD -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'w') translateRolRorArrayArgs(what.variable, what, "rol2", 'w')
asmgen.out(" jsr prog8_lib.rol2_array_uw") asmgen.out(" jsr prog8_lib.rol2_array_uw")
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ") asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
} }
@ -570,26 +487,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcRol(fcall: IFunctionCall) { private fun funcRol(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single() val what = fcall.args.single()
val dt = what.inferType(program) when (what.type) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'b') translateRolRorArrayArgs(what.variable, what, "rol", 'b')
asmgen.out(" jsr prog8_lib.rol_array_ub") asmgen.out(" jsr prog8_lib.rol_array_ub")
} }
is DirectMemoryRead -> { is PtMemoryByte -> {
if (what.addressExpression is NumericLiteral) { if (what.address is PtNumber) {
val number = (what.addressExpression as NumericLiteral).number val number = (what.address as PtNumber).number
asmgen.out(" rol ${number.toHex()}") asmgen.out(" rol ${number.toHex()}")
} else { } else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression) val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) { if(ptrAndIndex!=null) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X) asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY) asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out(""" asmgen.out("""
@ -598,7 +514,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+ rol ${'$'}ffff,x ; modified""") + rol ${'$'}ffff,x ; modified""")
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
sta (+) + 1 sta (+) + 1
sty (+) + 2 sty (+) + 2
@ -606,7 +522,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable") asmgen.out(" rol $variable")
} }
@ -615,11 +531,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
DataType.UWORD -> { DataType.UWORD -> {
when (what) { when (what) {
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'w') translateRolRorArrayArgs(what.variable, what, "rol", 'w')
asmgen.out(" jsr prog8_lib.rol_array_uw") asmgen.out(" jsr prog8_lib.rol_array_uw")
} }
is IdentifierReference -> { is PtIdentifier -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable | rol $variable+1") asmgen.out(" rol $variable | rol $variable+1")
} }
@ -630,22 +546,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) { private fun translateRolRorArrayArgs(arrayvar: PtIdentifier, indexer: PtArrayIndexer, operation: String, dt: Char) {
if(arrayvar.targetVarDecl(program)!!.datatype==DataType.UWORD) { if(arrayvar.type==DataType.UWORD) {
if(dt!='b') if(dt!='b')
throw AssemblyError("non-array var indexing requires bytes dt") throw AssemblyError("non-array var indexing requires bytes dt")
asmgen.assignExpressionToVariable(arrayvar, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null) asmgen.assignExpressionToVariable(arrayvar, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
} else { } else {
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null) val addressOf = PtAddressOf(arrayvar.position)
addressOf.add(arrayvar)
addressOf.parent = arrayvar.parent.parent
asmgen.assignExpressionToVariable(addressOf, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
} }
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null) asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
} }
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { private fun funcSgn(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall, scope)
val dt = fcall.args.single().inferType(program) val dt = fcall.args.single().type
if(resultToStack) { if(resultToStack) {
when (dt.getOr(DataType.UNDEFINED)) { when (dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack") DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack") DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack") DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
@ -654,7 +573,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
} else { } else {
when (dt.getOr(DataType.UNDEFINED)) { when (dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A") DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A") DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A") DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
@ -666,30 +585,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0]) outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program) val dt = fcall.args.single().type
if(resultToStack) { if(resultToStack) {
when (dt.getOr(DataType.UNDEFINED)) { when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack") DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_stack")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack") DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack") DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_stack")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
} else { } else {
when (dt.getOr(DataType.UNDEFINED)) { when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A | ldy #0") DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A | ldy #0") DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0") DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
} }
} }
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { private fun funcAbs(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall, scope)
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED) val dt = fcall.args.single().type
if(resultToStack) { if(resultToStack) {
when (dt) { when (dt) {
DataType.UBYTE -> asmgen.out(" ldy #0") DataType.UBYTE -> asmgen.out(" ldy #0")
@ -710,19 +629,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcPokeW(fcall: IFunctionCall) { private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) { when(val addrExpr = fcall.args[0]) {
is NumericLiteral -> { is PtNumber -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
val addr = addrExpr.number.toHex() val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr | sty ${addr}+1") asmgen.out(" sta $addr | sty ${addr}+1")
return return
} }
is IdentifierReference -> { is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr) val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) { if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX) asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) { if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out(""" asmgen.out("""
@ -742,14 +661,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
return return
} }
} }
is BinaryExpression -> { is PtBinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) { if(addrExpr.operator=="+" && addrExpr.left is PtIdentifier && addrExpr.right is PtNumber) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference) val varname = asmgen.asmVariableName(addrExpr.left as PtIdentifier)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) { if(asmgen.isZpVar(addrExpr.left as PtIdentifier)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX) asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val index = (addrExpr.right as NumericLiteral).number.toHex() val index = (addrExpr.right as PtNumber).number.toHex()
asmgen.out(""" asmgen.out("""
ldy #$index ldy #$index
sta ($varname),y sta ($varname),y
@ -769,13 +688,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" jsr prog8_lib.func_pokew") asmgen.out(" jsr prog8_lib.func_pokew")
} }
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
when(val addrExpr = fcall.args[0]) { when(val addrExpr = fcall.args[0]) {
is NumericLiteral -> { is PtNumber -> {
val addr = addrExpr.number.toHex() val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr | ldy ${addr}+1") asmgen.out(" lda $addr | ldy ${addr}+1")
} }
is IdentifierReference -> { is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr) val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) { if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
@ -800,12 +719,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" jsr prog8_lib.func_peekw") asmgen.out(" jsr prog8_lib.func_peekw")
} }
} }
is BinaryExpression -> { is PtBinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) { if(addrExpr.operator=="+" && addrExpr.left is PtIdentifier && addrExpr.right is PtNumber) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference) val varname = asmgen.asmVariableName(addrExpr.left as PtIdentifier)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) { if(asmgen.isZpVar(addrExpr.left as PtIdentifier)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
val index = (addrExpr.right as NumericLiteral).number.toHex() val index = (addrExpr.right as PtNumber).number.toHex()
asmgen.out(""" asmgen.out("""
ldy #$index ldy #$index
lda ($varname),y lda ($varname),y
@ -845,7 +764,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcMkword(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(resultToStack) { if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
@ -854,12 +773,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
val reg = resultRegister ?: RegisterOrPair.AY val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = asmgen.needAsaveForExpr(fcall.args[0]) var needAsave = asmgen.needAsaveForExpr(fcall.args[0])
if(!needAsave) { if(!needAsave) {
val mr0 = fcall.args[0] as? DirectMemoryRead val mr0 = fcall.args[0] as? PtMemoryByte
val mr1 = fcall.args[1] as? DirectMemoryRead val mr1 = fcall.args[1] as? PtMemoryByte
if (mr0 != null) if (mr0 != null)
needAsave = mr0.addressExpression !is NumericLiteral needAsave = mr0.address !is PtNumber
if (mr1 != null) if (mr1 != null)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral) needAsave = needAsave or (mr1.address !is PtNumber)
} }
when(reg) { when(reg) {
RegisterOrPair.AX -> { RegisterOrPair.AX -> {
@ -898,13 +817,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcMsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single() val arg = fcall.args.single()
if (!arg.inferType(program).isWords) if (arg.type !in WordDatatypes)
throw AssemblyError("msb required word argument") throw AssemblyError("msb required word argument")
if (arg is NumericLiteral) if (arg is PtNumber)
throw AssemblyError("msb(const) should have been const-folded away") throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) { if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg) val sourceName = asmgen.asmVariableName(arg)
if(resultToStack) { if(resultToStack) {
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex") asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
@ -952,14 +871,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcLsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single() val arg = fcall.args.single()
if (!arg.inferType(program).isWords) if (arg.type !in WordDatatypes)
throw AssemblyError("lsb required word argument") throw AssemblyError("lsb required word argument")
if (arg is NumericLiteral) if (arg is PtNumber)
throw AssemblyError("lsb(const) should have been const-folded away") throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) { if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg) val sourceName = asmgen.asmVariableName(arg)
if(resultToStack) { if(resultToStack) {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex") asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
@ -1013,35 +932,39 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun outputAddressAndLenghtOfArray(arg: Expression) { private fun outputAddressAndLenghtOfArray(arg: PtExpression) {
// address in P8ZP_SCRATCH_W1, number of elements in A // address in P8ZP_SCRATCH_W1, number of elements in A
arg as IdentifierReference arg as PtIdentifier
val arrayVar = arg.targetVarDecl(program)!! val symbol = asmgen.symbolTable.lookup(arg.name)
if(!arrayVar.isArray) val arrayVar = symbol!!.astNode as IPtVariable
throw AssemblyError("length of non-array requested") val numElements = when(arrayVar) {
val size = arrayVar.arraysize!!.constIndex()!! is PtConstant -> null
is PtMemMapped -> arrayVar.arraySize
is PtVariable -> arrayVar.arraySize
} ?: throw AssemblyError("length of non-array requested")
val identifierName = asmgen.asmVariableName(arg) val identifierName = asmgen.asmVariableName(arg)
asmgen.out(""" asmgen.out("""
lda #<$identifierName lda #<$identifierName
ldy #>$identifierName ldy #>$identifierName
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
lda #$size lda #$numElements
""") """)
} }
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) { private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
val callConv = signature.callConvention(args.map { val signature = BuiltinFunctions.getValue(call.name)
it.inferType(program).getOrElse { throw AssemblyError("unknown dt") } val callConv = signature.callConvention(call.args.map { it.type})
})
fun getSourceForFloat(value: Expression): AsmAssignSource { fun getSourceForFloat(value: PtExpression): AsmAssignSource {
return when (value) { return when (value) {
is IdentifierReference -> { is PtIdentifier -> {
val addr = AddressOf(value, value.position) val addr = PtAddressOf(value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen) AsmAssignSource.fromAstSource(addr, program, asmgen)
} }
is NumericLiteral -> { is PtNumber -> {
throw AssemblyError("float literals should have been converted into autovar") throw AssemblyError("float literals should have been converted into autovar")
} }
else -> { else -> {
@ -1049,27 +972,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("cannot use float arguments outside of a subroutine scope") throw AssemblyError("cannot use float arguments outside of a subroutine scope")
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position) val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, value.position)
val addr = AddressOf(variable, value.position) val addr = PtAddressOf(value.position)
addr.linkParents(value) addr.add(variable)
addr.parent = call
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT, scope) asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT, scope)
AsmAssignSource.fromAstSource(addr, program, asmgen) AsmAssignSource.fromAstSource(addr, program, asmgen)
} }
} }
} }
args.zip(callConv.params).zip(signature.parameters).forEach { call.args.zip(callConv.params).zip(signature.parameters).forEach {
val paramName = it.second.name val paramName = it.second.name
val conv = it.first.second val conv = it.first.second
val value = it.first.first val value = it.first.first
when { when {
conv.variable -> { conv.variable -> {
val varname = "prog8_lib.func_${signature.name}._arg_${paramName}" val varname = "prog8_lib.func_${call.name}._arg_${paramName}"
val src = when (conv.dt) { val src = when (conv.dt) {
DataType.FLOAT -> getSourceForFloat(value) DataType.FLOAT -> getSourceForFloat(value)
in PassByReferenceDatatypes -> { in PassByReferenceDatatypes -> {
// put the address of the argument in AY // put the address of the argument in AY
val addr = AddressOf(value as IdentifierReference, value.position) val addr = PtAddressOf(value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen) AsmAssignSource.fromAstSource(addr, program, asmgen)
} }
else -> { else -> {
@ -1077,7 +1003,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, variableAsmName = varname) val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position) val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }
conv.reg != null -> { conv.reg != null -> {
@ -1085,7 +1011,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
DataType.FLOAT -> getSourceForFloat(value) DataType.FLOAT -> getSourceForFloat(value)
in PassByReferenceDatatypes -> { in PassByReferenceDatatypes -> {
// put the address of the argument in AY // put the address of the argument in AY
val addr = AddressOf(value as IdentifierReference, value.position) val addr = PtAddressOf(value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen) AsmAssignSource.fromAstSource(addr, program, asmgen)
} }
else -> { else -> {
@ -1093,7 +1021,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, asmgen) val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position) val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }
else -> throw AssemblyError("callconv") else -> throw AssemblyError("callconv")

View File

@ -1,18 +1,17 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.Program import prog8.code.ast.*
import prog8.ast.expressions.*
import prog8.code.core.* import prog8.code.core.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, internal class ExpressionsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen, private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator) { private val allocator: VariableAllocator) {
@Deprecated("avoid calling this as it generates slow evalstack based code") @Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) { internal fun translateExpression(expression: PtExpression) {
if (this.asmgen.options.slowCodegenWarnings) { if (this.asmgen.options.slowCodegenWarnings) {
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position) asmgen.errors.warn("slow stack evaluation used for expression", expression.position)
} }
translateExpressionInternal(expression) translateExpressionInternal(expression)
} }
@ -21,41 +20,42 @@ internal class ExpressionsAsmGen(private val program: Program,
// the rest of the methods are all PRIVATE // the rest of the methods are all PRIVATE
private fun translateExpressionInternal(expression: Expression) { private fun translateExpressionInternal(expression: PtExpression) {
when(expression) { when(expression) {
is PrefixExpression -> translateExpression(expression) is PtPrefix -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression) is PtBinaryExpression -> translateExpression(expression)
is ArrayIndexedExpression -> translateExpression(expression) is PtArrayIndexer -> translateExpression(expression)
is TypecastExpression -> translateExpression(expression) is PtTypeCast -> translateExpression(expression)
is AddressOf -> translateExpression(expression) is PtAddressOf -> translateExpression(expression)
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true) is PtMemoryByte -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteral -> translateExpression(expression) is PtNumber -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression) is PtIdentifier -> translateExpression(expression)
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression) is PtFunctionCall -> translateFunctionCallResultOntoStack(expression)
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null) is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported") is PtContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") is PtArray, is PtString -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values") is PtRange -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding") is PtMachineRegister -> throw AssemblyError("machine register ast node should not occur in 6502 codegen it is for IR code")
else -> TODO("missing expression asmgen for $expression") else -> TODO("missing expression asmgen for $expression")
} }
} }
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) { private fun translateFunctionCallResultOntoStack(call: PtFunctionCall) {
// only for use in nested expression evaluation // only for use in nested expression evaluation
val sub = call.target.targetSubroutine(program)!! val symbol = asmgen.symbolTable.lookup(call.name)
val sub = symbol!!.astNode as IPtSubroutine
asmgen.saveXbeforeCall(call) asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true) asmgen.translateFunctionCall(call)
if(sub.regXasResult()) { if(sub.regXasResult()) {
// store the return value in X somewhere that we can access again below // store the return value in X somewhere that we can access again below
asmgen.out(" stx P8ZP_SCRATCH_REG") asmgen.out(" stx P8ZP_SCRATCH_REG")
} }
asmgen.restoreXafterCall(call) asmgen.restoreXafterCall(call)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) val returns: List<Pair<RegisterOrStatusflag, DataType>> = sub.returnsWhatWhere()
for ((_, reg) in returns) { for ((reg, _) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree) // result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) { if (reg.registerOrPair != null) {
when (reg.registerOrPair!!) { when (reg.registerOrPair!!) {
@ -133,9 +133,9 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateExpression(typecast: TypecastExpression) { private fun translateExpression(typecast: PtTypeCast) {
translateExpressionInternal(typecast.expression) translateExpressionInternal(typecast.value)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) { when(typecast.value.type) {
DataType.UBYTE, DataType.BOOL -> { DataType.UBYTE, DataType.BOOL -> {
when(typecast.type) { when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {} DataType.UBYTE, DataType.BYTE -> {}
@ -197,12 +197,12 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateExpression(expr: AddressOf) { private fun translateExpression(expr: PtAddressOf) {
val name = asmgen.asmVariableName(expr.identifier) val name = asmgen.asmVariableName(expr.identifier)
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex") asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
} }
private fun translateExpression(expr: NumericLiteral) { private fun translateExpression(expr: PtNumber) {
when(expr.type) { when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex") DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out(""" DataType.UWORD, DataType.WORD -> asmgen.out("""
@ -220,9 +220,9 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateExpression(expr: IdentifierReference) { private fun translateExpression(expr: PtIdentifier) {
val varname = asmgen.asmVariableName(expr) val varname = asmgen.asmVariableName(expr)
when(expr.inferType(program).getOr(DataType.UNDEFINED)) { when(expr.type) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex") asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
} }
@ -239,22 +239,16 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateExpression(expr: BinaryExpression) { private fun translateExpression(expr: PtBinaryExpression) {
// Uses evalstack to evaluate the given expression. // Uses evalstack to evaluate the given expression. THIS IS SLOW AND SHOULD BE AVOIDED!
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance). val leftDt = expr.left.type
val leftIDt = expr.left.inferType(program) val rightDt = expr.right.type
val rightIDt = expr.right.inferType(program) // see if we can apply some optimized routines still
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
// see if we can apply some optimized routines
when(expr.operator) { when(expr.operator) {
"+" -> { "+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt() val leftVal = expr.left.asConstInteger()
val rightVal = expr.right.constValue(program)?.number?.toInt() val rightVal = expr.right.asConstInteger()
if (leftVal!=null && leftVal in -4..4) { if (leftVal!=null && leftVal in -4..4) {
translateExpressionInternal(expr.right) translateExpressionInternal(expr.right)
if(rightDt in ByteDatatypes) { if(rightDt in ByteDatatypes) {
@ -318,7 +312,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
"-" -> { "-" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt() val rightVal = expr.right.asConstInteger()
if (rightVal!=null && rightVal in -4..4) if (rightVal!=null && rightVal in -4..4)
{ {
translateExpressionInternal(expr.left) translateExpressionInternal(expr.left)
@ -352,7 +346,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
">>" -> { ">>" -> {
val amount = expr.right.constValue(program)?.number?.toInt() val amount = expr.right.asConstInteger()
if(amount!=null) { if(amount!=null) {
translateExpressionInternal(expr.left) translateExpressionInternal(expr.left)
when (leftDt) { when (leftDt) {
@ -423,7 +417,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
"<<" -> { "<<" -> {
val amount = expr.right.constValue(program)?.number?.toInt() val amount = expr.right.asConstInteger()
if(amount!=null) { if(amount!=null) {
translateExpressionInternal(expr.left) translateExpressionInternal(expr.left)
if (leftDt in ByteDatatypes) { if (leftDt in ByteDatatypes) {
@ -450,13 +444,13 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
"*" -> { "*" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVar = expr.left as? IdentifierReference val leftVar = expr.left as? PtIdentifier
val rightVar = expr.right as? IdentifierReference val rightVar = expr.right as? PtIdentifier
if(leftVar!=null && rightVar!=null && leftVar==rightVar) if(leftVar!=null && rightVar!=null && leftVar==rightVar)
return translateSquared(leftVar, leftDt) return translateSquared(leftVar, leftDt)
} }
val value = expr.right.constValue(program) val value = expr.right as? PtNumber
if(value!=null) { if(value!=null) {
if(rightDt in IntegerDatatypes) { if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt() val amount = value.number.toInt()
@ -516,7 +510,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
"/" -> { "/" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt() val rightVal = expr.right.asConstInteger()
if(rightVal!=null && rightVal==2) { if(rightVal!=null && rightVal==2) {
translateExpressionInternal(expr.left) translateExpressionInternal(expr.left)
when (leftDt) { when (leftDt) {
@ -557,8 +551,8 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
in ComparisonOperators -> { in ComparisonOperators -> {
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) { if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val rightVal = expr.right.constValue(program)?.number val rightVal = expr.right.asConstInteger()
if(rightVal==0.0) if(rightVal==0)
return translateComparisonWithZero(expr.left, leftDt, expr.operator) return translateComparisonWithZero(expr.left, leftDt, expr.operator)
} }
} }
@ -572,7 +566,8 @@ internal class ExpressionsAsmGen(private val program: Program,
translateCompareStrings(expr.left, expr.operator, expr.right) translateCompareStrings(expr.left, expr.operator, expr.right)
} }
else { else {
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore) // the general, non-optimized cases
// TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
translateExpressionInternal(expr.left) translateExpressionInternal(expr.left)
translateExpressionInternal(expr.right) translateExpressionInternal(expr.right)
when (leftDt) { when (leftDt) {
@ -584,8 +579,8 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) { private fun translateComparisonWithZero(expr: PtExpression, dt: DataType, operator: String) {
if(expr.isSimple) { if(expr.isSimple()) {
if(operator=="!=") { if(operator=="!=") {
when (dt) { when (dt) {
in ByteDatatypes -> { in ByteDatatypes -> {
@ -641,7 +636,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
"<" -> { "<" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD) if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position)) return translateExpressionInternal(PtNumber.fromBoolean(false, expr.position))
when(dt) { when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b") DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w") DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
@ -671,7 +666,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
">=" -> { ">=" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD) if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position)) return translateExpressionInternal(PtNumber.fromBoolean(true, expr.position))
when(dt) { when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb") DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw") DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
@ -683,7 +678,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateSquared(variable: IdentifierReference, dt: DataType) { private fun translateSquared(variable: PtIdentifier, dt: DataType) {
val asmVar = asmgen.asmVariableName(variable) val asmVar = asmgen.asmVariableName(variable)
when(dt) { when(dt) {
DataType.BYTE, DataType.UBYTE -> { DataType.BYTE, DataType.UBYTE -> {
@ -699,14 +694,12 @@ internal class ExpressionsAsmGen(private val program: Program,
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex") asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} }
private fun translateExpression(expr: PrefixExpression) { private fun translateExpression(expr: PtPrefix) {
translateExpressionInternal(expr.expression) translateExpressionInternal(expr.value)
val itype = expr.inferType(program)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when(expr.operator) { when(expr.operator) {
"+" -> {} "+" -> {}
"-" -> { "-" -> {
when(type) { when(expr.type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b") in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w") in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f") DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
@ -714,7 +707,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
"~" -> { "~" -> {
when(type) { when(expr.type) {
in ByteDatatypes -> in ByteDatatypes ->
asmgen.out(""" asmgen.out("""
lda P8ESTACK_LO+1,x lda P8ESTACK_LO+1,x
@ -729,22 +722,18 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateExpression(arrayExpr: ArrayIndexedExpression) { private fun translateExpression(arrayExpr: PtArrayIndexer) {
val elementIDt = arrayExpr.inferType(program) val elementDt = arrayExpr.type
if(!elementIDt.isKnown) val arrayVarName = asmgen.asmVariableName(arrayExpr.variable)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
val arrayVarDecl = arrayExpr.arrayvar.targetVarDecl(program)!! if(arrayExpr.variable.type==DataType.UWORD) {
if(arrayVarDecl.datatype==DataType.UWORD) {
// indexing a pointer var instead of a real array or string // indexing a pointer var instead of a real array or string
if(elementDt !in ByteDatatypes) if(elementDt !in ByteDatatypes)
throw AssemblyError("non-array var indexing requires bytes dt") throw AssemblyError("non-array var indexing requires bytes dt")
if(arrayExpr.inferType(program) isnot DataType.UBYTE) if(arrayExpr.index.type != DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index") throw AssemblyError("non-array var indexing requires bytes index")
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y) asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
if(asmgen.isZpVar(arrayExpr.arrayvar)) { if(asmgen.isZpVar(arrayExpr.variable)) {
asmgen.out(" lda ($arrayVarName),y") asmgen.out(" lda ($arrayVarName),y")
} else { } else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1") asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
@ -754,7 +743,7 @@ internal class ExpressionsAsmGen(private val program: Program,
return return
} }
val constIndexNum = arrayExpr.indexer.constIndex() val constIndexNum = arrayExpr.index.asConstInteger()
if(constIndexNum!=null) { if(constIndexNum!=null) {
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt) val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
@ -881,7 +870,7 @@ internal class ExpressionsAsmGen(private val program: Program,
} }
} }
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) { private fun translateCompareStrings(s1: PtExpression, operator: String, s2: PtExpression) {
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null) asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null) asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A

View File

@ -0,0 +1,60 @@
package prog8.codegen.cpu6502
import prog8.code.ast.IPtSubroutine
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtSub
import prog8.code.core.*
internal fun IPtSubroutine.regXasResult(): Boolean =
(this is PtAsmSub) && this.returns.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
internal fun IPtSubroutine.shouldSaveX(): Boolean =
this.regXasResult() || (this is PtAsmSub && (CpuRegister.X in this.clobbers || regXasParam()))
internal fun PtAsmSub.regXasParam(): Boolean =
parameters.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
internal class KeepAresult(val saveOnEntry: Boolean, val saveOnReturn: Boolean)
internal fun PtAsmSub.shouldKeepA(): KeepAresult {
// determine if A's value should be kept when preparing for calling the subroutine, and when returning from it
// it seems that we never have to save A when calling? will be loaded correctly after setup.
// but on return it depends on wether the routine returns something in A.
val saveAonReturn = returns.any { it.first.registerOrPair==RegisterOrPair.A || it.first.registerOrPair==RegisterOrPair.AY || it.first.registerOrPair==RegisterOrPair.AX }
return KeepAresult(false, saveAonReturn)
}
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
when(this) {
is PtAsmSub -> {
return returns
}
is PtSub -> {
// for non-asm subroutines, determine the return registers based on the type of the return value
return if(returntype==null)
emptyList()
else {
val register = when (returntype!!) {
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
listOf(Pair(register, returntype!!))
}
}
}
}
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
return when(returntype) {
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
null -> null
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
}

View File

@ -1,46 +1,43 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold import com.github.michaelbull.result.fold
import prog8.ast.Program import prog8.code.ast.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpression
import prog8.ast.statements.ForLoop
import prog8.code.core.* import prog8.code.core.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) { internal class ForLoopsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val zeropage: Zeropage) {
internal fun translate(stmt: ForLoop) { internal fun translate(stmt: PtForLoop) {
val iterableDt = stmt.iterable.inferType(program) val iterableDt = stmt.iterable.type
if(!iterableDt.isKnown)
throw AssemblyError("unknown dt")
when(stmt.iterable) { when(stmt.iterable) {
is RangeExpression -> { is PtRange -> {
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange() val range = (stmt.iterable as PtRange).toConstantIntegerRange()
if(range==null) { if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression) translateForOverNonconstRange(stmt, iterableDt, stmt.iterable as PtRange)
} else { } else {
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range) translateForOverConstRange(stmt, iterableDt, range)
} }
} }
is IdentifierReference -> { is PtIdentifier -> {
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference) translateForOverIterableVar(stmt, iterableDt, stmt.iterable as PtIdentifier)
} }
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable") else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
} }
} }
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) { private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = program.makeLabel("for_modified") val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = program.makeLabel("for_modifiedb") val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val stepsize=range.step.constValue(program)!!.number.toInt() val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) { if(stepsize < -1) {
val limit = range.to.constValue(program)?.number val limit = range.to.asConstInteger()
if(limit==0.0) if(limit==0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping") throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
} }
@ -52,11 +49,11 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val incdec = if(stepsize==1) "inc" else "dec" val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar // loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null) asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null) asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel) asmgen.out(loopLabel)
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
$modifiedLabel cmp #0 ; modified $modifiedLabel cmp #0 ; modified
@ -70,11 +67,11 @@ $modifiedLabel cmp #0 ; modified
// bytes, step >= 2 or <= -2 // bytes, step >= 2 or <= -2
// loop over byte range via loopvar // loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null) asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null) asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel) asmgen.out(loopLabel)
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
if(stepsize>0) { if(stepsize>0) {
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
@ -102,14 +99,14 @@ $modifiedLabel cmp #0 ; modified
// words, step 1 or -1 // words, step 1 or -1
stepsize == 1 || stepsize == -1 -> { stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range) assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
sty $modifiedLabel+1 sty $modifiedLabel+1
sta $modifiedLabel2+1 sta $modifiedLabel2+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
asmgen.out(""" asmgen.out("""
lda $varname+1 lda $varname+1
$modifiedLabel cmp #0 ; modified $modifiedLabel cmp #0 ; modified
@ -136,14 +133,14 @@ $modifiedLabel2 cmp #0 ; modified
stepsize > 0 -> { stepsize > 0 -> {
// (u)words, step >= 2 // (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range) assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
sty $modifiedLabel+1 sty $modifiedLabel+1
sta $modifiedLabel2+1 sta $modifiedLabel2+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
if (iterableDt == DataType.ARRAY_UW) { if (iterableDt == DataType.ARRAY_UW) {
asmgen.out(""" asmgen.out("""
@ -184,14 +181,14 @@ $endLabel""")
else -> { else -> {
// (u)words, step <= -2 // (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range) assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
sty $modifiedLabel+1 sty $modifiedLabel+1
sta $modifiedLabel2+1 sta $modifiedLabel2+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
if(iterableDt==DataType.ARRAY_UW) { if(iterableDt==DataType.ARRAY_UW) {
asmgen.out(""" asmgen.out("""
@ -237,12 +234,18 @@ $endLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) { private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident) val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program)!! val symbol = asmgen.symbolTable.lookup(ident.name)
val decl = symbol!!.astNode as IPtVariable
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("length of non-array requested")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
}
when(iterableDt) { when(iterableDt) {
DataType.STR -> { DataType.STR -> {
asmgen.out(""" asmgen.out("""
@ -252,8 +255,8 @@ $endLabel""")
sty $loopLabel+2 sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified $loopLabel lda ${65535.toHex()} ; modified
beq $endLabel beq $endLabel
sta ${asmgen.asmVariableName(stmt.loopVar)}""") sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
asmgen.out(""" asmgen.out("""
inc $loopLabel+1 inc $loopLabel+1
bne $loopLabel bne $loopLabel
@ -262,19 +265,18 @@ $loopLabel lda ${65535.toHex()} ; modified
$endLabel""") $endLabel""")
} }
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
val length = decl.arraysize!!.constIndex()!! val indexVar = asmgen.makeLabel("for_index")
val indexVar = program.makeLabel("for_index")
asmgen.out(""" asmgen.out("""
ldy #0 ldy #0
$loopLabel sty $indexVar $loopLabel sty $indexVar
lda $iterableName,y lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.loopVar)}""") sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
if(length<=255) { if(numElements!!<=255u) {
asmgen.out(""" asmgen.out("""
ldy $indexVar ldy $indexVar
iny iny
cpy #$length cpy #$numElements
beq $endLabel beq $endLabel
bne $loopLabel""") bne $loopLabel""")
} else { } else {
@ -285,9 +287,9 @@ $loopLabel sty $indexVar
bne $loopLabel bne $loopLabel
beq $endLabel""") beq $endLabel""")
} }
if(length>=16) { if(numElements>=16u) {
// allocate index var on ZP if possible // allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors) val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold( result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") }, success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") } failure = { asmgen.out("$indexVar .byte 0") }
@ -298,9 +300,9 @@ $loopLabel sty $indexVar
asmgen.out(endLabel) asmgen.out(endLabel)
} }
DataType.ARRAY_W, DataType.ARRAY_UW -> { DataType.ARRAY_W, DataType.ARRAY_UW -> {
val length = decl.arraysize!!.constIndex()!! * 2 val length = numElements!! * 2u
val indexVar = program.makeLabel("for_index") val indexVar = asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.loopVar) val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out(""" asmgen.out("""
ldy #0 ldy #0
$loopLabel sty $indexVar $loopLabel sty $indexVar
@ -308,8 +310,8 @@ $loopLabel sty $indexVar
sta $loopvarName sta $loopvarName
lda $iterableName+1,y lda $iterableName+1,y
sta $loopvarName+1""") sta $loopvarName+1""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
if(length<=127) { if(length<=127u) {
asmgen.out(""" asmgen.out("""
ldy $indexVar ldy $indexVar
iny iny
@ -326,9 +328,9 @@ $loopLabel sty $indexVar
bne $loopLabel bne $loopLabel
beq $endLabel""") beq $endLabel""")
} }
if(length>=16) { if(length>=16u) {
// allocate index var on ZP if possible // allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors) val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold( result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") }, success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") } failure = { asmgen.out("$indexVar .byte 0") }
@ -346,7 +348,7 @@ $loopLabel sty $indexVar
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) { private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
if (range.isEmpty() || range.step==0) if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0") throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) { if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
@ -359,18 +361,18 @@ $loopLabel sty $indexVar
} }
// not one of the easy cases, generate more complex code... // not one of the easy cases, generate more complex code...
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
when(iterableDt) { when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> { DataType.ARRAY_B, DataType.ARRAY_UB -> {
// loop over byte range via loopvar, step >= 2 or <= -2 // loop over byte range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out(""" asmgen.out("""
lda #${range.first} lda #${range.first}
sta $varname sta $varname
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
when (range.step) { when (range.step) {
0, 1, -1 -> { 0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt") throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
@ -430,7 +432,7 @@ $loopLabel""")
} }
DataType.ARRAY_W, DataType.ARRAY_UW -> { DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar, step >= 2 or <= -2 // loop over word range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
when (range.step) { when (range.step) {
0, 1, -1 -> { 0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt") throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
@ -444,7 +446,7 @@ $loopLabel""")
sta $varname sta $varname
sty $varname+1 sty $varname+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
cmp #<${range.last} cmp #<${range.last}
@ -470,16 +472,16 @@ $loopLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) { private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out(""" asmgen.out("""
lda #${range.first} lda #${range.first}
sta $varname sta $varname
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
if (range.last == 255) { if (range.last == 255) {
asmgen.out(""" asmgen.out("""
inc $varname inc $varname
@ -496,16 +498,16 @@ $endLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) { private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out(""" asmgen.out("""
lda #${range.first} lda #${range.first}
sta $varname sta $varname
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
when (range.last) { when (range.last) {
0 -> { 0 -> {
asmgen.out(""" asmgen.out("""
@ -533,18 +535,18 @@ $endLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) { private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out(""" asmgen.out("""
lda #<${range.first} lda #<${range.first}
ldy #>${range.first} ldy #>${range.first}
sta $varname sta $varname
sty $varname+1 sty $varname+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
cmp #<${range.last} cmp #<${range.last}
@ -560,18 +562,18 @@ $loopLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) { private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out(""" asmgen.out("""
lda #<${range.first} lda #<${range.first}
ldy #>${range.first} ldy #>${range.first}
sta $varname sta $varname
sty $varname+1 sty $varname+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.statements)
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
cmp #<${range.last} cmp #<${range.last}
@ -588,10 +590,10 @@ $loopLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) = private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable( asmgen.assignExpressionToVariable(
range.from, range.from,
asmgen.asmVariableName(stmt.loopVar), asmgen.asmVariableName(stmt.variable),
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") }, stmt.variable.type,
stmt.definingSubroutine) stmt.definingISub())
} }

View File

@ -1,16 +1,6 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall import prog8.code.ast.*
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.code.core.* import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignSource import prog8.codegen.cpu6502.assignment.AsmAssignSource
import prog8.codegen.cpu6502.assignment.AsmAssignTarget import prog8.codegen.cpu6502.assignment.AsmAssignTarget
@ -18,51 +8,60 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.cpu6502.assignment.TargetStorageKind import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) { internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
saveXbeforeCall(stmt) saveXbeforeCall(stmt)
translateFunctionCall(stmt, false) translateFunctionCall(stmt)
restoreXafterCall(stmt) restoreXafterCall(stmt)
// just ignore any result values from the function call. // just ignore any result values from the function call.
} }
internal fun saveXbeforeCall(stmt: IFunctionCall) { internal fun saveXbeforeCall(stmt: PtFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val symbol = asmgen.symbolTable.lookup(stmt.name)
val sub = symbol!!.astNode as IPtSubroutine
if(sub.shouldSaveX()) { if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls if(sub is PtAsmSub) {
if(regSaveOnStack) val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry) if (regSaveOnStack)
else asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!) else
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
} else
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
} }
} }
internal fun restoreXafterCall(stmt: IFunctionCall) { internal fun restoreXafterCall(stmt: PtFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val symbol = asmgen.symbolTable.lookup(stmt.name)
val sub = symbol!!.astNode as IPtSubroutine
if(sub.shouldSaveX()) { if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls if(sub is PtAsmSub) {
if(regSaveOnStack) val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn) if (regSaveOnStack)
else asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
} else
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
} }
} }
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) = internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes) (sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes) || (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter internal fun translateFunctionCall(call: PtFunctionCall) {
// Output only the code to set up the parameters and perform the actual call // Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values! // NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!! // NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this) // (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}") val symbol = asmgen.symbolTable.lookup(call.name)
val subAsmName = asmgen.asmSymbolName(call.target) val sub = symbol!!.astNode as IPtSubroutine
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub.isAsmSubroutine) { if(sub is PtAsmSub) {
argumentsViaRegisters(sub, call) argumentsViaRegisters(sub, call)
if (sub.inline && asmgen.options.optimize) { if (sub.inline && asmgen.options.optimize) {
// inline the subroutine. // inline the subroutine.
@ -70,16 +69,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine // NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier) // (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}") asmgen.out(" \t; inlined routine follows: ${sub.name}")
sub.statements.forEach { asmgen.translate(it as InlineAssembly) } sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
asmgen.out(" \t; inlined routine end: ${sub.name}") asmgen.out(" \t; inlined routine end: ${sub.name}")
} else { } else {
asmgen.out(" jsr $subAsmName") asmgen.out(" jsr $subAsmName")
} }
} }
else { else if(sub is PtSub) {
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
if(optimizeIntArgsViaRegisters(sub)) { if(optimizeIntArgsViaRegisters(sub)) {
if(sub.parameters.size==1) { if(sub.parameters.size==1) {
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
@ -100,84 +96,82 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
asmgen.out(" jsr $subAsmName") asmgen.out(" jsr $subAsmName")
} }
else throw AssemblyError("invalid sub type")
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
} }
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) { private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
if(sub.parameters.size==1) { if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0]) argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else { } else {
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) { if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
registerArgsViaCpuStackEvaluation(call, sub) registerArgsViaCpuStackEvaluation(call, sub)
} else { } else {
asmsub6502ArgsEvalOrder(sub).forEach { asmsub6502ArgsEvalOrder(sub).forEach {
val param = sub.parameters[it] val param = sub.parameters[it]
val arg = call.args[it] val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg) argumentViaRegister(sub, IndexedValue(it, param.second), arg)
} }
} }
} }
} }
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: Subroutine) { private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
// this is called when one or more of the arguments are 'complex' and // this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers. // cannot be assigned to a register easily or risk clobbering other registers.
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
if(callee.parameters.isEmpty()) if(callee.parameters.isEmpty())
return return
// use the cpu hardware stack as intermediate storage for the arguments. // use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmsub6502ArgsEvalOrder(callee) val argOrder = asmsub6502ArgsEvalOrder(callee)
argOrder.reversed().forEach { argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it]) asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
} }
argOrder.forEach { argOrder.forEach {
val param = callee.parameters[it] val param = callee.parameters[it]
val targetVar = callee.searchParameter(param.name)!! asmgen.popCpuStack(callee, param.second, param.first)
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
} }
} }
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) { private fun argumentViaVariable(sub: PtSub, parameter: PtSubroutineParameter, value: PtExpression) {
// pass parameter via a regular variable (not via registers) // pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program) if(!isArgumentTypeCompatible(value.type, parameter.type))
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name) val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub) asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
} }
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) { private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
// pass argument via a register parameter // pass argument via a register parameter
val valueIDt = value.inferType(program) if(!isArgumentTypeCompatible(value.type, parameter.value.type))
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null) val paramRegister: RegisterOrStatusflag = when(sub) {
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
is PtSub -> RegisterOrStatusflag(registerOverride!!, null)
}
val statusflag = paramRegister.statusflag val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type val requiredDt = parameter.value.type
if(requiredDt!=valueDt) { if(requiredDt!=value.type) {
if(valueDt largerThan requiredDt) if(value.type largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types") throw AssemblyError("can only convert byte values to word param types")
} }
if (statusflag!=null) { if (statusflag!=null) {
if(requiredDt!=valueDt) if(requiredDt!=value.type)
throw AssemblyError("for statusflag, byte value is required") throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) { if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr // this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker // for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) { when(value) {
is NumericLiteral -> { is PtNumber -> {
val carrySet = value.number.toInt() != 0 val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc") asmgen.out(if(carrySet) " sec" else " clc")
} }
is IdentifierReference -> { is PtIdentifier -> {
val sourceName = asmgen.asmVariableName(value) val sourceName = asmgen.asmVariableName(value)
asmgen.out(""" asmgen.out("""
pha pha
@ -202,10 +196,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
else { else {
// via register or register pair // via register or register pair
register!! register!!
if(requiredDt largerThan valueDt) { if(requiredDt largerThan value.type) {
// we need to sign extend the source, do this via temporary word variable // we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub) asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt) asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register) asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
} else { } else {
val target: AsmAssignTarget = val target: AsmAssignTarget =
@ -215,9 +209,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen) AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
} }
val src = if(valueDt in PassByReferenceDatatypes) { val src = if(value.type in PassByReferenceDatatypes) {
if(value is IdentifierReference) { if(value is PtIdentifier) {
val addr = AddressOf(value, Position.DUMMY) val addr = PtAddressOf(Position.DUMMY)
addr.add(value)
addr.parent = sub as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target) AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else { } else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
@ -225,7 +221,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} else { } else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
} }
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY)) asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY))
} }
} }
} }

View File

@ -1,23 +1,23 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.Program import prog8.code.ast.PtIdentifier
import prog8.ast.expressions.IdentifierReference import prog8.code.ast.PtNumber
import prog8.ast.expressions.NumericLiteral import prog8.code.ast.PtPostIncrDecr
import prog8.ast.statements.PostIncrDecr import prog8.code.ast.PtProgram
import prog8.code.core.* import prog8.code.core.*
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class PostIncrDecrAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translate(stmt: PostIncrDecr) { internal fun translate(stmt: PtPostIncrDecr) {
val incr = stmt.operator=="++" val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress val targetMemory = stmt.target.memory
val targetArrayIdx = stmt.target.arrayindexed val targetArrayIdx = stmt.target.array
val scope = stmt.definingSubroutine val scope = stmt.definingISub()
when { when {
targetIdent!=null -> { targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent) val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) { when (stmt.target.type) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what") in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> { in WordDatatypes -> {
if(incr) if(incr)
@ -38,12 +38,12 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
} }
targetMemory!=null -> { targetMemory!=null -> {
when (val addressExpr = targetMemory.addressExpression) { when (val addressExpr = targetMemory.address) {
is NumericLiteral -> { is PtNumber -> {
val what = addressExpr.number.toHex() val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what") asmgen.out(if(incr) " inc $what" else " dec $what")
} }
is IdentifierReference -> { is PtIdentifier -> {
val what = asmgen.asmVariableName(addressExpr) val what = asmgen.asmVariableName(addressExpr)
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2") asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr) if(incr)
@ -62,9 +62,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
} }
targetArrayIdx!=null -> { targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar) val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED) val elementDt = targetArrayIdx.type
val constIndex = targetArrayIdx.indexer.constIndex() val constIndex = targetArrayIdx.index.asConstInteger()
if(constIndex!=null) { if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt) val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {

View File

@ -1,12 +1,10 @@
package prog8.codegen.cpu6502 package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.statements.*
import prog8.code.* import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.* import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.TargetStorageKind import prog8.codegen.cpu6502.assignment.TargetStorageKind
import prog8.compiler.CallGraph
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -20,30 +18,26 @@ import kotlin.math.absoluteValue
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!) * - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
*/ */
internal class ProgramAndVarsGen( internal class ProgramAndVarsGen(
val program: Program, val program: PtProgram,
val options: CompilationOptions, val options: CompilationOptions,
val errors: IErrorReporter, val errors: IErrorReporter,
private val symboltable: SymbolTable, private val symboltable: SymbolTable,
private val functioncallAsmGen: FunctionCallAsmGen, private val functioncallAsmGen: FunctionCallAsmGen,
private val asmgen: AsmGen, private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator, private val allocator: VariableAllocator,
private val zeropage: Zeropage private val zeropage: Zeropage
) { ) {
private val compTarget = options.compTarget private val compTarget = options.compTarget
private val callGraph = CallGraph(program, true) private val blockVariableInitializers = program.allBlocks().associateWith { it.children.filterIsInstance<PtAssignment>() }
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
internal fun generate() { internal fun generate() {
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
header() header()
val allBlocks = program.allBlocks val allBlocks = program.allBlocks()
if(allBlocks.first().name != "main") if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'") throw AssemblyError("first block should be 'main'")
if(errors.noErrors()) { if(errors.noErrors()) {
program.allBlocks.forEach { block2asm(it) } program.allBlocks().forEach { block2asm(it) }
// the global list of all floating point constants for the whole program // the global list of all floating point constants for the whole program
asmgen.out("; global float constants") asmgen.out("; global float constants")
@ -71,7 +65,7 @@ internal class ProgramAndVarsGen(
asmgen.out("; assembler syntax is for the 64tasm cross-assembler") asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
asmgen.out("") asmgen.out("")
asmgen.out(".cpu '$cpu'\n.enc 'none'\n") asmgen.out(".cpu '$cpu'\n.enc 'none'")
// the global prog8 variables needed // the global prog8 variables needed
val zp = zeropage val zp = zeropage
@ -94,13 +88,13 @@ internal class ProgramAndVarsGen(
when(options.output) { when(options.output) {
OutputType.RAW -> { OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----") asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n") asmgen.out("* = ${options.loadAddress.toHex()}")
} }
OutputType.PRG -> { OutputType.PRG -> {
when(options.launcher) { when(options.launcher) {
CbmPrgLauncherType.BASIC -> { CbmPrgLauncherType.BASIC -> {
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) { if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position) errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
} }
asmgen.out("; ---- basic program with sys call ----") asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}") asmgen.out("* = ${options.loadAddress.toHex()}")
@ -108,14 +102,14 @@ internal class ProgramAndVarsGen(
asmgen.out(" .word (+), $year") asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0") asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here\n") asmgen.out("prog8_entrypoint\t; assembly code starts here")
if(!options.noSysInit) if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system") asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2") asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
} }
CbmPrgLauncherType.NONE -> { CbmPrgLauncherType.NONE -> {
asmgen.out("; ---- program without basic sys call ----") asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n") asmgen.out("* = ${options.loadAddress.toHex()}")
if(!options.noSysInit) if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system") asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2") asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
@ -124,7 +118,7 @@ internal class ProgramAndVarsGen(
} }
OutputType.XEX -> { OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----") asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n") asmgen.out("* = ${options.loadAddress.toHex()}")
if(!options.noSysInit) if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system") asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2") asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
@ -168,61 +162,88 @@ internal class ProgramAndVarsGen(
} }
private fun memorySlabs() { private fun memorySlabs() {
asmgen.out("; memory slabs") if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("prog8_slabs\t.block") asmgen.out("; memory slabs\n .section slabs_BSS")
for(slab in symboltable.allMemorySlabs) { asmgen.out("prog8_slabs\t.block")
if(slab.align>1u) for (slab in symboltable.allMemorySlabs) {
asmgen.out("\t.align ${slab.align.toHex()}") if (slab.align > 1u)
asmgen.out("${slab.name}\t.fill ${slab.size}") asmgen.out("\t.align ${slab.align.toHex()}")
asmgen.out("${slab.name}\t.fill ${slab.size}")
}
asmgen.out("\t.bend\n .send slabs_BSS")
} }
asmgen.out("\t.bend")
} }
private fun footer() { private fun footer() {
// program end asmgen.out("; bss sections")
asmgen.out("prog8_program_end\t; end of program label for progend()") if(options.varsHigh) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
}
// BSS vars in high ram area, memory() slabs just concatenated at the end of the program.
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out(" .dsection slabs_BSS")
}
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${options.compTarget.machine.BSSHIGHRAM_START.toHex()}")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out(" .cerror * >= ${options.compTarget.machine.BSSHIGHRAM_END.toHex()}, \"too many variables for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
// BSS vars followed by memory() slabs, concatenated at the end of the program.
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out(" .dsection slabs_BSS")
}
asmgen.out("prog8_program_end\t; end of program label for progend()")
}
} }
private fun block2asm(block: Block) { private fun block2asm(block: PtBlock) {
asmgen.out("") asmgen.out("")
asmgen.out("; ---- block: '${block.name}' ----") asmgen.out("; ---- block: '${block.name}' ----")
if(block.address!=null) if(block.address!=null)
asmgen.out("* = ${block.address!!.toHex()}") asmgen.out("* = ${block.address!!.toHex()}")
else { else {
if("align_word" in block.options()) if(block.alignment==PtBlock.BlockAlignment.WORD)
asmgen.out("\t.align 2") asmgen.out("\t.align 2")
else if("align_page" in block.options()) else if(block.alignment==PtBlock.BlockAlignment.PAGE)
asmgen.out("\t.align $100") asmgen.out("\t.align $100")
} }
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n")) asmgen.out("${block.name}\t" + (if(block.forceOutput) ".block" else ".proc"))
asmgen.outputSourceLine(block) asmgen.outputSourceLine(block)
createBlockVariables(block) createBlockVariables(block)
asmsubs2asm(block.statements) asmsubs2asm(block.children)
asmgen.out("") asmgen.out("")
val initializers = blockVariableInitializers.getValue(block) val initializers = blockVariableInitializers.getValue(block)
val notInitializers = block.statements.filterNot { it in initializers } val notInitializers = block.children.filterNot { it in initializers }
notInitializers.forEach { asmgen.translate(it) } notInitializers.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) { // generate subroutine to initialize block-level (global) variables
// generate subroutine to initialize block-level (global) variables if (initializers.isNotEmpty()) {
if (initializers.isNotEmpty()) { asmgen.out("prog8_init_vars\t.block")
asmgen.out("prog8_init_vars\t.block\n") initializers.forEach { assign ->
initializers.forEach { assign -> asmgen.translate(assign) } if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.out(" rts\n .bend") asmgen.translate(assign)
// the other variables that should be set to zero are done so as part of the BSS section.
} }
asmgen.out(" rts\n .bend")
} }
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n") asmgen.out(if(block.forceOutput) "\n\t.bend" else "\n\t.pend")
} }
private fun getVars(scope: StNode): Map<String, StNode> = private fun getVars(scope: StNode): Map<String, StNode> =
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) } scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
private fun createBlockVariables(block: Block) { private fun createBlockVariables(block: PtBlock) {
val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") } val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.BLOCK) require(scope.type==StNodeType.BLOCK)
val varsInBlock = getVars(scope) val varsInBlock = getVars(scope)
@ -242,23 +263,15 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables // normal statically allocated variables
val variables = varsInBlock val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) } .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.map { it.value as StStaticVariable } .map { it.value as StStaticVariable }
nonZpVariables2asm(variables) nonZpVariables2asm(variables)
} }
internal fun translateSubroutine(sub: Subroutine) { internal fun translateAsmSubroutine(sub: PtAsmSub) {
var onlyVariables = false
if(sub.inline) { if(sub.inline) {
if(options.optimize) { if(options.optimize) {
if(sub.isAsmSubroutine || callGraph.unused(sub)) return
return
// from an inlined subroutine only the local variables are generated,
// all other code statements are omitted in the subroutine itself
// (they've been inlined at the call site, remember?)
onlyVariables = true
} }
} }
@ -266,7 +279,7 @@ internal class ProgramAndVarsGen(
val asmStartScope: String val asmStartScope: String
val asmEndScope: String val asmEndScope: String
if(sub.definingBlock.options().contains("force_output")) { if(sub.definingBlock()!!.forceOutput) {
asmStartScope = ".block" asmStartScope = ".block"
asmEndScope = ".bend" asmEndScope = ".bend"
} else { } else {
@ -274,105 +287,127 @@ internal class ProgramAndVarsGen(
asmEndScope = ".pend" asmEndScope = ".pend"
} }
if(sub.isAsmSubroutine) { if(sub.address!=null)
if(sub.asmAddress!=null) return // already done at the memvars section
return // already done at the memvars section
// asmsub with most likely just an inline asm in it // asmsub with most likely just an inline asm in it
asmgen.out("${sub.name}\t$asmStartScope") asmgen.out("${sub.name}\t$asmStartScope")
sub.statements.forEach { asmgen.translate(it) } sub.children.forEach { asmgen.translate(it) }
asmgen.out(" $asmEndScope\n") asmgen.out(" $asmEndScope")
}
internal fun translateSubroutine(sub: PtSub) {
asmgen.out("")
val asmStartScope: String
val asmEndScope: String
if(sub.definingBlock()!!.forceOutput) {
asmStartScope = ".block"
asmEndScope = ".bend"
} else { } else {
// regular subroutine asmStartScope = ".proc"
asmgen.out("${sub.name}\t$asmStartScope") asmEndScope = ".pend"
val scope = symboltable.lookupOrElse(sub.scopedName.joinToString(".")) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
val mvs = varsInSubroutine
.filter { it.value.type==StNodeType.MEMVAR }
.map { it.value as StMemVar }
val consts = varsInSubroutine
.filter { it.value.type==StNodeType.CONSTANT }
.map { it.value as StConstant }
memdefsAndConsts2asm(mvs, consts)
asmsubs2asm(sub.statements)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock.name=="main")
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
asmgen.out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
asmgen.assignRegister(RegisterOrPair.A, target1)
asmgen.assignRegister(RegisterOrPair.Y, target2)
}
}
if(!onlyVariables) {
asmgen.out("; statements")
sub.statements.forEach { asmgen.translate(it) }
}
asmgen.out("; variables")
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
DataType.UBYTE -> asmgen.out("$name .byte 0")
DataType.UWORD -> asmgen.out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(asmGenInfo.usedRegsaveA) // will probably never occur
asmgen.out("prog8_regsaveA .byte 0")
if(asmGenInfo.usedRegsaveX)
asmgen.out("prog8_regsaveX .byte 0")
if(asmGenInfo.usedRegsaveY)
asmgen.out("prog8_regsaveY .byte 0")
if(asmGenInfo.usedFloatEvalResultVar1)
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
asmgen.out(" $asmEndScope\n")
} }
asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
val mvs = varsInSubroutine
.filter { it.value.type==StNodeType.MEMVAR }
.map { it.value as StMemVar }
val consts = varsInSubroutine
.filter { it.value.type==StNodeType.CONSTANT }
.map { it.value as StConstant }
memdefsAndConsts2asm(mvs, consts)
asmsubs2asm(sub.children)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
asmgen.out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
asmgen.assignRegister(RegisterOrPair.A, target1)
asmgen.assignRegister(RegisterOrPair.Y, target2)
}
}
asmgen.out("; statements")
sub.children.forEach { asmgen.translate(it) }
asmgen.out("; variables")
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
DataType.UBYTE -> asmgen.out("$name .byte 0")
DataType.UWORD -> asmgen.out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(asmGenInfo.usedRegsaveA) // will probably never occur
asmgen.out("prog8_regsaveA .byte 0")
if(asmGenInfo.usedRegsaveX)
asmgen.out("prog8_regsaveX .byte 0")
if(asmGenInfo.usedRegsaveY)
asmgen.out("prog8_regsaveY .byte 0")
if(asmGenInfo.usedFloatEvalResultVar1)
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
asmgen.out(" $asmEndScope")
} }
private fun entrypointInitialization() { private fun entrypointInitialization() {
asmgen.out("; program startup initialization") asmgen.out("; program startup initialization")
asmgen.out(" cld") asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
if(!options.dontReinitGlobals) { // set full BSS area to zero
blockVariableInitializers.forEach { asmgen.out("""
if (it.value.isNotEmpty()) .if prog8_bss_section_size>0
asmgen.out(" jsr ${it.key.name}.prog8_init_vars") ; reset all variables in BSS section to zero
} lda #<prog8_bss_section_start
ldy #>prog8_bss_section_start
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #<prog8_bss_section_size
ldy #>prog8_bss_section_size
lda #0
jsr prog8_lib.memset
.endif""")
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
} }
// string and array variables in zeropage that have initializer value, should be initialized // string and array variables in zeropage that have initializer value, should be initialized
@ -420,9 +455,8 @@ internal class ProgramAndVarsGen(
arrayVariable2asm(varname, it.alloc.dt, it.value, null) arrayVariable2asm(varname, it.alloc.dt, it.value, null)
} }
asmgen.out("""+ tsx asmgen.out("""+
stx prog8_lib.orig_stackpointer ; required for sys.exit() ldx #127 ; init estack ptr (half page)
ldx #255 ; init estack ptr
clv clv
clc""") clc""")
} }
@ -443,7 +477,7 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpStringWithInitial>() val result = mutableListOf<ZpStringWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR } val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
for (variable in vars) { for (variable in vars) {
val scopedName = variable.key.joinToString(".") val scopedName = variable.key
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationStringValue!=null) if(svar.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!)) result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
@ -455,7 +489,7 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpArrayWithInitial>() val result = mutableListOf<ZpArrayWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes } val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
for (variable in vars) { for (variable in vars) {
val scopedName = variable.key.joinToString(".") val scopedName = variable.key
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationArrayValue!=null) if(svar.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!)) result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
@ -464,29 +498,60 @@ internal class ProgramAndVarsGen(
} }
private fun zeropagevars2asm(varNames: Set<String>) { private fun zeropagevars2asm(varNames: Set<String>) {
val namesLists = varNames.map { it.split('.') }.toSet() val zpVariables = allocator.zeropageVars.filter { it.key in varNames }.toList().sortedBy { it.second.address }
val zpVariables = allocator.zeropageVars.filter { it.key in namesLists }.toList().sortedBy { it.second.address }
for ((scopedName, zpvar) in zpVariables) { for ((scopedName, zpvar) in zpVariables) {
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit()) if (scopedName.startsWith("cx16.r"))
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}") asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
} }
} }
private fun nonZpVariables2asm(variables: List<StStaticVariable>) { private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
asmgen.out("") asmgen.out("")
asmgen.out("; non-zeropage variables") val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
val (stringvars, othervars) = variables.sortedBy { it.name }.partition { it.dt==DataType.STR } if(varsNoInit.isNotEmpty()) {
stringvars.forEach { asmgen.out("; non-zeropage variables without initialization value")
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first) asmgen.out(" .section BSS")
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
uninitializedVariable2asm(it)
}
asmgen.out(" .send BSS")
} }
othervars.sortedBy { it.type }.forEach {
staticVariable2asm(it) if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
stringvars.forEach {
outputStringvar(
it.name,
it.onetimeInitializationStringValue!!.second,
it.onetimeInitializationStringValue!!.first
)
}
othervars.sortedBy { it.type }.forEach {
staticVariable2asm(it)
}
}
}
private fun uninitializedVariable2asm(variable: StStaticVariable) {
when (variable.dt) {
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
DataType.BYTE -> asmgen.out("${variable.name}\t.char ?")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ?")
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
in ArrayDatatypes -> {
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
asmgen.out("${variable.name}\t.fill $numbytes")
}
else -> {
throw AssemblyError("weird dt")
}
} }
} }
private fun staticVariable2asm(variable: StStaticVariable) { private fun staticVariable2asm(variable: StStaticVariable) {
val name = variable.name
val initialValue: Number = val initialValue: Number =
if(variable.onetimeInitializationNumericValue!=null) { if(variable.onetimeInitializationNumericValue!=null) {
if(variable.dt== DataType.FLOAT) if(variable.dt== DataType.FLOAT)
@ -496,22 +561,22 @@ internal class ProgramAndVarsGen(
} else 0 } else 0
when (variable.dt) { when (variable.dt) {
DataType.UBYTE -> asmgen.out("$name\t.byte ${initialValue.toHex()}") DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("$name\t.char $initialValue") DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
DataType.UWORD -> asmgen.out("$name\t.word ${initialValue.toHex()}") DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
DataType.WORD -> asmgen.out("$name\t.sint $initialValue") DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
DataType.FLOAT -> { DataType.FLOAT -> {
if(initialValue==0) { if(initialValue==0) {
asmgen.out("$name\t.byte 0,0,0,0,0 ; float") asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
} else { } else {
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue) val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
asmgen.out("$name\t.byte $floatFill ; float $initialValue") asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
} }
} }
DataType.STR -> { DataType.STR -> {
throw AssemblyError("all string vars should have been interned into prog") throw AssemblyError("all string vars should have been interned into prog")
} }
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.onetimeInitializationArrayValue, variable.length) in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
else -> { else -> {
throw AssemblyError("weird dt") throw AssemblyError("weird dt")
} }
@ -593,12 +658,12 @@ internal class ProgramAndVarsGen(
} }
} }
private fun asmsubs2asm(statements: List<Statement>) { private fun asmsubs2asm(statements: List<PtNode>) {
statements statements
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null } .filter { it is PtAsmSub && it.address!=null }
.forEach { asmsub -> .forEach { asmsub ->
asmsub as Subroutine asmsub as PtAsmSub
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}") asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
} }
} }

View File

@ -16,13 +16,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
private val zeropage = options.compTarget.machine.zeropage private val zeropage = options.compTarget.machine.zeropage
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname) internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
init { init {
allocateZeropageVariables() allocateZeropageVariables()
zeropageVars = zeropage.allocatedVariables
} }
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropageVars internal fun isZpVar(scopedName: String) = scopedName in zeropageVars
internal fun getFloatAsmConst(number: Double): String { internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number] val asmName = globalFloatConsts[number]
@ -56,10 +57,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable -> varsRequiringZp.forEach { variable ->
val result = zeropage.allocate( val result = zeropage.allocate(
variable.scopedName.split('.'), variable.scopedName,
variable.dt, variable.dt,
variable.length, variable.length,
variable.position, variable.astNode.position,
errors errors
) )
result.fold( result.fold(
@ -67,7 +68,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
numVariablesAllocatedInZP++ numVariablesAllocatedInZP++
}, },
failure = { failure = {
errors.err(it.message!!, variable.position) errors.err(it.message!!, variable.astNode.position)
} }
) )
} }
@ -75,10 +76,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) { if(errors.noErrors()) {
varsPreferringZp.forEach { variable -> varsPreferringZp.forEach { variable ->
val result = zeropage.allocate( val result = zeropage.allocate(
variable.scopedName.split('.'), variable.scopedName,
variable.dt, variable.dt,
variable.length, variable.length,
variable.position, variable.astNode.position,
errors errors
) )
result.onSuccess { numVariablesAllocatedInZP++ } result.onSuccess { numVariablesAllocatedInZP++ }
@ -95,10 +96,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
break break
} else { } else {
val result = zeropage.allocate( val result = zeropage.allocate(
variable.scopedName.split('.'), variable.scopedName,
variable.dt, variable.dt,
variable.length, variable.length,
variable.position, variable.astNode.position,
errors errors
) )
result.onSuccess { numVariablesAllocatedInZP++ } result.onSuccess { numVariablesAllocatedInZP++ }

View File

@ -1,13 +1,9 @@
package prog8.codegen.cpu6502.assignment package prog8.codegen.cpu6502.assignment
import prog8.ast.Program import prog8.code.ast.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.code.core.* import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.returnsWhatWhere
internal enum class TargetStorageKind { internal enum class TargetStorageKind {
@ -29,25 +25,25 @@ internal enum class SourceStorageKind {
} }
internal class AsmAssignTarget(val kind: TargetStorageKind, internal class AsmAssignTarget(val kind: TargetStorageKind,
private val asmgen: AsmGen, private val asmgen: AsmGen6502Internal,
val datatype: DataType, val datatype: DataType,
val scope: Subroutine?, val scope: IPtSubroutine?,
private val variableAsmName: String? = null, private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null, val array: PtArrayIndexer? = null,
val memory: DirectMemoryWrite? = null, val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null, val register: RegisterOrPair? = null,
val origAstTarget: AssignTarget? = null val origAstTarget: PtAssignTarget? = null
) )
{ {
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() } val constArrayIndexValue by lazy { array?.index?.asConstInteger()?.toUInt() }
val asmVarname: String by lazy { val asmVarname: String by lazy {
if (array == null) if (array == null)
variableAsmName!! variableAsmName!!
else else
asmgen.asmVariableName(array.arrayvar) asmgen.asmVariableName(array.variable)
} }
lateinit var origAssign: AsmAssignment lateinit var origAssign: AsmAssignmentBase
init { init {
if(register!=null && datatype !in NumericDatatypes) if(register!=null && datatype !in NumericDatatypes)
@ -55,33 +51,31 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
} }
companion object { companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget { fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget {
with(assign.target) { with(target) {
val idt = inferType(program)
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
when { when {
identifier != null -> { identifier != null -> {
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter val parameter = asmgen.findSubroutineParameter(identifier!!.name, asmgen)
if (parameter!=null) { if (parameter!=null) {
val sub = parameter.definingSubroutine!! val sub = parameter.definingAsmSub()
if (sub.isAsmSubroutine) { if (sub!=null) {
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)] val reg = sub.parameters.single { it.second===parameter }.first
if(reg.statusflag!=null) if(reg.statusflag!=null)
throw AssemblyError("can't assign value to processor statusflag directly") throw AssemblyError("can't assign value to processor statusflag directly")
else else
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this) return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, type, definingSub, register=reg.registerOrPair, origAstTarget = this)
} }
} }
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this) return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, type, definingSub, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
} }
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this) array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, array = array, origAstTarget = this)
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this) memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, memory = memory, origAstTarget = this)
else -> throw AssemblyError("weird target") else -> throw AssemblyError("weird target")
} }
} }
} }
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget = fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget =
when(registers) { when(registers) {
RegisterOrPair.A, RegisterOrPair.A,
RegisterOrPair.X, RegisterOrPair.X,
@ -112,69 +106,66 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
} }
internal class AsmAssignSource(val kind: SourceStorageKind, internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program, private val program: PtProgram,
private val asmgen: AsmGen, private val asmgen: AsmGen6502Internal,
val datatype: DataType, val datatype: DataType,
private val variableAsmName: String? = null, private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null, val array: PtArrayIndexer? = null,
val memory: DirectMemoryRead? = null, val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null, val register: RegisterOrPair? = null,
val number: NumericLiteral? = null, val number: PtNumber? = null,
val expression: Expression? = null val expression: PtExpression? = null
) )
{ {
val asmVarname: String val asmVarname: String
get() = if(array==null) get() = if(array==null)
variableAsmName!! variableAsmName!!
else else
asmgen.asmVariableName(array.arrayvar) asmgen.asmVariableName(array.variable)
companion object { companion object {
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource { fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
val cv = value.constValue(program) val cv = value as? PtNumber
if(cv!=null) if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv) return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) { return when(value) {
is NumericLiteral -> throw AssemblyError("should have been constant value") // checked above: is PtNumber -> throw AssemblyError("should have been constant value")
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation") is PtString -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation") is PtArray -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> { is PtIdentifier -> {
val parameter = value.targetVarDecl(program)?.subroutineParameter val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine) if(parameter?.definingAsmSub() != null)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}") throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
val varName=asmgen.asmVariableName(value) val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system // special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) { if(value.type == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
val regStr = varName.lowercase().substring(5) val regStr = varName.lowercase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.uppercase()) val reg = RegisterOrPair.valueOf(regStr.uppercase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg) AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg)
} else { } else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName) AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, value.type, variableAsmName = varName)
} }
} }
is DirectMemoryRead -> { is PtMemoryByte -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value) AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
} }
is ArrayIndexedExpression -> { is PtArrayIndexer -> {
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") } AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
} }
is BuiltinFunctionCall -> { is PtBuiltinFunctionCall -> {
val returnType = value.inferType(program) AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
} }
is FunctionCallExpression -> { is PtFunctionCall -> {
val sub = value.target.targetSubroutine(program)!! val symbol = asmgen.symbolTable.lookup(value.name)
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first val sub = symbol!!.astNode as IPtSubroutine
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment") ?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value) AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
} }
else -> { else -> {
val returnType = value.inferType(program) AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
} }
} }
} }
@ -199,17 +190,27 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
} }
internal class AsmAssignment(val source: AsmAssignSource, internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
val target: AsmAssignTarget, val target: AsmAssignTarget,
val isAugmentable: Boolean, val memsizer: IMemSizer,
memsizer: IMemSizer, val position: Position) {
val position: Position) {
init { init {
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" } require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) { require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source dt size must be less or equal to target dt size at $position" "source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}"
} }
} }
} }
internal class AsmAssignment(source: AsmAssignSource,
target: AsmAssignTarget,
memsizer: IMemSizer,
position: Position): AsmAssignmentBase(source, target, memsizer, position)
internal class AsmAugmentedAssignment(source: AsmAssignSource,
val operator: String,
target: AsmAssignTarget,
memsizer: IMemSizer,
position: Position): AsmAssignmentBase(source, target, memsizer, position)

View File

@ -1,166 +1,71 @@
package prog8.codegen.cpu6502.assignment package prog8.codegen.cpu6502.assignment
import prog8.ast.Program import prog8.code.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.code.core.* import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator import prog8.codegen.cpu6502.VariableAllocator
internal class AugmentableAssignmentAsmGen(private val program: Program, internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private val assignmentAsmGen: AssignmentAsmGen, private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen, private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator private val allocator: VariableAllocator
) { ) {
fun translate(assign: AsmAssignment) { fun translate(assign: AsmAugmentedAssignment) {
require(assign.isAugmentable)
require(assign.source.kind == SourceStorageKind.EXPRESSION) {
"non-expression assign value should be handled elsewhere ${assign.position}"
}
when (val value = assign.source.expression!!) { when(assign.operator) {
is PrefixExpression -> { "-" -> {
// A = -A , A = +A, A = ~A, A = not A val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
when (value.operator) { assignmentAsmGen.inplaceNegate(a2, false)
"+" -> {} }
"-" -> inplaceNegate(assign, false) "~" -> {
"~" -> inplaceInvert(assign) val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
else -> throw AssemblyError("invalid prefix operator") assignmentAsmGen.inplaceInvert(a2)
} }
"+" -> { /* is a nop */ }
else -> {
if(assign.operator.endsWith('='))
augmentedAssignExpr(assign)
else
throw AssemblyError("invalid augmented assign operator ${assign.operator}")
} }
is TypecastExpression -> inplaceCast(assign.target, value, assign.position)
is BinaryExpression -> inplaceBinary(assign.target, value)
else -> throw AssemblyError("invalid aug assign value type")
} }
} }
private fun inplaceBinary(target: AsmAssignTarget, binExpr: BinaryExpression) { private fun augmentedAssignExpr(assign: AsmAugmentedAssignment) {
val astTarget = target.origAstTarget!! val srcValue = assign.source.toAstExpression(assign.target.scope as PtNamedNode)
if (binExpr.left isSameAs astTarget) { when (assign.operator) {
// A = A <operator> Something "+=" -> inplaceModification(assign.target, "+", srcValue)
return inplaceModification(target, binExpr.operator, binExpr.right) "-=" -> inplaceModification(assign.target, "-", srcValue)
"*=" -> inplaceModification(assign.target, "*", srcValue)
"/=" -> inplaceModification(assign.target, "/", srcValue)
"|=" -> inplaceModification(assign.target, "|", srcValue)
"&=" -> inplaceModification(assign.target, "&", srcValue)
"^=" -> inplaceModification(assign.target, "^", srcValue)
"<<=" -> inplaceModification(assign.target, "<<", srcValue)
">>=" -> inplaceModification(assign.target, ">>", srcValue)
else -> throw AssemblyError("invalid augmented assign operator ${assign.operator}")
} }
if (binExpr.operator in AssociativeOperators) {
if (binExpr.right isSameAs astTarget) {
// A = 5 <operator> A
return inplaceModification(target, binExpr.operator, binExpr.left)
}
val leftBinExpr = binExpr.left as? BinaryExpression
if (leftBinExpr?.operator == binExpr.operator) {
// TODO better optimize the chained asm to avoid intermediate stores/loads?
when {
binExpr.right isSameAs astTarget -> {
// A = (x <associative-operator> y) <same-operator> A
inplaceModification(target, binExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, leftBinExpr.right)
return
}
leftBinExpr.left isSameAs astTarget -> {
// A = (A <associative-operator> x) <same-operator> y
inplaceModification(target, binExpr.operator, leftBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
leftBinExpr.right isSameAs astTarget -> {
// A = (x <associative-operator> A) <same-operator> y
inplaceModification(target, binExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
}
}
val rightBinExpr = binExpr.right as? BinaryExpression
if (rightBinExpr?.operator == binExpr.operator) {
when {
binExpr.left isSameAs astTarget -> {
// A = A <associative-operator> (x <same-operator> y)
inplaceModification(target, binExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, rightBinExpr.right)
return
}
rightBinExpr.left isSameAs astTarget -> {
// A = y <associative-operator> (A <same-operator> x)
inplaceModification(target, binExpr.operator, binExpr.left)
inplaceModification(target, binExpr.operator, rightBinExpr.right)
return
}
rightBinExpr.right isSameAs astTarget -> {
// A = y <associative-operator> (x <same-operator> y)
inplaceModification(target, binExpr.operator, binExpr.left)
inplaceModification(target, binExpr.operator, rightBinExpr.left)
return
}
}
}
}
val leftBinExpr = binExpr.left as? BinaryExpression
val rightBinExpr = binExpr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs astTarget) {
// X = (X <oper> Right) <oper> Something
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
if(leftBinExpr.right isSameAs astTarget) {
// X = (Left <oper> X) <oper> Something
if(leftBinExpr.operator in AssociativeOperators) {
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs astTarget) {
// X = Something <oper> (X <oper> Right)
if(binExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
if(rightBinExpr.right isSameAs astTarget) {
// X = Something <oper> (Left <oper> X)
if(binExpr.operator in AssociativeOperators && rightBinExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
throw FatalAstException("assignment should follow augmentable rules $binExpr")
} }
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) { private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: PtExpression) {
// the asm-gen code can deal with situations where you want to assign a byte into a word. // the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us). // it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion is required // But we can't deal with writing a word into a byte - explicit typeconversion is required
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOrElse { throw AssemblyError("unknown dt") }) > program.memsizer.memorySize(target.datatype)) { val value = if(program.memsizer.memorySize(origValue.type) > program.memsizer.memorySize(target.datatype)) {
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position) val typecast = PtTypeCast(target.datatype, origValue.position)
typecast.linkParents(origValue.parent) typecast.add(origValue)
require(typecast.type!=origValue.type)
typecast typecast
} }
else { else {
origValue origValue
} }
val valueLv = (value as? NumericLiteral)?.number val valueLv = (value as? PtNumber)?.number
val ident = value as? IdentifierReference val ident = value as? PtIdentifier
val memread = value as? DirectMemoryRead val memread = value as? PtMemoryByte
when (target.kind) { when (target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -170,7 +75,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_byte_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt()) valueLv != null -> inplaceModification_byte_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident) ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread) memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> { value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value) inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
} }
@ -182,7 +87,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_word_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt()) valueLv != null -> inplaceModification_word_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident) ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread) memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> { value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator))
return return
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value) inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -194,7 +99,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when { when {
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!) valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!) ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
value is TypecastExpression -> { value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!) inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
} }
@ -206,27 +111,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
val memory = target.memory!! val memory = target.memory!!
when (memory.addressExpression) { when (memory.address) {
is NumericLiteral -> { is PtNumber -> {
val addr = (memory.addressExpression as NumericLiteral).number.toInt() val addr = (memory.address as PtNumber).number.toInt()
// re-use code to assign a variable, instead this time, use a direct memory address // re-use code to assign a variable, instead this time, use a direct memory address
when { when {
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt()) valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident) ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value) memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
value is TypecastExpression -> { value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value) inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
} }
else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value) else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
} }
} }
is IdentifierReference -> { is PtIdentifier -> {
val pointer = memory.addressExpression as IdentifierReference val pointer = memory.address as PtIdentifier
when { when {
valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt()) valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident) ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident)
value is TypecastExpression -> { value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_pointer(pointer, operator, value) inplaceModification_byte_value_to_pointer(pointer, operator, value)
} }
@ -235,13 +140,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
else -> { else -> {
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from // TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingSubroutine)) asmgen.assignExpressionTo(memory.address, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingISub()))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
when { when {
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt()) valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident) ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread) memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread)
value is TypecastExpression -> { value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value) inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
} }
@ -252,91 +157,89 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
with(target.array!!.indexer) { val indexNum = target.array!!.index as? PtNumber
val indexNum = indexExpr as? NumericLiteral val indexVar = target.array.index as? PtIdentifier
val indexVar = indexExpr as? IdentifierReference when {
when { indexNum!=null -> {
indexNum!=null -> { val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}" when (target.datatype) {
when (target.datatype) { in ByteDatatypes -> {
in ByteDatatypes -> { when {
when { valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt()) ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident) memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread) value is PtTypeCast -> {
value is TypecastExpression -> { if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
} }
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
} }
in WordDatatypes -> {
when {
valueLv != null -> inplaceModification_word_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
}
DataType.FLOAT -> {
when {
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
value is TypecastExpression -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
} }
} in WordDatatypes -> {
indexVar!=null -> { when {
when (target.datatype) { valueLv != null -> inplaceModification_word_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
in ByteDatatypes -> { ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
val tgt = memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
AsmAssignTarget.fromRegisters( value is PtTypeCast -> {
RegisterOrPair.A, if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
target.datatype == DataType.BYTE, null, inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
asmgen }
) else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
} }
in WordDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
} }
DataType.FLOAT -> {
when {
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
} }
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
} }
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.A,
target.datatype == DataType.BYTE, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
}
in WordDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
} }
} }
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification") TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
@ -344,21 +247,20 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun tryInplaceModifyWithRemovedRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean { private fun tryInplaceModifyWithRemovedRedundantCast(value: PtTypeCast, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) { if (target.datatype == value.type) {
val childIDt = value.expression.inferType(program) val childDt = value.value.type
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) { if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value. // this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
// (works for integer types, not for float.) // (works for integer types, not for float.)
inplaceModification(target, operator, value.expression) inplaceModification(target, operator, value.value)
return true return true
} }
} }
return false return false
} }
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) { private fun inplaceModification_byte_value_to_pointer(pointervar: PtIdentifier, operator: String, value: PtExpression) {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar) val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) { when (operator) {
@ -394,7 +296,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.storeAIntoZpPointerVar(sourceName) asmgen.storeAIntoZpPointerVar(sourceName)
} }
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) { private fun inplaceModification_byte_variable_to_pointer(pointervar: PtIdentifier, operator: String, value: PtIdentifier) {
val otherName = asmgen.asmVariableName(value) val otherName = asmgen.asmVariableName(value)
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar) val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
@ -431,7 +333,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.storeAIntoZpPointerVar(sourceName) asmgen.storeAIntoZpPointerVar(sourceName)
} }
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) { private fun inplaceModification_byte_litval_to_pointer(pointervar: PtIdentifier, operator: String, value: Int) {
when (operator) { when (operator) {
// note: ** (power) operator requires floats. // note: ** (power) operator requires floats.
"+" -> { "+" -> {
@ -499,7 +401,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) { private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
// this should be the last resort for code generation for this, // this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow). // because the value is evaluated onto the eval stack (=slow).
when (operator) { when (operator) {
@ -594,7 +496,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_byte_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) { private fun inplaceModification_byte_variable_to_variable(name: String, dt: DataType, operator: String, ident: PtIdentifier) {
val otherName = asmgen.asmVariableName(ident) val otherName = asmgen.asmVariableName(ident)
when (operator) { when (operator) {
// note: ** (power) operator requires floats. // note: ** (power) operator requires floats.
@ -762,7 +664,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) { private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
when (operator) { when (operator) {
"+" -> { "+" -> {
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false) asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
@ -799,7 +701,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) { private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
when (operator) { when (operator) {
"+" -> { "+" -> {
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false) asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
@ -1111,9 +1013,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) { private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: PtIdentifier) {
val otherName = asmgen.asmVariableName(ident) val otherName = asmgen.asmVariableName(ident)
when (val valueDt = ident.targetVarDecl(program)!!.datatype) { when (val valueDt = ident.type) {
in ByteDatatypes -> { in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that // the other variable is a BYTE type so optimize for that
when (operator) { when (operator) {
@ -1356,12 +1258,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) { private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
// this should be the last resort for code generation for this, // this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow). // because the value is evaluated onto the eval stack (=slow).
val valueiDt = value.inferType(program)
val valueDt = valueiDt.getOrElse { throw AssemblyError("unknown dt") }
fun multiplyVarByWordInAY() { fun multiplyVarByWordInAY() {
asmgen.out(""" asmgen.out("""
@ -1410,7 +1310,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""") """)
} }
when (valueDt) { when (val valueDt = value.type) {
in ByteDatatypes -> { in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that // the other variable is a BYTE type so optimize for that
when (operator) { when (operator) {
@ -1581,7 +1481,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine) { private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: PtExpression, scope: IPtSubroutine) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1) asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1)
asmgen.saveRegisterLocal(CpuRegister.X, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) { when (operator) {
@ -1623,8 +1523,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
} }
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) { private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: PtIdentifier, scope: IPtSubroutine) {
val valueDt = ident.targetVarDecl(program)!!.datatype val valueDt = ident.type
if(valueDt != DataType.FLOAT) if(valueDt != DataType.FLOAT)
throw AssemblyError("float variable expected") throw AssemblyError("float variable expected")
@ -1682,7 +1582,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
} }
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) { private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: IPtSubroutine) {
val constValueName = allocator.getFloatAsmConst(value) val constValueName = allocator.getFloatAsmConst(value)
asmgen.saveRegisterLocal(CpuRegister.X, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) { when (operator) {
@ -1743,259 +1643,50 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""") """)
asmgen.restoreRegisterLocal(CpuRegister.X) asmgen.restoreRegisterLocal(CpuRegister.X)
} }
}
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
val outerCastDt = cast.type private fun AsmAssignSource.toAstExpression(scope: PtNamedNode): PtExpression {
val innerCastDt = (cast.expression as? TypecastExpression)?.type return when(kind) {
if (innerCastDt == null) { SourceStorageKind.LITERALNUMBER -> this.number!!
// simple typecast where the value is the target SourceStorageKind.VARIABLE -> {
when (target.datatype) { val ident = PtIdentifier(scope.scopedName + '.' + asmVarname, datatype, Position.DUMMY)
DataType.UBYTE, DataType.BYTE -> { /* byte target can't be typecasted to anything else at all */ } ident.parent = scope
DataType.UWORD, DataType.WORD -> { ident
when (outerCastDt) { }
DataType.UBYTE, DataType.BYTE -> { SourceStorageKind.ARRAY -> this.array!!
when (target.kind) { SourceStorageKind.MEMORY -> this.memory!!
TargetStorageKind.VARIABLE -> { SourceStorageKind.EXPRESSION -> this.expression!!
if(asmgen.isTargetCpu(CpuType.CPU65c02)) SourceStorageKind.REGISTER -> {
asmgen.out(" stz ${target.asmVarname}+1") if(register in Cx16VirtualRegisters) {
else val ident = PtIdentifier("cx16.${register!!.name.lowercase()}", DataType.UWORD, position = scope.position)
asmgen.out(" lda #0 | sta ${target.asmVarname}+1") ident.parent = scope
} ident
TargetStorageKind.ARRAY -> { } else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y, true) throw AssemblyError("no ast expr possible for source register $register")
asmgen.out(" lda #0 | sta ${target.asmVarname},y") }
} }
TargetStorageKind.STACK -> { else -> throw AssemblyError("invalid assign source kind $kind")
if(asmgen.isTargetCpu(CpuType.CPU65c02)) }
asmgen.out(" stz P8ESTACK_HI+1,x") }
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x") private fun AsmAssignTarget.toAstExpression(): PtExpression {
} return when(kind) {
else -> throw AssemblyError("weird target") TargetStorageKind.VARIABLE -> {
} val ident = PtIdentifier((this.scope as PtNamedNode).scopedName + '.' + asmVarname, datatype, origAstTarget?.position ?: Position.DUMMY)
} ident.parent = this.scope
DataType.UWORD, DataType.WORD, in IterableDatatypes -> {} ident
DataType.FLOAT -> throw AssemblyError("can't cast float in-place") }
else -> throw AssemblyError("weird cast type") TargetStorageKind.ARRAY -> this.array!!
} TargetStorageKind.MEMORY -> this.memory!!
} TargetStorageKind.REGISTER -> {
DataType.FLOAT -> { if(register in Cx16VirtualRegisters) {
if (outerCastDt != DataType.FLOAT) val ident = PtIdentifier("cx16.${register!!.name.lowercase()}", DataType.UWORD, position = this.origAssign.position)
throw AssemblyError("in-place cast of a float makes no sense") ident.parent = (this.scope as? PtNamedNode) ?: this.origAstTarget!!
} ident
else -> throw AssemblyError("invalid cast target type") } else {
} throw AssemblyError("no ast expr possible for target register $register")
} else { }
// typecast with nested typecast, that has the target as a value }
// calculate singular cast that is required else -> throw AssemblyError("invalid assign target kind $kind")
val castDt = if (outerCastDt largerThan innerCastDt) innerCastDt else outerCastDt }
val value = (cast.expression as TypecastExpression).expression
val resultingCast = TypecastExpression(value, castDt, false, position)
inplaceCast(target, resultingCast, position)
}
}
internal fun inplaceInvert(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
eor #255
sta ${target.asmVarname}""")
}
TargetStorageKind.MEMORY -> {
val memory = target.memory!!
when (memory.addressExpression) {
is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteral).number.toHex()
asmgen.out("""
lda $addr
eor #255
sta $addr""")
}
is IdentifierReference -> {
val sourceName = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
asmgen.out(" eor #255")
asmgen.out(" sta ($sourceName),y")
}
else -> {
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
eor #255""")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" eor #255")
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
else -> throw AssemblyError("invalid reg dt for byte invert")
}
}
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.UWORD -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
eor #255
sta ${target.asmVarname}
lda ${target.asmVarname}+1
eor #255
sta ${target.asmVarname}+1""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
val target = assign.target
val datatype = if(ignoreDatatype) {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
DataType.UWORD, DataType.WORD -> DataType.WORD
else -> target.datatype
}
} else target.datatype
when (datatype) {
DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" eor #255 | ina")
else
asmgen.out(" eor #255 | clc | adc #1")
}
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.WORD -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}
lda #0
sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) { //P8ZP_SCRATCH_REG
RegisterOrPair.AX -> {
asmgen.out("""
sec
eor #255
adc #0
pha
txa
eor #255
adc #0
tax
pla""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sec
eor #255
adc #0
pha
tya
eor #255
adc #0
tay
pla""")
}
RegisterOrPair.XY -> {
asmgen.out("""
sec
txa
eor #255
adc #0
tax
tya
eor #255
adc #0
tay""")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
TargetStorageKind.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("""
lda ${target.asmVarname}+1
eor #$80
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target for in-place float negation")
}
}
else -> throw AssemblyError("negate of invalid type")
}
}
} }

View File

@ -0,0 +1,60 @@
package prog8tests.codegencpu6502
import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = when(dt) {
in ByteDatatypes -> 1
DataType.FLOAT -> 5
else -> 2
}
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
DataType.ARRAY_UW -> numElements*2
DataType.ARRAY_W -> numElements*2
DataType.ARRAY_F -> numElements*5
else -> numElements
}
}
internal object DummyStringEncoder : IStringEncoding {
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
}
}

View File

@ -0,0 +1,106 @@
package prog8tests.codegencpu6502
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.codegen.cpu6502.AsmGen6502
import java.nio.file.Files
import kotlin.io.path.Path
class TestCodegen: FunSpec({
fun getTestOptions(): CompilationOptions {
val target = C64Target()
return CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
floats = true,
noSysInit = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
}
test("augmented assign on arrays") {
//main {
// sub start() {
// ubyte[] particleX = [1,2,3]
// ubyte[] particleDX = [1,2,3]
// particleX[2] += particleDX[2]
//
// word @shared xx = 1
// xx = -xx
// xx += 42
// xx += cx16.r0
// }
//}
val codegen = AsmGen6502()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
assign.add(target)
assign.add(value)
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
sub.add(cxregAssign)
block.add(sub)
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
val options = getTestOptions()
val st = SymbolTableMaker(program, options).make()
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors)!!
result.name shouldBe "test"
Files.deleteIfExists(Path("${result.name}.asm"))
}
})

View File

@ -3,19 +3,19 @@ package prog8.codegen.experimental
import prog8.code.SymbolTable import prog8.code.SymbolTable
import prog8.code.ast.PtProgram import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter import prog8.intermediate.IRFileWriter
class CodeGen(private val program: PtProgram, class ExperiCodeGen: ICodeGeneratorBackend {
private val symbolTable: SymbolTable, override fun generate(
private val options: CompilationOptions, program: PtProgram,
private val errors: IErrorReporter symbolTable: SymbolTable,
): IAssemblyGenerator { options: CompilationOptions,
override fun compileToAssembly(): IAssemblyProgram? { errors: IErrorReporter
): IAssemblyProgram? {
// you could write a code generator directly on the PtProgram AST, // you could write a code generator directly on the PtProgram AST,
// but you can also use the Intermediate Representation to build a codegen on: // but you can also use the Intermediate Representation to build a codegen on:
val irCodeGen = IRCodeGen(program, symbolTable, options, errors) val irCodeGen = IRCodeGen(program, symbolTable, options, errors)

View File

@ -3,6 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.AssemblyError import prog8.code.core.AssemblyError
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.PrefixOperators
import prog8.code.core.SignedDatatypes import prog8.code.core.SignedDatatypes
import prog8.intermediate.* import prog8.intermediate.*
@ -12,22 +13,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(assignment.target.children.single() is PtMachineRegister) if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return if (assignment.isInplaceAssign) return translateRegularAssign(assignment)
translateInplaceAssign(assignment)
else
translateRegularAssign(assignment)
} }
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks { internal fun translate(augmentedAssign: PtAugmentedAssign): IRCodeChunks {
if(augmentedAssign.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return translateInplaceAssign(augmentedAssign)
}
private fun translateInplaceAssign(assignment: PtAugmentedAssign): IRCodeChunks {
val ident = assignment.target.identifier val ident = assignment.target.identifier
val memory = assignment.target.memory val memory = assignment.target.memory
val array = assignment.target.array val array = assignment.target.array
return if(ident!=null) { return if(ident!=null) {
assignSelfInMemory(ident.name, assignment.value, assignment) assignVarAugmented(ident.name, assignment)
} else if(memory != null) { } else if(memory != null) {
if(memory.address is PtNumber) if(memory.address is PtNumber)
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment) assignMemoryAugmented((memory.address as PtNumber).number.toInt(), assignment)
else else
fallbackAssign(assignment) fallbackAssign(assignment)
} else if(array!=null) { } else if(array!=null) {
@ -40,120 +45,83 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} }
} }
private fun assignSelfInMemoryKnownAddress( private fun assignMemoryAugmented(
address: Int, address: Int,
value: PtExpression, assignment: PtAugmentedAssign
origAssign: PtAssignment
): IRCodeChunks { ): IRCodeChunks {
val value = assignment.value
val vmDt = codeGen.irType(value.type) val vmDt = codeGen.irType(value.type)
when(value) { return when(assignment.operator) {
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment. "+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment "-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null) "*" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign) "/" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
is PtMemoryByte -> { "|" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt())) "&" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
emptyList() // do nothing, mem=mem null assignment. "^" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
else { "<<" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
// read and write a (i/o) memory location to itself. ">>" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
val tempReg = codeGen.registers.nextFree() in PrefixOperators -> inplacePrefix(assignment.operator, vmDt, address, null)
val code = IRCodeChunk(null, null) else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
listOf(code)
}
}
else -> return fallbackAssign(origAssign)
} }
} }
private fun assignSelfInMemory( private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
symbol: String, val value = assignment.value
value: PtExpression, val valueVmDt = codeGen.irType(value.type)
origAssign: PtAssignment return when (assignment.operator) {
): IRCodeChunks { "+=" -> expressionEval.operatorPlusInplace(null, symbol, valueVmDt, value)
val vmDt = codeGen.irType(value.type) "-=" -> expressionEval.operatorMinusInplace(null, symbol, valueVmDt, value)
return when(value) { "*=" -> expressionEval.operatorMultiplyInplace(null, symbol, valueVmDt, value)
is PtIdentifier -> emptyList() // do nothing, x=x null assignment. "/=" -> expressionEval.operatorDivideInplace(null, symbol, valueVmDt, value.type in SignedDatatypes, value)
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment "|=" -> expressionEval.operatorOrInplace(null, symbol, valueVmDt, value)
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol) "&=" -> expressionEval.operatorAndInplace(null, symbol, valueVmDt, value)
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign) "^=" -> expressionEval.operatorXorInplace(null, symbol, valueVmDt, value)
is PtMemoryByte -> { "<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, valueVmDt, value)
val code = IRCodeChunk(null, null) ">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, valueVmDt, value.type in SignedDatatypes, value)
val tempReg = codeGen.registers.nextFree() in PrefixOperators -> inplacePrefix(assignment.operator, valueVmDt, null, symbol)
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol) else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
listOf(code)
}
else -> fallbackAssign(origAssign)
} }
} }
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks { private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
if (codeGen.options.slowCodegenWarnings) if (codeGen.options.slowCodegenWarnings)
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position) codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
return translateRegularAssign(origAssign) val normalAssign = PtAssignment(origAssign.position)
} normalAssign.add(origAssign.target)
val value: PtExpression
private fun inplaceBinexpr( if(origAssign.operator in PrefixOperators) {
operator: String, value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
operand: PtExpression, value.add(origAssign.value)
vmDt: IRDataType,
signed: Boolean,
knownAddress: Int?,
symbol: String?,
origAssign: PtAssignment
): IRCodeChunks {
if(knownAddress!=null) {
when (operator) {
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
"-" -> return expressionEval.operatorMinusInplace(knownAddress, null, vmDt, operand)
"*" -> return expressionEval.operatorMultiplyInplace(knownAddress, null, vmDt, operand)
"/" -> return expressionEval.operatorDivideInplace(knownAddress, null, vmDt, signed, operand)
"|" -> return expressionEval.operatorOrInplace(knownAddress, null, vmDt, operand)
"&" -> return expressionEval.operatorAndInplace(knownAddress, null, vmDt, operand)
"^" -> return expressionEval.operatorXorInplace(knownAddress, null, vmDt, operand)
"<<" -> return expressionEval.operatorShiftLeftInplace(knownAddress, null, vmDt, operand)
">>" -> return expressionEval.operatorShiftRightInplace(knownAddress, null, vmDt, signed, operand)
else -> {}
}
} else { } else {
symbol!! require(origAssign.operator.endsWith('='))
when (operator) { value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
"+" -> return expressionEval.operatorPlusInplace(null, symbol, vmDt, operand) val left: PtExpression = origAssign.target.children.single() as PtExpression
"-" -> return expressionEval.operatorMinusInplace(null, symbol, vmDt, operand) value.add(left)
"*" -> return expressionEval.operatorMultiplyInplace(null, symbol, vmDt, operand) value.add(origAssign.value)
"/" -> return expressionEval.operatorDivideInplace(null, symbol, vmDt, signed, operand)
"|" -> return expressionEval.operatorOrInplace(null, symbol, vmDt, operand)
"&" -> return expressionEval.operatorAndInplace(null, symbol, vmDt, operand)
"^" -> return expressionEval.operatorXorInplace(null, symbol, vmDt, operand)
"<<" -> return expressionEval.operatorShiftLeftInplace(null, symbol, vmDt, operand)
">>" -> return expressionEval.operatorShiftRightInplace(null, symbol, vmDt, signed, operand)
else -> {}
}
} }
return fallbackAssign(origAssign) normalAssign.add(value)
return translateRegularAssign(normalAssign)
} }
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks { private fun inplacePrefix(operator: String, vmDt: IRDataType, address: Int?, symbol: String?): IRCodeChunks {
val code= IRCodeChunk(null, null) val code= IRCodeChunk(null, null)
when(operator) { when(operator) {
"+" -> { } "+" -> { }
"-" -> { "-" -> {
code += if(knownAddress!=null) code += if(address!=null)
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress) IRInstruction(Opcode.NEGM, vmDt, value = address)
else else
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol) IRInstruction(Opcode.NEGM, vmDt, labelSymbol = symbol)
} }
"~" -> { "~" -> {
val regMask = codeGen.registers.nextFree() val regMask = codeGen.registers.nextFree()
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask) code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
code += if(knownAddress!=null) code += if(address!=null)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress) IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
else else
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol) IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = symbol)
} }
else -> throw AssemblyError("weird prefix operator") else -> throw AssemblyError("weird prefix operator")
} }

View File

@ -26,7 +26,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"rrestore", "rrestore",
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore "rrestorex" -> emptyList() // vm doesn't have registers to save/restore
"callfar" -> throw AssemblyError("callfar() is for cx16 target only") "callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"msb" -> funcMsb(call, resultRegister) "msb" -> funcMsb(call, resultRegister)
"lsb" -> funcLsb(call, resultRegister) "lsb" -> funcLsb(call, resultRegister)
"memory" -> funcMemory(call, resultRegister) "memory" -> funcMemory(call, resultRegister)

View File

@ -988,20 +988,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(fcall.type==DataType.FLOAT) if(fcall.type==DataType.FLOAT)
throw AssemblyError("doesn't support float register result in asm romsub") throw AssemblyError("doesn't support float register result in asm romsub")
val returns = callTarget.returns.single() val returns = callTarget.returns.single()
val regStr = if(returns.registerOrPair!=null) returns.registerOrPair.toString() else returns.statusflag.toString() val regStr = if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null) addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
} }
else -> { else -> {
val returnRegister = callTarget.returns.singleOrNull{ it.registerOrPair!=null } val returnRegister = callTarget.returns.singleOrNull{ it.register.registerOrPair!=null }
if(returnRegister!=null) { if(returnRegister!=null) {
// we skip the other values returned in the status flags. // we skip the other values returned in the status flags.
val regStr = returnRegister.registerOrPair.toString() val regStr = returnRegister.register.registerOrPair.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null) addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
} else { } else {
val firstReturnRegister = callTarget.returns.firstOrNull{ it.registerOrPair!=null } val firstReturnRegister = callTarget.returns.firstOrNull{ it.register.registerOrPair!=null }
if(firstReturnRegister!=null) { if(firstReturnRegister!=null) {
// we just take the first register return value and ignore the rest. // we just take the first register return value and ignore the rest.
val regStr = firstReturnRegister.registerOrPair.toString() val regStr = firstReturnRegister.register.registerOrPair.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null) addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
} else { } else {
throw AssemblyError("invalid number of return values from call") throw AssemblyError("invalid number of return values from call")

View File

@ -33,15 +33,13 @@ class IRCodeGen(
if(options.evalStackBaseAddress!=null) if(options.evalStackBaseAddress!=null)
throw AssemblyError("IR doesn't use eval-stack") throw AssemblyError("IR doesn't use eval-stack")
if(!options.dontReinitGlobals) { // collect global variables initializers
// collect global variables initializers program.allBlocks().forEach {
program.allBlocks().forEach { val result = mutableListOf<IRCodeChunkBase>()
val result = mutableListOf<IRCodeChunkBase>() it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) }
it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) } result.forEach { chunk ->
result.forEach { chunk -> if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk)
if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk) else throw AssemblyError("only expect code chunk for global inits")
else throw AssemblyError("only expect code chunk for global inits")
}
} }
} }
@ -238,6 +236,7 @@ class IRCodeGen(
is PtMemMapped -> emptyList() // memmapped var should be looked up via symbol table is PtMemMapped -> emptyList() // memmapped var should be looked up via symbol table
is PtConstant -> emptyList() // constants have all been folded into the code is PtConstant -> emptyList() // constants have all been folded into the code
is PtAssignment -> assignmentGen.translate(node) is PtAssignment -> assignmentGen.translate(node)
is PtAugmentedAssign -> assignmentGen.translate(node)
is PtNodeGroup -> translateGroup(node.children) is PtNodeGroup -> translateGroup(node.children)
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0) is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
is PtFunctionCall -> expressionEval.translate(node, 0, 0) is PtFunctionCall -> expressionEval.translate(node, 0, 0)
@ -1178,7 +1177,7 @@ class IRCodeGen(
for (child in block.children) { for (child in block.children) {
when(child) { when(child) {
is PtNop -> { /* nothing */ } is PtNop -> { /* nothing */ }
is PtAssignment -> { /* global variable initialization is done elsewhere */ } is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ } is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtSub -> { is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position) val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
@ -1203,8 +1202,8 @@ class IRCodeGen(
child.name, child.name,
child.address, child.address,
child.clobbers, child.clobbers,
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore. child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) }, child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
asmChunk, asmChunk,
child.position child.position
) )

View File

@ -3,29 +3,28 @@ package prog8.codegen.vm
import prog8.code.SymbolTable import prog8.code.SymbolTable
import prog8.code.ast.PtProgram import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram import prog8.intermediate.IRProgram
class VmCodeGen(private val program: PtProgram, class VmCodeGen: ICodeGeneratorBackend {
private val symbolTable: SymbolTable, override fun generate(
private val options: CompilationOptions, program: PtProgram,
private val errors: IErrorReporter symbolTable: SymbolTable,
): IAssemblyGenerator { options: CompilationOptions,
override fun compileToAssembly(): IAssemblyProgram? { errors: IErrorReporter
): IAssemblyProgram? {
val irCodeGen = IRCodeGen(program, symbolTable, options, errors) val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate() val irProgram = irCodeGen.generate()
return VmAssemblyProgram(irProgram.name, irProgram) return VmAssemblyProgram(irProgram.name, irProgram)
} }
} }
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram { internal class VmAssemblyProgram(override val name: String, internal val irProgram: IRProgram): IAssemblyProgram {
override fun assemble(options: CompilationOptions): Boolean { override fun assemble(options: CompilationOptions): Boolean {
// the VM reads the IR file from disk. // the VM reads the IR file from disk.

View File

@ -1,12 +1,18 @@
import prog8.code.core.DataType import prog8.code.core.*
import prog8.code.core.Encoding
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
internal object DummyMemsizer : IMemSizer { internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = 0 override fun memorySize(dt: DataType) = when(dt) {
override fun memorySize(arrayDt: DataType, numElements: Int) = 0 in ByteDatatypes -> 1
DataType.FLOAT -> 5
else -> 2
}
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
DataType.ARRAY_UW -> numElements*2
DataType.ARRAY_W -> numElements*2
DataType.ARRAY_F -> numElements*5
else -> numElements
}
} }
internal object DummyStringEncoder : IStringEncoding { internal object DummyStringEncoder : IStringEncoding {
@ -14,7 +20,39 @@ internal object DummyStringEncoder : IStringEncoding {
return emptyList() return emptyList()
} }
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String { override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return "" return ""
} }
} }
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
}
}

View File

@ -0,0 +1,103 @@
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.codegen.vm.VmAssemblyProgram
import prog8.codegen.vm.VmCodeGen
import prog8.intermediate.IRSubroutine
class TestVmCodeGen: FunSpec({
fun getTestOptions(): CompilationOptions {
val target = VMTarget()
return CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
floats = true,
noSysInit = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
}
test("augmented assigns") {
//main {
// sub start() {
// ubyte[] particleX = [1,2,3]
// ubyte[] particleDX = [1,2,3]
// particleX[2] += particleDX[2]
//
// word @shared xx = 1
// xx = -xx
// xx += 42
// xx += cx16.r0
// }
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
assign.add(target)
assign.add(value)
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
sub.add(cxregAssign)
block.add(sub)
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
val options = getTestOptions()
val st = SymbolTableMaker(program, options).make()
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBeGreaterThan 4
}
})

View File

@ -4,9 +4,6 @@ import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.getTempVar
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin import prog8.ast.statements.AssignmentOrigin
@ -31,23 +28,6 @@ class BinExprSplitter(private val program: Program, private val options: Compila
val binExpr = assignment.value as? BinaryExpression val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) { if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) { if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.right) if(assignment.target isSameAs binExpr.right)
return noModifications return noModifications
@ -60,20 +40,6 @@ X = BinExpr X = LeftExpr
val rightBx = binExpr.right as? BinaryExpression val rightBx = binExpr.right as? BinaryExpression
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple)) if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
return noModifications return noModifications
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
// val dt = assignment.target.inferType(program)
// if(!dt.isInteger)
// return noModifications
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
// val assignTempVar = Assignment(
// AssignTarget(tempVar, null, null, binExpr.right.position),
// binExpr.right, binExpr.right.position
// )
// return listOf(
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
// )
} }
if(binExpr.right.isSimple) { if(binExpr.right.isSimple) {
@ -87,30 +53,10 @@ X = BinExpr X = LeftExpr
} }
} }
// TODO further unraveling of binary expression trees into flat statements. // Further unraveling of binary expressions is really complicated here and
// however this should probably be done in a more generic way to also work on // often results in much bigger code, thereby defeating the purpose a bit.
// the expressiontrees that are not used in an assignment statement... // All in all this should probably be fixed in a better code generation backend
} // that doesn't require this at all.
val typecast = assignment.value as? TypecastExpression
if(typecast!=null) {
val origExpr = typecast.expression as? BinaryExpression
if(origExpr!=null && options.compTarget.name!=VMTarget.NAME) {
// it's a typecast of a binary expression.
// we can see if we can unwrap the binary expression by working on a new temporary variable
// (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence.
val tempvarDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
val (tempVarName, _) = program.getTempVar(tempvarDt)
val assignTempVar = Assignment(
AssignTarget(IdentifierReference(tempVarName, typecast.position), null, null, typecast.position),
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
)
return listOf(
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVarName, typecast.position), typecast)
)
}
} }
return noModifications return noModifications

View File

@ -354,9 +354,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
if(decl.type== VarDeclType.CONST && numval!=null) { if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program) val valueDt = numval.inferType(program)
if(valueDt isnot decl.datatype) { if(valueDt isnot decl.datatype) {
val cast = numval.cast(decl.datatype) if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
if(cast.isValid) val cast = numval.cast(decl.datatype)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl)) if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
} }
} }
return noModifications return noModifications
@ -386,9 +388,9 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
subleftIsConst: Boolean, subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification? subrightIsConst: Boolean): IAstModification?
{ {
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS // NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add more
// TODO: this implements only a small set of possible reorderings at this time, we could think of more
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
// both operators are the same. // both operators are the same.

View File

@ -18,7 +18,7 @@ import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html // TODO add more peephole expression optimizations? Investigate what optimizations binaryen has
class ExpressionSimplifier(private val program: Program, class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter, private val errors: IErrorReporter,
@ -187,12 +187,37 @@ class ExpressionSimplifier(private val program: Program,
} }
} }
// X <= Y-1 ---> X<Y , X >= Y+1 ---> X>Y
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightExpr = expr.right as? BinaryExpression
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
if (expr.operator == "<=" && rightExpr.operator == "-") {
expr.operator = "<"
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
} else if (expr.operator == ">=" && rightExpr.operator == "+") {
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
}
}
}
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) { if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0 // for integers: x >= 1 --> x > 0
expr.operator = ">" expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr)) return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
} }
// for signed integers: X <= -1 => X<0 , X > -1 => X>=0
if(leftDt in SignedDatatypes && leftDt!=DataType.FLOAT && rightVal?.number==-1.0) {
if(expr.operator=="<=") {
expr.operator = "<"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
} else if(expr.operator==">") {
expr.operator = ">="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
}
}
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) { if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if(expr.operator == ">=" && rightVal?.number == 0.0) { if(expr.operator == ">=" && rightVal?.number == 0.0) {
// unsigned >= 0 --> true // unsigned >= 0 --> true

View File

@ -118,9 +118,11 @@ class Inliner(val program: Program): AstWalker() {
} }
private fun makeFullyScoped(identifier: IdentifierReference) { private fun makeFullyScoped(identifier: IdentifierReference) {
val scoped = (identifier.targetStatement(program)!! as INamedStatement).scopedName identifier.targetStatement(program)?.let { target ->
val scopedIdent = IdentifierReference(scoped, identifier.position) val scoped = (target as INamedStatement).scopedName
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent) val scopedIdent = IdentifierReference(scoped, identifier.position)
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
}
} }
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) { private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
@ -130,27 +132,30 @@ class Inliner(val program: Program): AstWalker() {
} }
private fun makeFullyScoped(call: FunctionCallStatement) { private fun makeFullyScoped(call: FunctionCallStatement) {
val sub = call.target.targetSubroutine(program)!! call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position) val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args) val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position) val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent) modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
} }
private fun makeFullyScoped(call: BuiltinFunctionCall) { private fun makeFullyScoped(call: BuiltinFunctionCall) {
val sub = call.target.targetSubroutine(program)!! call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position) val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args) val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position) val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent) modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
} }
private fun makeFullyScoped(call: FunctionCallExpression) { private fun makeFullyScoped(call: FunctionCallExpression) {
val sub = call.target.targetSubroutine(program)!! call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position) val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args) val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position) val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent) modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
} }
private fun makeScopedArgs(args: List<Expression>): List<Expression> { private fun makeScopedArgs(args: List<Expression>): List<Expression> {

View File

@ -120,17 +120,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
}} }}
} }
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY { asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y ; ---- fac1 to unsigned word in A/Y
%asm {{ %asm {{

View File

@ -28,10 +28,6 @@ romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2 romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order) ; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
@ -147,17 +143,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
}} }}
} }
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY { asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y ; ---- fac1 to unsigned word in A/Y
%asm {{ %asm {{

View File

@ -521,7 +521,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
;Convert 16 bit Hex to Decimal (0-65535) Rev 2 ;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples ;By Omegamatrix Further optimizations by tepples
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15 ; routine from https://forums.nesdev.org/viewtopic.php?p=130363&sid=1944ba8bac4d6afa9c02e3cc42304e6b#p130363
;HexToDec99 ;HexToDec99
; start in A ; start in A

View File

@ -119,17 +119,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
}} }}
} }
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY { asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y ; ---- fac1 to unsigned word in A/Y
%asm {{ %asm {{

View File

@ -454,20 +454,19 @@ inline asmsub getrombank() -> ubyte @A {
} }
inline asmsub getrambank() -> ubyte @A { inline asmsub getrambank() -> ubyte @A {
; -- get the current ram bank ; -- get the current RAM bank
%asm {{ %asm {{
lda $00 lda $00
}} }}
} }
asmsub numbanks() -> uword @AY { asmsub numbanks() -> uword @AY {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb) ; -- Returns the number of available RAM banks according to the kernal (each bank is 8 Kb).
; Note that the number of such banks can be bigger than 255 so a word is returned, but mostly ; Note that the number of such banks can be 256 so a word is returned.
; the A register could suffice as the lsb. ; But just looking at the A register (the LSB of the result word) could suffice if you know that A=0 means 256 banks:
; The maximum number of banks is 256 = 2 Megabytes of banked Ram aka Hiram. (Y=1 and A=0 in this case). ; The maximum number of RAM banks in the X16 is currently 256 (2 Megabytes of banked RAM).
; MEMTOP itself reports 0 in this case which we change into 256 for convenience. ; Kernal's MEMTOP routine reports 0 in this case but that doesn't mean 'zero banks', instead it means 256 banks,
; It reporting 0 doesn't mean 'zero banks', instead it means 256 banks (=2Mb banked RAM), ; as there is no X16 without at least 1 page of banked RAM. So this routine returns 256 instead of 0.
; as there is no X16 without at least 1 page of banked RAM.
%asm {{ %asm {{
phx phx
sec sec
@ -634,13 +633,17 @@ asmsub init_system() {
; Called automatically by the loader program logic. ; Called automatically by the loader program logic.
%asm {{ %asm {{
sei sei
lda #0
tax
tay
jsr cx16.mouse_config ; disable mouse
cld cld
lda VERA_DC_VIDEO lda VERA_DC_VIDEO
and #%00000111 ; retain chroma + output mode and #%00000111 ; retain chroma + output mode
sta P8ZP_SCRATCH_REG sta P8ZP_SCRATCH_REG
lda #$80 lda #$80
sta VERA_CTRL ; reset vera sta VERA_CTRL ; reset vera
stz $01 ; select rom bank 0 (enable kernal) stz $01 ; rom bank 0 (kernal)
jsr c64.IOINIT jsr c64.IOINIT
jsr c64.RESTOR jsr c64.RESTOR
jsr c64.CINT jsr c64.CINT
@ -650,8 +653,9 @@ asmsub init_system() {
sta VERA_DC_VIDEO ; keep old output mode sta VERA_DC_VIDEO ; keep old output mode
lda #$90 ; black lda #$90 ; black
jsr c64.CHROUT jsr c64.CHROUT
lda #1 ; swap fg/bg lda #1
jsr c64.CHROUT sta $00 ; select ram bank 1
jsr c64.CHROUT ; swap fg/bg
lda #$9e ; yellow lda #$9e ; yellow
jsr c64.CHROUT jsr c64.CHROUT
lda #147 ; clear screen lda #147 ; clear screen
@ -684,7 +688,7 @@ asmsub cleanup_at_exit() {
lda #1 lda #1
sta $00 ; ram bank 1 sta $00 ; ram bank 1
lda #4 lda #4
sta $01 ; rom bank 4 (kernal) sta $01 ; rom bank 4 (basic)
stz $2d ; hack to reset machine code monitor bank to 0 stz $2d ; hack to reset machine code monitor bank to 0
rts rts
}} }}
@ -714,8 +718,7 @@ _modified jsr $ffff ; modified
lda _use_kernal lda _use_kernal
bne + bne +
; end irq processing - don't use kernal's irq handling ; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR lda #1
ora #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply ply
plx plx

View File

@ -1 +1 @@
8.9 8.10

View File

@ -43,7 +43,6 @@ private fun compileMain(args: Array<String>): Boolean {
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen") val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)") val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results") val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
@ -52,6 +51,7 @@ private fun compileMain(args: Array<String>): Boolean {
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 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 .p8ir IR source file in the VM") 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 watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val varsHigh by cli.option(ArgType.Boolean, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try { try {
@ -120,12 +120,12 @@ private fun compileMain(args: Array<String>): Boolean {
filepath, filepath,
dontOptimize != true, dontOptimize != true,
optimizeFloatExpressions == true, optimizeFloatExpressions == true,
dontReinitGlobals == true,
dontWriteAssembly != true, dontWriteAssembly != true,
slowCodegenWarnings == true, slowCodegenWarnings == true,
quietAssembler == true, quietAssembler == true,
asmListfile == true, asmListfile == true,
experimentalCodegen == true, experimentalCodegen == true,
varsHigh == true,
compilationTarget, compilationTarget,
evalStackAddr, evalStackAddr,
processedSymbols, processedSymbols,
@ -184,12 +184,12 @@ private fun compileMain(args: Array<String>): Boolean {
filepath, filepath,
dontOptimize != true, dontOptimize != true,
optimizeFloatExpressions == true, optimizeFloatExpressions == true,
dontReinitGlobals == true,
dontWriteAssembly != true, dontWriteAssembly != true,
slowCodegenWarnings == true, slowCodegenWarnings == true,
quietAssembler == true, quietAssembler == true,
asmListfile == true, asmListfile == true,
experimentalCodegen == true, experimentalCodegen == true,
varsHigh == true,
compilationTarget, compilationTarget,
evalStackAddr, evalStackAddr,
processedSymbols, processedSymbols,
@ -206,13 +206,13 @@ private fun compileMain(args: Array<String>): Boolean {
} }
if(startEmulator1==true || startEmulator2==true) { if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.program.name.isEmpty()) { if (compilationResult.compilerAst.name.isEmpty()) {
println("\nCan't start emulator because no program was assembled.") println("\nCan't start emulator because no program was assembled.")
return true return true
} }
} }
val programNameInPath = outputPath.resolve(compilationResult.program.name) val programNameInPath = outputPath.resolve(compilationResult.compilerAst.name)
if(startEmulator1==true || startEmulator2==true) { if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.compilationOptions.launcher != CbmPrgLauncherType.NONE || compilationTarget=="atari") { if (compilationResult.compilationOptions.launcher != CbmPrgLauncherType.NONE || compilationTarget=="atari") {

View File

@ -0,0 +1,158 @@
package prog8.compiler
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.VarDecl
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.sign
import kotlin.math.sqrt
private typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteral
internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller> = mapOf(
"abs" to ::builtinAbs,
"len" to ::builtinLen,
"sizeof" to ::builtinSizeof,
"sgn" to ::builtinSgn,
"sqrt16" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
"any" to { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
"mkword" to ::builtinMkword
)
private fun builtinAny(array: List<Double>): Double = if(array.any { it!=0.0 }) 1.0 else 0.0
private fun builtinAll(array: List<Double>): Double = if(array.all { it!=0.0 }) 1.0 else 0.0
internal fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
if(function in arrayOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
return InferredTypes.InferredType.void()
val func = BuiltinFunctions.getValue(function)
val returnType = func.returnType
return if(returnType==null)
InferredTypes.InferredType.void()
else
InferredTypes.knownFor(returnType)
}
internal class NotConstArgumentException: AstException("not a const argument to a built-in function")
internal class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Double): NumericLiteral {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
throw SyntaxError("built-in function requires one integer argument", position)
val integer = constval.number.toInt()
return NumericLiteral.optimalInteger(function(integer).toInt(), args[0].position)
}
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Double): NumericLiteral {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val array= args[0] as? ArrayLiteral ?: throw NotConstArgumentException()
val constElements = array.value.map{it.constValue(program)?.number}
if(constElements.contains(null))
throw NotConstArgumentException()
return NumericLiteral.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
}
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = int, result type= uword
if(args.size!=1)
throw SyntaxError("abs requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return when (constval.type) {
in IntegerDatatypesNoBool -> NumericLiteral.optimalInteger(abs(constval.number.toInt()), args[0].position)
else -> throw SyntaxError("abs requires one integer argument", position)
}
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference)
throw SyntaxError("sizeof argument should be an identifier", position)
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
return when {
dt.isArray -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt) * length, position)
}
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteral(DataType.UBYTE, program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)).toDouble(), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteral.optimalInteger(arraySize, position)
if(args[0] is ArrayLiteral)
return NumericLiteral.optimalInteger((args[0] as ArrayLiteral).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
in ArrayDatatypes -> {
arraySize = target.arraysize?.constIndex()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
NumericLiteral.optimalInteger(arraySize, args[0].position)
}
DataType.STR -> {
val refLv = target.value as? StringLiteral ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteral.optimalInteger(refLv.value.length, args[0].position)
}
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw InternalCompilerException("weird datatype")
}
}
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
return NumericLiteral(DataType.UWORD, result.toDouble(), position)
}
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteral(DataType.BYTE, constval.number.sign, position)
}

View File

@ -1,17 +1,14 @@
package prog8.compiler package prog8.compiler
import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onFailure
import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.IStatementContainer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.VarDecl import prog8.code.SymbolTableMaker
import prog8.ast.walk.IAstVisitor import prog8.code.ast.PtProgram
import prog8.code.SymbolTable
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.* import prog8.code.target.*
import prog8.codegen.vm.VmCodeGen import prog8.codegen.vm.VmCodeGen
@ -25,19 +22,20 @@ import kotlin.math.round
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class CompilationResult(val program: Program, class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
val codegenAst: PtProgram?,
val compilationOptions: CompilationOptions, val compilationOptions: CompilationOptions,
val importedFiles: List<Path>) val importedFiles: List<Path>)
class CompilerArguments(val filepath: Path, class CompilerArguments(val filepath: Path,
val optimize: Boolean, val optimize: Boolean,
val optimizeFloatExpressions: Boolean, val optimizeFloatExpressions: Boolean,
val dontReinitGlobals: Boolean,
val writeAssembly: Boolean, val writeAssembly: Boolean,
val slowCodegenWarnings: Boolean, val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean, val quietAssembler: Boolean,
val asmListfile: Boolean, val asmListfile: Boolean,
val experimentalCodegen: Boolean, val experimentalCodegen: Boolean,
val varsHigh: Boolean,
val compilationTarget: String, val compilationTarget: String,
val evalStackBaseAddress: UInt?, val evalStackBaseAddress: UInt?,
val symbolDefs: Map<String, String>, val symbolDefs: Map<String, String>,
@ -63,23 +61,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
} }
var compilationOptions: CompilationOptions var compilationOptions: CompilationOptions
var ast: PtProgram? = null
try { try {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and everything it needs val (programresult, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs)
val (programresult, options, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
compilationOptions = options compilationOptions = options
print("Parsed ${args.filepath}")
ModuleImporter.ansiEraseRestOfLine(true)
with(compilationOptions) { with(compilationOptions) {
slowCodegenWarnings = args.slowCodegenWarnings slowCodegenWarnings = args.slowCodegenWarnings
optimize = args.optimize optimize = args.optimize
optimizeFloatExpressions = optimizeFloatExpr optimizeFloatExpressions = optimizeFloatExpr
dontReinitGlobals = args.dontReinitGlobals
asmQuiet = args.quietAssembler asmQuiet = args.quietAssembler
asmListfile = args.asmListfile asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen experimentalCodegen = args.experimentalCodegen
varsHigh = args.varsHigh
evalStackBaseAddress = args.evalStackBaseAddress evalStackBaseAddress = args.evalStackBaseAddress
outputDir = args.outputDir.normalize() outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs symbolDefs = args.symbolDefs
@ -113,10 +109,22 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
args.errors.report() args.errors.report()
if (args.writeAssembly) { if (args.writeAssembly) {
if(!createAssemblyAndAssemble(program, args.errors, compilationOptions)) {
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
val intermediateAst = IntermediateAstMaker(program, compilationOptions).transform()
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(intermediateAst, ::println)
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions)) {
System.err.println("Error in codegeneration or assembler") System.err.println("Error in codegeneration or assembler")
return null return null
} }
ast = intermediateAst
} }
} }
@ -124,7 +132,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
System.err.flush() System.err.flush()
val seconds = totalTime/1000.0 val seconds = totalTime/1000.0
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.") println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
return CompilationResult(program, compilationOptions, importedFiles) return CompilationResult(program, ast, compilationOptions, importedFiles)
} catch (px: ParseError) { } catch (px: ParseError) {
System.err.print("\n\u001b[91m") // bright red System.err.print("\n\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim()) System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
@ -208,7 +216,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? { override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? {
val func = BuiltinFunctions[funcName] val func = BuiltinFunctions[funcName]
if(func!=null) { if(func!=null) {
val exprfunc = func.constExpressionFunc val exprfunc = constEvaluatorsForBuiltinFuncs[funcName]
if(exprfunc!=null) { if(exprfunc!=null) {
return try { return try {
exprfunc(args, position, program) exprfunc(args, position, program)
@ -226,17 +234,16 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override fun returnType(funcName: String) = builtinFunctionReturnType(funcName) override fun returnType(funcName: String) = builtinFunctionReturnType(funcName)
} }
fun parseImports(filepath: Path, fun parseMainModule(filepath: Path,
errors: IErrorReporter, errors: IErrorReporter,
compTarget: ICompilationTarget, compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> { sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compilation target: ${compTarget.name}")
val bf = BuiltinFunctionsFacade(BuiltinFunctions) val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget) val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program bf.program = program
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs) val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath) val importedModuleResult = importer.importMainModule(filepath)
importedModuleResult.onFailure { throw it } importedModuleResult.onFailure { throw it }
errors.report() errors.report()
@ -246,11 +253,11 @@ fun parseImports(filepath: Path,
val compilerOptions = determineCompilationOptions(program, compTarget) val compilerOptions = determineCompilationOptions(program, compTarget)
// depending on the machine and compiler options we may have to include some libraries // depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name)) for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importLibraryModule(lib) importer.importImplicitLibraryModule(lib)
// always import prog8_lib and math // always import prog8_lib and math
importer.importLibraryModule("math") importer.importImplicitLibraryModule("math")
importer.importLibraryModule("prog8_lib") importer.importImplicitLibraryModule("prog8_lib")
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG) if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position) errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
@ -328,7 +335,6 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
} }
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
println("Analyzing code...")
program.preprocessAst(errors, compilerOptions) program.preprocessAst(errors, compilerOptions)
program.checkIdentifiers(errors, compilerOptions) program.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
@ -353,7 +359,6 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
} }
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) { private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
println("Optimizing...")
val remover = UnusedCodeRemover(program, errors, compTarget) val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(program) remover.visit(program)
remover.applyModifications() remover.applyModifications()
@ -385,25 +390,23 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
errors.report() errors.report()
} }
private fun createAssemblyAndAssemble(program: Program, private fun createAssemblyAndAssemble(program: PtProgram,
errors: IErrorReporter, errors: IErrorReporter,
compilerOptions: CompilationOptions compilerOptions: CompilationOptions
): Boolean { ): Boolean {
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
val symbolTable = SymbolTableMaker(program, compilerOptions).make()
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast, val asmgen = if(compilerOptions.experimentalCodegen)
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow. prog8.codegen.experimental.ExperiCodeGen()
// Note: we don't actually *need* to remove the VarDecl nodes, but it is nice as a temporary measure else if (compilerOptions.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
// to help clean up the code that still depends on them. prog8.codegen.cpu6502.AsmGen6502()
// removeAllVardeclsFromAst(program) else if (compilerOptions.compTarget.name == VMTarget.NAME)
VmCodeGen()
else
throw NotImplementedError("no asm generator for cpu ${compilerOptions.compTarget.machine.cpu}")
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************") val stMaker = SymbolTableMaker(program, compilerOptions)
// printProgram(program) val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
errors.report() errors.report()
return if(assembly!=null && errors.noErrors()) { return if(assembly!=null && errors.noErrors()) {
@ -412,51 +415,3 @@ private fun createAssemblyAndAssemble(program: Program,
false false
} }
} }
private fun removeAllVardeclsFromAst(program: Program) {
// remove all VarDecl nodes from the AST.
// code generation doesn't require them anymore, it operates only on the 'variables' collection.
class SearchAndRemove: IAstVisitor {
private val allVars = mutableListOf<VarDecl>()
init {
visit(program)
for (it in allVars) {
require((it.parent as IStatementContainer).statements.remove(it))
}
}
override fun visit(decl: VarDecl) {
allVars.add(decl)
}
}
SearchAndRemove()
}
fun printProgram(program: Program) {
println()
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)
println()
}
internal fun asmGeneratorFor(program: Program,
errors: IErrorReporter,
symbolTable: SymbolTable,
options: CompilationOptions): IAssemblyGenerator
{
if(options.experimentalCodegen) {
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
if (options.compTarget.name == VMTarget.NAME) {
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return VmCodeGen(intermediateAst, symbolTable, options, errors)
}
}
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
}

View File

@ -13,7 +13,9 @@ import prog8.code.core.SourceCode
import prog8.parser.Prog8Parser import prog8.parser.Prog8Parser
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.* import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
class ModuleImporter(private val program: Program, class ModuleImporter(private val program: Program,
@ -21,30 +23,25 @@ class ModuleImporter(private val program: Program,
val errors: IErrorReporter, val errors: IErrorReporter,
sourceDirs: List<String>) { sourceDirs: List<String>) {
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) } private val sourcePaths: List<Path> = sourceDirs.map { Path(it).absolute().normalize() }.toSortedSet().toList()
fun importModule(filePath: Path): Result<Module, NoSuchFileException> { fun importMainModule(filePath: Path): Result<Module, NoSuchFileException> {
val currentDir = Path("").absolute() val searchIn = (listOf(Path("").absolute()) + sourcePaths).toSortedSet()
val searchIn = listOf(currentDir) + sourcePaths val normalizedFilePath = filePath.normalize()
val candidates = searchIn for(path in searchIn) {
.map { it.absolute().div(filePath).normalize().absolute() } val programPath = path.resolve(normalizedFilePath)
.filter { it.exists() } if(programPath.exists()) {
.map { currentDir.relativize(it) } println("Compiling program ${Path("").absolute().relativize(programPath)}")
.map { if (it.isAbsolute) it else Path(".", "$it") } val source = SourceCode.File(programPath)
return Ok(importModule(source))
val srcPath = when (candidates.size) { }
0 -> return Err(NoSuchFileException(
file = filePath.normalize().toFile(),
reason = "Searched in $searchIn"))
1 -> candidates.first()
else -> candidates.first() // when more candiates, pick the one from the first location
} }
return Err(NoSuchFileException(
val source = SourceCode.File(srcPath) file = normalizedFilePath.toFile(),
return Ok(importModule(source)) reason = "Searched in $searchIn"))
} }
fun importLibraryModule(name: String): Module? { fun importImplicitLibraryModule(name: String): Module? {
val import = Directive("%import", listOf( val import = Directive("%import", listOf(
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0)) DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0)) ), Position("<<<implicit-import>>>", 0, 0, 0))
@ -52,7 +49,6 @@ class ModuleImporter(private val program: Program,
} }
private fun importModule(src: SourceCode) : Module { private fun importModule(src: SourceCode) : Module {
printImportingMessage(src.name, src.origin)
val moduleAst = Prog8Parser.parseModule(src) val moduleAst = Prog8Parser.parseModule(src)
program.addModule(moduleAst) program.addModule(moduleAst)
@ -146,10 +142,8 @@ class ModuleImporter(private val program: Program,
if (importingModule == null) { // <=> imported from library module if (importingModule == null) { // <=> imported from library module
sourcePaths sourcePaths
} else { } else {
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0 val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).absolute()
sourcePaths.drop(dropCurDir) + listOf(pathFromImportingModule) + sourcePaths
listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib"))
} }
locations.forEach { locations.forEach {
@ -161,18 +155,4 @@ class ModuleImporter(private val program: Program,
return Err(NoSuchFileException(File("name"))) return Err(NoSuchFileException(File("name")))
} }
fun printImportingMessage(module: String, origin: String) {
print(" importing '$module' (from ${origin})")
ansiEraseRestOfLine(false)
print("\r")
}
companion object {
fun ansiEraseRestOfLine(newline: Boolean) {
print("\u001b[0K")
if(newline)
println()
}
}
} }

View File

@ -8,8 +8,6 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.VMTarget import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
import prog8.compiler.InplaceModifyingBuiltinFunctions
import prog8.compiler.builtinFunctionReturnType import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException import java.io.CharConversionException
import java.io.File import java.io.File
@ -62,10 +60,12 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(identifier: IdentifierReference) { override fun visit(identifier: IdentifierReference) {
val targetParam = identifier.targetVarDecl(program)?.subroutineParameter val target = identifier.targetVarDecl(program)
if(targetParam!=null) { if(target != null && target.origin==VarDeclOrigin.SUBROUTINEPARAM) {
if((targetParam.parent as Subroutine).isAsmSubroutine) if(target.definingSubroutine!!.isAsmSubroutine) {
errors.err("cannot refer to parameter of asmsub by name", identifier.position) if(target.definingSubroutine!!.parameters.any { it.name == identifier.nameInSource.last() })
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
}
} }
} }
@ -664,8 +664,12 @@ internal class AstChecker(private val program: Program,
err("string var must be initialized with a string literal") err("string var must be initialized with a string literal")
} }
if(decl.value !is StringLiteral) if(decl.value !is StringLiteral) {
err("string var must be initialized with a string literal") if(decl.type==VarDeclType.MEMORY)
err("strings can't be memory mapped")
else
err("string var must be initialized with a string literal")
}
} }
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE) if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE)

View File

@ -136,12 +136,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
internal fun Program.variousCleanups(errors: IErrorReporter, options: CompilationOptions) { internal fun Program.variousCleanups(errors: IErrorReporter, options: CompilationOptions) {
val process = VariousCleanups(this, errors, options) val process = VariousCleanups(this, errors, options)
process.visit(this) process.visit(this)
if(errors.noErrors()) { while(errors.noErrors() && process.applyModifications()>0) {
if(process.applyModifications()>0) { process.visit(this)
process.visit(this)
if(errors.noErrors())
process.applyModifications()
}
} }
} }

View File

@ -7,11 +7,11 @@ import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.StringLiteral import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.code.core.BuiltinFunctions
import prog8.code.core.ICompilationTarget import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.target.VMTarget import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
internal class AstIdentifiersChecker(private val errors: IErrorReporter, internal class AstIdentifiersChecker(private val errors: IErrorReporter,
@ -92,7 +92,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val paramsToCheck = paramNames.intersect(namesInSub) val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) { for(name in paramsToCheck) {
val symbol = subroutine.searchSymbol(name) val symbol = subroutine.searchSymbol(name)
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null) if(symbol!=null && (symbol as? VarDecl)?.origin!=VarDeclOrigin.SUBROUTINEPARAM)
nameError(name, symbol.position, subroutine) nameError(name, symbol.position, subroutine)
} }
@ -163,7 +163,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("invalid number of arguments", pos) errors.err("invalid number of arguments", pos)
} }
if(func.name=="memory") { if(target.name=="memory") {
val name = call.args[0] as? StringLiteral val name = call.args[0] as? StringLiteral
if(name!=null) { if(name!=null) {
val processed = name.value.map { val processed = name.value.map {

View File

@ -31,7 +31,7 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take // 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. // into account the case where the index value is a word type.
// The replacement here is to fix missing cases in the 6502 codegen. // 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 // TODO make the 6502 codegen better so this workaround can be removed
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program) val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) { if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
if(parent is AssignTarget) { if(parent is AssignTarget) {

View File

@ -36,27 +36,9 @@ internal class BeforeAsmAstChanger(val program: Program,
return noModifications return noModifications
} }
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
// adjust global variables initialization
if(options.dontReinitGlobals) {
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
if(it.type==VarDeclType.VAR) {
it.zeropage = ZeropageWish.NOT_IN_ZEROPAGE
it.findInitializer(program)?.let { initializer ->
it.value = initializer.value // put the init value back into the vardecl
}
}
}
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(!options.dontReinitGlobals) { if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes) throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
}
return noModifications return noModifications
} }
@ -90,7 +72,7 @@ internal class BeforeAsmAstChanger(val program: Program,
"unknown dt" "unknown dt"
) )
}, sourceDt, implicit=true) }, sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.BEFOREASMGEN, assignment.position) val assignRight = Assignment(assignment.target, right, AssignmentOrigin.ASMGEN, assignment.position)
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer), IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
@ -103,7 +85,7 @@ internal class BeforeAsmAstChanger(val program: Program,
"unknown dt" "unknown dt"
) )
}, sourceDt, implicit=true) }, sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.BEFOREASMGEN, assignment.position) val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.ASMGEN, assignment.position)
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer), IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr) IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
@ -293,7 +275,7 @@ internal class BeforeAsmAstChanger(val program: Program,
val dt = expr.indexer.indexExpr.inferType(program) val dt = expr.indexer.indexExpr.inferType(program)
val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") }) val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position) val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.BEFOREASMGEN, expr.indexer.position) val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.ASMGEN, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer)) modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add( modifications.add(
IAstModification.ReplaceNode( IAstModification.ReplaceNode(

View File

@ -30,7 +30,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position) newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position)
} }
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name, val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name,
newvalue, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position) newvalue, decl.isArray, decl.sharedWithAsm, decl.position)
return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent)) return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
} }
@ -47,7 +47,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position) newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position)
} }
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name, val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name,
newarray, true, decl.sharedWithAsm, decl.subroutineParameter, decl.position) newarray, true, decl.sharedWithAsm, decl.position)
return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent)) return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
} }

View File

@ -8,12 +8,11 @@ import prog8.ast.Program
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.code.SymbolTable
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.BuiltinFunctions
import prog8.code.core.CompilationOptions import prog8.code.core.CompilationOptions
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.SourceCode import prog8.code.core.SourceCode
import prog8.compiler.BuiltinFunctions
import prog8.compiler.builtinFunctionReturnType import prog8.compiler.builtinFunctionReturnType
import java.io.File import java.io.File
import kotlin.io.path.Path import kotlin.io.path.Path
@ -23,7 +22,7 @@ import kotlin.io.path.isRegularFile
/** /**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types. * Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/ */
class IntermediateAstMaker(private val program: Program, private val symbolTable: SymbolTable, private val options: CompilationOptions) { class IntermediateAstMaker(private val program: Program, private val options: CompilationOptions) {
fun transform(): PtProgram { fun transform(): PtProgram {
val ptProgram = PtProgram( val ptProgram = PtProgram(
program.name, program.name,
@ -91,6 +90,54 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
} }
private fun transform(srcAssign: Assignment): PtNode { private fun transform(srcAssign: Assignment): PtNode {
if(srcAssign.isAugmentable) {
val srcExpr = srcAssign.value
val (operator: String, augmentedValue: Expression?) = when(srcExpr) {
is BinaryExpression -> {
if(srcExpr.operator=="==" || srcExpr.operator=="%") {
// no special code possible for 'in-place comparison and result as boolean' or 'remainder'
Pair("", null)
}
else if(srcExpr.left isSameAs srcAssign.target) {
Pair(srcExpr.operator+'=', srcExpr.right)
} else if(srcExpr.right isSameAs srcAssign.target) {
Pair(srcExpr.operator+'=', srcExpr.left)
} else {
// either left or right is same as target, other combinations are not supported here
Pair("", null)
}
}
is PrefixExpression -> {
require(srcExpr.expression isSameAs srcAssign.target)
Pair(srcExpr.operator, srcExpr.expression)
}
is TypecastExpression -> {
// At this time, there are no special optimized instructions to do an in-place type conversion.
// so we simply revert to a regular type converting assignment.
// Also, an in-place type cast is very uncommon so probably not worth optimizing anyway.
Pair("", null)
// the following is what *could* be used here if such instructions *were* available:
// if(srcExpr.expression isSameAs srcAssign.target)
// Pair("cast", srcExpr.expression)
// else {
// val subTypeCast = srcExpr.expression as? TypecastExpression
// val targetDt = srcAssign.target.inferType(program).getOrElse { DataType.UNDEFINED }
// if (subTypeCast!=null && srcExpr.type==targetDt && subTypeCast.expression isSameAs srcAssign.target) {
// Pair("cast", subTypeCast)
// } else
// Pair("", null)
// }
}
else -> Pair("", null)
}
if(augmentedValue!=null) {
val assign = PtAugmentedAssign(operator, srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(transformExpression(augmentedValue))
return assign
}
}
val assign = PtAssignment(srcAssign.position) val assign = PtAssignment(srcAssign.position)
assign.add(transform(srcAssign.target)) assign.add(transform(srcAssign.target))
assign.add(transformExpression(srcAssign.value)) assign.add(transformExpression(srcAssign.value))
@ -130,7 +177,8 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
} }
} }
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl } val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position) val src = srcBlock.definingModule.source
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, src, srcBlock.position)
makeScopeVarsDecls(vardecls).forEach { block.add(it) } makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements) for (stmt in statements)
block.add(transformStatement(stmt)) block.add(transformStatement(stmt))
@ -285,18 +333,15 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
} }
private fun transformAsmSub(srcSub: Subroutine): PtAsmSub { private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
val params = srcSub.parameters val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) })
.map { PtSubroutineParameter(it.name, it.type, it.position) }
.zip(srcSub.asmParameterRegisters)
val sub = PtAsmSub(srcSub.name, val sub = PtAsmSub(srcSub.name,
srcSub.asmAddress, srcSub.asmAddress,
srcSub.asmClobbers, srcSub.asmClobbers,
params, params,
srcSub.returntypes, srcSub.asmReturnvaluesRegisters.zip(srcSub.returntypes),
srcSub.asmReturnvaluesRegisters,
srcSub.inline, srcSub.inline,
srcSub.position) srcSub.position)
sub.parameters.forEach { it.first.parent=sub } sub.parameters.forEach { it.second.parent=sub }
if(srcSub.asmAddress==null) { if(srcSub.asmAddress==null) {
var combinedTrueAsm = "" var combinedTrueAsm = ""
@ -329,10 +374,11 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
var returntype = srcSub.returntypes.singleOrNull() var returntype = srcSub.returntypes.singleOrNull()
if(returntype==DataType.STR) 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. returntype=DataType.UWORD // if a sub returns 'str', replace with uword. Intermediate AST and I.R. don't contain 'str' datatype anymore.
// do not bother about the 'inline' hint of the source subroutine.
val sub = PtSub(srcSub.name, val sub = PtSub(srcSub.name,
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) }, srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) },
returntype, returntype,
srcSub.inline,
srcSub.position) srcSub.position)
sub.parameters.forEach { it.parent=sub } sub.parameters.forEach { it.parent=sub }
makeScopeVarsDecls(vardecls).forEach { sub.add(it) } makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
@ -346,7 +392,7 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
return when(srcVar.type) { return when(srcVar.type) {
VarDeclType.VAR -> { VarDeclType.VAR -> {
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
} }
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position) VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
@ -385,8 +431,8 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
} }
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer { private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val type = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") } val arrayVarType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(type, srcArr.position) val array = PtArrayIndexer(arrayVarType, srcArr.position)
array.add(transform(srcArr.arrayvar)) array.add(transform(srcArr.arrayvar))
array.add(transformExpression(srcArr.indexer.indexExpr)) array.add(transformExpression(srcArr.indexer.indexExpr))
return array return array
@ -464,6 +510,7 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
private fun transform(srcCast: TypecastExpression): PtTypeCast { private fun transform(srcCast: TypecastExpression): PtTypeCast {
val cast = PtTypeCast(srcCast.type, srcCast.position) val cast = PtTypeCast(srcCast.type, srcCast.position)
cast.add(transformExpression(srcCast.expression)) cast.add(transformExpression(srcCast.expression))
require(cast.type!=cast.value.type)
return cast return cast
} }

View File

@ -153,17 +153,6 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
return CondExprSimplificationResult(null, null, null, null) return CondExprSimplificationResult(null, null, null, null)
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
val leftDt = expr.left.inferType(program) val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightDt = expr.right.inferType(program)
@ -172,13 +161,24 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
return CondExprSimplificationResult(null, null, null, null) return CondExprSimplificationResult(null, null, null, null)
} }
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
if(separateLeftExpr) { if(separateLeftExpr) {
val name = getTempRegisterName(leftDt) val name = getTempRegisterName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position) leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment( leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position), AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left.copy(), expr.left.copy(),
AssignmentOrigin.BEFOREASMGEN, expr.position AssignmentOrigin.ASMGEN, expr.position
) )
} }
if(separateRightExpr) { if(separateRightExpr) {
@ -187,7 +187,7 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
rightAssignment = Assignment( rightAssignment = Assignment(
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position), AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
expr.right.copy(), expr.right.copy(),
AssignmentOrigin.BEFOREASMGEN, expr.position AssignmentOrigin.ASMGEN, expr.position
) )
} }
return CondExprSimplificationResult( return CondExprSimplificationResult(

View File

@ -153,7 +153,7 @@ internal class StatementReorderer(val program: Program,
subroutine.statements subroutine.statements
.asSequence() .asSequence()
.filterIsInstance<VarDecl>() .filterIsInstance<VarDecl>()
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames } .filter { it.origin==VarDeclOrigin.SUBROUTINEPARAM && it.name in stringParamsByNames }
.map { .map {
val newvar = VarDecl(it.type, it.origin, DataType.UWORD, val newvar = VarDecl(it.type, it.origin, DataType.UWORD,
it.zeropage, it.zeropage,
@ -162,7 +162,6 @@ internal class StatementReorderer(val program: Program,
null, null,
false, false,
it.sharedWithAsm, it.sharedWithAsm,
stringParamsByNames.getValue(it.name),
it.position it.position
) )
IAstModification.ReplaceNode(it, newvar, subroutine) IAstModification.ReplaceNode(it, newvar, subroutine)

View File

@ -1,138 +0,0 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
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.*
internal class SymbolTableMaker(private val program: Program, private val options: CompilationOptions): IAstVisitor {
private val st = SymbolTable()
private val scopestack = Stack<StNode>()
private var dontReinitGlobals = false
fun make(): SymbolTable {
scopestack.clear()
st.children.clear()
dontReinitGlobals = options.dontReinitGlobals
this.visit(program)
program.builtinFunctions.names.forEach {
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
st.add(node)
}
require(scopestack.isEmpty())
return st
}
override fun visit(block: Block) {
val node = StNode(block.name, StNodeType.BLOCK, block.position)
st.add(node)
scopestack.push(node)
super.visit(block)
scopestack.pop()
// st.origAstLinks[block] = node
}
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.asmReturnvaluesRegisters, subroutine.position)
scopestack.peek().add(node)
// st.origAstLinks[subroutine] = node
} else {
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
val returnType = if(subroutine.returntypes.isEmpty()) null else subroutine.returntypes.first()
val node = StSub(subroutine.name, parameters, returnType, subroutine.position)
scopestack.peek().add(node)
scopestack.push(node)
super.visit(subroutine)
scopestack.pop()
// st.origAstLinks[subroutine] = node
}
}
override fun visit(decl: VarDecl) {
val node =
when(decl.type) {
VarDeclType.VAR -> {
var initialNumeric = (decl.value as? NumericLiteral)?.number
if(initialNumeric==0.0)
initialNumeric=null // variable will go into BSS and this will be set to 0
val initialStringLit = decl.value as? StringLiteral
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
val initialArrayLit = decl.value as? ArrayLiteral
val initialArray = makeInitialArray(initialArrayLit)
if(decl.isArray && decl.datatype !in ArrayDatatypes)
throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
val numElements =
if(decl.isArray)
decl.arraysize!!.constIndex()
else if(initialStringLit!=null)
initialStringLit.value.length+1 // include the terminating 0-byte
else
null
val bss = if(decl.datatype==DataType.STR)
false
else if(decl.isArray)
initialArray.isNullOrEmpty()
else
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 -> {
val numElements =
if(decl.datatype in ArrayDatatypes)
decl.arraysize!!.constIndex()
else null
StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, decl.position)
}
}
scopestack.peek().add(node)
// st.origAstLinks[decl] = node
}
private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
if(arrayLit==null)
return null
return arrayLit.value.map {
when(it){
is AddressOf -> {
val scopedName = it.identifier.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is IdentifierReference -> {
val scopedName = it.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is NumericLiteral -> StArrayElement(it.number, null)
else -> throw FatalAstException("weird element dt in array literal")
}
}.toList()
}
override fun visit(label: Label) {
val node = StNode(label.name, StNodeType.LABEL, label.position)
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

@ -9,7 +9,6 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.code.core.* import prog8.code.core.*
import prog8.compiler.BuiltinFunctions
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() { class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {

View File

@ -40,7 +40,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
} }
val sourceDt = typecast.expression.inferType(program) val sourceDt = typecast.expression.inferType(program)
if(sourceDt istype typecast.type) if(sourceDt istype typecast.type || (sourceDt istype DataType.BOOL && typecast.type==DataType.UBYTE))
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(parent is Assignment) { if(parent is Assignment) {
@ -62,6 +62,11 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
} }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
// remove duplicated assignments, but not if it's a memory mapped IO register // remove duplicated assignments, but not if it's a memory mapped IO register
val isIO = try { val isIO = try {
assignment.target.isIOAddress(options.compTarget.machine) assignment.target.isIOAddress(options.compTarget.machine)

View File

@ -5,11 +5,7 @@ import prog8.ast.Program
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.code.core.ByteDatatypes import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.compiler.BuiltinFunctions
internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor { internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor {

View File

@ -43,7 +43,7 @@ class TestModuleImporter: FunSpec({
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString) val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist") val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
val srcPathAbs = srcPathRel.absolute() val srcPathAbs = srcPathRel.absolute()
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") } val error1 = importer.importMainModule(srcPathRel).getErrorOrElse { fail("should have import error") }
withClue(".file should be normalized") { withClue(".file should be normalized") {
"${error1.file}" shouldBe "${error1.file.normalize()}" "${error1.file}" shouldBe "${error1.file.normalize()}"
} }
@ -51,7 +51,7 @@ class TestModuleImporter: FunSpec({
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}" error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
} }
program.modules.size shouldBe 1 program.modules.size shouldBe 1
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") } val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
withClue(".file should be normalized") { withClue(".file should be normalized") {
"${error2.file}" shouldBe "${error2.file.normalize()}" "${error2.file}" shouldBe "${error2.file.normalize()}"
} }
@ -67,7 +67,7 @@ class TestModuleImporter: FunSpec({
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
val importer = makeImporter(null, searchIn) val importer = makeImporter(null, searchIn)
shouldThrow<FileSystemException> { importer.importModule(srcPathRel) } shouldThrow<FileSystemException> { importer.importMainModule(srcPathRel) }
.let { .let {
withClue(".file should be normalized") { withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}" "${it.file}" shouldBe "${it.file.normalize()}"
@ -78,7 +78,7 @@ class TestModuleImporter: FunSpec({
} }
program.modules.size shouldBe 1 program.modules.size shouldBe 1
shouldThrow<FileSystemException> { importer.importModule(srcPathAbs) } shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
.let { .let {
withClue(".file should be normalized") { withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}" "${it.file}" shouldBe "${it.file.normalize()}"
@ -101,7 +101,7 @@ class TestModuleImporter: FunSpec({
val fileName = "ast_simple_main.p8" val fileName = "ast_simple_main.p8"
val path = assumeReadableFile(searchIn[0], fileName) val path = assumeReadableFile(searchIn[0], fileName)
val module = importer.importModule(path.absolute()).getOrElse { throw it } val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
program.modules.size shouldBe 2 program.modules.size shouldBe 2
module shouldBeIn program.modules module shouldBeIn program.modules
module.program shouldBe program module.program shouldBe program
@ -118,7 +118,7 @@ class TestModuleImporter: FunSpec({
path.isAbsolute shouldBe false path.isAbsolute shouldBe false
} }
val module = importer.importModule(path).getOrElse { throw it } val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2 program.modules.size shouldBe 2
module shouldBeIn program.modules module shouldBeIn program.modules
module.program shouldBe program module.program shouldBe program
@ -133,7 +133,7 @@ class TestModuleImporter: FunSpec({
val path = Path(".", fileName) val path = Path(".", fileName)
assumeReadableFile(searchIn, path) assumeReadableFile(searchIn, path)
val module = importer.importModule(path).getOrElse { throw it } val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2 program.modules.size shouldBe 2
module shouldBeIn program.modules module shouldBeIn program.modules
module.program shouldBe program module.program shouldBe program
@ -145,7 +145,7 @@ class TestModuleImporter: FunSpec({
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString) val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8") val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
val act = { importer.importModule(srcPath) } val act = { importer.importMainModule(srcPath) }
repeat(2) { n -> withClue(count[n] + " call") { repeat(2) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError> { act() }.let { shouldThrow<ParseError> { act() }.let {
@ -165,7 +165,7 @@ class TestModuleImporter: FunSpec({
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8") val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8") val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
val act = { importer.importModule(importing) } val act = { importer.importMainModule(importing) }
repeat(repetitions) { n -> withClue(count[n] + " call") { repeat(repetitions) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError> { act() }.let { shouldThrow<ParseError> { act() }.let {
@ -201,14 +201,14 @@ class TestModuleImporter: FunSpec({
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
repeat(2) { n -> repeat(2) { n ->
val result = importer.importLibraryModule(filenameNoExt) val result = importer.importImplicitLibraryModule(filenameNoExt)
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null } withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false } withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist" errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
errors.report() errors.report()
program.modules.size shouldBe 1 program.modules.size shouldBe 1
val result2 = importer.importLibraryModule(filenameWithExt) val result2 = importer.importImplicitLibraryModule(filenameWithExt)
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null } withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false } withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8" errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
@ -228,7 +228,7 @@ class TestModuleImporter: FunSpec({
repeat(2) { n -> withClue(count[n] + " call") { repeat(2) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError>() shouldThrow<ParseError>()
{ {
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let { importer.importImplicitLibraryModule(srcPath.nameWithoutExtension) }.let {
it.position.file shouldBe SourceCode.relative(srcPath).toString() it.position.file shouldBe SourceCode.relative(srcPath).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 } withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 } withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
@ -246,7 +246,7 @@ class TestModuleImporter: FunSpec({
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8") val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8") val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
val act = { importer.importLibraryModule(importing.nameWithoutExtension) } val act = { importer.importImplicitLibraryModule(importing.nameWithoutExtension) }
repeat(repetitions) { n -> withClue(count[n] + " call") { repeat(repetitions) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError> { shouldThrow<ParseError> {

View File

@ -3,31 +3,19 @@ package prog8tests
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.code.core.BuiltinFunctions
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.NumericDatatypesNoBool import prog8.code.core.NumericDatatypesNoBool
import prog8.code.core.RegisterOrPair import prog8.code.core.RegisterOrPair
import prog8.code.target.Cx16Target import prog8.code.target.Cx16Target
import prog8.compiler.BuiltinFunctions
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
class TestBuiltinFunctions: FunSpec({ class TestBuiltinFunctions: FunSpec({
test("push pop") {
val src="""
main {
sub start () {
pushw(cx16.r0)
push(cx16.r1L)
pop(cx16.r1L)
popw(cx16.r0)
}
}"""
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("pure func with fixed type") { test("pure func with fixed type") {
val func = BuiltinFunctions.getValue("sgn") val func = BuiltinFunctions.getValue("sgn")
func.name shouldBe "sgn"
func.parameters.size shouldBe 1 func.parameters.size shouldBe 1
func.parameters[0].name shouldBe "value" func.parameters[0].name shouldBe "value"
func.parameters[0].possibleDatatypes shouldBe NumericDatatypesNoBool func.parameters[0].possibleDatatypes shouldBe NumericDatatypesNoBool
@ -46,7 +34,6 @@ class TestBuiltinFunctions: FunSpec({
test("not-pure func with varying result value type") { test("not-pure func with varying result value type") {
val func = BuiltinFunctions.getValue("cmp") val func = BuiltinFunctions.getValue("cmp")
func.name shouldBe "cmp"
func.parameters.size shouldBe 2 func.parameters.size shouldBe 2
func.pure shouldBe false func.pure shouldBe false
func.returnType shouldBe null func.returnType shouldBe null
@ -60,7 +47,6 @@ class TestBuiltinFunctions: FunSpec({
test("func without return type") { test("func without return type") {
val func = BuiltinFunctions.getValue("poke") val func = BuiltinFunctions.getValue("poke")
func.name shouldBe "poke"
func.parameters.size shouldBe 2 func.parameters.size shouldBe 2
func.parameters[0].name shouldBe "address" func.parameters[0].name shouldBe "address"
func.parameters[0].possibleDatatypes shouldBe arrayOf(DataType.UWORD) func.parameters[0].possibleDatatypes shouldBe arrayOf(DataType.UWORD)
@ -81,5 +67,44 @@ class TestBuiltinFunctions: FunSpec({
conv.returns.floatFac1 shouldBe false conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe null conv.returns.reg shouldBe null
} }
test("push pop") {
val src="""
main {
sub start () {
pushw(cx16.r0)
push(cx16.r1L)
pop(cx16.r1L)
popw(cx16.r0)
}
}"""
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("certain builtin functions should be compile time evaluated") {
val src="""
main {
sub start() {
uword[] array = [1,2,3]
str name = "hello"
cx16.r0L = len(array)
cx16.r0L = len(name)
cx16.r0L = sizeof(array)
cx16.r0 = mkword(200,100)
}
}"""
val result = compileText(Cx16Target(), false, src, writeAssembly = false)
val statements = result!!.compilerAst.entrypoint.statements
statements.size shouldBe 6
val a1 = statements[2] as Assignment
val a2 = statements[3] as Assignment
val a3 = statements[4] as Assignment
val a4 = statements[5] as Assignment
(a1.value as NumericLiteral).number shouldBe 3.0
(a2.value as NumericLiteral).number shouldBe 5.0
(a3.value as NumericLiteral).number shouldBe 6.0
(a4.value as NumericLiteral).number shouldBe 200*256+100
}
}) })

View File

@ -27,11 +27,11 @@ class TestCallgraph: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, sourcecode)!! val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program) val graph = CallGraph(result.compilerAst)
graph.imports.size shouldBe 1 graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1 graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule val toplevelModule = result.compilerAst.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single() val importedModule = graph.imports.getValue(toplevelModule).single()
importedModule.name shouldBe "string" importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single() val importedBy = graph.importedBy.getValue(importedModule).single()
@ -46,7 +46,7 @@ class TestCallgraph: FunSpec({
graph.calls shouldNotContainKey sub graph.calls shouldNotContainKey sub
graph.calledBy shouldNotContainKey sub graph.calledBy shouldNotContainKey sub
if(sub === result.program.entrypoint) if(sub === result.compilerAst.entrypoint)
withClue("start() should always be marked as used to avoid having it removed") { withClue("start() should always be marked as used to avoid having it removed") {
graph.unused(sub) shouldBe false graph.unused(sub) shouldBe false
} }
@ -68,11 +68,11 @@ class TestCallgraph: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, sourcecode)!! val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program) val graph = CallGraph(result.compilerAst)
graph.imports.size shouldBe 1 graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1 graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule val toplevelModule = result.compilerAst.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single() val importedModule = graph.imports.getValue(toplevelModule).single()
importedModule.name shouldBe "string" importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single() val importedBy = graph.importedBy.getValue(importedModule).single()
@ -111,7 +111,7 @@ class TestCallgraph: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, sourcecode)!! val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program) val graph = CallGraph(result.compilerAst)
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5 graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") } val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
empties.size shouldBe 3 empties.size shouldBe 3

View File

@ -33,7 +33,7 @@ class TestCompilerOnCharLit: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -57,7 +57,7 @@ class TestCompilerOnCharLit: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -92,7 +92,7 @@ class TestCompilerOnCharLit: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]

View File

@ -27,12 +27,12 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
filepath, filepath,
optimize, optimize,
optimizeFloatExpressions = true, optimizeFloatExpressions = true,
dontReinitGlobals = false,
writeAssembly = true, writeAssembly = true,
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true, quietAssembler = true,
asmListfile = false, asmListfile = false,
experimentalCodegen = false, experimentalCodegen = false,
varsHigh = false,
compilationTarget = target.name, compilationTarget = target.name,
evalStackBaseAddress = null, evalStackBaseAddress = null,
symbolDefs = emptyMap(), symbolDefs = emptyMap(),
@ -188,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val (displayName, filepath) = prepareTestFiles(it, false, target) val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) { test(displayName) {
val src = filepath.readText() val src = filepath.readText()
compileText(target, false, src, writeAssembly = true) shouldNotBe null compileText(target, true, src, writeAssembly = true) shouldNotBe null
compileText(target, false, src, writeAssembly = true) shouldNotBe null compileText(target, true, src, writeAssembly = true) shouldNotBe null
} }
} }
}) })

View File

@ -30,7 +30,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val platform = Cx16Target() val platform = Cx16Target()
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!! val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val strLits = startSub.statements val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>() .filterIsInstance<FunctionCallStatement>()
@ -52,7 +52,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val platform = Cx16Target() val platform = Cx16Target()
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!! val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val args = startSub.statements val args = startSub.statements
.filterIsInstance<FunctionCallStatement>() .filterIsInstance<FunctionCallStatement>()

View File

@ -40,7 +40,7 @@ class TestCompilerOnRanges: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val decl = startSub val decl = startSub
.statements.filterIsInstance<VarDecl>()[0] .statements.filterIsInstance<VarDecl>()[0]
@ -72,7 +72,7 @@ class TestCompilerOnRanges: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val decl = startSub val decl = startSub
.statements.filterIsInstance<VarDecl>()[0] .statements.filterIsInstance<VarDecl>()[0]
@ -143,7 +143,7 @@ class TestCompilerOnRanges: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val iterable = startSub val iterable = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
@ -177,7 +177,7 @@ class TestCompilerOnRanges: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val rangeExpr = startSub val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
@ -203,7 +203,7 @@ class TestCompilerOnRanges: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val rangeExpr = startSub val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
@ -247,7 +247,7 @@ class TestCompilerOnRanges: FunSpec({
} }
""")!! """)!!
val program = result.program val program = result.compilerAst
val startSub = program.entrypoint val startSub = program.entrypoint
val iterable = startSub val iterable = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
@ -279,7 +279,7 @@ class TestCompilerOnRanges: FunSpec({
} }
} }
""")!! """)!!
val statements = result.program.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
val array = (statements[0] as VarDecl).value val array = (statements[0] as VarDecl).value
array shouldBe instanceOf<ArrayLiteral>() array shouldBe instanceOf<ArrayLiteral>()
(array as ArrayLiteral).value.size shouldBe 26 (array as ArrayLiteral).value.size shouldBe 26
@ -301,7 +301,7 @@ class TestCompilerOnRanges: FunSpec({
} }
} }
""")!! """)!!
val statements = result.program.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
val forloop = (statements.dropLast(1).last() as ForLoop) val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpression>() forloop.iterable shouldBe instanceOf<RangeExpression>()
(forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY) (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)

View File

@ -44,12 +44,12 @@ class TestCompilerOptionSourcedirs: FunSpec({
filepath = filePath, filepath = filePath,
optimize = false, optimize = false,
optimizeFloatExpressions = false, optimizeFloatExpressions = false,
dontReinitGlobals = false,
writeAssembly = true, writeAssembly = true,
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true, quietAssembler = true,
asmListfile = false, asmListfile = false,
experimentalCodegen = false, experimentalCodegen = false,
varsHigh = false,
compilationTarget = Cx16Target.NAME, compilationTarget = Cx16Target.NAME,
evalStackBaseAddress = null, evalStackBaseAddress = null,
symbolDefs = emptyMap(), symbolDefs = emptyMap(),

View File

@ -25,7 +25,7 @@ class TestGoldenRam: FunSpec({
test("empty golden ram allocations") { test("empty golden ram allocations") {
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests()
val golden = GoldenRam(options, UIntRange.EMPTY) val golden = GoldenRam(options, UIntRange.EMPTY)
val result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors) val result = golden.allocate("test", DataType.UBYTE, null, null, errors)
result.expectError { "should not be able to allocate anything" } result.expectError { "should not be able to allocate anything" }
} }
@ -33,28 +33,28 @@ class TestGoldenRam: FunSpec({
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests()
val golden = GoldenRam(options, 0x400u until 0x800u) val golden = GoldenRam(options, 0x400u until 0x800u)
var result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors) var result = golden.allocate("test", DataType.UBYTE, null, null, errors)
var alloc = result.getOrThrow() var alloc = result.getOrThrow()
alloc.size shouldBe 1 alloc.size shouldBe 1
alloc.address shouldBe 0x400u alloc.address shouldBe 0x400u
result = golden.allocate(listOf("test"), DataType.STR, 100, null, errors) result = golden.allocate("test", DataType.STR, 100, null, errors)
alloc = result.getOrThrow() alloc = result.getOrThrow()
alloc.size shouldBe 100 alloc.size shouldBe 100
alloc.address shouldBe 0x401u alloc.address shouldBe 0x401u
repeat(461) { repeat(461) {
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors) result = golden.allocate("test", DataType.UWORD, null, null, errors)
alloc = result.getOrThrow() alloc = result.getOrThrow()
alloc.size shouldBe 2 alloc.size shouldBe 2
} }
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors) result = golden.allocate("test", DataType.UWORD, null, null, errors)
result.expectError { "just 1 more byte available" } result.expectError { "just 1 more byte available" }
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors) result = golden.allocate("test", DataType.UBYTE, null, null, errors)
alloc = result.getOrThrow() alloc = result.getOrThrow()
alloc.size shouldBe 1 alloc.size shouldBe 1
alloc.address shouldBe golden.region.last alloc.address shouldBe golden.region.last
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors) result = golden.allocate("test", DataType.UBYTE, null, null, errors)
result.expectError { "nothing more available" } result.expectError { "nothing more available" }
} }

View File

@ -8,7 +8,7 @@ import prog8.code.core.ZeropageType
import prog8.code.core.internedStringsModuleName import prog8.code.core.internedStringsModuleName
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.compiler.determineCompilationOptions import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports import prog8.compiler.parseMainModule
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import prog8tests.helpers.outputDir import prog8tests.helpers.outputDir
@ -28,9 +28,9 @@ main {
} }
} }
""")!! """)!!
result.program.toplevelModule.name shouldStartWith "on_the_fly_test" result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
val moduleNames = result.program.modules.map { it.name } val moduleNames = result.compilerAst.modules.map { it.name }
withClue("main module must be first") { withClue("main module must be first") {
moduleNames[0] shouldStartWith "on_the_fly_test" moduleNames[0] shouldStartWith "on_the_fly_test"
} }
@ -46,7 +46,7 @@ main {
"prog8_lib" "prog8_lib"
) )
} }
result.program.toplevelModule.name shouldStartWith "on_the_fly_test" result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
} }
test("testCompilationOptionsCorrectFromMain") { test("testCompilationOptionsCorrectFromMain") {
@ -63,8 +63,8 @@ main {
} }
} }
""")!! """)!!
result.program.toplevelModule.name shouldStartWith "on_the_fly_test" result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
val options = determineCompilationOptions(result.program, C64Target()) val options = determineCompilationOptions(result.compilerAst, C64Target())
options.floats shouldBe true options.floats shouldBe true
options.zeropage shouldBe ZeropageType.DONTUSE options.zeropage shouldBe ZeropageType.DONTUSE
options.noSysInit shouldBe true options.noSysInit shouldBe true
@ -87,7 +87,7 @@ main {
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
val filepath = outputDir.resolve("$filenameBase.p8") val filepath = outputDir.resolve("$filenameBase.p8")
filepath.toFile().writeText(sourceText) filepath.toFile().writeText(sourceText)
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target(), emptyList()) val (program, options, importedfiles) = parseMainModule(filepath, errors, C64Target(), emptyList())
program.toplevelModule.name shouldBe filenameBase program.toplevelModule.name shouldBe filenameBase
withClue("all imports other than the test source must have been internal resources library files") { withClue("all imports other than the test source must have been internal resources library files") {

View File

@ -19,10 +19,10 @@ class TestLaunchEmu: FunSpec({
<ASMSYMBOLS> <ASMSYMBOLS>
</ASMSYMBOLS> </ASMSYMBOLS>
<BSS> <VARIABLESNOINIT>
</BSS> </VARIABLESNOINIT>
<VARIABLES> <VARIABLESWITHINIT>
</VARIABLES> </VARIABLESWITHINIT>
<MEMORYMAPPEDVARIABLES> <MEMORYMAPPEDVARIABLES>
</MEMORYMAPPEDVARIABLES> </MEMORYMAPPEDVARIABLES>

View File

@ -15,7 +15,6 @@ import prog8.code.core.Position
import prog8.code.core.SourceCode import prog8.code.core.SourceCode
import prog8.code.core.ZeropageWish import prog8.code.core.ZeropageWish
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.compiler.printProgram
import prog8tests.helpers.DummyFunctions import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder import prog8tests.helpers.DummyStringEncoder
@ -28,7 +27,7 @@ class TestMemory: FunSpec({
fun wrapWithProgram(statements: List<Statement>): Program { fun wrapWithProgram(statements: List<Statement>): Program {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), statements.toMutableList(), false, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements.toMutableList(), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
program.addModule(module) program.addModule(module)
return program return program
@ -113,7 +112,7 @@ class TestMemory: FunSpec({
} }
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget { fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY) val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY) val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@ -147,12 +146,11 @@ class TestMemory: FunSpec({
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
wrapWithProgram(listOf(assign)) wrapWithProgram(listOf(assign))
printProgram(target.definingModule.program)
target.isIOAddress(c64target.machine) shouldBe true target.isIOAddress(c64target.machine) shouldBe true
} }
test("regular variable not in mapped IO ram on C64") { test("regular variable not in mapped IO ram on C64") {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@ -164,7 +162,7 @@ class TestMemory: FunSpec({
test("memory mapped variable not in mapped IO ram on C64") { test("memory mapped variable not in mapped IO ram on C64") {
val address = 0x1000u val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@ -176,7 +174,7 @@ class TestMemory: FunSpec({
test("memory mapped variable in mapped IO ram on C64") { test("memory mapped variable in mapped IO ram on C64") {
val address = 0xd020u val address = 0xd020u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@ -187,7 +185,7 @@ class TestMemory: FunSpec({
} }
test("array not in mapped IO ram") { test("array not in mapped IO ram") {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@ -200,7 +198,7 @@ class TestMemory: FunSpec({
test("memory mapped array not in mapped IO ram") { test("memory mapped array not in mapped IO ram") {
val address = 0x1000u val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@ -213,7 +211,7 @@ class TestMemory: FunSpec({
test("memory mapped array in mapped IO ram") { test("memory mapped array in mapped IO ram") {
val address = 0xd800u val address = 0xd800u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)

View File

@ -14,7 +14,6 @@ import prog8.code.core.Position
import prog8.code.core.toHex import prog8.code.core.toHex
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5 import prog8.code.target.cbm.Mflpt5
import prog8.compiler.printProgram
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
@ -199,9 +198,8 @@ class TestNumbers: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, src, writeAssembly = false)!! val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val statements = result.program.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 8 statements.size shouldBe 8
printProgram(result.program)
(statements[1] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 32768.0, Position.DUMMY) (statements[1] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 32768.0, Position.DUMMY)
(statements[3] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65535.0, Position.DUMMY) (statements[3] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65535.0, Position.DUMMY)
(statements[5] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY) (statements[5] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY)

View File

@ -16,7 +16,6 @@ import prog8.code.core.DataType
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.Cx16Target import prog8.code.target.Cx16Target
import prog8.compiler.printProgram
import prog8tests.helpers.* import prog8tests.helpers.*
@ -32,10 +31,10 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), true, sourcecode)!! val result = compileText(C64Target(), true, sourcecode)!!
val toplevelModule = result.program.toplevelModule val toplevelModule = result.compilerAst.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements.single() as Subroutine val startSub = mainBlock.statements.single() as Subroutine
result.program.entrypoint shouldBeSameInstanceAs startSub result.compilerAst.entrypoint shouldBeSameInstanceAs startSub
withClue("only start sub should remain") { withClue("only start sub should remain") {
startSub.name shouldBe "start" startSub.name shouldBe "start"
} }
@ -57,11 +56,11 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), true, sourcecode)!! val result = compileText(C64Target(), true, sourcecode)!!
val toplevelModule = result.program.toplevelModule val toplevelModule = result.compilerAst.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements[0] as Subroutine val startSub = mainBlock.statements[0] as Subroutine
val emptySub = mainBlock.statements[1] as Subroutine val emptySub = mainBlock.statements[1] as Subroutine
result.program.entrypoint shouldBeSameInstanceAs startSub result.compilerAst.entrypoint shouldBeSameInstanceAs startSub
startSub.name shouldBe "start" startSub.name shouldBe "start"
emptySub.name shouldBe "empty" emptySub.name shouldBe "empty"
withClue("compiler has inserted return in empty subroutines") { withClue("compiler has inserted return in empty subroutines") {
@ -131,7 +130,7 @@ class TestOptimization: FunSpec({
// cx16.r5s -= 1899 // cx16.r5s -= 1899
// cx16.r7s = llw // cx16.r7s = llw
// cx16.r7s += 99 // cx16.r7s += 99
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 14 stmts.size shouldBe 14
val addR0value = (stmts[5] as Assignment).value val addR0value = (stmts[5] as Assignment).value
@ -183,7 +182,7 @@ class TestOptimization: FunSpec({
// cx16.r4s = llw // cx16.r4s = llw
// cx16.r4s *= 90 // cx16.r4s *= 90
// cx16.r4s /= 5 // cx16.r4s /= 5
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 13 stmts.size shouldBe 13
val mulR0Value = (stmts[3] as Assignment).value val mulR0Value = (stmts[3] as Assignment).value
@ -225,7 +224,7 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, sourcecode)!! val result = compileText(C64Target(), false, sourcecode)!!
val mainsub = result.program.entrypoint val mainsub = result.compilerAst.entrypoint
mainsub.statements.size shouldBe 10 mainsub.statements.size shouldBe 10
val declTest = mainsub.statements[0] as VarDecl val declTest = mainsub.statements[0] as VarDecl
val declX1 = mainsub.statements[1] as VarDecl val declX1 = mainsub.statements[1] as VarDecl
@ -265,8 +264,7 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, src, writeAssembly = true)!! val result = compileText(C64Target(), false, src, writeAssembly = true)!!
printProgram(result.program) val stmts = result.compilerAst.entrypoint.statements
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8 stmts.size shouldBe 8
val value1 = (stmts[4] as Assignment).value as BinaryExpression val value1 = (stmts[4] as Assignment).value as BinaryExpression
@ -278,39 +276,6 @@ class TestOptimization: FunSpec({
value3.operator shouldBe "&" value3.operator shouldBe "&"
} }
test("intermediate assignment steps generated for typecasted expression") {
val src = """
main {
sub start() {
ubyte r
ubyte @shared bb = (sgn(r)*2 + 100) as ubyte
}
}
"""
val result = compileText(C64Target(), true, src, writeAssembly = true)!!
/* turned into:
ubyte r
r = 0
ubyte bb
prog8_lib.retval_interm_b = sgn(r)
prog8_lib.retval_interm_b <<= 1
prog8_lib.retval_interm_b += 100
bb = prog8_lib.retval_interm_b
return
*/
val st = result.program.entrypoint.statements
st.size shouldBe 8
st.last() shouldBe instanceOf<Return>()
var assign = st[3] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[4] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[5] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[6] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("bb")
}
test("asmgen correctly deals with float typecasting in augmented assignment") { test("asmgen correctly deals with float typecasting in augmented assignment") {
val src=""" val src="""
%import floats %import floats
@ -324,7 +289,7 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), optimize=false, src, writeAssembly = false)!! val result = compileText(C64Target(), optimize=false, src, writeAssembly = false)!!
val assignFF = result.program.entrypoint.statements.last() as Assignment val assignFF = result.compilerAst.entrypoint.statements.last() as Assignment
assignFF.isAugmentable shouldBe true assignFF.isAugmentable shouldBe true
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff") assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignFF.value as BinaryExpression val value = assignFF.value as BinaryExpression
@ -350,8 +315,8 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
result.program.entrypoint.statements.size shouldBe 7 result.compilerAst.entrypoint.statements.size shouldBe 7
val alldecls = result.program.entrypoint.allDefinedSymbols.toList() val alldecls = result.compilerAst.entrypoint.allDefinedSymbols.toList()
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar") alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
} }
@ -373,11 +338,11 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
result.program.entrypoint.statements.size shouldBe 3 result.compilerAst.entrypoint.statements.size shouldBe 3
val ifstmt = result.program.entrypoint.statements[0] as IfElse val ifstmt = result.compilerAst.entrypoint.statements[0] as IfElse
ifstmt.truepart.statements.size shouldBe 1 ifstmt.truepart.statements.size shouldBe 1
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0") (ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
val func2 = result.program.entrypoint.statements[2] as Subroutine val func2 = result.compilerAst.entrypoint.statements[2] as Subroutine
func2.statements.size shouldBe 1 func2.statements.size shouldBe 1
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0") (func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
} }
@ -401,7 +366,6 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program)
/* expected: /* expected:
ubyte z1 ubyte z1
z1 = 10 z1 = 10
@ -418,7 +382,7 @@ class TestOptimization: FunSpec({
z6 = z1 z6 = z1
z6 -= 5 z6 -= 5
*/ */
val statements = result.program.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 14 statements.size shouldBe 14
val z1decl = statements[0] as VarDecl val z1decl = statements[0] as VarDecl
val z1init = statements[1] as Assignment val z1init = statements[1] as Assignment
@ -469,7 +433,7 @@ class TestOptimization: FunSpec({
""" """
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6 stmts.size shouldBe 6
val assign=stmts.last() as Assignment val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa") (assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
@ -486,7 +450,7 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6 stmts.size shouldBe 6
val assign=stmts.last() as Assignment val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa") (assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
@ -505,7 +469,7 @@ class TestOptimization: FunSpec({
} }
""" """
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 10 stmts.size shouldBe 10
stmts.filterIsInstance<VarDecl>().size shouldBe 5 stmts.filterIsInstance<VarDecl>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 5 stmts.filterIsInstance<Assignment>().size shouldBe 5
@ -554,8 +518,8 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!! val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
result.program.entrypoint.statements.size shouldBe 11 result.compilerAst.entrypoint.statements.size shouldBe 11
result.program.entrypoint.statements.last() shouldBe instanceOf<Return>() result.compilerAst.entrypoint.statements.last() shouldBe instanceOf<Return>()
} }
test("keep the value initializer assignment if the next one depends on it") { test("keep the value initializer assignment if the next one depends on it") {
@ -582,7 +546,7 @@ class TestOptimization: FunSpec({
xx = abs(xx) xx = abs(xx)
xx += 6 xx += 6
*/ */
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 8 stmts.size shouldBe 8
stmts.filterIsInstance<VarDecl>().size shouldBe 3 stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 5 stmts.filterIsInstance<Assignment>().size shouldBe 5
@ -611,7 +575,7 @@ class TestOptimization: FunSpec({
yy = 0 yy = 0
xx += 10 xx += 10
*/ */
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7 stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 2 stmts.filterIsInstance<VarDecl>().size shouldBe 2
stmts.filterIsInstance<Assignment>().size shouldBe 5 stmts.filterIsInstance<Assignment>().size shouldBe 5
@ -638,7 +602,6 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program)
/* /*
expected result: expected result:
ubyte[] auto_heap_var = [1,4,99,3] ubyte[] auto_heap_var = [1,4,99,3]
@ -649,7 +612,7 @@ class TestOptimization: FunSpec({
if source in auto_heap_var if source in auto_heap_var
thingy++ thingy++
*/ */
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6 stmts.size shouldBe 6
val ifStmt = stmts[5] as IfElse val ifStmt = stmts[5] as IfElse
val containment = ifStmt.condition as ContainmentCheck val containment = ifStmt.condition as ContainmentCheck
@ -679,8 +642,7 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program) val stmts = result.compilerAst.entrypoint.statements
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 5 stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>() ifStmt.condition shouldBe instanceOf<BinaryExpression>()
@ -698,8 +660,7 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program) val stmts = result.compilerAst.entrypoint.statements
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 5 stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>() ifStmt.condition shouldBe instanceOf<BinaryExpression>()
@ -717,7 +678,7 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5 stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>() ifStmt.condition shouldBe instanceOf<BinaryExpression>()
@ -734,7 +695,7 @@ class TestOptimization: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!! val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3 stmts.size shouldBe 3
} }
@ -756,15 +717,15 @@ main {
} }
}""" }"""
var result = compileText(Cx16Target(), true, srcX16, writeAssembly = true)!! var result = compileText(Cx16Target(), true, srcX16, writeAssembly = true)!!
var statements = result.program.entrypoint.statements var statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 9 statements.size shouldBe 9
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx") (statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0") (statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0") (statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0") (statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff (statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff (statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff (statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
val srcC64=""" val srcC64="""
main { main {
@ -783,14 +744,26 @@ main {
} }
}""" }"""
result = compileText(C64Target(), true, srcC64, writeAssembly = true)!! result = compileText(C64Target(), true, srcC64, writeAssembly = true)!!
statements = result.program.entrypoint.statements statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 9 statements.size shouldBe 9
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx") (statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL") (statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL") (statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL") (statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0 (statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0 (statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0 (statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
}
test("no crash on sorting unused array") {
val text="""
main {
ubyte[5] cards = [ 14, 6, 29, 16, 3 ]
sub start() {
sort(cards)
}
}"""
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
} }
}) })

View File

@ -27,9 +27,9 @@ class TestScoping: FunSpec({
""" """
val result = compileText(C64Target(), false, src, writeAssembly = false)!! val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val module = result.program.toplevelModule val module = result.compilerAst.toplevelModule
module.parent shouldBe instanceOf<GlobalNamespace>() module.parent shouldBe instanceOf<GlobalNamespace>()
module.program shouldBeSameInstanceAs result.program module.program shouldBeSameInstanceAs result.compilerAst
module.parent.parent shouldBe instanceOf<ParentSentinel>() module.parent.parent shouldBe instanceOf<ParentSentinel>()
} }
@ -46,7 +46,7 @@ class TestScoping: FunSpec({
""" """
val result = compileText(C64Target(), false, src, writeAssembly = false)!! val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val module = result.program.toplevelModule val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine val start = mainBlock.statements.single() as Subroutine
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
@ -120,7 +120,7 @@ class TestScoping: FunSpec({
""" """
val result = compileText(C64Target(), false, src, writeAssembly = true)!! val result = compileText(C64Target(), false, src, writeAssembly = true)!!
val module = result.program.toplevelModule val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>() val labels = start.statements.filterIsInstance<Label>()

View File

@ -60,7 +60,7 @@ class TestSubroutines: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val module = result.program.toplevelModule val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"} val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"} val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
@ -116,7 +116,7 @@ class TestSubroutines: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, text, writeAssembly = true)!! val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val module = result.program.toplevelModule val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"} val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"} val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
@ -178,7 +178,7 @@ class TestSubroutines: FunSpec({
""" """
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val module = result.program.toplevelModule val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"} val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"} val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
@ -284,7 +284,7 @@ class TestSubroutines: FunSpec({
} }
""" """
val result = compileText(C64Target(), false, text, writeAssembly = true)!! val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.last() shouldBe instanceOf<Subroutine>() stmts.last() shouldBe instanceOf<Subroutine>()
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough

View File

@ -4,19 +4,26 @@ import io.kotest.assertions.fail
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.code.* import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.code.core.ZeropageWish import prog8.code.core.ZeropageWish
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
class TestSymbolTable: FunSpec({ class TestSymbolTable: FunSpec({
test("empty symboltable") { test("empty symboltable") {
val st = SymbolTable() val astNode = PtProgram("test", DummyMemsizer, DummyStringEncoder)
st.scopedName shouldBe "" val st = SymbolTable(astNode)
st.name shouldBe "" st.name shouldBe "test"
st.type shouldBe StNodeType.GLOBAL st.type shouldBe StNodeType.GLOBAL
st.children shouldBe mutableMapOf() st.children shouldBe mutableMapOf()
st.position shouldBe Position.DUMMY st.astNode shouldBeSameInstanceAs astNode
st.astNode.position shouldBe Position.DUMMY
} }
test("symboltable flatten") { test("symboltable flatten") {
@ -33,9 +40,9 @@ class TestSymbolTable: FunSpec({
st.lookupUnscoped("undefined") shouldBe null st.lookupUnscoped("undefined") shouldBe null
st.lookup("undefined") shouldBe null st.lookup("undefined") shouldBe null
st.lookup("undefined.undefined") shouldBe null st.lookup("undefined.undefined") shouldBe null
var default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) } var default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, PtIdentifier("default", DataType.BYTE, Position.DUMMY)) }
default.name shouldBe "default" default.name shouldBe "default"
default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) } default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, PtIdentifier("default", DataType.BYTE, Position.DUMMY)) }
default.name shouldBe "default" default.name shouldBe "default"
val msbFunc = st.lookupUnscopedOrElse("msb") { fail("msb must be found") } val msbFunc = st.lookupUnscopedOrElse("msb") { fail("msb must be found") }
@ -52,7 +59,7 @@ class TestSymbolTable: FunSpec({
sub1.name shouldBe "sub1" sub1.name shouldBe "sub1"
sub1.scopedName shouldBe "block1.sub1" sub1.scopedName shouldBe "block1.sub1"
sub1.type shouldBe StNodeType.SUBROUTINE sub1.type shouldBe StNodeType.SUBROUTINE
sub1.children.size shouldBe 2 sub1.children.size shouldBe 4
val v1 = sub1.lookupUnscopedOrElse("v1") { fail("v1 must be found") } as StStaticVariable val v1 = sub1.lookupUnscopedOrElse("v1") { fail("v1 must be found") } as StStaticVariable
v1.type shouldBe StNodeType.STATICVAR v1.type shouldBe StNodeType.STATICVAR
@ -67,35 +74,84 @@ class TestSymbolTable: FunSpec({
subsub.lookupUnscoped("blockc") shouldBe null subsub.lookupUnscoped("blockc") shouldBe null
subsub.lookupUnscoped("label") shouldNotBe null subsub.lookupUnscoped("label") shouldNotBe null
} }
test("symboltable collections") {
val st= makeSt()
st.allVariables.size shouldBe 4
st.allMemMappedVariables.single().scopedName shouldBe "block1.sub1.v3"
st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1"
}
}) })
private fun makeSt(): SymbolTable { private fun makeSt(): SymbolTable {
val st = SymbolTable()
val block1 = StNode("block1", StNodeType.BLOCK, Position.DUMMY) // first build the AST
val sub11 = StNode("sub1", StNodeType.SUBROUTINE, Position.DUMMY) val astProgram = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val sub12 = StNode("sub2", StNodeType.SUBROUTINE, Position.DUMMY) val astBlock1 = PtBlock("block1", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astConstant1 = PtConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY)
val astConstant2 = PtConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY)
astBlock1.add(astConstant1)
astBlock1.add(astConstant2)
val astSub1 = PtSub("sub1", emptyList(), null, Position.DUMMY)
val astSub2 = PtSub("sub2", emptyList(), null, Position.DUMMY)
val astSub1v1 = PtVariable("v1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY)
val astSub1v2 = PtVariable("v2", DataType.BYTE, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub1v3 = PtVariable("v3", DataType.FLOAT, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub1v4 = PtVariable("slab1", DataType.UWORD, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub2v1 = PtVariable("v1", DataType.BYTE, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub2v2 = PtVariable("v2", DataType.BYTE, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
astSub1.add(astSub1v1)
astSub1.add(astSub1v2)
astSub1.add(astSub1v3)
astSub1.add(astSub1v4)
astSub2.add(astSub2v2)
astSub2.add(astSub2v2)
astBlock1.add(astSub1)
astBlock1.add(astSub2)
val astBfunc = PtIdentifier("msb", DataType.UBYTE, Position.DUMMY)
astBlock1.add(astBfunc)
val astBlock2 = PtBlock("block2", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astSub21 = PtSub("sub1", emptyList(), null, Position.DUMMY)
val astSub22 = PtSub("sub2", emptyList(), null, Position.DUMMY)
val astSub221 = PtSub("subsub", emptyList(), null, Position.DUMMY)
val astLabel = PtLabel("label", Position.DUMMY)
astSub221.add(astLabel)
astSub22.add(astSub221)
astBlock2.add(astSub21)
astBlock2.add(astSub22)
astProgram.add(astBlock1)
astProgram.add(astBlock2)
// now hook up the SymbolTable on that AST
val st = SymbolTable(astProgram)
val block1 = StNode("block1", StNodeType.BLOCK, astBlock1)
val sub11 = StNode("sub1", StNodeType.SUBROUTINE, astSub1)
val sub12 = StNode("sub2", StNodeType.SUBROUTINE, astSub2)
block1.add(sub11) block1.add(sub11)
block1.add(sub12) block1.add(sub12)
block1.add(StConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY)) block1.add(StConstant("c1", DataType.UWORD, 12345.0, astConstant1))
block1.add(StConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY)) block1.add(StConstant("blockc", DataType.UWORD, 999.0, astConstant2))
sub11.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY)) sub11.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub1v1))
sub11.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY)) sub11.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub1v2))
sub12.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY)) sub11.add(StMemVar("v3", DataType.FLOAT, 12345u, null, astSub1v3))
sub12.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY)) sub11.add(StMemorySlab("slab1", 200u, 64u, astSub1v4))
sub12.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub2v1))
val block2 = StNode("block2", StNodeType.BLOCK, Position.DUMMY) sub12.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub2v2))
val sub21 = StNode("sub1", StNodeType.SUBROUTINE, Position.DUMMY) val block2 = StNode("block2", StNodeType.BLOCK, astBlock2)
val sub22 = StNode("sub2", StNodeType.SUBROUTINE, Position.DUMMY) val sub21 = StNode("sub1", StNodeType.SUBROUTINE, astSub21)
val sub22 = StNode("sub2", StNodeType.SUBROUTINE, astSub22)
block2.add(sub21) block2.add(sub21)
block2.add(sub22) block2.add(sub22)
val sub221 = StNode("subsub", StNodeType.SUBROUTINE, Position.DUMMY) val sub221 = StNode("subsub", StNodeType.SUBROUTINE, astSub221)
sub221.add(StNode("label", StNodeType.LABEL, Position.DUMMY)) sub221.add(StNode("label", StNodeType.LABEL, astLabel))
sub22.add(sub221) sub22.add(sub221)
val builtinfunc = StNode("msb", StNodeType.BUILTINFUNC, Position.DUMMY) val builtinfunc = StNode("msb", StNodeType.BUILTINFUNC, astBfunc)
st.add(block1) st.add(block1)
st.add(block2) st.add(block2)
st.add(builtinfunc) st.add(builtinfunc)
return st return st
} }

View File

@ -49,7 +49,7 @@ class TestTypecasts: FunSpec({
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 4 stmts.size shouldBe 4
val expr = (stmts[3] as Assignment).value as BinaryExpression val expr = (stmts[3] as Assignment).value as BinaryExpression
expr.operator shouldBe "and" expr.operator shouldBe "and"
@ -57,7 +57,7 @@ class TestTypecasts: FunSpec({
(expr.left as IdentifierReference).nameInSource shouldBe listOf("bb2") // no cast (expr.left as IdentifierReference).nameInSource shouldBe listOf("bb2") // no cast
val result2 = compileText(C64Target(), true, text, writeAssembly = true)!! val result2 = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts2 = result2.program.entrypoint.statements val stmts2 = result2.compilerAst.entrypoint.statements
stmts2.size shouldBe 6 stmts2.size shouldBe 6
val expr2 = (stmts2[4] as Assignment).value as BinaryExpression val expr2 = (stmts2[4] as Assignment).value as BinaryExpression
expr2.operator shouldBe "&" expr2.operator shouldBe "&"
@ -86,7 +86,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!! val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
/* /*
ubyte ub1 ubyte ub1
ub1 = 1 ub1 = 1
@ -120,6 +120,42 @@ main {
right3.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY) right3.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
} }
test("simple logical with bool no typecast") {
val text="""
main {
bool bb
sub start() {
bb = bb and 123
}
}"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 2
val assignValue = (stmts[0] as Assignment).value as BinaryExpression
assignValue.left shouldBe instanceOf<IdentifierReference>()
assignValue.operator shouldBe "&"
(assignValue.right as NumericLiteral).number shouldBe 1.0
}
test("simple logical with byte instead of bool ok with typecasting") {
val text="""
main {
ubyte ubb
sub start() {
ubb = ubb and 123
}
}"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 2
val assignValue = (stmts[0] as Assignment).value as BinaryExpression
assignValue.left shouldBe instanceOf<BinaryExpression>() // as a result of the cast to boolean
assignValue.operator shouldBe "&"
(assignValue.right as NumericLiteral).number shouldBe 1.0
}
test("logical with byte instead of bool") { test("logical with byte instead of bool") {
val text=""" val text="""
%import textio %import textio
@ -329,7 +365,7 @@ main {
} }
""" """
val result = compileText(C64Target(), true, text, writeAssembly = true)!! val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBeGreaterThan 10 stmts.size shouldBeGreaterThan 10
} }
@ -542,7 +578,7 @@ main {
} }
""" """
val result = compileText(C64Target(), true, text, writeAssembly = true)!! val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBeGreaterThan 10 stmts.size shouldBeGreaterThan 10
} }
@ -561,7 +597,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6 stmts.size shouldBe 6
val arraydecl = stmts[0] as VarDecl val arraydecl = stmts[0] as VarDecl
arraydecl.datatype shouldBe DataType.ARRAY_BOOL arraydecl.datatype shouldBe DataType.ARRAY_BOOL
@ -595,7 +631,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 9 stmts.size shouldBe 9
val fcall1 = ((stmts[6] as Assignment).value as IFunctionCall) val fcall1 = ((stmts[6] as Assignment).value as IFunctionCall)
fcall1.args[0] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY) fcall1.args[0] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
@ -628,7 +664,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!! val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBeGreaterThan 10 stmts.size shouldBeGreaterThan 10
} }
@ -647,7 +683,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3 stmts.size shouldBe 3
} }
@ -670,7 +706,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!! val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 8 stmts.size shouldBe 8
val arg1 = (stmts[2] as IFunctionCall).args.single() val arg1 = (stmts[2] as IFunctionCall).args.single()
val arg2 = (stmts[3] as IFunctionCall).args.single() val arg2 = (stmts[3] as IFunctionCall).args.single()
@ -707,7 +743,7 @@ main {
} }
""" """
val result = compileText(C64Target(), false, text, writeAssembly = true)!! val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.program.entrypoint.statements.size shouldBe 15 result.compilerAst.entrypoint.statements.size shouldBe 15
} }
test("invalid typecasts of numbers") { test("invalid typecasts of numbers") {
@ -827,7 +863,7 @@ main {
} }
""" """
val result = compileText(C64Target(), false, text, writeAssembly = true)!! val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val statements = result.program.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
statements.size shouldBeGreaterThan 10 statements.size shouldBeGreaterThan 10
} }
@ -854,7 +890,7 @@ main {
} }
}""" }"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!! val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val statements = result.program.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
statements.size shouldBeGreaterThan 10 statements.size shouldBeGreaterThan 10
} }

View File

@ -71,26 +71,26 @@ class TestC64Zeropage: FunSpec({
compTarget = c64target, loadAddress = 999u compTarget = c64target, loadAddress = 999u
)) ))
var result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) var result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) } result.onFailure { fail(it.toString()) }
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) } result.onFailure { fail(it.toString()) }
result = zp.allocate(listOf("varname"), DataType.UBYTE, null, null, errors) result = zp.allocate("varname", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) } result.onFailure { fail(it.toString()) }
shouldThrow<IllegalArgumentException> { zp.allocate(listOf("varname"), DataType.UBYTE,null, null, errors) } shouldThrow<IllegalArgumentException> { zp.allocate("varname", DataType.UBYTE,null, null, errors) }
result = zp.allocate(listOf("varname2"), DataType.UBYTE, null, null, errors) result = zp.allocate("varname2", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) } result.onFailure { fail(it.toString()) }
} }
test("testZpFloatEnable") { test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors) var result = zp.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled floats" } result.expectError { "should be allocation error due to disabled floats" }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target, 999u)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target, 999u))
result = zp2.allocate(emptyList(), DataType.FLOAT, null, null, errors) result = zp2.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled ZP use" } result.expectError { "should be allocation error due to disabled ZP use" }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
zp3.allocate(emptyList(), DataType.FLOAT, null, null, errors) zp3.allocate("", DataType.FLOAT, null, null, errors)
} }
test("testZpModesWithFloats") { test("testZpModesWithFloats") {
@ -112,7 +112,7 @@ class TestC64Zeropage: FunSpec({
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target, 999u)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target, 999u))
println(zp.free) println(zp.free)
zp.availableBytes() shouldBe 0 zp.availableBytes() shouldBe 0
val result = zp.allocate(emptyList(), DataType.BYTE, null, null, errors) val result = zp.allocate("", DataType.BYTE, null, null, errors)
result.expectError { "expected error due to disabled ZP use" } result.expectError { "expected error due to disabled ZP use" }
} }
@ -125,9 +125,9 @@ class TestC64Zeropage: FunSpec({
zp3.availableBytes() shouldBe 97 zp3.availableBytes() shouldBe 97
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u)) val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
zp4.availableBytes() shouldBe 207 zp4.availableBytes() shouldBe 207
zp4.allocate(listOf("test"), DataType.UBYTE, null, null, errors) zp4.allocate("test", DataType.UBYTE, null, null, errors)
zp4.availableBytes() shouldBe 206 zp4.availableBytes() shouldBe 206
zp4.allocate(listOf("test2"), DataType.UBYTE, null, null, errors) zp4.allocate("test2", DataType.UBYTE, null, null, errors)
zp4.availableBytes() shouldBe 205 zp4.availableBytes() shouldBe 205
} }
@ -166,19 +166,19 @@ class TestC64Zeropage: FunSpec({
zp.hasByteAvailable() shouldBe true zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true zp.hasWordAvailable() shouldBe true
var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors) var result = zp.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "expect allocation error: in regular zp there aren't 5 sequential bytes free" } result.expectError { "expect allocation error: in regular zp there aren't 5 sequential bytes free" }
for (i in 0 until zp.availableBytes()) { for (i in 0 until zp.availableBytes()) {
val alloc = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) val alloc = zp.allocate("", DataType.UBYTE, null, null, errors)
alloc.getOrElse { throw it } alloc.getOrElse { throw it }
} }
zp.availableBytes() shouldBe 0 zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false zp.hasByteAvailable() shouldBe false
zp.hasWordAvailable() shouldBe false zp.hasWordAvailable() shouldBe false
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.expectError { "expected allocation error" } result.expectError { "expected allocation error" }
result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors) result = zp.allocate("", DataType.UWORD, null, null, errors)
result.expectError { "expected allocation error" } result.expectError { "expected allocation error" }
} }
@ -187,47 +187,47 @@ class TestC64Zeropage: FunSpec({
zp.availableBytes() shouldBe 207 zp.availableBytes() shouldBe 207
zp.hasByteAvailable() shouldBe true zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true zp.hasWordAvailable() shouldBe true
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors) var result = zp.allocate("", DataType.UWORD, null, null, errors)
val loc = result.getOrElse { throw it } .address val loc = result.getOrElse { throw it } .address
loc shouldBeGreaterThan 3u loc shouldBeGreaterThan 3u
loc shouldNotBeIn zp.free loc shouldNotBeIn zp.free
val num = zp.availableBytes() / 2 val num = zp.availableBytes() / 2
for(i in 0..num-3) { for(i in 0..num-3) {
zp.allocate(emptyList(), DataType.UWORD, null, null, errors) zp.allocate("", DataType.UWORD, null, null, errors)
} }
zp.availableBytes() shouldBe 5 zp.availableBytes() shouldBe 5
// can't allocate because no more sequential bytes, only fragmented // can't allocate because no more sequential bytes, only fragmented
result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors) result = zp.allocate("", DataType.UWORD, null, null, errors)
result.expectError { "should give allocation error" } result.expectError { "should give allocation error" }
for(i in 0..4) { for(i in 0..4) {
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) zp.allocate("", DataType.UBYTE, null, null, errors)
} }
zp.availableBytes() shouldBe 0 zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false zp.hasByteAvailable() shouldBe false
zp.hasWordAvailable() shouldBe false zp.hasWordAvailable() shouldBe false
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.expectError { "should give allocation error" } result.expectError { "should give allocation error" }
} }
test("testEfficientAllocation") { test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18 zp.availableBytes() shouldBe 18
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
zp.availableBytes() shouldBe 0 zp.availableBytes() shouldBe 0
} }
@ -258,9 +258,9 @@ class TestCx16Zeropage: FunSpec({
zp2.availableBytes() shouldBe 175 zp2.availableBytes() shouldBe 175
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u)) val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
zp3.availableBytes() shouldBe 216 zp3.availableBytes() shouldBe 216
zp3.allocate(listOf("test"), DataType.UBYTE, null, null, errors) zp3.allocate("test", DataType.UBYTE, null, null, errors)
zp3.availableBytes() shouldBe 215 zp3.availableBytes() shouldBe 215
zp3.allocate(listOf("test2"), DataType.UBYTE, null, null, errors) zp3.allocate("test2", DataType.UBYTE, null, null, errors)
zp3.availableBytes() shouldBe 214 zp3.availableBytes() shouldBe 214
} }
@ -276,12 +276,12 @@ class TestCx16Zeropage: FunSpec({
test("preallocated zp vars") { test("preallocated zp vars") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u)) val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
zp1.allocatedVariables[listOf("test")] shouldBe null zp1.allocatedVariables["test"] shouldBe null
zp1.allocatedVariables[listOf("cx16", "r0")] shouldNotBe null zp1.allocatedVariables["cx16.r0"] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15")] shouldNotBe null zp1.allocatedVariables["cx16.r15"] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r0L")] shouldNotBe null zp1.allocatedVariables["cx16.r0L"] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15L")] shouldNotBe null zp1.allocatedVariables["cx16.r15L"] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r0sH")] shouldNotBe null zp1.allocatedVariables["cx16.r0sH"] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15sH")] shouldNotBe null zp1.allocatedVariables["cx16.r15sH"] shouldNotBe null
} }
}) })

View File

@ -8,7 +8,6 @@ import prog8.code.ast.*
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.compiler.astprocessing.IntermediateAstMaker import prog8.compiler.astprocessing.IntermediateAstMaker
import prog8.compiler.astprocessing.SymbolTableMaker
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({ class TestIntermediateAst: FunSpec({
@ -38,9 +37,8 @@ class TestIntermediateAst: FunSpec({
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
) )
val result = compileText(target, false, text, writeAssembly = false)!! val result = compileText(target, false, text, writeAssembly = false)!!
val st = SymbolTableMaker(result.program, options).make() val ast = IntermediateAstMaker(result.compilerAst, options).transform()
val ast = IntermediateAstMaker(result.program, st, options).transform() ast.name shouldBe result.compilerAst.name
ast.name shouldBe result.program.name
ast.allBlocks().any() shouldBe true ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found") val entry = ast.entrypoint() ?: fail("no main.start() found")
entry.children.size shouldBe 5 entry.children.size shouldBe 5
@ -59,18 +57,16 @@ class TestIntermediateAst: FunSpec({
ccdecl.name shouldBe "cc" ccdecl.name shouldBe "cc"
ccdecl.scopedName shouldBe "main.start.cc" ccdecl.scopedName shouldBe "main.start.cc"
ccdecl.type shouldBe DataType.UBYTE ccdecl.type shouldBe DataType.UBYTE
val arraydecl = entry.children[1] as PtVariable val arraydecl = entry.children[1] as IPtVariable
arraydecl.name shouldBe "array" arraydecl.name shouldBe "array"
arraydecl.type shouldBe DataType.ARRAY_UB arraydecl.type shouldBe DataType.ARRAY_UB
val containmentCast = (entry.children[3] as PtAssignment).value as PtTypeCast val containment = (entry.children[3] as PtAssignment).value as PtContainmentCheck
containmentCast.type shouldBe DataType.UBYTE
val containment = containmentCast.value as PtContainmentCheck
(containment.element as PtNumber).number shouldBe 11.0 (containment.element as PtNumber).number shouldBe 11.0
val fcall = (entry.children[4] as PtAssignment).value as PtFunctionCall val fcall = (entry.children[4] as PtAssignment).value as PtFunctionCall
fcall.void shouldBe false fcall.void shouldBe false
fcall.type shouldBe DataType.UBYTE fcall.type shouldBe DataType.UBYTE
ast.print() printAst(ast, ::println)
} }
}) })

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