1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-09-27 12:57:41 +00:00

Add custom segment layout support

This commit is contained in:
Karol Stasiak 2019-09-25 01:17:52 +02:00
parent 2a21dd8e2e
commit 5837c6fd7d
6 changed files with 196 additions and 130 deletions

View File

@ -152,6 +152,13 @@ For better debugging on NES, RAM segments should use bank number `$ff`.
* `segment_NAME_fill` the byte value used to fill gaps and other unused space in the bank. Default: `0`.
* `segment_NAME_layout` a comma-separated list of object names that defines in what order the objects are laid out in the segment.
One item has to be `*`, it means "all the other objects".
For example, `a,b,*,c,d` means that the output will contain `a` first, then `b`, then everything else except for `c` and `d`,
then `c` and finally `d`.
If an object from that list does not exist, it is ignored.
Default: `main,*`
#### `[output]` section
* `style` how multi-segment programs should be output:

View File

@ -34,6 +34,7 @@ class Platform(
val generateBbcMicroInfFile: Boolean,
val generateGameBoyChecksums: Boolean,
val bankNumbers: Map[String, Int],
val bankLayouts: Map[String, Seq[String]],
val bankFill: Map[String, Int],
val defaultCodeBank: String,
val ramInitialValuesBank: Option[String],
@ -174,6 +175,24 @@ object Platform {
case "" => 0
case x => parseNumber(x)
})).toMap
// needed for ZX81
val bankLayouts = banks.map(b => b -> {
val layout = as.get(classOf[String], s"segment_${b}_layout", "main,*").split(',').map(_.trim).toSeq
if (layout.isEmpty) {
log.error(s"Layout for segment $b shouldn't be empty")
} else {
if (!layout.contains("*")) {
log.error(s"Layout for segment $b should contain *")
}
if (layout.toSet.size != layout.size) {
log.error(s"Layout for segment $b should contains duplicates")
}
if (ramInitialValuesBank.contains(b) && layout.last != "*") {
log.warn(s"Layout for the ram_init_segment $b does not end with *")
}
}
layout
}).toMap
// TODO: validate stuff
banks.foreach(b => {
@ -281,6 +300,7 @@ object Platform {
generateBbcMicroInfFile,
generateGameBoyChecksums,
bankNumbers,
bankLayouts,
bankFills,
defaultCodeBank,
ramInitialValuesBank,

View File

@ -4,7 +4,7 @@ import millfork.assembly._
import millfork.compiler.{AbstractCompiler, CompilationContext}
import millfork.env._
import millfork.error.Logger
import millfork.node.{CallGraph, Expression, LiteralExpression, NiceFunctionProperty, Program}
import millfork.node.{CallGraph, Expression, FunctionCallExpression, LiteralExpression, NiceFunctionProperty, Program, SumExpression}
import millfork._
import millfork.assembly.z80.ZLine
@ -211,6 +211,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val inliningResult = inliningCalculator.calculate(
program,
platform.bankLayouts,
options.flags(CompilationFlag.InlineFunctions) || options.flags(CompilationFlag.OptimizeForSonicSpeed),
if (options.flags(CompilationFlag.OptimizeForSonicSpeed)) 4.0
else if (options.flags(CompilationFlag.OptimizeForSpeed)) 1.3
@ -310,7 +311,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
bank0.readable(index) = true
index += 1
}
case None => log.error(s"Non-constant contents of array `$name`", item.position)
case None =>
env.debugConstness(item)
log.error(s"Non-constant contents of array `$name`: " + item, item.position)
}
}
printArrayToAssemblyOutput(assembly, name, elementType, items)
@ -338,148 +341,179 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val codeAllocators = platform.codeAllocators.mapValues(new VariableAllocator(Nil, _)).view.force
var justAfterCode = platform.codeAllocators.mapValues(a => a.startAt).view.force
val sortedCompilerFunctions = compiledFunctions.toList.sortBy { case (name, cf) => if (name == "main") 0 -> "" else cf.orderKey }
sortedCompilerFunctions.filterNot(o => unusedRuntimeObjects(o._1)).foreach {
case (_, NormalCompiledFunction(_, _, true, _)) =>
// already done before
case (name, NormalCompiledFunction(bank, functionCode, false, alignment)) =>
val size = functionCode.map(_.sizeInBytes).sum
val bank0 = mem.banks(bank)
val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
justAfterCode += bank -> outputFunction(bank, functionCode, index, assembly, options)
case _ =>
}
sortedCompilerFunctions.foreach {
case (name, RedirectedFunction(_, target, offset)) =>
val tuple = labelMap(target)
labelMap(name) = tuple._1 -> (tuple._2 + offset)
case _ =>
}
// force early allocation of text literals:
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach{
case thing@InitializedArray(_, _, items, _, _, _, _, _) =>
items.foreach(env.eval(_))
case InitializedMemoryVariable(_, _, _, value, _, _, _) =>
env.eval(value)
case _ =>
}
if (options.flag(CompilationFlag.LUnixRelocatableCode)) {
env.allThings.things.foreach {
case (_, m@UninitializedMemoryVariable(name, typ, _, _, _, _)) if name.endsWith(".addr") || env.maybeGet[Thing](name + ".array").isDefined =>
val isUsed = compiledFunctions.values.exists{
case NormalCompiledFunction(_, functionCode, _, _) => functionCode.exists(_.parameter.isRelatedTo(m))
case _ => false
}
// println(m.name -> isUsed)
if (isUsed) {
val bank = m.bank(options)
if (bank != "default") ???
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size + 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = m.alignment)
labelMap(name) = bank0.index -> (index + 1)
val altName = m.name.stripPrefix(env.prefix) + "`"
val thing = if (name.endsWith(".addr")) env.get[ThingInMemory](name.stripSuffix(".addr")) else env.get[ThingInMemory](name + ".array")
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
assembly.append("* = $" + index.toHexString)
assembly.append(" " + bytePseudoopcode + " $2c")
assembly.append(name + ":")
val c = thing.toAddress
writeByte(bank, index, 0x2c.toByte) // BIT abs
index += 1
if (platform.isBigEndian) {
throw new IllegalStateException("LUnix cannot run on big-endian architectures")
}
for (i <- 0 until typ.size) {
writeByte(bank, index, subbyte(c, i, typ.size))
assembly.append(" " + bytePseudoopcode + " " + subbyte(c, i, typ.size).quickSimplify)
index += 1
}
initializedVariablesSize += typ.size
justAfterCode += bank -> index
}
case _ => ()
def getLayoutStage(name: String, segment: String): Int = {
var result = platform.bankLayouts(segment).indexOf(name)
if (result < 0) {
result = platform.bankLayouts(segment).indexOf("*")
}
val index = codeAllocators("default").allocateBytes(mem.banks("default"), options, 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = NoAlignment)
writeByte("default", index, 2.toByte) // BIT abs
assembly.append("* = $" + index.toHexString)
assembly.append(" " + bytePseudoopcode + " 2 ;; end of LUnix relocatable segment")
justAfterCode += "default" -> (index + 1)
// log.trace(s"Emit stage for ${name} is $result")
result
}
env.getAllFixedAddressObjects.foreach {
case (bank, addr, size) =>
val bank0 = mem.banks(bank)
for(i <- 0 until size) bank0.occupied(addr + i) = true
variableAllocators(bank).notifyAboutHole(bank0, addr, size)
@inline
def getLayoutStageThing(th: ThingInMemory): Int = getLayoutStage(th.name, th.bank(options))
@inline
def getLayoutStageNcf(name: String, th: NormalCompiledFunction[_]): Int = getLayoutStage(name, th.segment)
val layoutStageCount = platform.bankLayouts.values.map(_.length).max
val defaultStage = platform.bankLayouts("default").indexOf("*")
if (defaultStage < 0) {
log.fatal("The layout for the default segment lacks *")
}
var rwDataStart = Int.MaxValue
var rwDataEnd = 0
for(readOnlyPass <- Seq(true, false)) {
if (!readOnlyPass) {
if (options.platform.ramInitialValuesBank.isDefined) {
codeAllocators("default").notifyAboutEndOfCode(codeAllocators("default").heapStart)
val sortedCompilerFunctions = compiledFunctions.toList.sortBy { case (_, cf) => cf.orderKey }
for (layoutStage <- 0 until layoutStageCount) {
sortedCompilerFunctions.filterNot(o => unusedRuntimeObjects(o._1)).foreach {
case (_, NormalCompiledFunction(_, _, true, _)) =>
// already done before
case (name, th@NormalCompiledFunction(bank, functionCode, false, alignment)) if layoutStage == getLayoutStageNcf(name, th) =>
val size = functionCode.map(_.sizeInBytes).sum
val bank0 = mem.banks(bank)
val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
justAfterCode += bank -> outputFunction(bank, functionCode, index, assembly, options)
case _ =>
}
sortedCompilerFunctions.foreach {
case (name, RedirectedFunction(segment, target, offset)) if (layoutStage == getLayoutStage(target, segment)) =>
val tuple = labelMap(target)
labelMap(name) = tuple._1 -> (tuple._2 + offset)
case _ =>
}
if (layoutStage == 0) {
// force early allocation of text literals:
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(_, _, items, _, _, _, _, _) =>
items.foreach(env.eval(_))
case InitializedMemoryVariable(_, _, _, value, _, _, _) =>
env.eval(value)
case _ =>
}
}
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(name, None, items, _, _, elementType, readOnly, alignment) if readOnly == readOnlyPass =>
val bank = thing.bank(options)
if (options.platform.ramInitialValuesBank.isDefined && !readOnly && bank != "default") {
log.error(s"Preinitialized writable array `$name` should be defined in the `default` bank")
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, thing.sizeInBytes, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + thing.sizeInBytes)
}
assembly.append("* = $" + index.toHexString)
assembly.append(name + ":")
for (item <- items) {
env.eval(item) match {
case Some(c) =>
for (i <- 0 until elementType.size) {
writeByte(bank, index, subbyte(c, i, elementType.size))
index += 1
}
case None => log.error(s"Non-constant contents of array `$name`", item.position)
if (layoutStage == defaultStage && options.flag(CompilationFlag.LUnixRelocatableCode)) {
env.allThings.things.foreach {
case (_, m@UninitializedMemoryVariable(name, typ, _, _, _, _)) if name.endsWith(".addr") || env.maybeGet[Thing](name + ".array").isDefined =>
val isUsed = compiledFunctions.values.exists {
case NormalCompiledFunction(_, functionCode, _, _) => functionCode.exists(_.parameter.isRelatedTo(m))
case _ => false
}
}
printArrayToAssemblyOutput(assembly, name, elementType, items)
initializedVariablesSize += items.length
justAfterCode += bank -> index
case m@InitializedMemoryVariable(name, None, typ, value, _, alignment, _) if !readOnlyPass=>
val bank = m.bank(options)
if (options.platform.ramInitialValuesBank.isDefined && bank != "default") {
log.error(s"Preinitialized variable `$name` should be defined in the `default` bank")
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + typ.size)
}
val altName = m.name.stripPrefix(env.prefix) + "`"
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
assembly.append("* = $" + index.toHexString)
assembly.append(name + ":")
env.eval(value) match {
case Some(c) =>
// println(m.name -> isUsed)
if (isUsed) {
val bank = m.bank(options)
if (bank != "default") ???
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size + 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = m.alignment)
labelMap(name) = bank0.index -> (index + 1)
val altName = m.name.stripPrefix(env.prefix) + "`"
val thing = if (name.endsWith(".addr")) env.get[ThingInMemory](name.stripSuffix(".addr")) else env.get[ThingInMemory](name + ".array")
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
assembly.append("* = $" + index.toHexString)
assembly.append(" " + bytePseudoopcode + " $2c")
assembly.append(name + ":")
val c = thing.toAddress
writeByte(bank, index, 0x2c.toByte) // BIT abs
index += 1
if (platform.isBigEndian) {
throw new IllegalStateException("LUnix cannot run on big-endian architectures")
}
for (i <- 0 until typ.size) {
writeByte(bank, index, subbyte(c, i, typ.size))
assembly.append(" " + bytePseudoopcode + " " + subbyte(c, i, typ.size).quickSimplify)
index += 1
}
case None =>
log.error(s"Non-constant initial value for variable `$name`")
index += typ.size
initializedVariablesSize += typ.size
justAfterCode += bank -> index
}
case _ => ()
}
val index = codeAllocators("default").allocateBytes(mem.banks("default"), options, 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = NoAlignment)
writeByte("default", index, 2.toByte) // BIT abs
assembly.append("* = $" + index.toHexString)
assembly.append(" " + bytePseudoopcode + " 2 ;; end of LUnix relocatable segment")
justAfterCode += "default" -> (index + 1)
}
if (layoutStage == 0) {
env.getAllFixedAddressObjects.foreach {
case (bank, addr, size) =>
val bank0 = mem.banks(bank)
for (i <- 0 until size) bank0.occupied(addr + i) = true
variableAllocators(bank).notifyAboutHole(bank0, addr, size)
}
}
for (readOnlyPass <- Seq(true, false)) {
if (layoutStage == defaultStage && !readOnlyPass) {
if (options.platform.ramInitialValuesBank.isDefined) {
codeAllocators("default").notifyAboutEndOfCode(codeAllocators("default").heapStart)
}
initializedVariablesSize += typ.size
justAfterCode += bank -> index
case _ =>
}
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(name, None, items, _, _, elementType, readOnly, alignment) if readOnly == readOnlyPass && layoutStage == getLayoutStageThing(thing) =>
val bank = thing.bank(options)
if (options.platform.ramInitialValuesBank.isDefined && !readOnly && bank != "default") {
log.error(s"Preinitialized writable array `$name` should be defined in the `default` bank")
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, thing.sizeInBytes, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + thing.sizeInBytes)
}
assembly.append("* = $" + index.toHexString)
assembly.append(name + ":")
for (item <- items) {
val w = env
env.eval(item) match {
case Some(c) =>
for (i <- 0 until elementType.size) {
writeByte(bank, index, subbyte(c, i, elementType.size))
index += 1
}
case None =>
env.debugConstness(item)
log.error(s"Non-constant contents of array `$name`:" + item, item.position)
}
}
printArrayToAssemblyOutput(assembly, name, elementType, items)
initializedVariablesSize += items.length
justAfterCode += bank -> index
case m@InitializedMemoryVariable(name, None, typ, value, _, alignment, _) if !readOnlyPass && layoutStage == getLayoutStageThing(m) =>
val bank = m.bank(options)
if (options.platform.ramInitialValuesBank.isDefined && bank != "default") {
log.error(s"Preinitialized variable `$name` should be defined in the `default` bank")
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + typ.size)
}
val altName = m.name.stripPrefix(env.prefix) + "`"
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
assembly.append("* = $" + index.toHexString)
assembly.append(name + ":")
env.eval(value) match {
case Some(c) =>
for (i <- 0 until typ.size) {
writeByte(bank, index, subbyte(c, i, typ.size))
assembly.append(" " + bytePseudoopcode + " " + subbyte(c, i, typ.size).quickSimplify)
index += 1
}
case None =>
env.debugConstness(value)
log.error(s"Non-constant initial value for variable `$name`")
index += typ.size
}
initializedVariablesSize += typ.size
justAfterCode += bank -> index
case _ =>
}
}
}
if (rwDataEnd == 0 && rwDataStart == Int.MaxValue) {

View File

@ -25,6 +25,7 @@ abstract class AbstractInliningCalculator[T <: AbstractCode] {
private val sizes = Seq(64, 64, 8, 6, 5, 5, 4)
def calculate(program: Program,
bankLayouts: Map[String, Seq[String]],
inlineByDefault: Boolean,
aggressivenessForNormal: Double,
aggressivenessForRecommended: Double): InliningResult = {
@ -48,6 +49,7 @@ abstract class AbstractInliningCalculator[T <: AbstractCode] {
|| f.interrupt
|| f.reentrant
|| f.name == "main"
|| bankLayouts.values.exists(_.contains(f.name))
|| containsReturnDispatch(f.statements.getOrElse(Nil))) badFunctions += f.name
case _ =>
}

View File

@ -174,7 +174,7 @@ abstract class Deduplicate[T <: AbstractCode](env: Environment, options: Compila
val representative = if (set("main")) "main" else set.head
options.log.debug(s"Functions [${set.mkString(",")}] are identical")
for (function <- set) {
if (function != representative) {
if (function != representative && !options.platform.bankLayouts(segmentName).contains(function)) {
result += function -> RedirectedFunction(segmentName, representative, 0)
} else {
segContents(function) match {
@ -216,6 +216,7 @@ abstract class Deduplicate[T <: AbstractCode](env: Environment, options: Compila
else getJump(code.last)
.filter(segContents.contains)
.filter(_ != name)
.filter(n => !options.platform.bankLayouts(segmentName).contains(n))
.filter(_ != "main")
.flatMap(to => follow(segContents, to))
.map(name -> _)
@ -250,6 +251,7 @@ abstract class Deduplicate[T <: AbstractCode](env: Environment, options: Compila
else getJump(code.last)
.filter(segContents.contains)
.filter(_ != name)
.filter(n => !options.platform.bankLayouts(segmentName).contains(n))
.filter(_ != "main")
.map(name -> _)
case _ => None

View File

@ -36,6 +36,7 @@ object EmuPlatform {
false,
false,
Map("default" -> 0, "second" -> 2, "third" -> 3),
Map("default" -> Seq("main", "*"), "second" -> Seq("main", "*"), "third" -> Seq("main", "*")),
Map("default" -> 0, "second" -> 0, "third" -> 0),
"default",
None,