From d499e40a4b83f7d9c0f0bdb8b6550fb922a2e0c1 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 11 Aug 2019 10:44:58 +0200 Subject: [PATCH] doc tweaks --- README.md | 2 +- compiler/src/prog8/CompilerMain.kt | 8 +-- compiler/src/prog8/ast/AstToplevel.kt | 2 +- .../prog8/ast/expressions/AstExpressions.kt | 2 +- .../src/prog8/ast/processing/AstChecker.kt | 9 ++- compiler/src/prog8/compiler/Main.kt | 4 +- compiler/src/prog8/optimizer/Extensions.kt | 4 +- .../src/prog8/optimizer/StatementOptimizer.kt | 69 +------------------ docs/source/building.rst | 2 +- docs/source/index.rst | 2 +- docs/source/todo.rst | 24 +------ examples/test.p8 | 22 ++---- 12 files changed, 26 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 219c164d0..87f7bf948 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ which aims to provide many conveniences over raw assembly code (even when using Rapid edit-compile-run-debug cycle: - use modern PC to work on -- quick compilation times (around a second, and less than 0.1 seconds when using the continuous compilation mode) +- quick compilation times (couple of seconds, and less than a second when using the continuous compilation mode) - option to automatically run the program in the Vice emulator - breakpoints, that let the Vice emulator drop into the monitor if execution hits them - source code labels automatically loaded in Vice emulator so it can show them in disassembly diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index c045547d8..25dc5d8a5 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -34,7 +34,6 @@ private fun compileMain(args: Array) { var moduleFile = "" var writeAssembly = true var optimize = true - var optimizeInlining = true var launchAstVm = false var watchMode = false for (arg in args) { @@ -46,8 +45,6 @@ private fun compileMain(args: Array) { writeAssembly = false else if(arg=="-noopt") optimize = false - else if(arg=="-nooptinline") - optimizeInlining = false else if(arg=="-avm") launchAstVm = true else if(arg=="-watch") @@ -69,7 +66,7 @@ private fun compileMain(args: Array) { println("Continuous watch mode active. Main module: $filepath") try { - val compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly) + val compilationResult = compileProgram(filepath, optimize, writeAssembly) println("Imported files (now watching:)") for (importedFile in compilationResult.importedFiles) { print(" ") @@ -97,7 +94,7 @@ private fun compileMain(args: Array) { val compilationResult: CompilationResult try { - compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly) + compilationResult = compileProgram(filepath, optimize, writeAssembly) if(!compilationResult.success) exitProcess(1) } catch (x: ParsingFailedError) { @@ -131,7 +128,6 @@ private fun usage() { System.err.println("Missing argument(s):") System.err.println(" [-noasm] don't create assembly code") System.err.println(" [-noopt] don't perform any optimizations") - System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations") System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation") System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation") System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation") diff --git a/compiler/src/prog8/ast/AstToplevel.kt b/compiler/src/prog8/ast/AstToplevel.kt index 692efb1ad..c864cb354 100644 --- a/compiler/src/prog8/ast/AstToplevel.kt +++ b/compiler/src/prog8/ast/AstToplevel.kt @@ -240,7 +240,7 @@ class GlobalNamespace(val modules: List): Node, INameScope { } } } - + // lookup something from the module. val stmt = localContext.definingModule().lookup(scopedName, localContext) return when (stmt) { is Label, is VarDecl, is Block, is Subroutine -> stmt diff --git a/compiler/src/prog8/ast/expressions/AstExpressions.kt b/compiler/src/prog8/ast/expressions/AstExpressions.kt index e453cd840..c12202af5 100644 --- a/compiler/src/prog8/ast/expressions/AstExpressions.kt +++ b/compiler/src/prog8/ast/expressions/AstExpressions.kt @@ -28,7 +28,7 @@ sealed class Expression: Node { abstract fun constValue(program: Program): NumericLiteralValue? abstract fun accept(visitor: IAstModifyingVisitor): Expression abstract fun accept(visitor: IAstVisitor) - abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this here and move it into CallGraph instead + abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead abstract fun inferType(program: Program): DataType? infix fun isSameAs(other: Expression): Boolean { diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index 103e1e939..80067a414 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -391,7 +391,7 @@ internal class AstChecker(private val program: Program, val targetSymbol = program.namespace.lookup(targetName, assignment) when (targetSymbol) { null -> { - checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position)) + checkResult.add(UndefinedSymbolError(targetIdentifier)) return } !is VarDecl -> { @@ -406,6 +406,9 @@ internal class AstChecker(private val program: Program, } } } + val targetDt = assignTarget.inferType(program, assignment) + if(targetDt in StringDatatypes || targetDt in ArrayDatatypes) + checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position)) if (assignment is Assignment) { @@ -909,7 +912,7 @@ internal class AstChecker(private val program: Program, val targetName = postIncrDecr.target.identifier!!.nameInSource val target = program.namespace.lookup(targetName, postIncrDecr) if(target==null) { - checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position)) + checkResult.add(UndefinedSymbolError(postIncrDecr.target.identifier!!)) } else { if(target !is VarDecl || target.type== VarDeclType.CONST) { checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position)) @@ -920,7 +923,7 @@ internal class AstChecker(private val program: Program, } else if(postIncrDecr.target.arrayindexed != null) { val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace) if(target==null) { - checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position)) + checkResult.add(NameError("undefined symbol", postIncrDecr.position)) } else { val dt = (target as VarDecl).datatype diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index d329c3ea5..544033080 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -24,7 +24,7 @@ class CompilationResult(val success: Boolean, fun compileProgram(filepath: Path, - optimize: Boolean, optimizeInlining: Boolean, + optimize: Boolean, writeAssembly: Boolean): CompilationResult { lateinit var programAst: Program var programName: String? = null @@ -84,7 +84,7 @@ fun compileProgram(filepath: Path, while (true) { // keep optimizing expressions and statements until no more steps remain val optsDone1 = programAst.simplifyExpressions() - val optsDone2 = programAst.optimizeStatements(optimizeInlining) + val optsDone2 = programAst.optimizeStatements() if (optsDone1 + optsDone2 == 0) break } diff --git a/compiler/src/prog8/optimizer/Extensions.kt b/compiler/src/prog8/optimizer/Extensions.kt index 2e7cf0e4a..6270134e3 100644 --- a/compiler/src/prog8/optimizer/Extensions.kt +++ b/compiler/src/prog8/optimizer/Extensions.kt @@ -27,8 +27,8 @@ internal fun Program.constantFold() { } -internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int { - val optimizer = StatementOptimizer(this, optimizeInlining) +internal fun Program.optimizeStatements(): Int { + val optimizer = StatementOptimizer(this) optimizer.visit(this) modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index 61f11a229..3ae972b15 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -14,86 +14,23 @@ import kotlin.math.floor /* todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers) todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this + + TODO: proper inlining of small subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?) */ -internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor { +internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor { var optimizationsDone: Int = 0 private set private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } private val callgraph = CallGraph(program) - private var generatedLabelSequenceNumber = 0 override fun visit(program: Program) { removeUnusedCode(callgraph) - if(optimizeInlining) { - inlineSubroutines(callgraph) - } super.visit(program) } - private fun inlineSubroutines(callgraph: CallGraph) { - val entrypoint = program.entrypoint() - program.modules.forEach { - callgraph.forAllSubroutines(it) { sub -> - if(sub!==entrypoint && !sub.isAsmSubroutine) { - if (sub.statements.size <= 3 && !sub.expensiveToInline) { - sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) } - } else if (sub.calledBy.size==1 && sub.statements.size < 50) { - inlineSubroutine(sub, sub.calledBy[0]) - } else if(sub.calledBy.size<=3 && sub.statements.size < 10 && !sub.expensiveToInline) { - sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) } - } - } - } - } - } - - private fun inlineSubroutine(sub: Subroutine, caller: Node) { - // if the sub is called multiple times from the isSameAs scope, we can't inline (would result in duplicate definitions) - // (unless we add a sequence number to all vars/labels and references to them in the inlined code, but I skip that for now) - val scope = caller.definingScope() - if(sub.calledBy.count { it.definingScope()===scope } > 1) - return - if(caller !is IFunctionCall || caller !is Statement || sub.statements.any { it is Subroutine }) - return - - if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) { - // sub without params and without return value can be easily inlined - val parent = caller.parent as INameScope - val inlined = AnonymousScope(sub.statements.toMutableList(), caller.position) - parent.statements[parent.statements.indexOf(caller)] = inlined - // replace return statements in the inlined sub by a jump to the end of it - var haveNewEndLabel = false - var endLabelUsed = false - var endlabel = inlined.statements.last() as? Label - if(endlabel==null) { - endlabel = makeLabel("_prog8_auto_sub_end", inlined.statements.last().position) - endlabel.parent = inlined - haveNewEndLabel = true - } - val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)} - for(returnIdx in returns) { - val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position) - inlined.statements[returnIdx.first] = jump - endLabelUsed = true - } - if(endLabelUsed && haveNewEndLabel) - inlined.statements.add(endlabel) - inlined.linkParents(caller.parent) - sub.calledBy.remove(caller) // if there are no callers left, the sub will be removed automatically later - optimizationsDone++ - } else { - // TODO inline subroutine that has params or returnvalues or both - } - } - - private fun makeLabel(name: String, position: Position): Label { - generatedLabelSequenceNumber++ - return Label("${name}_$generatedLabelSequenceNumber", position) - } - private fun removeUnusedCode(callgraph: CallGraph) { // remove all subroutines that aren't called, or are empty val removeSubroutines = mutableSetOf() diff --git a/docs/source/building.rst b/docs/source/building.rst index 3d4b981ef..40dd5b28b 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -89,7 +89,7 @@ a successful compilation. This will load your program and the symbol and breakpo Continuous compilation mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Almost instant compilation times (<0.1 second) can be achieved when using the continuous compilation mode. +Almost instant compilation times (less than a second) can be achieved when using the continuous compilation mode. Start the compiler with the ``-watch`` argument to enable this. It will compile your program and then instead of exiting, it waits for any changes in the module source files. As soon as a change happens, the program gets compiled again. diff --git a/docs/source/index.rst b/docs/source/index.rst index b91d0dbc8..e08348e75 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -151,7 +151,7 @@ Design principles and features the compiled program in an emulator and provide debugging information to the emulator. - The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself. The (separate) '64tass' cross-assembler tool is used for that. -- Goto is usually considered harmful, but not here: arbitrary control flow jumps and branches are possible, +- Arbitrary control flow jumps and branches are possible, and will usually translate directly into the appropriate single 6502 jump/branch instruction. - There are no complicated built-in error handling or overflow checks, you'll have to take care of this yourself if required. This keeps the language and code simple and efficient. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 0128e9efa..357341067 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -2,28 +2,6 @@ TODO ==== - -Fixes -^^^^^ -variable naming issue:: - - main { - - sub start() { - for A in 0 to 10 { - ubyte note1 = 44 - Y+=note1 - } - delay(1) - - sub delay(ubyte note1) { ; TODO: redef of note1 above, conflicts because that one was moved to the zeropage - A= note1 - } - } - } - - - Memory Block Operations integrated in language? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +27,6 @@ More optimizations Add more compiler optimizations to the existing ones. - on the language AST level -- on the StackVM intermediate code level - on the final assembly source level - can the parameter passing to subroutines be optimized to avoid copying? @@ -57,6 +34,7 @@ Add more compiler optimizations to the existing ones. this requires rethinking the way parameters are represented, simply injecting vardecls to declare local variables for them is not always correct anymore +- working subroutine inlining (taking care of vars and identifier refs to them) Also some library routines and code patterns could perhaps be optimized further diff --git a/examples/test.p8 b/examples/test.p8 index b208e9e01..fcceb571b 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,28 +3,16 @@ main { - str title="bla" - struct Color { - ubyte red - ubyte green - ubyte blue - } - sub start() { - str subtitle = "basdf" - Color rgb - derp.dop() - uword zz = &title - zz=&main.title - zz=&subtitle - zz=&main.start.subtitle + A=derp.dop.zzz -; uword addr = &derp.dop.name ; @todo strange error "pointer-of operand must be the name of a heap variable" -; c64scr.print(&derp.dop.name) + derp.dop.zzz=3 - zz=&rgb + + uword addr = &derp.dop.name ; @todo strange error "pointer-of operand must be the name of a heap variable" + c64scr.print(&derp.dop.name) } }