doc tweaks

This commit is contained in:
Irmen de Jong 2019-08-11 10:44:58 +02:00
parent 62a66d89c6
commit d499e40a4b
12 changed files with 26 additions and 124 deletions

View File

@ -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

View File

@ -34,7 +34,6 @@ private fun compileMain(args: Array<String>) {
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<String>) {
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<String>) {
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<String>) {
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")

View File

@ -240,7 +240,7 @@ class GlobalNamespace(val modules: List<Module>): 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

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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<Subroutine>()

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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)
}
}