diff --git a/compiler/src/prog8/optimizer/CallGraph.kt b/compiler/src/prog8/optimizer/CallGraph.kt index 31b639168..b417de0d4 100644 --- a/compiler/src/prog8/optimizer/CallGraph.kt +++ b/compiler/src/prog8/optimizer/CallGraph.kt @@ -174,7 +174,7 @@ class CallGraph(private val program: Program) : IAstVisitor { } fun unused(decl: VarDecl): Boolean { - if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove) + if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm) return false if(decl.definingBlock() !in usedBlocks) diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index 2a8372d83..413b6e2fd 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -30,6 +30,7 @@ internal class StatementOptimizer(private val program: Program, val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null, isArray = false, autogeneratedDontRemove = true, + sharedWithAsm = false, position = returnvar.third ) returnvar.first.statements.add(0, decl) diff --git a/compiler/src/prog8/optimizer/UnusedCodeRemover.kt b/compiler/src/prog8/optimizer/UnusedCodeRemover.kt index 94700f61d..6493aa7ae 100644 --- a/compiler/src/prog8/optimizer/UnusedCodeRemover.kt +++ b/compiler/src/prog8/optimizer/UnusedCodeRemover.kt @@ -103,7 +103,7 @@ internal class UnusedCodeRemover(private val program: Program, override fun after(decl: VarDecl, parent: Node): Iterable { if(decl.type==VarDeclType.VAR) { val forceOutput = "force_output" in decl.definingBlock().options() - if (!forceOutput && !decl.autogeneratedDontRemove && !decl.definingBlock().isInLibrary) { + if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock().isInLibrary) { if (callgraph.unused(decl)) { errors.warn("removing unused variable '${decl.name}'", decl.position) return listOf(IAstModification.Remove(decl, decl.definingScope())) diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index 735842dbd..ee56170ba 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -502,7 +502,7 @@ class TestMemory { @Test private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget { - val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) + val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val memexpr = IdentifierReference(listOf("address"), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) @@ -522,7 +522,7 @@ class TestMemory { @Test fun testInValidRamC64_variable() { - val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY) + val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) @@ -535,7 +535,7 @@ class TestMemory { @Test fun testInValidRamC64_memmap_variable() { val address = 0x1000 - val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) + val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) @@ -548,7 +548,7 @@ class TestMemory { @Test fun testNotInValidRamC64_memmap_variable() { val address = 0xd020 - val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) + val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) @@ -560,7 +560,7 @@ class TestMemory { @Test fun testInValidRamC64_array() { - val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY) + val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) @@ -574,7 +574,7 @@ class TestMemory { @Test fun testInValidRamC64_array_memmapped() { val address = 0x1000 - val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) + val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) @@ -588,7 +588,7 @@ class TestMemory { @Test fun testNotValidRamC64_array_memmapped() { val address = 0xe000 - val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) + val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) diff --git a/compilerAst/src/prog8/ast/AstToplevel.kt b/compilerAst/src/prog8/ast/AstToplevel.kt index 913c43d69..3e53e2e3b 100644 --- a/compilerAst/src/prog8/ast/AstToplevel.kt +++ b/compilerAst/src/prog8/ast/AstToplevel.kt @@ -297,7 +297,7 @@ class Program(val name: String, val varName = "string_${internedStringsBlock.statements.size}" val decl = VarDecl( VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string, - isArray = false, autogeneratedDontRemove = true, position = string.position + isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position ) internedStringsBlock.statements.add(decl) decl.linkParents(internedStringsBlock) diff --git a/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt index 4d2d5d70e..efe9f81a7 100644 --- a/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt @@ -69,6 +69,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi it.expression().toAst(encoding), vd.ARRAYSIG() != null || vd.arrayindex() != null, false, + vd.SHARED()!=null, it.toPosition() ) } @@ -85,6 +86,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi cvarinit.expression().toAst(encoding), vd.ARRAYSIG() != null || vd.arrayindex() != null, false, + vd.SHARED() != null, cvarinit.toPosition() ) } @@ -101,6 +103,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi mvarinit.expression().toAst(encoding), vd.ARRAYSIG() != null || vd.arrayindex() != null, false, + vd.SHARED()!=null, mvarinit.toPosition() ) } @@ -603,6 +606,7 @@ private fun prog8Parser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl null, ARRAYSIG() != null || arrayindex() != null, false, + SHARED()!=null, toPosition() ) } diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 4bdd35734..38a6e95b2 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -164,6 +164,7 @@ open class VarDecl(val type: VarDeclType, var value: Expression?, val isArray: Boolean, val autogeneratedDontRemove: Boolean, + val sharedWithAsm: Boolean, override val position: Position) : Statement(), ISymbolStatement { override lateinit var parent: Node var allowInitializeWithZero = true @@ -183,7 +184,7 @@ open class VarDecl(val type: VarDeclType, val declaredType = ArrayToElementTypes.getValue(arrayDt) val arraysize = ArrayIndex.forArray(array) return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array, - isArray = true, autogeneratedDontRemove = true, position = array.position) + isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, position = array.position) } fun defaultZero(dt: DataType, position: Position) = when(dt) { @@ -239,7 +240,7 @@ open class VarDecl(val type: VarDeclType, } fun copy(): VarDecl { - val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, position) + val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, sharedWithAsm, position) c.allowInitializeWithZero = this.allowInitializeWithZero return c } @@ -247,7 +248,7 @@ open class VarDecl(val type: VarDeclType, // a vardecl used only for subroutine parameters class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position) - : VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, position) + : VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, false, position) class ArrayIndex(var indexExpr: Expression, override val position: Position) : Node { diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 795b1af1f..f3757d2d9 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -202,6 +202,16 @@ Example:: byte @zp zeropageCounter = 42 +*shared tag:* +If you add the ``@shared`` tag to the variable declaration, the compiler will know that this variable +is a prog8 variable shared with some assembly code elsewhere. This means that the assembly code can +refer to the variable even if it's otherwise not used in prog8 code itself. +(usually, these kinds of 'unused' variables are optimized away by the compiler, resulting in an error +when assembling the rest of the code). Example:: + + byte @shared assemblyVariable = 42 + + Integers ^^^^^^^^ diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 80e971b71..6b8beb804 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -235,9 +235,11 @@ for them. You can give them an initial value as well. That value can be a simple or an expression. If you don't provide an intial value yourself, zero will be used. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it when selecting variables to be put into zeropage. +You can add a ``@shared`` shared-tag, to tell the compiler that the variable is shared +with some assembly code and that it should not be optimized away if not used elsewhere. The syntax is:: - [ @zp ] [ = ] + [ @shared ] [ @zp ] [ = ] Various examples:: @@ -252,7 +254,8 @@ Various examples:: byte[5] values ; array of 5 bytes, initially set to zero byte[5] values = 255 ; initialize with five 255 bytes - word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage + word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage + word @shared asmvar ; variable is used in assembly code but not elsewhere Data types diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 2561ff86d..79ffd5b09 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -2,8 +2,7 @@ TODO ==== -- possible idea: option to mark vardecls 'shared' to indicate they should not be optimized away because they're shared with assembly code? - However: who even needs variables declared in prog8 code that are only used by assembly??? +- should give error when passing invalid command line arguments - test all examples (including imgviewer, assembler and petaxian) before release of the new version diff --git a/examples/test.p8 b/examples/test.p8 index f8de245d8..4d306a0b9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,12 +4,31 @@ main { sub start() { + ubyte @shared xx=99 + uword @shared asmvar + %asm {{ + inc xx + lda asmvar + ldy asmvar+1 + }} + txt.nl() + str string1 = "stringvalue\n" str string2 = "stringvalue\n" str string3 = "stringvalue\n" str string4 = "a" str string5 = "bb" + txt.print(string1) + txt.print(string2) + txt.print(string3) + string1[1]='?' + string2[2] = '?' + string3[3] = '?' + txt.print(string1) + txt.print(string2) + txt.print(string3) + txt.print("a") txt.print("a") txt.print(string4) @@ -18,9 +37,6 @@ main { txt.print(string5) txt.print("\n") txt.print("\n\n") - txt.print(string1) - txt.print(string2) - txt.print(string3) txt.print("hello\n") txt.print("hello\n") txt.print("hello\n") diff --git a/parser/antlr/prog8.g4 b/parser/antlr/prog8.g4 index 50f9ad19f..ba017e72b 100644 --- a/parser/antlr/prog8.g4 +++ b/parser/antlr/prog8.g4 @@ -62,11 +62,14 @@ ZEROPAGE : '@zp' ; +SHARED : + '@shared' + ; + ARRAYSIG : '[]' ; - cpuregister: 'A' | 'X' | 'Y'; register: 'A' | 'X' | 'Y' | 'AX' | 'AY' | 'XY' | 'Pc' | 'Pz' | 'Pn' | 'Pv' | 'R0' | 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12' | 'R13' | 'R14' | 'R15'; @@ -134,7 +137,7 @@ directive : directivearg : stringliteral | identifier | integerliteral ; -vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ; +vardecl: datatype SHARED? ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ; varinitializer : vardecl '=' expression ;