mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 01:29:28 +00:00
doc tweaks
This commit is contained in:
parent
62a66d89c6
commit
d499e40a4b
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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>()
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user