1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-12 06:29:34 +00:00
millfork/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala
2023-01-27 17:00:29 +01:00

2552 lines
135 KiB
Scala

package millfork.compiler.mos
import millfork.CompilationFlag
import millfork.assembly.Elidability
import millfork.assembly.mos.AddrMode._
import millfork.assembly.mos.Opcode._
import millfork.assembly.mos._
import millfork.compiler._
import millfork.env._
import millfork.node.{MosRegister, _}
import millfork.output.NoAlignment
/**
* @author Karol Stasiak
*/
object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
def compileConstant(ctx: CompilationContext, expr0: Constant, exprType: Type, target: Variable): List[AssemblyLine] = {
val expr = expr0.fitInto(exprType, target.typ)
target match {
case RegisterVariable(MosRegister.A, _) => List(AssemblyLine(LDA, Immediate, expr))
case RegisterVariable(MosRegister.AW, _) =>
List(
AssemblyLine.accu16,
AssemblyLine(LDA_W, WordImmediate, expr),
AssemblyLine.accu8)
case RegisterVariable(MosRegister.X, _) => List(AssemblyLine(LDX, Immediate, expr))
case RegisterVariable(MosRegister.Y, _) => List(AssemblyLine(LDY, Immediate, expr))
case RegisterVariable(MosRegister.AX, _) => List(
AssemblyLine(LDA, Immediate, expr.loByte),
AssemblyLine(LDX, Immediate, expr.hiByte))
case RegisterVariable(MosRegister.AY, _) => List(
AssemblyLine(LDA, Immediate, expr.loByte),
AssemblyLine(LDY, Immediate, expr.hiByte))
case RegisterVariable(MosRegister.XA, _) => List(
AssemblyLine(LDA, Immediate, expr.hiByte),
AssemblyLine(LDX, Immediate, expr.loByte))
case RegisterVariable(MosRegister.YA, _) => List(
AssemblyLine(LDA, Immediate, expr.hiByte),
AssemblyLine(LDY, Immediate, expr.loByte))
case RegisterVariable(MosRegister.XY, _) => List(
AssemblyLine(LDY, Immediate, expr.hiByte),
AssemblyLine(LDX, Immediate, expr.loByte))
case RegisterVariable(MosRegister.YX, _) => List(
AssemblyLine(LDX, Immediate, expr.hiByte),
AssemblyLine(LDY, Immediate, expr.loByte))
case m: VariableInMemory =>
val elidability = if (m.isVolatile) Elidability.Volatile else Elidability.Elidable
val addrMode = if (m.zeropage) ZeroPage else Absolute
val addr = m.toAddress
m.typ.size match {
case 0 => Nil
case 1 => List(
AssemblyLine(LDA, Immediate, expr.loByte),
AssemblyLine(STA, addrMode, addr, elidability = elidability))
case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) ::(AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8)
} else List(
AssemblyLine(LDA, Immediate, expr.loByte),
AssemblyLine(STA, addrMode, addr, elidability = elidability),
AssemblyLine(LDA, Immediate, expr.hiByte),
AssemblyLine(STA, addrMode, addr + 1, elidability = elidability))
case s => List.tabulate(s)(i => List(
AssemblyLine(LDA, Immediate, expr.subbyte(i)),
AssemblyLine(STA, addrMode, addr + i, elidability = elidability))).flatten
}
case m@StackVariable(_, t, offset) =>
t.size match {
case 0 => Nil
case 1 => AssemblyLine.tsx(ctx) ++ List(
AssemblyLine.immediate(LDA, expr.loByte),
AssemblyLine.dataStackX(ctx, STA, offset))
case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) :: (AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8)
} else AssemblyLine.tsx(ctx) ++ List(
// TODO: ???
AssemblyLine.implied(TSX),
AssemblyLine.immediate(LDA, expr.loByte),
AssemblyLine.dataStackX(ctx, STA, offset),
AssemblyLine.immediate(LDA, expr.hiByte),
AssemblyLine.absoluteX(STA, offset + 1))
case s => AssemblyLine.tsx(ctx) ++ List.tabulate(s)(i => List(
AssemblyLine.immediate(LDA, expr.subbyte(i)),
AssemblyLine.dataStackX(ctx, STA, offset + i))).flatten
}
}
}
def fixTsx(code: List[AssemblyLine]): List[AssemblyLine] = code match {
case (access@AssemblyLine0(_, Stack | IndexedSY, p)) :: xs => access.copy(parameter = (p + 1).quickSimplify) :: fixTsx(xs)
case (tsx@AssemblyLine0(TSX, _, _)) :: xs => tsx :: AssemblyLine.implied(INX) :: fixTsx(xs)
case (txs@AssemblyLine0(TXS, _, _)) :: xs => ???
case x :: xs => x :: fixTsx(xs)
case Nil => Nil
}
def preserveZpregIfNeededDestroyingAAndX(ctx: CompilationContext, Offset: Int, code: List[AssemblyLine]): List[AssemblyLine] = {
if (changesZpreg(code, Offset)) {
List(AssemblyLine.zeropage(LDA, ctx.env.get[VariableInMemory]("__reg"), Offset), AssemblyLine.implied(PHA)) ++
code ++
List(
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, ctx.env.get[VariableInMemory]("__reg"), Offset),
AssemblyLine.implied(TXA))
} else code
}
def preserve2ZpregIfNeededDestroyingAAndX(ctx: CompilationContext, Offset1: Int, Offset2: Int, code: List[AssemblyLine]): List[AssemblyLine] = {
if (changesZpreg(code, Offset1) || changesZpreg(code, Offset2)) {
val reg = ctx.env.get[VariableInMemory]("__reg")
List(
AssemblyLine.zeropage(LDA, reg, Offset1),
AssemblyLine.implied(PHA),
AssemblyLine.zeropage(LDA, reg, Offset2),
AssemblyLine.implied(PHA)) ++
code ++
List(
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, Offset2),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, Offset1),
AssemblyLine.implied(TXA))
} else code
}
def changesZpreg(code: List[AssemblyLine], Offset: Int): Boolean = {
code.exists {
case AssemblyLine0(op,
AddrMode.ZeroPage | AddrMode.Absolute | AddrMode.LongAbsolute,
CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(Offset, _))) if th.name == "__reg" && OpcodeClasses.ChangesMemoryAlways(op) || OpcodeClasses.ChangesMemoryIfNotImplied(op) => true
case AssemblyLine0(op,
AddrMode.ZeroPage | AddrMode.Absolute | AddrMode.LongAbsolute,
MemoryAddressConstant(th)) if th.name == "__reg" && Offset == 0 && OpcodeClasses.ChangesMemoryAlways(op) || OpcodeClasses.ChangesMemoryIfNotImplied(op) => true
case AssemblyLine0(JSR | BYTE | BSR, _, _) => true
case _ => false
}
}
def preserveCarryIfNeeded(ctx: CompilationContext, code: List[AssemblyLine]): List[AssemblyLine] = {
if (code.exists {
case AssemblyLine0(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th)) => true
case x => OpcodeClasses.ChangesC(x.opcode)
}) {
AssemblyLine.implied(PHP) +: fixTsx(code) :+ AssemblyLine.implied(PLP)
} else code
}
def preserveRegisterIfNeeded(ctx: CompilationContext, register: MosRegister.Value, code: List[AssemblyLine]): List[AssemblyLine] = {
val states = register match {
case MosRegister.A => Seq(State.A)
case MosRegister.AX | MosRegister.XA => Seq(State.A, State.X)
case MosRegister.X => Seq(State.X)
case MosRegister.AY | MosRegister.YA => Seq(State.A, State.Y)
case MosRegister.Y => Seq(State.Y)
}
val cmos = ctx.options.flag(CompilationFlag.EmitCmosOpcodes)
if (register == MosRegister.AX && !code.exists(_.concernsX) && code.forall(_.treatment(State.X).==(Treatment.Unchanged))) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (register == MosRegister.AY && !code.exists(_.concernsY) && code.forall(_.treatment(State.Y).==(Treatment.Unchanged))) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (states.exists(state => AssemblyLine.treatment(code, state) != Treatment.Unchanged)) {
register match {
case MosRegister.A => AssemblyLine.implied(PHA) +: fixTsx(code) :+ AssemblyLine.implied(PLA)
case MosRegister.X | MosRegister.AX | MosRegister.XA => if (cmos) {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(PHX),
) ++ fixTsx(fixTsx(code)) ++ List(
AssemblyLine.implied(PLX),
AssemblyLine.implied(PLA),
)
} else {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(PHA),
) ++ fixTsx(fixTsx(code)) ++ List(
AssemblyLine.implied(PLA),
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA),
)
}
case MosRegister.Y | MosRegister.AY | MosRegister.YA => if (cmos) {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(PHY),
) ++ fixTsx(fixTsx(code)) ++ List(
AssemblyLine.implied(PLY),
AssemblyLine.implied(PLA),
)
} else {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TYA),
AssemblyLine.implied(PHA),
) ++ fixTsx(fixTsx(code)) ++ List(
AssemblyLine.implied(PLA),
AssemblyLine.implied(TAY),
AssemblyLine.implied(PLA),
)
}
}
} else {
code
}
}
def prepareWordIndexing(ctx: CompilationContext, pointy: Pointy, indexExpression: Expression): List[AssemblyLine] = {
val w = ctx.env.get[Type]("word")
if (ctx.options.zpRegisterSize < 2) {
ctx.log.error("16-bit indexing requires a zeropage pseudoregister")
return Nil
}
val reg = ctx.env.get[VariableInMemory]("__reg")
val compileIndex = compile(ctx, indexExpression, Some(MosExpressionCompiler.getExpressionType(ctx, indexExpression) -> RegisterVariable(MosRegister.YA, w)), BranchSpec.None)
val prepareRegister = pointy match {
case p:ConstantPointy =>
List(
AssemblyLine.implied(CLC),
AssemblyLine.immediate(ADC, p.value.hiByte),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.immediate(LDA, p.value.loByte),
AssemblyLine.zeropage(STA, reg))
case VariablePointy(addr, _, _, true, v) =>
List(
AssemblyLine.implied(CLC),
AssemblyLine.zeropage(ADC, addr + 1).applyVolatile(v),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.zeropage(LDA, addr).applyVolatile(v),
AssemblyLine.zeropage(STA, reg))
case VariablePointy(addr, _, _, false, v) =>
List(
AssemblyLine.implied(CLC),
AssemblyLine.absolute(ADC, addr + 1).applyVolatile(v),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.absolute(LDA, addr).applyVolatile(v),
AssemblyLine.zeropage(STA, reg))
case StackVariablePointy(offset, _, _) =>
if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
List(
AssemblyLine.implied(CLC),
AssemblyLine.stackRelative(ADC, offset + 1 + ctx.extraStackOffset),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.stackRelative(LDA, offset + ctx.extraStackOffset),
AssemblyLine.zeropage(STA, reg))
} else {
List(
AssemblyLine.implied(CLC),
AssemblyLine.implied(TSX),
AssemblyLine.absoluteX(ADC, offset + 1 + ctx.extraStackOffset),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset),
AssemblyLine.zeropage(STA, reg))
}
}
compileIndex ++ prepareRegister
}
def compileByteStorage(ctx: CompilationContext, register: MosRegister.Value, target: LhsExpression): List[AssemblyLine] = {
val env = ctx.env
val b = env.get[Type]("byte")
val store = register match {
case MosRegister.A => STA
case MosRegister.X => STX
case MosRegister.Y => STY
}
val transferToA = register match {
case MosRegister.A => NOP
case MosRegister.X => TXA
case MosRegister.Y => TYA
}
target match {
case VariableExpression(name) =>
val v = env.get[Variable](name)
v.typ.size match {
case 0 => ???
case 1 =>
v match {
case mv: VariableInMemory => AssemblyLine.variable(ctx, store, mv)
case sv: StackVariable =>
AssemblyLine.implied(transferToA) :: (AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, sv))
}
case s if s > 1 =>
v match {
case mv: VariableInMemory =>
AssemblyLine.variable(ctx, store, mv) ++
List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, mv, i + 1)).flatten
case sv: StackVariable =>
AssemblyLine.implied(transferToA) :: (
AssemblyLine.tsx(ctx) ++
List(AssemblyLine.dataStackX(ctx, STA, sv), AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, sv, i + 1))
)
}
}
case IndexedExpression(arrayName, indexExpr) =>
val pointy = env.getPointy(arrayName)
val (variableIndex, constIndex) = env.evalVariableAndConstantSubParts(indexExpr)
val variableIndexSize = variableIndex.map(v => getExpressionType(ctx, v).size).getOrElse(0)
val totalIndexSize = getExpressionType(ctx, indexExpr).size
def storeToArrayAtUnknownIndex(variableIndex: Expression, arrayAddr: Constant) = {
// TODO check typ
val indexRegister = if (register == MosRegister.Y) MosRegister.X else MosRegister.Y
val calculatingIndex = preserveRegisterIfNeeded(ctx, register, compile(ctx, variableIndex, Some(b, RegisterVariable(indexRegister, b)), NoBranching))
if (register == MosRegister.A) {
indexRegister match {
case MosRegister.Y =>
calculatingIndex ++ arrayBoundsCheck(ctx, pointy, MosRegister.Y, indexExpr) ++ List(AssemblyLine.absoluteY(STA, arrayAddr + constIndex))
case MosRegister.X =>
calculatingIndex ++ arrayBoundsCheck(ctx, pointy, MosRegister.X, indexExpr) ++ List(AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
}
} else {
indexRegister match {
case MosRegister.Y =>
calculatingIndex ++ arrayBoundsCheck(ctx, pointy, MosRegister.Y, indexExpr) ++ List(AssemblyLine.implied(transferToA), AssemblyLine.absoluteY(STA, arrayAddr + constIndex))
case MosRegister.X =>
calculatingIndex ++ arrayBoundsCheck(ctx, pointy, MosRegister.X, indexExpr) ++ List(AssemblyLine.implied(transferToA), AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
}
}
}
def wrapWordIndexingStorage(code: List[AssemblyLine]) = {
val reg = ctx.env.get[VariableInMemory]("__reg")
val cmos = ctx.options.flag(CompilationFlag.EmitCmosOpcodes)
register match {
case MosRegister.A =>
List(AssemblyLine.implied(PHA)) ++ fixTsx(code) ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg))
case MosRegister.X =>
if (code.exists(l => OpcodeClasses.ChangesX(l.opcode))) {
if (cmos)
List(AssemblyLine.implied(PHX)) ++ fixTsx(code) ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg))
else
List(AssemblyLine.implied(TXA), AssemblyLine.implied(PHA)) ++ fixTsx(code) ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg))
} else {
code ++ List(AssemblyLine.implied(TXA), AssemblyLine.indexedY(STA, reg))
}
case MosRegister.Y =>
if (cmos)
List(AssemblyLine.implied(PHY)) ++ fixTsx(code) ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg))
else
List(AssemblyLine.implied(TYA), AssemblyLine.implied(PHA)) ++ fixTsx(code) ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg))
}
}
(pointy, variableIndex, variableIndexSize, totalIndexSize) match {
case (p: ConstantPointy, None, _, _) =>
if (p.readOnly) {
ctx.log.error("Writing to a constant array", target.position)
}
List(AssemblyLine.absolute(store, env.genRelativeVariable(p.value + constIndex, b, zeropage = false)))
case (p: VariablePointy, _, _, 2) =>
wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr))
case (p: VariablePointy, _, 0 | 1, _) if !p.zeropage =>
// TODO: optimize?
wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr))
case (p: ConstantPointy, Some(v), 2, _) =>
if (p.readOnly) {
ctx.log.error("Writing to a constant array", target.position)
}
val w = env.get[VariableType]("word")
wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, None, if (constIndex.isProvablyZero) p.sizeInBytes else None, if (constIndex.isProvablyZero) p.elementCount else None, w, p.elementType, NoAlignment, p.readOnly), v))
case (p: ConstantPointy, Some(v), 1, _) =>
if (p.readOnly) {
ctx.log.error("Writing to a constant array", target.position)
}
storeToArrayAtUnknownIndex(v, p.value)
//TODO: should there be a type check or a zeropage check?
case (pointerVariable@VariablePointy(varAddr, _, _, true, volatile), None, _, 0 | 1) =>
register match {
case MosRegister.A =>
List(AssemblyLine.immediate(LDY, constIndex), AssemblyLine.indexedY(STA, pointerVariable.addr).applyVolatile(volatile))
case MosRegister.Y =>
List(AssemblyLine.implied(TYA), AssemblyLine.immediate(LDY, constIndex), AssemblyLine.indexedY(STA, pointerVariable.addr).applyVolatile(volatile), AssemblyLine.implied(TAY))
case MosRegister.X =>
List(AssemblyLine.immediate(LDY, constIndex), AssemblyLine.implied(TXA), AssemblyLine.indexedY(STA, pointerVariable.addr).applyVolatile(volatile))
case _ =>
ctx.log.error("Cannot store a word in an array", target.position)
Nil
}
case (p@VariablePointy(varAddr, _, _, true, volatile), Some(_), _, 0 | 1) =>
val calculatingIndex = compile(ctx, indexExpr, Some(b, RegisterVariable(MosRegister.Y, b)), NoBranching)
register match {
case MosRegister.A =>
preserveRegisterIfNeeded(ctx, MosRegister.A, calculatingIndex) :+ AssemblyLine.indexedY(STA, varAddr).applyVolatile(volatile)
case MosRegister.X =>
preserveRegisterIfNeeded(ctx, MosRegister.X, calculatingIndex) ++ List(AssemblyLine.implied(TXA), AssemblyLine.indexedY(STA, varAddr).applyVolatile(volatile))
case MosRegister.Y =>
AssemblyLine.implied(TYA) :: preserveRegisterIfNeeded(ctx, MosRegister.A, calculatingIndex) ++ List(
AssemblyLine.indexedY(STA, varAddr), AssemblyLine.implied(TAY)
)
case _ =>
ctx.log.error("Cannot store a word in an array", target.position)
Nil
}
case (StackVariablePointy(offset, _, _), None, _, 0 | 1) if ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes) =>
register match {
case MosRegister.A =>
List(AssemblyLine.immediate(LDY, constIndex), AssemblyLine.indexedSY(STA, offset))
case MosRegister.Y =>
List(AssemblyLine.implied(TYA), AssemblyLine.immediate(LDY, constIndex), AssemblyLine.indexedSY(STA, offset), AssemblyLine.implied(TAY))
case MosRegister.X =>
List(AssemblyLine.immediate(LDY, constIndex), AssemblyLine.implied(TXA), AssemblyLine.indexedSY(STA, offset))
case _ =>
ctx.log.error("Cannot store a word in an array", target.position)
Nil
}
case (p@StackVariablePointy(offset, _, _), Some(_), _, 0 | 1) if ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes) =>
val calculatingIndex = compile(ctx, indexExpr, Some(b, RegisterVariable(MosRegister.Y, b)), NoBranching)
register match {
case MosRegister.A =>
preserveRegisterIfNeeded(ctx, MosRegister.A, calculatingIndex) :+ AssemblyLine.indexedSY(STA, offset)
case MosRegister.X =>
preserveRegisterIfNeeded(ctx, MosRegister.X, calculatingIndex) ++ List(AssemblyLine.implied(TXA), AssemblyLine.indexedSY(STA, offset))
case MosRegister.Y =>
AssemblyLine.implied(TYA) :: preserveRegisterIfNeeded(ctx, MosRegister.A, calculatingIndex) ++ List(
AssemblyLine.indexedSY(STA, offset), AssemblyLine.implied(TAY)
)
case _ =>
ctx.log.error("Cannot store a word in an array", target.position)
Nil
}
case (p: StackVariablePointy, _, _, _) =>
// TODO: optimize?
wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr))
case _ =>
ctx.log.error("Invalid index for writing", indexExpr.position)
Nil
}
case DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner)
if (targetType.size == 1) {
fast match {
case Some((fastBase, fastAddrMode, fastIndex)) =>
return preserveRegisterIfNeeded(ctx, MosRegister.A, fastIndex(offset)) ++ List(AssemblyLine(STA, fastAddrMode, fastBase, elidability = el))
case _ =>
}
}
val lo = preserveRegisterIfNeeded(ctx, MosRegister.A, prepare) ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(STA, am, addr, elidability = el))
if (targetType.size == 1) {
lo
} else {
lo ++ List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(targetType.size - 1)(i => List(AssemblyLine.implied(INY), AssemblyLine(STA, am, addr, elidability = el))).flatten
}
}
}
val noop: List[AssemblyLine] = Nil
def areNZFlagsBasedOnA(code: List[AssemblyLine]): Boolean = {
for (line <- code.reverse) {
line.opcode match {
case TXA | LDA | ADC | EOR | SBC | AND | ORA | TYA | TZA | PLA | LAX => return true
case CMP => return line.addrMode == Immediate && line.parameter.isProvablyZero
case STA | STX | STY | STZ | SAX |
STA_W | STX_W | STY_W | STZ_W |
CLD | SED | CLV | SEI | CLI | SEC | CLC |
NOP => ()
case ASL | LSR | ROL | ROR | INC | DEC => return line.addrMode == Implied
case _ => return false
}
}
false
}
def compileToFatBooleanInA(ctx: CompilationContext, expr: Expression): List[AssemblyLine] = {
val env = ctx.env
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, expr)
sourceType match {
case FatBooleanType =>
compileToA(ctx, expr)
case t: ConstantBooleanType =>
List(AssemblyLine.immediate(LDA, if (t.value) 1 else 0))
case t: FlagBooleanType =>
val condition = compile(ctx, expr, None, NoBranching)
condition ++ (t.jumpIfFalse.mosOpcode match {
case BCC => List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL))
case BCS => List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL), AssemblyLine.immediate(EOR, 1))
case o =>
val label = env.nextLabel("bo")
List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.relative(o, label),
AssemblyLine.immediate(LDA, 1),
AssemblyLine.label(label))
})
case BuiltInBooleanType =>
val label = env.nextLabel("bo")
val condition = compile(ctx, expr, None, BranchIfFalse(label))
if (condition.isEmpty) {
???
}
val conditionWithoutJump = condition.init
val hasOnlyOneJump = !conditionWithoutJump.exists(_.refersTo(label))
// TODO: helper functions to convert flags to booleans, to make code smaller
if (hasOnlyOneJump) {
condition.last.opcode match {
case BCC =>
// our bool is in the carry flag
// 3 bytes 4 cycles
return conditionWithoutJump ++ List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL))
case BCS if !ctx.options.flag(CompilationFlag.OptimizeForSpeed) =>
// our bool is in the carry flag, negated
// 5 bytes 6 cycles
return conditionWithoutJump ++ List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL), AssemblyLine.immediate(EOR, 1))
case BPL if areNZFlagsBasedOnA(conditionWithoutJump) =>
// our bool is in the N flag and the 7th bit of A
// 4 bytes 6 cycles
return conditionWithoutJump ++ List(AssemblyLine.implied(ASL), AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL))
case BMI if areNZFlagsBasedOnA(conditionWithoutJump) && ctx.options.flag(CompilationFlag.OptimizeForSize)=>
// our bool is in the N flag and the 7th bit of A, negated
// 6 bytes 8 cycles
return conditionWithoutJump ++ List(AssemblyLine.implied(ASL), AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL), AssemblyLine.immediate(EOR, 1))
case _ =>
}
}
if (hasOnlyOneJump) {
condition.last.opcode match {
case BCC | BCS | BVC | BVS =>
// 7 bytes; for true: 5 cycles, for false 6 cycles
return conditionWithoutJump ++ List(
AssemblyLine.immediate(LDA, 0),
condition.last,
AssemblyLine.immediate(LDA, 1),
AssemblyLine.label(label))
case _ => ()
}
}
val skip = env.nextLabel("bo")
// at most 9 bytes; for true: 7 cycles, for false 5 cycles
condition ++ List(
AssemblyLine.immediate(LDA, 1),
AssemblyLine.absolute(JMP, Label(skip)),
AssemblyLine.label(label),
AssemblyLine.immediate(LDA, 0),
AssemblyLine.label(skip))
case _ =>
ctx.log.fatal(s"Cannot assign `${sourceType.name}` to `bool`", expr.position)
}
}
def compileToA(ctx: CompilationContext, expr: Expression): List[AssemblyLine] = {
val env = ctx.env
val b = env.get[Type]("byte")
compile(ctx, expr, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None)
}
def compileToY(ctx: CompilationContext, expr: Expression): List[AssemblyLine] = {
val env = ctx.env
val b = env.get[Type]("byte")
compile(ctx, expr, Some(b -> RegisterVariable(MosRegister.Y, b)), BranchSpec.None)
}
def compileToAX(ctx: CompilationContext, expr: Expression): List[AssemblyLine] = {
val env = ctx.env
val w = env.get[Type]("word")
compile(ctx, expr, Some(w -> RegisterVariable(MosRegister.AX, w)), BranchSpec.None)
}
def compileToZReg(ctx: CompilationContext, expr: Expression): List[AssemblyLine] = {
val env = ctx.env
val p = env.get[Type]("pointer")
compile(ctx, expr, Some(p -> env.get[Variable]("__reg.loword")), BranchSpec.None)
}
def compileToZReg2(ctx: CompilationContext, expr: Expression): List[AssemblyLine] = {
val env = ctx.env
val p = env.get[Type]("pointer")
compile(ctx, expr, Some(p -> env.get[Variable]("__reg.b2b3")), BranchSpec.None)
}
def getPhysicalPointerForDeref(ctx: CompilationContext, pointerExpression: Expression): (List[AssemblyLine], Constant, AddrMode.Value, Option[(Constant, AddrMode.Value, Int => List[AssemblyLine])]) = {
pointerExpression match {
case VariableExpression(name) =>
val p = ctx.env.maybeGet[ThingInMemory](name)
p match {
case Some(array: MfArray) => return (Nil, array.toAddress, AddrMode.AbsoluteY,
if (array.sizeInBytes <= 256) Some((array.toAddress, AbsoluteY, (i: Int) => List(AssemblyLine.immediate(LDY, i)))) else None)
case Some(q) => if (q.zeropage) return (Nil, q.toAddress, AddrMode.IndexedY, None)
case _ =>
}
case _ =>
}
ctx.env.eval(pointerExpression) match {
case Some(addr) => (Nil, addr, AddrMode.AbsoluteY, None)
case _ =>
val baseAndIndex: Option[(Expression, Expression)] = pointerExpression match {
case SumExpression(List((false, base), (false, index)), false) => Some(base -> index)
case FunctionCallExpression(pointerType, List(SumExpression(List((false, base), (false, index)), false))) if pointerType.startsWith("pointer.") =>
Some(base -> index)
case _ => None
}
val zreg = ctx.env.get[ThingInMemory]("__reg.loword").toAddress
(compileToZReg(ctx, pointerExpression), zreg, AddrMode.IndexedY, baseAndIndex match {
case Some((base, index)) =>
val itype = AbstractExpressionCompiler.getExpressionType(ctx, index)
if (itype.size != 1 || itype.isSigned) {
None
} else {
ctx.env.eval(base) match {
case Some(baseConst) =>
Some((baseConst, AbsoluteY, { (i: Int) =>
val b = ctx.env.get[Type]("byte")
compileToY(ctx, index #+# i)
}))
case _ =>
if (compileToY(ctx, index #+# 42).exists(l => OpcodeClasses.ChangesMemoryAlways(l.opcode) || OpcodeClasses.ChangesMemoryIfNotImplied(l.opcode))) {
None
} else {
val initZreg = compileToZReg(ctx, base)
initZreg match {
case List(
AssemblyLine0(LDA, ZeroPage, l),
AssemblyLine0(STA, ZeroPage, _),
AssemblyLine0(LDA, ZeroPage, h),
AssemblyLine0(STA, ZeroPage, _)) if h == l+1 =>
Some((zreg, IndexedY, { (i: Int) =>
compileToZReg(ctx, base) ++ compileToY(ctx, index #+# i)
}))
case initZreg =>
Some((zreg, IndexedY, { (i: Int) =>
initZreg ++ compileToY(ctx, index #+# i)
}))
}
}
}
}
case _ => None
})
}
}
def compileStackOffset(ctx: CompilationContext, target: Variable, offset: Int, subbyte: Option[Int]): List[AssemblyLine] = {
val hi = if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
MemoryAddressConstant(ctx.env.get[ThingInMemory]("__stack")).hiByte
} else {
Constant.One
}
val tsx = if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
AssemblyLine.absolute(LDX, MemoryAddressConstant(ctx.env.get[ThingInMemory]("__sp")))
} else {
AssemblyLine.implied(TSX)
}
val actualOffset = if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
offset & 0xff
} else {
(offset + ctx.extraStackOffset) & 0xff
}
val b = ctx.env.get[Type]("byte")
subbyte match {
case Some(1) => compile(ctx, GeneratedConstantExpression(hi, b), Some(b -> target), BranchSpec.None)
case Some(0) => target match {
case RegisterVariable(MosRegister.A, _) => actualOffset match {
case 0 => List(tsx, AssemblyLine.implied(TXA))
case 1 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case 2 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset))
}
case RegisterVariable(MosRegister.X, _) => actualOffset match {
case 0 => List(tsx)
case 1 => List(tsx, AssemblyLine.implied(INX))
case 2 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX))
case 3 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX), AssemblyLine.implied(INX))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset), AssemblyLine.implied(TAX))
}
case RegisterVariable(MosRegister.Y, _) => actualOffset match {
case 0 => List(tsx)
case 1 =>
if (ctx.options.flag(CompilationFlag.IdentityPage))
List(tsx, AssemblyLine.absoluteX(LDY, ctx.env.identityPage), AssemblyLine.implied(INY))
else
List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(TAY), AssemblyLine.implied(INY))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset), AssemblyLine.implied(TAY))
}
case _ =>
val loadA = actualOffset match {
case 0 => List(tsx, AssemblyLine.implied(TXA))
case 1 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(INX))
case 2 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(INX), AssemblyLine.implied(INX))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset))
}
loadA ++ expressionStorageFromA(ctx, Some(b -> target), None, signedSource = false)
}
case None =>
val loadA = actualOffset match {
case 0 => List(tsx, AssemblyLine.implied(TXA))
case 1 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case 2 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset))
}
loadA ++ List(AssemblyLine.immediate(LDX, hi)) ++ expressionStorageFromAX(ctx, Some(ctx.env.get[Type]("pointer") -> target), None)
case _ => throw new IllegalArgumentException
}
}
def compile(ctx: CompilationContext, expr: Expression, exprTypeAndVariable: Option[(Type, Variable)], branches: BranchSpec): List[AssemblyLine] = try {
val env = ctx.env
val b = env.get[Type]("byte")
val exprType = AbstractExpressionCompiler.getExpressionType(ctx, expr)
if (branches != NoBranching) {
(exprType, branches) match {
case (FatBooleanType, _) =>
return compile(ctx, FunctionCallExpression("!=", List(expr, LiteralExpression(0, 1))), exprTypeAndVariable, branches)
case (ConstantBooleanType(_, false), BranchIfTrue(_)) | (ConstantBooleanType(_, true), BranchIfFalse(_))=>
return compile(ctx, expr, exprTypeAndVariable, NoBranching)
case (ConstantBooleanType(_, true), BranchIfTrue(x)) =>
return compile(ctx, expr, exprTypeAndVariable, NoBranching) :+ AssemblyLine.absolute(JMP, Label(x))
case (ConstantBooleanType(_, false), BranchIfFalse(x)) =>
return compile(ctx, expr, exprTypeAndVariable, NoBranching) :+ AssemblyLine.absolute(JMP, Label(x))
case _ => ()
}
}
env.eval(expr) match {
case Some(value) =>
return exprTypeAndVariable.fold(noop) { case (exprType, target) =>
assertCompatible(exprType, target.typ)
compileConstant(ctx, value, exprType, target)
}
case _ =>
}
val w = env.get[Type]("word")
expr match {
case HalfWordExpression(expression, _) => ??? // TODO
case LiteralExpression(value, size) =>
exprTypeAndVariable.fold(noop) { case (exprType, target) =>
assertCompatible(exprType, target.typ)
compileConstant(ctx, NumericConstant(value, size), exprType, target)
}
case GeneratedConstantExpression(value, _) =>
exprTypeAndVariable.fold(noop) { case (exprType, target) =>
assertCompatible(exprType, target.typ)
compileConstant(ctx, value, exprType, target)
}
case VariableExpression(name) =>
exprTypeAndVariable.fold(noop) { case (exprType, target) =>
assertCompatible(exprType, target.typ)
env.eval(expr).map(c => compileConstant(ctx, c, exprType, target)).getOrElse {
env.get[TypedThing](name) match {
case source: StackOffsetThing => compileStackOffset(ctx, target, source.offset, source.subbyte)
case source: VariableInMemory =>
target match {
case RegisterVariable(MosRegister.A, _) => AssemblyLine.variable(ctx, LDA, source)
case RegisterVariable(MosRegister.AW, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDA, source) ++ List(
AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(
AssemblyLine.implied(XBA),
AssemblyLine.implied(PLA))
} else List(AssemblyLine.immediate(LDX, 0), AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source) :+ AssemblyLine.immediate(LDX, 0)
case 2 =>
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) :+ AssemblyLine.accu8)
} else {
AssemblyLine.variable(ctx, LDA, source, 1) ++ List(AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source)
}
}
case RegisterVariable(MosRegister.X, _) => AssemblyLine.variable(ctx, LDX, source)
case RegisterVariable(MosRegister.Y, _) => AssemblyLine.variable(ctx, LDY, source)
case RegisterVariable(MosRegister.AX, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDA, source) ++ List(
AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA))
} else AssemblyLine.variable(ctx, LDA, source) :+ AssemblyLine.immediate(LDX, 0)
case 2 =>
val lo = AssemblyLine.variable(ctx, LDA, source)
if (lo.exists(_.concernsX)) {
lo ++ AssemblyLine.variable(ctx, LDX, source, 1, preserveA = true)
} else {
val hi = AssemblyLine.variable(ctx, LDX, source, 1)
if (hi.length == 1 && hi.head.opcode == LDX) lo ++ hi
else hi ++ lo
}
}
case RegisterVariable(MosRegister.AY, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDA, source) ++ List(
AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(
AssemblyLine.implied(TAY),
AssemblyLine.implied(PLA))
} else {
AssemblyLine.variable(ctx, LDA, source) :+ AssemblyLine.immediate(LDY, 0)
}
case 2 =>
AssemblyLine.variable(ctx, LDA, source) ++ AssemblyLine.variable(ctx, LDY, source, 1)
}
case RegisterVariable(MosRegister.XA, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDX, source) ++ List(AssemblyLine.implied(TXA)) ++ signExtendA(ctx)
} else
AssemblyLine.variable(ctx, LDX, source) :+ AssemblyLine.immediate(LDA, 0)
case 2 =>
AssemblyLine.variable(ctx, LDX, source) ++ AssemblyLine.variable(ctx,LDA, source, 1)
}
case RegisterVariable(MosRegister.XY, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDX, source) ++ List(AssemblyLine.implied(TXA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAY))
} else
AssemblyLine.variable(ctx, LDX, source) :+ AssemblyLine.immediate(LDY, 0)
case 2 =>
AssemblyLine.variable(ctx, LDX, source) ++ AssemblyLine.variable(ctx, LDY, source, 1)
}
case RegisterVariable(MosRegister.YA, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDY, source) ++ List(AssemblyLine.implied(TYA)) ++ signExtendA(ctx)
} else
AssemblyLine.variable(ctx, LDY, source) :+ AssemblyLine.immediate(LDA, 0)
case 2 =>
AssemblyLine.variable(ctx, LDY, source) ++ AssemblyLine.variable(ctx, LDA, source, 1)
}
case RegisterVariable(MosRegister.YX, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDY, source) ++ List(AssemblyLine.implied(TYA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAX))
} else
AssemblyLine.variable(ctx, LDY, source) :+ AssemblyLine.immediate(LDX, 0)
case 2 =>
AssemblyLine.variable(ctx, LDY, source) ++ AssemblyLine.variable(ctx, LDX, source, 1)
}
case target: VariableInMemory =>
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && AbstractExpressionCompiler.getExpressionType(ctx, expr).size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copyFromLo = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) ++ AssemblyLine.variable(ctx, STA, target, i))
val copy = if (shouldCopyFromHiToLo(source.toAddress, target.toAddress)) copyFromLo.reverse else copyFromLo
val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) {
signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.variable(ctx, STA, target, i + exprType.size)).flatten
} else {
AssemblyLine.immediate(LDA, 0) ::
List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.variable(ctx, STA, target, i + exprType.size)).flatten
}
copy.flatten ++ extend
}
case target: StackVariable =>
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copy = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) :+ AssemblyLine.dataStackX(ctx, STA, target, i))
val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) {
signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size))
} else {
AssemblyLine.immediate(LDA, 0) ::
List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size))
}
AssemblyLine.tsx(ctx) ++ copy.flatten ++ extend
}
}
case source@StackVariable(_, sourceType, offset) =>
target match {
case RegisterVariable(MosRegister.A, _) => AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, LDA, offset)
case RegisterVariable(MosRegister.X, _) => AssemblyLine.tsx(ctx) ++ List(AssemblyLine.dataStackX(ctx, LDA, offset), AssemblyLine.implied(TAX))
case RegisterVariable(MosRegister.Y, _) => AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, LDY, offset)
case RegisterVariable(MosRegister.AX, _) =>
AssemblyLine.tsx(ctx) ++ (exprType.size match {
case 1 => if (exprType.isSigned) {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA))
} else List(
AssemblyLine.implied(TSX),
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.immediate(LDX, 0))
case 2 => List(
AssemblyLine.implied(TSX),
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(PHA),
AssemblyLine.dataStackX(ctx, LDA, offset + 1),
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA))
})
case RegisterVariable(MosRegister.AY, _) =>
AssemblyLine.tsx(ctx) ++ (exprType.size match {
case 1 => if (exprType.isSigned) {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(PHA)) ++
signExtendA(ctx) ++
List(AssemblyLine.implied(PLA))
} else {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.immediate(LDY, 0))
}
case 2 => List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.dataStackX(ctx, LDY, offset + 1))
})
case RegisterVariable(MosRegister.XA, _) =>
AssemblyLine.tsx(ctx) ++ (exprType.size match {
case 1 => if (exprType.isSigned) {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(TAX)) ++
signExtendA(ctx)
} else {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(TAX),
AssemblyLine.immediate(LDA, 0))
}
case 2 => List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.dataStackX(ctx, LDY, offset + 1),
AssemblyLine.implied(TAX),
AssemblyLine.implied(TYA))
})
case RegisterVariable(MosRegister.YA, _) =>
AssemblyLine.tsx(ctx) ++ (exprType.size match {
case 1 => if (exprType.isSigned) {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(TAY)) ++ signExtendA(ctx)
} else {
List(
AssemblyLine.dataStackX(ctx, LDY, offset),
AssemblyLine.immediate(LDA, 0))
}
case 2 => List(
AssemblyLine.dataStackX(ctx, LDY, offset),
AssemblyLine.dataStackX(ctx, LDA, offset + 1))
})
case RegisterVariable(MosRegister.YX, _) =>
AssemblyLine.tsx(ctx) ++ (exprType.size match {
case 1 => if (exprType.isSigned) {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(TAY)) ++
signExtendA(ctx) ++
List(AssemblyLine.implied(TAX))
} else {
List(
AssemblyLine.dataStackX(ctx, LDY, offset),
AssemblyLine.immediate(LDX, 0))
}
case 2 => List(
AssemblyLine.dataStackX(ctx, LDY, offset),
AssemblyLine.dataStackX(ctx, LDA, offset + 1),
AssemblyLine.implied(TAX))
})
case RegisterVariable(MosRegister.XY, _) =>
AssemblyLine.tsx(ctx) ++ (exprType.size match {
case 1 => if (exprType.isSigned) {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(TAX)) ++
signExtendA(ctx) ++
List(AssemblyLine.implied(TAY))
} else {
List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.implied(TAX),
AssemblyLine.immediate(LDY, 0))
}
case 2 => List(
AssemblyLine.dataStackX(ctx, LDA, offset),
AssemblyLine.dataStackX(ctx, LDY, offset + 1),
AssemblyLine.implied(TAX))
})
case target: VariableInMemory =>
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copy = List.tabulate(exprType.size)(i => AssemblyLine.dataStackX(ctx, LDA, offset + i) :: AssemblyLine.variable(ctx, STA, target, i))
val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) {
signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.variable(ctx, STA, target, i + exprType.size)).flatten
} else {
AssemblyLine.immediate(LDA, 0) ::
List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.variable(ctx, STA, target, i + exprType.size)).flatten
}
AssemblyLine.tsx(ctx) ++ copy.flatten ++ extend
}
case target: StackVariable =>
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copyFromLo = List.tabulate(exprType.size)(i => List(AssemblyLine.dataStackX(ctx, LDA, offset + i), AssemblyLine.dataStackX(ctx, STA, target, i)))
val copy = if (shouldCopyFromHiToLo(NumericConstant(source.baseOffset, 2), NumericConstant(target.baseOffset, 2))) copyFromLo.reverse else copyFromLo
val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) {
signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size))
} else {
AssemblyLine.immediate(LDA, 0) ::
List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size))
}
AssemblyLine.tsx(ctx) ++ copy.flatten ++ extend
}
}
case source@ConstantThing(_, value, _) =>
compileConstant(ctx, value, exprType, target)
}
}
}
case IndexedExpression(arrayName, indexExpr) =>
val pointy = env.getPointy(arrayName)
AbstractExpressionCompiler.checkIndexType(ctx, pointy, indexExpr)
if (pointy.elementType.alignedSize != 1) ctx.log.fatal("Whee!") // the statement preprocessor should have removed all of those
// TODO: check
val (variableIndex, constantIndex) = env.evalVariableAndConstantSubParts(indexExpr)
val variableIndexSize = variableIndex.map(v => getExpressionType(ctx, v).size).getOrElse(0)
val totalIndexSize = getExpressionType(ctx, indexExpr).size
exprTypeAndVariable.fold(compile(ctx, indexExpr, None, BranchSpec.None)) { case (exprType, target) =>
val register = target match {
case RegisterVariable(r, _) => r
case _ => MosRegister.A
}
val loRegister = register match {
case MosRegister.AX => MosRegister.A
case MosRegister.AY => MosRegister.A
case MosRegister.AW => MosRegister.A
case MosRegister.XA => MosRegister.X
case MosRegister.XY => MosRegister.X
case MosRegister.YA => MosRegister.Y
case MosRegister.YX => MosRegister.Y
case _ => register
}
val suffix = target match {
case RegisterVariable(_, _) => Nil
case target: VariableInMemory =>
if (target.typ.size == 1) {
AssemblyLine.variable(ctx, STA, target)
}
else if (pointy.elementType.isSigned) {
AssemblyLine.variable(ctx, STA, target) ++ signExtendA(ctx) ++
List.tabulate(target.typ.size - 1)(i => AssemblyLine.variable(ctx, STA, target, i + 1)).flatten
} else {
AssemblyLine.variable(ctx, STA, target) ++
List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(target.typ.size - 1)(i => AssemblyLine.variable(ctx, STA, target, i + 1)).flatten
}
}
val load = loRegister match {
case MosRegister.A => LDA
case MosRegister.X => LDX
case MosRegister.Y => LDY
}
def loadFromArrayAtUnknownIndex(variableIndex: Expression, arrayAddr: Constant) = {
// TODO check typ
val indexRegister = if (register == MosRegister.Y) MosRegister.X else MosRegister.Y
val calculatingIndex = compile(ctx, variableIndex, Some(b, RegisterVariable(indexRegister, b)), NoBranching)
indexRegister match {
case MosRegister.Y =>
calculatingIndex ++ arrayBoundsCheck(ctx, pointy, MosRegister.Y, indexExpr) ++ List(AssemblyLine.absoluteY(load, arrayAddr + constantIndex))
case MosRegister.X =>
calculatingIndex ++ arrayBoundsCheck(ctx, pointy, MosRegister.X, indexExpr) ++ List(AssemblyLine.absoluteX(load, arrayAddr + constantIndex))
}
}
def loadFromReg() = {
val reg = ctx.env.get[VariableInMemory]("__reg")
loRegister match {
case MosRegister.A =>
List(AssemblyLine.indexedY(LDA, reg))
case MosRegister.X =>
List(AssemblyLine.indexedY(LDA, reg), AssemblyLine.implied(TAX))
case MosRegister.Y =>
List(AssemblyLine.indexedY(LDA, reg), AssemblyLine.implied(TAY))
}
}
val result = (pointy, variableIndex, totalIndexSize, variableIndexSize) match {
case (a: ConstantPointy, None, _, _) =>
List(AssemblyLine.absolute(load, env.genRelativeVariable(a.value + constantIndex, b, zeropage = false)))
case (a: ConstantPointy, Some(v), _, 1) =>
loadFromArrayAtUnknownIndex(v, a.value)
case (a: ConstantPointy, Some(v), _, 2) =>
prepareWordIndexing(ctx, ConstantPointy(
a.value + constantIndex,
None,
if (constantIndex.isProvablyZero) a.sizeInBytes else None,
if (constantIndex.isProvablyZero) a.elementCount else None,
env.get[VariableType]("word"),
a.elementType, NoAlignment, a.readOnly), v) ++ loadFromReg()
case (a: VariablePointy, _, 2, _) =>
prepareWordIndexing(ctx, a, indexExpr) ++ loadFromReg()
case (p: VariablePointy, _, 0 | 1, _) if !p.zeropage =>
prepareWordIndexing(ctx, p, indexExpr) ++ loadFromReg()
case (p:VariablePointy, None, 0 | 1, _) =>
loRegister match {
case MosRegister.A =>
List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedY(LDA, p.addr))
case MosRegister.Y =>
List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedY(LDA, p.addr), AssemblyLine.implied(TAY))
case MosRegister.X =>
List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedY(LDA, p.addr), AssemblyLine.implied(TAX))
}
case (p:VariablePointy, Some(_), 0 | 1, _) =>
val calculatingIndex = compile(ctx, indexExpr, Some(b, RegisterVariable(MosRegister.Y, b)), NoBranching)
loRegister match {
case MosRegister.A =>
calculatingIndex :+ AssemblyLine.indexedY(LDA, p.addr)
case MosRegister.X =>
calculatingIndex ++ List(AssemblyLine.indexedY(LDA, p.addr), AssemblyLine.implied(TAX))
case MosRegister.Y =>
calculatingIndex ++ List(AssemblyLine.indexedY(LDA, p.addr), AssemblyLine.implied(TAY))
}
case (p: StackVariablePointy, _, 0 | 1, _) if ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes) =>
loRegister match {
case MosRegister.A =>
List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedSY(LDA, p.offset))
case MosRegister.Y =>
List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedSY(LDA, p.offset), AssemblyLine.implied(TAY))
case MosRegister.X =>
List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedSY(LDA, p.offset), AssemblyLine.implied(TAX))
}
case (p: StackVariablePointy, _, _, _) =>
prepareWordIndexing(ctx, p, indexExpr) ++ loadFromReg()
case _ =>
ctx.log.error("Invalid index for reading", indexExpr.position)
Nil
}
register match {
case MosRegister.A | MosRegister.X | MosRegister.Y =>
result ++ suffix
case MosRegister.AX | MosRegister.YX if !pointy.elementType.isSigned =>
result :+ AssemblyLine.immediate(LDX, 0)
case MosRegister.AY | MosRegister.XY if !pointy.elementType.isSigned =>
result :+ AssemblyLine.immediate(LDY, 0)
case MosRegister.XA | MosRegister.YA if !pointy.elementType.isSigned =>
result :+ AssemblyLine.immediate(LDA, 0)
case MosRegister.AX =>
result ++ List(AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAX), AssemblyLine.implied(PLA))
case MosRegister.AY =>
result ++ List(AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAY), AssemblyLine.implied(PLA))
case MosRegister.XY =>
result ++ List(AssemblyLine.implied(TAX)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAY))
case MosRegister.YX =>
result ++ List(AssemblyLine.implied(TAY)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAX))
case MosRegister.XA =>
result ++ List(AssemblyLine.implied(TAX)) ++ signExtendA(ctx)
case MosRegister.YA =>
result ++ List(AssemblyLine.implied(TAY)) ++ signExtendA(ctx)
case MosRegister.AW if !pointy.elementType.isSigned =>
result ++ List(AssemblyLine.implied(XBA), AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(XBA))
case MosRegister.AW =>
result ++ List(AssemblyLine.implied(PHA), AssemblyLine.implied(XBA), AssemblyLine.implied(PLA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(XBA))
}
}
case DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner)
(fast, targetType.size, am) match {
case (Some((fastBase, fastAddrMode, fastIndex)), 1, _) =>
fastIndex(offset) ++ List(AssemblyLine(LDA, fastAddrMode, fastBase, elidability = el)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
case (_, 1, AbsoluteY) =>
prepare ++ List(AssemblyLine.absolute(LDA, addr + offset).copy(elidability = el)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
case (_, 1, _) =>
prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr, elidability = el)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
case (Some((fastBase, AbsoluteY, fastIndex)), 2, _) =>
fastIndex(offset) ++ List(
AssemblyLine.absoluteY(LDA, fastBase).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.absoluteY(LDX, fastBase).copy(elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case (Some((fastBase, fastAddrMode, fastIndex)), 2, _) =>
fastIndex(offset) ++ List(
AssemblyLine.implied(INY),
AssemblyLine(LDA, fastAddrMode, fastBase).copy(elidability = el),
AssemblyLine.implied(TAX),
AssemblyLine.implied(DEY),
AssemblyLine(LDA, fastAddrMode, fastBase).copy(elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case (_, 2, AbsoluteY) =>
prepare ++
List(
AssemblyLine.absolute(LDA, addr + offset).copy(elidability = el),
AssemblyLine.absolute(LDX, addr + offset + 1).copy(elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case (_, 2, _) =>
prepare ++
List(
AssemblyLine.immediate(LDY, offset+1),
AssemblyLine(LDA, am, addr, elidability = el),
AssemblyLine.implied(TAX),
AssemblyLine.implied(DEY),
AssemblyLine(LDA, am, addr, elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case _ =>
exprTypeAndVariable match {
case Some((variableType, variable)) =>
prepare ++ (0 until variableType.size).flatMap { i =>
val load =
if (i == targetType.size) signExtendA(ctx)
else if (i > targetType.size) Nil
else if (am == AbsoluteY) List(AssemblyLine.absolute(LDA, addr + offset + i))
else if (i == 0) List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr))
else List(AssemblyLine.implied(INY), AssemblyLine(LDA, am, addr))
load ++ AssemblyLine.variable(ctx, STA, variable, i)
}
case None => Nil
}
}
case SumExpression(params, decimal) =>
assertAllArithmetic(ctx, params.map(_._2))
val a = params.map{case (n, p) => env.eval(p).map(n -> _)}
if (a.forall(_.isDefined)) {
val value = a.foldLeft(Constant.Zero){(c, pair) =>
val Some((neg, v)) = pair
CompoundConstant(if (decimal) {
if (neg) MathOperator.DecimalMinus else MathOperator.DecimalPlus
} else {
if (neg) MathOperator.Minus else MathOperator.Plus
}, c, v).quickSimplify
}
exprTypeAndVariable.map(x => compileConstant(ctx, value.quickSimplify, exprType, x._2)).getOrElse(Nil)
} else {
getSumSize(ctx, params) match {
case 1 =>
val calculate = BuiltIns.compileAddition(ctx, params, decimal = decimal)
val store = expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
if (exprTypeAndVariable.exists(_._1.size >= 2)) {
calculate ++ List(AssemblyLine.immediate(LDX, 0)) ++ store
} else {
calculate ++ store
}
case 2 =>
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
val calculate = PseudoregisterBuiltIns.compileWordAdditionToAW(ctx, params, decimal = decimal)
val store = expressionStorageFromAW(ctx, exprTypeAndVariable, expr.position)
calculate ++ store
} else {
PseudoregisterBuiltIns.compileWordAdditionViaAX(ctx, exprTypeAndVariable, expr.position, params, decimal = decimal)
}
case 0 =>
Nil
case _ =>
ctx.log.error("Non-in-place addition or subtraction of variables larger than 2 bytes is not supported", expr.position)
Nil
}
}
case SeparateBytesExpression(h, l) =>
exprTypeAndVariable.fold {
// TODO: order?
compile(ctx, l, None, branches) ++ compile(ctx, h, None, branches)
} { case (exprType, target) =>
assertCompatible(exprType, target.typ)
target match {
// TODO: some more complex ones may not work correctly
case RegisterVariable(MosRegister.A | MosRegister.X | MosRegister.Y, _) => compile(ctx, l, exprTypeAndVariable, branches)
case RegisterVariable(MosRegister.AX, _) =>
compile(ctx, l, Some(b -> RegisterVariable(MosRegister.A, b)), branches) ++
preserveRegisterIfNeeded(ctx, MosRegister.A, compile(ctx, h, Some(b -> RegisterVariable(MosRegister.X, b)), branches))
case RegisterVariable(MosRegister.AY, _) =>
compile(ctx, l, Some(b -> RegisterVariable(MosRegister.A, b)), branches) ++
preserveRegisterIfNeeded(ctx, MosRegister.A, compile(ctx, h, Some(b -> RegisterVariable(MosRegister.Y, b)), branches))
case RegisterVariable(MosRegister.XA, _) =>
compile(ctx, h, Some(b -> RegisterVariable(MosRegister.A, b)), branches) ++
preserveRegisterIfNeeded(ctx, MosRegister.A, compile(ctx, l, Some(b -> RegisterVariable(MosRegister.X, b)), branches))
case RegisterVariable(MosRegister.YA, _) =>
compile(ctx, h, Some(b -> RegisterVariable(MosRegister.A, b)), branches) ++
preserveRegisterIfNeeded(ctx, MosRegister.A, compile(ctx, l, Some(b -> RegisterVariable(MosRegister.Y, b)), branches))
case RegisterVariable(MosRegister.XY, _) =>
compile(ctx, l, Some(b -> RegisterVariable(MosRegister.X, b)), branches) ++
preserveRegisterIfNeeded(ctx, MosRegister.X, compile(ctx, h, Some(b -> RegisterVariable(MosRegister.Y, b)), branches))
case RegisterVariable(MosRegister.YX, _) =>
compile(ctx, l, Some(b -> RegisterVariable(MosRegister.Y, b)), branches) ++
preserveRegisterIfNeeded(ctx, MosRegister.Y, compile(ctx, h, Some(b -> RegisterVariable(MosRegister.X, b)), branches))
case target: VariableInMemory =>
target.typ.size match {
case 1 =>
ctx.log.error(s"Variable `$target.name` cannot hold a word", expr.position)
Nil
case 2 =>
compile(ctx, l, Some(b -> env.genRelativeVariable(target.toAddress, b, zeropage = target.zeropage)), branches) ++
compile(ctx, h, Some(b -> env.genRelativeVariable(target.toAddress + 1, b, zeropage = target.zeropage)), branches)
case n if n > 2 =>
val zero = LiteralExpression(0,1).pos(expr.position)
compile(ctx, l, Some(b -> env.genRelativeVariable(target.toAddress, b, zeropage = target.zeropage)), branches) ++
compile(ctx, h, Some(b -> env.genRelativeVariable(target.toAddress + 1, b, zeropage = target.zeropage)), branches) ++
List.tabulate(n - 2)(i => compile(ctx, zero, Some(b -> env.genRelativeVariable(target.toAddress + (i + 2), b, zeropage = target.zeropage)), branches)).flatten
}
case target: StackVariable =>
target.typ.size match {
case 1 =>
ctx.log.error(s"Variable `$target.name` cannot hold a word", expr.position)
Nil
case 2 =>
if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset)), branches) ++
compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + 1)), branches)
} else {
compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset)), branches) ++
compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset + 1)), branches)
}
case n if n > 2 =>
val zero = LiteralExpression(0, 1).pos(expr.position)
if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset)), branches) ++
compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + 1)), branches) ++
List.tabulate(n - 2)(i => compile(ctx, zero, Some(b -> StackVariable("", b, target.baseOffset + 2 + i)), branches)).flatten
} else {
compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset)), branches) ++
compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset + 1)), branches) ++
List.tabulate(n - 2)(i => compile(ctx, zero, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset + 2 + i)), branches)).flatten
}
}
}
}
case f@FunctionCallExpression(name, params) =>
var zeroExtend = false
var signExtend = false
var resultVariable = ""
val calculate: List[AssemblyLine] = name match {
case "call" =>
params match {
case List(fp) =>
getExpressionType(ctx, fp) match {
case KernalInterruptPointerType =>
compileToZReg2(ctx, fp) :+ AssemblyLine.absolute(JSR, env.get[ThingInMemory]("call"))
case FunctionPointerType(_, _, _, _, Some(v)) if (v.name == "void") =>
compileToZReg2(ctx, fp) :+ AssemblyLine.absolute(JSR, env.get[ThingInMemory]("call"))
case _: FunctionPointerType =>
ctx.log.error("Invalid function pointer type", fp.position)
compile(ctx, fp, None, BranchSpec.None)
case _ =>
ctx.log.error("Not a function pointer", fp.position)
compile(ctx, fp, None, BranchSpec.None)
}
case List(fp, param) =>
getExpressionType(ctx, fp) match {
case FunctionPointerType(_, _, _, Some(pt), Some(v)) =>
if (pt.size > 2 || pt.size < 1) {
ctx.log.error("Invalid parameter type", param.position)
compile(ctx, fp, None, BranchSpec.None) ++ compile(ctx, param, None, BranchSpec.None)
} else if (getExpressionType(ctx, param).isAssignableTo(pt)) {
pt.size match {
case 1 =>
compileToA(ctx, param) ++ preserveRegisterIfNeeded(ctx, MosRegister.A, compileToZReg2(ctx, fp)) :+ AssemblyLine.absolute(JSR, env.get[ThingInMemory]("call"))
case 2 =>
compileToAX(ctx, param) ++ preserveRegisterIfNeeded(ctx, MosRegister.AX, compileToZReg2(ctx, fp)) :+ AssemblyLine.absolute(JSR, env.get[ThingInMemory]("call"))
}
} else {
ctx.log.error("Invalid parameter type", param.position)
compile(ctx, fp, None, BranchSpec.None) ++ compile(ctx, param, None, BranchSpec.None)
}
case KernalInterruptPointerType =>
ctx.log.error("Invalid function pointer type", fp.position)
compile(ctx, fp, None, BranchSpec.None)
case _ =>
ctx.log.error("Not a function pointer", fp.position)
compile(ctx, fp, None, BranchSpec.None) ++ compile(ctx, param, None, BranchSpec.None)
}
case _ =>
ctx.log.error("Invalid call syntax", f.position)
Nil
}
case "not" =>
assertBool(ctx, "not", params, 1)
compile(ctx, params.head, exprTypeAndVariable, branches.flip)
case "hi" | "lo" =>
zeroExtend = true
val hi = name == "hi"
if (params.length != 1) {
ctx.log.error("Too many parameters for hi/lo", f.position)
Nil
} else {
val param = params.head
val typ = getExpressionType(ctx, param)
if (typ.size < 1 || typ.size > 2) {
ctx.log.error("Invalid parameter type for hi/lo", param.position)
compile(ctx, param, None, BranchSpec.None)
} else {
val compilation = compile(ctx, param, Some(MosExpressionCompiler.getExpressionType(ctx, param) -> RegisterVariable(MosRegister.AX, w)), BranchSpec.None)
if (hi) {
if (typ.size == 2) compilation :+ AssemblyLine.implied(TXA)
else if (typ.isSigned) compilation ++ signExtendA(ctx)
else List(AssemblyLine.immediate(LDA, 0))
} else compilation
}
}
case "sizeof" =>
env.eval(expr) match {
case Some(c) =>
exprTypeAndVariable match {
case Some((t, v)) =>
compileConstant(ctx, c, w, v)
case _ =>
Nil
}
case None => Nil
}
case "nonet" =>
if (params.length != 1) {
ctx.log.error("Invalid number of parameters", f.position)
Nil
} else {
env.eval(expr) match {
case Some(c) =>
exprTypeAndVariable match {
case Some((t, v)) =>
compileConstant(ctx, c, w, v)
case _ =>
Nil
}
case None =>
assertAllArithmeticBytes("Nonet argument has to be a byte", ctx, params)
if (ctx.options.flag(CompilationFlag.BuggyCodeWarning)) params.head match {
case SumExpression(addends, _) =>
if (addends.exists(a => a._1)) {
ctx.log.warn("Nonet subtraction may not work as expected", expr.position)
}
if (addends.size > 2) {
ctx.log.warn("Nonet addition works correctly only for two operands", expr.position)
}
case FunctionCallExpression("+" | "+'" | "<<" | "<<'" | "nonet", _) => // ok
case _ =>
ctx.log.warn("Unspecified nonet operation, results might be unpredictable", expr.position)
}
val label = ctx.nextLabel("no")
compile(ctx, params.head, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None) ++ List(
AssemblyLine.immediate(LDX, 0),
AssemblyLine.relative(BCC, label),
AssemblyLine.implied(INX),
AssemblyLine.label(label)
)
}
}
case "&&" =>
assertBool(ctx, "&&", params)
branches match {
case BranchIfFalse(_) =>
params.flatMap(compile(ctx, _, exprTypeAndVariable, branches))
case _ =>
val skip = ctx.nextLabel("an")
params.init.flatMap(compile(ctx, _, exprTypeAndVariable, BranchIfFalse(skip))) ++
compile(ctx, params.last, exprTypeAndVariable, branches) ++
List(AssemblyLine.label(skip))
}
case "||" =>
assertBool(ctx, "||", params)
branches match {
case BranchIfTrue(_) =>
params.flatMap(compile(ctx, _, exprTypeAndVariable, branches))
case _ =>
val skip = ctx.nextLabel("or")
params.init.flatMap(compile(ctx, _, exprTypeAndVariable, BranchIfTrue(skip))) ++
compile(ctx, params.last, exprTypeAndVariable, branches) ++
List(AssemblyLine.label(skip))
}
case "^^" => ???
case "&" =>
getArithmeticParamMaxSize(ctx, params, booleanHint = "&&") match {
case 1 =>
zeroExtend = true
BuiltIns.compileBitOps(AND, ctx, params)
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, AND)
case 0 => Nil
case _ =>
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
Nil
}
case "*" =>
assertSizesForMultiplication(ctx, params, inPlace = false)
getArithmeticParamMaxSize(ctx, params) match {
case 1 =>
zeroExtend = true
BuiltIns.compileByteMultiplication(ctx, params)
case 2 =>
//noinspection ZeroIndexToHead
PseudoregisterBuiltIns.compileWordMultiplication(ctx, Some(params(0)), params(1), storeInRegLo = false)
case 0 => Nil
case _ =>
ctx.log.error("Multiplication of variables larger than 2 bytes are not supported", expr.position)
Nil
}
case "|" =>
getArithmeticParamMaxSize(ctx, params, booleanHint = "||") match {
case 1 =>
zeroExtend = true
BuiltIns.compileBitOps(ORA, ctx, params)
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, ORA)
case 0 => Nil
case _ =>
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
Nil
}
case "^" =>
getArithmeticParamMaxSize(ctx, params) match {
case 1 =>
zeroExtend = true
BuiltIns.compileBitOps(EOR, ctx, params)
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, EOR)
case 0 => Nil
case _ =>
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
Nil
}
case ">>>>" =>
val (l, r, size) = assertArithmeticBinary(ctx, params)
size match {
case 2 =>
zeroExtend = true
BuiltIns.compileNonetOps(ctx, l, r)
case 1 =>
zeroExtend = true
BuiltIns.compileShiftOps(LSR, ctx, l ,r)
case 0 => Nil
case _ =>
ctx.log.error("Non-in-place shifts of variables larger than 2 bytes is not supported", expr.position)
Nil
}
case "<<" =>
val (l, r, size) = assertArithmeticBinary(ctx, params)
size match {
case 1 =>
zeroExtend = true
BuiltIns.compileShiftOps(ASL, ctx, l, r)
case 2 =>
BuiltIns.maybeCompileShiftFromByteToWord(ctx, l, r, left = true).getOrElse(PseudoregisterBuiltIns.compileWordShiftOps(left = true, ctx, l, r))
case 0 => Nil
case _ =>
ctx.log.error("Long shift ops not supported", l.position)
Nil
}
case ">>" =>
val (l, r, size) = assertArithmeticBinary(ctx, params)
size match {
case 1 =>
zeroExtend = true
BuiltIns.compileShiftOps(LSR, ctx, l, r)
case 2 =>
BuiltIns.maybeCompileShiftFromByteToWord(ctx, l, r, left = false).getOrElse(PseudoregisterBuiltIns.compileWordShiftOps(left = false, ctx, l, r))
case 0 => Nil
case _ =>
ctx.log.error("Long shift ops not supported", l.position)
Nil
}
case "<<'" =>
zeroExtend = true
assertAllArithmeticBytes("Long shift ops not supported", ctx, params)
val (l, r, 1) = assertArithmeticBinary(ctx, params)
DecimalBuiltIns.compileByteShiftLeft(ctx, l, r, rotate = false)
case ">>'" =>
zeroExtend = true
assertAllArithmeticBytes("Long shift ops not supported", ctx, params)
val (l, r, 1) = assertArithmeticBinary(ctx, params)
DecimalBuiltIns.compileByteShiftRight(ctx, l, r, rotate = false)
case "<" =>
// TODO: signed
val (size, signed) = assertArithmeticComparison(ctx, params)
compileTransitiveRelation(ctx, "<", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.LessSigned else ComparisonType.LessUnsigned, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, if (signed) ComparisonType.LessSigned else ComparisonType.LessUnsigned, l, r, branches)
case _ => BuiltIns.compileLongComparison(ctx, if (signed) ComparisonType.LessSigned else ComparisonType.LessUnsigned, l, r, size, branches)
}
}
case ">=" =>
// TODO: signed
val (size, signed) = assertArithmeticComparison(ctx, params)
compileTransitiveRelation(ctx, ">=", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.GreaterOrEqualSigned else ComparisonType.GreaterOrEqualUnsigned, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, if (signed) ComparisonType.GreaterOrEqualSigned else ComparisonType.GreaterOrEqualUnsigned, l, r, branches)
case _ => BuiltIns.compileLongComparison(ctx, if (signed) ComparisonType.GreaterOrEqualSigned else ComparisonType.GreaterOrEqualUnsigned, l, r, size, branches)
}
}
case ">" =>
// TODO: signed
val (size, signed) = assertArithmeticComparison(ctx, params)
compileTransitiveRelation(ctx, ">", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.GreaterSigned else ComparisonType.GreaterUnsigned, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, if (signed) ComparisonType.GreaterSigned else ComparisonType.GreaterUnsigned, l, r, branches)
case _ => BuiltIns.compileLongComparison(ctx, if (signed) ComparisonType.GreaterSigned else ComparisonType.GreaterUnsigned, l, r, size, branches)
}
}
case "<=" =>
// TODO: signed
val (size, signed) = assertArithmeticComparison(ctx, params)
compileTransitiveRelation(ctx, "<=", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
case _ => BuiltIns.compileLongComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, size, branches)
}
}
case "==" =>
val size = params.map(p => getExpressionType(ctx, p).size).max
compileTransitiveRelation(ctx, "==", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
case 1 => BuiltIns.compileByteComparison(ctx, ComparisonType.Equal, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, ComparisonType.Equal, l, r, branches)
case _ => BuiltIns.compileLongComparison(ctx, ComparisonType.Equal, l, r, size, branches)
}
}
case "!=" =>
val (l, r, size) = assertBinary(ctx, params)
size match {
case 1 => BuiltIns.compileByteComparison(ctx, ComparisonType.NotEqual, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, ComparisonType.NotEqual, l, r, branches)
case _ => BuiltIns.compileLongComparison(ctx, ComparisonType.NotEqual, l, r, size, branches)
}
case "+=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = false, decimal = false)
case 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = false, decimal = false)
}
case i if i > 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = false, decimal = false)
}
}
case "-=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = true, decimal = false)
case 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = true, decimal = false)
}
case i if i > 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = true, decimal = false)
}
}
case "+'=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = false, decimal = true)
case 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = false, decimal = true)
}
case i if i > 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = false, decimal = true)
}
}
case "-'=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = true, decimal = true)
case 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = true, decimal = true)
}
case i if i > 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = true, decimal = true)
}
}
case "<<=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteShiftOps(ASL, ctx, l, r)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongShiftOps(ctx, v, r, aslRatherThanLsr = true)
}
}
case ">>=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteShiftOps(LSR, ctx, l, r)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongShiftOps(ctx, v, r, aslRatherThanLsr = false)
}
}
case "<<'=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
DecimalBuiltIns.compileByteShiftLeft(ctx, l, r, rotate = false) ++ compileByteStorage(ctx, MosRegister.A, l)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
DecimalBuiltIns.compileInPlaceLongShiftLeft(ctx, v, r)
}
}
case ">>'=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
DecimalBuiltIns.compileByteShiftRight(ctx, l, r, rotate = false) ++ compileByteStorage(ctx, MosRegister.A, l)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
DecimalBuiltIns.compileInPlaceLongShiftRight(ctx, v, r)
}
}
case "*=" =>
assertSizesForMultiplication(ctx, params, inPlace = true)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteMultiplication(ctx, l, r)
case 2 =>
BuiltIns.compileInPlaceWordMultiplication(ctx, l, r)
case _ => ctx.log.fatal("Oops")
}
case "/=" | "%%=" =>
assertSizesForDivision(ctx, params, inPlace = true)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%=") ++ compileByteStorage(ctx, MosRegister.A, l)
case 2 =>
if (f.functionName == "%%=") {
BuiltIns.compileUnsignedWordByByteDivision(ctx, l, r, modulo = true) ++ compileByteStorage(ctx, MosRegister.A, l)
} else {
compileAssignment(ctx, FunctionCallExpression("/", List(l, r)).pos(f.position), l)
}
case 0 => Nil
case _ =>
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
Nil
}
case "/" | "%%" =>
assertSizesForDivision(ctx, params, inPlace = false)
val (l, r, size) = assertArithmeticBinary(ctx, params)
size match {
case 1 =>
zeroExtend = true
BuiltIns.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%")
case 2 =>
BuiltIns.compileUnsignedWordByByteDivision(ctx, l, r, f.functionName == "%%")
case 0 => Nil
case _ =>
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
Nil
}
case "*'=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
DecimalBuiltIns.compileInPlaceByteMultiplication(ctx, l, r)
case "&=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteBitOp(ctx, l, r, AND)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongBitOp(ctx, l, r, AND)
}
}
case "^=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteBitOp(ctx, l, r, EOR)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongBitOp(ctx, l, r, EOR)
}
}
case "|=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteBitOp(ctx, l, r, ORA)
case i if i >= 2 =>
l match {
case v: LhsExpression =>
BuiltIns.compileInPlaceWordOrLongBitOp(ctx, l, r, ORA)
}
}
case _ =>
env.maybeGet[Type](f.functionName) match {
case Some(typ) =>
val sourceType = validateTypeCastAndGetSourceExpressionType(ctx, typ, params)
if (sourceType.isBoollike) {
return compileToFatBooleanInA(ctx, params.head) ++ expressionStorageFromA(ctx, exprTypeAndVariable, position = expr.position, signedSource = false)
}
exprTypeAndVariable match {
case None =>
return compile(ctx, params.head, None, branches)
case Some((_, targetVariable)) =>
val targetType = targetVariable.typ
val officialType: Type = if (targetType.size == typ.size) {
typ
} else if (targetType.size == sourceType.size) {
targetType
} else if (typ.isSigned == sourceType.isSigned) {
targetType
} else if (typ.size == 1) {
return compileToA(ctx, f) ++ expressionStorageFromA(ctx, exprTypeAndVariable, position = expr.position, signedSource = typ.isSigned)
} else if (typ.size == 2) {
return compileToAX(ctx, f) ++ expressionStorageFromAX(ctx, exprTypeAndVariable, position = expr.position)
} else {
ctx.log.fatal(s"Cannot compile $typ($sourceType(...)) to $targetType")
}
return compile(ctx, params.head, Some(officialType -> targetVariable), branches)
}
case None =>
// fallthrough to the lookup below
}
lookupFunction(ctx, f) match {
case function: MacroFunction =>
val (paramPreparation, statements) = MosMacroExpander.inlineFunction(ctx, function, params, expr.position)
paramPreparation ++ statements.map {
case MosAssemblyStatement(opcode, addrMode, expression, elidability) =>
val param = env.evalForAsm(expression).getOrElse {
ctx.log.error("Inlining failed due to non-constant things", expression.position)
Constant.Zero
}
AssemblyLine(opcode, addrMode, param, elidability)
}
case function: EmptyFunction =>
??? // TODO: type conversion?
case function: FunctionInMemory =>
if (function.returnType.size > 2) {
resultVariable = function.name + ".return"
}
if (function.returnType.size == 1) {
zeroExtend = !function.returnType.isSigned
signExtend = function.returnType.isSigned
}
function match {
case nf: NormalFunction =>
if (nf.interrupt) {
ctx.log.error(s"Calling an interrupt function `${f.functionName}`", expr.position)
}
if (nf.name == "main" && ctx.options.flag(CompilationFlag.SoftwareStack)) {
if (nf.stackVariablesSize != 0 || env.things.values.exists(_.isInstanceOf[StackVariable])) {
ctx.log.error("Calling the main function when using software stack is not allowed", expr.position)
} else if (ctx.options.flag(CompilationFlag.BuggyCodeWarning)) {
ctx.log.warn("Calling the main function when using software stack is not allowed", expr.position)
}
}
case _ => ()
}
val result = function.params match {
case AssemblyOrMacroParamSignature(paramConvs) =>
val pairs = params.zip(paramConvs)
val secondViaMemory = pairs.flatMap {
case (paramExpr, AssemblyOrMacroParam(typ, paramVar: VariableInMemory, AssemblyParameterPassingBehaviour.Copy)) =>
compile(ctx, paramExpr, Some(typ -> paramVar), NoBranching)
case _ => Nil
}
val thirdViaRegisters = pairs.flatMap {
case (paramExpr, AssemblyOrMacroParam(typ, paramVar@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy)) =>
Some(register -> compile(ctx, paramExpr, Some(typ -> paramVar), NoBranching))
case _ => Nil
} match {
case Seq() => Nil
case Seq((_, param)) => param
case Seq((MosRegister.A, pa), (_, pxy)) => pa ++ preserveRegisterIfNeeded(ctx, MosRegister.A, pxy)
case Seq((_, pxy), (MosRegister.A, pa)) => pa ++ preserveRegisterIfNeeded(ctx, MosRegister.A, pxy)
case Seq((r1, p1), (r2, p2), (r3, p3)) if Set(r1, r2, r3) == Set(MosRegister.A, MosRegister.X, MosRegister.Y) =>
val pa = if (r1 == MosRegister.A) p1 else if (r2 == MosRegister.A) p2 else if (r3 == MosRegister.A) p3 else ???
val px = if (r1 == MosRegister.X) p1 else if (r2 == MosRegister.X) p2 else if (r3 == MosRegister.X) p3 else ???
val py = if (r1 == MosRegister.Y) p1 else if (r2 == MosRegister.Y) p2 else if (r3 == MosRegister.Y) p3 else ???
if (!px.exists(_.concernsY)) {
pa ++ preserveRegisterIfNeeded(ctx, MosRegister.A, py) ++ preserveRegisterIfNeeded(ctx, MosRegister.AY, px)
} else {
pa ++ preserveRegisterIfNeeded(ctx, MosRegister.A, px) ++ preserveRegisterIfNeeded(ctx, MosRegister.AX, py)
}
case other =>
if (ctx.options.flag(CompilationFlag.BuggyCodeWarning)) {
ctx.log.warn("Unsupported register parameter combination: " + other.map(_._1.toString).mkString("(", ",", ")"), expr.position)
}
other.flatMap(_._2) // TODO : make sure all registers are passed in correctly
}
secondViaMemory ++ thirdViaRegisters :+ AssemblyLine.absoluteOrLongAbsolute(JSR, function, ctx.options)
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 =>
compile(ctx, params.head, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None) ++ List(AssemblyLine.absoluteOrLongAbsolute(JSR, function, ctx.options))
case NormalParamSignature(paramVars) =>
params.zip(paramVars).flatMap {
case (paramExpr, paramVar) =>
val callCtx = callingContext(ctx, function.name, paramVar)
compileAssignment(callCtx, paramExpr, VariableExpression(paramVar.name + "`aa"))
} ++ List(AssemblyLine.absoluteOrLongAbsolute(JSR, function, ctx.options))
}
result
}
}
if (resultVariable == "") {
val store: List[AssemblyLine] = if (zeroExtend || signExtend) {
expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, signExtend)
} else {
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
}
calculate ++ store
} else {
calculate ++ compile(ctx, VariableExpression(resultVariable), exprTypeAndVariable, branches)
}
}
} catch {
case ex: ConstantOverflowException =>
ctx.log.error(ex.getMessage, ex.position.orElse(expr.position))
Nil
}
private def compileTransitiveRelation(ctx: CompilationContext,
operator: String,
params: List[Expression],
exprTypeAndVariable: Option[(Type, Variable)],
branches: BranchSpec)(binary: (Expression, Expression) => List[AssemblyLine]): List[AssemblyLine] = {
params match {
case List(l, r) => binary(l, r)
case List(_) | Nil =>
ctx.log.fatal("")
case _ =>
params.tail.init.foreach { e =>
if (ctx.env.eval(e).isEmpty) e match {
case VariableExpression(_) =>
case LiteralExpression(_, _) =>
case GeneratedConstantExpression(_, _) =>
case IndexedExpression(_, VariableExpression(_)) =>
case IndexedExpression(_, LiteralExpression(_, _)) =>
case IndexedExpression(_, GeneratedConstantExpression(_, _)) =>
case IndexedExpression(_, SumExpression(ps, false)) if isUpToOneVar(ps) =>
case _ =>
if (ctx.options.flag(CompilationFlag.BuggyCodeWarning)) ctx.log.warn("A complex expression may be evaluated multiple times", e.position)
}
}
val conjunction = params.init.zip(params.tail).map {
case (l, r) => FunctionCallExpression(operator, List(l, r))
}.reduceLeft((a, b) => FunctionCallExpression("&&", List(a, b)))
compile(ctx, conjunction, exprTypeAndVariable, branches)
}
}
def expressionStorageFromAX(ctx: CompilationContext, exprTypeAndVariable: Option[(Type, Variable)], position: Option[Position]): List[AssemblyLine] = {
exprTypeAndVariable.fold(noop) {
case (VoidType, _) => ctx.log.fatal("Cannot assign word to void", position)
case (_, RegisterVariable(MosRegister.A, _)) => noop
case (_, RegisterVariable(MosRegister.AW, _)) => List(AssemblyLine.implied(XBA), AssemblyLine.implied(TXA), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.X, _)) => List(AssemblyLine.implied(TAX))
case (_, RegisterVariable(MosRegister.Y, _)) => List(AssemblyLine.implied(TAY))
case (_, RegisterVariable(MosRegister.AX, _)) =>
// TODO: sign extension
noop
case (_, RegisterVariable(MosRegister.XA, _)) =>
// TODO: sign extension
if (ctx.options.flag(CompilationFlag.EmitHudsonOpcodes)) {
List(AssemblyLine.implied(HuSAX))
} else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(PHX),
AssemblyLine.implied(PLA),
AssemblyLine.implied(PLX))
} else {
List(
AssemblyLine.implied(TAY),
AssemblyLine.implied(TXA),
AssemblyLine.implied(PHA),
AssemblyLine.implied(TYA),
AssemblyLine.implied(TAX),
AssemblyLine.implied(PLA)) // fuck this shit
}
case (_, RegisterVariable(MosRegister.YA, _)) =>
// TODO: sign extension
List(
AssemblyLine.implied(TAY),
AssemblyLine.implied(TXA))
case (_, RegisterVariable(MosRegister.AY, _)) =>
// TODO: sign extension
if (ctx.options.flag(CompilationFlag.EmitHudsonOpcodes)) {
List(AssemblyLine.implied(SXY))
} else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
List(AssemblyLine.implied(TXY))
} else {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(TAY),
AssemblyLine.implied(PLA))
}
case (t, v: VariableInMemory) => t.size match {
case 0 => ???
case 1 => v.typ.size match {
case 1 =>
AssemblyLine.variable(ctx, STA, v)
case s if s > 1 =>
if (t.isSigned) {
AssemblyLine.variable(ctx, STA, v) ++ signExtendA(ctx) ++ List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
} else {
AssemblyLine.variable(ctx, STA, v) ++ List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
}
}
case 2 => v.typ.size match {
case 1 =>
ctx.log.error(s"Variable `${v.name}` cannot hold a word", position)
Nil
case 2 =>
AssemblyLine.variable(ctx, STA, v) ++ AssemblyLine.variable(ctx, STX, v, 1)
case s if s > 2 =>
if (t.isSigned) {
AssemblyLine.variable(ctx, STA, v) ++
AssemblyLine.variable(ctx, STX, v, 1) ++
List(AssemblyLine.implied(TXA)) ++
signExtendA(ctx) ++
List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten
} else {
AssemblyLine.variable(ctx, STA, v) ++ AssemblyLine.variable(ctx, STX, v, 1) ++ List(
AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten
}
}
}
case (t, v: StackVariable) => t.size match {
case 1 => v.typ.size match {
case 1 =>
AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, v)
case s if s > 1 =>
AssemblyLine.tsx(ctx) ++ (if (t.isSigned) {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset)) ++
signExtendA(ctx) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
} else {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset),
AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
})
}
case 2 => v.typ.size match {
case 1 =>
ctx.log.error(s"Variable `${v.name}` cannot hold a word", position)
Nil
case 2 =>
List(
AssemblyLine.implied(TAY),
AssemblyLine.implied(TXA)) ++ AssemblyLine.tsx(ctx) ++ List(
AssemblyLine.dataStackX(ctx, STA, v, 1),
AssemblyLine.implied(TYA),
AssemblyLine.dataStackX(ctx, STA, v))
case s if s > 2 => ???
}
}
}
}
def expressionStorageFromA(ctx: CompilationContext, exprTypeAndVariable: Option[(Type, Variable)], position: Option[Position], signedSource: Boolean): List[AssemblyLine] = {
exprTypeAndVariable.fold(noop) {
case (VoidType, _) => ctx.log.fatal("Cannot assign word to void", position)
case (_, RegisterVariable(MosRegister.A, _)) => noop
case (typ, RegisterVariable(MosRegister.AW, _)) =>
if (signedSource) List(AssemblyLine.implied(TAX)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(XBA), AssemblyLine.implied(TXA))
else List(AssemblyLine.implied(XBA), AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.X, _)) => List(AssemblyLine.implied(TAX))
case (_, RegisterVariable(MosRegister.Y, _)) => List(AssemblyLine.implied(TAY))
case (typ, RegisterVariable(MosRegister.AX, _)) =>
if (signedSource) {
if (ctx.options.flag(CompilationFlag.EmitHudsonOpcodes)) {
List(AssemblyLine.implied(TAX)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(HuSAX))
} else {
List(AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAX), AssemblyLine.implied(PLA))
}
} else List(AssemblyLine.immediate(LDX, 0))
case (typ, RegisterVariable(MosRegister.XA, _)) =>
if (signedSource) {
List(AssemblyLine.implied(TAX)) ++ signExtendA(ctx)
} else {
List(AssemblyLine.implied(TAX), AssemblyLine.immediate(LDA, 0))
}
case (typ, RegisterVariable(MosRegister.YA, _)) =>
if (signedSource) {
List(AssemblyLine.implied(TAY)) ++ signExtendA(ctx)
} else {
List(AssemblyLine.implied(TAY), AssemblyLine.immediate(LDA, 0))
}
case (typ, RegisterVariable(MosRegister.AY, _)) =>
if (signedSource) {
if (ctx.options.flag(CompilationFlag.EmitHudsonOpcodes)) {
List(AssemblyLine.implied(TAY)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(SAY))
} else {
List(AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(TAY), AssemblyLine.implied(PLA))
}
} else List(AssemblyLine.immediate(LDY, 0))
case (t, v: VariableInMemory) =>
v.typ.size match {
case 1 =>
AssemblyLine.variable(ctx, STA, v)
case s if s > 1 =>
if (signedSource) {
AssemblyLine.variable(ctx, STA, v) ++ signExtendA(ctx) ++ List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
} else {
AssemblyLine.variable(ctx, STA, v) ++ List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
}
}
case (t, v: StackVariable) =>
v.typ.size match {
case 1 =>
AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, v)
case s if s > 1 =>
AssemblyLine.tsx(ctx) ++ (if (signedSource) {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset)) ++
signExtendA(ctx) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
} else {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset),
AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
})
}
}
}
def expressionStorageFromAW(ctx: CompilationContext, exprTypeAndVariable: Option[(Type, Variable)], position: Option[Position]): List[AssemblyLine] = {
exprTypeAndVariable.fold(noop) {
case (VoidType, _) => ctx.log.fatal("Cannot assign word to void", position)
case (_, RegisterVariable(MosRegister.A, _)) => noop
case (_, RegisterVariable(MosRegister.AW, _)) => noop
case (_, RegisterVariable(MosRegister.X, _)) => List(AssemblyLine.implied(TAX))
case (_, RegisterVariable(MosRegister.Y, _)) => List(AssemblyLine.implied(TAY))
case (_, RegisterVariable(MosRegister.AX, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAX), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.XA, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(TAX), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.YA, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(TAY), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.AY, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAY), AssemblyLine.implied(XBA))
case (t, v: VariableInMemory) => t.size match {
case 1 => v.typ.size match {
case 1 =>
AssemblyLine.variable(ctx, STA, v)
case s if s > 1 =>
if (t.isSigned) {
AssemblyLine.variable(ctx, STA, v) ++ signExtendA(ctx) ++ List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
} else {
AssemblyLine.variable(ctx, STA, v) ++ List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
}
}
case 2 => v.typ.size match {
case 1 =>
ctx.log.error(s"Variable `${v.name}` cannot hold a word", position)
Nil
case 2 =>
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, STA_W, v) :+ AssemblyLine.accu8)
case s if s > 2 =>
if (t.isSigned) {
AssemblyLine.accu16 :: AssemblyLine.variable(ctx, STA_W, v) ++
List(AssemblyLine.accu8, AssemblyLine.implied(XBA)) ++
signExtendA(ctx) ++
List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten
} else {
AssemblyLine.accu16 :: AssemblyLine.variable(ctx, STA_W, v)
List(AssemblyLine.accu8, AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten
}
}
}
case (t, v: StackVariable) => t.size match {
case 1 => v.typ.size match {
case 1 =>
AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, v)
case s if s > 1 =>
AssemblyLine.tsx(ctx) ++ (if (t.isSigned) {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset)) ++
signExtendA(ctx) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
} else {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset),
AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
})
}
case 2 => v.typ.size match {
case 1 =>
ctx.log.error(s"Variable `${v.name}` cannot hold a word", position)
Nil
case 2 =>
AssemblyLine.tsx(ctx) ++ List(AssemblyLine.accu16, AssemblyLine.dataStackX(ctx, STA_W, v), AssemblyLine.accu8)
case s if s > 2 => ???
}
}
}
}
def compileAssignment(ctx: CompilationContext, source: Expression, target: LhsExpression): List[AssemblyLine] = {
val env = ctx.env
val sourceType = AbstractExpressionCompiler.checkAssignmentTypeAndGetSourceType(ctx, source, target)
if (target == BlackHoleExpression) return compile(ctx, source, None, NoBranching)
val lhsType = AbstractExpressionCompiler.getExpressionType(ctx, target)
val b = env.get[Type]("byte")
val w = env.get[Type]("word")
target match {
case VariableExpression(name) =>
val v = env.get[Variable](name, target.position)
// TODO check v.typ
if (v.typ == FatBooleanType) {
compileToFatBooleanInA(ctx, source) ++ compileByteStorage(ctx, MosRegister.A, target)
} else {
compile(ctx, source, Some((sourceType, v)), NoBranching)
}
case SeparateBytesExpression(h: LhsExpression, l: LhsExpression) =>
compile(ctx, source, Some(w, RegisterVariable(MosRegister.AX, w)), NoBranching) ++
compileByteStorage(ctx, MosRegister.A, l) ++ compileByteStorage(ctx, MosRegister.X, h)
case SeparateBytesExpression(_, _) =>
ctx.log.error("Invalid left-hand-side use of `:`")
Nil
case DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val (prepare, addr, am, fastTarget) = getPhysicalPointerForDeref(ctx, inner)
env.eval(source) match {
case Some(constant) =>
am match {
case AbsoluteY =>
prepare ++ (0 until targetType.size).flatMap(i => List(
AssemblyLine.immediate(LDA, constant.subbyte(i)),
AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el)))
case _ =>
fastTarget match {
case Some((constAddr, fastAddrMode, initializeY)) =>
initializeY(offset) ++ (0 until targetType.size).flatMap { i =>
val load = List(AssemblyLine.immediate(LDA, constant.subbyte(i)))
load ++ (if (i == 0) List(AssemblyLine(STA, fastAddrMode, constAddr)) else List(
AssemblyLine.implied(INY),
AssemblyLine(STA, fastAddrMode, constAddr, elidability = el)))
}
case _ =>
prepare ++ (0 until targetType.size).flatMap(i => List(
if (i == 0) AssemblyLine.immediate(LDY, offset) else AssemblyLine.implied(INY),
AssemblyLine.immediate(LDA, constant.subbyte(i)),
AssemblyLine(STA, am, addr, elidability = el)))
}
}
case None =>
source match {
case VariableExpression(vname) =>
val variable = env.get[Variable](vname)
(targetType.size, am) match {
case (1, AbsoluteY) =>
prepare ++
AssemblyLine.variable(ctx, LDA, variable) :+
AssemblyLine.absolute(STA, addr + offset).copy(elidability = el)
case (1, _) if fastTarget.isEmpty =>
prepare ++
AssemblyLine.variable(ctx, LDA, variable) ++ List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine(STA, am, addr))
case (_, AbsoluteY) =>
prepare ++ (0 until targetType.size).flatMap { i =>
val load = loadSingleByteAssumingAPreserved(ctx, sourceType, variable, i)
load ++ List(
AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el))
}
case (_, _) =>
fastTarget match {
case Some((constAddr, fastAddrMode, initializeY)) =>
initializeY(offset) ++ (0 until targetType.size).flatMap { i =>
val load = loadSingleByteAssumingAPreserved(ctx, sourceType, variable, i)
load ++ (if (i == 0) List(AssemblyLine(STA, fastAddrMode, constAddr)) else List(
AssemblyLine.implied(INY),
AssemblyLine(STA, fastAddrMode, constAddr, elidability = el)))
}
case _ =>
prepare ++ (0 until targetType.size).flatMap { i =>
val load = loadSingleByteAssumingAPreserved(ctx, sourceType, variable, i)
load ++ List(
if (i == 0) AssemblyLine.immediate(LDY, offset) else AssemblyLine.implied(INY),
AssemblyLine(STA, am, addr).copy(elidability = el))
}
}
case _ =>
ctx.log.error("Cannot assign to a large object indirectly", target.position)
Nil
}
case DerefExpression(innerSource, sourceOffset, sourceVolatile, _) =>
val sEl = if (sourceVolatile) Elidability.Volatile else Elidability.Elidable
val (prepareSource, addrSource, amSource, fastSource) = getPhysicalPointerForDeref(ctx, innerSource)
(am, amSource) match {
case (AbsoluteY, AbsoluteY) =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
loadSingleByteAssumingAPreserved(ctx, sourceType, addrSource + sourceOffset, i, sEl) :+ AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el)
}
case (AbsoluteY, _) =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
loadSingleByteIndexedCountingYAssumingAPreserved(ctx, sourceType, amSource, addrSource, sourceOffset, i, sEl) :+
AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el)
}
case (_, AbsoluteY) =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteAssumingAPreserved(ctx, sourceType, addrSource + sourceOffset, i, sEl) :+
AssemblyLine(STA, am, addr, elidability = el))
}
case (IndexedY, IndexedY) =>
val reg = env.get[ThingInMemory]("__reg.loword")
(addr, addrSource) match {
case (MemoryAddressConstant(th1: Thing), MemoryAddressConstant(th2: Thing))
if (th1.name == "__reg.loword" || th1.name == "__reg.b2b3" || th1.name == "__reg") && (th2.name == "__reg.loword" || th2.name == "__reg.b2b3" || th2.name == "__reg") =>
(MosExpressionCompiler.changesZpreg(prepareSource, 2) || MosExpressionCompiler.changesZpreg(prepareSource, 3),
MosExpressionCompiler.changesZpreg(prepareSource, 2) || MosExpressionCompiler.changesZpreg(prepareSource, 3)) match {
case (_, false) =>
prepare ++ List(
AssemblyLine.zeropage(LDA, reg),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.zeropage(STA, reg, 3)) ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress, i, elidability = sEl) :+
AssemblyLine.indexedY(STA, reg, 2).copy(elidability = el))
}
case (false, true) =>
prepareSource ++ List(
AssemblyLine.zeropage(LDA, reg),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.zeropage(STA, reg, 3)) ++ prepare ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress + 2, i, elidability = sEl) :+
AssemblyLine.indexedY(STA, reg).copy(elidability = el))
}
case _ =>
prepare ++ List(
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.implied(PHA),
AssemblyLine.zeropage(LDA, reg),
AssemblyLine.implied(PHA)) ++ fixTsx(fixTsx(prepareSource)) ++ List(
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, 3)) ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress, i, sEl) :+
AssemblyLine.indexedY(STA, reg, 2).copy(elidability = el))
}
}
case (MemoryAddressConstant(thT: MemoryVariable), MemoryAddressConstant(thS: MemoryVariable)) if thT.name != thS.name =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, thS.toAddress, i, sEl) :+
AssemblyLine.indexedY(STA, thT).copy(elidability = el))
}
case _ =>
???
}
case _ =>
???
}
case _ =>
(targetType.size, am) match {
case (1, _) =>
compile(ctx, source, Some(targetType, RegisterVariable(MosRegister.A, targetType)), BranchSpec.None) ++ compileByteStorage(ctx, MosRegister.A, target)
case (2, AbsoluteY) =>
val someTuple = Some(targetType, RegisterVariable(MosRegister.AX, targetType))
// TODO: optimize if prepare is empty
if (prepare.isEmpty) {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.absolute(STA, addr + offset).copy(elidability = el),
AssemblyLine.absolute(STX, addr + offset + 1).copy(elidability = el))
} else {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(PHA)) ++ prepare ++ List(
AssemblyLine.implied(PLA),
AssemblyLine.absolute(STA, addr + offset + 1).copy(elidability = el),
AssemblyLine.implied(PLA),
AssemblyLine.absolute(STA, addr + offset).copy(elidability = el))
}
case (2, _) =>
val someTuple = Some(targetType, RegisterVariable(MosRegister.AX, targetType))
fastTarget match {
case Some((baseOffset, fastAddrMode, initializeY)) =>
compile(ctx, source, someTuple, BranchSpec.None) ++
preserveRegisterIfNeeded(ctx, MosRegister.AX, initializeY(offset)) ++
List(
AssemblyLine(STA, fastAddrMode, baseOffset, elidability = el),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine(STA, fastAddrMode, baseOffset, elidability = el))
case _ =>
if (prepare.isEmpty) {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(STA, addr).copy(elidability = el),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(STA, addr).copy(elidability = el))
} else {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(PHA)) ++ prepare ++ List(
AssemblyLine.immediate(LDY, offset+1),
AssemblyLine.implied(PLA),
AssemblyLine.indexedY(STA, addr).copy(elidability = el),
AssemblyLine.implied(PLA),
AssemblyLine.implied(DEY),
AssemblyLine.indexedY(STA, addr).copy(elidability = el))
}
}
case _ =>
ctx.log.error("Cannot assign to a large object indirectly", target.position)
Nil
}
}
}
case i: IndexedExpression =>
if (AbstractExpressionCompiler.getExpressionType(ctx, target).size != 1) {
ctx.log.error("Cannot store a large object this way", target.position)
}
compile(ctx, source, Some(b, RegisterVariable(MosRegister.A, b)), NoBranching) ++ compileByteStorage(ctx, MosRegister.A, target)
case _ =>
compile(ctx, source, Some(b, RegisterVariable(MosRegister.A, b)), NoBranching) ++ compileByteStorage(ctx, MosRegister.A, target)
}
}
private def loadSingleByteAssumingAPreserved(ctx: CompilationContext, sourceType: Type, variable: Variable, i: Int) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else AssemblyLine.variable(ctx, LDA, variable, i)
}
private def loadSingleByteAssumingAPreserved(ctx: CompilationContext, sourceType: Type, varAddr: Constant, i: Int, elidability: Elidability.Value) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else List(AssemblyLine(LDA, Absolute, varAddr + i, elidability))
}
private def loadSingleByteIndexedAssumingAPreserved(ctx: CompilationContext, sourceType: Type, addrMode: AddrMode.Value, baseAddr: Constant, i: Int, elidability: Elidability.Value) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else List(AssemblyLine(LDA, addrMode, baseAddr, elidability))
}
private def loadSingleByteIndexedCountingYAssumingAPreserved(ctx: CompilationContext, sourceType: Type, addrMode: AddrMode.Value, baseAddr: Constant, sourceOffset:Int, i: Int, elidability: Elidability.Value) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else List(if(i==0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY), AssemblyLine(LDA, addrMode, baseAddr, elidability))
}
def arrayBoundsCheck(ctx: CompilationContext, pointy: Pointy, register: MosRegister.Value, index: Expression): List[AssemblyLine] = {
if (!ctx.options.flags(CompilationFlag.CheckIndexOutOfBounds)) return Nil
val arrayLength:Int = pointy match {
case _: VariablePointy => return Nil
case p: ConstantPointy => p.sizeInBytes match {
case None => return Nil
case Some(s) =>
// don't do checks on arrays size 0 or 1
if (p.elementCount.exists(_ < 2)) return Nil
s
}
}
ctx.env.eval(index) match {
case Some(NumericConstant(i, _)) =>
if (i >= 0) {
if (i < arrayLength) return Nil
if (i >= arrayLength) return List(
AssemblyLine.implied(PHP),
AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[ThingInMemory]("_panic"), ctx.options))
}
case _ =>
}
if (arrayLength > 0 && arrayLength < 255) {
val label = ctx.nextLabel("bc")
val compare = register match {
case MosRegister.A => CMP
case MosRegister.X => CPX
case MosRegister.Y => CPY
}
List(
AssemblyLine.implied(PHP),
AssemblyLine.immediate(compare, arrayLength),
AssemblyLine.relative(BCC, label),
AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[ThingInMemory]("_panic"), ctx.options),
AssemblyLine.label(label),
AssemblyLine.implied(PLP))
} else {
Nil
}
}
private def signExtendA(ctx: CompilationContext): List[AssemblyLine] = {
val label = ctx.nextLabel("sx")
List(
AssemblyLine.immediate(ORA, 0x7F),
AssemblyLine.relative(BMI, label),
AssemblyLine.immediate(LDA, 0),
AssemblyLine.label(label))
}
private def shouldCopyFromHiToLo(srcAddress: Constant, destAddress: Constant): Boolean = (srcAddress, destAddress) match {
case (
CompoundConstant(MathOperator.Plus, a: MemoryAddressConstant, NumericConstant(s, _)),
CompoundConstant(MathOperator.Plus, b: MemoryAddressConstant, NumericConstant(d, _))
) if a == b => s < d
case (
a: MemoryAddressConstant,
CompoundConstant(MathOperator.Plus, b: MemoryAddressConstant, NumericConstant(d, _))
) if a == b => 0 < d
case (NumericConstant(s, _), NumericConstant(d, _)) => s < d
case _ => false
}
}