org.devzendo.tma.codegen.AssemblyModel.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tma-assembler Show documentation
Show all versions of tma-assembler Show documentation
The Transputer Macro Asesmbler Code
(Apache License v2) 2018-2019 Matt Gumbley, DevZendo.org
The newest version!
/*
* Copyright (C) 2008-2018 Matt Gumbley, DevZendo.org http://devzendo.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.devzendo.tma.codegen
import org.devzendo.commoncode.string.HexDump
import org.devzendo.tma.ast.AST.SymbolName
import org.devzendo.tma.ast._
import org.log4s.Logger
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
object Endianness extends Enumeration {
val Little, Big = Value
}
sealed abstract class SourcedValue(val line: Line)
// Macro expansions have the same line number as their invocation; hence line number -> list[storage+]
case class Storage(address: Int, cellWidth: Int, data: Array[Int], override val line: Line, exprs: List[Expression]) extends SourcedValue(line)
// Constant and Variable assignments are recalled against the line that sets them to a particular value
case class AssignmentValue(data: Int, override val line: Line, symbolType: SymbolType.Value) extends SourcedValue(line)
case class SymbolTableEntry(casedSymbolName: CasedSymbolName, value: Int)
object SymbolType extends Enumeration {
// This used to be named 'UnresolvableSymbolType', but now is used to denote symbol types for resolved ones.
// Before implementing Convergence, this could not be a Label, as this is always set to $, which was always known.
// Now blocks of code can converge, due to variable-length direct instruction encodings, a label's value can
// change - and we need to record whether a symbol is a label, for fixing up Storage.
val Variable, Constant, Label = Value
}
case class UnresolvableSymbol(line: Line, symbolType: SymbolType.Value, casedSymbolName: CasedSymbolName, expr: Expression)
class SymbolForwardReferenceFixupState {
var resolutionCount: Int = 0
val unresolvableSymbols: mutable.HashSet[UnresolvableSymbol] = mutable.HashSet[UnresolvableSymbol]()
def += (us: UnresolvableSymbol): Unit = {
unresolvableSymbols += us
}
def nonEmpty: Boolean = unresolvableSymbols.nonEmpty
def resolve(): Unit = {
resolutionCount += 1
}
override def toString: SymbolName = s"resolutions $resolutionCount: references: $unresolvableSymbols"
}
class SymbolForwardReferenceFixups {
val logger: Logger = org.log4s.getLogger
private val map = mutable.HashMap[CasedSymbolName, SymbolForwardReferenceFixupState]()
def dump(): Unit = {
logger.debug(s"Symbol forward reference fixups: Size ${size()}")
def logEntry(entry: (CasedSymbolName, SymbolForwardReferenceFixupState)): Unit = {
logger.debug(s"Undefined Symbol ${entry._1}; Resolution Count: ${entry._2.resolutionCount}")
val references = entry._2.unresolvableSymbols
references.foreach((us: UnresolvableSymbol) => {
logger.debug(s" Unresolvable ${us.symbolType} ${us.casedSymbolName} line number ${us.line.number}")
})
}
map.foreach (logEntry)
}
def -= (us: UnresolvableSymbol): Unit = {
logger.debug(s"Removing $us from all entries in the fixup map")
map.foreach (_._2.unresolvableSymbols -= us)
}
def += (casedSymbolName: CasedSymbolName, us: UnresolvableSymbol): Unit = {
logger.debug(s"Adding $us as needing fixup when $casedSymbolName is defined in the fixup map")
getOrElseCreate(casedSymbolName) += us
}
private def getOrElseCreate(casedSymbolName: CasedSymbolName): SymbolForwardReferenceFixupState = {
map.getOrElseUpdate(casedSymbolName, new SymbolForwardReferenceFixupState())
}
def resolve(casedSymbolName: CasedSymbolName): Unit = {
map.get(casedSymbolName) match {
case Some(fixupState) => fixupState.resolve()
case None =>
}
}
def size(): Int = map.size
def resolutionCount(casedSymbolName: CasedSymbolName): Int = {
map.get(casedSymbolName) match {
case Some(fixupState) => fixupState.resolutionCount
case None => 0
}
}
def getUnresolvableSymbols(casedSymbolName: CasedSymbolName): Set[UnresolvableSymbol] = {
map.get(casedSymbolName) match {
case Some(fixupState) => fixupState.unresolvableSymbols.toSet
case None => Set.empty
}
}
def allUnresolvedSymbolForwardReferences(): Map[CasedSymbolName, SymbolForwardReferenceFixupState] = {
def isUnresolved(mapEntry: (CasedSymbolName, SymbolForwardReferenceFixupState)): Boolean = {
mapEntry._2.resolutionCount == 0
}
val unresolved = map.filter(isUnresolved).toMap
logger.debug("Unresolved symbols: " + unresolved)
unresolved
}
}
class StorageForwardReferenceFixups {
val logger: Logger = org.log4s.getLogger
private val map = mutable.HashMap[CasedSymbolName, mutable.HashSet[Storage]]()
private val resolutionCount = mutable.HashMap[CasedSymbolName, Integer]()
def dump(): Unit = {
logger.debug(s"Storage forward reference fixups: Size ${map.size}")
map.keySet.foreach ((casedSymbolName: CasedSymbolName) => {
logger.debug(s" Undefined Symbol ${map(casedSymbolName)}; Resolution Count: ${resolutionCount(casedSymbolName)}")
})
}
def getSymbolReferences(casedSymbolName: CasedSymbolName): Set[Storage] = {
map.getOrElse(casedSymbolName, Set.empty).toSet
}
def unresolvedStorages(): Map[CasedSymbolName, Set[Storage]] = {
val unresolveds = map.filter { case (symbol, _) => resolutionCount(symbol) == 0 }
// return deep immutable version
unresolveds.map {case (symbol, storageSet) => (symbol, storageSet.toSet)}.toMap
}
def += (casedSymbolName: CasedSymbolName, storageToReEvaluate: Storage): mutable.Set[Storage] = {
logger.debug(s"Adding storage for symbol $casedSymbolName, set resolution count to 0")
resolutionCount.put(casedSymbolName, 0)
map.getOrElseUpdate(casedSymbolName, mutable.HashSet[Storage]()) += storageToReEvaluate
}
def resolve(casedSymbolName: CasedSymbolName, symbolType: SymbolType.Value): Unit = {
logger.debug(s"Resolving symbol $casedSymbolName as a $symbolType")
// Undefined Constants and Labels can be initially defined, and redefined during Convergence; So can
// Variables - but they are defined once, and redefinition is not allowed.
symbolType match {
case SymbolType.Variable =>
map.remove(casedSymbolName)
case _ =>
// Constants and Labels track changes, so don't get removed.
}
val increment = resolutionCount.getOrElseUpdate(casedSymbolName, -1) + 1
resolutionCount.put(casedSymbolName, increment)
logger.debug(s"Resolved resolution count of $casedSymbolName is now " + resolutionCount.get(casedSymbolName))
}
}
/*
* A mutable structure holding the output of the CodeGenerator.
*/
class AssemblyModel(debugCodegen: Boolean) {
val logger: Logger = org.log4s.getLogger
private val dollar = CasedSymbolName("$")
var title = ""
var rows = 25
var columns = 80
var processor: Option[String] = None
// "Converge Mode" is used when converging sequences of DirectInstructions containing undefined symbols: label and
// constant addresses may be adjusted in this state. If not in this state, then throw on reassignment.
var convergeMode: Boolean = false
case class Value(value: Int, symbolType: SymbolType.Value, definitionLine: Int)
private val symbols = mutable.HashMap[CasedSymbolName, Value]()
// All incoming Lines (original-in-source and macro expansion lines) are appended here after they have been
// transformed by any StatementTransformers. Recall that macro expansion lines will have the same line number as
// original-in-source lines.
private val lines = mutable.ArrayBuffer[Line]()
// SourcedValues has a reference to its Line, so when the map of Undefined forward references -> Set[Storage]
// is scanned at the end of the codegen phase, each Storage can show the Line on which the forward reference is.
private val sourcedValuesForLineNumbers = mutable.HashMap[Int, mutable.ArrayBuffer[SourcedValue]]() // indexed by line number
// And it's a map, since it's likely to be sparsely populated (not every line generates Storage)
// Recall that macro expansions could lead to multiple entries here for a given line number, hence the ArrayBuffer.
// Forward references are only resolved for Storages and Constants.
// Any forward references to Storages are noted here, and resolution done on definition of the forwardly-referenced
// symbol (either a variable, constant, or label).
private val storageForwardReferenceFixups = new StorageForwardReferenceFixups()
// Any forward references to Constants/Variables are noted here, and resolution done on definition of the
// forwardly-referenced symbol (either a variable, constant, or label).
private val symbolForwardReferenceFixups = new SymbolForwardReferenceFixups()
private var endSeen = false
var endianness: Endianness.Value = Endianness.Big
// Initialise $ without storing back reference to a Line, since there isn't one.
setDollarSilently(0)
def getDollar: Int = getVariable(dollar)
def setDollar(n: Int, line: Line): Unit = {
setVariable(dollar, n, line)
}
def setDollarSilently(n: Int): Unit = {
// Set $ without storing back reference to a Line, since there isn't one.
logger.debug("Variable $ (silently) = " + n)
symbols.put(dollar, Value(n, SymbolType.Variable, 0))
}
def incrementDollar(n: Int): Unit = {
setDollarSilently(getDollar + n)
}
def getVariable(casedSymbolName: CasedSymbolName): Int = {
getSymbolValue(SymbolType.Variable, casedSymbolName)
}
def variable(casedSymbolName: CasedSymbolName): Option[Int] = {
maybeSymbol(SymbolType.Variable, casedSymbolName)
}
def setVariable(casedSymbolName: CasedSymbolName, n: Int, line: Line): Unit = {
setVariableInternal(n, line, casedSymbolName, SymbolType.Variable)
}
def getConstant(casedSymbolName: CasedSymbolName): Int = {
getSymbolValue(SymbolType.Constant, casedSymbolName)
}
def constant(casedSymbolName: CasedSymbolName): Option[Int] = {
maybeSymbol(SymbolType.Constant, casedSymbolName)
}
def setConstant(casedSymbolName: CasedSymbolName, n: Int, line: Line): Unit = {
setConstantOrLabelInternal(n, line, casedSymbolName, SymbolType.Constant)
}
def getLabel(casedSymbolName: CasedSymbolName): Int = {
getSymbolValue(SymbolType.Label, casedSymbolName)
}
def label(casedSymbolName: CasedSymbolName): Option[Int] = {
maybeSymbol(SymbolType.Label, casedSymbolName)
}
def setLabel(casedSymbolName: CasedSymbolName, n: Int, line: Line): Unit = {
setConstantOrLabelInternal(n, line, casedSymbolName, SymbolType.Label)
}
private def setVariableInternal(n: Int, line: Line, casedSymbolName: CasedSymbolName, symbolType: SymbolType.Value): Unit = {
symbols.get(casedSymbolName) match {
case Some(Value(_, `symbolType`, _)) => // drop through to reassign
case Some(sym) => throw new AssemblyModelException(symbolType + " '" + casedSymbolName + "' cannot override existing " + sym.symbolType.toString.toLowerCase + "; initially defined on line " + sym.definitionLine)
case None => // drop through
}
storeSymbolInternal(n, line, casedSymbolName, symbolType)
}
private def setConstantOrLabelInternal(n: Int, line: Line, casedSymbolName: CasedSymbolName, symbolType: SymbolType.Value): Unit = {
// Allow replacement...
if (convergeMode && symbolExists(symbolType, casedSymbolName))
symbols.remove(casedSymbolName)
symbols.get(casedSymbolName) match {
case Some(sym) => throw new AssemblyModelException(symbolType + " '" + casedSymbolName + "' cannot override existing " + sym.symbolType.toString.toLowerCase + "; defined on line " + sym.definitionLine)
case None => storeSymbolInternal(n, line, casedSymbolName, symbolType)
}
}
private def storeSymbolInternal(n: Int, line: Line, casedSymbolName: CasedSymbolName, symbolType: SymbolType.Value): Unit = {
symbols.put(casedSymbolName, Value(n, symbolType, line.number))
sourcedValuesArrayBufferForLineNumber(line.number) += AssignmentValue(n, line, symbolType)
if (debugCodegen) {
logger.info(symbolType + " " + casedSymbolName + " = " + n)
}
resolveForwardReferences(casedSymbolName, n, symbolType)
}
private def maybeSymbol(requiredSymbolType: SymbolType.Value, casedSymbolName: CasedSymbolName) = {
symbols.get(casedSymbolName) match {
case Some(sym) => if (sym.symbolType == requiredSymbolType) Some(sym.value) else None
case None => None
}
}
private def symbolExists(requiredSymbolType: SymbolType.Value, casedSymbolName: CasedSymbolName): Boolean = {
maybeSymbol(requiredSymbolType, casedSymbolName).isDefined
}
private def getSymbolValue(requiredSymbolType: SymbolType.Value, casedSymbolName: CasedSymbolName) = {
symbols.get(casedSymbolName) match {
case Some(Value(value, `requiredSymbolType`, _)) => value
case _ => throw new AssemblyModelException(requiredSymbolType + " '" + casedSymbolName + "' has not been defined")
}
}
def getLabelsAndConstants: List[SymbolTableEntry] = {
def toSTE(pair: (CasedSymbolName, Value)): SymbolTableEntry = {
SymbolTableEntry(pair._1, pair._2.value)
}
val labelsAndConstantsList = symbols.toList.filter(
(p: (CasedSymbolName, Value)) => { p._2.symbolType == SymbolType.Label || p._2.symbolType == SymbolType.Constant })
labelsAndConstantsList.map(toSTE)
}
def getConvergeMode: Boolean = convergeMode
def setConvergeMode(newMode: Boolean): Unit = {
convergeMode = newMode
}
/**
* Evaluate an expression, returning Left(Set(undefined variable names)) or Right(value)
*
* @param expr some expression, of any complexity
* @return undefined variable names, or the evaluated value.
*/
def evaluateExpression(expr: Expression): Either[Set[CasedSymbolName], Int] = {
//logger.debug("Evaluating " + expr)
val undefineds = findUndefineds(expr)
if (undefineds.nonEmpty) {
//logger.debug("Undefined symbols: " + undefineds)
Left(undefineds)
} else {
val value = evaluateExpressionWithNoUndefineds(expr)
//logger.debug("Evaluation of " + expr + " = " + value)
Right(value)
}
}
// precondition: all SymbolArgs here are defined as a variable/constant/label
private def evaluateExpressionWithNoUndefineds(expr: Expression): Int = {
expr match {
case SymbolArg(name) => lookupValue(CasedSymbolName(name))
case Number(n) => n
case Characters(_) => throw new AssemblyModelException("Cannot evaluate '" + expr + "' as an Int")
case Unary(op, uExpr) => evaluateUnary(op, uExpr)
case Binary(op, lExpr, rExpr) => evaluateBinary(op, lExpr, rExpr)
}
}
// precondition: name is defined as a variable/constant/label
private def lookupValue(name: CasedSymbolName): Int = {
def getFallback(symbolType: SymbolType.Value, key: CasedSymbolName)(fallback: => Int): Int = {
symbols.get(key) match {
case Some(x) => if (x.symbolType == symbolType) x.value else fallback
case None => fallback
}
}
getFallback(SymbolType.Variable, name) (getFallback(SymbolType.Constant, name) (getFallback(SymbolType.Label, name) ({
throw new IllegalStateException("Precondition violation: " + name + " is supposed to be present as a variable/constant/label")
})))
}
def definedValue(casedSymbolName: CasedSymbolName): Boolean = {
symbols.contains(casedSymbolName)
}
def findUndefineds(expr: Expression): Set[CasedSymbolName] = {
expr match {
case SymbolArg(name) => if (definedValue(CasedSymbolName(name))) Set.empty else Set(CasedSymbolName(name))
case Number(_) => Set.empty
case Characters(_) => Set.empty
case Unary(_, uExpr) => findUndefineds(uExpr)
case Binary(_, lExpr, rExpr) => findUndefineds(lExpr) ++ findUndefineds(rExpr)
}
}
def containsUndefineds(expr: Expression): Boolean = {
findUndefineds(expr).nonEmpty
}
// precondition: expr has no undefineds
private def evaluateUnary(op: Operator, expr: Expression): Int = {
val value = evaluateDefinedExpression(expr)
op match {
case Negate() => value * -1
case Not() => ~ value
case OffsetFrom(storedDollar) =>
val ret = value - storedDollar
logger.debug("Offset of value=%d (0x%x) and stored $=%d (0x%x): %d (0x%x)".format(value, value, storedDollar, storedDollar, ret, ret))
ret
case Offset() =>
throw new IllegalStateException("Offset should have been transformed to an OffsetFrom")
case _ =>
throw new IllegalStateException("Parser has passed an operation of " + op + " to a Unary")
}
}
private def evaluateDefinedExpression(expr: Expression): Int = {
evaluateExpression(expr) match {
case Right(value) => value
case Left(_) => throw new IllegalStateException("Precondition violation: " + expr + " contains undefined symbols")
}
}
// precondition: lExpr, rExpr have no undefineds
private def evaluateBinary(op: Operator, lExpr: Expression, rExpr: Expression): Int = {
val lValue = evaluateDefinedExpression(lExpr)
val rValue = evaluateDefinedExpression(rExpr)
op match {
case Add() => lValue + rValue
case Sub() => lValue - rValue
case Mult() => lValue * rValue
case Div() => lValue / rValue
case ShiftLeft() => lValue << rValue
case ShiftRight() => lValue >> rValue
case And() => lValue & rValue
case Or() => lValue | rValue
case Xor() => lValue ^ rValue
case _ => throw new IllegalStateException("Parser has passed an operation of " + op + " to a Binary")
}
}
def addLine(line: Line): Unit = {
lines += line
}
def getSourcedValuesForLineNumber(lineNumber: Int): List[SourcedValue] = {
sourcedValuesForLineNumbers.getOrElse(lineNumber, ArrayBuffer[SourcedValue]()).toList
}
private def sourcedValuesArrayBufferForLineNumber(lineNumber: Int) = {
sourcedValuesForLineNumbers.getOrElseUpdate(lineNumber, mutable.ArrayBuffer[SourcedValue]())
}
private def getUnsignedInt(x: Int): Long = x & 0x00000000ffffffffL
private def validateDataSizes(lineNumber: Int, data: Array[Int], cellWidth: Int): Unit = {
val (max, name) = cellWidth match {
case 1 => (0xffL, "BYTE")
case 2 => (0xffffL, "WORD")
case 4 => (0xffffffffL, "DWORD")
}
for (d <- data) {
val dUnsigned = getUnsignedInt(d)
//logger.debug("cellWidth " + cellWidth + "; max " + max + "; name " + name + "; data " + dUnsigned)
if (dUnsigned < 0 || dUnsigned > max) {
throw new AssemblyModelException("Value of " + dUnsigned + " cannot be expressed in a " + name + " on line " + lineNumber)
}
}
}
private def expandCharacterExpressions(possCharacterExprs: List[Expression]): List[Expression] = {
possCharacterExprs.flatMap((expr: Expression) => {
expr match {
case Characters(chars) =>
chars.map((char: Char) => Number(char.toInt)).toList
case _ =>
List(expr)
}
})
}
def allocateStorageForLine(line: Line, cellWidth: Int, exprs: List[Expression]): Storage = {
val lineNumber = line.number
val existingSourcedValues = sourcedValuesArrayBufferForLineNumber(lineNumber)
// The incoming exprs will need evaluating to numbers that are stored in the Storage's data field. Most
// expressions are evaluated to a single number, but Characters are evaluated to multiple. So expand all
// elements of a Characters expression to an individual Number.
val characterExpandedExprs = expandCharacterExpressions(exprs)
val storage = Storage(getDollar, cellWidth, Array.ofDim[Int](characterExpandedExprs.size), line, characterExpandedExprs)
existingSourcedValues += storage
// Evaluate expressions, storing, or record forward references if symbols are undefined at the moment.
characterExpandedExprs.zipWithIndex.foreach((tuple: (Expression, Int)) => {
val storeValue = evaluateExpression(tuple._1) match {
case Right(value) => value
case Left(undefineds) =>
if (debugCodegen) {
logger.info("Symbol(s) (" + undefineds + ") are not yet defined on line " + lineNumber)
}
recordStorageForwardReferences(undefineds, storage)
0
}
storage.data(tuple._2) = storeValue
})
validateDataSizes(lineNumber, storage.data, cellWidth)
dumpStorage(storage)
incrementDollar(cellWidth * characterExpandedExprs.size)
storage
}
private def dumpStorage(storage: Storage): Unit = {
if (debugCodegen) {
def widthToDx(width: Int) = width match {
case 1 => "DB"
case 2 => "DW"
case 4 => "DD"
}
def data(width: Int, data: Array[Int]) = {
val hexnumStrings = data.map((d: Int) => {
width match {
case 1 => HexDump.byte2hex(d.toByte)
case 2 => HexDump.short2hex(d.toShort)
case 4 => HexDump.int2hex(d)
}
})
hexnumStrings.mkString(" ")
}
// This diagnostic does not honour endianness.
logger.info(s"Storage @ ${HexDump.int2hex(storage.address)} ${widthToDx(storage.cellWidth)} (${data(storage.cellWidth, storage.data)})")
}
}
def clearSourcedValuesForLineNumber(lineNumber: Int): Unit = {
if (debugCodegen) {
logger.debug("Clearing sourced values for line number " + lineNumber)
}
sourcedValuesForLineNumbers.remove(lineNumber)
}
def allocateInstructionStorageForLine(line: Line, opbytes: List[Int]): Storage = {
val lineNumber = line.number
val existingSourcedValues = sourcedValuesArrayBufferForLineNumber(lineNumber)
val storage = Storage(getDollar, 1, opbytes.toArray, line, opbytes map { Number })
existingSourcedValues += storage
validateDataSizes(lineNumber, storage.data, 1) // they should be instruction bytes, so this shouldn't fail
dumpStorage(storage)
incrementDollar(opbytes.size)
storage
}
// Note that this will give you the original-in-source and macro expansion Lines, and for each Storage,
// back-references to its originating Line. (Which will be the Line of the first argument.)
// Each Line can have more than one SourcedValue: e.g. a Label on the same line as a DB generates an AssignmentValue
// and a Storage.
def foreachLineSourcedValues(op: (Line, List[SourcedValue]) => Unit): Unit = {
for (line <- lines) { // can have many macro expanded lines' storages for this line number
val lineNumber = line.number
val sourcedValues = getSourcedValuesForLineNumber(lineNumber)
val sourcedValuesForThisLine = sourcedValues.filter((s: SourcedValue) => {s.line == line}) // NB: don't compare on line number!
op(line, sourcedValuesForThisLine)
// So: line is the original Line, sourcedValues(x).line is always the Line that caused it, could be the original
// Line or a macro expansion from it
// And: sourcedValues could be empty, if there's no sourced value for Line
}
}
def allLines(): List[Line] = {
lines.toList
}
// Note, this does not get you the original-in-source Lines, only those Lines that have had Storage allocated.
def foreachSourcedValue(op: (Int, List[SourcedValue]) => Unit): Unit = {
val lineNumbers = sourcedValuesForLineNumbers.keySet.toList.sorted
lineNumbers.foreach(num => {
val sourcedValues = getSourcedValuesForLineNumber(num)
op(num, sourcedValues)
})
}
// Called when a Constant or Variable is defined with one or more currently-undefined symbols in its expression.
// Records the Constant/Variable against each of those currently-undefined symbols, so that when they are defined,
// the Constant/Variable can be evaluated.
//
// unresolvableSymbolName is of type unresolvableSymbolType, has Expression unresolvableExpr which cannot be
// evaluated since it has undefined symbols, undefinedSymbols; unresolvableSymbolName is declared on line line.
// e.g. A EQU B+C*2
// where B and C are undefined...
// unresolvableSymbolName=A, unresolvableExpr=B+C*2, undefinedSymbols={B,C},
// unresolvableSymbolType=Constant and line = (5, "A EQU B+C*2", None, Some(unresolvableExpr)) (etc.)
def recordSymbolForwardReferences(undefinedSymbols: Set[CasedSymbolName], unresolvableSymbolName: CasedSymbolName,
unresolvableExpr: Expression, line: Line, unresolvableSymbolType: SymbolType.Value): Unit = {
val unresolvableSymbol = UnresolvableSymbol(line, unresolvableSymbolType, unresolvableSymbolName, unresolvableExpr)
for (undefinedSymbol <- undefinedSymbols) {
if (debugCodegen) {
logger.info(s"Recording symbol $unresolvableSymbolName's forward reference to $undefinedSymbol")
}
// For all {B, C} add a reference to UnresolvableSymbol A
// But {B, C} might be referenced from a set of other UnresolvableSymbols e.g. {D, E}
// So B -> {A, D, E}
// and C -> {A, D, E}
symbolForwardReferenceFixups += (undefinedSymbol, unresolvableSymbol) // (B, A) and (C, A)
}
if (debugCodegen) {
symbolForwardReferenceFixups.dump()
}
}
private def recordStorageForwardReferences(undefinedSymbols: Set[CasedSymbolName], storageToReEvaluate: Storage): Unit = {
for (undefinedSymbol <- undefinedSymbols) {
if (debugCodegen) {
logger.info(s"Recording storage forward reference to $undefinedSymbol")
}
storageForwardReferenceFixups += (undefinedSymbol, storageToReEvaluate)
}
if (debugCodegen) {
storageForwardReferenceFixups.dump()
}
}
// The Symbol (Label/Variable/Constant) symbolName has been resolved to a value. Where it had been recorded as
// needing fixing up in Storages or other Symbols, fix up, and if each fix up is complete, remove the record of it
// needing fixing up.
private def resolveForwardReferences(casedSymbolName: CasedSymbolName, value: Int, symbolType: SymbolType.Value): Unit = {
// TODO mark the storage as having had a forward reference resolved, so the R can be shown in the listing
// TODO can the two types of forward reference fixup be generalised?
// Resolve forward references to Storages...
val storagesWithForwardReferences = storageForwardReferences(casedSymbolName)
if (storagesWithForwardReferences.nonEmpty) {
if (debugCodegen) {
logger.info("Resolving Storage references to symbol '" + casedSymbolName + "' with value " + value)
}
for (storage <- storagesWithForwardReferences) {
if (debugCodegen) {
logger.info("Resolving on line " + storage.line.number)
}
// Re-evaluate expressions, storing, and removing the forward reference.
storage.exprs.zipWithIndex.foreach((tuple: (Expression, Int)) => {
val storeValue = evaluateExpression(tuple._1) match {
case Right(result) => result
case Left(_) => 0 // Expr still contains other undefined symbols, so more resolution to do....
}
storage.data(tuple._2) = storeValue
})
dumpStorage(storage)
}
storageForwardReferenceFixups.resolve(casedSymbolName, symbolType)
}
// Resolve forward references to UnresolvableSymbols...
val unresolvableSymbols = unresolvedSymbolForwardReferences(casedSymbolName)
if (unresolvableSymbols.nonEmpty) {
if (debugCodegen) {
logger.info("Resolving Symbol references to symbol '" + casedSymbolName + "' with value " + value)
}
for (unresolvableSymbol <- unresolvableSymbols) {
if (debugCodegen) {
logger.info("Resolving " + unresolvableSymbol.symbolType + " " +
unresolvableSymbol.casedSymbolName + " on line " + unresolvableSymbol.line.number)
}
// Re-evaluate expressions, setting variable or constant, and removing the forward reference if it's a
// variable.
evaluateExpression(unresolvableSymbol.expr) match {
case Right(result) =>
unresolvableSymbol.symbolType match {
case SymbolType.Constant =>
setConstant(unresolvableSymbol.casedSymbolName, result, unresolvableSymbol.line)
// When Converging, constant changes are tracked, so keep the
// unresolvableSymbolReference in the fixup map.
if (debugCodegen) {
logger.debug(s"${unresolvableSymbol.casedSymbolName} is a Constant so not removing from fixup map, so convergence will track changes")
}
case SymbolType.Variable =>
setVariable(unresolvableSymbol.casedSymbolName, result, unresolvableSymbol.line)
// When Converging, variable changes are not tracked, so remove the
// unresolvableSymbolReference in the fixup map, now that it has been set initially.
if (debugCodegen) {
logger.debug(s"${unresolvableSymbol.casedSymbolName} is a Variable and not in Converge mode, so removing from fixup map")
}
symbolForwardReferenceFixups -= unresolvableSymbol
case SymbolType.Label => // This should not happen....
throw new AssemblyModelException("Labels cannot be unresolvable?")
}
case Left(_) => // Expr still contains other undefined symbols, so more resolution to do....
}
symbolForwardReferenceFixups.resolve(casedSymbolName)
symbolForwardReferenceFixups.dump()
}
}
}
private [codegen] def storageForwardReferences(casedSymbolName: CasedSymbolName): Set[Storage] = {
storageForwardReferenceFixups.getSymbolReferences(casedSymbolName)
}
private [codegen] def unresolvedSymbolForwardReferences(casedSymbolName: CasedSymbolName): Set[UnresolvableSymbol] = {
symbolForwardReferenceFixups.getUnresolvableSymbols(casedSymbolName)
}
private [codegen] def resolutionCount(casedSymbolName: CasedSymbolName): Int = {
symbolForwardReferenceFixups.resolutionCount(casedSymbolName: CasedSymbolName)
}
def checkUnresolvedForwardReferences(): Unit = {
// If there are any undefined symbols, sort them alphabetically, and list them with the line numbers they're
// referenced on (sorted numerically). e.g. (aardvark: #1; FNORD: #3, #4; foo: #5; zygote: #1)
val unresolvedStorages: Map[CasedSymbolName, Set[Storage]] = storageForwardReferenceFixups.unresolvedStorages()
if (unresolvedStorages.nonEmpty) {
val undefinedSymbolNamesSorted = unresolvedStorages.keySet.toList.sortWith((a: CasedSymbolName, b: CasedSymbolName) => {
a.toString.compareToIgnoreCase(b.toString) < 0
})
val allStorageNamesAndLineReferences = undefinedSymbolNamesSorted.map((usn: CasedSymbolName) => {
val storageSet = unresolvedStorages(usn)
val storageLinesSorted = storageSet.map(_.line.number).toList.sorted
val storageLineReferences = storageLinesSorted.map("#" + _).mkString(", ")
val storageNameAndLineReferences = usn + ": " + storageLineReferences
storageNameAndLineReferences
})
throw new AssemblyModelException("Storage forward references remain unresolved at end of Pass 1: (" +
allStorageNamesAndLineReferences.mkString("; ") + ")")
}
val unresolvedSymbols = symbolForwardReferenceFixups.allUnresolvedSymbolForwardReferences()
if (unresolvedSymbols.nonEmpty) {
val undefinedSymbolNamesSorted = unresolvedSymbols.keySet.toList.sortWith((a: CasedSymbolName, b: CasedSymbolName) => {
a.toString.compareToIgnoreCase(b.toString) < 0
})
val allStorageNamesAndLineReferences = undefinedSymbolNamesSorted.map((usn: CasedSymbolName) => {
val unresolvableSymbols = unresolvedSymbols(usn).unresolvableSymbols
val unresolvableSymbolLinesSorted = unresolvableSymbols.map(_.line.number).toList.sorted
val unresolvableSymbolLineReferences = unresolvableSymbolLinesSorted.map("#" + _).mkString(", ")
val unresolvableSymbolNameAndLineReferences = usn + ": " + unresolvableSymbolLineReferences
unresolvableSymbolNameAndLineReferences
})
throw new AssemblyModelException("Symbol forward references remain unresolved at end of Pass 1: (" +
allStorageNamesAndLineReferences.mkString("; ") + ")")
}
}
def endHasBeenSeen(): Unit = {
endSeen = true
}
def hasEndBeenSeen: Boolean = endSeen
private var lowStorageAddress = 0
private var highStorageAddress = 0
private def calculateBounds(): Unit = {
if ((lowStorageAddress, highStorageAddress) == (0, 0)) {
if (sourcedValuesForLineNumbers.nonEmpty) {
lowStorageAddress = Int.MaxValue
highStorageAddress = Int.MinValue
for (sourcedValues <- sourcedValuesForLineNumbers.values) {
for (sourcedValue <- sourcedValues) {
sourcedValue match {
case storage: Storage =>
val start = storage.address
val end = start + (storage.cellWidth * storage.data.length) - 1
logger.debug("start " + start + " end " + end + " (" + (end - start + 1) + " byte(s))")
if (start < lowStorageAddress) {
logger.debug("new low bound")
lowStorageAddress = start
}
if (end > highStorageAddress) {
logger.debug("new high bound")
highStorageAddress = end
}
case _ => // do nothing
}
}
}
}
}
}
def lowestStorageAddress: Int = {
calculateBounds()
lowStorageAddress
}
def highestStorageAddress: Int = {
calculateBounds()
highStorageAddress
}
def dump(): Unit = {
// Uses lines storage
foreachLineSourcedValues((line: Line, sourcedValues: List[SourcedValue]) => {
logger.debug("----------")
logger.debug(s"line $line")
for (sv <- sourcedValues) {
sv match {
case st: Storage =>
logger.debug(s" storage data ${st.data.toList} address ${st.address}")
case av: AssignmentValue =>
logger.debug(s" assigned value ${av.data}")
}
}
logger.debug("----------")
})
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy