1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-25 19:29:49 +00:00

Never remove or inline volatile variables (fixes #27)

This commit is contained in:
Karol Stasiak 2020-01-03 21:28:10 +01:00
parent b9cdd0ffff
commit 6e36166af2
10 changed files with 34 additions and 20 deletions

View File

@ -4,7 +4,7 @@
Variables in Millfork can belong to one of the following storage classes:
* static: all global variables; local variables declared with `static`
* static: all global variables; local variables declared with `static` or `volatile`
* stack: local variables declared with `stack`

View File

@ -43,7 +43,8 @@ Examples:
* `volatile` means that the variable is volatile.
The optimizer shouldn't remove or reorder accesses to volatile variables.
Volatile variables cannot be declared as `register` or `stack.
Volatile variables (unline non-volatile ones) will not be removed or inlined by the optimizer.
Volatile variables cannot be declared as `register` or `stack`.
* `<storage>` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing.
`register` is only a hint for the optimizer.

View File

@ -79,13 +79,13 @@ object TwoVariablesToIndexRegistersOptimization extends AssemblyOptimization[Ass
case _ => None
}.toSet
val localVariables = f.environment.getAllLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case v@MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
case _ => false
}
val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case v@MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
case _ => false
}.map(_.name).toSet

View File

@ -156,13 +156,13 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case _ => None
}.toSet
val localVariables = f.environment.getAllLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case v@MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
case _ => false
}
val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case v@MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
case _ => false
}.map(_.name).toSet

View File

@ -210,7 +210,7 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
var bonus = CyclesAndBytes.Zero
if (vs.variablesWithRegisterHint(v.name)) bonus += CyclesAndBytes(16, 16)
if (id.startsWith("IX+") || id.startsWith("IY+")) bonus += savingsForRemovingOneStackVariable
if (id.startsWith("SP+")) None
if (id.startsWith("SP+") || v.isVolatile) None
else canBeInlined(id, synced = false, register, Some(false), Some(false), Some(false), vs.codeWithFlow.slice(range.start, range.end)).map { score =>
(id, range, score + bonus)
}

View File

@ -223,6 +223,7 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
def okPrefix(vs: VariableStatus, v: Variable, range: Range, reg: ZRegister.Value, allowDirectLoad: Boolean, allowIndirectLoad: Boolean): Boolean = {
if (!vs.paramVariables(v.name)) return true
if (v.isVolatile) return false
if (!allowDirectLoad && !allowIndirectLoad) {
// println(s"okPrefix $v false: better cpu required for $reg")
return false

View File

@ -1742,6 +1742,15 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
else if (stmt.global) VariableAllocationMethod.Static
else if (stmt.register) VariableAllocationMethod.Register
else VariableAllocationMethod.Auto
if (stmt.volatile && !stmt.global) {
log.warn(s"Volatile variable `$name` assumed to be static", position)
}
if (stmt.volatile && stmt.stack) {
log.error(s"Volatile variable `$name` cannot be allocated on stack", position)
}
if (stmt.volatile && stmt.register) {
log.error(s"Volatile variable `$name` cannot be allocated in a register", position)
}
if (alloc != VariableAllocationMethod.Static && stmt.initialValue.isDefined) {
log.error(s"`$name` cannot be preinitialized`", position)
}

View File

@ -15,9 +15,8 @@ object UnusedGlobalVariables extends NodeOptimization {
case AliasDefinitionStatement(source, target, _) => Some(source -> target)
case _ => None
}.toMap
// TODO: volatile
val allNonvolatileGlobalVariables = nodes.flatMap {
case v: VariableDeclarationStatement => if (v.address.isDefined) Nil else List(v.name)
case v: VariableDeclarationStatement => if (v.address.isDefined || v.volatile) Nil else List(v.name)
case v: ArrayDeclarationStatement => if (v.address.isDefined) Nil else List(v.name)
case _ => Nil
}.toSet

View File

@ -19,12 +19,12 @@ object UnusedLocalVariables extends NodeOptimization {
Nil
}
def getAllLocalVariables(statements: List[Statement]): List[String] = statements.flatMap {
case v: VariableDeclarationStatement => List(v.name)
def getAllRemovableLocalVariables(statements: List[Statement]): List[String] = statements.flatMap {
case v: VariableDeclarationStatement => if (v.volatile) Nil else List(v.name)
case v: ArrayDeclarationStatement => List(v.name)
case x: IfStatement => getAllLocalVariables(x.thenBranch) ++ getAllLocalVariables(x.elseBranch)
case x: WhileStatement => getAllLocalVariables(x.body)
case x: DoWhileStatement => getAllLocalVariables(x.body)
case x: IfStatement => getAllRemovableLocalVariables(x.thenBranch) ++ getAllRemovableLocalVariables(x.elseBranch)
case x: WhileStatement => getAllRemovableLocalVariables(x.body)
case x: DoWhileStatement => getAllRemovableLocalVariables(x.body)
case _ => Nil
}
@ -55,7 +55,7 @@ object UnusedLocalVariables extends NodeOptimization {
}
def optimizeVariables(log: Logger, statements: List[Statement]): List[Statement] = {
val allLocals = getAllLocalVariables(statements)
val allLocals = getAllRemovableLocalVariables(statements)
val allRead = statements.flatMap {
case Assignment(VariableExpression(v), expression) => List(extractThingName(v) -> expression)
case ExpressionStatement(FunctionCallExpression(op, VariableExpression(_) :: params)) if op.endsWith("=") => params.map("```" -> _)

View File

@ -10,14 +10,18 @@ import org.scalatest.{FunSuite, Matchers}
class VolatileSuite extends FunSuite with Matchers {
test("Basic volatile test") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| word addr @$c000
| volatile byte output @$c0ea
| volatile byte unused_but_should_exist
| byte unused_global
| byte thing
| void main () {
| static volatile byte unused_local
| f(55)
| addr = f.addr
| unused_local = 55
| }
| noinline void f(byte x) {
| output = 5