From 1f5420010debef38eb778c6fc94d27f62ab8d926 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 13 Jul 2019 00:27:03 +0200 Subject: [PATCH] prevent struct member vars from shuffling around, can take address of struct now --- compiler/src/prog8/ast/antlr/Antr2Kotlin.kt | 10 +- .../src/prog8/ast/processing/AstChecker.kt | 14 +- .../ast/processing/AstIdentifiersChecker.kt | 10 +- .../VarInitValueAndAddressOfCreator.kt | 2 +- .../src/prog8/ast/statements/AstStatements.kt | 12 +- .../src/prog8/compiler/AstToSourceCode.kt | 4 +- compiler/src/prog8/compiler/Compiler.kt | 19 ++ .../intermediate/IntermediateProgram.kt | 34 ++-- .../src/prog8/compiler/target/c64/AsmGen.kt | 177 ++++++++++-------- .../src/prog8/optimizer/StatementOptimizer.kt | 2 +- compiler/src/prog8/vm/RuntimeValue.kt | 8 +- compiler/src/prog8/vm/astvm/Expressions.kt | 42 +++-- .../src/prog8/vm/astvm/VariablesCreator.kt | 7 +- compiler/src/prog8/vm/stackvm/StackVm.kt | 15 +- docs/source/programming.rst | 5 +- examples/test.p8 | 36 ++-- 16 files changed, 235 insertions(+), 162 deletions(-) diff --git a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt index 5ee45174f..0d3f88ac2 100644 --- a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt +++ b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt @@ -66,7 +66,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { return VarDecl( VarDeclType.VAR, vd.datatype()?.toAst() ?: DataType.STRUCT, - vd.ZEROPAGE() != null, + if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, vd.arrayindex()?.toAst(), vd.varname.text, vd.structname?.text, @@ -83,7 +83,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { return VarDecl( VarDeclType.CONST, vd.datatype()?.toAst() ?: DataType.STRUCT, - vd.ZEROPAGE() != null, + if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, vd.arrayindex()?.toAst(), vd.varname.text, vd.structname?.text, @@ -100,7 +100,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { return VarDecl( VarDeclType.MEMORY, vd.datatype()?.toAst() ?: DataType.STRUCT, - vd.ZEROPAGE() != null, + if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, vd.arrayindex()?.toAst(), vd.varname.text, vd.structname?.text, @@ -519,7 +519,7 @@ private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf private fun prog8Parser.ForloopContext.toAst(): ForLoop { val loopregister = register()?.toAst() val datatype = datatype()?.toAst() - val zeropage = ZEROPAGE()!=null + val zeropage = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE val loopvar = identifier()?.toAst() val iterable = expression()!!.toAst() val scope = @@ -573,7 +573,7 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl { return VarDecl( VarDeclType.VAR, datatype()?.toAst() ?: DataType.STRUCT, - ZEROPAGE() != null, + if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, arrayindex()?.toAst(), varname.text, structname?.text, diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index c104b6e6d..8a98e1226 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -450,7 +450,7 @@ internal class AstChecker(private val program: Program, if(variable==null) checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position)) else { - if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes) + if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes && variable.datatype!=DataType.STRUCT) checkResult.add(ExpressionError("pointer-of operand must be the name of a string or array heap variable", addressOf.position)) } if(addressOf.scopedname==null) @@ -500,12 +500,14 @@ internal class AstChecker(private val program: Program, when(decl.type) { VarDeclType.VAR, VarDeclType.CONST -> { - if(decl.struct!=null || decl.datatype==DataType.STRUCT) { - if(decl.datatype!=DataType.STRUCT) - throw FatalAstException("struct vardecl should be of data type struct $decl") + if(decl.datatype==DataType.STRUCT) { if(decl.struct==null) throw FatalAstException("struct vardecl should be linked to its struct $decl") - if(decl.zeropage) + if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE) + err("struct can not be in zeropage") + } + if(decl.struct!=null) { + if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE) err("struct can not be in zeropage") } if (decl.value == null) { @@ -1284,7 +1286,7 @@ internal class AstChecker(private val program: Program, if(decl==null) checkResult.add(SyntaxError("struct can only contain variable declarations", structDecl.position)) else { - if(decl.zeropage) + if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position)) if(decl.datatype !in NumericDatatypes) checkResult.add(SyntaxError("structs can only contain numerical types", decl.position)) diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index 98dcec05f..cc9774c12 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -71,10 +71,10 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo VarDecl( VarDeclType.VAR, member.datatype, - false, + ZeropageWish.NOT_IN_ZEROPAGE, member.arraysize, mangledStructMemberName(decl.name, member.name), - null, + decl.struct!!.name, initvalue, member.isArray, true, @@ -131,7 +131,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo subroutine.parameters .filter { it.name !in namesInSub } .forEach { - val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, null, + val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.DONTCARE, null, it.name, null, null, isArray = false, hiddenButDoNotRemove = true, position = subroutine.position) vardecl.linkParents(subroutine) subroutine.statements.add(0, vardecl) @@ -183,7 +183,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) if(existing==null) { // create loop iteration counter variable (without value, to avoid an assignment) - val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, null, + val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, ForLoop.iteratorLoopcounterVarname, null, null, isArray = false, hiddenButDoNotRemove = true, position = forLoop.loopVar.position) vardecl.linkParents(forLoop.body) forLoop.body.statements.add(0, vardecl) @@ -230,7 +230,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo val declaredType = if(literalValue.isArray) ArrayElementTypes.getValue(literalValue.type) else literalValue.type val variable = VarDecl(VarDeclType.VAR, declaredType, - false, + ZeropageWish.NOT_IN_ZEROPAGE, null, "$autoHeapValuePrefix${literalValue.heapId}", null, diff --git a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt index 37ee4f42d..21ad099d2 100644 --- a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt +++ b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt @@ -128,7 +128,7 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope pointerExpr.linkParents(arglist[argparam.first.index].parent) arglist[argparam.first.index] = pointerExpr // add a vardecl so that the autovar can be resolved in later lookups - val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, null, strvalue, + val variable = VarDecl(VarDeclType.VAR, strvalue.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, strvalue, isArray = false, hiddenButDoNotRemove = false, position = strvalue.position) addVarDecl(strvalue.definingScope(), variable) } diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt index 310f40e19..3cfd36487 100644 --- a/compiler/src/prog8/ast/statements/AstStatements.kt +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -135,9 +135,17 @@ class Break(override val position: Position) : IStatement { override fun accept(visitor: IAstVisitor) = visitor.visit(this) } + +enum class ZeropageWish { + REQUIRE_ZEROPAGE, + PREFER_ZEROPAGE, + DONTCARE, + NOT_IN_ZEROPAGE +} + class VarDecl(val type: VarDeclType, private val declaredDatatype: DataType, - val zeropage: Boolean, + val zeropage: ZeropageWish, var arraysize: ArrayIndex?, val name: String, private val structName: String?, @@ -617,7 +625,7 @@ class BranchStatement(var condition: BranchCondition, class ForLoop(val loopRegister: Register?, val decltype: DataType?, - val zeropage: Boolean, + val zeropage: ZeropageWish, val loopVar: IdentifierReference?, var iterable: IExpression, var body: AnonymousScope, diff --git a/compiler/src/prog8/compiler/AstToSourceCode.kt b/compiler/src/prog8/compiler/AstToSourceCode.kt index b79cbff98..50dac6d33 100644 --- a/compiler/src/prog8/compiler/AstToSourceCode.kt +++ b/compiler/src/prog8/compiler/AstToSourceCode.kt @@ -116,7 +116,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor { if(decl.isArray) output("]") - if(decl.zeropage) + if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) output(" @zp") output(" ${decl.name} ") if(decl.value!=null) { @@ -305,7 +305,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor { output("for ") if(forLoop.decltype!=null) { output(datatypeString(forLoop.decltype)) - if (forLoop.zeropage) + if (forLoop.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || forLoop.zeropage==ZeropageWish.PREFER_ZEROPAGE) output(" @zp ") else output(" ") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index a4fd311af..0da5206c3 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1488,6 +1488,22 @@ internal class Compiler(private val program: Program) { } } + private fun pushStructAddress(value: IExpression) { + when (value) { + is LiteralValue -> throw CompilerException("can only push address of struct that is a variable on the heap") + is IdentifierReference -> { + // notice that the mangled name of the first struct member is the start address of this struct var + val vardecl = value.targetVarDecl(program.namespace)!! + val firstStructMember = (vardecl.struct!!.statements.first() as VarDecl).name + val firstVarName = listOf(vardecl.name, firstStructMember) + // find the flattened var that belongs to this first struct member + val firstVar = value.definingScope().lookup(firstVarName, value) as VarDecl + prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = firstVar.scopedname) // TODO + } + else -> throw CompilerException("can only take address of a the float as constant literal or variable") + } + } + private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) { when { assignTarget.identifier != null -> { @@ -2066,6 +2082,9 @@ internal class Compiler(private val program: Program) { else if(target.datatype== DataType.FLOAT) { pushFloatAddress(addrof.identifier) } + else if(target.datatype == DataType.STRUCT) { + pushStructAddress(addrof.identifier) + } else throw CompilerException("cannot take memory pointer $addrof") } diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 0b1399ef6..21a2810e0 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -6,6 +6,7 @@ import prog8.ast.base.printWarning import prog8.ast.expressions.LiteralValue import prog8.ast.statements.StructDecl import prog8.ast.statements.VarDecl +import prog8.ast.statements.ZeropageWish import prog8.vm.RuntimeValue import prog8.compiler.CompilerException import prog8.compiler.HeapValues @@ -17,10 +18,12 @@ import java.nio.file.Path class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) { + data class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?) + class ProgramBlock(val name: String, var address: Int?, val instructions: MutableList = mutableListOf(), - val variables: MutableMap = mutableMapOf(), // names are fully scoped + val variables: MutableList> = mutableListOf(), // names are fully scoped val memoryPointers: MutableMap> = mutableMapOf(), val labels: MutableMap = mutableMapOf(), // names are fully scoped val force_output: Boolean) @@ -29,7 +32,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap get() { return variables.size } val numInstructions: Int get() { return instructions.filter { it.opcode!= Opcode.LINE }.size } - val variablesMarkedForZeropage: MutableSet = mutableSetOf() + val variablesMarkedForZeropage: MutableSet = mutableSetOf() // TODO maybe this can be removed now we have ValueParameters } val allocatedZeropageVariables = mutableMapOf>() @@ -46,14 +49,16 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap // allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP) var notAllocated = 0 for(block in blocks) { - val zpVariables = block.variables.filter { it.key in block.variablesMarkedForZeropage } + val zpVariables = block.variables.filter { it.first in block.variablesMarkedForZeropage } if (zpVariables.isNotEmpty()) { - for (variable in zpVariables) { + for ((varname, value, varparams) in zpVariables) { + if(varparams.zp==ZeropageWish.NOT_IN_ZEROPAGE || varparams.memberOfStruct!=null) + throw CompilerException("zp conflict") try { - val address = zeropage.allocate(variable.key, variable.value.type, null) - allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type) + val address = zeropage.allocate(varname, value.type, null) + allocatedZeropageVariables[varname] = Pair(address, value.type) } catch (x: ZeropageDepletedError) { - printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}") + printWarning(x.toString() + " variable $varname type ${value.type}") notAllocated++ } } @@ -399,6 +404,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap if(decl.parent is StructDecl) return + val valueparams = VariableParameters(decl.zeropage, decl.struct) val value = when(decl.datatype) { in NumericDatatypes -> { RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!) @@ -421,9 +427,11 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap } else -> throw CompilerException("weird datatype") } - currentBlock.variables[scopedname] = value - if(decl.zeropage) + currentBlock.variables.add(Triple(scopedname, value, valueparams)) + if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) currentBlock.variablesMarkedForZeropage.add(scopedname) + else if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE) + TODO("REQUIRE_ZEROPAGE not yet implemented") } VarDeclType.MEMORY -> { // note that constants are all folded away, but assembly code may still refer to them @@ -513,9 +521,11 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}") out.println("%variables") - for (variable in blk.variables) { - val valuestr = variable.value.toString() - out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr") + for ((vname, value, parameters) in blk.variables) { + if(parameters.zp==ZeropageWish.REQUIRE_ZEROPAGE) + throw CompilerException("zp conflict") + val valuestr = value.toString() + out.println("$vname ${value.type.name.toLowerCase()} $valuestr") } out.println("%end_variables") out.println("%memorypointers") diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index 4d0ac055b..927761178 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -6,6 +6,7 @@ package prog8.compiler.target.c64 import prog8.ast.antlr.escape import prog8.ast.base.DataType import prog8.ast.base.initvarsSubName +import prog8.ast.statements.ZeropageWish import prog8.vm.RuntimeValue import prog8.compiler.* import prog8.compiler.intermediate.* @@ -44,7 +45,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter // Convert invalid label names (such as "") to something that's allowed. val newblocks = mutableListOf() for(block in program.blocks) { - val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() + val newvars = block.variables.map { Triple(symname(it.first, block), it.second, it.third) }.toMutableList() val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet() val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap() val newinstructions = block.instructions.asSequence().map { @@ -236,18 +237,20 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } // deal with zeropage variables - for(variable in blk.variables) { - val sym = symname(blk.name+"."+variable.key, null) + for((varname, value, parameters) in blk.variables) { + val sym = symname(blk.name+"."+varname, null) val zpVar = program.allocatedZeropageVariables[sym] if(zpVar==null) { // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) - if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) { + if(parameters.zp != ZeropageWish.NOT_IN_ZEROPAGE && + value.type in zeropage.allowedDatatypes + && value.type != DataType.FLOAT) { try { - val address = zeropage.allocate(sym, variable.value.type, null) - out("${variable.key} = $address\t; auto zp ${variable.value.type}") + val address = zeropage.allocate(sym, value.type, null) + out("$varname = $address\t; auto zp ${value.type}") // make sure we add the var to the set of zpvars for this block - blk.variablesMarkedForZeropage.add(variable.key) - program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type) + blk.variablesMarkedForZeropage.add(varname) + program.allocatedZeropageVariables[sym] = Pair(address, value.type) } catch (x: ZeropageDepletedError) { // leave it as it is. } @@ -255,7 +258,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } else { // it was already allocated on the zp - out("${variable.key} = ${zpVar.first}\t; zp ${zpVar.second}") + out("$varname = ${zpVar.first}\t; zp ${zpVar.second}") } } @@ -289,79 +292,95 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) { - // these are the non-zeropage variables - val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type } - for (v in sortedVars) { - when (v.second.type) { - DataType.UBYTE -> out("${v.first}\t.byte 0") - DataType.BYTE -> out("${v.first}\t.char 0") - DataType.UWORD -> out("${v.first}\t.word 0") - DataType.WORD -> out("${v.first}\t.sint 0") - DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float") - DataType.STR, DataType.STR_S -> { - val rawStr = heap.get(v.second.heapId!!).str!! - val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } - out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "")}\"") - for (chunk in bytes.chunked(16)) - out(" .byte " + chunk.joinToString()) - } - DataType.ARRAY_UB -> { - // unsigned integer byte arraysize - val data = makeArrayFillDataUnsigned(v.second) - if (data.size <= 16) - out("${v.first}\t.byte ${data.joinToString()}") - else { - out(v.first) - for (chunk in data.chunked(16)) - out(" .byte " + chunk.joinToString()) - } - } - DataType.ARRAY_B -> { - // signed integer byte arraysize - val data = makeArrayFillDataSigned(v.second) - if (data.size <= 16) - out("${v.first}\t.char ${data.joinToString()}") - else { - out(v.first) - for (chunk in data.chunked(16)) - out(" .char " + chunk.joinToString()) - } - } - DataType.ARRAY_UW -> { - // unsigned word arraysize - val data = makeArrayFillDataUnsigned(v.second) - if (data.size <= 16) - out("${v.first}\t.word ${data.joinToString()}") - else { - out(v.first) - for (chunk in data.chunked(16)) - out(" .word " + chunk.joinToString()) - } - } - DataType.ARRAY_W -> { - // signed word arraysize - val data = makeArrayFillDataSigned(v.second) - if (data.size <= 16) - out("${v.first}\t.sint ${data.joinToString()}") - else { - out(v.first) - for (chunk in data.chunked(16)) - out(" .sint " + chunk.joinToString()) - } - } - DataType.ARRAY_F -> { - // float arraysize - val array = heap.get(v.second.heapId!!).doubleArray!! - val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) } - out(v.first) - for(f in array.zip(floatFills)) - out(" .byte ${f.second} ; float ${f.first}") - } - DataType.STRUCT -> TODO("datatype struct") - } + val uniqueNames = block.variables.map { it.first }.toSet() + if (uniqueNames.size != block.variables.size) + throw AssemblyError("not all variables have unique names") + + // these are the non-zeropage variables. + // first get all the flattened struct members, they MUST remain in order + val (structMembers, normalVars) = block.variables.partition { it.third.memberOfStruct!=null } + structMembers.forEach { vardecl2asm(it.first, it.second, it.third) } + + // leave outsort the other variables by type + val sortedVars = normalVars.sortedBy { it.second.type } + for ((varname, value, parameters) in sortedVars) { + if(varname in block.variablesMarkedForZeropage) + continue // skip the ones that belong in the zero page + vardecl2asm(varname, value, parameters) } } + private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) { + when (value.type) { + DataType.UBYTE -> out("$varname\t.byte 0") + DataType.BYTE -> out("$varname\t.char 0") + DataType.UWORD -> out("$varname\t.word 0") + DataType.WORD -> out("$varname\t.sint 0") + DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float") + DataType.STR, DataType.STR_S -> { + val rawStr = heap.get(value.heapId!!).str!! + val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') } + out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "")}\"") + for (chunk in bytes.chunked(16)) + out(" .byte " + chunk.joinToString()) + } + DataType.ARRAY_UB -> { + // unsigned integer byte arraysize + val data = makeArrayFillDataUnsigned(value) + if (data.size <= 16) + out("$varname\t.byte ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .byte " + chunk.joinToString()) + } + } + DataType.ARRAY_B -> { + // signed integer byte arraysize + val data = makeArrayFillDataSigned(value) + if (data.size <= 16) + out("$varname\t.char ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .char " + chunk.joinToString()) + } + } + DataType.ARRAY_UW -> { + // unsigned word arraysize + val data = makeArrayFillDataUnsigned(value) + if (data.size <= 16) + out("$varname\t.word ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .word " + chunk.joinToString()) + } + } + DataType.ARRAY_W -> { + // signed word arraysize + val data = makeArrayFillDataSigned(value) + if (data.size <= 16) + out("$varname\t.sint ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .sint " + chunk.joinToString()) + } + } + DataType.ARRAY_F -> { + // float arraysize + val array = heap.get(value.heapId!!).doubleArray!! + val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) } + out(varname) + for(f in array.zip(floatFills)) + out(" .byte ${f.second} ; float ${f.first}") + } + DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened") + } + } + + private fun encodeStr(str: String, dt: DataType): List { return when(dt) { DataType.STR -> { diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index e56dcde2a..cc292961f 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -650,7 +650,7 @@ internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor { nop.parent = namescope as Node namescope.statements[idx] = nop namescope.statements.addAll(idx, scope.statements) - scope.statements.forEach { it.parent = namescope as Node } + scope.statements.forEach { it.parent = namescope } visit(nop) } } diff --git a/compiler/src/prog8/vm/RuntimeValue.kt b/compiler/src/prog8/vm/RuntimeValue.kt index 4ce740125..89128f2e2 100644 --- a/compiler/src/prog8/vm/RuntimeValue.kt +++ b/compiler/src/prog8/vm/RuntimeValue.kt @@ -2,6 +2,8 @@ package prog8.vm import prog8.ast.base.* import prog8.ast.expressions.LiteralValue +import prog8.ast.statements.StructDecl +import prog8.ast.statements.ZeropageWish import prog8.compiler.HeapValues import prog8.compiler.target.c64.Petscii import kotlin.math.abs @@ -13,7 +15,8 @@ import kotlin.math.pow * this runtime value can be used to *execute* the parsed Ast (or another intermediary form) * It contains a value of a variable during run time of the program and provides arithmetic operations on the value. */ -open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array?=null, val heapId: Int?=null) { +open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, + val array: Array?=null, val heapId: Int?=null) { val byteval: Short? val wordval: Int? @@ -45,8 +48,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?= if(elt.value.integer!=null) resultArray.add(elt.value.integer!!) else { - println("ADDRESSOF ${elt.value}") - resultArray.add(0x8000) + TODO("ADDRESSOF ${elt.value}") } } RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId) diff --git a/compiler/src/prog8/vm/astvm/Expressions.kt b/compiler/src/prog8/vm/astvm/Expressions.kt index 070cbfe11..960122656 100644 --- a/compiler/src/prog8/vm/astvm/Expressions.kt +++ b/compiler/src/prog8/vm/astvm/Expressions.kt @@ -3,6 +3,7 @@ package prog8.vm.astvm import prog8.ast.* import prog8.ast.base.ArrayElementTypes import prog8.ast.base.DataType +import prog8.ast.base.FatalAstException import prog8.ast.base.VarDeclType import prog8.ast.expressions.* import prog8.ast.statements.BuiltinFunctionStatementPlaceholder @@ -87,8 +88,14 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue { } is AddressOf -> { // we support: address of heap var -> the heap id - val heapId = expr.identifier.heapId(ctx.program.namespace) - return RuntimeValue(DataType.UWORD, heapId) + return try { + val heapId = expr.identifier.heapId(ctx.program.namespace) + RuntimeValue(DataType.UWORD, heapId) + } catch( f: FatalAstException) { + // fallback: use the hash of the name, so we have at least *a* value... + val address = expr.identifier.hashCode() and 65535 + RuntimeValue(DataType.UWORD, address) + } } is DirectMemoryRead -> { val address = evaluate(expr.addressExpression, ctx).wordval!! @@ -99,22 +106,21 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue { val scope = expr.definingScope() val variable = scope.lookup(expr.nameInSource, expr) if(variable is VarDecl) { - if(variable.type==VarDeclType.VAR) - return ctx.runtimeVars.get(variable.definingScope(), variable.name) - else if(variable.datatype==DataType.STRUCT) { - throw VmExecutionException("cannot process structs by-value. at ${expr.position}") - } - else { - val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name) - return when(variable.datatype) { - DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address)) - DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address)) - DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address)) - DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address)) - DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address)) - DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address)) - DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address)) - else -> throw VmExecutionException("unexpected datatype $variable") + when { + variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name) + variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}") + else -> { + val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name) + return when(variable.datatype) { + DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address)) + DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address)) + DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address)) + DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address)) + DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address)) + DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address)) + DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address)) + else -> throw VmExecutionException("unexpected datatype $variable") + } } } } else diff --git a/compiler/src/prog8/vm/astvm/VariablesCreator.kt b/compiler/src/prog8/vm/astvm/VariablesCreator.kt index 5c6dba7d3..b3fc4aed2 100644 --- a/compiler/src/prog8/vm/astvm/VariablesCreator.kt +++ b/compiler/src/prog8/vm/astvm/VariablesCreator.kt @@ -6,6 +6,7 @@ import prog8.ast.expressions.LiteralValue import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.statements.StructDecl import prog8.ast.statements.VarDecl +import prog8.ast.statements.ZeropageWish import prog8.compiler.HeapValues import prog8.vm.RuntimeValue @@ -18,11 +19,11 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0)) val globalpos = Position("<>", 0, 0, 0) - val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, null, + val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null, LiteralValue.optimalInteger(0, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos) - val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, null, + val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null, LiteralValue.optimalInteger(255, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos) - val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, null, + val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null, LiteralValue.optimalInteger(0, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos) vdA.linkParents(program.namespace) vdX.linkParents(program.namespace) diff --git a/compiler/src/prog8/vm/stackvm/StackVm.kt b/compiler/src/prog8/vm/stackvm/StackVm.kt index 9c7007cef..f7382feeb 100644 --- a/compiler/src/prog8/vm/stackvm/StackVm.kt +++ b/compiler/src/prog8/vm/stackvm/StackVm.kt @@ -1873,10 +1873,17 @@ class StackVm(private var traceOutputFile: String?) { Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code $ins") Opcode.INCLUDE_FILE -> throw VmExecutionException("stackVm doesn't support including a file $ins") Opcode.PUSH_ADDR_HEAPVAR -> { - val heapId = variables.getValue(ins.callLabel!!).heapId!! - if(heapId<0) - throw VmExecutionException("expected variable on heap") - evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator) + val variable = variables.getValue(ins.callLabel!!) + if(variable.heapId!=null) { + val heapId = variable.heapId + if (heapId < 0) + throw VmExecutionException("expected variable on heap") + evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator) + } else { + // hack: return hash of the name, so we have at least *a* value... + val addr = ins.callLabel.hashCode() and 65535 + evalstack.push(RuntimeValue(DataType.UWORD, addr)) + } } Opcode.CAST_UB_TO_B -> typecast(DataType.UBYTE, DataType.BYTE) Opcode.CAST_W_TO_B -> typecast(DataType.WORD, DataType.BYTE) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 5b2bce652..92dab40e8 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -284,9 +284,10 @@ use a scoped name to refer to them: ``structvariable.membername``. Structs are a bit limited in Prog8: you can only use numerical variables as member of a struct, so strings and arrays and other structs can not be part of a struct. Also, it is not possible to use a struct itself inside an array. - Structs are mainly syntactic sugar for repeated groups of vardecls -and assignments that belong together. +and assignments that belong together. However, *they are layed out +in sequence in memory as the members are defined* which may be useful +if you want to pass pointers around To create a variable of a struct type you need to define the struct itself, and then create a variable with it:: diff --git a/examples/test.p8 b/examples/test.p8 index dd3c1498b..69ac57716 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,28 +3,26 @@ ~ main { - Color blocklevelcolor - - sub start() { - - Color subcol - - A=msb(subcol.red) - for ubyte i in 10 to 20 { - ;A=subcol.red - ;A=blocklevelcolor.green - - ;subcol.blue = Y - ;blocklevelcolor.green=Y - A=msb(subcol.red) - } - return - } - struct Color { - ubyte red + uword red ubyte green ubyte blue } + sub start() { + + Color col_one + Color col_two + Color col_three + + col_one.red= 111 + col_two.blue= 222 + + c64scr.print_uwhex(1, &col_one) + c64scr.print_uwhex(1, &col_two) + c64scr.print_uwhex(1, &col_three) + + return + } + }