Compare commits

...
7 Commits
Author SHA1 Message Date
Irmen de Jong 065587525e version 2019-08-11 17:43:14 +02:00
Irmen de Jong 58e5d5c071 hash 2019-08-11 17:32:28 +02:00
Irmen de Jong b44e76db57 fix any/all assembly routine, added asm for min/max/sum/ etc aggregates
removed avg function because of hidden internal overflow issues
2019-08-11 16:13:09 +02:00
Irmen de Jong 2ce6bc5946 fix strlen 2019-08-11 14:02:53 +02:00
Irmen de Jong fe5b225732 asmsub stack arg 2019-08-11 12:29:18 +02:00
Irmen de Jong d499e40a4b doc tweaks 2019-08-11 10:56:36 +02:00
Irmen de Jong 62a66d89c6 was not needed 2019-08-11 10:15:34 +02:00
29 changed files with 317 additions and 227 deletions

No files matched your search

+1 -1
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
+4 -3
View File
@@ -35,7 +35,7 @@ init_system .proc
rts
.pend
read_byte_from_address .proc
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
lda c64.ESTACK_LO+1,x
@@ -45,7 +45,7 @@ read_byte_from_address .proc
+ lda $ffff ; modified
rts
.pend
add_a_to_zpword .proc
; -- add ubyte in A to the uword in c64.SCRATCH_ZPWORD1
@@ -851,11 +851,12 @@ func_all_w .proc
bne +
iny
lda (c64.SCRATCH_ZPWORD1),y
bne +
bne ++
lda #0
sta c64.ESTACK_LO+1,x
rts
+ iny
+ iny
_cmp_mod cpy #255 ; modified
bne -
lda #1
+1 -1
View File
@@ -1 +1 @@
1.50
1.51
+2 -6
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")
+1 -1
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
+3 -3
View File
@@ -10,13 +10,13 @@ class SyntaxError(override var message: String, val position: Position) : AstExc
override fun toString() = "$position Syntax error: $message"
}
class NameError(override var message: String, val position: Position) : AstException(message) {
open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
}
open class ExpressionError(message: String, val position: Position) : AstException(message) {
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
@@ -18,6 +18,7 @@ import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.*
import kotlin.math.abs
@@ -28,7 +29,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 {
@@ -240,7 +241,6 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
identifier.parent=this
}
var scopedname: String? = null // will be set in a later state by the compiler // TODO get rid of this??
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD
@@ -317,7 +317,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun inferType(program: Program) = type
override fun hashCode(): Int = type.hashCode() * 31 xor number.hashCode()
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteralValue)
@@ -327,7 +327,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue? {
fun cast(targettype: DataType): NumericLiteralValue {
if(type==targettype)
return this
val numval = number.toDouble()
@@ -382,7 +382,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
else -> {}
}
return null // invalid type conversion from $this to $targettype
throw ExpressionError("can't cast $type into $targettype", position)
}
}
@@ -458,11 +458,7 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
override fun inferType(program: Program) = type
override fun hashCode(): Int {
val sh = str?.hashCode() ?: 0x00014567
val ah = array?.hashCode() ?: 0x11119876
return sh * 31 xor ah xor type.hashCode()
}
override fun hashCode(): Int = Objects.hash(str, array, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is ReferenceLiteralValue)
@@ -501,7 +497,7 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
throw FatalAstException("weird array element $it")
it
} else {
num.cast(elementType)!!
num.cast(elementType) // TODO this can throw an exception
}
}.toTypedArray()
return ReferenceLiteralValue(targettype, null, array=castArray, position = position)
+23 -14
View File
@@ -317,14 +317,11 @@ internal class AstChecker(private val program: Program,
err("carry parameter has to come last")
} else {
// TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays) Maybe this can be improved now that we have '&' ?
// the way string params are treated is almost okay (their address is passed) but the receiving subroutine treats it as an integer rather than referring back to the original string.
// the way array params are treated is buggy; it thinks the subroutine needs a byte parameter in place of a byte[] ...
// This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap)
// while passing them as subroutine parameters would require a "real" pointer OR copying the VALUE to the subroutine's parameter variable (which is very inefficient).
// For now, don't pass strings and arrays as parameters and instead create the workaround as suggested in the error message below.
if(!subroutine.parameters.all{it.type in NumericDatatypes }) {
err("Non-asm subroutine can only take numerical parameters (no str/array types) for now. Workaround (for nested subroutine): access the variable from the outer scope directly.")
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
// The language has no typed pointers at this time.
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
}
}
}
@@ -391,7 +388,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 +403,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) {
@@ -442,8 +442,6 @@ internal class AstChecker(private val program: Program,
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes && variable.datatype!=DataType.STRUCT)
checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position))
}
if(addressOf.scopedname==null)
throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf")
super.visit(addressOf)
}
@@ -850,9 +848,12 @@ internal class AstChecker(private val program: Program,
if(args.size!=func.parameters.size)
checkResult.add(SyntaxError("invalid number of arguments", position))
else {
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.possibleDatatypes)) {
if (argDt != null
&& !(argDt isAssignableTo arg.second.possibleDatatypes)
&& (argDt != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
}
}
@@ -869,6 +870,14 @@ internal class AstChecker(private val program: Program,
else if(dt1 !in NumericDatatypes)
checkResult.add(ExpressionError("swap requires args of numerical type", position))
}
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype in StringDatatypes) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
}
if(args[0].inferType(program) in StringDatatypes) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
}
}
}
} else if(target is Subroutine) {
if(args.size!=target.parameters.size)
@@ -911,7 +920,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))
@@ -922,7 +931,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
@@ -237,8 +237,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
val newValue: Expression
val lval = returnStmt.value as? NumericLiteralValue
if(lval!=null) {
val adjusted = lval.cast(subroutine.returntypes.single())
newValue = if(adjusted!=null && adjusted !== lval) adjusted else lval
newValue = lval.cast(subroutine.returntypes.single())
} else {
newValue = returnStmt.value!!
}
@@ -316,7 +315,6 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(addressOf: AddressOf): Expression {
// register the scoped name of the referenced identifier
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf
addressOf.scopedname = variable.scopedname
return super.visit(addressOf)
}
@@ -382,7 +380,14 @@ internal fun fixupArrayDatatype(array: ReferenceLiteralValue, vardecl: VarDecl,
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val litval2 = array.cast(vardecl.datatype)!!
val litval2 =
try {
array.cast(vardecl.datatype)!!
} catch(x: ExpressionError) {
// couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error
ReferenceLiteralValue(vardecl.datatype, null, array.array, array.heapId, array.position)
}
vardecl.value = litval2
litval2.linkParents(vardecl)
litval2.addToHeap(heap) // TODO is the previous array discarded from the resulting asm code?
@@ -319,7 +319,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
}
null -> {}
else -> TODO("call to something weird $sub ${call.target}")
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
@@ -337,7 +337,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
if(dt!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
@@ -351,7 +351,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
@@ -57,10 +57,8 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is NumericLiteralValue) {
val converted = declvalue.cast(decl.datatype)
converted ?: declvalue
}
if(declvalue is NumericLiteralValue)
declvalue.cast(decl.datatype)
else
declvalue
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
@@ -114,7 +112,6 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
@@ -127,7 +124,6 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
// replace the argument with &autovar
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(variable.name)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
@@ -137,15 +133,15 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(DataType.UWORD in arg.second.possibleDatatypes && argDt in PassByReferenceDatatypes) {
if(argDt in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position)
args[arg.first.index] = addrOf
addrOf.scopedname = parent.makeScopedName(argvalue.nameInSource.single())
addrOf.linkParents(parent)
}
}
+1 -7
View File
@@ -83,13 +83,7 @@ class HeapValues {
return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray)
}
override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + (str?.hashCode() ?: 0)
result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0)
result = 31 * result + (doubleArray?.let { Arrays.hashCode(it) } ?: 0)
return result
}
override fun hashCode(): Int = Objects.hash(str, array, doubleArray)
val arraysize: Int = array?.size ?: doubleArray?.size ?: 0
}
+3 -3
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
}
@@ -94,7 +94,7 @@ fun compileProgram(filepath: Path,
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
printAst(programAst)
// printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
@@ -662,35 +662,35 @@ internal class AsmGen2(val program: Program,
out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
fun translateSubroutineArgument(arg: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
fun translateSubroutineArgument(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceDt = value.inferType(program)!!
if(!argumentTypeCompatible(sourceDt, arg.value.type))
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass arg via a variable
val paramVar = arg.value
// pass parameter via a variable
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(arg.value.type) {
when(parameter.value.type) {
in ByteDatatypes -> assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird arg datatype")
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (arg.value.type) {
when (parameter.value.type) {
in ByteDatatypes -> assignFromByteVariable(target, value)
in WordDatatypes -> assignFromWordVariable(target, value)
DataType.FLOAT -> assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird arg datatype")
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
@@ -718,13 +718,17 @@ internal class AsmGen2(val program: Program,
}
}
} else {
// pass arg via a register parameter
val paramRegister = sub.asmParameterRegisters[arg.index]
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack==true -> TODO("param on stack")
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
@@ -1673,8 +1677,7 @@ $endLabel""")
}
is AddressOf -> {
val identifier = (assign.value as AddressOf).identifier
val scopedname = (assign.value as AddressOf).scopedname!!
assignFromAddressOf(assign.target, identifier, scopedname)
assignFromAddressOf(assign.target, identifier)
}
is DirectMemoryRead -> {
val read = (assign.value as DirectMemoryRead)
@@ -2235,7 +2238,7 @@ $endLabel""")
}
}
private fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference, scopedname: String) {
private fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val struct = name.memberOfStruct(program.namespace)
@@ -2248,8 +2251,9 @@ $endLabel""")
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
firstVar.name
} else {
fixNameSymbols(scopedname)
fixNameSymbols(name.nameInSource.joinToString ("."))
}
when {
targetIdent!=null -> {
val targetName = asmIdentifierName(targetIdent)
@@ -54,7 +54,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++
}
// TODO more assembly optimizations?
// TODO more assembly optimizations
return numberOfOptimizations
}
@@ -101,7 +101,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and work on opcodes, and never even create the inefficient asm...
// @todo a better place to do this is in the Compiler instead and transform the Ast, and never even create the inefficient asm in the first place...
val removeLines = mutableListOf<Int>()
for (pair in linesByFourteen) {
@@ -11,6 +11,7 @@ import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
@@ -79,7 +80,32 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.assignFromEvalResult(firstTarget)
asmgen.assignFromEvalResult(secondTarget)
}
// TODO: any(f), all(f), max(f), min(f), sum(f)
"strlen" -> {
outputPushAddressOfIdentifier(fcall.arglist[0])
asmgen.out(" jsr prog8_lib.func_strlen")
}
"min", "max", "sum" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!!
when(dt) {
DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"any", "all" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!!
when(dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
@@ -242,6 +268,33 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference
val identifierName = asmgen.asmIdentifierName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!!
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex
lda #$size
sta $ESTACK_LO_HEX,x
dex
""")
}
private fun outputPushAddressOfIdentifier(arg: Expression) {
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex
""")
}
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
args.forEach {
asmgen.translateExpression(it)
@@ -52,7 +52,6 @@ val BuiltinFunctions = mapOf(
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT) { a, p, _ -> collectionArgNeverConst(a, p) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
+13 -18
View File
@@ -9,6 +9,7 @@ import prog8.ast.processing.fixupArrayDatatype
import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.codegen2.AssemblyError
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
@@ -174,7 +175,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
copy.parent = identifier.parent
copy
}
cval.type in PassByReferenceDatatypes -> TODO("ref type $identifier")
cval.type in PassByReferenceDatatypes -> throw AssemblyError("pass-by-reference type should not be considered a constant")
else -> identifier
}
} catch (ax: AstException) {
@@ -209,11 +210,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first())
if(convertedValue!=null) {
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
val convertedValue = argConst.cast(possibleDts.first()) // TODO can throw exception
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
return
@@ -227,11 +226,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt)
if(convertedValue!=null) {
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
val convertedValue = argConst.cast(expectedDt) // TODO can throw exception
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
@@ -315,7 +312,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue)
TODO("binexpr with reference litval")
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@@ -547,14 +544,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
// TODO casts can throw exception
val newFrom = rangeFrom.cast(targetDt)
val newTo = rangeTo.cast(targetDt)
if (newFrom != null && newTo != null) {
val newStep: Expression =
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
return range
val newStep: Expression =
stepLiteral?.cast(targetDt) ?: range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
+2 -2
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
@@ -43,7 +43,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val literal = tc.expression as? NumericLiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!=null && newLiteral!==literal) {
if(newLiteral!==literal) {
optimizationsDone++
return newLiteral
}
@@ -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>()
@@ -170,13 +107,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(subroutine.canBeAsmSubroutine) {
optimizationsDone++
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
// TODO fix parameter passing so this also works:
// asmsub aa(byte arg @ Y) -> clobbers() -> () {
// byte local = arg ; @todo fix 'undefined symbol arg' by some sort of alias name for the parameter
// A=44
// }
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
+2 -6
View File
@@ -5,6 +5,7 @@ import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import java.util.*
import kotlin.math.abs
import kotlin.math.pow
@@ -154,12 +155,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
}
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue)
-4
View File
@@ -845,10 +845,6 @@ class AstVm(val program: Program) {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
}
"avg" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.single().array!!.map { it.toDouble() }.sum()
when (args[0].type) {
+1 -1
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.
+1 -1
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.
-3
View File
@@ -707,9 +707,6 @@ max(x)
min(x)
Minimum of the values in the array value x
avg(x)
Average of the values in the array value x
sum(x)
Sum of the values in the array value x
+10 -23
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?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -41,6 +19,15 @@ these should call optimized pieces of assembly code, so they run as fast as poss
For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
Fixes
^^^^^
fix asmsub parameters so this works::
asmsub aa(byte arg @ Y) -> clobbers() -> () {
byte local = arg ; @todo fix 'undefined symbol arg' that occurs here
A=44
}
More optimizations
@@ -49,7 +36,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 +43,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
+110
View File
@@ -0,0 +1,110 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage dontuse
main {
sub start() {
ubyte[] ubarr = [100, 0, 99, 199, 22]
byte[] barr = [-100, 0, 99, -122, 22]
uword[] uwarr = [1000, 0, 222, 4444, 999]
word[] warr = [-1000, 0, 999, -4444, 222]
float[] farr = [-1000.1, 0, 999.9, -4444.4, 222.2]
str name = "irmen"
ubyte ub
byte bb
word ww
uword uw
float ff
; LEN/STRLEN
ubyte length = len(name)
if length!=5 c64scr.print("error len1\n")
length = len(uwarr)
if length!=5 c64scr.print("error len2\n")
length=strlen(name)
if length!=5 c64scr.print("error strlen1\n")
name[3] = 0
length=strlen(name)
if length!=3 c64scr.print("error strlen2\n")
; MAX
ub = max(ubarr)
if ub!=199 c64scr.print("error max1\n")
bb = max(barr)
if bb!=99 c64scr.print("error max2\n")
uw = max(uwarr)
if uw!=4444 c64scr.print("error max3\n")
ww = max(warr)
if ww!=999 c64scr.print("error max4\n")
ff = max(farr)
if ff!=999.9 c64scr.print("error max5\n")
; MIN
ub = min(ubarr)
if ub!=0 c64scr.print("error min1\n")
bb = min(barr)
if bb!=-122 c64scr.print("error min2\n")
uw = min(uwarr)
if uw!=0 c64scr.print("error min3\n")
ww = min(warr)
if ww!=-4444 c64scr.print("error min4\n")
ff = min(farr)
if ff!=-4444.4 c64scr.print("error min5\n")
; SUM
uw = sum(ubarr)
if uw!=420 c64scr.print("error sum1\n")
ww = sum(barr)
if ww!=-101 c64scr.print("error sum2\n")
uw = sum(uwarr)
if uw!=6665 c64scr.print("error sum3\n")
ww = sum(warr)
if ww!=-4223 c64scr.print("error sum4\n")
ff = sum(farr)
if ff!=-4222.4 c64scr.print("error sum5\n")
; ANY
ub = any(ubarr)
if ub==0 c64scr.print("error any1\n")
ub = any(barr)
if ub==0 c64scr.print("error any2\n")
ub = any(uwarr)
if ub==0 c64scr.print("error any3\n")
ub = any(warr)
if ub==0 c64scr.print("error any4\n")
ub = any(farr)
if ub==0 c64scr.print("error any5\n")
; ALL
ub = all(ubarr)
if ub==1 c64scr.print("error all1\n")
ub = all(barr)
if ub==1 c64scr.print("error all2\n")
ub = all(uwarr)
if ub==1 c64scr.print("error all3\n")
ub = all(warr)
if ub==1 c64scr.print("error all4\n")
ub = all(farr)
if ub==1 c64scr.print("error all5\n")
ubarr[1]=$40
barr[1]=$40
uwarr[1]=$4000
warr[1]=$4000
farr[1]=1.1
ub = all(ubarr)
if ub==0 c64scr.print("error all6\n")
ub = all(barr)
if ub==0 c64scr.print("error all7\n")
ub = all(uwarr)
if ub==0 c64scr.print("error all8\n")
ub = all(warr)
if ub==0 c64scr.print("error all9\n")
ub = all(farr)
if ub==0 c64scr.print("error all10\n")
c64scr.print("\nyou should see no errors above.")
}
}
+37 -11
View File
@@ -1,21 +1,47 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage dontuse
main {
sub start() {
byte[] barr = [-100, 0, 99, -122, 22]
ubyte[] ubarr = [100, 0, 99, 199, 22]
word[] warr = [-1000, 0, 999, -4444, 222]
uword[] uwarr = [1000, 0, 222, 4444, 999]
float[] farr = [-1000.1, 0, 999.9, -4444.4, 222.2]
str name = "irmen"
ubyte ub
byte bb
word ww
uword uw
float ff
for ubyte ax in 0 to 255 {
word wcosa = cos8(ax) as word
word wsina = sin8(ax) as word
; LEN/STRLEN
ubyte length = len(name)
if(length!=5) c64scr.print("error len1\n")
length = len(uwarr)
if(length!=5) c64scr.print("error len2\n")
length=strlen(name)
if(length!=5) c64scr.print("error strlen1\n")
name[3] = 0
length=strlen(name)
if(length!=3) c64scr.print("error strlen2\n")
c64scr.print_ub(ax)
c64.CHROUT(':')
c64.CHROUT(' ')
c64scr.print_w(wcosa)
c64.CHROUT(',')
c64scr.print_w(wsina)
c64.CHROUT('\n')
}
; MAX
; ub = max(ubarr)
; bb = max(barr)
; ww = max(warr)
; uw = max(uwarr)
; ff = max(farr)
; word ww = sum(barr)
; uword uw = sum(ubarr)
; ww = sum(warr)
; uw = sum(uwarr)
; float ff = sum(farr)
c64scr.print("\nyou should see no errors above.")
}
}