diff --git a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala index 971b9868..f5b367db 100644 --- a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala +++ b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala @@ -411,7 +411,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte val b = env.get[Type]("byte") var ok = true var result = optimizeExpr(root, currentVarValues).pos(pos) - def applyIndex(result: Expression, index: Expression, guaranteedSmall: Boolean): Expression = { + def applyIndex(result: Expression, index: Expression, smallArraySizeInBytes: Option[Int]): Expression = { AbstractExpressionCompiler.getExpressionType(env, env.log, result) match { case pt@PointerType(_, _, Some(targetType)) => val zero = env.eval(index) match { @@ -424,17 +424,43 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte DerefExpression(result, 0, targetType) } else { val indexType = AbstractExpressionCompiler.getExpressionType(env, env.log, index) + val (newResult, constantOffset) = result match { + case FunctionCallExpression(pType, List(SumExpression(List((false, newResult), (false, LiteralExpression(offset, _))), false))) + if offset >= 0 && offset < 100 && env.maybeGet[VariableType](pType).exists(_.isPointy) => + (pType <| newResult) -> offset.toInt + case _ => result -> 0 + } env.eval(index) match { - case Some(NumericConstant(n, _)) if n >= 0 && (guaranteedSmall || (targetType.alignedSize * n) <= 127) => - DerefExpression( - ("pointer." + targetType.name) <| result, - targetType.alignedSize * n.toInt, targetType) + case Some(NumericConstant(n, _)) if n >= 0 && (smallArraySizeInBytes.isDefined || (targetType.alignedSize * n) <= 127) => + if (constantOffset + (targetType.alignedSize * n) <= 127) { + DerefExpression( + ("pointer." + targetType.name) <| newResult, + constantOffset + targetType.alignedSize * n.toInt, targetType) + } else { + DerefExpression( + ("pointer." + targetType.name) <| result, + targetType.alignedSize * n.toInt, targetType) + } case _ => - val small = guaranteedSmall || (indexType.size == 1 && !indexType.isSigned) - val scaledIndex: Expression = scaleIndexForArrayAccess(index, targetType, if (small) Some(256) else None) - DerefExpression(("pointer." + targetType.name) <| ( - ("pointer" <| result) #+# optimizeExpr(scaledIndex, Map()) - ), 0, targetType) + val small = smallArraySizeInBytes.isDefined || (indexType.size == 1 && !indexType.isSigned) + smallArraySizeInBytes match { + case Some(sz) if constantOffset != 0 && sz + constantOffset <= 255 => + val scaledIndex: Expression = scaleIndexForArrayAccess(index, targetType, smallArraySizeInBytes) + var scaledIndexWithOffset = optimizeExpr(scaledIndex #+# LiteralExpression(constantOffset, 1), Map()) + val cpuFamily = ctx.options.platform.cpuFamily + if ((cpuFamily == CpuFamily.M6502 || cpuFamily == CpuFamily.I80) + && AbstractExpressionCompiler.getExpressionType(ctx, scaledIndexWithOffset).size == 1) { + scaledIndexWithOffset = "byte" <| scaledIndexWithOffset + } + DerefExpression(("pointer." + targetType.name) <| ( + ("pointer" <| newResult) #+# scaledIndexWithOffset + ), 0, targetType) + case _ => + val scaledIndex: Expression = scaleIndexForArrayAccess(index, targetType, smallArraySizeInBytes) + DerefExpression(("pointer." + targetType.name) <| ( + ("pointer" <| result) #+# optimizeExpr(scaledIndex, Map()) + ), 0, targetType) + } } } case x if x.isPointy => @@ -473,9 +499,9 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte } for (index <- firstIndices) { - result = applyIndex(result, index, guaranteedSmall = false) + result = applyIndex(result, index, smallArraySizeInBytes = None) } - var guaranteedSmall = false + var currentArraySizeInBytes = Option.empty[Int] for ((dot, fieldName, indices) <- fieldPath) { if (dot && ok) { val pointer = result match { @@ -525,7 +551,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte val offsetExpression = LiteralExpression(fieldOffset, 2).pos(pos) subvariable.arrayIndexTypeAndSize match { case Some((indexType, arraySize)) => - guaranteedSmall = arraySize * target.alignedSize <= 256 + currentArraySizeInBytes = Some(arraySize * subvariable.typ.alignedSize).filter(_ <= 256) pointerWrap match { case 0 | 1 => if (fieldOffset == 0) { @@ -554,7 +580,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte case _ => throw new IllegalStateException } case None => - guaranteedSmall = false + currentArraySizeInBytes = None pointerWrap match { case 0 => DerefExpression(inner, fieldOffset, fieldType) @@ -601,8 +627,8 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte } if (ok) { for (index <- indices) { - result = applyIndex(result, index, guaranteedSmall) - guaranteedSmall = false + result = applyIndex(result, index, currentArraySizeInBytes) + currentArraySizeInBytes = None } } } diff --git a/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala b/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala index e089b36b..9d94a046 100644 --- a/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala @@ -1,13 +1,12 @@ package millfork.compiler.m6809 import java.util.concurrent.AbstractExecutorService - import millfork.CompilationFlag import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, Indexed, InherentB, MLine, MLine0, MOpcode, RegisterSet, TwoRegisters} import millfork.compiler.{AbstractExpressionCompiler, BranchIfFalse, BranchIfTrue, BranchSpec, ComparisonType, CompilationContext, NoBranching} import millfork.node.{DerefExpression, Expression, FunctionCallExpression, GeneratedConstantExpression, IndexedExpression, LhsExpression, LiteralExpression, M6809Register, SeparateBytesExpression, SumExpression, VariableExpression} import millfork.assembly.m6809.MOpcode._ -import millfork.env.{AssemblyOrMacroParamSignature, BuiltInBooleanType, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FlagBooleanType, FunctionInMemory, FunctionPointerType, KernalInterruptPointerType, Label, M6809RegisterVariable, MacroFunction, MathOperator, MemoryAddressConstant, MemoryVariable, NonFatalCompilationException, NormalFunction, NormalParamSignature, NumericConstant, StackOffsetThing, StackVariable, StackVariablePointy, StructureConstant, Thing, ThingInMemory, Type, Variable, VariableInMemory, VariableLikeThing, VariablePointy} +import millfork.env.{AssemblyOrMacroParamSignature, BuiltInBooleanType, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FlagBooleanType, FunctionInMemory, FunctionPointerType, KernalInterruptPointerType, Label, M6809RegisterVariable, MacroFunction, MathOperator, MemoryAddressConstant, MemoryVariable, NonFatalCompilationException, NormalFunction, NormalParamSignature, NumericConstant, StackOffsetThing, StackVariable, StackVariablePointy, StructureConstant, Thing, ThingInMemory, Type, Variable, VariableInMemory, VariableLikeThing, VariablePointy, VariableType} import scala.collection.GenTraversableOnce @@ -46,6 +45,21 @@ import MExpressionTarget.toLd object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] { + def extractConstantOffset(ctx: CompilationContext, expr: Expression): (Expression, Int) = { + expr match { + case FunctionCallExpression(pType, List(e)) + if ctx.env.maybeGet[VariableType](pType).exists(_.isPointy) => + var (i, o) = extractConstantOffset(ctx, e) + FunctionCallExpression(pType, List(i)) -> o + case SumExpression(List((false, i), (false, LiteralExpression(o, 1|2))), false) if o >= 0 && o < 127 => + i -> o.toInt + case SumExpression(List((false, i1), (false, i2), (false, LiteralExpression(o, 1|2))), false) if o >= 0 && o < 127 => + (i1 #+# i2) -> o.toInt + case _ => + expr -> 0 + } + } + def compile(ctx: CompilationContext, expr: Expression, target: MExpressionTarget.Value, branches: BranchSpec = BranchSpec.None): List[MLine] = try { val env = ctx.env val exprType = getExpressionType(ctx, expr) @@ -104,13 +118,16 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] { case _ => List(MLine.immediate(MExpressionTarget.toLd(target), NumericConstant(c, MExpressionTarget.size(target)))) } case DerefExpression(inner, offset, _) => - compileToX(ctx, inner) match { + val (i, o) = if (offset == 0) { + extractConstantOffset(ctx, inner) + } else (inner, offset) + compileToX(ctx, i) match { case List(l@MLine0(LDX, Immediate, _)) => - List(l.copy(opcode = toLd(target), addrMode = Absolute(false), parameter = l.parameter + offset)) - case List(l@MLine0(LDX, addrMode, _)) if addrMode.isDeferenceable && offset == 0 => + List(l.copy(opcode = toLd(target), addrMode = Absolute(false), parameter = l.parameter + o)) + case List(l@MLine0(LDX, addrMode, _)) if addrMode.isDeferenceable && o == 0 => List(l.copy(opcode = toLd(target), addrMode = addrMode.dereference())) - case _ => - compileToX(ctx, inner) :+ MLine(toLd(target), Indexed(M6809Register.X, indirect = false), NumericConstant(offset, 2)) + case other => + other :+ MLine(toLd(target), Indexed(M6809Register.X, indirect = false), NumericConstant(o, 2)) } case IndexedExpression(name, index) => env.getPointy(name) match { diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index b53c58d1..ea957ba3 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -435,8 +435,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner) if (targetType.size == 1) { fast match { - case Some((fastBase, fastIndex)) => - return preserveRegisterIfNeeded(ctx, MosRegister.A, fastIndex(offset)) ++ List(AssemblyLine.absoluteY(STA, fastBase)) + case Some((fastBase, fastAddrMode, fastIndex)) => + return preserveRegisterIfNeeded(ctx, MosRegister.A, fastIndex(offset)) ++ List(AssemblyLine(STA, fastAddrMode, fastBase)) case _ => } } @@ -560,13 +560,13 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { 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, Int => List[AssemblyLine])]) = { + 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.get[ThingInMemory](name) p match { case array: MfArray => return (Nil, p.toAddress, AddrMode.AbsoluteY, - if (array.sizeInBytes <= 256) Some(p.toAddress, (i: Int) => List(AssemblyLine.immediate(LDY, i))) else None) + if (array.sizeInBytes <= 256) Some((p.toAddress, AbsoluteY, (i: Int) => List(AssemblyLine.immediate(LDY, i)))) else None) case _ => } if (p.zeropage) return (Nil, p.toAddress, AddrMode.IndexedY, None) @@ -581,17 +581,39 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { Some(base -> index) case _ => None } - (compileToZReg(ctx, pointerExpression), ctx.env.get[ThingInMemory]("__reg.loword").toAddress, AddrMode.IndexedY, baseAndIndex match { + 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).map { baseConst => - baseConst -> { (i: Int) => - val b = ctx.env.get[Type]("byte") - compileToY(ctx, index #+# i) - } + 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 @@ -1106,18 +1128,26 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case DerefExpression(inner, offset, targetType) => val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner) (fast, targetType.size, am) match { - case (Some((fastBase, fastIndex)), 1, _) => - fastIndex(offset) ++ List(AssemblyLine.absoluteY(LDA, fastBase)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned) + case (Some((fastBase, fastAddrMode, fastIndex)), 1, _) => + fastIndex(offset) ++ List(AssemblyLine(LDA, fastAddrMode, fastBase)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned) case (_, 1, AbsoluteY) => prepare ++ List(AssemblyLine.absolute(LDA, addr + offset)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned) case (_, 1, _) => prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned) - case (Some((fastBase, fastIndex)), 2, _) => + case (Some((fastBase, AbsoluteY, fastIndex)), 2, _) => fastIndex(offset) ++ List( AssemblyLine.absoluteY(LDA, fastBase), AssemblyLine.implied(INY), AssemblyLine.absoluteY(LDX, fastBase)) ++ expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position) + case (Some((fastBase, fastAddrMode, fastIndex)), 2, _) => + fastIndex(offset) ++ List( + AssemblyLine.implied(INY), + AssemblyLine(LDA, fastAddrMode, fastBase), + AssemblyLine.implied(TAX), + AssemblyLine.implied(DEY), + AssemblyLine(LDA, fastAddrMode, fastBase)) ++ + expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position) case (_, 2, AbsoluteY) => prepare ++ List( @@ -2189,12 +2219,12 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { AssemblyLine.absolute(STA, addr + offset + i))) case _ => fastTarget match { - case Some((constAddr, initializeY)) => + 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.absoluteY(STA, constAddr)) else List( + load ++ (if (i == 0) List(AssemblyLine(STA, fastAddrMode, constAddr)) else List( AssemblyLine.implied(INY), - AssemblyLine.absoluteY(STA, constAddr))) + AssemblyLine(STA, fastAddrMode, constAddr))) } case _ => prepare ++ (0 until targetType.size).flatMap(i => List( @@ -2225,12 +2255,12 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { } case (_, _) => fastTarget match { - case Some((constAddr, initializeY)) => + case Some((constAddr, fastAddrMode, initializeY)) => initializeY(offset) ++ (0 until targetType.size).flatMap { i => val load = if (i >= sourceType.size) List(AssemblyLine.immediate(LDA, 0)) else AssemblyLine.variable(ctx, LDA, variable, i) - load ++ (if (i == 0) List(AssemblyLine.absoluteY(STA, constAddr)) else List( + load ++ (if (i == 0) List(AssemblyLine(STA, fastAddrMode, constAddr)) else List( AssemblyLine.implied(INY), - AssemblyLine.absoluteY(STA, constAddr))) + AssemblyLine(STA, fastAddrMode, constAddr))) } case _ => prepare ++ (0 until targetType.size).flatMap { i => @@ -2375,14 +2405,14 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case (2, _) => val someTuple = Some(targetType, RegisterVariable(MosRegister.AX, targetType)) fastTarget match { - case Some((baseOffset, initializeY)) => + case Some((baseOffset, fastAddrMode, initializeY)) => compile(ctx, source, someTuple, BranchSpec.None) ++ preserveRegisterIfNeeded(ctx, MosRegister.AX, initializeY(offset)) ++ List( - AssemblyLine.absoluteY(STA, baseOffset), + AssemblyLine(STA, fastAddrMode, baseOffset), AssemblyLine.implied(TXA), AssemblyLine.implied(INY), - AssemblyLine.absoluteY(STA, baseOffset)) + AssemblyLine(STA, fastAddrMode, baseOffset)) case _ => if (prepare.isEmpty) { compile(ctx, source, someTuple, BranchSpec.None) ++ List( diff --git a/src/test/scala/millfork/test/StructSuite.scala b/src/test/scala/millfork/test/StructSuite.scala index 95328481..427fbffc 100644 --- a/src/test/scala/millfork/test/StructSuite.scala +++ b/src/test/scala/millfork/test/StructSuite.scala @@ -346,4 +346,18 @@ class StructSuite extends FunSuite with Matchers { | } |""".stripMargin) } + + test("Structs with array fields – performance") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086, Cpu.Motorola6809)( + """ + | struct S { byte header, array data[20] } + | S dummy @$c000 + | noinline byte f(pointer.S s, byte i) { + | return s[0].data[i] + | } + | void main() { + | f(dummy.pointer, 5) + | } + |""".stripMargin){m => } + } }