1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-31 18:41:30 +00:00
millfork/src/main/scala/millfork/output/VariableAllocator.scala
2019-06-28 16:44:41 +02:00

199 lines
6.4 KiB
Scala

package millfork.output
import millfork.error.{ConsoleLogger, Logger}
import millfork.node.{CallGraph, VariableVertex}
import millfork.{CompilationFlag, CompilationOptions}
import scala.annotation.tailrec
import scala.collection.mutable
/**
* @author Karol Stasiak
*/
object AllocationLocation extends Enumeration {
val Zeropage, High, Either = Value
def matches(addr:Int, location: AllocationLocation.Value): Boolean = location match {
case Zeropage => addr < 0x100
case High => addr >= 0x100
case Either => true
}
}
sealed trait ByteAllocator {
def startAt: Int
def endBefore: Int
def heapStart: Int
def preferredOrder: Option[List[Int]]
def notifyAboutEndOfCode(org: Int): Unit
def findFreeBytes(mem: MemoryBank, count: Int, options: CompilationOptions, alignment: MemoryAlignment): Int = {
var lastFree = startAt
var counter = 0
val occupied = mem.occupied
var previous = -800
for(i <- preferredOrder.getOrElse(startAt until endBefore)) {
if (occupied(i)) {
counter = 0
} else if (counter == 0 && (alignment match {
case WithinPageAlignment => count <= 256 && i.&(0xff00) != i.+(count - 1).&(0xff00)
case DivisibleAlignment(divisor) => i % divisor != 0
case NoAlignment => false
})) {
counter = 0
} else {
if (previous != i - 1) {
counter = 0
}
if (counter == 0) {
lastFree = i
}
if (count == 0) {
return lastFree
}
counter += 1
if (counter == count) {
return lastFree
}
}
previous = i
}
-1
}
}
class UpwardByteAllocator(val startAt: Int, val endBefore: Int) extends ByteAllocator {
var heapStart: Int = startAt
def notifyAboutEndOfCode(org: Int): Unit = {
heapStart = org
}
override def preferredOrder: Option[List[Int]] = None
}
class ZeropageAllocator(val freeZpBytes: List[Int]) extends ByteAllocator {
def notifyAboutEndOfCode(org: Int): Unit = ()
override def preferredOrder: Option[List[Int]] = if (freeZpBytes.isEmpty) None else Some(freeZpBytes)
override def startAt: Int = if (freeZpBytes.isEmpty) 2 else freeZpBytes.min
override def endBefore: Int = if (freeZpBytes.isEmpty) 2 else freeZpBytes.max + 1
override def heapStart: Int = 0
}
class AfterCodeByteAllocator(startIfNoCode: Int, val endBefore: Int) extends ByteAllocator {
var startAt = startIfNoCode
def notifyAboutEndOfCode(org: Int): Unit = startAt = org
override def preferredOrder: Option[List[Int]] = None
override def heapStart: Int = startAt
}
class VariableAllocator(zpBytes: List[Int], private val bytes: ByteAllocator) {
def totalHimemSize: Int = bytes.endBefore - bytes.startAt
val zeropage: ByteAllocator = new ZeropageAllocator(zpBytes)
private val variableMap = mutable.Map[Int, mutable.Map[Int, Set[VariableVertex]]]()
var heapStart: Int = bytes.heapStart
def allocateBytes(mem: MemoryBank, callGraph: CallGraph, p: VariableVertex, options: CompilationOptions, count: Int, initialized: Boolean, writeable: Boolean, location: AllocationLocation.Value, alignment: MemoryAlignment): Int = {
if (!variableMap.contains(count)) {
variableMap(count) = mutable.Map()
}
variableMap(count).foreach { case (a, alreadyThere) =>
if (AllocationLocation.matches(a, location) && alreadyThere.forall(q => callGraph.canOverlap(p, q))) {
variableMap(count)(a) += p
return a
}
}
val addr = allocateBytes(mem, options, count, initialized, writeable, location, alignment)
variableMap(count)(addr) = Set(p)
addr
}
def tryAllocateZeropageBytes(mem: MemoryBank, callGraph: CallGraph, p: VariableVertex, options: CompilationOptions, count: Int, alignment: MemoryAlignment): Option[Int]={
if (!variableMap.contains(count)) {
variableMap(count) = mutable.Map()
}
variableMap(count).foreach { case (a, alreadyThere) =>
if (a < 0x100 && alreadyThere.forall(q => callGraph.canOverlap(p, q))) {
variableMap(count)(a) += p
return Some(a)
}
}
val addr = zeropage.findFreeBytes(mem, count, options, alignment)
if (addr < 0) None else {
markBytes(options.log, mem, addr, count, initialized = false, writeable = true)
Some(addr)
}
}
def allocateBytes(mem: MemoryBank, options: CompilationOptions, count: Int, initialized: Boolean, writeable: Boolean, location: AllocationLocation.Value, alignment: MemoryAlignment): Int = {
val addr = if (options.platform.hasZeroPage) {
location match {
case AllocationLocation.Zeropage =>
val a = zeropage.findFreeBytes(mem, count, options, alignment)
if (a < 0) {
options.log.fatal("Out of zeropage memory")
}
a
case AllocationLocation.High =>
val a = bytes.findFreeBytes(mem, count, options, alignment)
if (a < 0) {
options.log.fatal("Out of high memory")
}
a
case AllocationLocation.Either =>
var a = zeropage.findFreeBytes(mem, count, options, alignment)
if (a < 0) {
a = bytes.findFreeBytes(mem, count, options, alignment)
if (a < 0) {
options.log.fatal("Out of high memory")
}
}
a
}
} else {
val a = bytes.findFreeBytes(mem, count, options, alignment)
if (a < 0) {
options.log.fatal("Out of high memory")
}
a
}
markBytes(options.log, mem, addr, count, initialized, writeable)
heapStart = heapStart max (addr + count)
addr
}
private def markBytes(log: Logger, mem: MemoryBank, addr: Int, count: Int, initialized: Boolean, writeable: Boolean): Unit = {
log.trace(s"allocating $count bytes at $$${addr.toHexString}")
(addr until (addr + count)).foreach { i =>
if (mem.occupied(i)) log.fatal("Overlapping objects")
mem.readable(i) = true
mem.occupied(i) = true
mem.initialized(i) = initialized
mem.writeable(i) = writeable
}
}
//TODO: Everything about the three methods below is ugly and wrong. Fix later.
def notifyAboutEndOfCode(org: Int): Unit = bytes.notifyAboutEndOfCode(org)
def notifyAboutEndOfData(org: Int): Unit = heapStart = heapStart max org
def notifyAboutHole(mem: MemoryBank, addr: Int, size: Int): Unit = {
if (Math.abs(addr - heapStart) <= 1) {
heapStart += size
while (mem.occupied(heapStart)) heapStart += 1
bytes.notifyAboutEndOfCode(heapStart)
}
}
}