mirror of
https://github.com/irmen/prog8.git
synced 2025-01-27 10:31:40 +00:00
restructure
This commit is contained in:
parent
6da048ba4c
commit
f3532e9014
@ -4,8 +4,9 @@ import java.nio.file.Paths
|
||||
import il65.ast.*
|
||||
import il65.parser.*
|
||||
import il65.compiler.*
|
||||
import il65.optimizing.optimizeExpressions
|
||||
import il65.optimizing.constantFold
|
||||
import il65.optimizing.optimizeStatements
|
||||
import il65.optimizing.simplifyExpressions
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@ -23,9 +24,10 @@ fun main(args: Array<String>) {
|
||||
|
||||
try {
|
||||
// import main module and process additional imports
|
||||
println("Parsing...")
|
||||
val moduleAst = importModule(filepath)
|
||||
moduleAst.linkParents()
|
||||
val globalNameSpaceBeforeOptimization = moduleAst.definingScope()
|
||||
var namespace = moduleAst.definingScope()
|
||||
|
||||
// determine special compiler options
|
||||
|
||||
@ -46,16 +48,25 @@ fun main(args: Array<String>) {
|
||||
|
||||
// perform syntax checks and optimizations
|
||||
moduleAst.checkIdentifiers()
|
||||
moduleAst.optimizeExpressions(globalNameSpaceBeforeOptimization)
|
||||
moduleAst.checkValid(globalNameSpaceBeforeOptimization, compilerOptions) // check if tree is valid
|
||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers()
|
||||
moduleAst.optimizeStatements(globalNameSpaceBeforeOptimization, allScopedSymbolDefinitions)
|
||||
StatementReorderer().process(moduleAst) // reorder statements to please the compiler later
|
||||
val globalNamespaceAfterOptimize = moduleAst.definingScope() // create it again, it could have changed in the meantime
|
||||
moduleAst.checkValid(globalNamespaceAfterOptimize, compilerOptions) // check if final tree is valid
|
||||
moduleAst.checkRecursion(globalNamespaceAfterOptimize) // check if there are recursive subroutine calls
|
||||
|
||||
// globalNamespaceAfterOptimize.debugPrint()
|
||||
println("Optimizing...")
|
||||
moduleAst.constantFold(namespace)
|
||||
moduleAst.checkValid(namespace, compilerOptions) // check if tree is valid
|
||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers()
|
||||
while(true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = moduleAst.simplifyExpressions(namespace)
|
||||
val optsDone2 = moduleAst.optimizeStatements(namespace, allScopedSymbolDefinitions)
|
||||
if(optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
StatementReorderer().process(moduleAst) // reorder statements to please the compiler later
|
||||
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
|
||||
moduleAst.checkValid(namespace, compilerOptions) // check if final tree is valid
|
||||
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
|
||||
|
||||
// namespace.debugPrint()
|
||||
|
||||
// compile the syntax tree into stackvmProg form, and optimize that
|
||||
val compiler = Compiler(compilerOptions)
|
||||
|
@ -225,19 +225,6 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
return super.process(decl)
|
||||
}
|
||||
|
||||
/**
|
||||
* check if condition
|
||||
*/
|
||||
override fun process(ifStatement: IfStatement): IStatement {
|
||||
val constvalue = ifStatement.condition.constValue(namespace)
|
||||
if(constvalue!=null) {
|
||||
val msg = if (constvalue.asBooleanValue) "condition is always true" else "condition is always false"
|
||||
println("${ifStatement.position} Warning: $msg")
|
||||
}
|
||||
|
||||
return super.process(ifStatement)
|
||||
}
|
||||
|
||||
/**
|
||||
* check the arguments of the directive
|
||||
*/
|
||||
@ -452,7 +439,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
if(av.register!=Register.A && av.register!=Register.X && av.register!=Register.Y)
|
||||
return err("register '$av' in byte array is not a single register")
|
||||
} else {
|
||||
TODO("array value $av")
|
||||
TODO("check array value $av")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va
|
||||
|
||||
class Compiler(private val options: CompilationOptions) {
|
||||
fun compile(module: Module) : StackVmProgram {
|
||||
println("\nCompiling parsed source code to stackvmProg code...")
|
||||
println("\nCreating stackVM code...")
|
||||
|
||||
val namespace = module.definingScope()
|
||||
|
||||
@ -423,7 +423,7 @@ class StackVmProgram(val name: String) {
|
||||
}
|
||||
|
||||
fun optimize() {
|
||||
println("\nOptimizing stackvmProg code...")
|
||||
println("\nOptimizing stackVM code...")
|
||||
// todo optimize stackvm code
|
||||
}
|
||||
|
||||
|
@ -1,269 +1,8 @@
|
||||
package il65.optimizing
|
||||
|
||||
import il65.parser.ParsingFailedError
|
||||
import il65.ast.*
|
||||
import il65.compiler.Petscii
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
fun Module.optimizeExpressions(globalNamespace: INameScope) {
|
||||
val optimizer = ExpressionOptimizer(globalNamespace)
|
||||
try {
|
||||
this.process(optimizer)
|
||||
} catch (ax: AstException) {
|
||||
optimizer.addError(ax)
|
||||
}
|
||||
|
||||
if(optimizer.optimizationsDone==0)
|
||||
println("[${this.name}] 0 expression optimizations performed")
|
||||
|
||||
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
println("[${this.name}] ${optimizer.optimizationsDone} expression optimizations performed")
|
||||
optimizer.optimizationsDone = 0
|
||||
this.process(optimizer)
|
||||
}
|
||||
|
||||
if(optimizer.errors.isNotEmpty()) {
|
||||
optimizer.errors.forEach { System.err.println(it) }
|
||||
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
|
||||
} else {
|
||||
this.linkParents() // re-link in final configuration
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
todo eliminate useless terms:
|
||||
*0 -> constant 0
|
||||
X*1, X/1, X//1 -> just X
|
||||
X*-1 -> unary prefix -X
|
||||
X**0 -> 1
|
||||
X**1 -> X
|
||||
X**-1 -> 1.0/X
|
||||
X << 0 -> X
|
||||
X | 0 -> X
|
||||
x & 0 -> 0
|
||||
X ^ 0 -> X
|
||||
|
||||
todo expression optimization: remove redundant builtin function calls
|
||||
todo expression optimization: reduce expression nesting / flattening of parenthesis
|
||||
todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0)
|
||||
todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2)
|
||||
todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call)
|
||||
|
||||
*/
|
||||
class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
var errors : MutableList<AstException> = mutableListOf()
|
||||
|
||||
private val reportedErrorMessages = mutableSetOf<String>()
|
||||
|
||||
fun addError(x: AstException) {
|
||||
// check that we don't add the same error more than once
|
||||
if(!reportedErrorMessages.contains(x.toString())) {
|
||||
reportedErrorMessages.add(x.toString())
|
||||
errors.add(x)
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true||
|
||||
decl.arrayspec?.x?.referencesIdentifier(decl.name) == true ||
|
||||
decl.arrayspec?.y?.referencesIdentifier(decl.name) == true) {
|
||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
||||
return decl
|
||||
}
|
||||
|
||||
val result = super.process(decl)
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
when {
|
||||
decl.datatype == DataType.FLOAT -> {
|
||||
// vardecl: for float vars, promote constant integer initialization values to floats
|
||||
val literal = decl.value as? LiteralValue
|
||||
if (literal != null && (literal.type == DataType.BYTE || literal.type==DataType.WORD)) {
|
||||
val newValue = LiteralValue(DataType.FLOAT, floatvalue = literal.asNumericValue!!.toDouble(), position = literal.position)
|
||||
decl.value = newValue
|
||||
}
|
||||
}
|
||||
decl.datatype == DataType.BYTE || decl.datatype == DataType.WORD -> {
|
||||
// vardecl: for byte/word vars, convert char/string of length 1 initialization values to integer
|
||||
val literal = decl.value as? LiteralValue
|
||||
if (literal != null && literal.isString && literal.strvalue?.length == 1) {
|
||||
val petscii = Petscii.encodePetscii(literal.strvalue)[0]
|
||||
val newValue = LiteralValue(DataType.BYTE, bytevalue = petscii, position = literal.position)
|
||||
decl.value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* replace identifiers that refer to const value, with the value itself
|
||||
*/
|
||||
override fun process(identifier: IdentifierReference): IExpression {
|
||||
return try {
|
||||
identifier.constValue(globalNamespace) ?: identifier
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
identifier
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
return try {
|
||||
super.process(functionCall)
|
||||
functionCall.constValue(globalNamespace) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
functionCall
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process a unary prefix expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
*/
|
||||
override fun process(expr: PrefixExpression): IExpression {
|
||||
return try {
|
||||
super.process(expr)
|
||||
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is LiteralValue) {
|
||||
// process prefixed literal values (such as -3, not true)
|
||||
return when {
|
||||
expr.operator == "+" -> subexpr
|
||||
expr.operator == "-" -> when {
|
||||
subexpr.asIntegerValue!= null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position)
|
||||
}
|
||||
subexpr.floatvalue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue(DataType.FLOAT, floatvalue = -subexpr.floatvalue, position = subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
expr.operator == "~" -> when {
|
||||
subexpr.asIntegerValue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.optimalNumeric(subexpr.asIntegerValue.inv(), subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
expr.operator == "not" -> when {
|
||||
subexpr.asIntegerValue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.fromBoolean(subexpr.asIntegerValue == 0, subexpr.position)
|
||||
}
|
||||
subexpr.floatvalue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.fromBoolean(subexpr.floatvalue == 0.0, subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError("can not take logical not of $subexpr", subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return expr
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
*/
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
return try {
|
||||
super.process(expr)
|
||||
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val leftconst = expr.left.constValue(globalNamespace)
|
||||
val rightconst = expr.right.constValue(globalNamespace)
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
}
|
||||
else -> expr
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(range: RangeExpr): IExpression {
|
||||
return try {
|
||||
super.process(range)
|
||||
val from = range.from.constValue(globalNamespace)
|
||||
val to = range.to.constValue(globalNamespace)
|
||||
if (from != null && to != null) {
|
||||
when {
|
||||
from.type==DataType.WORD || to.type==DataType.WORD -> {
|
||||
// range on word value boundaries
|
||||
val rangeValue = from.asIntegerValue!!.rangeTo(to.asIntegerValue!!)
|
||||
if (rangeValue.last - rangeValue.first > 65535) {
|
||||
throw ExpressionError("amount of values in range exceeds 65535", range.position)
|
||||
}
|
||||
return LiteralValue(DataType.ARRAY_W, arrayvalue = rangeValue.map {
|
||||
val v = LiteralValue(DataType.WORD, wordvalue = it, position = range.position)
|
||||
v.parent = range.parent
|
||||
v
|
||||
}.toMutableList(), position = from.position)
|
||||
}
|
||||
from.type==DataType.BYTE && to.type==DataType.BYTE -> {
|
||||
// range on byte value boundaries
|
||||
val rangeValue = from.bytevalue!!.rangeTo(to.bytevalue!!)
|
||||
if (rangeValue.last - rangeValue.first > 65535) {
|
||||
throw ExpressionError("amount of values in range exceeds 65535", range.position)
|
||||
}
|
||||
return LiteralValue(DataType.ARRAY, arrayvalue = rangeValue.map {
|
||||
val v = LiteralValue(DataType.BYTE, bytevalue = it.toShort(), position = range.position)
|
||||
v.parent = range.parent
|
||||
v
|
||||
}.toMutableList(), position = from.position)
|
||||
}
|
||||
from.strvalue != null && to.strvalue != null -> {
|
||||
// char range
|
||||
val rangevalue = from.strvalue[0].rangeTo(to.strvalue[0])
|
||||
if (rangevalue.last - rangevalue.first > 65535) {
|
||||
throw ExpressionError("amount of characters in range exceeds 65535", range.position)
|
||||
}
|
||||
val newval = LiteralValue(DataType.STR, strvalue = rangevalue.toList().joinToString(""), position = range.position)
|
||||
newval.parent = range.parent
|
||||
return newval
|
||||
}
|
||||
else -> AstException("range on weird datatype")
|
||||
}
|
||||
}
|
||||
return range
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
range
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(literalValue: LiteralValue): LiteralValue {
|
||||
if(literalValue.arrayvalue!=null) {
|
||||
val newArray = literalValue.arrayvalue.map { it.process(this) }
|
||||
literalValue.arrayvalue.clear()
|
||||
literalValue.arrayvalue.addAll(newArray)
|
||||
}
|
||||
return super.process(literalValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ConstExprEvaluator {
|
||||
|
||||
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression {
|
||||
@ -410,11 +149,11 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun bitwisexor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
if(left.type==DataType.BYTE) {
|
||||
if(left.type== DataType.BYTE) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.BYTE, bytevalue = (left.bytevalue!!.toInt() xor (right.asIntegerValue and 255)).toShort(), position = left.position)
|
||||
}
|
||||
} else if(left.type==DataType.WORD) {
|
||||
} else if(left.type== DataType.WORD) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.WORD, wordvalue = left.wordvalue!! xor right.asIntegerValue, position = left.position)
|
||||
}
|
||||
@ -423,11 +162,11 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun bitwiseor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
if(left.type==DataType.BYTE) {
|
||||
if(left.type== DataType.BYTE) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.BYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
|
||||
}
|
||||
} else if(left.type==DataType.WORD) {
|
||||
} else if(left.type== DataType.WORD) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.WORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
|
||||
}
|
||||
@ -436,11 +175,11 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun bitwiseand(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
if(left.type==DataType.BYTE) {
|
||||
if(left.type== DataType.BYTE) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.BYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
|
||||
}
|
||||
} else if(left.type==DataType.WORD) {
|
||||
} else if(left.type== DataType.WORD) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.WORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
|
||||
}
|
||||
@ -555,4 +294,4 @@ class ConstExprEvaluator {
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
217
il65/src/il65/optimizing/ConstantFolding.kt
Normal file
217
il65/src/il65/optimizing/ConstantFolding.kt
Normal file
@ -0,0 +1,217 @@
|
||||
package il65.optimizing
|
||||
|
||||
import il65.ast.*
|
||||
import il65.compiler.Petscii
|
||||
|
||||
|
||||
class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
var errors : MutableList<AstException> = mutableListOf()
|
||||
|
||||
private val reportedErrorMessages = mutableSetOf<String>()
|
||||
|
||||
fun addError(x: AstException) {
|
||||
// check that we don't add the same error more than once
|
||||
if(!reportedErrorMessages.contains(x.toString())) {
|
||||
reportedErrorMessages.add(x.toString())
|
||||
errors.add(x)
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true||
|
||||
decl.arrayspec?.x?.referencesIdentifier(decl.name) == true ||
|
||||
decl.arrayspec?.y?.referencesIdentifier(decl.name) == true) {
|
||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
||||
return decl
|
||||
}
|
||||
|
||||
val result = super.process(decl)
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
when {
|
||||
decl.datatype == DataType.FLOAT -> {
|
||||
// vardecl: for float vars, promote constant integer initialization values to floats
|
||||
val literal = decl.value as? LiteralValue
|
||||
if (literal != null && (literal.type == DataType.BYTE || literal.type==DataType.WORD)) {
|
||||
val newValue = LiteralValue(DataType.FLOAT, floatvalue = literal.asNumericValue!!.toDouble(), position = literal.position)
|
||||
decl.value = newValue
|
||||
}
|
||||
}
|
||||
decl.datatype == DataType.BYTE || decl.datatype == DataType.WORD -> {
|
||||
// vardecl: for byte/word vars, convert char/string of length 1 initialization values to integer
|
||||
val literal = decl.value as? LiteralValue
|
||||
if (literal != null && literal.isString && literal.strvalue?.length == 1) {
|
||||
val petscii = Petscii.encodePetscii(literal.strvalue)[0]
|
||||
val newValue = LiteralValue(DataType.BYTE, bytevalue = petscii, position = literal.position)
|
||||
decl.value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* replace identifiers that refer to const value, with the value itself
|
||||
*/
|
||||
override fun process(identifier: IdentifierReference): IExpression {
|
||||
return try {
|
||||
identifier.constValue(globalNamespace) ?: identifier
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
identifier
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
return try {
|
||||
super.process(functionCall)
|
||||
functionCall.constValue(globalNamespace) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
functionCall
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process a unary prefix expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
*/
|
||||
override fun process(expr: PrefixExpression): IExpression {
|
||||
return try {
|
||||
super.process(expr)
|
||||
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is LiteralValue) {
|
||||
// process prefixed literal values (such as -3, not true)
|
||||
return when {
|
||||
expr.operator == "+" -> subexpr
|
||||
expr.operator == "-" -> when {
|
||||
subexpr.asIntegerValue!= null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position)
|
||||
}
|
||||
subexpr.floatvalue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue(DataType.FLOAT, floatvalue = -subexpr.floatvalue, position = subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
expr.operator == "~" -> when {
|
||||
subexpr.asIntegerValue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.optimalNumeric(subexpr.asIntegerValue.inv(), subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
expr.operator == "not" -> when {
|
||||
subexpr.asIntegerValue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.fromBoolean(subexpr.asIntegerValue == 0, subexpr.position)
|
||||
}
|
||||
subexpr.floatvalue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.fromBoolean(subexpr.floatvalue == 0.0, subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError("can not take logical not of $subexpr", subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return expr
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
*/
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
return try {
|
||||
super.process(expr)
|
||||
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val leftconst = expr.left.constValue(globalNamespace)
|
||||
val rightconst = expr.right.constValue(globalNamespace)
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
}
|
||||
else -> expr
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(range: RangeExpr): IExpression {
|
||||
return try {
|
||||
super.process(range)
|
||||
val from = range.from.constValue(globalNamespace)
|
||||
val to = range.to.constValue(globalNamespace)
|
||||
if (from != null && to != null) {
|
||||
when {
|
||||
from.type==DataType.WORD || to.type==DataType.WORD -> {
|
||||
// range on word value boundaries
|
||||
val rangeValue = from.asIntegerValue!!.rangeTo(to.asIntegerValue!!)
|
||||
if (rangeValue.last - rangeValue.first > 65535) {
|
||||
throw ExpressionError("amount of values in range exceeds 65535", range.position)
|
||||
}
|
||||
return LiteralValue(DataType.ARRAY_W, arrayvalue = rangeValue.map {
|
||||
val v = LiteralValue(DataType.WORD, wordvalue = it, position = range.position)
|
||||
v.parent = range.parent
|
||||
v
|
||||
}.toMutableList(), position = from.position)
|
||||
}
|
||||
from.type==DataType.BYTE && to.type==DataType.BYTE -> {
|
||||
// range on byte value boundaries
|
||||
val rangeValue = from.bytevalue!!.rangeTo(to.bytevalue!!)
|
||||
if (rangeValue.last - rangeValue.first > 65535) {
|
||||
throw ExpressionError("amount of values in range exceeds 65535", range.position)
|
||||
}
|
||||
return LiteralValue(DataType.ARRAY, arrayvalue = rangeValue.map {
|
||||
val v = LiteralValue(DataType.BYTE, bytevalue = it.toShort(), position = range.position)
|
||||
v.parent = range.parent
|
||||
v
|
||||
}.toMutableList(), position = from.position)
|
||||
}
|
||||
from.strvalue != null && to.strvalue != null -> {
|
||||
// char range
|
||||
val rangevalue = from.strvalue[0].rangeTo(to.strvalue[0])
|
||||
if (rangevalue.last - rangevalue.first > 65535) {
|
||||
throw ExpressionError("amount of characters in range exceeds 65535", range.position)
|
||||
}
|
||||
val newval = LiteralValue(DataType.STR, strvalue = rangevalue.toList().joinToString(""), position = range.position)
|
||||
newval.parent = range.parent
|
||||
return newval
|
||||
}
|
||||
else -> AstException("range on weird datatype")
|
||||
}
|
||||
}
|
||||
return range
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
range
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(literalValue: LiteralValue): LiteralValue {
|
||||
if(literalValue.arrayvalue!=null) {
|
||||
val newArray = literalValue.arrayvalue.map { it.process(this) }
|
||||
literalValue.arrayvalue.clear()
|
||||
literalValue.arrayvalue.addAll(newArray)
|
||||
}
|
||||
return super.process(literalValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
48
il65/src/il65/optimizing/Extensions.kt
Normal file
48
il65/src/il65/optimizing/Extensions.kt
Normal file
@ -0,0 +1,48 @@
|
||||
package il65.optimizing
|
||||
|
||||
import il65.ast.AstException
|
||||
import il65.ast.INameScope
|
||||
import il65.ast.IStatement
|
||||
import il65.ast.Module
|
||||
import il65.parser.ParsingFailedError
|
||||
|
||||
fun Module.constantFold(globalNamespace: INameScope) {
|
||||
val optimizer = ConstantFolding(globalNamespace)
|
||||
try {
|
||||
this.process(optimizer)
|
||||
} catch (ax: AstException) {
|
||||
optimizer.addError(ax)
|
||||
}
|
||||
|
||||
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
println("[${this.name}] ${optimizer.optimizationsDone} constant folds performed")
|
||||
optimizer.optimizationsDone = 0
|
||||
this.process(optimizer)
|
||||
}
|
||||
|
||||
if(optimizer.errors.isNotEmpty()) {
|
||||
optimizer.errors.forEach { System.err.println(it) }
|
||||
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
|
||||
} else {
|
||||
this.linkParents() // re-link in final configuration
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefinitions: MutableMap<String, IStatement>): Int {
|
||||
val optimizer = StatementOptimizer(globalNamespace)
|
||||
this.process(optimizer)
|
||||
optimizer.removeUnusedNodes(globalNamespace.usedNames(), allScopedSymbolDefinitions)
|
||||
if(optimizer.optimizationsDone > 0)
|
||||
println("[${this.name}] ${optimizer.optimizationsDone} statement optimizations performed")
|
||||
this.linkParents() // re-link in final configuration
|
||||
return optimizer.optimizationsDone
|
||||
}
|
||||
|
||||
fun Module.simplifyExpressions(namespace: INameScope) : Int {
|
||||
val optimizer = SimplifyExpressions(namespace)
|
||||
this.process(optimizer)
|
||||
if(optimizer.optimizationsDone > 0)
|
||||
println("[${this.name}] ${optimizer.optimizationsDone} expression optimizations performed")
|
||||
return optimizer.optimizationsDone
|
||||
}
|
33
il65/src/il65/optimizing/SimplifyExpressions.kt
Normal file
33
il65/src/il65/optimizing/SimplifyExpressions.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package il65.optimizing
|
||||
|
||||
import il65.ast.IAstProcessor
|
||||
import il65.ast.INameScope
|
||||
|
||||
/*
|
||||
todo eliminate useless terms:
|
||||
*0 -> constant 0
|
||||
X*1, X/1, X//1 -> just X
|
||||
X*-1 -> unary prefix -X
|
||||
X**0 -> 1
|
||||
X**1 -> X
|
||||
X**-1 -> 1.0/X
|
||||
X << 0 -> X
|
||||
X | 0 -> X
|
||||
x & 0 -> 0
|
||||
X ^ 0 -> X
|
||||
|
||||
todo expression optimization: remove redundant builtin function calls
|
||||
todo expression optimization: reduce expression nesting / flattening of parenthesis
|
||||
todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0)
|
||||
todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2)
|
||||
todo optimize addition with self into shift 1 (A+=A -> A<<=1)
|
||||
todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call)
|
||||
todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?)
|
||||
|
||||
*/
|
||||
|
||||
class SimplifyExpressions(namespace: INameScope) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
// @todo build this optimizer
|
||||
}
|
@ -5,22 +5,9 @@ import il65.functions.BuiltinFunctionNames
|
||||
import il65.functions.BuiltinFunctionsWithoutSideEffects
|
||||
|
||||
|
||||
fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefinitions: MutableMap<String, IStatement>) {
|
||||
val optimizer = StatementOptimizer(globalNamespace)
|
||||
this.process(optimizer)
|
||||
optimizer.removeUnusedNodes(globalNamespace.usedNames(), allScopedSymbolDefinitions)
|
||||
if(optimizer.optimizationsDone==0)
|
||||
println("[${this.name}] 0 statement optimizations performed")
|
||||
|
||||
while(optimizer.optimizationsDone>0) {
|
||||
println("[${this.name}] ${optimizer.optimizationsDone} statement optimizations performed")
|
||||
optimizer.reset()
|
||||
this.process(optimizer)
|
||||
}
|
||||
this.linkParents() // re-link in final configuration
|
||||
}
|
||||
|
||||
/*
|
||||
todo remove if statements with empty statement blocks
|
||||
todo replace if statements with only else block
|
||||
todo statement optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...)
|
||||
todo statement optimization: X+=1, X-=1 --> X++/X-- ,
|
||||
todo remove statements that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
|
||||
@ -37,10 +24,6 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
|
||||
private var statementsToRemove = mutableListOf<IStatement>()
|
||||
|
||||
fun reset() {
|
||||
optimizationsDone = 0
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
val target = globalNamespace.lookup(functionCall.target.nameInSource, functionCall)
|
||||
if(target!=null)
|
||||
@ -100,6 +83,8 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
}
|
||||
|
||||
fun removeUnusedNodes(usedNames: Set<String>, allScopedSymbolDefinitions: MutableMap<String, IStatement>) {
|
||||
val symbolsToRemove = mutableListOf<String>()
|
||||
|
||||
for ((name, value) in allScopedSymbolDefinitions) {
|
||||
if(!usedNames.contains(name)) {
|
||||
val parentScope = value.parent as INameScope
|
||||
@ -109,10 +94,15 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
if(value is Block)
|
||||
println("${value.position} Info: block '$localname' is never used")
|
||||
parentScope.removeStatement(value)
|
||||
symbolsToRemove.add(name)
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
|
||||
for(name in symbolsToRemove) {
|
||||
allScopedSymbolDefinitions.remove(name)
|
||||
}
|
||||
|
||||
for(stmt in statementsToRemove) {
|
||||
stmt.definingScope().removeStatement(stmt)
|
||||
}
|
@ -15,7 +15,7 @@ private val importedModules : HashMap<String, Module> = hashMapOf()
|
||||
|
||||
|
||||
fun importModule(filePath: Path) : Module {
|
||||
println("importing '${filePath.fileName}' (from ${filePath.parent})...")
|
||||
println("importing '${filePath.fileName}' (from '${filePath.parent}')")
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
|
@ -130,9 +130,10 @@ enum class Syscall(val callNr: Short) {
|
||||
INPUT_VAR(15), // user input a string into a variable
|
||||
GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order
|
||||
GFX_CLEARSCR(17), // clear the screen with color pushed on stack
|
||||
GFX_TEXT(18), // write text on screen at (x,y,text,color) pushed on stack in that order
|
||||
GFX_TEXT(18), // write text on screen at (x,y,color,text) pushed on stack in that order
|
||||
RANDOM(19), // push a random byte on the stack
|
||||
RANDOM_W(20), // push a random word on the stack
|
||||
RANDOM_F(21), // push a random float on the stack (between 0.0 and 1.0)
|
||||
|
||||
|
||||
FUNC_P_CARRY(100),
|
||||
@ -598,7 +599,7 @@ class Program (prog: MutableList<Instruction>,
|
||||
Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues)
|
||||
}
|
||||
else -> {
|
||||
println("INSTR $opcode at $lineNr args=$args") // TODO weg
|
||||
// println("INSTR $opcode at $lineNr args=$args")
|
||||
Instruction(opcode, getArgValue(args))
|
||||
}
|
||||
}
|
||||
@ -1006,13 +1007,14 @@ class StackVm(val traceOutputFile: String?) {
|
||||
canvas.clearScreen(color.integerValue())
|
||||
}
|
||||
Syscall.GFX_TEXT -> {
|
||||
val color = evalstack.pop()
|
||||
val text = evalstack.pop()
|
||||
val color = evalstack.pop()
|
||||
val (y, x) = evalstack.pop2()
|
||||
canvas.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue())
|
||||
}
|
||||
Syscall.RANDOM -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
|
||||
Syscall.RANDOM_W -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
|
||||
Syscall.RANDOM_F -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble()))
|
||||
else -> throw VmExecutionException("unimplemented syscall $syscall")
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user