diff --git a/src/main/scala/millfork/OptimizationPresets.scala b/src/main/scala/millfork/OptimizationPresets.scala index cda3db0e..4fc1ec39 100644 --- a/src/main/scala/millfork/OptimizationPresets.scala +++ b/src/main/scala/millfork/OptimizationPresets.scala @@ -172,6 +172,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.PointlessStackStashing, AlwaysGoodOptimizations.PointlessStashingToIndexOverShortSafeBranch, AlwaysGoodOptimizations.PoinlessStoreBeforeStore, + AlwaysGoodOptimizations.PointlessStoreToTheSameVariable, AlwaysGoodOptimizations.RearrangableLoadFromTheSameLocation, AlwaysGoodOptimizations.RearrangeMath, AlwaysGoodOptimizations.RemoveNops, diff --git a/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala index 2f646665..f9f2ddac 100644 --- a/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala @@ -156,6 +156,57 @@ object AlwaysGoodOptimizations { (MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STZ)) ~~> (_.tail), ) + private def pointlessNonimmediateStoreToTheSameVariable(ld1: Set[Opcode.Value], st1: Opcode.Value, ld2: Opcode.Value, st2: Opcode.Value) = { + (HasOpcodeIn(ld1) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcode(st1) & MatchAddrMode(2) & MatchParameter(3)) ~ + (Linear & DoesntChangeIndexingInAddrMode(0) & DoesntChangeIndexingInAddrMode(2) & + DoesNotConcernMemoryAt(0, 1) & DoesntChangeMemoryAt(2, 3)).* ~ + (Elidable & HasOpcode(ld2) & MatchAddrMode(0) & MatchParameter(1)) ~ + (Elidable & HasOpcode(st2) & MatchAddrMode(2) & MatchParameter(3)) ~~> (code => code.init), + } + + private def pointlessImmediateStoreToTheSameVariable(match1: AssemblyLinePattern, st1: Opcode.Value, match2: AssemblyLinePattern, st2: Opcode.Value) = { + (HasOpcode(st1) & MatchAddrMode(2) & MatchParameter(3) & match1) ~ + (Linear & DoesntChangeIndexingInAddrMode(2) & ( + DoesntChangeMemoryAt(2, 3) | + HasAddrModeIn(Set(IndexedX, IndexedY, IndexedZ, LongIndexedY, LongIndexedZ, Indirect)) & + MatchParameter(3) & HasParameterWhere({ + case MemoryAddressConstant(th) => th.name.startsWith("__") + case _ => false + }) + )).* ~ + (Elidable & match2 & HasOpcode(st2) & MatchAddrMode(2) & MatchParameter(3)) ~~> (code => code.init), + } + + val PointlessStoreToTheSameVariable = new RuleBasedAssemblyOptimization("Pointless store to the same variable", + needsFlowInfo = FlowInfoRequirement.ForwardFlow, + + pointlessNonimmediateStoreToTheSameVariable(Set(LDA, LAX), STA, LDA, STA), + pointlessNonimmediateStoreToTheSameVariable(Set(LDA, LAX), STX, LDA, STA), + pointlessNonimmediateStoreToTheSameVariable(Set(LDY), STY, LDA, STA), + + pointlessNonimmediateStoreToTheSameVariable(Set(LDA, LAX), STA, LDX, STX), + pointlessNonimmediateStoreToTheSameVariable(Set(LDA, LAX), STX, LDX, STX), + pointlessNonimmediateStoreToTheSameVariable(Set(LDY), STY, LDX, STX), + + pointlessNonimmediateStoreToTheSameVariable(Set(LDA, LAX), STA, LDY, STY), + pointlessNonimmediateStoreToTheSameVariable(Set(LDA, LAX), STX, LDY, STY), + pointlessNonimmediateStoreToTheSameVariable(Set(LDY), STY, LDY, STY), + + pointlessImmediateStoreToTheSameVariable(MatchA(0), STA, MatchA(0), STA), + pointlessImmediateStoreToTheSameVariable(MatchX(0), STX, MatchA(0), STA), + pointlessImmediateStoreToTheSameVariable(MatchY(0), STY, MatchA(0), STA), + + pointlessImmediateStoreToTheSameVariable(MatchA(0), STA, MatchX(0), STX), + pointlessImmediateStoreToTheSameVariable(MatchX(0), STX, MatchX(0), STX), + pointlessImmediateStoreToTheSameVariable(MatchY(0), STY, MatchX(0), STX), + + pointlessImmediateStoreToTheSameVariable(MatchA(0), STA, MatchY(0), STY), + pointlessImmediateStoreToTheSameVariable(MatchX(0), STX, MatchY(0), STY), + pointlessImmediateStoreToTheSameVariable(MatchY(0), STY, MatchY(0), STY), + + ) + val PointlessLoadBeforeReturn = new RuleBasedAssemblyOptimization("Pointless load before return", needsFlowInfo = FlowInfoRequirement.NoRequirement, (Set(LDA, TXA, TYA, EOR, AND, ORA, ANC) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_AF))).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail), diff --git a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala index b63c1a3f..b6aeabcf 100644 --- a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala @@ -437,6 +437,12 @@ trait TrivialAssemblyLinePattern extends AssemblyLinePattern with (AssemblyLine override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = this (line) } +case class Match(predicate: (AssemblyMatchingContext => Boolean)) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = predicate(ctx) + + override def toString: String = "Match(...)" +} + case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) extends AssemblyPattern { override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = { val s1s = ctx.get[List[AssemblyLine]](ix1) @@ -871,6 +877,10 @@ case class HasImmediateWhere(predicate: Int => Boolean) extends TrivialAssemblyL }) } +case class HasParameterWhere(predicate: Constant => Boolean) extends TrivialAssemblyLinePattern { + override def apply(line: AssemblyLine): Boolean = predicate(line.parameter) +} + case class MatchObject(i: Int, f: Function[AssemblyLine, Any]) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = ctx.addObject(i, f(line)) diff --git a/src/main/scala/millfork/compiler/BuiltIns.scala b/src/main/scala/millfork/compiler/BuiltIns.scala index 7b595e78..e7b3e4e6 100644 --- a/src/main/scala/millfork/compiler/BuiltIns.scala +++ b/src/main/scala/millfork/compiler/BuiltIns.scala @@ -46,26 +46,26 @@ object BuiltIns { } Nil -> AssemblyLine.variable(ctx, opcode, v) case IndexedExpression(arrayName, index) => - indexChoice match { - case IndexChoice.RequireX | IndexChoice.PreferX => - val array = env.getArrayOrPointer(arrayName) - val calculateIndex = ExpressionCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.X, b)), NoBranching) - val baseAddress = array match { - case c: ConstantThing => c.value - case a: MfArray => a.toAddress - } - calculateIndex -> List(AssemblyLine.absoluteX(opcode, baseAddress)) - case IndexChoice.PreferY => - val array = env.getArrayOrPointer(arrayName) - val calculateIndex = ExpressionCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching) - array match { - case c: ConstantThing => - calculateIndex -> List(AssemblyLine.absoluteY(opcode, c.value)) - case a: MfArray => - calculateIndex -> List(AssemblyLine.absoluteY(opcode, a.toAddress)) - case v: VariableInMemory if v.typ.name == "pointer" => - calculateIndex -> List(AssemblyLine.indexedY(opcode, v.toAddress)) - } + val pointy = env.getPointy(arrayName) + val (variablePart, constantPart) = env.evalVariableAndConstantSubParts(index) + val indexerSize = variablePart.map(v => getIndexerSize(ctx, v)).getOrElse(1) + val totalIndexSize = getIndexerSize(ctx, index) + (pointy, totalIndexSize, indexerSize, indexChoice, variablePart) match { + case (p: ConstantPointy, _, _, _, None) => + Nil -> List(AssemblyLine.absolute(opcode, p.value + constantPart)) + case (p: ConstantPointy, _, 1, IndexChoice.RequireX | IndexChoice.PreferX, Some(v)) => + ExpressionCompiler.compile(ctx, v, Some(b -> RegisterVariable(Register.X, b)), NoBranching) -> List(AssemblyLine.absoluteX(opcode, p.value + constantPart)) + case (p: ConstantPointy, _, 1, IndexChoice.PreferY, Some(v)) => + ExpressionCompiler.compile(ctx, v, Some(b -> RegisterVariable(Register.Y, b)), NoBranching) -> List(AssemblyLine.absoluteY(opcode, p.value + constantPart)) + case (p: VariablePointy, 0 | 1, _, IndexChoice.PreferX | IndexChoice.PreferY, _) => + ExpressionCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching) -> List(AssemblyLine.indexedY(opcode, p.addr)) + case (p: ConstantPointy, _, 2, IndexChoice.PreferX | IndexChoice.PreferY, Some(v)) => + ExpressionCompiler.prepareWordIndexing(ctx, p, index) -> List(AssemblyLine.indexedY(opcode, env.get[VariableInMemory]("__reg"))) + case (p: VariablePointy, 2, _, IndexChoice.PreferX | IndexChoice.PreferY, _) => + ExpressionCompiler.prepareWordIndexing(ctx, p, index) -> List(AssemblyLine.indexedY(opcode, env.get[VariableInMemory]("__reg"))) + case _ => + ErrorReporting.error("Invalid index for simple operation argument", index.position) + Nil -> Nil } case FunctionCallExpression(name, List(param)) if env.maybeGet[Type](name).isDefined => return simpleOperation(opcode, ctx, param, indexChoice, preserveA, commutative, decimal) @@ -154,7 +154,7 @@ object BuiltIns { case None => expr match { case VariableExpression(_) => 'V' case IndexedExpression(_, LiteralExpression(_, _)) => 'K' - case IndexedExpression(_, VariableExpression(_)) => 'J' + case IndexedExpression(_, VariableExpression(v)) if env.get[Variable](v).typ.size == 1 => 'J' case IndexedExpression(_, _) => 'I' case _ => 'A' } @@ -165,19 +165,7 @@ object BuiltIns { def compileBitOps(opcode: Opcode.Value, ctx: CompilationContext, params: List[Expression]): List[AssemblyLine] = { val b = ctx.env.get[Type]("byte") - val sortedParams = params.sortBy { expr => - ctx.env.eval(expr) match { - case Some(NumericConstant(_, _)) => "Z" - case Some(_) => "Y" - case None => expr match { - case VariableExpression(_) => "V" - case IndexedExpression(_, LiteralExpression(_, _)) => "K" - case IndexedExpression(_, VariableExpression(_)) => "J" - case IndexedExpression(_, _) => "I" - case _ => "A" - } - } - } + val sortedParams = params.sortBy { expr => simplicity(ctx.env, expr) } val h = sortedParams.head val firstParamCompiled = ExpressionCompiler.compile(ctx, h, Some(b -> RegisterVariable(Register.A, b)), NoBranching) @@ -577,11 +565,13 @@ object BuiltIns { val env = ctx.env val b = env.get[Type]("byte") val lhsIsDirectlyIncrementable = v match { - case _:VariableExpression => true - case IndexedExpression(pointy, _) => env.getPointy(pointy) match { - case _:ConstantPointy => true - case _:VariablePointy => false - } + case _: VariableExpression => true + case IndexedExpression(pointy, indexExpr) => + val indexerSize = getIndexerSize(ctx, indexExpr) + indexerSize <= 1 && (env.getPointy(pointy) match { + case _: ConstantPointy => true + case _: VariablePointy => false + }) case _ => false } env.eval(addend) match { @@ -616,6 +606,10 @@ object BuiltIns { } } + private def getIndexerSize(ctx: CompilationContext, indexExpr: Expression) = { + ctx.env.evalVariableAndConstantSubParts(indexExpr)._1.map(v => ExpressionCompiler.getExpressionType(ctx, v)).size + } + def compileInPlaceWordOrLongAddition(ctx: CompilationContext, lhs: LhsExpression, addend: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = { if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode)) { ErrorReporting.warn("Unsupported decimal operation", ctx.options, lhs.position) diff --git a/src/main/scala/millfork/compiler/ExpressionCompiler.scala b/src/main/scala/millfork/compiler/ExpressionCompiler.scala index 7f3050dc..33fe541c 100644 --- a/src/main/scala/millfork/compiler/ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/ExpressionCompiler.scala @@ -209,6 +209,34 @@ object ExpressionCompiler { } } + def prepareWordIndexing(ctx: CompilationContext, pointy: Pointy, indexExpression: Expression): List[AssemblyLine] = { + val w = ctx.env.get[Type]("word") + if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + ErrorReporting.error("16-bit indexing requires a zeropage pseudoregister") + compile(ctx, indexExpression, Some(w -> RegisterVariable(Register.YA, w)), BranchSpec.None) + return Nil + } + val reg = ctx.env.get[VariableInMemory]("__reg") + val compileIndex = compile(ctx, indexExpression, Some(w -> RegisterVariable(Register.YA, w)), BranchSpec.None) + val prepareRegister = pointy match { + case ConstantPointy(addr, _) => + List( + AssemblyLine.implied(CLC), + AssemblyLine.immediate(ADC, addr.hiByte), + AssemblyLine.zeropage(STA, reg, 1), + AssemblyLine.immediate(LDA, addr.loByte), + AssemblyLine.zeropage(STA, reg)) + case VariablePointy(addr) => + List( + AssemblyLine.implied(CLC), + AssemblyLine.zeropage(ADC, addr + 1), + AssemblyLine.zeropage(STA, reg, 1), + AssemblyLine.zeropage(LDA, addr), + AssemblyLine.zeropage(STA, reg)) + } + compileIndex ++ prepareRegister + } + def compileByteStorage(ctx: CompilationContext, register: Register.Value, target: LhsExpression): List[AssemblyLine] = { val env = ctx.env val b = env.get[Type]("byte") @@ -253,6 +281,8 @@ object ExpressionCompiler { 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 @@ -274,13 +304,40 @@ object ExpressionCompiler { } } } - (pointy, variableIndex) match { - case (p: ConstantPointy, None) => + def wrapWordIndexingStorage(code: List[AssemblyLine]) = { + val reg = ctx.env.get[VariableInMemory]("__reg") + val cmos = ctx.options.flag(CompilationFlag.EmitCmosOpcodes) + register match { + case Register.A => + List(AssemblyLine.implied(PHA)) ++ code ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg)) + case Register.X => + if (code.exists(l => OpcodeClasses.ChangesX(l.opcode))) { + if (cmos) + List(AssemblyLine.implied(PHX)) ++ code ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg)) + else + List(AssemblyLine.implied(TXA), AssemblyLine.implied(PHA)) ++ code ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg)) + } else { + code ++ List(AssemblyLine.implied(TXA), AssemblyLine.indexedY(STA, reg)) + } + case Register.Y => + if (cmos) + List(AssemblyLine.implied(PHY)) ++ code ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg)) + else + List(AssemblyLine.implied(TYA), AssemblyLine.implied(PHA)) ++ code ++ List(AssemblyLine.implied(PLA), AssemblyLine.indexedY(STA, reg)) + } + } + + (pointy, variableIndex, variableIndexSize, totalIndexSize) match { + case (p: ConstantPointy, None, _, _) => List(AssemblyLine.absolute(store, env.genRelativeVariable(p.value + constIndex, b, zeropage = false))) - case (p: ConstantPointy, Some(v)) => + case (p: VariablePointy, _, _, 2) => + wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr)) + case (p: ConstantPointy, Some(v), 2, _) => + wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, if (constIndex.isProvablyZero) p.size else None), v)) + case (p: ConstantPointy, Some(v), 1, _) => storeToArrayAtUnknownIndex(v, p.value) //TODO: should there be a type check or a zeropage check? - case (pointerVariable:VariablePointy, None) => + case (pointerVariable:VariablePointy, None, _, 0 | 1) => register match { case Register.A => List(AssemblyLine.immediate(LDY, constIndex), AssemblyLine.indexedY(STA, pointerVariable.addr)) @@ -292,7 +349,7 @@ object ExpressionCompiler { ErrorReporting.error("Cannot store a word in an array", target.position) Nil } - case (pointerVariable:VariablePointy, Some(_)) => + case (pointerVariable:VariablePointy, Some(_), _, 0 | 1) => val calculatingIndex = compile(ctx, indexExpr, Some(b, RegisterVariable(Register.Y, b)), NoBranching) register match { case Register.A => @@ -307,6 +364,9 @@ object ExpressionCompiler { ErrorReporting.error("Cannot store a word in an array", target.position) Nil } + case _ => + ErrorReporting.error("Invalid index for writing", indexExpr.position) + Nil } } } @@ -570,6 +630,8 @@ object ExpressionCompiler { val pointy = env.getPointy(arrayName) // 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(noop) { case (exprType, target) => val register = target match { @@ -609,12 +671,27 @@ object ExpressionCompiler { } } - val result = (pointy, variableIndex) match { - case (a: ConstantPointy, None) => + def loadFromReg() = { + val reg = ctx.env.get[VariableInMemory]("__reg") + register match { + case Register.A => + List(AssemblyLine.indexedY(LDA, reg)) + case Register.X => + List(AssemblyLine.indexedY(LDX, reg)) + case Register.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)) => + case (a: ConstantPointy, Some(v), _, 1) => loadFromArrayAtUnknownIndex(v, a.value) - case (p:VariablePointy, None) => + case (a: ConstantPointy, Some(v), _, 2) => + prepareWordIndexing(ctx, ConstantPointy(a.value + constantIndex, if (constantIndex.isProvablyZero) a.size else None), v) ++ loadFromReg() + case (a: VariablePointy, _, 2, _) => + prepareWordIndexing(ctx, a, indexExpr) ++ loadFromReg() + case (p:VariablePointy, None, 0 | 1, _) => register match { case Register.A => List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedY(LDA, p.addr)) @@ -623,7 +700,7 @@ object ExpressionCompiler { case Register.X => List(AssemblyLine.immediate(LDY, constantIndex), AssemblyLine.indexedY(LDX, p.addr)) } - case (p:VariablePointy, Some(_)) => + case (p:VariablePointy, Some(_), 0 | 1, _) => val calculatingIndex = compile(ctx, indexExpr, Some(b, RegisterVariable(Register.Y, b)), NoBranching) register match { case Register.A => @@ -633,6 +710,9 @@ object ExpressionCompiler { case Register.Y => calculatingIndex ++ List(AssemblyLine.indexedY(LDA, p.addr), AssemblyLine.implied(TAY)) } + case _ => + ErrorReporting.error("Invalid index for reading", indexExpr.position) + Nil } register match { case Register.A | Register.X | Register.Y => result ++ suffix @@ -1116,7 +1196,7 @@ object ExpressionCompiler { exprTypeAndVariable.fold(noop) { case (VoidType, _) => ??? case (_, RegisterVariable(Register.A, _)) => noop - case (_, RegisterVariable(Register.AW, _)) => List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAX), AssemblyLine.implied(XBA)) + case (_, RegisterVariable(Register.AW, _)) => List(AssemblyLine.implied(XBA), AssemblyLine.implied(TXA), AssemblyLine.implied(XBA)) case (_, RegisterVariable(Register.X, _)) => List(AssemblyLine.implied(TAX)) case (_, RegisterVariable(Register.Y, _)) => List(AssemblyLine.implied(TAY)) case (_, RegisterVariable(Register.AX, _)) => diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index a16c13e3..8509776f 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -20,6 +20,7 @@ import millfork.error.ErrorReporting import millfork.node.Position sealed trait Constant { + def isProvablyZero: Boolean = false def asl(i: Constant): Constant = i match { case NumericConstant(sa, _) => asl(sa.toInt) @@ -74,6 +75,7 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant { throw new IllegalArgumentException("The constant is too big") } } + override def isProvablyZero: Boolean = value == 0 override def isLowestByteAlwaysEqual(i: Int) : Boolean = (value & 0xff) == (i&0xff) diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 7d6a236d..e923d4a7 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -239,6 +239,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } def evalVariableAndConstantSubParts(e: Expression): (Option[Expression], Constant) = + // TODO: prevent accidental negative indexing more robustly e match { case SumExpression(params, false) => val (constants, variables) = params.map { case (sign, expr) => (sign, expr, eval(expr)) }.partition(_._3.isDefined) @@ -248,7 +249,37 @@ class Environment(val parent: Option[Environment], val prefix: String) { case List((false, x, _)) => Some(x) case _ => Some(SumExpression(variables.map(x => (x._1, x._2)), decimal = false)) } - variable -> constant + variable match { + case None => variable -> constant + case Some(x@VariableExpression(v)) => + if (get[Variable](v).typ.isSigned) Some(FunctionCallExpression("^", List(x, LiteralExpression(0x80, 1)))) -> (constant - 128).quickSimplify + else variable -> constant + case Some(IndexedExpression(_, _)) => variable -> constant + case Some(LiteralExpression(_, _)) => variable -> constant + case Some(SumExpression(List(negative@(true, _)), false)) => + Some(SumExpression(List(false -> LiteralExpression(0xff, 1), negative), decimal = false)) -> (constant - 255).quickSimplify + case Some(FunctionCallExpression( + "<<" | ">>" | + "<<'" | ">>'" | + "&" | "|" | "^" | + ">>>>" | "<<<<" | + "*" | "*'", _)) => variable -> constant + case Some(FunctionCallExpression(fname, _)) => + maybeGet[Thing](fname) match { + case Some(ff: MangledFunction) => + if (ff.returnType.isSigned) Some(e) -> Constant.Zero + else variable -> constant + case Some(t: Type) => + if (t.isSigned) Some(e) -> Constant.Zero + else variable -> constant + case _ => + // dunno what to do + Some(e) -> Constant.Zero + } + case _ => + // dunno what to do + Some(e) -> Constant.Zero + } case _ => eval(e) match { case Some(c) => None -> c case None => Some(e) -> Constant.Zero diff --git a/src/test/scala/millfork/test/ArraySuite.scala b/src/test/scala/millfork/test/ArraySuite.scala index bb3bb787..289bad57 100644 --- a/src/test/scala/millfork/test/ArraySuite.scala +++ b/src/test/scala/millfork/test/ArraySuite.scala @@ -184,4 +184,64 @@ class ArraySuite extends FunSuite with Matchers { """.stripMargin) } + + test("Negative subindex") { + val m = EmuUnoptimizedRun( + """ + | + | array output [$fff] @$c000 + | void main () { + | byte i + | output[$100] = 55 + | i = one() + | output[1 - i] = 5 + | } + | noinline byte one() {return 1} + """.stripMargin) + m.readByte(0xc100) should equal(55) + m.readByte(0xc000) should equal(5) + + } + + test("Word subindex 1") { + EmuBenchmarkRun( + """ + | + | array output [$fff] @$c000 + | void main () { + | word i + | i = big() + | output[$64] = 55 + | output[i] = 6 + | output[i] = 5 + | } + | noinline word big() {return $564} + """.stripMargin) {m => + m.readByte(0xc064) should equal(55) + m.readByte(0xc564) should equal(5) + } + + } + + test("Word subindex 2") { + EmuBenchmarkRun( + """ + | + | array output [$fff] @$c000 + | void main () { + | word i + | pointer o + | o = p() + | i = big() + | o[$164] = 55 + | o[i] = 5 + | } + | noinline word big() {return $564} + | noinline word p() {return output.addr} + """.stripMargin) {m => + m.readByte(0xc164) should equal(55) + m.readByte(0xc564) should equal(5) + } + + } }