mirror of
https://github.com/KarolS/millfork.git
synced 2025-08-10 01:25:31 +00:00
Add custom segment layout support
This commit is contained in:
@@ -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_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
|
#### `[output]` section
|
||||||
|
|
||||||
* `style` – how multi-segment programs should be output:
|
* `style` – how multi-segment programs should be output:
|
||||||
|
@@ -34,6 +34,7 @@ class Platform(
|
|||||||
val generateBbcMicroInfFile: Boolean,
|
val generateBbcMicroInfFile: Boolean,
|
||||||
val generateGameBoyChecksums: Boolean,
|
val generateGameBoyChecksums: Boolean,
|
||||||
val bankNumbers: Map[String, Int],
|
val bankNumbers: Map[String, Int],
|
||||||
|
val bankLayouts: Map[String, Seq[String]],
|
||||||
val bankFill: Map[String, Int],
|
val bankFill: Map[String, Int],
|
||||||
val defaultCodeBank: String,
|
val defaultCodeBank: String,
|
||||||
val ramInitialValuesBank: Option[String],
|
val ramInitialValuesBank: Option[String],
|
||||||
@@ -174,6 +175,24 @@ object Platform {
|
|||||||
case "" => 0
|
case "" => 0
|
||||||
case x => parseNumber(x)
|
case x => parseNumber(x)
|
||||||
})).toMap
|
})).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
|
// TODO: validate stuff
|
||||||
banks.foreach(b => {
|
banks.foreach(b => {
|
||||||
@@ -281,6 +300,7 @@ object Platform {
|
|||||||
generateBbcMicroInfFile,
|
generateBbcMicroInfFile,
|
||||||
generateGameBoyChecksums,
|
generateGameBoyChecksums,
|
||||||
bankNumbers,
|
bankNumbers,
|
||||||
|
bankLayouts,
|
||||||
bankFills,
|
bankFills,
|
||||||
defaultCodeBank,
|
defaultCodeBank,
|
||||||
ramInitialValuesBank,
|
ramInitialValuesBank,
|
||||||
|
@@ -4,7 +4,7 @@ import millfork.assembly._
|
|||||||
import millfork.compiler.{AbstractCompiler, CompilationContext}
|
import millfork.compiler.{AbstractCompiler, CompilationContext}
|
||||||
import millfork.env._
|
import millfork.env._
|
||||||
import millfork.error.Logger
|
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._
|
||||||
import millfork.assembly.z80.ZLine
|
import millfork.assembly.z80.ZLine
|
||||||
|
|
||||||
@@ -211,6 +211,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
|
|
||||||
val inliningResult = inliningCalculator.calculate(
|
val inliningResult = inliningCalculator.calculate(
|
||||||
program,
|
program,
|
||||||
|
platform.bankLayouts,
|
||||||
options.flags(CompilationFlag.InlineFunctions) || options.flags(CompilationFlag.OptimizeForSonicSpeed),
|
options.flags(CompilationFlag.InlineFunctions) || options.flags(CompilationFlag.OptimizeForSonicSpeed),
|
||||||
if (options.flags(CompilationFlag.OptimizeForSonicSpeed)) 4.0
|
if (options.flags(CompilationFlag.OptimizeForSonicSpeed)) 4.0
|
||||||
else if (options.flags(CompilationFlag.OptimizeForSpeed)) 1.3
|
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
|
bank0.readable(index) = true
|
||||||
index += 1
|
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)
|
printArrayToAssemblyOutput(assembly, name, elementType, items)
|
||||||
@@ -338,11 +341,36 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
val codeAllocators = platform.codeAllocators.mapValues(new VariableAllocator(Nil, _)).view.force
|
val codeAllocators = platform.codeAllocators.mapValues(new VariableAllocator(Nil, _)).view.force
|
||||||
var justAfterCode = platform.codeAllocators.mapValues(a => a.startAt).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 }
|
|
||||||
|
def getLayoutStage(name: String, segment: String): Int = {
|
||||||
|
var result = platform.bankLayouts(segment).indexOf(name)
|
||||||
|
if (result < 0) {
|
||||||
|
result = platform.bankLayouts(segment).indexOf("*")
|
||||||
|
}
|
||||||
|
// log.trace(s"Emit stage for ${name} is $result")
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
val sortedCompilerFunctions = compiledFunctions.toList.sortBy { case (_, cf) => cf.orderKey }
|
||||||
|
for (layoutStage <- 0 until layoutStageCount) {
|
||||||
sortedCompilerFunctions.filterNot(o => unusedRuntimeObjects(o._1)).foreach {
|
sortedCompilerFunctions.filterNot(o => unusedRuntimeObjects(o._1)).foreach {
|
||||||
case (_, NormalCompiledFunction(_, _, true, _)) =>
|
case (_, NormalCompiledFunction(_, _, true, _)) =>
|
||||||
// already done before
|
// already done before
|
||||||
case (name, NormalCompiledFunction(bank, functionCode, false, alignment)) =>
|
case (name, th@NormalCompiledFunction(bank, functionCode, false, alignment)) if layoutStage == getLayoutStageNcf(name, th) =>
|
||||||
val size = functionCode.map(_.sizeInBytes).sum
|
val size = functionCode.map(_.sizeInBytes).sum
|
||||||
val bank0 = mem.banks(bank)
|
val bank0 = mem.banks(bank)
|
||||||
val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment)
|
val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment)
|
||||||
@@ -351,12 +379,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
sortedCompilerFunctions.foreach {
|
sortedCompilerFunctions.foreach {
|
||||||
case (name, RedirectedFunction(_, target, offset)) =>
|
case (name, RedirectedFunction(segment, target, offset)) if (layoutStage == getLayoutStage(target, segment)) =>
|
||||||
val tuple = labelMap(target)
|
val tuple = labelMap(target)
|
||||||
labelMap(name) = tuple._1 -> (tuple._2 + offset)
|
labelMap(name) = tuple._1 -> (tuple._2 + offset)
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
if (layoutStage == 0) {
|
||||||
// force early allocation of text literals:
|
// force early allocation of text literals:
|
||||||
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
|
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
|
||||||
case thing@InitializedArray(_, _, items, _, _, _, _, _) =>
|
case thing@InitializedArray(_, _, items, _, _, _, _, _) =>
|
||||||
@@ -365,8 +393,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
env.eval(value)
|
env.eval(value)
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.flag(CompilationFlag.LUnixRelocatableCode)) {
|
if (layoutStage == defaultStage && options.flag(CompilationFlag.LUnixRelocatableCode)) {
|
||||||
env.allThings.things.foreach {
|
env.allThings.things.foreach {
|
||||||
case (_, m@UninitializedMemoryVariable(name, typ, _, _, _, _)) if name.endsWith(".addr") || env.maybeGet[Thing](name + ".array").isDefined =>
|
case (_, m@UninitializedMemoryVariable(name, typ, _, _, _, _)) if name.endsWith(".addr") || env.maybeGet[Thing](name + ".array").isDefined =>
|
||||||
val isUsed = compiledFunctions.values.exists {
|
val isUsed = compiledFunctions.values.exists {
|
||||||
@@ -408,22 +437,22 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
assembly.append(" " + bytePseudoopcode + " 2 ;; end of LUnix relocatable segment")
|
assembly.append(" " + bytePseudoopcode + " 2 ;; end of LUnix relocatable segment")
|
||||||
justAfterCode += "default" -> (index + 1)
|
justAfterCode += "default" -> (index + 1)
|
||||||
}
|
}
|
||||||
|
if (layoutStage == 0) {
|
||||||
env.getAllFixedAddressObjects.foreach {
|
env.getAllFixedAddressObjects.foreach {
|
||||||
case (bank, addr, size) =>
|
case (bank, addr, size) =>
|
||||||
val bank0 = mem.banks(bank)
|
val bank0 = mem.banks(bank)
|
||||||
for (i <- 0 until size) bank0.occupied(addr + i) = true
|
for (i <- 0 until size) bank0.occupied(addr + i) = true
|
||||||
variableAllocators(bank).notifyAboutHole(bank0, addr, size)
|
variableAllocators(bank).notifyAboutHole(bank0, addr, size)
|
||||||
}
|
}
|
||||||
var rwDataStart = Int.MaxValue
|
}
|
||||||
var rwDataEnd = 0
|
|
||||||
for (readOnlyPass <- Seq(true, false)) {
|
for (readOnlyPass <- Seq(true, false)) {
|
||||||
if (!readOnlyPass) {
|
if (layoutStage == defaultStage && !readOnlyPass) {
|
||||||
if (options.platform.ramInitialValuesBank.isDefined) {
|
if (options.platform.ramInitialValuesBank.isDefined) {
|
||||||
codeAllocators("default").notifyAboutEndOfCode(codeAllocators("default").heapStart)
|
codeAllocators("default").notifyAboutEndOfCode(codeAllocators("default").heapStart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
|
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
|
||||||
case thing@InitializedArray(name, None, items, _, _, elementType, readOnly, alignment) if readOnly == readOnlyPass =>
|
case thing@InitializedArray(name, None, items, _, _, elementType, readOnly, alignment) if readOnly == readOnlyPass && layoutStage == getLayoutStageThing(thing) =>
|
||||||
val bank = thing.bank(options)
|
val bank = thing.bank(options)
|
||||||
if (options.platform.ramInitialValuesBank.isDefined && !readOnly && bank != "default") {
|
if (options.platform.ramInitialValuesBank.isDefined && !readOnly && bank != "default") {
|
||||||
log.error(s"Preinitialized writable array `$name` should be defined in the `default` bank")
|
log.error(s"Preinitialized writable array `$name` should be defined in the `default` bank")
|
||||||
@@ -438,19 +467,22 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
assembly.append("* = $" + index.toHexString)
|
assembly.append("* = $" + index.toHexString)
|
||||||
assembly.append(name + ":")
|
assembly.append(name + ":")
|
||||||
for (item <- items) {
|
for (item <- items) {
|
||||||
|
val w = env
|
||||||
env.eval(item) match {
|
env.eval(item) match {
|
||||||
case Some(c) =>
|
case Some(c) =>
|
||||||
for (i <- 0 until elementType.size) {
|
for (i <- 0 until elementType.size) {
|
||||||
writeByte(bank, index, subbyte(c, i, elementType.size))
|
writeByte(bank, index, subbyte(c, i, elementType.size))
|
||||||
index += 1
|
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)
|
printArrayToAssemblyOutput(assembly, name, elementType, items)
|
||||||
initializedVariablesSize += items.length
|
initializedVariablesSize += items.length
|
||||||
justAfterCode += bank -> index
|
justAfterCode += bank -> index
|
||||||
case m@InitializedMemoryVariable(name, None, typ, value, _, alignment, _) if !readOnlyPass=>
|
case m@InitializedMemoryVariable(name, None, typ, value, _, alignment, _) if !readOnlyPass && layoutStage == getLayoutStageThing(m) =>
|
||||||
val bank = m.bank(options)
|
val bank = m.bank(options)
|
||||||
if (options.platform.ramInitialValuesBank.isDefined && bank != "default") {
|
if (options.platform.ramInitialValuesBank.isDefined && bank != "default") {
|
||||||
log.error(s"Preinitialized variable `$name` should be defined in the `default` bank")
|
log.error(s"Preinitialized variable `$name` should be defined in the `default` bank")
|
||||||
@@ -474,6 +506,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
case None =>
|
case None =>
|
||||||
|
env.debugConstness(value)
|
||||||
log.error(s"Non-constant initial value for variable `$name`")
|
log.error(s"Non-constant initial value for variable `$name`")
|
||||||
index += typ.size
|
index += typ.size
|
||||||
}
|
}
|
||||||
@@ -482,6 +515,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
|||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (rwDataEnd == 0 && rwDataStart == Int.MaxValue) {
|
if (rwDataEnd == 0 && rwDataStart == Int.MaxValue) {
|
||||||
rwDataStart = 0
|
rwDataStart = 0
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,7 @@ abstract class AbstractInliningCalculator[T <: AbstractCode] {
|
|||||||
private val sizes = Seq(64, 64, 8, 6, 5, 5, 4)
|
private val sizes = Seq(64, 64, 8, 6, 5, 5, 4)
|
||||||
|
|
||||||
def calculate(program: Program,
|
def calculate(program: Program,
|
||||||
|
bankLayouts: Map[String, Seq[String]],
|
||||||
inlineByDefault: Boolean,
|
inlineByDefault: Boolean,
|
||||||
aggressivenessForNormal: Double,
|
aggressivenessForNormal: Double,
|
||||||
aggressivenessForRecommended: Double): InliningResult = {
|
aggressivenessForRecommended: Double): InliningResult = {
|
||||||
@@ -48,6 +49,7 @@ abstract class AbstractInliningCalculator[T <: AbstractCode] {
|
|||||||
|| f.interrupt
|
|| f.interrupt
|
||||||
|| f.reentrant
|
|| f.reentrant
|
||||||
|| f.name == "main"
|
|| f.name == "main"
|
||||||
|
|| bankLayouts.values.exists(_.contains(f.name))
|
||||||
|| containsReturnDispatch(f.statements.getOrElse(Nil))) badFunctions += f.name
|
|| containsReturnDispatch(f.statements.getOrElse(Nil))) badFunctions += f.name
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
@@ -174,7 +174,7 @@ abstract class Deduplicate[T <: AbstractCode](env: Environment, options: Compila
|
|||||||
val representative = if (set("main")) "main" else set.head
|
val representative = if (set("main")) "main" else set.head
|
||||||
options.log.debug(s"Functions [${set.mkString(",")}] are identical")
|
options.log.debug(s"Functions [${set.mkString(",")}] are identical")
|
||||||
for (function <- set) {
|
for (function <- set) {
|
||||||
if (function != representative) {
|
if (function != representative && !options.platform.bankLayouts(segmentName).contains(function)) {
|
||||||
result += function -> RedirectedFunction(segmentName, representative, 0)
|
result += function -> RedirectedFunction(segmentName, representative, 0)
|
||||||
} else {
|
} else {
|
||||||
segContents(function) match {
|
segContents(function) match {
|
||||||
@@ -216,6 +216,7 @@ abstract class Deduplicate[T <: AbstractCode](env: Environment, options: Compila
|
|||||||
else getJump(code.last)
|
else getJump(code.last)
|
||||||
.filter(segContents.contains)
|
.filter(segContents.contains)
|
||||||
.filter(_ != name)
|
.filter(_ != name)
|
||||||
|
.filter(n => !options.platform.bankLayouts(segmentName).contains(n))
|
||||||
.filter(_ != "main")
|
.filter(_ != "main")
|
||||||
.flatMap(to => follow(segContents, to))
|
.flatMap(to => follow(segContents, to))
|
||||||
.map(name -> _)
|
.map(name -> _)
|
||||||
@@ -250,6 +251,7 @@ abstract class Deduplicate[T <: AbstractCode](env: Environment, options: Compila
|
|||||||
else getJump(code.last)
|
else getJump(code.last)
|
||||||
.filter(segContents.contains)
|
.filter(segContents.contains)
|
||||||
.filter(_ != name)
|
.filter(_ != name)
|
||||||
|
.filter(n => !options.platform.bankLayouts(segmentName).contains(n))
|
||||||
.filter(_ != "main")
|
.filter(_ != "main")
|
||||||
.map(name -> _)
|
.map(name -> _)
|
||||||
case _ => None
|
case _ => None
|
||||||
|
@@ -36,6 +36,7 @@ object EmuPlatform {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Map("default" -> 0, "second" -> 2, "third" -> 3),
|
Map("default" -> 0, "second" -> 2, "third" -> 3),
|
||||||
|
Map("default" -> Seq("main", "*"), "second" -> Seq("main", "*"), "third" -> Seq("main", "*")),
|
||||||
Map("default" -> 0, "second" -> 0, "third" -> 0),
|
Map("default" -> 0, "second" -> 0, "third" -> 0),
|
||||||
"default",
|
"default",
|
||||||
None,
|
None,
|
||||||
|
Reference in New Issue
Block a user